Java A的新本地变量类型推断

对于编程语言迷来说,新闻几乎比这更令人兴奋!

现在有一个状态为“候选”的本地变量类型推断JEP 286 。 以及Brian Goetz的反馈请求,我很想邀请您参加: http : //mail.openjdk.java.net/pipermail/platform-jep-discuss/2016-March/000037.html

请这样做,调查仅在3月9日至3月16日开放!

这不是要实现的功能。 这可能实现。 因此,尚无特定的Java版本,这就是为什么我将Java版本命名为“ A”(对于Awesome)。

什么是局部变量类型推断,为什么好呢?

让我们看一下其他各种语言已经存在了一段时间的功能。 在此博客文章中,我想讨论总体思想,而不是讨论可能针对Java计划的特定实现,因为这还为时过早,而且我当然不了解如何将其适合Java。 。

在Java和其他某些语言中,类型总是明确和冗长地声明。 例如,您编写如下内容:

// Java 5 and 6
List<String> list = new ArrayList<String>();

// Java 7
List<String> list = new ArrayList<>();

请注意,在Java 7中,如何通过有用的菱形运算符<>添加了一些语法糖。 它有助于以Java方式消除不必要的冗余,即通过应用“目标类型”,这意味着类型是由“目标”定义的。 可能的目标是:

  • 局部变量声明
  • 方法参数(从方法的外部和内部)
  • 班级成员

由于在许多情况下, 必须明确声明目标类型(方法参数,类成员),因此Java的方法很有意义。 但是,对于局部变量,实际上不需要声明目标类型。 由于类型定义绑定在一个非常局部的范围内,因此无法逃脱,因此编译器很可能会在没有源代码明确的情况下从“源类型”推断出它。 这意味着,我们将能够执行以下操作:

// Java A as suggested in the JEP

// infers ArrayList<String>
var list = new ArrayList<String>();

// infers Stream<String>
val stream = list.stream();

在上面的示例中, var表示可变(非最终)局部变量,而val表示不可变(最终)局部变量。 请注意,从来没有真正需要过列表类型,就像我们编写以下内容一样,今天已经推断出了类型:

stream = new ArrayList<String>().stream();

这与lambda表达式没有什么不同,在Java 8中我们已经有了这种类型推断:

List<String> list = new ArrayList<>();

// infers String
list.forEach(s -> {
    System.out.println(s);
};

将lambda参数视为局部变量。 这种lambda表达式的另一种语法可能是:

List<String> list = new ArrayList<>();

// infers String
list.forEach((val s) -> {
    System.out.println(s);
};

其他语言都有这个,但是好吗?

这些其他语言包括:C#,Scala和JavaScript(如果需要的话)。 YAGNI可能是对此功能的常见反应。 对于大多数人来说,不能一直输入所有类型只是为了方便。 某些人在阅读代码时可能更喜欢看到明确记录下来的类型。 特别是,当您具有复杂的Java 8 Stream处理管道时,很难跟踪沿途推断的所有类型。 在我们有关jOOλ的窗口函数支持的文章中可以看到一个示例:

BigDecimal currentBalance = new BigDecimal("19985.81");
 
Seq.of(
    tuple(9997, "2014-03-18", new BigDecimal("99.17")),
    tuple(9981, "2014-03-16", new BigDecimal("71.44")),
    tuple(9979, "2014-03-16", new BigDecimal("-94.60")),
    tuple(9977, "2014-03-16", new BigDecimal("-6.96")),
    tuple(9971, "2014-03-15", new BigDecimal("-65.95")))
.window(Comparator
    .comparing((Tuple3<Integer, String, BigDecimal> t) 
        -> t.v1, reverseOrder())
    .thenComparing(t -> t.v2), Long.MIN_VALUE, -1)
.map(w -> w.value().concat(
     currentBalance.subtract(w.sum(t -> t.v3)
                              .orElse(BigDecimal.ZERO))
));

上面实现了运行总计计算,得出:

+------+------------+--------+----------+
|   v0 | v1         |     v2 |       v3 |
+------+------------+--------+----------+
| 9997 | 2014-03-18 |  99.17 | 19985.81 |
| 9981 | 2014-03-16 |  71.44 | 19886.64 |
| 9979 | 2014-03-16 | -94.60 | 19815.20 |
| 9977 | 2014-03-16 |  -6.96 | 19909.80 |
| 9971 | 2014-03-15 | -65.95 | 19916.76 |
+------+------------+--------+----------+

尽管由于现有Java 8的有限类型推断功能而需要声明Tuple3类型( 另请参见有关广义目标类型推断的本文 ),但是您是否可以跟踪所有其他类型? 您可以轻松预测结果吗? 有些人喜欢短款,有些则声称:

另一方面,您是否想手动写下类似Tuple3<Integer, String, BigDecimal> ? 或者,在使用jOOQ时 ,您更喜欢以下哪个版本的相同代码?

// Explicit typing
// ----------------------------------------
for (Record3<String, Integer, Date> record : ctx
    .select(BOOK.TITLE, BOOK.ID, BOOK.MODIFIED_AT)
    .from(BOOK)
    .where(TITLE.like("A%"))
) {
    // Do things with record
    String title = record.value1();
}

// "Don't care" typing
// ----------------------------------------
for (Record record : ctx
    .select(BOOK.TITLE, BOOK.ID, BOOK.MODIFIED_AT)
    .from(BOOK)
    .where(TITLE.like("A%"))
) {
    // Do things with record
    String title = record.getValue(0, String.class);
}

// Implicit typing
// ----------------------------------------
for (val record : ctx
    .select(BOOK.TITLE, BOOK.ID, BOOK.MODIFIED_AT)
    .from(BOOK)
    .where(TITLE.like("A%"))
) {
    // Do things with record
    String title = record.value1();
}

我敢肯定,很少有人真的愿意显式地写下整个泛型类型,但是如果您的编译器仍然可以记住这一点,那真是太棒了,不是吗? 这是一个可选功能。 您始终可以恢复为显式类型声明。

使用地点差异的边缘案例

没有这种类型推断,有些事情是不可能的,它们与使用站点的差异以及Java中实现的泛型的细节有关。 使用使用场所差异和通配符,可以构造无法分配给任何东西的“危险”类型,因为它们是不确定的。 有关详细信息,请阅读Ross Tate关于在Java的Type System中驯服通配符的论文

当从方法返回类型暴露时,使用站点差异也是一个痛苦,如在某些库中可以看到的:

  • 不在乎他们给用户带来的痛苦
  • 没有找到更好的解决方案,因为Java没有声明站点差异
  • 忽略了这个问题

一个例子:

interface Node {
    void add(List<? extends Node> children);
    List<? extends Node> children();
}

想象一下一个树数据结构库,其中树节点返回其子级列表。 从技术上讲正确的子类型为List<? extends Node> List<? extends Node>因为子级是Node子类型,并且使用Node子类型列表是完全可以的。

从API设计的角度来看,在add()方法中接受此类型非常有用。 例如,它允许人们添加List<LeafNode> 。 但是,从children()返回它是可怕的,因为现在唯一的选择是:

// Raw type. meh
List children = parent.children();

// Wild card. meh
List<?> children = parent.children();

// Full type declaration. Yuk
List<? extends Node> children = parent.children();

使用JEP 286,我们也许可以解决所有这些问题,并拥有第四个不错的选择:

// Awesome. The compiler knows it's 
// List<? extends Node>
val children = parent.children();

结论

局部变量类型推断是一个热门话题。 它是完全可选的,我们不需要它。 但这使很多事情变得容易得多,尤其是在使用大量仿制药时。 我们已经看到,在使用lambda表达式和复杂的Java 8 Stream转换时,类型推断是一项致命功能。 当然,要在一条较长的语句中跟踪所有类型会比较困难,但是同时,如果拼出了这些类型,则会使该语句非常难以阅读(并且通常也很难编写)。

类型推断有助于使开发人员提高工作效率,而又不放弃类型安全性。 实际上,这鼓励了类型安全,因为API设计人员现在不那么愿意向用户公开复杂的泛型类型,因为用户可以更轻松地使用这些类型( 请参见jOOQ示例 )。

实际上,此功能已经在各种情况下在Java中提供,只是在为局部变量赋值并为其命名时不存在。

无论您的意见是什么:请确保将其分享给社区并回答此调查: http : //mail.openjdk.java.net/pipermail/platform-jep-discuss/2016-March/000037.html

期待Java A,A代表Awesome。

翻译自: https://www.javacodegeeks.com/2016/03/java-new-local-variable-type-inference.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值