PHP8 —— New String Helpers

新增了三个字符串函数,str_starts_with, str_ends_with, str_contains, PHP 的函数这么方便,很难想象竟然一直没有这几个。 str_starts_with 判断字符串是否以另一个字符串开头,在PHP7以及之前 $id = 'inv_abcdefgh'; $result = strpos($id, 'inv_') === 0; var_dump($result); // true PHP8 中可以直接这么写 $result = str_starts_with($id, 'inv_'); str_ends_with 判断字符串是否以另外一个字符串结尾,在 PHP7 及之前,比较麻烦,通常是这么写 $id = 'abcd_inv'; $result = strpos(strrev($id), strrev('_inv')) === 0; 或者 $result = substr($id, -1 * strlen('_inv')) === '_inv'; 或者上正则吧 $result = preg_match('/_inv$/', $id) === 1; 看起来都是比较麻烦的。PHP8 里面可以简化成下面这样了 $id = 'abcd_inv'; $result = str_ends_with($id, '_ind'); str_contains 字符串包含,PHP8 之前一般就是 strpos 来实现了 $url = 'https://example?for=bar'; $result = strpos($url, '?') !== FALSE; PHP8 就直接一点 $result = str_contains($url, '?');

Java学习笔记 —— Java 8 Map 新特性

Java 8引入了Map接口支持的几种默认方法。 我们从forEach开始介绍。

forEach

旧版Java遍历Map的键和值一直很尴尬,需要在Map上使用Map.Entry 的迭代器:

for(Map.Entry<String, Integer> entry: ageOfFriends.entrySet()) { 
    String friend = entry.getKey();
    Integer age = entry.getValue();
    System.out.println(friend + " is " + age + " years old");
}

从Java 8开始,Map接口支持forEach方法,该方法接受BiConsumer,并将键和值作为参数。 使用forEach可以让代码更为简洁:

ageOfFriends.forEach((friend, age) -> System.out.println(friend + " is " + age + " years old"));

Java 8引入了两种便捷的方法来比较Map中的条目,可以按值或键对Map进行排序:

  • Entry.comparingByValue
  • Entry.comparingByKey

以下代码:


Map<String, String> favouriteMovies
    = Map.ofEntries(entry("Raphael", "Star Wars"), 
    entry("Cristina", "Matrix"),
    entry("Olivia","James Bond"));
favouriteMovies
    .entrySet()
    .stream() .sorted(Entry.comparingByKey()) .forEachOrdered(System.out::println);

的输出为:

Cristina=Matrix
Olivia=James Bond
Raphael=Star Wars

getOrDefault

因为要查找的key可能不存在,为了防止NullPointerException,就必须检查空引用,一般我们会补一个默认值。 Java8之后可以使用getOrDefault方法简单实现这个需求。key 作为第一个参数,默认值作为第二个参数:


Map<String, String> favouriteMovies
    = Map.ofEntries(entry("Raphael", "Star Wars"), 
    entry("Olivia", "James Bond"));
System.out.println(favouriteMovies.getOrDefault("Olivia", "Matrix"));
System.out.println(favouriteMovies.getOrDefault("Thibaut", "Matrix"));

如果 key 存在于Map中,但意外地指向了null值,则getOrDefault仍可以返回null。 另外一点,无论 key 是否存在,默认值表达式都会执行一次。
Java 8还包括一些更高级的模式来处理给定键的值的存在和不存在。

Compute patterns

有时,我们希望根据 Map中是否存在key,有条件地执行运算并存储其结果。 例如,用map对耗时计算结果进行缓存。 如果存在key,则无需重新计算结果。通常使用下面三个方法:

  • computeIfAbsent —— 如果给定key没有指定值(不存在,或者其值为null),计算新值并将其添加到Map中。
  • computeIfPresent —— 如果存在指定的key,计算一个新值并将其添加到 map。
  • compute —— 始终计算给定key的新值并将其存储。

我们可以使用computeIfAbsent来缓存信息。 假设计算一组文件的SHA-256。 如果你以前处理过该文件,则无需重新计算。

我们使用Map来实现缓存,使用MessageDigest的一个实例来计算SHA-256哈希值:

Map<String, byte[]> dataToHash = new HashMap<>();
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");

然后,我们遍历数据并缓存结果:

lines.forEach(line ->
    dataToHash.computeIfAbsent(line,
    this::calculateDigest));
private byte[] calculateDigest(String key) {
    return messageDigest.digest(key.getBytes(StandardCharsets.UTF_8));
 }

这种模式对于批量添加元素也很有用。如果需要向Map>添加元素,我们需要确保元素已初始化。传统方法如下:

String friend = "Raphael";
List<String> movies = friendsToMovies.get(friend); 
if(movies == null) {
   movies = new ArrayList<>();
   friendsToMovies.put(friend, movies); 
}
movies.add("Star Wars");
System.out.println(friendsToMovies);

改用 computeIfAbsent之后就很简单了:

friendsToMovies.computeIfAbsent("Raphael", name -> new ArrayList<>())
.add("Star Wars");

如果与键关联的当前值存在于Map中并且为非null,则validateIfPresent方法将计算一个新值。需要注意一点,如果产生的值为null,当前的key会从Map中删除。

Remove patterns

Java 8 之前,删除指定映射关系的元素,实现如下:

String key = "Raphael";
String value = "Jack Reacher 2";
if (favouriteMovies.containsKey(key) &&
    Objects.equals(favouriteMovies.get(key), value)) { 
    favouriteMovies.remove(key);
    return true;
} else {
   return false;
}

Java8 可以使用如下方法:

favouriteMovies.remove(key, value);

Replacement patterns

Java 8 提供了两个新方法,用于替换Map中的条目:

  • replaceAll —— 将每个条目的值替换为应用BiFunction的结果。此方法的作用类似于List上replaceAll。
  • replace —— 如果存在key,则可以替换Map中的值。仅当键映射到特定值时,附加重载才替换该值。

可以使用如下方法格式化map中的所有值:

Map<String, String> favouriteMovies = new HashMap<>();
favouriteMovies.put("Raphael", "Star Wars");
favouriteMovies.put("Olivia", "james bond");
favouriteMovies.replaceAll((friend, movie) -> movie.toUpperCase());
System.out.println(favouriteMovies);

Merge

假设需要合并两个map,比如两组联系人的map。 可以按如下方式使用putAll:

Map<String, String> family = Map.ofEntries(
entry("Teo", "Star Wars"), entry("Cristina", "James Bond"));
Map<String, String> friends = Map.ofEntries( entry("Raphael", "Star Wars"));
Map<String, String> everyone = new HashMap<>(family);
everyone.putAll(friends);
System.out.println(everyone);

只要没有重复的key,此代码就可以正常工作。 如果需要更加灵活的merge,就可以使用新方法。 此方法采用BiFunction合并具有重复key的值:

Map<String, String> family = Map.ofEntries(
entry("Teo", "Star Wars"), entry("Cristina", "James Bond"));
Map<String, String> friends = Map.ofEntries(
    entry("Raphael", "Star Wars"), entry("Cristina", "Matrix"));    

也将merge方法与forEach结合使用,提供一种解决冲突的方法。

Map<String, String> everyone = new HashMap<>(family);
friends.forEach((k, v) ->
    everyone.merge(k, v, (movie1, movie2) -> movie1 + " & " + movie2));
System.out.println(everyone);

最后要注意一点,Javadoc指出,merge方法具有相当复杂的空值处理方式:

如果指定的键尚未与值关联或与null关联,则[merge]会将其与给定的非null值关联。 否则,[merge]将关联的值替换为给定重新映射函数的[result],如果结果为null,则将其删除。

我们还可以使用merge来实现初始化检查。 假设有一个map,用于记录观看电影的次数。 你需要先检查map中代表电影的关键点,然后才能增加其值:

Map<String, Long> moviesToCount = new HashMap<>(); 
String movieName = "JamesBond";
long count = moviesToCount.get(movieName); if(count == null) {
    moviesToCount.put(movieName, 1); }
else {
    moviesToCount.put(moviename, count + 1);
}

以上代码可以简化为

moviesToCount.merge(movieName, 1L, (key, count) -> count + 1L);

在这种情况下要合并的第二个参数是1L。 因为为该key返回的值为null,所以第一次赋值为1。 下次计数时,由于key的值已初始化为1,因此将应用BiFunction来增加计数。

评论

此博客中的热门博文

D3js Data-binding basics

JavaScript 数据类型

Vue3新特性(1) —— Vite