2014-09-08 3 views
2

В качестве более описательного интерфейса, я хотел бы создать следующее:Java 8/лямбды/Струйные: Наследование Функция <T,R> приводит к проблемам с andThen() метод

@FunctionalInterface 
public interface Dial extends Function<Double,Double> {} 

Однако, когда я пытаюсь слагающим Dial экземпляров как это:

public class DialExample { 
    public Dial dialMult (double mult) { 
     return (i -> i * mult); 
    } 

    public void myTest() { 
     // Yes 
     Function<Double,Double> f1 = dialMult(3.0).andThen(dialMult(4.0)); 

     // No 
     Dial d1 = dialMult(3.0).andThen(dialMult(4.0));  
    } 
} 

код под // No комментарий дает мне следующую ошибку компиляции:

Error:(22, 40) java: incompatible types: no instance(s) of type variable(s) V exist so that java.util.function.Function conforms to lambdas.Dial

Конечно, код под комментарием // Yes работает, но он поражает цель иметь описательный интерфейс Dial.

У меня есть несколько вопросов:

  • Что это в разработке/реализации Java дженериков и лямбды, что приводит к этому вопросу?
  • Есть ли обходной путь?
  • Является ли даже хорошей практикой подкласс Function<Double,Double> как описательный интерфейс, такой как мой , или я должен избегать этого в первую очередь?
+1

«Циферблат» - это 'Функция <Двойной, двойной>', но 'Функция <Двойной, Двойной>' не обязательно является 'Цифром'. –

+2

Лучше использовать «DoubleUnaryOperator» или, по крайней мере, «UnaryOperator », а не 'Function '. Ваш интерфейс 'Dial' в основном является клоном интерфейса« DoubleUnaryOperator », но без примитивов. – bcsb1001

ответ

3

Метод default унаследованный от Function объявляется вернуть Function и что не изменяется при вызове метода default через суб- interface.Обратите внимание, что Function.andThen позволяет объединить Dial экземпляр с произвольным Function возвращает другой тип создавая Function, который не совместим с вашим Dial «ы функциональной подписи:

Function<Double,String> f=dialMult(3.0).andThen(Object::toString); 

Но если типы совпадают вы можете преобразовать Function на -The-муха:

Dial d1 = dialMult(3.0).andThen(dialMult(4.0))::apply; 

В качестве альтернативы вы можете создать свой собственный andThen меня ThOD. Обратите внимание, что этот метод не переопределяет метод Function.andThen(Function), так как это потребует ограничения допустимых параметров типа для второй функции, которая будет являться недопустимым сужением параметра. Но для объединения двух экземпляров Dial (или Dial экземпляра с UnaryOperator<Double>) он работает.

@FunctionalInterface 
public interface Dial extends UnaryOperator<Double> { 
    public default Dial andThen(UnaryOperator<Double> after) { 
     return d->after.apply(apply(d)); 
    } 
} 

Так как кажется, объяснение, почему метод выше не переопределяет Function.andThen(Function) был слишком коротким, так что позвольте мне вдаваться в подробности:

Как сказано в начале, метод andThen унаследовал от Function позволяет использовать функцию параметра с произвольным типом возврата, которая была проиллюстрирована примером с использованием функции, которая возвращает String, а не Double. При применении параметров типа в Dial определяет, для его супер типа, эффективная подпись становится:

public interface Dial extends UnaryOperator<Double> { 
    @Override 
    public default <R> 
    Function<Double, R> andThen(Function<? super Double, ? extends R> after) { 
     return UnaryOperator.super.andThen(after); 
    } 
} 

при переопределении этого метода, вы можете объявить более конкретный тип возвращаемого значения, т.е. суб-интерфейс Function, однако, его должен соответствовать типу Function<Double, R>, который не относится к Dial, поскольку Dial распространяет Function<Double,Double>. Для этого необходимо ограничить параметр, чтобы разрешить только Double для R, что недопустимо. Чтобы быть допустимой заменой Function<Double, R>, суб-интерфейс должен быть сам по себе, чтобы быть параметризуемым с помощью <R>.

+0

Вторая половина вашего ответа («Альтернативно ...») отлично работала и именно так я и искал. –

+2

Обратите внимание, что вы также можете сделать простое ковариантное переопределение «andThen», где вы уточняете только тип возвращаемого значения и сохраняете аргументы постоянными. Тогда это действительно переопределение, просто с ковариантным типом возврата (эта функция была добавлена ​​в Java 5 и одинаково хорошо применима к методам по умолчанию). –

+2

@Brian Goetz: как объяснено в моем ответе, это именно то, что вы можете * не *. 'andThen' принимает произвольную функцию с произвольным типом возвращаемого значения и должен возвращать' Function' с возвращаемым типом, совместимым со второй функцией. Поэтому вы не можете переопределить его, чтобы вернуть экземпляр 'Dial', который неизменно расширяет' Function '. Это потребовало бы ограничения параметра 'Function ' '' Функция ', что невозможно. – Holger

4

Определение andThen является

default <V> Function<T,V> andThen(Function<? super R,? extends V> after) 

Что вам нужно это

default <V> TypeOfThis andThen(Function<? super R,? extends V> after) 

, который по крайней мере, трудно выразить в Java и является менее гибким.

Не могли бы вы попытаться переустановить andThen в циферблате, используя Циферблат как возвращаемое значение? Может работать ...

1

Подпись andThen находится в here. В нем четко сказано, что andThen возвращает Function, а не Dial.

Java не имеет никакого способа сделать типы безопасных псевдонимов типами.

+0

Как. Я не вижу способа сделать это без изменения функционального интерфейса, добавив еще один общий параметр, который будет представлять собой реальный производный интерфейс. Это будет выглядеть уродливо. – talex

Смежные вопросы