2016-01-26 14 views
7

Stavo esaminando uno dei percorsi Oracle sui generics Java, intitolato "Effects of Type Erasure and Bridge Methods", e non riuscivo a convincermi di una spiegazione fornita. Curioso, ho testato il codice localmente e non riuscivo nemmeno a riprodurre il comportamento spiegato dal sentiero. Ecco il codice rilevante:Problema potenziale con uno dei percorsi Oracle sui generici Java

public class Node<T> { 
    public T data; 

    public Node(T data) { this.data = data; } 

    public void setData(T data) { 
     System.out.println("Node.setData"); 
     this.data = data; 
    } 
} 

public class MyNode extends Node<Integer> { 
    public MyNode(Integer data) { super(data); } 

    public void setData(Integer data) { 
     System.out.println("MyNode.setData"); 
     super.setData(data); 
    } 
} 

Il sentiero Oracle sostiene il seguente comportamento di questo frammento di codice:

MyNode mn = new MyNode(5); 
Node n = mn;   // A raw type - compiler throws an unchecked warning 
n.setData("Hello");  
Integer x = mn.data; // Causes a ClassCastException to be thrown. 

Questo frammento di codice dovrebbe essere simile al seguente dopo la cancellazione del tipo:

MyNode mn = new MyNode(5); 
Node n = (MyNode)mn;   // A raw type - compiler throws an unchecked warning 
n.setData("Hello"); 
Integer x = (String)mn.data; // Causes a ClassCastException to be thrown. 

Non ho capito i cast utilizzati qui o il comportamento. Quando ho provato a fare funzionare questo codice in locale utilizzando IntelliJ con Java 7, ho ottenuto questo comportamento:

MyNode mn = new MyNode(5); 
Node n = mn;   // A raw type - compiler throws an unchecked warning 
n.setData("Hello");  // Causes a ClassCastException to be thrown. 
Integer x = mn.data; 

In altre parole, la JVM non permetterà un String da utilizzare con setData(). Questo è in realtà intuitivo per me e concorda con la mia comprensione dei farmaci generici. Poiché MyNodemn è stato creato con Integer, il compilatore deve trasmettere ogni chiamata a setData() con Integer per garantire la sicurezza del tipo (ad esempio un Integer viene passato).

Qualcuno può fare luce su questo bug apparente nel percorso Oracle?

risposta

3

Hai erroneamente letto la pagina Oracle. Se leggi fino alla fine, scoprirai che afferma che quello che succede è ciò che descrivi.

Non è una pagina ben scritta; l'autore dice "blah accade" quando ciò che intendono è "SE QUESTA ERA IL CASO POI bla succede MA COME VEDRANNO CHE NON È IL CASO". Sono troppo sciolti con la loro lingua.

Il punto della pagina - metodi bridge - è quello di spiegare come il comportamento reale è come osservato, quando il comportamento previsto (basato sulla progettazione acutale di Generics + implementazione) è ciò che hanno "suggerito" all'inizio.

+0

Penso che dicendo che la pagina è sciolto con il suo linguaggio sta dando troppo credito. Quindi quello che stai dicendo è che la chiamata 'n.setData (" Hello ")' sta effettivamente colpendo il metodo bridge generato dal compilatore, che eseguirà il cast di ogni oggetto passato a 'Integer' prima di chiamare il metodo super. Bene, questo spiega tutto, suppongo, grazie per le intuizioni. –

+1

Tendo a essere pungente per i documenti non validi, ma mi sono abituato al fatto che il reparto pubblichi documenti terribili, in particolare per le API. Ironia della sorte, Java aveva alcuni dei migliori documenti di sempre, quando era giovane. Al giorno d'oggi ... cerco di mantenere il mio disprezzo per me :) – Adam

1

Bene, è spiegato nel percorso.

In teoria , quando la classe Node viene compilato, relativo tipo di base T viene cancellato a Object.

Quindi, in realtà, è compilato in qualcosa di simile

class Node { 
    public Object data; 

    public Node(Object data) {this.data = data; } 

    public void setData(Object data) { 
     System.out.println("Node.setData"); 
     this.data = data; 
    } 
} 

Poi si crea una classe figlia MyNode, che ha una sua setData(Integer data). Per quanto riguarda Java, questo è un sovraccarico del metodo setData, non un override di esso. Ogni oggetto MyNode ha due metodi setData. Uno è setData(Object) ereditato da Node e l'altro è setData(Integer).

Quindi, fondamentalmente, se si utilizza il tipo di prima, e si chiama setData con qualsiasi riferimento che non è un Integer, normale interpretazione di Java di questo sarebbe quello di chiamare il sovraccarico di setData(Object).

Ciò non causerebbe un problema sull'assegnazione, perché data è dichiarato come Object, non come Integer. Il problema sarebbe solo quando si tenta di assegnare i dati a un riferimento Integer. Questo semplice comportamento di Java farebbe sì che l'oggetto MyNode fosse "contaminato" da dati inappropriati.

Tuttavia, come dice la traccia, il compilatore aggiunge un metodo "ponte" per rendere la classe figlio più simile a come la si pensa intuitivamente. Aggiunge un override di setData(Object) a MyNode, in modo che non è possibile chiamare l'originale, non sicuro Node.setData(Object). In questo metodo di bridge sovrascritto, esiste un cast esplicito a Integer che garantisce che non sarà possibile assegnare un riferimento non intero a data.

E questo è il comportamento che si vede quando si compila e si esegue l'esempio.

Se si esegue javap -p sul file MyNode.class, vedrete che in effetti, ha due metodi: setData

class MyNode extends Node<java.lang.Integer> { 
    public MyNode(java.lang.Integer); 
    public void setData(java.lang.Integer); 
    public void setData(java.lang.Object); 
}