使用了 23 年的 Java 不再免费!

简介: 编程语言界的扛把子 Java,不仅搭载上了如火箭版的更新速度,现在还有意让上车的用户付费买票了,那么身为 Java 开发者,下一步究竟是该弃用 23 年的老牌工具 JDK 还是乖乖付费继续搭载这列车? 上个月,Java 开发工具包(JDK)11 刚刚到来,JDK 12 就在紧密敲锣筹备中,随着消.

编程语言界的扛把子 Java,不仅搭载上了如火箭版的更新速度,现在还有意让上车的用户付费买票了,那么身为 Java 开发者,下一步究竟是该弃用 23 年的老牌工具 JDK 还是乖乖付费继续搭载这列车?

1


上个月, Java 开发工具包(JDK)11 刚刚到来,JDK 12 就在紧密敲锣筹备中,随着消息接连不断地 爆出,不少使用 Java 的开发者开始有种一年一万个更新版本的错觉,而当面对厚厚的一堆堆 Java 8/9/10/11 的入门书籍和教程时,就问你怕不怕?

不仅如此,就在 Java 早已在移动 App、服务器应用、Web 开发、J2EE 企业级应用和嵌入式等领域根深蒂固时,Oracle 于近日最新发布的一纸 Java 用户使用协议轰动了整个业界,因为 Java 将收费了!

2


JDK 11 不容错过的那些新特性


JDK 11 作为 Oracle 以六个月为更新周期之后公开发布的第一个长期支持版本,其中还是有许多实用的功能特性。

局部变量推断

Java 10 引入了新的关键字 var,可以在定义局部变量时代替类型信息(局部指的是在方法体内部的变量定义)。

在 Java 10 之前,局部变量必须这样定义:

String text = "Hello Java 9";

而现在可以用 var 代替 String。编译器会根据变量的赋值推断出正确的类型。因此,text 的类型是 String:

var text = "Hello Java 10";

使用 var 定义的变量依然是静态类型。这种变量不能重新用不兼容的类型赋值。比如,下面的代码无法通过编译:

var text = "Hello Java 11";
text = 23; // Incompatible types

还可以通过同时使用 var 和 final 来禁止变量的重新赋值:

final var text = "Banana";
text = "Joe"; // Cannot assign a value to final variable 'text'

而且,当编译器无法推断出正确类型时也不允许使用 var:

// Cannot infer type:
var a;
var nothing = null;
var lambda = () -> System.out.println("Pity!");
var method = this::someMethod;

局部变量类型的推断在泛型中非常有用。下面的例子中,current 有个非常复杂的类型 Map>,而这个类型可以简化成一个 var 关键字,节省了很多敲代码的时间:

var myList = new ArrayList<Map<String, List<Integer>>>();

for (var current : myList) {
// current is infered to type: Map<String, List<Integer>>
System.out.println(current);
}

由于 Java 11 的 var 关键字也可以在 lambda 的参数上使用,因此可以给参数加注解:

Predicate<String> predicate = (@Nullable var a) -> true;

小提示:Intellij IDEA 中可以按住 CMD/CTRL 键并将鼠标悬停在变量上来查看推断出的类型(键盘快捷键为Ctrl+J)。

HTTP 客户端
Java 9 引入了新的 HttpClient API 来处理 HTTP 请求。在 Java 11 中,这个 API 已稳定,可以通过 java.net 包使用。我们来看看这个 API 能干什么。

新的 HttpClient 支持同步和异步方式。同步请求会阻塞当前线程直到响应返回。BodyHandlers 定义了期待的响应体的类型(如字符串、字节数组或文件):

var request = HttpRequest.newBuilder()
.uri(URI.create("https://winterbe.com"))
.GET()
.build();
var client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());

同样的请求也可以异步进行。调用 sendAsync 不会阻塞当前线程,它会返回一个 CompletableFuture 用来构建异步操作管线。

var request = HttpRequest.newBuilder()
.uri(URI.create("https://winterbe.com"))
.build();
var client = HttpClient.newHttpClient();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);

小提示:可以省略 .GET() 调用,因为它是默认的。

下面这个例子用 POST 方式将数据发送到给定的 URL。与 BodyHandlers 类似,这里使用 BodyPublishers 来定义请求体中要发送的数据类型,如字符串、字节数组、文件或输入流:

var request = HttpRequest.newBuilder()
.uri(URI.create("https://postman-echo.com/post"))
.header("Content-Type", "text/plain")
.POST(HttpRequest.BodyPublishers.ofString("Hi there!"))
.build();
var client = HttpClient.newHttpClient();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode()); // 200

下面的例子演示了通过 BASIC-AUTH 方式进行认证的方法:

var request = HttpRequest.newBuilder()
.uri(URI.create("https://postman-echo.com/basic-auth"))
.build();
var client = HttpClient.newBuilder()
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("postman", "password".toCharArray());
}
})
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode()); // 200

集合

像 List、Set 和 Map 等集合类都加入了新的方法。List.of 会根据给定的参数创建一个新的不可变的 list。List.copyOf 能创建 list 的不可变副本。

var list = List.of("A", "B", "C");
var copy = List.copyOf(list);
System.out.println(list == copy); // true

因为 list 已经是不可变的了,因此不需要实际创建 list 实例的副本,因此 list 和 copy 会指向同一个副本。但如果赋值一个可变的列表,copy 就会生成一个新的实例,以保证修改原始列表时不会产生副作用:

var list = new ArrayList<String>();
var copy = List.copyOf(list);
System.out.println(list == copy); // false

在创建不可变的 map 时,不需要自行创建 map 的内容,只需要传递键和值即可:

var map = Map.of("A", 1, "B", 2);
System.out.println(map); // {B=2, A=1}

Java 11 中的不可变集合依然使用与原来的集合 API 同样的接口。但如果试图通过添加或删除元素的方式改变不可变集合,则会发生 java.lang.UnsupportedOperationException 异常。好在 Intellij IDEA 会在你试图改变不可变集合时发出警告。


Java 8 引入了 流的概念,现在它有三个新的方法。Stream.ofNullable 能从单个元素构建一个流:

Stream.ofNullable(null)
.count() // 0

dropWhile 和 takeWhile 两个方法都能接受 predicate 对象,从而可以抛弃流中的一些元素:

Stream.of(1, 2, 3, 2, 1)
.dropWhile(n -> n < 3)
.collect(Collectors.toList()); // [3, 2, 1]

Stream.of(1, 2, 3, 2, 1)
.takeWhile(n -> n < 3)
.collect(Collectors.toList()); // [1, 2]

Optional
Optional 也增加了几个新方法,比如现在可以很容易将 optional 转换成流,或者给空的 optional 提供另一个 optional 作为出错时的备选方案:

Optional.of("foo").orElseThrow(); // foo
Optional.of("foo").stream().count(); // 1
Optional.ofNullable(null)
.or(() -> Optional.of("fallback"))
.get(); // fallback

字符串
最基础的类之一 String 也加了几个辅助方法用来去除空白、检查空白,以及以流的方式输出字符串:

" ".isBlank(); // true
" Foo Bar ".strip(); // "Foo Bar"
" Foo Bar ".stripTrailing(); // " Foo Bar"
" Foo Bar ".stripLeading(); // "Foo Bar "
"Java".repeat(3); // "JavaJavaJava"
"A\nB\nC".lines().count(); // 3

其他 JVM 特性
在我看来,上述这些是 Java 11 与 8 相比时最有意思的语言 API 特性,不过新的特性还有许多,比如下面这些:
用于响应式编程的流式 API
● Java 模块系统
● 应用程序类数据共享
● 动态类-文件常量
● Java REPL(JShell)
● 飞行记录器
● Unicode 10
● G1:完全并行的垃圾回收器
● ZGC:可扩展的低延迟垃圾回收器
● Epsilon:No-Op垃圾回收器
● 不推荐使用的Nashorn JavaScript引擎
● ……

3


学不动的 Java,还要收费了?


对于 Java 新版本,不少开发者望尘莫及,纷纷表示不要再更新了,我的项目还停留在 Java 8 呢。话虽如此,事实上 Oracle 曾在今年四月就宣布,自 2019 年 1 月起,Java SE 8 公开更新将不向没有商用许可证的业务、商用或生产用途提供。即未来开发者还想使用 JDK 8,Oracle 将不会提供免费的技术支持,需要另外收费。所以总体而言还是建议开发者应该转换到最新版的 Java 11。

4


但就在这时,据国外网友@Stephen Colebourne 发布的一篇名为《Oracle's Java 11 trap - Use OpenJDK instead!》博文,我们才注意到,在 Java 11 中,Oracle 悄然更新了用户使用协议( https://www.oracle.com/technetwork/java/javase/terms/license/javase-license.html):

5


简而言之:

新版 Oracle JDK 不可以用在数据处理、商业、产品、或者内部商业用途(需要购买 License),仅可免费用于开发、测试、原型、演示。

6


正是这一修改意味着免费使用 23 年的 Java 即将走上收费的路子。倘若开发者还是如往常那般下载 Oracle JDK,并将其投入商业项目中使用,后续可能会带来不小的商业纠纷。

7


Oracle JDK 收费了,企业和开发者怎么办?


提及 Java 的商业纠纷,我们不禁想起此前轰动一时且长达八年的 Oracle 与 Google 关于 Java 的侵权案:

1995 年,Sun 公司发布了 Java;

这其中需要介绍到 2006 年,Sun 公司开源了其 Java 项目——OpenJDK,但是并未开源此前的 Java 项目 SunJDK(现在是 OracleJDK);

2009 年,Oracle 以 74 亿美元收购 Sun 从而获得了 Java 的版权,而其中也包含了 Sun 研发的 Java 商业项目版权;

2010 年 8 月,Oracle 认为 Google Android 系统抄袭了 37 个 Java API 代码段,而这些代码属于 Oracle 商业私有 JDK(OracleJDK)的一部分,于是将 Google 诉讼至法庭,要求赔偿 26 亿美元;

经过 8 年的调解及上诉,这一案最终于今年 3 月,美国联邦巡回法院判 Google 向 Oracle 赔偿 88 亿美元而告一段落。之所以没说案件告终,是因为 Google 不服判决,还在向最高法院上诉中。

而就在 Google 边应对诉讼时,或许就在周密思量针对自己的 Android 系统甚至是整个公司研发的下一步:如何摆脱 Oracle?

于是,在 2015 年年底,我们就亲眼见证了 Google 宣布将 Java 应用程序接口(APIs)从 Oracle JavaAPI 替换成开源的 OpenJDK。甚至在两年后的 Google I/O 大会上,震撼宣布 Kotlin 成为 Android 开发的一级编程语言,让其与 Java 齐驾并驱,反观,也是与之相抗衡。

再针对这一次的 Oracle 修改了 JDK 11 的用户协议来看,从 Oracle 的角度其实不难理解他的这一行为,毕竟作为纯软件先驱的 Oracle 也是个商业公司,他需要提供一个软件的商业版本来提高增值服务。但是对于使用 Oracle JDK 的开发者或企业而言,情况就变得有所不同了,为了避免上述 Google 的同等遭遇,我们除了付费是否还有其他选择?

对此,不少开发者纷纷热议:

● Java 程序员是时候向 C# 转移了;
● 换 .NET 也行;
● 这个仅针对 JDK 11,不升级不使用 Java 11 不就行了。
在访问了几位知名的 Java 开发者之后,他们给出的答案几乎都是,「使用 Kotlin」。甚至就连微博研发副总经理@TimYang 也表示,这一行为直接导致 Kotlin 才是最大赢家,IDEA 环境将 Java 代码粘贴到 Kotlin 文件,自动转换。

8


不过相较于 23 岁的 Java,不少开发者还是对年仅 7 岁的 Kotlin 的未来感到迷茫,所以在面对 JDK 8 即将停止免费更新支持、JDK 11 无法商用的情况下,请记得 Oracle 还有一个名为 OpenJDK 的开源项目。

注:Java 9、10 并不是长期支持(LTS)版本,所以上述文章中并未提及。

要说 Oracle JDK 和 Open JDK 之前的差距很明显,那么在 JDK 11 中,Oracle 很人性化地将两者的不同尽可能地缩小了,甚至可以忽略微乎其微的差距。

或者除了 Open JDK 外,我们此前也发文为大家推荐了一些实用的 JDK,譬如 AdoptOpenJDK builds、Red Hat OpenJDK builds、Azul Zulu 等等。

最后,对于 Oracle 修改 JDK 11 的使用协议,你有着什么样的看法?欢迎下方留言,分享你的想法。

原文发布时间为:2018-10-23
本文作者:震惊的
本文来自云栖社区合作伙伴“Java架构师之路”,了解相关信息可以关注“Java架构师之路”。


社群技术交流:欢迎关注【阿里Java技术进阶】每周在群内进行【免费技术直播】和【在线回答技术问题】欢迎点击link入群: http://tb.cn/gXRstIw
或者 钉钉扫码入群:
JAVA_

相关文章
|
7天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
77 38
|
4天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
9天前
|
Java 调度
[Java]线程生命周期与线程通信
本文详细探讨了线程生命周期与线程通信。文章首先分析了线程的五个基本状态及其转换过程,结合JDK1.8版本的特点进行了深入讲解。接着,通过多个实例介绍了线程通信的几种实现方式,包括使用`volatile`关键字、`Object`类的`wait()`和`notify()`方法、`CountDownLatch`、`ReentrantLock`结合`Condition`以及`LockSupport`等工具。全文旨在帮助读者理解线程管理的核心概念和技术细节。
24 1
[Java]线程生命周期与线程通信
|
6天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
1天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
6 1
|
5天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
11 3
|
7天前
|
监控 安全 Java
Java多线程编程的艺术与实践
【10月更文挑战第22天】 在现代软件开发中,多线程编程是一项不可或缺的技能。本文将深入探讨Java多线程编程的核心概念、常见问题以及最佳实践,帮助开发者掌握这一强大的工具。我们将从基础概念入手,逐步深入到高级主题,包括线程的创建与管理、同步机制、线程池的使用等。通过实际案例分析,本文旨在提供一种系统化的学习方法,使读者能够在实际项目中灵活运用多线程技术。
|
5天前
|
缓存 安全 Java
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文将深入探讨Java中的多线程编程,包括其基本原理、实现方式以及常见问题。我们将从简单的线程创建开始,逐步深入了解线程的生命周期、同步机制、并发工具类等高级主题。通过实际案例和代码示例,帮助读者掌握多线程编程的核心概念和技术,提高程序的性能和可靠性。
8 2
|
6天前
|
Java
Java中的多线程编程:从基础到实践
本文深入探讨Java多线程编程,首先介绍多线程的基本概念和重要性,接着详细讲解如何在Java中创建和管理线程,最后通过实例演示多线程的实际应用。文章旨在帮助读者理解多线程的核心原理,掌握基本的多线程操作,并能够在实际项目中灵活运用多线程技术。
|
7天前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
34 4