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

在我的代码中,我正在创建一个对象集合,这些对象将由各种线程以一种只有在对象不可变时才安全的方式访问。当尝试将新对象插入到我的集合中时,我想测试它是否不可变(如果没有,我将抛出一个异常)。

我能做的一件事是检查一些众所周知的不可变类型:

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%的方法,但有时我的用户会想要创建自己的简单不可变类型:

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; }
}

有没有办法(也许使用反射)可以可靠地检测一个类是否不可变?误报(认为它是不可变的,当它不是)是不可接受的,但假阴性(认为它是可变的,当它不是)是。

编辑以添加:感谢您的有见地和有用的答案。正如一些答案所指出的那样,我忽略了定义我的安全目标。这里的威胁是无能为力的开发人员 - 这是一段框架代码,将被大量对线程几乎一无所知并且不会阅读文档的人使用。我不需要防御恶意开发人员 - 任何聪明到足以改变String或执行其他恶作剧的人也会足够聪明,知道在这种情况下它不安全。代码库的静态分析是一种选择,只要它是自动化的,但是代码审查不能指望,因为不能保证每次审查都会有精通线程的审查者。


答案 1

没有可靠的方法来检测类是否不可变。这是因为有很多方法可以更改类的属性,并且您无法通过反射检测到所有这些方法。

接近这一点的唯一方法是:

  • 只允许不可变类型的最终属性(基元类型和您知道是不可变的类),
  • 要求类本身是最终的
  • 要求它们从您提供的基类继承(保证不可变)

然后,您可以使用以下代码检查您拥有的对象是否不可变:

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);
}

更新:正如注释中所建议的那样,它可以扩展到在超类上递归,而不是检查某个类。还建议在 isValidFieldType 方法中递归使用 isImmutable。这可能有效,我也做了一些测试。但这并不是微不足道的。您不能只通过调用 isImmutable 来检查所有字段类型,因为 String 已未通过此测试(其字段不是最终字段!此外,您也很容易遇到无休止的递归,导致StackOverflowErrors;)其他问题可能是由泛型引起的,您还必须检查它们的类型是否不可思议。hash

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


答案 2

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