2015-06-14 10 views
8

Come sovraccaricare una funzione con parametro generico in Java 8?Metodo generico per eseguire un'operazione di riduzione della mappa. (Java-8)

public class Test<T> { 

    List<T> list = new ArrayList<>(); 

    public int sum(Function<T, Integer> function) { 
     return list.stream().map(function).reduce(Integer::sum).get(); 
    } 


    public double sum(Function<T, Double> function) { 
     return list.stream().map(function).reduce(Double::sum).get(); 
    } 
} 

Error: java: name clash: sum(java.util.function.Function<T,java.lang.Double>) and sum(java.util.function.Function<T,java.lang.Integer>) have the same erasure

+2

Uguali a Java 7, 6 e 5: non è possibile . Questo è quello che ti dice il messaggio. –

risposta

3

L'esempio che presenti nel tuo la domanda non ha nulla a che fare con Java 8 e tutto ciò che riguarda il modo in cui i generici funzionano in Java. Function<T, Integer> function e Function<T, Double> function passeranno attraverso type-erasure quando compilati e verranno trasformati in Function. La regola generale per l'overloading del metodo è di avere numero, tipo o sequenza di parametri diversi. Poiché entrambi i metodi si trasformeranno per prendere un argomento Function, il compilatore si lamenta di ciò.

Detto questo, srborlongan ha già fornito un modo per risolvere il problema. Il problema con questa soluzione è che devi continuare a modificare la tua classe Test per ogni tipo di operazione (addizione, sottrazione, ecc.) Su diversi tipi (intero, doppio, ecc.). Una soluzione alternativa sarebbe quella di utilizzare method overriding invece di method overloading:

cambiare la classe Test un po 'come segue:

public abstract class Test<I,O extends Number> { 

    List<I> list = new ArrayList<>(); 

    public O performOperation(Function<I,O> function) { 
     return list.stream().map(function).reduce((a,b)->operation(a,b)).get(); 
    } 

    public void add(I i) { 
     list.add(i); 
    } 

    public abstract O operation(O a,O b); 
} 

creare una sottoclasse di Test che aggiungerà due Integer s.

public class MapStringToIntAddtionOperation extends Test<String,Integer> { 

    @Override 
    public Integer operation(Integer a,Integer b) { 
     return a+b; 
    } 

} 

codice client può quindi utilizzare il codice precedente come segue:

public static void main(String []args) { 
    Test<String,Integer> test = new MapStringToIntAddtionOperation(); 
    test.add("1"); 
    test.add("2"); 
    System.out.println(test.performOperation(Integer::parseInt)); 
} 

Il vantaggio dell'utilizzo di questo approccio è che la classe Test è in linea con il principio open-closed. Per aggiungere una nuova operazione come la moltiplicazione, tutto ciò che devi fare è aggiungere una nuova sottoclasse di Test e override al metodo operation per moltiplicare due numeri. Club con lo schema Decorator e puoi anche ridurre al minimo il numero di sottoclassi che devi creare.

Nota L'esempio in questa risposta è indicativo. Ci sono molte aree di miglioramento (come rendere l'interfaccia funzionale Test invece di una classe astratta) che vanno oltre lo scopo della domanda.

+0

È molto più semplice usare solo nomi di metodi diversi :) 'sumInt, sumDouble' – ZhongYu

+0

Questo può essere facilmente organizzato facendo' operation' un metodo 'protected' in' Test', creando un metodo 'sumXYZ' nella sottoclasse e chiamando il metodo overriden 'operation' in esso. :) Ma come ho detto, ci sono molte aree di miglioramento che vanno oltre lo scopo della risposta .. – CKing

6

Benji Weber once wrote of a way to circumvent this. Quello che dovete fare è quello di definire le interfacce funzionali personalizzate che estendono i tipi per i parametri:

public class Test<T> { 

    List<T> list = new ArrayList<>(); 

    @FunctionalInterface 
    public interface ToIntFunction extends Function<T, Integer>{} 
    public int sum(ToIntegerFunction function) { 
     return list.stream().map(function).reduce(Integer::sum).get(); 
    } 


    @FunctionalInterface 
    public interface ToDoubleFunction extends Function<T, Double>{} 
    public double sum(ToDoubleFunction function) { 
     return list.stream().map(function).reduce(Double::sum).get(); 
    } 
} 

Un altro modo è quello di utilizzare java.util.function.ToIntFunction e java.util.function.ToDoubleFunction:

public class Test<T> { 

    List<T> list = new ArrayList<>(); 

    @FunctionalInterface 
    public int sum(ToIntFunction function) { 
     return list.stream().mapToInt(function).sum(); 
    } 

    public double sum(ToDoubleFunction function) { 
     return list.stream().mapToDouble(function).sum(); 
    } 
} 
+1

Il tuo suggerimento di utilizzare un'interfaccia wrapper è buono ma non "pulito" perché "Test" deve essere modificato ogni volta che deve essere introdotta una nuova operazione o un nuovo tipo. Con solo due tipi (Integer o Doble) e due operazioni (somma e moltiplica), sono necessari 4 metodi. Per non parlare delle interfacce wrapper che devi creare per ogni tipo. Questa è un'esplosione dell'API. +1 per il collegamento però. – CKing

+1

Questo non funziona molto bene :) Vedi per un esempio simile [Comparatore] (https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html) 'comparingDouble (ToDoubleFunction) ',' comparingInt (ToIntFunction) '- I metodi hanno nomi diversi, perché l'overloading non è una buona idea. – ZhongYu

0

@srborlongan 's soluzione non funziona molto bene :)

Vedi un esempio simile - Comparator metodi - comparingDouble(ToDoubleFunction), comparingInt(ToIntFunction), ecc I metodi hanno nomi diversi, in quanto il sovraccarico non è una buona idea qui.

Il motivo è che quando si esegue sum(t->{...}), il compilatore non è in grado di dedurre quale metodo chiamare; in realtà ha bisogno di risolvere prima l'overloading del metodo, di scegliere un metodo, prima di inferire il tipo di espressione lambda implicita (basata sulla firma di quel metodo)

Questo è deludente. Nella fase precedente, Java8 aveva un motore di inferenza più sofisticato e Comparator aveva sovraccaricato i metodi comparing(); e sum(t->{...}) sarebbe anche correttamente dedotto. Purtroppo, hanno deciso di semplicemente :(Ed eccoci ora

regola empirica per sovraccaricare i metodi con argomenti funzionali:. I arietà delle interfacce funzionali devono essere diversi, a meno che entrambi sono 0.

// OK, different arity 
m1(X->Y) 
m1((X1, X2)->Y) 

// not OK, both are arity 1 
m2(X->Y) 
m2(A->B) 

    m2(t->{...}); // fail; type of `t` cannot be inferred 

// OK! both are arity 0 
m3(()->Y) 
m3(()->B) 

Il motivo per cui l'overload con arity 0 è OK è che le espressioni lambda non saranno implicite - tutti i tipi di argomenti sono noti (perché non c'è argomento!), Non abbiamo bisogno di informazioni contestuali per inferire il tipo lambda

m3(()-> return new Y()); // lambda type is()->Y 
m3(()-> return new B()); // lambda type is()->B 
Problemi correlati