除了众所周知的 JEP 之外,Java 21 还有更多内容。首先请确认 java 版本:
$ java -version
openjdk version "21" 2023-09-19
OpenJDK Runtime Environment (build 21+35-2513)
OpenJDK 64-Bit Server VM (build 21+35-2513, mixed mode, sharing)
我们一起来看看 String 和它的朋友们 Collections、Date、Time、HttpClient、并发、Math 和 BigInteger 的新增一些 API。
String 新增了 indexOf() 方法,允许在起始索引和结束索引之间查找单个字符或子字符串。
public int indexOf(int ch, int beginIndex, int endIndex);
public int indexOf(String str, int beginIndex, int endIndex);
示例:
String text = "牛牛帮";
int index = text.indexOf("0", 5, 15);
Character 类新增了几个方法来支持 Emoji 表情符号,新增了五个方法:isEmoji()
、isEmojiComponent()
、isEmojiModifier()
、isEmojiModifierBase()
和 isEmojiPresentation()
。
public static boolean isEmoji(int codePoint);
public static boolean isEmojiPresentation(int codePoint);
public static boolean isEmojiModifier(int codePoint);
public static boolean isEmojiModifierBase(int codePoint);
public static boolean isEmojiComponent(int codePoint);
public static boolean isExtendedPictographic(int codePoint);
所有这些方法都接受一个 int 类型的码点作为参数,判断传入的码点是否具有对应的表情符号。
StringBuffer
和 StringBuilder
新增了 repeat()
方法重复单个字符或字符串多次。
StringBuilder:
@Override
public StringBuilder repeat(int codePoint, int count);
@Override
public StringBuilder repeat(CharSequence cs, int count);
StringBuffer:
@Override
public synchronized StringBuffer repeat(int codePoint, int count);
@Override
public synchronized StringBuffer repeat(CharSequence cs, int count);
Charset 新增了一个带 fallback 的 forName()
方法。
public static Charset forName(String charsetName, Charset fallback);
提取地址中的省市区县,代码如下:
笔者有幸用过正则分组,需求是提取地址中的省市区县,代码如下:
private static final Pattern PATTERN = Pattern.compile("(?[^省]+自治区|.*?省|.*?行政区|.*?市)(?[^市]+自治州|.*?地区|.*?行政单位|.+盟|市辖区|.*?市|.*?县)(?[^县]+县|.+区|.+市|.+旗|.+海域|.+岛)?(?[^区]+区|.+镇)?(?.*)");
public static Map getAddressResolution(String address) {
Matcher matcher = PATTERN.matcher(address);
String province, city, county, town, village;
Map row = new HashMap<>();
while (matcher.find()) {
province = matcher.group("province");
row.put("province", province == null ? "" : province.trim());
city = matcher.group("city");
row.put("city", city == null ? "" : city.trim());
county = matcher.group("county");
row.put("county", county == null ? "" : county.trim());
town = matcher.group("town");
row.put("town", town == null ? "" : town.trim());
village = matcher.group("village");
row.put("village", village == null ? "" : village.trim());
}
return row;
}
public static void main(String[] args) {
String address = "湖南省长沙市岳麓区永青路668号";
System.out.println(getAddressResolution(address));
// {province=湖南省, city=长沙市, county=岳麓区, town=, village=永青路668号}
}
Matcher、MatchResult、Pattern 现在新增了 namedGroups()
相关方法, getAddressResolution()
方法简化成这样了:
public static Map getAddressResolution(String address) {
Matcher matcher = PATTERN.matcher(address);
Map row = new HashMap<>();
if (matcher.matches()) {
Map groupMap = matcher.namedGroups();
groupMap.forEach((key, group) -> row.put(key, matcher.group(group)));
}
return row;
}
Collections 框架中添加了 SequencedCollection、SequencedSet 和 SequencedMap 三个接口(Java21 新增)。
interface SequencedCollection extends Collection {
// new method
SequencedCollection reversed();
// methods promoted from Deque
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
interface SequencedSet extends Set, SequencedCollection {
SequencedSet reversed(); // covariant override
}
interface SequencedMap extends Map {
// new methods
SequencedMap reversed();
SequencedSet sequencedKeySet();
SequencedCollection sequencedValues();
SequencedSet> sequencedEntrySet();
V putFirst(K, V);
V putLast(K, V);
// methods promoted from NavigableMap
Entry firstEntry();
Entry lastEntry();
Entry pollFirstEntry();
Entry pollLastEntry();
}
添加这 3 个新接口后,Java 集合类图发生了变化,图示:
除了这些,现在还可以使用多个工厂方法创建具有给定初始容量的不同类型的 Map(Java19新增)。
HashMap.newHashMap(100);
HashSet.newHashSet(100);
LinkedHashMap.newLinkedHashMap(100);
LinkedHashSet.newLinkedHashSet(100);
WeakHashMap.newWeakHashMap(100);
Map 创建看起来很简单,但实际上却有一些技巧。例如,HashMap 内部是基于数组构建的,当该数组装满时,它会被复制到一个更大的数组中。所以阿里《JAVA开发手册》也建议设置 HashMap 的初始容量。问题在于,实际上并不仅仅是复制,第一个数组的所有键值对都需要重新计算哈希值,以便放置在新数组的正确位置上。由于哈希值的计算取决于数组的大小,每个数组的存储位置很可能不同。因此,我们不能简单地将第一个数组的内容复制到第二个数组中。为了避免冲突,当数组达到 75% 的容量时,就被认为已经满了。因此,要创建一个可以存储 100 个键值对的 Map,实际上需要一个大小为 134 的数组。考虑到数组的大小必须是 2 的次方,因此实际上会创建一个大小为 256 的数组。这种计算很容易出错,所以现在有了一个工厂方法。现在我们只需要调用 newHashMap(size)
,传递所需的容量就好了。
Date and Time API 的一个很好的增加功能是 DateTimeFormatter 的本地化支持ofLocalizedPattern()
工厂方法,传递一个字符串格式,它会生成一个本地化的模式。
DateTimeFormatterBuilder 也已更新,添加了一个 appendLocalized()
方法,用于添加一个本地化模式(类型为字符串)。
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedPattern("yM");
System.out.println(now.format(formatter)); // 2023年9月
PrintStream 在 Java18 中新增了 charset()
方法用于获取使用的字符集。
public Charset charset();
HttpClient、ExecutorService 和 ForkJoinPool 类在 17 和 21 之间实现了 AutoCloseable
。现在可以使用 try-with-resources
语句来创建实例。
Math 类和 BigInteger 类也得到了一些关注。首先,Math类添加了一系列方法。
// 执行两个数字的向上取整除法运算,并返回大于或等于除法结果的最小整数。
public static int ceilDiv(int x, int y);
public static long ceilDiv(long x, int y);
public static long ceilDiv(long x, long y);
// 执行两个数字的向上取整除法运算,但是在被除数不能被除数整除时,
// 会抛出ArithmeticException异常。
public static int ceilDivExact(int x, int y);
public static long ceilDivExact(long x, long y);
// 这个方法返回的是两个数字进行向上取整除法运算后的余数。
public static int ceilMod(int x, int y);
public static int ceilMod(long x, int y);
public static long ceilMod(long x, long y);
// 这个方法用于对两个数字进行整除运算,并确保结果是一个整数。
// 如果除数无法整除被除数,将会抛出ArithmeticException异常。
public static int divideExact(int x, int y);
public static long divideExact(long x, long y);
// 执行两个数字的向下取整除法运算,但是在被除数不能被除数整除时,
// 会抛出ArithmeticException异常。
public static int floorDivExact(int x, int y);
public static long floorDivExact(long x, long y);
添加了 clamp()
方法可以处理 int、long、float 和 double 原始类型,将提供的值限制在给定的最小值和最大值之间。
public static int clamp(long value, int min, int max);
public static long clamp(long value, long min, long max);
public static double clamp(double value, double min, double max);
public static float clamp(float value, float min, float max);
还有一个方法:unsignedMultiplyHigh()
,用于将两个长整型数作为无符号数相乘,并将64位高位作为长整型返回。
public static long unsignedMultiplyHigh(long x, long y);
BigInteger 类增加了一个功能:在并行中执行乘法运算,使用了 Fork/Join 框架。
public BigInteger parallelMultiply(BigInteger val);
关于 VirtualThread 的内容这里不做过多介绍,想必各位会在各种途径看到各式各样的文章。在这里,我只想介绍两个方面。
首先,Thread 现在添加了 join()
方法和 sleep()
方法,两者都接受 Duration 作为参数,Duration 语义化更加易用。另外就是 isVirtual()
方法,用于判断线程是否为虚拟线程。
public static void sleep(Duration duration) throws InterruptedException;
public final boolean join(Duration duration) throws InterruptedException;
public final boolean isVirtual();
另外需要重点介绍的一个是在 Future 接口中添加的新方法。实际上有三个方法:
resultNow()
和exceptionNow()
这两个方法不会抛出已检查异常,因此代码中不再需要 try catch
,使用起来更加方便。但请注意,如果调用这些方法并且使用的 Future 对象尚未完成,那么将会得到一个 IllegalStateException
异常。需要在确保 Future 对象产生了结果或异常时调用这些方法。
第三个方法是 state()
,可以调用它来检查 Future 对象的当前状态。返回值是一个新的枚举:Future.State。
enum State {
/**
* 表示任务尚未完成
*/
RUNNING,
/**
* 表示任务成功完成
*/
SUCCESS,
/**
* 表示任务完成但出现异常
*/
FAILED,
/**
* 表示任务被取消。
*/
CANCELLED
}
因此,通过这个API,现在可以更精确地监控任务的进展情况。