時々、Haberの記事で、C ++テンプレートでできることについて説明しています: 有限状態マシン 、 ラムダ計算 、 チューリングマシンなど 。
Javaのパラメータ化された型は、伝統的にC ++テンプレートのパロディと見なされます(それらを比較することさえ何らかの形で間違っているという事実にもかかわらず)、そしてこの理由は理解しやすいです。 それにもかかわらず、すべてがそれほど悪いわけではなく、十分なRAMがある場合にのみ、Javaコンパイラが型チェック中に計算を実行することを強制できます。 これを行う特定の方法は、 この美しい出版物で 2016年11月に説明されました。 説明したいと思います。
シードとして、次のコードを提供します。 彼は正しいですか? 結果を推測した場合は、コンパイルして確認することをお勧めします。
class Sample { interface BadList<T> extends List<List<? super BadList<? super T>>> {} public static void main(String[] args) { BadList<? super String> badList = null; List<? super BadList<? super String>> list = badList; } }
コンパイラは、スタックのサイズに関係なくjava.lang.StackOverflowError
をスローしjava.lang.StackOverflowError
。
これらの理由を理解することは有用であり、チューリングマシンはどこで動作するのか、コンパイラが正確にこのように動作する理由を理解します(バグとは呼びません)。
1.共分散と反分散について
まず、基本についてお話しましょう。 パラメーター化された型を使用する最も簡単な方法は、次のようなものです。
List<Integer> integerList = new ArrayList<>(); List<Number> numberList = new ArrayList<>();
通常、これで十分ですが、問題があります-要素のタイプは固定されています。 List<Number>
必要な場所でintegerList
オブジェクトを使用することはできません。そのようなリストにDouble
型の要素を挿入すると、その整合性がintegerList
れるためです。 逆に、 List<Integer>
が期待される場所でnumberList
使用することはできません。これは、整数以外の数字が入った場合にリストアイテムを読み取るときにClassCastException
するためです。 もちろん、2番目の問題を解決するには、この小さなトリックを使用できます。
<T extends Number> Number firstNum(List<T> xs) { return xs.get(0); }
このアプローチでは、追加のT
型を導入する必要があるため、これをトリックと呼びますT
さらに、この方法での挿入の問題は原則として解決できません。 私の意見では、この方法のより適切なバージョンは次のようになります。
Number firstNum(List<? extends Number> xs) { return xs.get(0); }
そのような型は、パラメータが共変であると言われています。 これは、 A
がB
サブタイプである場合、 List<A>
( List<? extends A>
)はList<? extends B>
サブタイプであることを意味しList<? extends B>
List<? extends B>
ます。つまり、それらの間で割り当てが許可されます。 副作用-そのようなリストは読み取り専用になり、オブジェクトの整合性に違反することはできません。
録音ケースには、別の構造があります。
void addOne(List<? super Integer> xs) { xs.add(1); }
List<? super Integer>
List<? super Integer>
は、整数を追加できるリストのコレクションを表します。つまり、そのサブタイプはList<Integer>
、 List<Number>
およびList<Object>
(さらにList<? super Integer>
、 List<? super Number>
およびList<? super Object>
)。 このようなクラスはパラメーターが反変です。つまり、 A
がB
スーパータイプである場合、 List<A>
はList<? super B>
サブタイプList<? super B>
List<? super B>
。 状況は共変の場合とまったく逆です。
反変型に注目しましょう。
2.反変型のサブセットの形式化
それでは、Javaのどのタイプが私たちにとって興味があるかを判断しましょう。 概念を帰納的に紹介します。
- タイプtのパラメーターはタイプです。
- Cが1つのパラメーターを持つクラスで、 tがtypeの場合、エントリ C t -これもタイプです。 そのようなレコードの場合は省略され
C<? super t>
:C<? super t>
C<? super t>
。
より複雑な型は次のように記述されます C 1 C 2。。。C n t それはこのコードに対応しています:
C1<? super C2<? super ...Cn<? super t>...>>
継承の関係を定義します。 それで、 タイプがあります
interface C<T> extends C1<C2<? super ...Cn<? super t>...>>
interface C<T> extends C1<C2<? super ...Cn<? super t>...>>
、この場合、パラメーターX
持たない任意の型またはクラスについて、次の継承関係が成り立つと言います。 C X < ::C 1、C 2。。。C n x 。 記号 < ::* 関係の推移的閉包を示す < :: (つまり、直接に加えて、クラスのチェーンを介した間接的な継承)。
関係がある < ::* 有用な特性が1つあります-非周期性です。 Javaには多重継承がないため、次のように記述できます。
- もし T < ::* C U そして T < ::* C U " それから u = u ′ ;
- 特に、 C U < ::* C U " それから u = u ′ 。
これで、最終的に関係を入力できます。 < : 」
- タイプ
T
については、それが適切なサブタイプであると仮定します。 T<:T 。 - 継承関係が満たされている場合 CT1<::∗C1C2...CnT1 そして、それは知られています T2<:T1 その後実行 CT1<:C1C2...CnT2 (注文と間違われませんでした T1 そして T2 、これはすべて反則です)。
この時点で、重大な制限が表示されます。 態度に <: Javaのサブタイプを決定する標準的な方法と一致したため、段落2でn
の値n
奇数であることが必要です。
私は完全に正式な証明をしません、それは不適切に長いでしょう。 例を挙げて状況を検討する方が正しいでしょう。
n = 1 :
interface S<T> extends C<T> {}
この場合、 S<? super X>
S<? super X>
はC<? super X>
サブタイプC<? super X>
C<? super X>
、これはパラメーターT
直接の置換であるため
n = 3 :
interface S<T> extends C1<C2<C3<? super T>>> {}
それが真実であることを証明しましょう S X < :C 1 C 2 C 3 X :
- タイプを取る Z そのような X < : Z ;
- それに続く C 3 Z < :C 3 X ;
- したがって C 2 C 3 X < :C 2 C 3 Z ;
- したがって C1C2C3Z<:C1C2C3X ;
-
C1<C2<? super C3<? super Z>>>
C1<C2<? super C3<? super Z>>>
C1<C2<? super C3<? super Z>>>
それはサブタイプです C1C2C3Z 、それはサブタイプでもあります C1C2C3X ; - つまり、
S<Z>
はサブタイプです C1C2C3X ; -
Capture#1 of (? super X)
Z
として考えると、真実が得られます SX<:C1C2C3X 。 この結論は、JLS 4.10.2のおかげで有効です。
ジェネリック型宣言がある場合 C<F1、...、Fn> (n> 0)、パラメーター化された型の直接のスーパータイプ C<R1、...、Rn> ここで、少なくとも1つ Ri(1 leqi leqn) はワイルドカード型の引数であり、パラメータ化された型の直接のスーパータイプです C<X1、...、Xn> これは、キャプチャ変換を適用した結果です C<R1、...、Rn> (§5.1.10)
n = 2 :
interface S<T> extends C1<C2<? super T>> {}
と仮定する SX<:C1C2X :
- この場合、タイプ
Z = Capture of (? super X)
(つまり、 X<:Z )S<Z>
がサブタイプであるようなもの C1C2X ; - この場所は議論の余地があるように見えるかもしれません。
S<Z>
はC1<C2<? super Z>>
サブタイプだからだと思いC1<C2<? super Z>>
C1<C2<? super Z>>
、次にC1<C2<? super Z>>
C1<C2<? super Z>>
もサブタイプです C1C2X ; - 前の段落から C2X<:C2Z ;
- したがって Z<:X それは確かに真実ではありません。 矛盾が生じました。
n> 2の場合も同様に考慮されます。
これで、後で証明される次の定理を述べることができます。
定理1
与えられた2つのタイプのアルゴリズムはありません T1 そして T2 ステートメントが真かどうかを判断できます T1<:T2 。
つまり、Javaでは一般に 、あるタイプが別のタイプのサブタイプであるかどうかを判断することは できません 。
3.チューリングマシンとは
チューリングマシン tau 5つです (Q、qI、qH、\シグマ、\デルタ) どこで Q 状態の有限集合です qI inQ 初期状態です qH inQ 最終状態です \シグマ 最後のアルファベットであり、 \ delta:Q \ times \ Sigma_ \ bot \ rightarrow Q \ times \ Sigma \ times \ {\ bf {L}、\ bf {S}、\ bf {R} \} 遷移関数です。 \ボット に含まれていない追加文字です \シグマ 。
チューリングマシンの構成は4 (q、 alpha、b、 gamma) その中で q inQ 現在の状態は 、 alpha in Sigma∗ テープの左側 、 b in Sigma bot 現在のキャラクターであり、 gamma in\シグマ∗ -これはテープの右側です。
機械ステップ tau 次のようにそれ自体で多くの構成を表示します。
- (q、 alphaa、b、 gamma) rightarrow(q′、 alpha、a、b′ gamma) のために delta(q、b)=(q′、b′、 bfL) ;
- (q、 alpha、b、 gamma) rightarrow(q′、 alpha、b′、 gamma) のために delta(q、b)=(q′、b′、 bfS) ;
- (q、 alpha、b、c gamma) rightarrow(q′、 alphab′、c、 gamma) のために delta(q、b)=(q′、b′、 bfR) 。
境界の場合も考慮されます(記号 \イプシロン ここでは空の文字列を意味します)。 行の終わり(左または右)に達すると、文字が自動的に追加されることを示しています \ボット :
- (q、 epsilon、b、 gamma) rightarrow(q′、 epsilon、 bot、b′ gamma) のために delta(q、b)=(q′、b′、 bfL) ;
- (q、 alpha、b、 epsilon)\右矢印(q′、 alphab′、 bot、 epsilon) のために delta(q、b)=(q′、b′、 bfR) 。
インレットマシンの起動 alphaI 構成から始まる一連のステップです (qI、\イプシロン、\ボット、 alphaI) 。 もし tau 届く qH それから私達は機械を言う tau 入り口で終了( 停止 ) alphaI 。 遷移関数で次のステップを実行できない場合、マシンは tau 入り口で休憩 alphaI 。
4.サブタイピングマシン
私たちが持っている概念を一緒にリンクしてみましょう。 目標は、チューリングマシンの実行手順をJavaの型チェックプロセスと比較することです。 このリクエストがあるとします:
C1C2...CmZ<:D1D2...DnZ
この声明は、その真実または虚偽を証明するために提案されています。 便宜上、録音の2つの代替形式を紹介します。
beginarrayll&ZCmCm−1...C1 blacktriangleleftD1D2...DnZ=&ZDnDn−1...D1 blacktrianglerightC1C2...CmZ end配列
2種類の実行ルールを導入します cdot rightsquigarrow cdot 私たちの車の場合:
- (ZCm...C1 blacktriangleleftZ) rightsquigarrow bullet もし C1...CmZ<::∗Z ;
- (ZCm...C1 blacktriangleleftD1...DnZ) rightsquigarrow(ZEp...E2 blacktrianglerightD2...DnZ) もし C1...CmZ<::∗D1E2...EpZ 。
bullet -これはhaltingと呼ばれる特別な設定です。
継承ルールがあることに注意してください C1t<::D1E2...Ept および置換タイプ t:=C2...CmZ 、次の実行ルールを取得します。
beginarrayll&ZCm...C2C1 blacktriangleleftD1D2...DnZ rightsquigarrow&ZCm...C2Ep...E2 blacktrianglerightD2...DnZ endarray
また、使用されるタイプの矛盾により、次のルールが当てはまります。
beginarrayll&ZCm...C1N blacktriangleleftND1...DnZ rightsquigarrow&ZCm...C1 blacktrianglerightD1...DnZ endarray
導入されたルールを使用して、記事の最初から例のタイプをチェックするときに何が起こるかを理解できます(これらは、反変タイプの選択されたサブセットでJavaタイプをチェックするアルゴリズムに完全に対応します)。 継承関係を設定します BadListt<::リストリストBadListt そしてリクエスト BadListString<:リストBadListString 。
実行ルールのチェーンは次のとおりです。
beginarrayll&StringBadList blacktriangleleftListBadListString rightsquigarrow&StringBadListListList blacktriangleleftListBadListString rightsquigarrow&StringBadListList blacktrianglerightBadListString rightsquigarrow&StringBadListList blacktrianglerightListListBadListString rightsquigarrow&StringBadList blacktriangleleftListBadListString rightsquigarrow&... endarray
ご覧のとおり、型チェックはループされています。これは、コンパイル中のスタックオーバーフローが原因です。 一般的に、説明されているプロセスは次のように表現できます。
表現 C1...CmZ<:D1...DnZ 完了プロセスがある場合にのみtrue (ZCm...C1 blacktriangleleftD1...DnZ) rightsquigarrow∗ bullet 。
より複雑な例
次のクラステーブルを検討してください。
beginarrayrllrllQLt&<::&LNQLLNt&QRt&<::&LNQRLNtQLt&<::&EQLRNt&QRt&<::&EQRLNtEt&<::&QLRNQREEt&Et&<::&QRLNQLEEt endarray
interface N<T> {} interface L<T> {} interface QLR<T> {} interface QRL<T> {} interface E<T> extends QLR<N<? super QR<? super E<? super E<? super T>>>>>, QRL<N<? super QL<? super E<? super E<? super T>>>>> {} interface QL<T> extends L<N<? super QL<? super L<? super N<? super T>>>>>, E<QLR<? super N<? super T>>> {} interface QR<T> extends L<N<? super QR<? super L<? super N<? super T>>>>>, E<QRL<? super N<? super T>>> {}
入り口でマシンの実行を監視します QREEZ<:LNLNEEZ (短縮版):
また、この要求は型チェックのループにつながりますが、現在ではすでに重要なサイクルです。 その場しのぎのテープに、両方向の「三角形」の本格的な放浪を実装しました。 次のコードを使用して、タイプチェックがループしていることを確認できます。
interface Z {} class Main { L<? super N<? super L<? super N<? super E<? super E<? super Z>>>>>> doit(QR<? super E<? super E<? super Z>>> v) { return v; } }
5.チューリングマシンの構築
チューリングマシンを持つ tau および入力テープ alphaI 、ビルドタイプ t1 そして t2 そのような t1<:t2 場合にのみ tau 入り口で止まる alphaI 。
各州について qs inQ 6つのクラスを紹介します。 QwLs 、 QwRs 、 QLs 、 QRs 、 QLRs そして QRLs ; 各キャラクター a in\シグマ クラスを作ります La 。 記号 \ボット 便宜上チューリングマシンの定義から \# 、クラスに対応します L\# 。 また、4つのクラスを紹介します。 N 、 E 、 ML そして MR 。
最後の例のように見えます。 次に、それらの意味のおおよその説明と、対応するチューリングマシンの遷移関数をエミュレートする特定の方法を説明します。
QwLs そして QwRs 基本的にタイプです QL そして QR 最後の例から。 ほとんどの場合、彼らはテープに沿ってさまよいます。 型の意味 QLRs そして QRLs また保存:テープの最後のターンに必要です。 唯一の例外は QwXs ( X それは L または R )対応する MX :彼は「に変わる」 QX 。 QwXH -特別な方法で処理された最終状態のクラス。
完全な説明は次のようになります。
\ begin {array} {rllrlll} Q_s ^ {wL} t&<::&L_aNQ_s ^ {wL} L_aNt&Q_s ^ {wR} t&<::&L_aNQ_s ^ {wR} L_aNt&\ forall q_s \ in Q、a \ in \ Sigma \ cup \ {\#\} \\ Q_s ^ {wL} t&<::&M ^ RNQ_s ^ {wL} M ^ RNt&Q_s ^ {wR} t&<::&M ^ LNQ_s ^ {wR} M ^ LNt&\ forall q_s \ in Q \\ Q_s ^ {wL} t&<::&M ^ LNQ_s ^ Lt&Q_s ^ {wR} t&<::&M ^ RNQ_s ^ Rt&\ forall q_s \ in Q \\ Q_s ^ {wL} t&<::&EQ_s ^ {LR} Nt&Q_s ^ {wR} t&<::&EQ_s ^ {RL} Nt&\ forall q_s \ in Q \ setminus \ {q_H \} \\ Q_H ^ {wL} t&<::&EEZ&Q_H ^ {wR} t&< ::&EEZ \\ Et&<::&Q_s ^ {LR} NQ_s ^ {wR} EEt&Et&<::&Q_s ^ {RL} NQ_s ^ {wL} EEt&\ forall q_s \ in Q \\ \ end {array}
QLs そして QRs 次の実行ステップの時間が来たことをマシンに示します。 したがって、各ルールについて \デルタ(q1、a1)\右矢印(q2、a2、 bfX) チューリングマシンで tau 特別な継承関係を構築しています。 同時に、クラスを操作するための特別なルールを忘れないでください。 L\# :
beginarrayrlllQRst&<::&LaNQwRs′LbNMLNt& forall(qs、a) rightarrow(qs′、b、 bfL)QRst&<::&LaNQwRs′MLNLbNt& forall(qs、a) rightarrow(qs′、b、 bfS)QRst&<::&LaNQwRs′MRNLbNt& forall(qs、a) rightarrow(qs′、b、 bfR)QRst&<::&L\#NQwRs′L\#NLbNMLNt& forall(qs、 bot) rightarrow(qs′、b、 bfL)QRst&<::&L\#NQwRs′L\#NMLNLbNt& forall(qs、 bot) rightarrow(qs′、b、 bfS)QRst&<::&L\#NQwRs′L\#NMRNLbNt& forall(qs、 bot) rightarrow(qs′、b、 bfR) endarray
beginarrayrlllQLst&<::&LaNQwLs′MLNLbNt& forall(qs、a) rightarrow(qs′、b、 bfL)QLst&<::&LaNQwLs′MRNLbNt& forall(qs、a) rightarrow(qs′、b、 bfS)QLst&<::&LaNQwLs′LbNMRNt& forall(qs、a) rightarrow(qs′、b、 bfR)QLst&<::&L\#NQwLs′L\#NMLNLbNt& forall(qs、 bot) rightarrow(qs′、b、 bfL)QLst&<::&L\#NQwLs′L\#NMRNLbNt& forall(qs、 bot) rightarrow(qs′、b、 bfS)QLst&<::&L\#NQwLs′L\#NLbNMRNt& forall(qs、 bot) rightarrow(qs′、b、 bfR) endarray
これらの関係はかなり複雑に見えますが、それでも多くのパターンを追跡できます。 各メカニズムを分析するのではなく、一部のみを検討します。
(qs、a)\右矢印(qs′、x、 bfR) :
beginarrayll&...QRs blacktriangleleftLaNLbN... rightsquigarrow&...NLxNMRQwRs′ blacktriangleleftLbN... rightsquigarrow&...さまよう... rightsquigarrow&...NLxQwRs′ blacktriangleleftMRNLbN... rightsquigarrow&...NLxQRs′ blacktriangleleftLbN... rightsquigarrow&... endarray
(qs、a)\右矢印(qs′、x、 bfS) :
beginarrayll&...QRs blacktriangleleftLaNLbN... rightsquigarrow&...NLxNMLQwRs′ blacktriangleleftLbN... rightsquigarrow&...さまよう... rightsquigarrow&...NLxNML blacktrianglerightQwLs′LbN... rightsquigarrow&...NLx blacktrianglerightQLs′LbN... rightsquigarrow&... endarray
(qs、a)\右矢印(qs′、x、 bfL) :
beginarrayll&...NLbQRs blacktriangleleftLaNLyN... rightsquigarrow&...NLbNMLNLxQwRs′ blacktriangleleftLyN... rightsquigarrow&...さまよう... rightsquigarrow&...NLbNML blacktrianglerightQwLs′LxNLyN... rightsquigarrow&...NLb blacktrianglerightQLs′LxNLyN... rightsquigarrow&... endarray
残りの9つのケースは同じ方法で署名されます。
6.流interfaceなインターフェイス
現実の世界に戻ります。 私たちはJavaについて話しているのです。つまり、私たちの推論は最終的に何らかのコードを書くことにつながるはずです。
OOPの世界では、 流動的なインターフェースのようなものがあります 。 この名前に出会ったことはないかもしれませんが、コードで実装を見たことがあるはずです。 実際、これは単なるカスケードメソッド呼び出しであり、その使用は読みやすさの向上などを目的としています。 次のようになります。
String s = new StringBuilder(a) .append(b) .append(c) .toString();
このような一連の呼び出しは、 単語のような新しい方法で見ることができます。 この例では、アルファベット{append, toString}
と単語append append toString
ます。
対応する単語が事前定義された文法に属しているかどうかを確認することにより、Javaコンパイラに呼び出しチェーンを検証するように要求するとします。 たとえば、10文字より長い単語は許可しません。 StringBuilder
クラスでは、この種のことは何もできないことは明らかですが、独自のインターフェイスを試すことができます。
たとえば、メソッドa
、 b
、 c
呼び出しのチェーンを検証したいとしc
。 また、対応する言語の単語を検証できるチューリングマシンを用意します(すべてのインターフェイスを備えたサブタイピングマシンが接続されています)。
私たちの目標は、コンパイラにa().b().c()
呼び出しチェーンに対して次のクエリを実行させることa().b().c()
ZEENL\#NMLNLaNLbNLcNL\#QwRI blacktriangleleftEEZ 。
これを行うには、次のクラスを作成します。
abstract class B<T> { static B<ML<? super N<? super LHash<? super N<? super E<? super E<? super Z>>>>>>> start; abstract QWRStart<? super LHash<? super N<? super T>>> stop(); abstract B<La<? super N<? super T>>> a(); abstract B<Lb<? super N<? super T>>> b(); abstract B<Lc<? super N<? super T>>> c(); }
ここでは、対応するメソッドの実装ではなく、署名のみを説明していることを理解してください。 実装については後で行われます。
よく見ると、式のタイプがB.start.a().b().c().stop
、次のように書かれていることがB.start.a().b().c().stop
QWRStart<? super LHash<? super N<? super Lc<? super N<? super Lb<? super N<? super La<? super N<? super ML<? super N<? super LHash<? super N<? super E<? super E<? super Z>>>>>>>>>>>>>>>
要求されたリクエストの左側を完全に繰り返します。 つまり、次のコード行は、型チェックアルゴリズムの実行中に、必要なチューリングマシンを実際に起動します。
E<? super E<? super Z>> res = B.start.a().b().c().stop;
7. Safe Builder
わかった! このような豊富なツールキットを使用して、わかりやすいものを作成します。
私はこの記事を読んだことを覚えています、私は思った:「面白いアイデア!」。 それで、今は安全なBuilder
を実行する番です。 前の部分のクラスB
の外観はいくつかの制限を規定しているため、私のBuilder
は少し冗長になります。
そのため、コンパイラで検証できる次のコードが必要です。
User user = build(user() .withFirstName("John") .withLastName("Doe") .please() );
要件は最も単純です-そのため、両方のフィールドが初期化され、その順序で1回しか初期化されません(このような単純なタスクはより適切な方法で解決できることを理解しています;文法の例は後でより複雑になります)。
この場合のチューリングマシンは非常に単純です。 Start
、 First
、 Last
、 Finish
、 Halt
5つの状態しかありません。 アルファベットには、 FirstName
、 LastName
、およびHash
3文字が含まれます。
遷移関数は次のとおりです。
(開始、ハッシュ)\右矢印(最初、ハッシュ、 bfR)
(名、名)\右矢印(名、名、 bfR)
(ラスト、ラストネーム)\右矢印(フィニッシュ、ラストネーム、 bfR)
(終了、ハッシュ)\右矢印(停止、ハッシュ、 bfR)
とても簡単です。 車は入り口でのみ停止します FirstName、LastName 。
public interface N<X> {} public interface ML<X> {} public interface MR<X> {} public interface LFirstName<X> {} public interface LLastName<X> {} public interface LHash<X> {} public interface E<X> extends QLRStart<N<? super QWRStart<? super E<? super E<? super X>>>>>, QRLStart<N<? super QWLStart<? super E<? super E<? super X>>>>>, QLRFirst<N<? super QWRFirst<? super E<? super E<? super X>>>>>, QRLFirst<N<? super QWLFirst<? super E<? super E<? super X>>>>>, QLRLast<N<? super QWRLast<? super E<? super E<? super X>>>>>, QRLLast<N<? super QWLLast<? super E<? super E<? super X>>>>>, QLRFinish<N<? super QWRFinish<? super E<? super E<? super X>>>>>, QRLFinish<N<? super QWLFinish<? super E<? super E<? super X>>>>>, QLRHalt<N<? super QWRHalt<? super E<? super E<? super X>>>>>, QRLHalt<N<? super QWLHalt<? super E<? super E<? super X>>>>> {} public interface QLRStart<X> {} public interface QWLStart<X> extends ML<N<? super QLStart<? super X>>>, MR<N<? super QWLStart<? super MR<? super N<? super X>>>>>, LFirstName<N<? super QWLStart<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWLStart<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWLStart<? super LHash<? super N<? super X>>>>>, E<QLRStart<? super N<? super X>>> {} public interface QRLStart<X> {} public interface QWRStart<X> extends MR<N<? super QRStart<? super X>>>, ML<N<? super QWRStart<? super ML<? super N<? super X>>>>>, LFirstName<N<? super QWRStart<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWRStart<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWRStart<? super LHash<? super N<? super X>>>>>, E<QRLStart<? super N<? super X>>> {} public interface QLRFirst<X> {} public interface QWLFirst<X> extends ML<N<? super QLFirst<? super X>>>, MR<N<? super QWLFirst<? super MR<? super N<? super X>>>>>, LFirstName<N<? super QWLFirst<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWLFirst<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWLFirst<? super LHash<? super N<? super X>>>>>, E<QLRFirst<? super N<? super X>>> {} public interface QRLFirst<X> {} public interface QWRFirst<X> extends MR<N<? super QRFirst<? super X>>>, ML<N<? super QWRFirst<? super ML<? super N<? super X>>>>>, LFirstName<N<? super QWRFirst<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWRFirst<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWRFirst<? super LHash<? super N<? super X>>>>>, E<QRLFirst<? super N<? super X>>> {} public interface QLRLast<X> {} public interface QWLLast<X> extends ML<N<? super QLLast<? super X>>>, MR<N<? super QWLLast<? super MR<? super N<? super X>>>>>, LFirstName<N<? super QWLLast<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWLLast<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWLLast<? super LHash<? super N<? super X>>>>>, E<QLRLast<? super N<? super X>>> {} public interface QRLLast<X> {} public interface QWRLast<X> extends MR<N<? super QRLast<? super X>>>, ML<N<? super QWRLast<? super ML<? super N<? super X>>>>>, LFirstName<N<? super QWRLast<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWRLast<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWRLast<? super LHash<? super N<? super X>>>>>, E<QRLLast<? super N<? super X>>> {} public interface QLRFinish<X> {} public interface QWLFinish<X> extends ML<N<? super QLFinish<? super X>>>, MR<N<? super QWLFinish<? super MR<? super N<? super X>>>>>, LFirstName<N<? super QWLFinish<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWLFinish<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWLFinish<? super LHash<? super N<? super X>>>>>, E<QLRFinish<? super N<? super X>>> {} public interface QRLFinish<X> {} public interface QWRFinish<X> extends MR<N<? super QRFinish<? super X>>>, ML<N<? super QWRFinish<? super ML<? super N<? super X>>>>>, LFirstName<N<? super QWRFinish<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWRFinish<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWRFinish<? super LHash<? super N<? super X>>>>>, E<QRLFinish<? super N<? super X>>> {} public interface QLRHalt<X> {} public interface QWLHalt<X> extends ML<N<? super QLHalt<? super X>>>, MR<N<? super QWLHalt<? super MR<? super N<? super X>>>>>, LFirstName<N<? super QWLHalt<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWLHalt<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWLHalt<? super LHash<? super N<? super X>>>>>, E<E<? super User>> {} public interface QRLHalt<X> {} public interface QWRHalt<X> extends MR<N<? super QRHalt<? super X>>>, ML<N<? super QWRHalt<? super ML<? super N<? super X>>>>>, LFirstName<N<? super QWRHalt<? super LFirstName<? super N<? super X>>>>>, LLastName<N<? super QWRHalt<? super LLastName<? super N<? super X>>>>>, LHash<N<? super QWRHalt<? super LHash<? super N<? super X>>>>>, E<E<? super User>> {} public interface QLStart<X> extends LHash<N<? super QWLFirst<? super LHash<? super N<? super LHash<? super N<? super MR<? super N<? super X>>>>>>>>> {} public interface QRStart<X> extends LHash<N<? super QWRFirst<? super LHash<? super N<? super MR<? super N<? super LHash<? super N<? super X>>>>>>>>> {} public interface QLFirst<X> extends LFirstName<N<? super QWLLast<? super LFirstName<? super N<? super MR<? super N<? super X>>>>>>> {} public interface QRFirst<X> extends LFirstName<N<? super QWRLast<? super MR<? super N<? super LFirstName<? super N<? super X>>>>>>> {} public interface QLLast<X> extends LLastName<N<? super QWLFinish<? super LLastName<? super N<? super MR<? super N<? super X>>>>>>> {} public interface QRLast<X> extends LLastName<N<? super QWRFinish<? super MR<? super N<? super LLastName<? super N<? super X>>>>>>> {} public interface QLFinish<X> extends LHash<N<? super QWLHalt<? super LHash<? super N<? super MR<? super N<? super LHash<? super N<? super X>>>>>>>>> {} public interface QRFinish<X> extends LHash<N<? super QWRHalt<? super LHash<? super N<? super ML<? super N<? super LHash<? super N<? super X>>>>>>>>> {} public interface QLHalt<X> {} public interface QRHalt<X> {}
User
UserBuilder
( ):
public class User { private final String firstName; private final String lastName; private User(UserBuilder<?> builder) { this.firstName = builder.firstName; this.lastName = builder.lastName; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public static User build(E<? super E<? super User>> e) { ... } public static UserBuilder<ML<? super N<? super LHash<? super N<? super E<? super E<? super User>>>>>>> user() { return new UserBuilder<>(); } public static class UserBuilder<X> { private String firstName; private String lastName; public UserBuilder<LFirstName<? super N<? super X>>> withFirstName(String firstName) { this.firstName = firstName; return (UserBuilder<LFirstName<? super N<? super X>>>) this; } public UserBuilder<LLastName<? super N<? super X>>> withLastName(String lastName) { this.lastName = lastName; return (UserBuilder<LLastName<? super N<? super X>>>) this; } public QWRStart<? super LHash<? super N<? super X>>> please() { ... } // interfaces ... } }
build
.
—E<? super E<? super User>>
,E
. ,e
User
, - . , , PECS (Producer Extends, Consumer Super).E
:
public interface E<X> extends Consumer<X>, ...
e
void accept(E<? super User>)
,E
.Consumer
User
. ,e.accept(callback)
,build
:
public static User build(E<? super E<? super User>> e) { User[] ref = { null }; E<? super User> callback = user -> ref[0] = user; e.accept(callback); return ref[0]; }
;
-
please
QWRStart
,E<? super E<? super User>>
. , , (,QWRStart
E
):
public QWRStart<? super LHash<? super N<? super X>>> please() { return (QWRStart) o -> { E<? super User> e = (E<? super User>) o; e.accept(new User(this)); }; }
, . , , , .. ()(()())
, ()())(()
— . A
B
( x
— , , ).
:
(Start,Hash)→(Scan,Hash,R)
(Scan,B)→(Back,x,L)
(Scan,Hash)→(Check,Hash,L)
(Scan,A)→(Scan,A,R)
(Scan,x)→(Scan,x,R)
(Back,A)→(Scan,x,R)
(Back,B)→(Back,B,L)
(Back,x)→(Back,x,L)
(Check,Hash)→(Halt,Hash,S)
(Check,x)→(Check,x,L)
:
E<? super E<? super String>> e = start.A().A().B().B().stop();
:
E<? super E<? super String>> e = start.B().A().B().A().stop();
8.
. , .
— .
-, , Builder
.
-, , . — .
-, . - ( ):
incompatible types: QWRStart<capture#1 of ? super LHash<? super N<? super LFirstName<? super N<? super LLastName<? super N<? super ML<? super N<? super LHash<? super N<? super E<? super E<? super User>>>>>>>>>>>>> cannot be converted to E<? super E<? super User>>
. , :
- ;
- , . .
? , , :
- for fun;
- . — .
Kotlin, , , Java , , .. Non-Expansive Inheritance Restriction . :
interface A<T> interface B<T> : A<B<Anything<T>>>
Kotlin, . - , B
B
, T
, . , .
— " " , . - Kotlin . , Kotlin .
, Java-, Kotlin , Java — . , Kotlin Java, , .