Java 9
Stream.takeWhile
使用一个断言作为参数,返回给定 Stream 的子集直到断言语句第一次返回 false。
可以用于有序流中获取第一个不满足条件之前的元素。
// 1 2
Stream.of(1, 2, 3, 4, 5).takeWhile(x -> x < 3).forEach(System.out::println);
Stream.dropWhile
使用一个断言作为参数,直到断言语句第一次返回 false 才返回给定 Stream 的子集。
适用于有序流中获取从第一个不满足条件的元素开始的元素。
// 3 4 5
Stream.of(1, 2, 3, 4, 5).dropWhile(x -> x < 3).forEach(System.out::println);
IntStream.iterate
使用初始种子值创建顺序流。
// 3 6 9
IntStream.iterate(3, x -> x < 10, x -> x + 3).forEach(System.out::println);
Collectors.flatMapping
Set<Integer> set = Stream.of("a", "ab", "abc").collect(Collectors.flatMapping(v -> v.chars().boxed(), Collectors.toSet()));
Java 11
HttpClient
GET
public void get() throws ExecutionException, InterruptedException {
String url = "https://www.githubstatus.com/api/v2/status.json";
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/x-www-form-urlencoded")
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
.GET()
.build();
// 异步
String responseBody = HttpClient.newHttpClient()
.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenApply(Object::toString)
.get();
System.out.println(responseBody);
}
GET 带有查询参数
public void getQuery() throws IOException, InterruptedException {
String url = "https://caniuse.com/process/query.php";
Map<String, String> queryMap = Maps.newHashMap();
queryMap.put("search", "encodeURIComponent");
// cn.hutool.core.util.URLUtil;
String query = URLUtil.buildQuery(queryMap, StandardCharsets.UTF_8);
if (query != null) {
url += "?" + query;
}
String[] headers = {
"Content-Type",
"application/x-www-form-urlencoded",
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
};
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(url))
.headers(headers)
.GET()
.build();
// 同步
HttpResponse<String> response = HttpClient.newHttpClient().send(httpRequest, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
POST
public void post() throws ExecutionException, InterruptedException {
String url = "https://example.com";
Map<String, String> queryMap = Maps.newHashMap();
queryMap.put("key", "value");
String query = URLUtil.buildQuery(queryMap, StandardCharsets.UTF_8);
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(query, StandardCharsets.UTF_8))
.build();
String response = HttpClient.newHttpClient()
.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenApply(Object::toString)
.get();
System.out.println(response);
}
POST JSON
public void postJson() throws ExecutionException, InterruptedException {
String url = "https://example.com";
String jsonStr = JSONUtil.toJsonStr(Maps.newHashMap());
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonStr, StandardCharsets.UTF_8))
.build();
String response = HttpClient.newHttpClient()
.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenApply(Object::toString)
.get();
System.out.println(response);
}
Java 14
switch 表达式增强
引入了对 lambda 语法的支持,并且可以忽略每个 case 后的 break,同时提供了 yield 来在 block 中返回值。
for (int i = 0; i < 10; i++) {
int flag = RandomUtil.randomInt(1, 50);
switch (flag) {
case 1 -> System.out.println("case 1");
case 2 -> System.out.println("case 2");
case 3 -> System.out.println("case 3");
case 4 -> System.out.println("case 4");
default -> System.out.println("case default");
}
}
for (int i = 0; i < 10; i++) {
int day = RandomUtil.randomInt(1, 15);
String result = switch (day) {
case 1, 2, 3 -> "123";
case 4, 5, 6 -> "456";
default -> {
if (day > 7) {
yield "Please insert a valid day.";
} else {
yield "Looks like a Sunday.";
}
}
};
System.out.println(result);
}
Java 15
隐藏类
引入隐藏类的主要目的是给框架来使用,使得框架可以在运行时生成类,并通过反射间接使用它们。
public class HiddenClass {
public static String hello() {
return "https://blog.homurax.com/";
}
}
public void test() throws Throwable {
String filePath = "target/classes/com/homurax/feature/HiddenClass.class";
byte[] bytes = Files.readAllBytes(Paths.get(filePath));
String CLASS_INFO = Base64.getEncoder().encodeToString(bytes);
System.out.println(CLASS_INFO);
// 通过反射加载上面生成的类,并调用隐藏类中的 hello
byte[] classInBytes = Base64.getDecoder().decode(CLASS_INFO);
Class<?> proxy = MethodHandles.lookup()
// 创建普通类是用 ClassLoader::defineClass
// bytes:符合 Java 虚拟机规范的字节码
// initialize:是否要初始化类
// options:Java 类的类型,详见 java.lang.invoke.MethodHandles.Lookup.ClassOption
.defineHiddenClass(classInBytes, true, MethodHandles.Lookup.ClassOption.NESTMATE)
.lookupClass();
// 类名
System.out.println(proxy.getName());
// 方法
for (Method method : proxy.getDeclaredMethods()) {
System.out.println(method.getName());
}
// 调用
MethodHandle mh = MethodHandles.lookup().findStatic(proxy, "hello", MethodType.methodType(String.class));
String result = (String) mh.invokeExact();
System.out.println(result);
}
Java 16
Record 类
可以单独文件声明、在类内部声明、方法内声明。
Record 类是一个 final 类,其自动实现 equals、hashCode、toString 方法,成员变量均为 public。
record Rectangle(int length, int width) {
// 自定义成员方法
public int area() {
return Rectangle.this.length * Rectangle.this.width;
}
}
Rectangle rectangle = new Rectangle(700, 300);
System.out.println(rectangle.length);
System.out.println(rectangle.width);
System.out.println(rectangle.area());
System.out.println(rectangle);
System.out.println(rectangle.equals(new Rectangle(700, 300)));
instanceof 增强
for (Object obj : new Object[]{"test", 10}) {
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
} else if (obj instanceof Integer n) {
System.out.println(n * n);
}
}
Stream.toList
List<Character> characters = Stream.of('a', 'b', 'c').toList();
注意创建的是一个 unmodifiableList。
// Stream.java
default List<T> toList() {
return (List<T>) Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())));
}
Java 17
Sealed Classes
密封类可以对继承或者实现它们的类进行限制,这样这个类就只能被指定的类继承。
密封类必须有子类,密封类不支持匿名类与函数式接口。
permits 指定了 Animal 只允许 Cat 和 Dog 继承。
如果允许扩展的子类和封闭类在同一个源代码文件里,封闭类可以不使用 permits 语句,Java 编译器将检索源文件,在编译期为封闭类添加上许可的子类
public sealed class Animal permits Cat, Dog {
}
任何扩展密封类的类本身都必须声明为 sealed、non-sealed 或 final。
public final class Cat extends Animal {
}
使用 non-sealed 关键字对类进行显式的声明不进行密封,由下游调用方承担打破密封的风险。
public non-sealed class Dog extends Animal {
}
sealed 子类传递了密封性,final 的子类确认密封性,non-sealed 显式放弃密封性。
增强的伪随机数生成器
JDK 17 之前,可以使用 Random、ThreadLocalRandom 和 SplittableRandom 来生成随机数。
不过这几个类实现代码质量和接口抽象不佳,且缺少常见的伪随机算法支持。
Java 17 为伪随机数生成器 PRNG(pseudorandom number generator,又称确定性随机位生成器)增加了新的接口类型和实现。
RandomGenerator 接口为所有的 PRNG 算法提供统一的 API,并且可以获取不同类型的 PRNG 对象流。
RandomGeneratorFactory 用于构造各种 RandomGenerator 实例。
RandomGeneratorFactory.all().forEach(factory -> System.out.println(factory.group() + ": " + factory.name()));
RandomGeneratorFactory<RandomGenerator> random = RandomGeneratorFactory.of("L128X256MixRandom");
RandomGenerator randomGenerator = random.create(System.currentTimeMillis());
for (int i = 0; i < 5; i++) {
System.out.println(randomGenerator.nextInt(10));
}