Java链式调用上的类型推导

Posted on May 2, 2018

JEP 101: Generalized Target-Type InferenceJava 8JEP),即广义(推广)的目标类型推导。

提出了2个推广加强的Case:

  1. Inference in argument position,在参数位置(提取形参的类型信息)的类型推导
  2. Inference in chained calls,链式调用上的类型推导

Java 8/9(9.0.4)/10(10.0.1)/11(11-ea) 目前都不支持 链式调用上做类型推导

测试分析以及结论与解决方法

为了简化代码及其类型推导的分析,选用List,在方法上完全不涉及类型上下限,涉及方法及其签名如下:

# 与在JEP 101: Generalized Target-Type Inference中对链式调用上的类型推导所用的示例(List.nil().head())一致。因为JavaList没有head方法,用功能和泛型参数一样的get(0)方法替代。

// java.util.Collections#emptyList
public static final <T> List<T> emptyList();

// java.util.List#get
E get(int index);

测试代码

import java.util.Collections;
import java.util.List;

public class TypeInferenceShowcase {
    public static void main(String[] args) {
        // Compile OK
        List<String> list1 = Collections.emptyList();
        String head1 = list1.get(0);

        // write as chained call.
        // Compile error!!
        String head = Collections.emptyList().get(0);
    }
}

编译结果

Java 8/9/10/11 的编译出错信息完全一样。

## Java 8 ##
$ javac -version && echo && javac TypeInferenceShowcase.java
javac 1.8.0_162

TypeInferenceShowcase.java:12: 错误: 不兼容的类型: Object无法转换为String
        String head = Collections.emptyList().get(0);
                                                 ^
1 个错误

## Java 9 ##
$ javac -version && echo && javac TypeInferenceShowcase.java
javac 9.0.4

TypeInferenceShowcase.java:12: 错误: 不兼容的类型: Object无法转换为String
        String head = Collections.emptyList().get(0);
                                                 ^
1 个错误

# Java 10
$ javac -version && echo && javac TypeInferenceShowcase.java
javac 10.0.1

TypeInferenceShowcase.java:12: 错误: 不兼容的类型: Object无法转换为String
        String head = Collections.emptyList().get(0);
                                                 ^
1 个错误

# Java 11
$ javac -version && echo && javac TypeInferenceShowcase.java
javac 11-ea

TypeInferenceShowcase.java:12: 错误: 不兼容的类型: Object无法转换为String
        String head = Collections.emptyList().get(0);
                                                 ^
1 个错误

解决方法

因为Java 8/9/10/11 不支持 链式调用上做类型推导
# 即 不支持 类型信息从链路后面的调用 向 前面 传递,即 支持在调用链上类型信息的逆向传递
所以需要自己在 链式调用 上手动补上类型信息:

String head = Collections.<String>emptyList().get(0);
//                         ^^^^^^

实际上,在IntelliJ IDEA中,执行变量list1内联,生成的就是上面的代码;IntelliJ IDEA都帮我们处理好了。具体的操作方法是:

List<String> list1 = Collections.emptyList();
//            ^ 光标放list1变量上(上面或下面的都行),执行【Refactor - Inline...】(Alt+Cmd+N),见下图
String head1 = list1.get(0);

image.png

展开分析与总结梳理

JEP 101: Generalized Target-Type InferenceJava 8JEP),即广义(推广)的目标类型推导。

提出了2个推广加强的Case:

  1. Inference in argument position,在参数位置(提取形参的类型信息)的类型推导
    • 类型信息 从嵌套调用的 外面调用 向 里面的调用 传递,即 支持在嵌套调用时 类型信息的 由外往里 的逆向传递
    • JDK 8之前,只有在 赋值语句 中 会做这样类型推导(提取赋值变量的类型信息)
      虽然在参数位置上,其实可以看作是 实际参数(实参)形式参数(形参)赋值
  2. Inference in chained calls,链式调用上的类型推导
    • 类型信息从调用链 后面的调用 向 前面的调用 传递,即 支持链式调用时 类型信息的 由后往前 的逆向传递
    • 这个Case,可以看作是 赋值语句推广加强
      因为链式只有一级调用时,就 退化成了 赋值语句 的Case。
    • JEP中说的好好的,然而,上面的测试可以看到,Java 8/9/10/11 目前是不支持的 !!

总得来说,Java的类型推导还是不够友好的,这些我们人肉解析器都能在直觉上很快完成推导觉得没问题的代码,Java编译却是过不了,surprise~ =_=!

不过,实际业务使用和工程应用上,这并不是多大的问题,因为 需要 在调用链上 类型信息的 由后往前 的逆向传递 的情况其实很少。

说到类型推导能力的强弱,我不得不想到邻家小伙Scala,相比起来做得就是到位贴心了。Just no surprise!

邻家小伙Scala的类型推导能力对比

链式调用上的类型推导,对于Scala是个小Case,毫无压力。对等的测试如下。

测试代码

class TypeInferenceScalaShowcase {
  def main(args: Array[String]): Unit = {
    // write as chained call.
    // Compile OK
    val head: String = List.empty.head
  }
}

编译结果

$ scalac -version && echo && scalac TypeInferenceScalaShowcase.scala && echo 'Compile Success!'
Scala compiler version 2.12.4 -- Copyright 2002-2017, LAMP/EPFL and Lightbend, Inc.

Compile Success!

妥妥的!~ :)

相关阅读/资料