Kowariancja w target typing w referencji do metody

2

Weźmy taki kod https://ideone.com/ubV2P5 :

import java.util.*;
import java.lang.*;
import java.io.*;
import java.util.function.Supplier;

class Ideone
{
    public static void main (String[] args) throws java.lang.Exception
    {
        bar(Ideone::foo, 1.5);
    }

    static String foo(){
        return "Foo";
    }

    static <T> void bar(Supplier<T> lambda, T value){
    }
}

To co mi się nie podoba w tym kodzie, to fakt, że kompiluje się on bez problemów, chociaż wygląda tak, jakbym do metody bar przekazywał lambdę zwracającą stringa oraz double'a. O ile dobrze rozumiem, wynika to z kowariancji typu zwracanego, przez co przekazując referencję do metody foo kompilator może to sobie potraktować jako lambdę zwracającą Object i wtedy double nie powoduje błędu. Szerszy opis można znaleźć w punkcie 9. w http://cr.openjdk.java.net/~b[...]ambda/lambda-state-final.html

Pytanie brzmi: czy jest jakikolwiek sposób na zablokowanie tego? Chciałbym, żeby kompilator krzyknął, że przekazuję double'a jako drugi parametr, a idealnie powinien tam być string. Sygnatura metody bar może się zmienić, bardziej zależy mi na zachowaniu wywołania w postaci bar(Ideone::foo, 1.5);, ale nie jest to absolutnie niezbędne, przy czym nie chcę jawnie specyfikować typu parametru, więc coś w stylu Ideone.<String>bar(Ideone::foo, 1.5); odpada.

5

Obejście:

{
   rab(Ideone::foo).accept("1"); //teraz walnij double zfaniaku
}

 static <T> Consumer<T> rab( Supplier<T> lambda) {
        return x ->  {
            System.out.println("lambda to"+ lambda.getClass());
            System.out.println("value to"+ x.getClass());
        };
    }
2

Nie jestem pewien czy da się to zmienić.
Możes ograniczyć typ przy definicji metody, tzn static <T extends String> void bar(Supplier<T> lambda, T value) ale pewnie nie o to ci chodzi.
Gdyby to był argument a nie wartość zwracana, to by działało out of the box:

import java.lang.*;
import java.util.function.Consumer;

class Ideone
{
    public static void main (String[] args)
    {
        bar(Ideone::foo, 1.5);
    }

    static void foo(String x){
    }

    static <T> void bar(Consumer<T> lambda, T value){
    }
}

Coś takiego już nie zadziała.

6

Może tak:

import java.util.*;
import java.lang.*;
import java.io.*;
import java.util.function.Supplier;

class Ideone
{
    public static void main (String[] args) throws java.lang.Exception
    {
        bar(Ideone::foo,1.5);
        //bar(Ideone::foo,"1.5");
    }

    static String foo(){
        return "Foo";
    }

    static <T,U extends Supplier<T>> void bar(U lambda, T value){
        System.out.println("lambda class="+lambda.getClass());
        System.out.println("value  class="+value.getClass());
        System.out.println("lambda value class="+lambda.get().getClass());
    }
}
0

@yarel: Wygląda super, dzięki!
@jarekr000000 @Shalom: Że z consumerem działa, to wiedziałem, ale nie wpadłem na to, żeby takiego utworzyć i w ten sposób wymusić zgodność typów. Jakby jeszcze składnia wołania consumera była krótsza, ale to wołanie accept jest troszkę irytujące na dłuższą metę, niemniej jest to ciekawe rozwiązanie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0