38065


Scala/Haskell の Either が便利であるため、これを自作してみようと思いました。

### Either クラス実装

[code]

    public final class Either<L,R> {
        private L left;
        private R right;
        private Either(L left, R right) {
            this.left = left;
            this.right = right;
        }
        public Optional<L> getLeft() {
            return Optional.ofNullable(left);
        }
        public Optional<R> getRight() {
            return Optional.ofNullable(right);
        }
        public static <L> Either<L,?> ofLeft(L left) {
            return new Either<>(left, null);
        }
        public static <R> Either<?,R> ofRight(R right) {
            return new Either<>(null, right);
        }
    }
    
[/code]

しかし、これは lambda で stream の中で利用しようとすると、下記のようにエラーが出ます。

### 実行ロジック

[code]

    public class AppTest {
    
        public static void main(String[] args) {
            List<Integer> integerList = Arrays.asList(1, 2);
            List<Either<Integer, String>> eithers = integerList.stream()
                    .map(i -> Either.ofLeft(i))
                    .collect(Collectors.toList());
        }
    }
    
[/code]

### 発生コンパイルエラー

[code]

    Error:(37, 25) java: 不適合な型: 推論変数Tには、不適合な境界があります
        等価制約: my.test.AppTest.Either<java.lang.Integer,java.lang.String>
        下限: my.test.AppTest.Either<java.lang.Integer,?のキャプチャ#1>
    
[/code]

## 質問

  * このコンパイルエラーはなぜ発生するのでしょうか。また、どのように回避できるでしょうか。


CC BY-SA 3.0
2017-09-20T05:58:13.017
38058
2017-09-20T08:49:49.083
754
question
java java8
Either を自作する際にエラーがでる


  * コンパイルエラーの理由

`Either<java.lang.Integer,java.lang.String>`のStringと`Either<java.lang.Integer,?のキャプチャ#1>`の?のキャプチャ#1が型として一致しないためコンパイルエラーとなります。  
Javaでは型引数に「?」を指定した場合、各?の型を表すためのプレースホルダとしてキャプチャが割り当てられます。そのキャプチャが`String`として受け入れ可能かは静的に検証できないためエラーとなります。  
`String`を`Object`にしたとしても、やはりプレースホルダーで特定される型の値として`Object`が受け入れ可能なことを静的に検証することはできないためエラーとなります。

  * 回避方法

その場しのぎの方法では`String`を?にします。手元では動作しましたが、実用的でない感じです。

[code]

    public static void main(String[] args) {
        List<Integer> integerList = Arrays.asList(1, 2);
        List<Either<Integer, ?>> eithers = integerList.stream()
                .map(i -> Either.ofLeft(i))
                .collect(Collectors.toList());
        System.out.println(eithers);
    }
    
[/code]

型をはっきりさせるため、`Either`の`ofLeft`,`ofRight`で2つの型どちらも指定させます。

[code]

    public static <L,R> Either<L,R> ofLeft(L left) {
        return new Either<>(left, null);
    }
    public static <L,R> Either<L,R> ofRight(R right) {
        return new Either<>(null, right);
    }
    
    public static void main(String[] args) {
        List<Integer> integerList = Arrays.asList(1, 2);
        List<Either<Integer, String>> eithers = integerList.stream()
                .map(i -> Either.<Integer, String>ofLeft(i))
                .collect(Collectors.toList());
    }
    
[/code]

これも動作します。

キャプチャについては、こちらの解説記事がよいと思います。  
Javaの理論と実践: Generics のワイルドカードを使いこなす、第 1 回  
<https://www.ibm.com/developerworks/jp/java/library/j-jtp04298.html>

Scalaであれば、`Nothing`があるのでいいですね。

[code]

    object AppTest extends App {
      val integerList = List(1, 2)
      val eithers: List[Left[Int, Nothing]] = integerList.map((i) => Left(i))
    }
    
[/code]

* * *

### OP追記

リンク先を読み込んでいった結果、次の理解を得ました。

  * generics にワイルドカードを指定した場合、 java コンパイラにおいては、そのメソッド呼び出し・戻り値は以下のように解決される。
  * キャプチャが ? super T の場合 
    * キャプチャを直にとるメソッド呼び出し: T のサブクラスならばできる
    * キャプチャの戻り値型: Object として取り扱う
  * キャプチャが ? extends T の場合 
    * キャプチャを直にとるメソッド呼び出し: できない。 (null だけはいける)
    * キャプチャの戻り値型: T として取り扱う
  * キャプチャがただの ? の場合 
    * キャプチャを直にとるメソッド呼び出し: できない。 (null だけはいける)
    * キャプチャの戻り値型: Object として取り扱う
  * キャプチャは作成された時点で他のどの型とも異なる方として取り扱われる。  
**型推論的なキャプチャの解決は行われない。**


CC BY-SA 3.0
2017-09-20T07:50:56.500
38065
2017-09-20T08:49:49.083
2017-09-20T08:49:49.083
754
4464
38058
answer