2015-01-03 16 views
13

Mi chiedo in che modo tutto ciò che riguarda i riferimenti ai metodi e le interfacce funzionali funzioni a un livello inferiore. L'esempio più semplice è dove abbiamo qualche ListaRiferimento a metodi con diversi parametri in Java8

List<String> list = new ArrayList<>(); 
list.add("b"); 
list.add("a"); 
list.add("c"): 

Ora vogliamo risolvere la cosa con classe Collections, in modo che possiamo chiamare:

Collections.sort(list, String::compareToIgnoreCase); 

Ma se definiamo comparatore personalizzato potrebbe essere qualcosa di simile :

Comparator<String> customComp = new MyCustomOrderComparator<>(); 
Collections.sort(list, customComp::compare); 

Il problema è che Collections.sort accetta due parametri: Elenco e Comparatore. Poiché Comparator è un'interfaccia funzionale, può essere sostituito con espressione lambda o riferimento al metodo con la stessa firma (parametri e tipo di ritorno). Quindi come funziona che possiamo passare anche il riferimento al confrontoPer quale prende solo un parametro e le firme di questi metodi non corrispondono? Come vengono tradotti i riferimenti al metodo in Java8?

+1

domanda interessante, forse anche più chiaro esempio: 'comparatore cmp = String :: compareToIgnoreCase; "Onestamente, non ho idea di come funzioni. – MightyPork

+0

Credo che questo possa essere chiuso come duplicato di http://stackoverflow.com/questions/20001427/double-colon-operator-in-java-8 – MightyPork

risposta

16

Dal Oracle method references tutorial:

riferimento ad un metodo di istanza di un oggetto arbitrario di un particolare tipo

Il seguente è un esempio di un riferimento a un metodo di un oggetto arbitrario di esempio un tipo particolare:

String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

L'espressione lambda equivalente per il metodo di riferimento String::compareToIgnoreCase avrebbe l'elenco di parametri formale (String a, String b), dove aeb sono nomi arbitrari utilizzati per descrivere meglio questo esempio. Il riferimento al metodo invocerebbe il metodo a.compareToIgnoreCase(b).

Ma cosa significa realmente l'operatore ::? Ebbene, l'operatore :: può essere descritto come questo (da this SO question):

Metodo di riferimento può essere ottenuta in diversi stili, ma tutti lo stesso significato:

  1. Procedimento statico (ClassName::methodName)
  2. un metodo di un oggetto particolare istanza (instanceRef::methodName)
  3. procedimento eccellente di un particolare oggetto (super::methodName)
  4. Un metodo di un oggetto arbitrario di un tipo particolare istanza (ClassName::methodName)
  5. Un riferimento costruttore di classe (ClassName::new)
  6. Un riferimento costruzione Array (TypeName[]::new)

Quindi, che significa che il metodo il riferimento String::compareToIgnoreCase rientra nella seconda categoria (instanceRef::methodName), il che significa che può essere tradotto in (a, b) -> a.compareToIgnoreCase(b).

Credo che i seguenti esempi lo illustrino ulteriormente. A Comparator<String> contiene un metodo che opera su due operandi String e restituisce uno int. Questo può essere pseudo descritto come (a, b) ==> return int (dove gli operandi sono a e b). Se si visualizzano in questo modo tutti i seguenti rientra nell'ambito di tale categoria:

// Trad anonymous inner class 
// Operands: o1 and o2. Return value: int 
Comparator<String> cTrad = new Comparator<String>() { 
    @Override 
    public int compare(final String o1, final String o2) { 
     return o1.compareToIgnoreCase(o2); 
    } 
}; 

// Lambda-style 
// Operands: o1 and o2. Return value: int 
Comparator<String> cLambda = (o1, o2) -> o1.compareToIgnoreCase(o2); 

// Method-reference à la bullet #2 above. 
// The invokation can be translated to the two operands and the return value of type int. 
// The first operand is the string instance, the second operand is the method-parameter to 
// to the method compareToIgnoreCase and the return value is obviously an int. This means that it 
// can be translated to "instanceRef::methodName". 
Comparator<String> cMethodRef = String::compareToIgnoreCase; 

This great SO-answer to explains how lambda functions are compiled. In questa risposta, Jarandinor si riferisce al seguente passaggio del documento eccellente di Brian Goetz che descrive more about lambda translations.

Invece di generare bytecode per creare l'oggetto che implementa l'espressione lambda (ad esempio chiamando un costruttore per una classe interna), si descrive una ricetta per costruire il lambda, e delegare la costruzione effettiva al runtime linguaggio. Quella ricetta è codificata negli elenchi di argomenti statici e dinamici di un'istruzione invokeynamic.

Fondamentalmente ciò significa che il runtime nativo decide come tradurre il lambda.

Brian continua:

riferimenti metodo sono trattati allo stesso modo di espressioni lambda, se non che la maggior parte dei riferimenti metodo non devono essere private di zucchero in un nuovo metodo; possiamo semplicemente caricare un handle di metodo costante per il metodo di riferimento e passarlo al metafactory.

Quindi, lambda sono private degli zuccheri in un nuovo metodo. Per esempio.

class A { 
    public void foo() { 
     List<String> list = ... 
     list.forEach(s -> { System.out.println(s); }); 
    } 
} 

Il codice di cui sopra sarà private degli zuccheri a qualcosa di simile:

class A { 
    public void foo() { 
     List<String> list = ... 
     list.forEach([lambda for lambda$1 as Consumer]); 
    } 

    static void lambda$1(String s) { 
     System.out.println(s); 
    } 
} 

Ma, Brian spiega anche questo nel documento:

se il metodo Dezuccherato è un'istanza metodo, il ricevitore è considerato il primo argomento

Brian continua a spiegare che gli argomenti rimanenti di lambda vengono passati come argomenti al metodo indicato.

Così, con l'aiuto di this entry by Moandji Ezana, il Dezuccheraggio di compareToIgnoreCase come Comparator<String> può essere suddiviso nelle seguenti fasi:

  • Collections#sort per un List<String> aspetta una Comparator<String>
  • Comparator<String> è un'interfaccia funzionale con il metodo int sort(String, String), che equivale a BiFunction<String, String, Integer>
  • L'istanza di confronto può quindi essere fornita da un BiFunction lambda compatibile: (String a, String b) -> a.compareToIgnoreCase(b)
  • String::compareToIgnoreCase si riferisce ad un metodo di istanza che prende un argomento String, quindi è compatibile con il lambda sopra: String a diventa il ricevitore e String b diventa l'argomento metodo

Edit: dopo l'input dal PO ho aggiunto un basso livello esempio che spiega la Dezuccheraggio

+0

Grazie, tutto ciò che hai descritto è vero, ma è ancora molto il pensiero ad alto livello e l'esempio del tutorial di Oracle sono facili da capire. Il problema è ancora con il metodo compareTo che non corrisponde alla firma del metodo 'compare()' del comparatore. Se abbiamo '(a, b) -> a.compareTo (b)' e '(a, b) -> comparator.compare (a, b)' c'è una differenza piuttosto grande. Significa che il riferimento al metodo deve corrispondere alla firma di espressione lambda '(a, b) -> int', e non alla firma del metodo effettivo? – swch

+1

@slawekwch. Ho provato a "abbassare il livello" un po 'con una spiegazione di come 'compareToIgnoreCase' è * desugared * a' Comparator '. – wassgren

+0

oh questa è un'ottima spiegazione, grazie – swch

Problemi correlati