あなたのエラーの鍵は、のタイプの一般的な宣言でありますF
:F extends Function<T, R>
。機能しないステートメントはnew Builder<MyInterface>().with(MyInterface::getNumber, 4L);
、次のとおりです。最初に、新しいがありBuilder<MyInterface>
ます。したがって、クラスの宣言はを意味しT = MyInterface
ます。の宣言に従って、はwith
でF
なければなりません。Function<T, R>
これは、Function<MyInterface, R>
この状況ではです。したがって、パラメーターは(メソッド参照andで満たされる)asパラメーターをgetter
取り、を返す必要があります。これは、関数の2番目のパラメーターと同じ型である必要があります。さて、これがあなたのすべてのケースに当てはまるかどうか見てみましょう:MyInterface
MyInterface::getNumber
MyInterface::getLong
R
with
// T = MyInterface, F = Function<MyInterface, Long>, R = Long
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L explicitly widened to Number
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L implicitly widened to Number
new Builder<MyInterface>().<Function<MyInterface, Number>, Number>with(MyInterface::getNumber, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L implicitly widened to Number
new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Long
// F = Function<T, not R> violates definition, therefore compilation error occurs
// Compiler cannot infer type of method reference and 4L at the same time,
// so it keeps the type of 4L as Long and attempts to infer a match for MyInterface::getNumber,
// only to find that the types don't match up
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
この問題は、次のオプションで「修正」できます。
// stick to Long
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// stick to Number
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// explicitly convert the result of getNumber:
new Builder<MyInterface>().with(myInstance -> (Long) myInstance.getNumber(), 4L);
// explicitly convert the result of getLong:
new Builder<MyInterface>().with(myInterface -> (Number) myInterface.getLong(), (Number) 4L);
この点を超えて、それはほとんどの場合、特定のアプリケーションのコードの複雑さを減らすオプションの設計上の決定であるため、最適なものを選択してください。
キャストせずにこれを実行できない理由は、Java言語仕様の次のとおりです。
ボクシング変換は、プリミティブ型の式を対応する参照型の式として扱います。具体的には、次の9つの変換はボクシング変換と呼ばれます。
- ブール型からブール型へ
- バイト型からバイト型へ
- タイプショートからタイプショートへ
- char型からCharacter型へ
- int型からInteger型へ
- ロングタイプからロングタイプへ
- float型からFloat型へ
- タイプダブルからタイプダブルへ
- null型からnull型へ
明らかなように、longからNumberへの暗黙のボックス化変換はありません。また、LongからNumberへの拡大変換は、コンパイラがLongではなくNumberを必要とすることが確実な場合にのみ発生します。数値を必要とするメソッド参照とLongを提供する4Lの間に矛盾があるため、コンパイラは(何らかの理由で???)Longが数値であるという論理的飛躍を実現できず、それF
がであると推定できませんFunction<MyInterface, Number>
。
代わりに、関数シグネチャを少し編集することで問題を解決することができました。
public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
return null;//TODO
}
この変更後、次のことが起こります。
// doesn't work, as it should not work
new Builder<MyInterface>().with(MyInterface::getLong, (Number), 4L);
// works, as it always did
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// works, as it should work
new Builder<MyInterface>().with(MyInterface::getNumber, (Number)4L);
// works, as you wanted
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
編集:
少し時間を費やした後、ゲッターベースのタイプセーフティを適用するのは面倒です。以下は、setterメソッドを使用してビルダーのタイプセーフを適用する実用的な例です。
public class Builder<T> {
static public interface MyInterface {
//setters
void number(Number number);
void Long(Long Long);
void string(String string);
//getters
Number number();
Long Long();
String string();
}
// whatever object we're building, let's say it's just a MyInterface for now...
private T buildee = (T) new MyInterface() {
private String string;
private Long Long;
private Number number;
public void number(Number number)
{
this.number = number;
}
public void Long(Long Long)
{
this.Long = Long;
}
public void string(String string)
{
this.string = string;
}
public Number number()
{
return this.number;
}
public Long Long()
{
return this.Long;
}
public String string()
{
return this.string;
}
};
public <R> Builder<T> with(BiConsumer<T, R> setter, R val)
{
setter.accept(this.buildee, val); // take the buildee, and set the appropriate value
return this;
}
public static void main(String[] args) {
// works:
new Builder<MyInterface>().with(MyInterface::Long, 4L);
// works:
new Builder<MyInterface>().with(MyInterface::number, (Number) 4L);
// compile time error, as it shouldn't work
new Builder<MyInterface>().with(MyInterface::Long, (Number) 4L);
// works, as it always did
new Builder<MyInterface>().with(MyInterface::Long, 4L);
// works, as it should
new Builder<MyInterface>().with(MyInterface::number, (Number)4L);
// works, as you wanted
new Builder<MyInterface>().with(MyInterface::number, 4L);
// compile time error, as you wanted
new Builder<MyInterface>().with(MyInterface::number, "blah");
}
}
オブジェクトを作成するタイプセーフ機能を提供します。うまくいけば、将来のある時点で、ビルダーから不変のデータオブジェクトを返すことができます(おそらくtoRecord()
インターフェイスにメソッドを追加し、ビルダーをとして指定することによりBuilder<IntermediaryInterfaceType, RecordType>
)。したがって、結果のオブジェクトが変更されることを心配する必要さえありません。正直なところ、タイプセーフなフィールドフレキシブルビルダーを入手するのに多大な労力を必要とすることは絶対に残念ですが、いくつかの新機能、コード生成、または煩わしい量のリフレクションなしでは、おそらく不可能です。
MyInterface
か?