Проверка типов с помощью универсальных поставщиков и лямбд - PullRequest
0 голосов
/ 03 мая 2018

У меня есть два универсальных метода, которые предназначены для того, чтобы вызывающая сторона предоставляла параметры, соответствующие типу:

private <T> void compareValues(Supplier<T> supplier, T value) {
    System.out.println(supplier.get() == value);
}

private <T> void setValue(Consumer<T> consumer, T value) {
    consumer.accept(value);
}

Однако при их вызове компилятор по-разному рассуждает о том, что разрешено передавать в качестве параметров:

compareValues(this::getString, "Foo"); // Valid, as expected
compareValues(this::getInt, "Foo");    // Valid, but compiler should raise error
compareValues(this::getString, 1);     // Valid, but compiler should raise error

setValue(this::setString, "Foo");      // Valid, as expected
setValue(this::setInt, "Foo");         // Type mismatch, as expected
setValue(this::setString, 1);          // Type mismatch, as expected


private String getString() {
    return  "Foo";
}

private int getInt() {
    return 1;
}

private void setString(String string) {
}

private void setInt(int integer) {
}

Как получилось? Является ли компилятор слишком неуклюжим, чтобы правильно рассуждать о типах здесь, или это особенность системы типов? Если да, то какие правила приводят к такому поведению? Кроме того, как я могу создать «типобезопасную» версию compareValues ​​без добавления искусственных параметров, если это вообще возможно?

Обратите внимание, что предоставленные методы просто содержат фиктивную реализацию и не отражают код в моей реальной кодовой базе. Основное внимание здесь уделяется исключительно вызовам методов.

Ответы [ 3 ]

0 голосов
/ 03 мая 2018

Вы можете сказать, что компилятор выбирает тип пересечения, используя

javac -XDverboseResolution=deferred-inference

вывод в одном из случаев:

 instantiated signature: (Supplier<INT#1>,INT#1)void
 target-type: <none>

 where T is a type-variable:
 T extends Object declared in method <T>compareValues(Supplier<T>,T)

 where INT#1,INT#2 are intersection types:
 INT#1 extends Object,Serializable,Comparable<? extends INT#2>
 INT#2 extends Object,Serializable,Comparable<?>
0 голосов
/ 03 мая 2018

Другие упоминали, почему это происходит, поэтому вот решение, чтобы обойти проблему.

Если вы создаете универсальный класс, отделяющий передачу поставщика от передачи аргумента, вы не дадите компилятору возможность выбрать тип пересечения:

public class Comparer<T>
{
    private final Supplier<T> supplier;

    Comparer(final Supplier<T> supplier)
    {
        this.supplier = supplier;
    }

    void compare(T value)
    {
        System.out.println(supplier.get() == value);
    }
}

new Comparer<>(this::getString).compare("Foo"); // Valid, as expected
new Comparer<>(this::getInt).compare("Foo"); // Invalid, compiler error
new Comparer<>(this::getString).compare(1);  // Invalid, compiler error

Отделяя это поведение, вы также позволяете Comparer выполнять потенциально полезные действия, такие как кэширование результата Supplier.get().

0 голосов
/ 03 мая 2018

ну тут T может быть что угодно. Это синоним типа, но может быть в основном любым типом.

Таким образом, когда у вас есть compareValues(Supplier<T> supplier, T value), это означает, что поставщик может дать мне любой тип и значение, которое может быть любого типа. Так что это не дает ошибки компиляции и даже работает. В вашем методе вы можете сделать:

private <T> void compareValues(Supplier<T> supplier, T value) {
    value=supplier.get();  //It is still valid even if you give different types
    System.out.println((supplier.get() == value) +" - "+ value);
}

Что касается другого метода, он отличается, потому что вы говорите «Дайте мне потребителя, который принимает любой тип», но вы даете ему потребителя, который принимает только String.

Так вот

private void setString(String s) {

    }

не будет работать, но

private <T> void setString(T s) {

}

будет работать просто отлично.

Это похоже на то, что если у вас есть переменная типа Object, вы можете назначить ей String, но не наоборот, в более странной ситуации. Поставщик String - это <T> поставщик, но потребитель String не является <T>.

См. Эти два метода:

    private <T> void setString(T a) {
        T var=a;
        T var2="Asdf"; //This doesn't compile! cannot convert String to T
    }

    private <String> void setString2(String a) {
        String var=a;
        String var2="asd";
    }

Вы хотите потребителя типа T, который является первым методом. Но вместо этого вы пытаетесь дать потребителю тип String, который не может работать, потому что он потребляет только строки, и вам нужен метод, который может потреблять все

...