2011-08-16 34 views
14

Sto provando a creare istanze di classi anonime mediante la riflessione. Ma occasionalmente ho visto strani comportamenti durante l'istanziazione.Costruzione dinamica di confusione di classe anonima

prega, un'occhiata a questi frammenti simili di codice

public class HideAndSeek { 

    @SuppressWarnings("unchecked") 
    public static void main(String[] args) throws IllegalAccessException, InstantiationException{ 

     final String finalString = "I'm final :)"; 

     Object object2 = new Object(){ 

      { 
       System.out.println("Instance initializing block"); 
       System.out.println(finalString); 
      }   

      private void hiddenMethod() { 
       System.out.println("Use reflection to find me :)"); 
      } 
     }; 

     Object tmp = object2.getClass().newInstance(); 
    } 

} 

questo codice funziona bene, ed i risultati attesi

Instance initializing block 
I'm final :) 
Instance initializing block 
I'm final :) 

Dopo questo ho deciso di cambiare il codice in modo semplice (basta Aggiunto java.util.Calendar)

import java.util.Calendar; 

    public class HideAndSeek { 

     @SuppressWarnings("unchecked") 
     public static void main(String[] args) throws IllegalAccessException, InstantiationException{ 

      final String finalString = "I'm final :)"; 

      final Calendar calendar = Calendar.getInstance(); 
      System.out.println(calendar.getTime().toString()); //works well 

      Object object2 = new Object(){ 

       { 
        System.out.println("Instance initializing block"); 
        System.out.println(finalString); 

        //simply added this line 
        System.out.println(calendar.getTime().toString()); 
       }   

       private void hiddenMethod() { 
        System.out.println("Use reflection to find me :)"); 
       } 
      }; 

      Object tmp = object2.getClass().newInstance(); 
     } 

    } 

Ed ecco in uscita che ho:

Wed Aug 17 02:08:47 EEST 2011 
Instance initializing block 
I'm final :) 
Wed Aug 17 02:08:47 EEST 2011 
Exception in thread "main" java.lang.InstantiationException: HideAndSeek$1 
    at java.lang.Class.newInstance0(Unknown Source) 
    at java.lang.Class.newInstance(Unknown Source) 
    at HideAndSeek.main(HideAndSeek.java:29) 

Come si può vedere, non è stata creata una nuova istanza.

Qualcuno potrebbe spiegarmi la ragione di tali cambiamenti?

Grazie

+0

E se l'instnace di Calendar non fosse definitiva? – SJuan76

+0

@ SJuan76. ... allora non saresti stato in grado di trasmetterlo a una classe interiore anonima. –

risposta

17

Questa è una domanda molto semplice con una risposta molto complessa. Per favore, sopportami mentre cerco di spiegarlo.

Guardando il codice sorgente in cui l'eccezione è sollevata in Class (non sono sicuro perché il vostro stack trace non dà i numeri di riga in Class):

try 
{ 
    Class[] empty = {}; 
    final Constructor<T> c = getConstructor0(empty, Member.DECLARED); 
    // removed some code that was not relevant 
} 
catch (NoSuchMethodException e) 
{ 
    throw new InstantiationException(getName()); 
} 

si vede che è NoSuchMethodException essere retharrown come InstantiationException. Ciò significa che non esiste un costruttore no-arg per il tipo di classe di object2.

Innanzitutto, che tipo è object2? Con il codice

System.out.println("object2 class: " + object2.getClass()); 

vediamo che

oggetto2 classe: class junk.NewMain $ 1

che è corretto (ho eseguito il codice di esempio nel pacchetto spazzatura, classe NewMain).

Quali sono quindi i costruttori di junk.NewMain$1?

Class obj2Class = object2.getClass(); 
try 
{ 
    Constructor[] ctors = obj2Class.getDeclaredConstructors(); 
    for (Constructor cc : ctors) 
    { 
    System.out.println("my ctor is " + cc.toString()); 
    } 
} 
catch (Exception ex) 
{ 
    ex.printStackTrace(); 
} 

che ci dà

mia ctor è junk.NewMain $ 1 (java.util.Calendar)

Così la vostra classe anonima è alla ricerca di un Calendar da passare in. Questo funzionerà quindi per voi:

Object newObj = ctors[0].newInstance(Calendar.getInstance()); 

Se avete qualcosa come e questo:

final String finalString = "I'm final :)"; 
final Integer finalInteger = new Integer(30); 
final Calendar calendar = Calendar.getInstance(); 
Object object2 = new Object() 
{ 
    { 
    System.out.println("Instance initializing block"); 
    System.out.println(finalString); 
    System.out.println("My integer is " + finalInteger); 
    System.out.println(calendar.getTime().toString()); 
    } 
    private void hiddenMethod() 
    { 
    System.out.println("Use reflection to find me :)"); 
    } 
}; 

allora la mia chiamata a newInstance non funziona perché non ci sono abbastanza argomenti nella ctor, perché ora vuole:

mia ctor è junk.NewMain $ 1 (java. lang.Integer, java.util.Calendar)

Se dunque io un'istanza di quello con

Object newObj = ctors[0].newInstance(new Integer(25), Calendar.getInstance()); 

una sbirciatina all'interno utilizzando il debugger mostra che finalInteger è 25 e non il valore finale 30.

Le cose sono leggermente complicate perché stai facendo tutto quanto sopra in un contesto statico. Se si prende tutto il codice sopra e spostarlo in un metodo non statico in questo modo (si ricordi, la mia classe è junk.NewMain):

public static void main(String[] args) 
{ 
    NewMain nm = new NewMain(); 
    nm.doIt(); 
} 

public void doIt() 
{ 
    final String finalString = "I'm final :)"; 
    // etc etc 
} 

troverete il ctor per la classe interna è ora (rimozione la mia aggiunta di riferimento Integer):

mia ctor è junk.NewMain $ 1 (junk.NewMain, java.util.Calendar)

La Java Language Specification, sezione 15.9.3 spiega in questo modo:

Se C è una classe anonima, e superclasse diretta di C, S, è un classe interna, quindi:

  • Se S è una classe locale e S avviene in un contesto statico, allora gli argomenti nell'elenco di argomenti, se presenti, sono gli argomenti a il costruttore, nell'ordine in cui appaiono nell'espressione.
  • caso contrario, l'istanza immediatamente racchiude dei rispetto al S è il primo argomento del costruttore, seguito dagli argomenti nella lista degli argomenti dell'espressione creazione dell'istanza classe eventualmente, nell'ordine in cui appaiono in l'espressione.

Perché il costruttore anonimo accetta argomenti?

Poiché non è possibile creare un costruttore per una classe interna anonima, il blocco di inizializzazione dell'istanza serve a tale scopo (ricordare, si dispone di una sola istanza di tale classe interna anonima). La VM non ha conoscenza della classe interna in quanto il compilatore separa tutto come singole classi (ad es. Junk.NewMain $ 1). Il ctor per quella classe contiene il contenuto del programma di inizializzazione dell'istanza.

Questo è explayed da JLS 15.9.5.1 Anonymous Constructors:

... il costruttore Anonymous ha un parametro formale per ciascun argomento reale per l'espressione creazione istanza di classe in cui C è dichiarata.

L'inizializzatore di istanza ha un riferimento a un oggetto Calendar. In quale altro modo il compilatore otterrà quel valore di runtime nella tua classe interna (che viene creata come una semplice classe per la VM) tranne attraverso il costruttore?

Infine (yay), la risposta all'ultima domanda bruciata. Perché il costruttore non richiede un String? L'ultimo pezzo di JLS 3.10.5 spiega che:

Strings calcolati da espressioni costanti sono calcolati al momento della compilazione e poi trattati come se fossero letterali.

In altre parole, il valore String è noto al momento della compilazione, perché è un letterale in modo che non ha bisogno di essere parte del costruttore anonimo. Per dimostrare questo è il caso ci prova l'istruzione successiva in JLS 3.10.5:

Strings calcolate dalla concatenazione in fase di esecuzione sono state recentemente create e pertanto distinta.

di modificare il codice nel seguente modo:

String str1 = "I'm"; 
String str2 = " final!"; 
final String finalString = str1 + str2 

e troverete il vostro ctor è ora (nel contesto non statica):

mia ctor è junk.NewMain $ 1 (junk.NewMain, java.lang.String, java.util.Calendar)

Phew. Spero che questo abbia senso ed è stato utile. Ho imparato molto, questo è sicuro!

+0

bravo! Ben ricercato –

+0

+37 (se potessi): ottima risposta! –

+0

@Paul Grazie per la risposta molto istruttiva :) – stemm

4

perché nel secondo caso non v'è alcun costruttore di default più.

Nel primo caso la stringa finale viene inline perché è una costante.

Nel secondo caso la classe interna anonima deve accettare l'istanza di Calendar nel suo costruttore per acquisire lo stato. Puoi facilmente verificare che:

Object tmp = object2.getClass().getDeclaredConstructor(Calendar.class).newInstance(calendar);