Pregunta

En mi código, estoy creando una colección de objetos a los que accederán varios hilos de una manera que solo es segura si los objetos son inmutables. Cuando se hace un intento de insertar un nuevo objeto en mi colección, quiero probar si es inmutable (si no, lanzaré una excepción).

Una cosa que puedo hacer es verificar algunos tipos inmutables conocidos:

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

Esto realmente me permite el 90% del camino, pero a veces mis usuarios querrán crear sus propios tipos simples e inmutables:

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

¿Hay alguna forma (quizás utilizando la reflexión) de que pueda detectar de manera confiable si una clase es inmutable? Los falsos positivos (pensar que es inmutable cuando no lo es) no son aceptables, pero los falsos negativos (pensar que es mutable cuando no lo es) son.

Editado para agregar: Gracias por las respuestas intuitivas y útiles. Como indicaron algunas de las respuestas, olvidé definir mis objetivos de seguridad. La amenaza aquí son los desarrolladores despistados: este es un fragmento de código de marco que será utilizado por un gran número de personas que no saben nada acerca de los subprocesos y no leerán la documentación. NO tengo que defenderme contra los desarrolladores maliciosos: cualquiera que sea lo suficientemente inteligente como para mutar un Cadena o realizar otras travesuras también será lo suficientemente inteligente como para saber que no es seguro en este caso. El análisis estático de la base de código ES una opción, siempre que sea automatizado, pero no se puede contar con las revisiones de código porque no hay garantía de que cada revisión tendrá revisores expertos en subprocesos.

¿Fue útil?

Solución

No hay una forma confiable de detectar si una clase es inmutable. Esto se debe a que hay muchas formas en que se puede modificar una propiedad de una clase y no se pueden detectar todas a través de la reflexión.

La única forma de acercarse a esto es:

  • Permitir solo las propiedades finales de los tipos que son inmutables (los tipos y clases primitivas que usted conoce son inmutables),
  • Exigir que la clase sea definitiva en sí misma
  • Exija que se hereden de una clase base que proporcione (que se garantiza que es inmutable)

Luego puedes verificar con el siguiente código si el objeto que tienes es inmutable:

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

Actualización: Como se sugiere en los comentarios, podría extenderse a la clase superior en lugar de verificar una determinada clase. También se sugirió el uso recursivo de isImmutable en el método isValidFieldType. Esto probablemente podría funcionar y también he hecho algunas pruebas. Pero esto no es trivial. Simplemente no puede verificar todos los tipos de campo con una llamada a isImmutable, porque String ya falla esta prueba (su campo hash no es final). También se está ejecutando fácilmente en recursiones sin fin, causando StackOverflowErrors ;) Otros problemas pueden ser causados ??por los genéricos, donde también debe verificar su tipo de inmutabilidad.

Creo que con algo de trabajo, estos problemas potenciales podrían resolverse de alguna manera. Pero luego, primero debes preguntarte si realmente vale la pena (también en cuanto al rendimiento).

Otros consejos

Utilice Anotación inmutable de Concurrencia de Java en la práctica . La herramienta FindBugs puede ayudar a detectar clases que son mutables pero no deberían serlo.

En mi empresa, hemos definido un atributo llamado @Immutable . Si eliges adjuntar eso a una clase, significa que prometes ser inmutable.

Funciona para documentación, y en su caso, funcionaría como un filtro.

Por supuesto, aún depende de que el autor cumpla su palabra de ser inmutable, pero como el autor agregó explícitamente la anotación, es una suposición razonable.

Básicamente no.

Podrías construir una lista blanca gigante de clases aceptadas, pero creo que la forma menos loca sería simplemente escribir en la documentación de la colección que todo lo que dice es que esta colección debe ser inmutable.

Editar: Otras personas han sugerido tener una anotación inmutable. Esto está bien, pero también necesita la documentación. De lo contrario, la gente simplemente pensará " si pongo esta anotación en mi clase puedo almacenarla en la colección " y simplemente lo echará en cualquier cosa, clases inmutables y mutables por igual. De hecho, desconfiaría de tener una anotación inmutable en caso de que la gente piense que la anotación hace su clase inmutable.

Esto podría ser otra pista:

Si la clase no tiene definidores, entonces no se puede mutar, dado que los parámetros con los que se creó son " primitivo " Tipos o no mutables ellos mismos.

Además, no se puede anular ningún método, todos los campos son finales y privados,

Trataré de codificar algo mañana para ti, pero el código de Simon con la reflexión parece bastante bueno.

Mientras tanto, intente obtener una copia de " Effective Java " libro de Josh Block, tiene un artículo relacionado con este tema. Si bien no es seguro cómo detectar una clase inmutable, muestra cómo crear una buena.

El elemento se llama: " Favorecer la inmutabilidad "

enlace: http://java.sun.com/docs/books/effective/

  

En mi código, estoy creando una colección de objetos a los que accederán varios subprocesos de una manera que solo es segura si los objetos son inmutables.

No es una respuesta directa a su pregunta, pero tenga en cuenta que no se garantiza automáticamente que los objetos que son inmutables estén a salvo de los hilos (por desgracia). El código debe estar libre de efectos secundarios para que sea seguro para subprocesos, y eso es un poco más difícil.

Supongamos que tienes esta clase:

class Foo {
  final String x;
  final Integer y;
  ...

  public bar() {
    Singleton.getInstance().foolAround();
  }
}

Luego, el método foolAround () puede incluir algunas operaciones seguras sin subprocesos, que harán explotar tu aplicación. Y no es posible probar esto utilizando la reflexión, ya que la referencia real solo se puede encontrar en el cuerpo del método, no en los campos o la interfaz expuesta.

Aparte de eso, los otros son correctos: puede escanear todos los campos declarados de la clase, verificar si cada uno de ellos es definitivo y también una clase inmutable, y listo. No creo que los métodos finales sean un requisito.

Además, tenga cuidado al verificar recursivamente los campos dependientes para la inmutabilidad, podría terminar con círculos:

class A {
  final B b; // might be immutable...
}

class B {
  final A a; // same so here.
}

Las clases A y B son perfectamente inmutables (y posiblemente incluso se pueden usar a través de algunos hacks de reflexión), pero el código recursivo ingenuo entrará en un bucle sin fin que comprueba A, luego B, luego A nuevamente, en adelante hacia B, ...

Puedes arreglar eso con un mapa 'visto' que no permite ciclos, o con algún código realmente inteligente que decida que las clases son inmutables si todos sus dependientes son inmutables solo dependiendo de ellos mismos, pero eso va a ser realmente complicado ...

Puede pedir a sus clientes que agreguen metadatos (anotaciones) y los comprueben en tiempo de ejecución con una reflexión como esta:

Metadatos:

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

Código del cliente:

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

Luego, utilizando la reflexión en la clase, compruebe si tiene la anotación (yo pegaría el código pero su plantilla y se puede encontrar fácilmente en línea)

¿Por qué todas las recomendaciones requieren que la clase sea definitiva? Si está utilizando la reflexión para verificar la clase de cada objeto, y puede determinar mediante programación que esa clase es inmutable (campos finales inmutables), no es necesario que la clase sea definitiva.

Puede usar AOP y @Immutable de jcabi-aspect :

@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();

Al igual que los otros respondedores ya han dicho, en mi humilde opinión no existe una forma confiable de averiguar si un objeto es realmente inmutable.

Acabo de introducir una interfaz " Inmutable " para comprobar en contra al anexar Esto funciona como una sugerencia de que solo se deben insertar objetos inmutables por cualquier motivo por el que lo esté haciendo.

interface Immutable {}

class MyImmutable implements Immutable{...}

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

Prueba esto:

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

Que yo sepa, no hay manera de identificar objetos inmutables que sea 100% correcto. Sin embargo, he escrito una biblioteca para acercarte más. Realiza el análisis del código de bytes de una clase para determinar si es inmutable o no, y puede ejecutarse en tiempo de ejecución. Está en el lado estricto, por lo que también permite la inclusión en listas blancas de clases inmutables conocidas.

Puede consultarlo en: www.mutabilitydetector.org

Le permite escribir código como este en su aplicación:

/*
* 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!
    */
}

Es gratuito y de código abierto bajo la licencia Apache 2.0.

Consulte joe-e , una implementación de capacidades para java.

Algo que funciona para un alto porcentaje de clases integradas es la prueba por ejemplo de Comparable. Para las clases que no son inmutables como la Fecha, a menudo se las trata como inmutables en la mayoría de los casos.

I appreciate and admire the amount of work Grundlefleck has put into his mutability detector, but I think it is a bit of an overkill. You can write a simple but practically very adequate (that is, pragmatic) detector as follows:

(note: this is a copy of my comment here: https://stackoverflow.com/a/28111150/773113)

First of all, you are not going to be just writing a method which determines whether a class is immutable; instead, you will need to write an immutability detector class, because it is going to have to maintain some state. The state of the detector will be the detected immutability of all classes which it has examined so far. This is not only useful for performance, but it is actually necessary because a class may contain a circular reference, which would cause a simplistic immutability detector to fall into infinite recursion.

The immutability of a class has four possible values: Unknown, Mutable, Immutable, and Calculating. You will probably want to have a map which associates each class that you have encountered so far to an immutability value. Of course, Unknown does not actually need to be implemented, since it will be the implied state of any class which is not yet in the map.

So, when you begin examining a class, you associate it with a Calculating value in the map, and when you are done, you replace Calculating with either Immutable or Mutable.

For each class, you only need to check the field members, not the code. The idea of checking bytecode is rather misguided.

First of all, you should not check whether a class is final; The finality of a class does not affect its immutability. Instead, a method which expects an immutable parameter should first of all invoke the immutability detector to assert the immutability of the class of the actual object that was passed. This test can be omitted if the type of the parameter is a final class, so finality is good for performance, but strictly speaking not necessary. Also, as you will see further down, a field whose type is of a non-final class will cause the declaring class to be considered as mutable, but still, that's a problem of the declaring class, not the problem of the non-final immutable member class. It is perfectly fine to have a tall hierarchy of immutable classes, in which all the non-leaf nodes must of course be non-final.

You should not check whether a field is private; it is perfectly fine for a class to have a public field, and the visibility of the field does not affect the immutability of the declaring class in any way, shape, or form. You only need to check whether the field is final and its type is immutable.

When examining a class, what you want to do first of all is to recurse to determine the immutability of its super class. If the super is mutable, then the descendant is by definition mutable too.

Then, you only need to check the declared fields of the class, not all fields.

If a field is non-final, then your class is mutable.

If a field is final, but the type of the field is mutable, then your class is mutable. (Arrays are by definition mutable.)

If a field is final, and the type of the field is Calculating, then ignore it and proceed to the next field. If all fields are either immutable or Calculating, then your class is immutable.

If the type of the field is an interface, or an abstract class, or a non-final class, then it is to be considered as mutable, since you have absolutely no control over what the actual implementation may do. This might seem like an insurmountable problem, because it means that wrapping a modifiable collection inside an UnmodifiableCollection will still fail the immutability test, but it is actually fine, and it can be handled with the following workaround.

Some classes may contain non-final fields and still be effectively immutable. An example of this is the String class. Other classes which fall into this category are classes which contain non-final members purely for performance monitoring purposes (invocation counters, etc.), classes which implement popsicle immutability (look it up), and classes which contain members that are interfaces which are known to not cause any side effects. Also, if a class contains bona fide mutable fields but promises not to take them into account when computing hashCode() and equals(), then the class is of course unsafe when it comes to multi-threading, but it can still be considered as immutable for the purpose of using it as a key in a map. So, all these cases can be handled in one of two ways:

  1. Manually adding classes (and interfaces) to your immutability detector. If you know that a certain class is effectively immutable despite the fact that the immutability test for it fails, you can manually add an entry to your detector which associates it with Immutable. This way, the detector will never attempt to check whether it is immutable, it will always just say 'yes, it is.'

  2. Introducing an @ImmutabilityOverride annotation. Your immutability detector can check for the presence of this annotation on a field, and if present, it may treat the field as immutable despite the fact that the field may be non-final or its type may be mutable. The detector may also check for the presence of this annotation on the class, thus treating the class as immutable without even bothering to check its fields.

I hope this helps future generations.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top