如何在Java中识别不可变对象

在我的代码中,我创build了一个对象的集合,这些对象将以各种线程访问,只有当对象是不可变的时候才是安全的。 当试图插入一个新的对象到我的集合,我想testing,看看它是不可变的(如果没有,我会抛出一个exception)。

我可以做的一件事是检查一些众所周知的不可变types:

private static final Set<Class> knownImmutables = new HashSet<Class>(Arrays.asList( String.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Boolean.class, BigInteger.class, BigDecimal.class )); ... public static boolean isImmutable(Object o) { return knownImmutables.contains(o.getClass()); } 

这实际上让我有90%的方式,但有时我的用户会想要创build自己的简单的不可变types:

 public class ImmutableRectangle { private final int width; private final int height; public ImmutableRectangle(int width, int height) { this.width = width; this.height = height; } public int getWidth() { return width; } public int getHeight() { return height; } } 

有没有什么方法(也许使用reflection),我可以可靠地检测一个类是不可变的? 错误的肯定(当它不是不可接受的时候,认为它是不可变的)是不可接受的,但是错误的否定(当它不是可变的时候,它是可变的)。

编辑补充:谢谢你的洞察力和有益的答案。 正如其中一些答案指出的那样,我忽略了定义我的安全目标。 这里的威胁是无知的开发人员 – 这是一个框架代码,将被大量的人知道线程,而不会阅读文档。 我不需要防御恶意开发者 – 任何聪明的人都可以改变一个string或者执行其他的恶意代码,但是在这种情况下,也会非常聪明地知道这是不安全的。 代码库的静态分析是一个选项,只要它是自动化的,但代码评论是不能被计算的,因为不能保证每一个评论都会有线程化的评论者。

没有可靠的方法来检测一个类是不可变的。 这是因为有太多的方法可能会改变一个类的属性,并且你不能通过reflection来检测它们。

接近这个的唯一方法是:

  • 只允许不可变types的最终属性(基本types和类,你知道是不可变的),
  • 要求class级本身是最终的
  • 要求他们从你提供的基类inheritance(这是保证是不可变的)

然后你可以检查下面的代码,如果你拥有的对象是不可变的:

 static boolean isImmutable(Object obj) { Class<?> objClass = obj.getClass(); // Class of the object must be a direct child class of the required class Class<?> superClass = objClass.getSuperclass(); if (!Immutable.class.equals(superClass)) { return false; } // Class must be final if (!Modifier.isFinal(objClass.getModifiers())) { return false; } // Check all fields defined in the class for type and if they are final Field[] objFields = objClass.getDeclaredFields(); for (int i = 0; i < objFields.length; i++) { if (!Modifier.isFinal(objFields[i].getModifiers()) || !isValidFieldType(objFields[i].getType())) { return false; } } // Lets hope we didn't forget something return true; } static boolean isValidFieldType(Class<?> type) { // Check for all allowed property types... return type.isPrimitive() || String.class.equals(type); } 

更新:正如评论中所build议的那样,它可以扩展到超类的recursion而不是检查某个类。 还build议在isValidFieldType方法中recursion使用isImmutable。 这可能工作,我也做了一些testing。 但这不是微不足道的。 你不能只通过调用isImmutable来检查所有的字段types,因为String已经失败了这个testing(它的字段hash不是最终的!)。 此外,你很容易遇到无尽的recursion,导致StackOverflowErrors ;)其他问题可能是由generics引起的,你也必须检查他们的types不可变性。

我想通过一些工作,这些潜在的问题可能以某种方式得到解决。 但是,那么你必须首先问问自己是否真的值得(也是性能方面)。

在Java实践中使用来自Java Concurrency的Immutable注释。 然后, FindBugs工具可以帮助检测可变但不应该是的类。

在我的公司,我们定义了一个名为@Immutable的属性。 如果你select把它附加到一个类上,这意味着你保证你是不可改变的。

它适用于文档,在你的情况下,它将作为一个filter。

当然,你仍然依赖于作者坚持不变的说法,但是由于作者明确地添加了注释,这是一个合理的假设。

基本上没有。

你可以构build一个可接受的类的白名单,但我认为不太疯狂的方法是只写在集合的文档中,这个集合的一切都是不可变的。

编辑:其他人build议有一个不可变的注释。 这很好,但你也需要文档。 否则,人们只会认为“如果我把这个注释放在我的课堂上,我可以把它存储在集合中”,并且只是把它放在任何东西上,不可变和可变的类。 事实上,如果人们认为注释使得它们的类不可变,我将会持有不可变的注释。

这可能是另一个提示:

如果这个类没有setter,那么它就不能被改变,赋予它创build的参数是“原始”types的,或者它们本身是不可变的。

也没有办法可以被重写,所有的领域是最终的和私人的,

我会尝试为你明天编写代码,但是使用reflection的Simon代码看起来相当不错。

同时尝试抓取Josh Block的“Effective Java”一书的副本,它有一个与此主题相关的项目。 虽然不能肯定地说如何检测一个不可修改的类,它显示了如何创build一个好的类。

该项目被称为:“赞成不变性”

链接: http : //java.sun.com/docs/books/effective/

在我的代码中,我创build了一个对象的集合,这些对象将以各种线程访问,只有当对象是不可变的时候才是安全的。

不是你的问题的直接答案,但请记住,不可变的对象不能自动保证线程安全(可悲)。 代码必须是无副作用的,才能保证线程安全,这是相当困难的。

假设你有这个类:

 class Foo { final String x; final Integer y; ... public bar() { Singleton.getInstance().foolAround(); } } 

然后, foolAround()方法可能会包含一些非线程安全的操作,这将炸毁你的应用程序。 而使用reflection来testing这个是不可能的,因为实际的引用只能在方法体中find,而不能在字段或暴露的接口中find。

除此之外,别人是正确的:你可以扫描类的所有声明的领域,检查他们每个人是否是最终的,也是一个不可变的类,你就完成了。 我不认为方法是最终的要求。

此外,请注意recursion检查依赖字段的不变性,最终可能会出现圆圈:

 class A { final B b; // might be immutable... } class B { final A a; // same so here. } 

类A和B是完全不可变的(甚至可能通过一些reflection攻击可用),但幼稚的recursion代码将进入一个无限循环检查A,然后B,然后再A,然后到B,…

你可以用一个不允许循环的'看过的'映射来修复它,或者用一些非常聪明的代码来决定类是不可变的,如果它们的所有依赖只依赖于它们是不可改变的,那将会非常复杂。

您可以要求您的客户添加元数据(注释),并在运行时用reflection来检查它们,如下所示:

元数据:

 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.CLASS) public @interface Immutable{ } 

客户代码:

 @Immutable public class ImmutableRectangle { private final int width; private final int height; public ImmutableRectangle(int width, int height) { this.width = width; this.height = height; } public int getWidth() { return width; } public int getHeight() { return height; } } 

然后通过对类的reflection,检查它是否有注释(我会粘贴代码,但它的样板,可以很容易地在网上find)

为什么所有的build议都要求class级是最终的? 如果您使用reflection来检查每个对象的类,并且可以通过编程方式确定该类是不可变的(不可变的,final字段),那么您不需要要求该类本身是最终的。

您可以使用jcabi方面的 AOP和@Immutable注释:

 @Immutable public class Foo { private String data; } // this line will throw a runtime exception since class Foo // is actually mutable, despite the annotation Object object = new Foo(); 

就像其他答复者已经说过的,恕我直言,没有可靠的方法来找出一个对象是否真的不可变。

我只是介绍一个接口“不可变”来检查时,附加。 这可以作为一个提示,只要不pipe不可变的对象是否因为什么原因而被插入。

 interface Immutable {} class MyImmutable implements Immutable{...} public void add(Object o) { if (!(o instanceof Immutable) && !checkIsImmutableBasePrimitive(o)) throw new IllegalArgumentException("o is not immutable!"); ... } 

尝试这个:

 public static boolean isImmutable(Object object){ if (object instanceof Number) { // Numbers are immutable if (object instanceof AtomicInteger) { // AtomicIntegers are mutable } else if (object instanceof AtomicLong) { // AtomLongs are mutable } else { return true; } } else if (object instanceof String) { // Strings are immutable return true; } else if (object instanceof Character) { // Characters are immutable return true; } else if (object instanceof Class) { // Classes are immutable return true; } Class<?> objClass = object.getClass(); // Class must be final if (!Modifier.isFinal(objClass.getModifiers())) { return false; } // Check all fields defined in the class for type and if they are final Field[] objFields = objClass.getDeclaredFields(); for (int i = 0; i < objFields.length; i++) { if (!Modifier.isFinal(objFields[i].getModifiers()) || !isImmutable(objFields[i].getType())) { return false; } } // Lets hope we didn't forget something return true; } 

查看joe-e ,一个java实现的function。

比较高比例的内build类的东西是testing比较。 对于像Date这样不可变的类,在大多数情况下,它们通常被视为不可变的。

据我所知,没有办法确定100%正确的不可变对象。 不过,我写了一个图书馆让你更接近。 它执行一个类的字节码分析,以确定它是否是不可变的,并且可以在运行时执行。 它是严格的,所以它也允许将已知的不可变类join白名单。

你可以在www.mutabilitydetector.org查看

它允许你在你的应用程序中编写这样的代码:

 /* * Request an analysis of the runtime class, to discover if this * instance will be immutable or not. */ AnalysisResult result = analysisSession.resultFor(dottedClassName); if (result.isImmutable.equals(IMMUTABLE)) { /* * rest safe in the knowledge the class is * immutable, share across threads with joyful abandon */ } else if (result.isImmutable.equals(NOT_IMMUTABLE)) { /* * be careful here: make defensive copies, * don't publish the reference, * read Java Concurrency In Practice right away! */ } 

它是Apache 2.0许可下的免费和开放源代码。

我很欣赏和欣赏Grundlefleck在他的可变性探测器上所做的工作量,但我认为这有点矫枉过正。 你可以写一个简单但实​​际上非常充分的(即实用的 )检测器,如下所示:

(注意:这是我的评论的副本: https : //stackoverflow.com/a/28111150/773113 )

首先,你不会只是写一个方法来决定一个类是不是一成不变的; 相反,你将需要写一个不变探测器类,因为它将不得不保持某种状态。 检测器的状态将是迄今为止检测到的所有类别的检测到的不变性。 这不仅对性能有用,而且实际上也是必要的,因为一个类可能包含一个循环引用,这会导致一个简单的不变性检测器陷入无限recursion。

一个类的不变性有四个可能的值: UnknownMutableImmutableCalculating 。 你可能会想要一张地图,把你到目前为止所遇到的每个类都关联到一个不变的值。 当然,“ Unknown ”实际上并不需要实现,因为它将是任何尚未在地图中的类的隐含状态。

因此,当您开始检查课程时,将其与地图中的“ Calculating值相关联,完成后,将“ Calculatingreplace为“ Immutable或“ Mutable

对于每个class级,您只需要检查现场成员,而不是代码。 检查字节码的想法是错误的。

首先,你不应该检查一个class级是否是最终的; 一个类的最终性不影响它的不变性。 相反,一个期望不可变参数的方法应该首先调用不变性检测器来断言传递的实际对象的类的不变性。 如果参数的types是最后一个类,那么这个testing可以省略,所以最终对性能有好处,但是严格来说不是必需的。 同样,你将会看到进一步下去,一个types是非final类的字段会导致声明类被认为是可变的,但是这仍然是声明类的问题,而不是非final的问题不可变的成员类。 拥有高层次的不可变类是完全正确的,其中所有的非叶节点当然都是非最终的。

你不应该检查一个字段是否是私人的; 一个class级拥有一个公共领域是完全不错的,而且这个领域的可见性不会以任何方式,forms或forms影响宣告class级的不变性。 你只需要检查这个字段是否是最终的,它的types是不可变的。

在考察class级时,首先要做的就是进行recursion,以确定class级的不变性。 如果super是可变的,那么后代的定义也是可变的。

然后,你只需要检查类的声明的领域,而不是所有的领域。

如果一个字段是非最终的,那么你的类是可变的。

如果一个字段是最终的,但字段的types是可变的,那么你的类是可变的。 (根据定义,数组是可变的。)

如果一个字段是最终的,并且字段的types是Calculating ,那么忽略它并继续下一个字段。 如果所有的字段都是不可变的或Calculating ,那么你的类是不可变的。

如果字段的types是一个接口,或者一个抽象类,或者一个非final类,那么它将被认为是可变的,因为你完全不能控制实际的实现是什么。 这可能看起来像一个不可逾越的问题,因为这意味着在UnmodifiableCollection中包装一个可修改的集合仍然会失败不可变性testing,但它实际上是好的,可以用下面的解决方法来处理它。

有些类可能包含非final字段,仍然是有效的不可变的 。 这个例子是String类。 属于这个类别的其他类是纯粹用于性能监控(调用计数器等),实现冰棍不可变性的类(查看它)和包含作为已知接口的成员的类的非最终成员的类不会造成任何副作用。 此外,如果一个类包含真正的可变字段,但是在计算hashCode()和equals()时承诺不考虑它们,那么当涉及到multithreading时,该类当然是不安全的,但它仍然可以被认为是不可变的目的是将其用作地图中的关键字。 所以,所有这些情况都可以通过以下两种方式来处理:

  1. 手动添加类(和接口)到你的不变探测器。 如果你知道某个类是不可变的,尽pipe事实上它的不变性testing失败了,你可以手动添加一个条目到你的检测器,它将它与Immutable关联起来。 这样,探测器将永远不会试图检查它是否是不可变的,它总是会说“是的,是的”。

  2. 引入@ImmutabilityOverride注释。 您的不变性检测器可以检查字段上是否存在此注释,如果存在,可能会将该字段视为不可变的,尽pipe该字段可能不是最终字段,或者其types可能是可变的。 探测器也可以检查这个注解是否存在于类中,从而将这个类视为不可变的,而不用麻烦检查它的字段。

我希望这有助于子孙后代。