Pergunta

No meu código, eu estou criando uma coleção de objetos que serão acessados ??por vários tópicos de uma forma que só é segura se os objetos são imutáveis. Quando é feita uma tentativa para inserir um novo objeto em minha coleção, eu quero testar para ver se ele é imutável (se não, eu vou lançar uma exceção).

Uma coisa que posso fazer é verificar alguns tipos imutáveis ??conhecido poços:

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

Isso realmente me deixa 90% do caminho, mas às vezes os meus usuários vão querer criar tipos imutáveis ??simples de sua própria:

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

Existe alguma maneira (talvez usando a reflexão) que eu poderia detectar com segurança se uma classe é imutável? Os falsos positivos (pensando que é imutável, quando não é) não são aceitáveis, mas falsos negativos (pensando que é mutável quando não é) são.

Editado para adicionar: Obrigado pelas respostas interessantes e úteis. Como algumas das respostas apontou, eu negligenciado para definir meus objetivos de segurança. A ameaça aqui é desenvolvedores nora - este é um pedaço de código de estrutura que será utilizada por um grande número de pessoas que conhecem seguinte-à-nada sobre threading e não vai estar lendo a documentação. Eu não preciso para se defender contra os desenvolvedores mal-intencionados - ninguém o suficiente inteligente para mutação de um corda ou executar outras travessuras também será inteligente o suficiente para saber que não é seguro neste caso. análise estática da base de código é uma opção, desde que é automatizado, mas as revisões de código não pode ser contado, porque não há garantia de cada revisão terá revisores de threading-savvy.

Foi útil?

Solução

Não há nenhuma maneira confiável para detectar se uma classe é imutável. Isso ocorre porque há tantas maneiras uma propriedade de uma classe pode ser alterado e você não pode detectar todos eles através de reflexão.

A única maneira de chegar perto para isso é:

  • Só permitir propriedades finais dos tipos que são imutáveis ??(tipos primitivos e classes que você conhece são imutáveis),
  • Exigir a classe para ser final em si
  • Exigir que eles herdam de uma classe base que você fornece (que é garantido para ser imutável)

Em seguida, você pode verificar com o código a seguir se o objeto que você tem é imutável:

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

Update: Como sugerido nos comentários, poderia ser estendido para recurse na superclasse em vez de verificar para uma determinada classe. Também foi sugerida a utilização de forma recursiva isImmutable no Método isValidFieldType. Isso provavelmente poderia trabalhar e eu também fiz alguns testes. Mas isso não é trivial. Você não pode apenas verificar todos os tipos de campo com uma chamada para isImmutable, porque Cordas já falhar este teste (seu hash campo não é final!). Também são facilmente correr para recursions intermináveis, causando StackOverflowErrors ;.) Outros problemas podem ser causados ??por medicamentos genéricos, onde você também tem que verificar seus tipos para immutablity

Eu acho que com algum trabalho, esses problemas potenciais pode ser resolvido de alguma forma. Mas então, você tem que se perguntar primeiro se ele realmente vale a pena (também desempenho sábio).

Outras dicas

Use o Imutável anotação de Java Concurrency in Practice . A ferramenta FindBugs pode, então, ajuda na detecção de classes que são mutáveis, mas não deve ser.

Na minha empresa nós definimos um atributo chamado @Immutable. Se você optar por anexar que para uma classe, isso significa que você prometer que está imutável.

Ele funciona para documentação, e no seu caso ele iria trabalhar como um filtro.

É claro que você ainda está dependendo do autor manter sua palavra sobre ser imutável, mas desde que o autor adicionado explicitamente a anotação é uma suposição razoável.

Basicamente não.

Você pode construir uma lista branca gigante das classes aceitas, mas eu acho que a maneira menos louco seria apenas para gravação na documentação para a coleção que tudo o que acontece é esta coleção deve ser imutável.

Editar: Outras pessoas sugeriram ter uma anotação imutável. Isso é bom, mas você precisa da documentação também. Caso contrário, as pessoas só vai pensar "se eu colocar essa anotação na minha classe I pode armazená-lo na coleção" e só vai lançá-lo em qualquer coisa, imutável e aulas mutáveis ??iguais. Na verdade, eu seria cauteloso em ter uma anotação imutável apenas no caso das pessoas pensa que a anotação faz sua imutável classe.

Esta poderia ser uma outra dica:

Se a classe não tem setters então ele não pode ser mutado, concedeu os parâmetros com que foi criado ou são tipos "primitivos" ou não mutável si.

Também há métodos poderia ser anulado, todos os campos são finais e privado,

Vou tentar código alguma coisa amanhã para você, mas o código de Simon usando a reflexão parece muito bom.

Na tentativa tempo médio para pegar uma cópia do livro "Effective Java" por Josh Block, que tem um item relacionado a este tópico. Enquanto é não, com certeza digamos como detectar uma classe inmmutable, ele mostra como criar uma boa.

O item é chamado: "Favor imutabilidade"

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

No meu código, eu estou criando uma coleção de objetos que serão acessados ??por vários tópicos de uma forma que só é segura se os objetos são imutáveis.

Não é uma resposta directa à sua pergunta, mas tenha em mente que os objetos que são imutáveis ??não são automaticamente garantido para ser thread-safe (infelizmente). Código precisa ser efeito colateral livre para ser thread-safe, e isso é um pouco mais difícil.

Suponha que você tenha essa classe:

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

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

Em seguida, o método foolAround() podem incluir algumas operações de seguros não-fio, que vai explodir sua aplicação. E não é possível testar isso usando reflexão, como a referência real só pode ser encontrada no corpo do método, não nos campos ou interface exposta.

Além disso, os outros são correto: você pode digitalizar para todos os campos declarados da classe, verifique se cada um deles é final e também uma classe imutável, e está feito. Eu não acho que os métodos sendo final é um requisito.

Além disso, ter cuidado com recursivamente verificar campos dependentes para imutabilidade, você pode acabar com círculos:

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

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

Classes A e B são perfeitamente imutável (e possivelmente até mesmo utilizável através de alguns hacks reflexão), mas o código recursivo ingênuo vai entrar em um loop infinito verificação A, então B, então A novamente, partir para B, ...

Você pode corrigir isso com um mapa 'visto' que não permite ciclos, ou com algum código realmente inteligente que decide aulas são imutáveis, se todos os seus dependees são imutáveis ??dependendo apenas de si mesmos, mas que vai ser muito complicado ...

Você pode perguntar a seus clientes para adicionar metadados (anotações) e vê-los em tempo de execução com reflexão, como este:

Metadados:

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

Código do 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; }
}

Em seguida, usando a reflexão sobre a classe, verifique se ele tem a anotação (eu colar o código, mas a sua clichê e podem ser facilmente encontrados on-line)

Por que todas as recomendações exigem a classe para ser final? se você estiver usando reflexão para verificar a classe de cada objeto, e você pode determinar programaticamente que essa classe é imutável (campos imutáveis, final), então você não precisa exigir que a própria classe é final.

Você pode usar AOP e @Immutable anotação de jcabi-aspectos :

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

Como os outros respondentes já disse, IMHO não há nenhuma maneira confiável para descobrir se um objeto é realmente imutáveis.

Gostaria apenas de apresentar uma interface "Imutável" para verificar contra ao anexar. Isso funciona como um indício de que apenas objetos imutáveis ??devem ser inseridas por qualquer motivo você está fazendo isso.

interface Immutable {}

class MyImmutable implements Immutable{...}

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

Tente isto:

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

A meu conhecimento, não há nenhuma maneira de identificar objetos imutáveis ??que é 100% correto. No entanto, eu escrevi uma biblioteca para chegar mais perto. Realiza a análise de bytecode de uma classe para determinar se ele é imutável ou não, e pode executar em tempo de execução. Ele está no lado rígida, por isso também permite listas brancas conhecido classes imutáveis.

Você pode verificá-la em: www.mutabilitydetector.org

Ele permite que você escrever código como este em sua aplicação:

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

É livre e de código aberto sob a licença Apache 2.0.

Confira joe-e , a implementação de recursos para Java.

Algo que funciona para uma percentagem elevada das classes embutidas é teste para instanceof Comparable. Para as classes que não são imutáveis, como Data, eles são muitas vezes tratadas como imutável na maioria dos casos.

I apreciar e admirar a quantidade de trabalho Grundlefleck colocou em seu detector mutabilidade, mas eu acho que é um pouco de um exagero. Você pode escrever um simples, mas praticamente muito adequada (isto é, pragmática ) detector da seguinte forma:

(Nota: Esta é uma cópia do meu comentário aqui: https://stackoverflow.com/a/28111150/773113)

Em primeiro lugar, você não vai ser apenas escrever um método que determina se uma classe é imutável; em vez disso, você terá que escrever uma classe detector imutabilidade, porque vai ter que manter algum estado. O estado do detector irá ser detectado o imutabilidade de todas as classes que se tem até agora examinados. Isto não só é útil para o desempenho, mas na verdade é necessário porque uma classe pode conter uma referência circular, o que causaria um detector de imutabilidade simplista a cair em recursão infinita.

A imutabilidade de uma classe tem quatro valores possíveis: Unknown, Mutable, Immutable e Calculating. Você provavelmente vai querer ter um mapa que associa cada classe que você encontrou até agora para um valor de imutabilidade. Claro, Unknown na verdade não precisam ser implementadas, uma vez que será o estado implícita de qualquer classe que ainda não está no mapa.

Assim, quando você começar a examinar uma classe, você associá-lo com um valor Calculating no mapa, e quando você está feito, você substitui Calculating com qualquer Immutable ou Mutable.

Para cada classe, você só precisa verificar os membros de campo, e não o código. A idéia de verificar bytecode é bastante equivocada.

Em primeiro lugar, você deve não Verifique se uma classe é final; A finalidade de uma classe não afeta sua imutabilidade. Em vez disso, um método que espera um parâmetro imutável deve em primeiro lugar invocar o detector imutabilidade afirmar a imutabilidade da classe do objeto real que foi aprovada. Este teste pode ser omitido se o tipo do parâmetro é uma classe final, de modo finalidade é bom para o desempenho, mas estritamente falando não é necessário. Além disso, como você vai ver mais abaixo, um campo cujo tipo é de uma classe que não seja final fará com que a classe declarante deve ser considerado como mutável, mas ainda assim, isso é um problema da classe declarante, não o problema da não-de-final membro da classe imutável. É perfeitamente possível ter uma hierarquia de altura de classes imutáveis, nas quais todos os nós não-folha deve ser, obviamente, não-final.

Você deve não verificar se um campo é privado; é perfeitamente bem para uma classe para ter um campo público, e a visibilidade do campo não afeta a imutabilidade da classe declarando em qualquer maneira, forma ou formulário. Você só precisa verificar se o campo é final e seu tipo é imutável.

Ao examinar uma classe, o que você quer fazer antes de tudo é recursiva para determinar a imutabilidade da sua classe super. Se o super é mutável, então o descendente é por mutável definição também.

Em seguida, você só precisa verificar o declarou campos da classe, não todas campos.

Se um campo não é final, então a sua classe é mutável.

Se um campo é final, mas o tipo de campo é mutável, em seguida, sua classe é mutável. (Arrays são por mutável definição.)

Se um campo é final, e o tipo do campo é Calculating, então ignorá-lo e avançar para o próximo campo. Se todos os campos ou são imutáveis ??ou Calculating, em seguida, sua classe é imutável.

Se o tipo de campo é uma interface ou uma classe abstrata, ou uma classe não-final, então é para ser considerado como mutável, já que você não tem absolutamente nenhum controle sobre o que a implementação real pode fazer. Isto pode parecer um problema insuperável, porque eumeios t que embalar uma coleção modificável dentro de um UnmodifiableCollection ainda irá falhar o teste de imutabilidade, mas é realmente bom, e pode ser tratada com a seguinte solução alternativa.

Algumas classes podem conter campos não-finais e ainda ser efetivamente imutável . Um exemplo disso é a classe String. Outras classes que se enquadram nesta categoria são classes que contêm membros não-finais puramente para fins de monitoramento de desempenho (contadores de invocação, etc.), classes que implementam picolé imutabilidade (procurá-lo), e classes que contêm membros que são interfaces que são conhecidos por não causam quaisquer efeitos secundários. Além disso, se uma classe contém bona fide campos mutáveis, mas promete não levá-las em conta no cálculo hashCode () e equals (), então a classe é, naturalmente, inseguro quando se trata de multi-threading, mas ele ainda pode ser considerado como imutável com o propósito de usá-lo como uma chave em um mapa. Então, todos esses casos pode ser tratada de uma de duas maneiras:

  1. manualmente adicionando classes (e interfaces) para o seu detector de imutabilidade. Se você sabe que uma certa classe é efetivamente imutável apesar do fato de que o teste de imutabilidade para isso falhar, você pode adicionar manualmente uma entrada para o detector que associa-lo com Immutable. Desta forma, o detector nunca tentará verificar se ele é imutável, será sempre apenas dizer 'sim, é.'

  2. Apresentando uma anotação @ImmutabilityOverride. Seu detector imutabilidade pode verificar a presença desta anotação em um campo, e se presente, pode tratar o campo como imutável apesar do fato de que o campo pode ser não-final ou seu tipo pode ser mutável. O detector pode também verificar a presença desta anotação na classe, tratando, assim, a classe como imutável, sem sequer se preocupar em verificar os seus campos.

Espero que isso ajude as gerações futuras.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top