Pergunta

Confira este código.

// Print object and recurse if iterable
private static void deep_print(Object o) {
  System.out.println(o.getClass().toString() + ", " + o.toString());

  boolean iter = false;
  Iterable<?> i1 = null;
  Object[] i2 = null;

  if (o instanceof Iterable<?>) {
    iter = true;
    i1 = (Iterable<?>) o;
  } else if (o instanceof Object[]) {
    iter = true;
    i2 = (Object[]) o;
  }

  if (iter) {
    for (Object o_ : i2 == null ? i1 : i2) deep_print(o_); // ERROR: Can only iterate over an array or an instance of java.lang.Iterable
  }

Eu sei como resolvê -lo. Eu só quero saber por que isso acontece. O compilador não deve simplesmente verificar todas as saídas possíveis?

Foi útil?

Solução

O resultado estático do tipo de expressão (i2 == null) ? i1 : i2 é o ancestral comum de i1 e i2 o que é objeto. UMA for A declaração requer o Tipo estático da expressão ser um Iterable ou um tipo de matriz. Não é esse o caso, então você recebe um erro de compilação.

Agora, se você está perguntando por que o compilador não deduz isso (i2 == null) ? i1 : i2 sempre será uma matriz ou um iterável:

  1. A especificação do idioma Java (JLS) não permite isso.
  2. Se o JLS permitisse (mas não exigisse), diferentes compiladores se comportariam de maneira diferente, dependendo de quão bons eles eram no teorema da prova. MAU.
  3. Se o JLS exigia, o compilador terá que incorporar um teorema sofisticado provador`1 (Slow Bad Slow), e você encontra o "menor inconveniente" de resolver o problema de parada2 (MAU MAU MAU).
  4. De fato, o compilador precisa saber qual dos dois tipos de tipo a expressão tem, porque precisa gerar código diferente em cada caso.

Hipoteticamente, se o sistema do tipo Java fosse um pouco diferente, este caso em particular poderia ser tratado melhor. Especificamente, se Java apoiado Tipos de dados algébricos então seria possível declarar o como sendo "uma matriz de objeto ou um iterável" ... e o for O loop seria verificável pelo tipo.


1 - suponha que o foi inicializado como o = (x * x < 0) ? new Object() : new Object[0]. Determinando que isso sempre resultará em um Object[] O exemplo implica uma pequena prova que envolve o fato de que o quadrado de um número (real) não é negativo. Esse é um exemplo simples, é possível construir exemplos arbitrariamente complicados, exigindo provas difíceis arbitrárias.

2 - O problema de interrupção é matematicamente comprovado como uma função incomputável. Em outras palavras, existem funções em que é matematicamente impossível provar se eles terminam ou não.

Outras dicas

Para ilustrar a resposta de Stephen C, considere o seguinte código:

void test() {
      Iterable<Integer> i1 = new ArrayList<Integer>();
      Object[] i2 = { 1, 2, 3 };      
      method1(false ? i1 : i2);
      method1(true ? i1 : i2);  
}

void method1(Object o) {
    System.out.println("method1(Object) called");
}

void method1(Object[] o) {
    System.out.println("method1(Object[]) called");
}

void method1(Iterable<?> o) {
    System.out.println("method1(Iterable<?>) called");
}

Esta é a saída do teste ():

method1(Object) called
method1(Object) called

Como a sobrecarga do método é feita em tempo de compilação, você pode ver que o tipo estático da expressão do operador ternário é objeto, uma vez que os tipos de operandos diferem. Portanto, quando você faz:

for (Object o_ : i2 == null ? i1 : i2)

Você está realmente pedindo ao compilador que gere um loop foreach sobre um objeto, o que é ilegal.

Nesse caso, a expressão condicional tem o tipo de limite superior inferior dos dois tipos, que é Object, e o loop foreach não funciona no tipo Object

Você realmente deve adicionar dois métodos deepprint sobrecarregados (objeto []) e deepprint (iterator i) e fazer o despacho em seu Deepprint (objeto). Sim, devido à natureza de como funciona o para/cada loop, você precisará copiar e colar o mesmo código com pequenas alterações.

Tentar colocar tudo isso em um grande método é um cheiro.

Você pode evitar a duplicação de código envolvendo o objeto [] em um Arrays.asList (objeto []) e depois ter um iterável em todos os casos. Sim, é um pouco mais lento do que trabalhar em uma matriz, mas tem a vantagem de estar seco, o que sempre deve ser a primeira aproximação, IMHO.

Então você acabaria com algo assim:

Iterable<?> it = null;
if (o instanceof Iterable<?>) {
    it = (Iterable<?>) o;
} else if (o instanceof Object[]) {
    it = Arrays.asList((Object[]) o);
}

if (it != null) {
   for (Object o_ : it) deep_print(o_);
}

Por uma questão de simplicidade (veja a resposta de Stephen C sobre como é mais simples para os escritores do compilador, mas também é um design de linguagem mais simples), o operador ternário assume que o tipo é o menor demoninador comum entre os dois tipos de retorno, em vez de acompanhar um dos dois tipos de retorno. Considere o caso de uma sobrecarga de método. O método correto é determinado e com o tempo de compilação. Isso significa que o compilador deve poder tomar uma decisão sobre o tipo de retorno declarado no horário de compilação e não pode atrasar essa decisão de executar o tempo.

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