2015-06-14 16 views
8

Jak przeciążyć funkcję za pomocą parametru ogólnego w Java 8?Ogólna metoda wykonywania operacji zmniejszania mapy. (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

Tak samo, jak Java 7, 6 i 5: nie można . Tak mówi ci ten komunikat. –

Odpowiedz

3

Przykład, który prezentujesz w swoim Pytanie nie ma nic wspólnego z Javą 8 i wszystkim, co ma wspólnego z tym, jak generics działa w Javie. Function<T, Integer> function i Function<T, Double> function przejdą type-erasure po kompilacji i zostaną przekształcone na Function. Regułą dla przeciążania metod jest posiadanie innej liczby, typu lub sekwencji parametrów. Ponieważ obie metody zostaną przekształcone, aby przyjąć argument Function, kompilator narzeka na to.

Zgodnie z powyższym, srborlongan podał już jeden sposób rozwiązania problemu. Problem z tym rozwiązaniem polega na tym, że musisz nadal modyfikować klasę Test dla każdego rodzaju operacji (dodawania, odejmowania itp.) Na różnych typach (liczba całkowita, liczba podwójna itd.). Alternatywnym rozwiązaniem byłoby wykorzystanie method overriding zamiast method overloading:

Zmień klasa Test nieco następująco:

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); 
} 

utworzyć podklasę Test że doda dwa Integer s.

public class MapStringToIntAddtionOperation extends Test<String,Integer> { 

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

} 

kod Klient może następnie wykorzystać powyższy kod w następujący sposób:

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)); 
} 

Zaletą stosowania tej metody jest to, że klasa Test jest zgodny z zasadą open-closed. Aby dodać nową operację, taką jak mnożenie, wystarczy dodać nową podklasę metody Test i override do pomnożenia dwóch liczb. Wymieszaj to z wzorcem Decorator i możesz nawet zminimalizować liczbę podklas, które musisz utworzyć.

Uwaga Przykład w tej odpowiedzi ma charakter orientacyjny. Istnieje wiele obszarów wymagających ulepszeń (takich jak sprawienie, że Test jest funkcjonalnym interfejsem zamiast klasy abstrakcyjnej), które wykraczają poza zakres pytania. Zobacz

+0

Łatwiej jest po prostu użyć różnych nazw metod :) 'sumInt, sumDouble' – ZhongYu

+0

Można to łatwo uporządkować, tworząc' operację' 'metodą' protected' w 'Test', tworząc metodę' sumXYZ' w podklasie i wywołując zastąpiono w nim metodę 'operation'. :) Ale tak jak powiedziałem, istnieje wiele obszarów poprawy, które wykraczają poza zakres odpowiedzi .. – CKing

6

Benji Weber once wrote of a way to circumvent this. Co trzeba zrobić, to określić niestandardowy funkcjonalne interfejsy rozszerzenia rodzajów dla parametrów:

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(); 
    } 
} 

Innym sposobem jest użycie java.util.function.ToIntFunction i java.util.function.ToDoubleFunction zamiast:

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

Twoja sugestia użycia interfejsu otoki jest dobra, ale nie "czysta", ponieważ 'Test' musi być modyfikowany za każdym razem, gdy nowa operacja lub nowy typ musi zostać wprowadzony. Tylko dwa typy (Integer lub Doble) i dwie operacje (suma i mnożenie) wymagają 4 metod. Nie wspominając o interfejsach opakowujących, które musisz utworzyć dla każdego typu. To eksplozja API. +1 dla linku. – CKing

+1

To nie działa zbyt dobrze :) Zobacz podobny przykład [Komparator] (https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html) 'comparingDouble (ToDoubleFunction) ',' comparingInt (ToIntFunction) '- Metody mają różne nazwy, ponieważ przeciążanie nie jest dobrym pomysłem. – ZhongYu

0

@srborlongan „s rozwiązanie nie będzie działać bardzo dobrze :)

podobny przykład - Comparator metod - comparingDouble(ToDoubleFunction), comparingInt(ToIntFunction) itp metody mają różne nazwy, ponieważ przeciążenie nie jest dobrym pomysłem tutaj.

Powodem jest, że kompilator nie może określić, która metoda wywołać; w rzeczywistości musi najpierw rozwiązać problem przeciążania metod, wybrać jedną z metod, zanim wyciągnie typ niejawnego wyrażenia lambda (w oparciu o sygnaturę tej metody). Jest to niezadowalające. Na wcześniejszym etapie Java8 posiadał bardziej zaawansowany mechanizm wnioskowania, a Comparator przeładowywał metody comparing(); i sum(t->{...}) został również poprawnie wywnioskowany. Niestety, postanowili po prostu go :(I tu jesteśmy teraz

zasada dla przeciążenia metody z argumentów funkcjonalnych. Z arities interfejsów funkcjonalnych musi być inny, chyba że obie są 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) 

Powodem, dla którego przeciążenie z aritem 0 jest OK, jest to, że wyrażenia lambda nie będą niejawne - wszystkie typy argumentów są znane (ponieważ nie ma argumentów!), Nie potrzebujemy informacji kontekstowych do wnioskowania typu lambda

m3(()-> return new Y()); // lambda type is()->Y 
m3(()-> return new B()); // lambda type is()->B 
Powiązane problemy