Por que o java exigir um elenco para a instanciação de um parâmetro de tipo limitada à sua classe limite superior?

Matthew S.:

Java exige a instanciação de um tipo de parâmetro limitada à sua classe limite superior de ter um elenco, por exemplo:

<T extends Integer> void passVal (T t) {
    Integer number = 5;
    t = (T) number; // Without cast a compile error is issued
}

Tjá está restrito a Integerou uma de suas subclasses (eu sei, não há qualquer) mediante declaração, e parece-me que qualquer parâmetro de tipo limitada só pode ser instanciado para sua classe limite superior ou uma de suas subclasses, por isso, porque é que o elenco precisava?

Além disso, eu sei que este não é o caso se eu instanciar o parâmetro através de fora invocação deste método, portanto, não oferecer isso como uma resposta; a questão é específico para parâmetros de tipo limitadas a ser instanciada dentro das suas declarando classe / métodos.

ggf31416:

T não significa inteiro, ele deve ser válido por inteiro ou qualquer classe que se estende a partir dele. Vamos dizer que StrangeInteger se estende de Integer e substituí-T com StrangeInteger:

void passVal (StrangeInteger t) {
    Integer number = 5;
    t = (StrangeInteger) number;
}

Ele tenta atribuir uma variável Integer a uma variável StrangeInteger, você não pode fazer isso a menos que o número foi um StrangeInteger ou uma classe derivada, em primeiro lugar. Na verdade seu código deve (conceitualmente) lançar uma exceção em tempo de execução, a menos que t é um inteiro, mas não vai realmente fazer isso neste caso devido ao tipo de apagamento (ver Editar 2).

A situação é semelhante a:

Object obj = "Hello"
String t = (String)obj; // this will not fail, but requires a cast

Object obj2 = getDBConnection();
String t2 = (String)obj2; // this will fail at runtime

Edit: Integer é realmente final, então T só pode ser Integer, mas é possível que o compilador não está verificando se o limite superior é final, depois de tudo o que faz pouco sentido para um limite superior para ser final, permitindo assim que o caso especial adiciona complexidade para muito pouco ganho real.

Editar 2: TL; DR: Você está confundindo limites superiores e inferiores, mas há ressalvas com tipo de rasura. Isso vai quebrar logo que você faça qualquer coisa que vale a pena fazer usando os genéricos em vez de apenas usando o tipo de base.

Inglês não é a minha primeira língua para que eu possa não ser totalmente claro.

Eu acho que você está lutando com a diferença entre usar um tipo genérico com um limite superior e apenas usando o limite superior como tipo. A idéia de genéricos (de C ++ e outras linguagens) é que o código deve ser válido se você substituir T por qualquer tipo T permitido pelos limites, então você não pode chamar qualquer método que não está definido no limite superior.

A ser um limite superior em T também significa que você pode sempre atribuir um objeto T a uma variável A. Não é possível atribuir com segurança um Um objeto a uma variável T (a menos que A == T), você só pode fazer isso se Um era um ligado na T inferior, não um limite superior. Ver também Compreender limites superiores e inferiores em? em Java Generics.

Java usa tipo de apagamento para implementar os genéricos, existem algumas vantagens , mas que faz com que algumas limitações que nem sempre são aparentes. Por causa do tipo de rasura neste caso, o próprio molde não falhará, após verificação de tipo T é substituído pelo limite superior na etapa de apagamento tipo, ou seja (T) é substituído pelo número de número (inteiro). Uma exceção ainda ocorrerá se você fizer qualquer coisa que provoca um elenco para a subclasse, por exemplo, se você retornar a alteração t e atribuir o resultado a uma variável da subclasse, porque o compilador acrescenta uma conversão implícita.

Isso também irá falhar se você chamar um método que depende de uma subclasse de T, que é um padrão comum, por exemplo:

List<Person> persons = ...
Comparator<Person> nameComparator = (p1,p2) -> p1.getName().compareTo(p2.getName())
java.util.Collections.sort(persons,nameComparator);

O seguinte exemplo de código exibe o comportamento em vários casos. Eu costumava System.err em tudo para evitar problemas de ordem na saída.

import java.util.function.Consumer;
import java.util.function.Function;

class A {
    @Override public String toString(){ return "A";}
    public String foo(){ return "foo";}
}

class B extends A {
    @Override public String toString(){ return "B";}
    public String bar(){ return "bar";}
}

class C extends B { }

public class Main {

    public static void main(String[] args) {
        Function<A,String> funA = a -> a.foo();
        Function<B,String> funB = b -> b.bar();
        Function<C,String> funC = c -> c.bar();
        Consumer<B> ignoreArgument = b -> {
            System.err.println("  Consumer called");
        };

        B b = new B();
        System.err.println("* voidTest *");
        voidTest(b);
        System.err.println("------------");
        System.err.println("* returnTest *"); 
        returnTest(b);
        System.err.println("returnTest without using result did not throw");
        System.err.println("------------");
        try {
            System.err.println("Returned " + returnTest(b).toString());
            System.err.println("returnTest: invoking method on result did not throw");
        }
        catch(Exception ex) {
            System.err.println("returnTest: invoking method on result threw");
            ex.printStackTrace();
        }
        System.err.println("------------");
        B b2 = null;
        try {
            b2 = returnTest(b);
            System.err.println("returnTest: assigning result to a B variable did not throw");
        }
        catch(Exception ex) {
            System.err.println("returnTest: assigning result to a B variable threw");
            ex.printStackTrace();
        }
        System.err.println("------------");
        System.err.println("* functionTest funA *");
        functionTest(b, funA);
        System.err.println("------------");
        System.err.println("* functionTest funB * ");
        functionTest(b, funB);
        System.err.println("------------");
        System.err.println("* consumerTest *");
        consumerTest(b, ignoreArgument);
        // The following won't work because C is not B or a superclass of B
        // Compiler error functionTest(T, Function<? super T,String>) is not applicable for the arguments (B, Function<C,String>)
        // functionTest(b, funC); 
    }

    private static <T extends A> void voidTest(T t){
        System.err.println("  Before: " + t.toString());
        t = (T)new A(); // warning Type safety: Unchecked cast from A to T
        System.err.println("  After: " + t.toString());
    }

    private static <T extends A> T returnTest(T t){
        System.err.println("  Before: " + t.toString());
        t = (T)new A();
        System.err.println("  After: " + t.toString());
        return t;
    }

    private static <T extends A> void functionTest(T t, Function<? super T,String> fun) {
        System.err.println("  fun Before: " + fun.apply(t));
        t = (T)new A();
        try {
            System.err.println("  fun After: " + fun.apply(t));
        }
        catch(Exception ex) {
            System.err.println("  fun After: threw");
            ex.printStackTrace();
        }
    }

    private static <T extends A> void consumerTest(T t, Consumer<? super T> c) {
        System.err.print("  Before: ");
        c.accept(t);
        t = (T)new A();
        try {
            System.err.println("  After: ");
            c.accept(t);
            System.err.println("    c.accept(t) After: worked");
        }
        catch(Exception ex) {
            System.err.println("    c.accept(t) After: threw");
            ex.printStackTrace();
        }
    }
}

A saída sob OpenJDK 11 é:

* voidTest *
  Before: B
  After: A
------------
* returnTest *
  Before: B
  After: A
returnTest without using result did not throw
------------
  Before: B
  After: A
returnTest: invoking method on result threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
    at Main.main(Main.java:35)
------------
  Before: B
  After: A
returnTest: assigning result to a B variable threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
    at Main.main(Main.java:45)
------------
* functionTest funA *
  fun Before: foo
  fun After: foo
------------
* functionTest funB * 
  fun Before: bar
  fun After: threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
    at Main.functionTest(Main.java:83)
    at Main.main(Main.java:57)
------------
* consumerTest *
  Before:   Consumer called
  After: 
    c.accept(t) After: threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
    at Main.consumerTest(Main.java:97)
    at Main.main(Main.java:60)

Eu não sou inteiramente certo resultTest porque não causou uma exceção se o resultado é completamente ignorado, talvez um elenco não é exigido pela linguagem, nesse caso, ou o compilador removido. Chamar um método definidos no limite superior do resultado ainda causou uma excepção. Finalmente, uma observação de consumerTest é que ele não tinha necessidade de barramento de chamadas () para causar uma ClassCastException, ele só precisava passar t para o consumidor que espera um argumento B.

Acho que você gosta

Origin http://43.154.161.224:23101/article/api/json?id=311205&siteId=1
Recomendado
Clasificación