还在用着Java 8吗?09月26日Oracle的长期支持版Java 11已经出炉,将一直支持到2026年9月。对广大的程序员们来说,从Java 9~11,日常的编码都有什么变化呢?一起来看看吧。
var关键字
Java 10引进了var
关键字来指代任意类型,让它朝C#又迈进了一步。以下是两个例子:
1 2 3
| var abc = new ArrayList<String>(); for (var character : abc) { }
|
var
并不仅仅能类型推断,它还能完成以前做不到的事——引用匿名类的变量:
1 2 3 4
| var o = new Object() { int a = 1; }; System.out.println(o.a);
|
在所有可能的地方都用上var
也许并不是一个好实践。一种做法是:如果后面的表达式一眼就能看出来是啥类型,我们就用var
;否则,就还是老老实实地写声明,一眼扫过就能明白的代码更具可读性。
更详细的用法请参考Oracle官方文档。Java 11更是允许对lambda的参数使用var
。
集合字面量
一直以来我们都是老老实实地用以下这些方法来初始化一个已知的不可变集合:
1 2 3 4 5 6 7 8
| List<String> abc1 = Arrays.asList("A", "B", "C"); Collection<String> abc2 = Collections.unmodifiableCollection(abc1); List<String> abc3 = ImmutableList.of("A", "B", "C");
|
可变集合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| List<String> abc4 = new ArrayList<>(); abc4.add("A"); abc4.add("B"); abc4.add("C"); List<String> abc5 = new ArrayList<>(Arrays.asList("A", "B", "C")); List<String> abc6 = new ArrayList<>(){{ add("A");add("B");add("C"); }} List<String> abc7 = Stream.of("A", "B", "C").collect(Collectors.toList()); List<String> abc8 = Lists.newArrayList("A", "B", "C");
|
对于不可变集合,Java 9引入了集合的工厂方法of
,这回终于可以用上原装的了:
1 2 3 4 5 6
| List<String> abc1 = List.of("A", "B", "C"); Set<String> abc2 = Set.of("A", "B", "C"); Map<String, Integer> abc3 = Map.of("A", 1, "B", 2, "C", 3); Map<String, Integer> abc4 = Map.ofEntries(Map.entry("A", 1), Map.entry("B", 2), Map.entry("C", 3));
|
值得注意的是,List
不允许通过of
传入null
,Map
不允许传入相同的键。
1 2
| List.of("A", null); Map.of("A", 1, "A", 2);
|
Java 10引入了copyOf
方法,也能方便地从可变集合中创建出不可变集合:
1 2
| var abcMutable = new ArrayList<>(List.of("A", "B", "C")) var abcImmutable = List.copyOf(abcMutable);
|
Java 10还在Collectors
类中增加了toUnmodifiableList
/toUnmodifiableMap
/toUnmodifiableSet
方法,可以直接从Stream
创建一个不可变集合:
1
| List<String> abc = Stream.of("A", "B", "C").collect(Collectors.toUnmodifiableList());
|
Deprecated
这个相对简单,就是给@Deprecated
注解增加了两个字段:
1
| @Deprecated(since="1.1", forRemoval = true)
|
前者表示从哪个版本起不建议使用,后者表示未来是否会将其删除。
接口的private方法
从Java 8起,允许给接口增加default
方法。从Java 9起,接口又增加了一项能力:可以定义private
方法了。示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public interface Dog { default void bark() { System.out.println("bowwow"); wagTail(); } default void walk() { System.out.println("walk"); wagTail(); } private void wagTail() { System.out.println("wag"); } }
|
既然如此,它跟抽象类还有什么区别吗?接口的优势就是允许子类实现多个接口,而抽象类因为可以拥有可变字段(接口的字段是final
的)而更为强大。我们在接口定义方法时,也应当让其尽可能简洁。
Optional的新方法
从Java 9起,Optional
终于可以通过stream()
方法返回一个Stream
了,这样它就可以用上Stream
提供的许多API了。原来只能这么做:
1 2 3 4 5 6 7
| Optional<String> optional = Optional.of("ggg"); Stream<String> texts = optional.map(Stream::of).orElseGet(Stream::empty); Stream<String> texts = optional.stream();
|
另一个可以耍耍的方法是ifPresentOrElse
:
1 2 3 4 5 6 7 8 9
| if (optional.isPresent()) { System.out.print(optional.get()); } else { System.out.println(); } optional.ifPresentOrElse(System.out::print, System.out::println);
|
Java 9还新增了一个方法or
,与原来的orElse
类似,但是返回的是一个Optional
:
1 2 3 4 5
| String name = optional.orElse("n/a"); Optional<String> name = optional.or(() -> Optional.of("n/a"));
|
HttpClient
Java 11引进了HttpClient,http请求变得简单了:
1 2 3 4 5
| HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder().uri(URI.create(uri)).build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body());
|
异步也很简单,返回一个CompletableFuture
:
1 2 3 4 5 6 7
| HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder().uri(URI.create(uri)).build(); CompletableFuture<Void> futureResponse = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println); futureResponse.get();
|
原生的HttpURLConnection
可以抛弃了。Apache的HttpClient
也许也可以雪藏了。
可过期的CompletableFuture
上一小节中,我们可以得到一个CompletableFuture
。从Java 9起,它可以设置过期时间了。可以把上面的futureResponse.get()
替换如下:
1 2
| CompletableFuture<Void> timeoutFuture = futureResponse.orTimeout(1, TimeUnit.SECONDS); timeoutFuture.get();
|
如果没有在设置的时间内获得结果,便会抛出java.util.concurrent.ExecutionException: java.util.concurrent.TimeoutException
。如果想在过期时不抛异常而是设一个默认值,可以这样做:
1 2 3 4
| CompletableFuture<String> futureResponse = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) CompletableFuture<String> timeoutFuture = futureResponse.completeOnTimeout("default value", 1, TimeUnit.MILLISECONDS); timeoutFuture.get();
|
Process API
Process API是元老级的API了,但是功能一直不够完整,无法获取进程的PID、用户、命令等。Java 9引入了ProcessHandle
,可以查询进程,甚至允许在进程退出时执行方法。它提供了获取当前进程的current
方法,以及获取全部进程的allProcesses
方法。用法如下:
1 2 3 4
| Optional<String> currentUser = ProcessHandle.current().info().user(); ProcessHandle.allProcesses() .filter(p -> p.info().user().equals(currentUser)) .forEach(p -> System.out.println(String.valueOf(p.pid()) + " " + p.info().command()));
|
如果在Mac中打开了一个TextEdit
,便可以看到类似这样的输出结果:1234 Optional[/Applications/TextEdit.app/Contents/MacOS/TextEdit]。Windows的话可以打开notepad.exe
,也能看到:1234 Optional[C:\Windows\System32\notepad.exe]。可以用以下方法在该进程退出时打印一些信息:
1 2 3 4
| Optional<ProcessHandle> optionalProcessHandle = ProcessHandle.of(1234); CompletableFuture<Void> future = optionalProcessHandle.get() .onExit().thenAccept(x -> System.out.println(x.pid())); future.get();
|
甚至还能用optionalProcessHandle.get().destroy()
来摧毁进程。如此这般,Java外部打开的TextEdit
或notepad.exe
都会被退出。由于其它用户的进程无法使用ProcessHandle.of
来获取,所以只能杀掉自己的进程。对安全方面感兴趣的话可以参考一下官方文档。