《Java8实战》读书笔记15:附录中描述的其他新特性

Posted 笑虾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Java8实战》读书笔记15:附录中描述的其他新特性相关的知识,希望对你有一定的参考价值。

《Java8实战》读书笔记15:附录中描述的其他新特性

附录 A 其他语言特性的更新

本附录中,讨论Java 8中其他的三个语言特性的更新,分别是重复注解(repeated annotation)、类型注解(type annotation)和通用目标类型推断(generalized target-type inference)。

A.1 注解

Java 8 在两个方面对注解机制进行了改进:

  1. 你现在可以定义重复注解。(以前,是借助注解容器来实现)
  2. 使用新版Java,你可以为任何类型添加注解。(以前,只有声明可以被注解)

A.1.1 重复注解

    /** 自定义注解的容器 */
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface MyRepeatables 
        MyRepeatable[] value();
    
    /** 自定义注解 */
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    @Repeatable( MyRepeatables.class ) // 声明为:可重复添加
    public @interface MyRepeatable 
        String value();
    
	// Java8 以前的用法
    @MyRepeatables( @MyRepeatable("老:自定义注解1"), @MyRepeatable("老:自定义注解2") )
    class Demo1  
    // Java8 后只要注解上加上 @Repeatable 就可以重复添加了:
    @MyRepeatable( "自定义注解1" ) @MyRepeatable( "自定义注解2" )
    class Demo2  

    public static void main(String[] args) 
        Arrays.stream(Demo1.class.getAnnotationsByType(MyRepeatable.class))
                .forEach(System.out::println);
        Arrays.stream(Demo2.class.getAnnotationsByType(MyRepeatable.class))
                .forEach(System.out::println);
    
@annotation.RepeatableAnnotationDemo$MyRepeatable(value=老:自定义注解1)
@annotation.RepeatableAnnotationDemo$MyRepeatable(value=老:自定义注解2)
@annotation.RepeatableAnnotationDemo$MyRepeatable(value=自定义注解1)
@annotation.RepeatableAnnotationDemo$MyRepeatable(value=自定义注解2)

A.1.2 类型注解

Java 8开始,注解能应用于任何类型。包括:new操作符类型转换instanceof 检查泛型类型参数,以及 implementsthrows 子句。

@NonNull String name = person.getName();
List<@NonNull Car> cars = new ArrayList<>();

字段 name 上加的是 lombok@NonNull,然后再用 @Setter 自动生成的效果:

public void setName(@NonNull String name) 
    if (name == null) 
        throw new NullPointerException("name is marked non-null but is null");
     else 
        this.name = name;
    

顺便提一句这个 @NonNull@Retention(RetentionPolicy.CLASS) 的,虚拟机不加载它,无法反射获取。我还试着去取,取了半天,才想起来看看。。。

A.2 通用目标类型推断

  • Java 8 这前泛型参数推断依赖上下文 。
static <T> List<T> emptyList();
// 显示指明类型
List<Car> cars = Collections.<Car>emptyList();
// Java自己推断泛型类型
List<Car> cars = Collections.emptyList();
  • Java 8对泛型参数的推断进行了增强。如下也能推的动:
static void cleanCars(List<Car> cars)  
cleanCars(Collections.emptyList());

换句话说,如果推不动,那 Stream 还怎么玩。

List<Car> cleanCars = dirtyCars.stream() 
	.filter(Car::isClean) 
	.collect(Collectors.toList());

附录 B 类库的更新

B.1 集合

B.1.1 其他新增的方法

表B-1 集合类和接口中新增的方法

类/接口新 方 法
MapgetOrDefault:如果 key 找不到映射 value。则返回给定的默认值
forEach:两个入参的消费者,默认按 entrySet 顺序读一遍。
compute:如果没有 value 就取nullK, V丢给 lambda 算出结果 put返回
computeIfAbsent:如果不存在,就算个值给它。
computeIfPresent:如果有值,就重新算个值给它。没有就不处理。
merge:如果为 put,否则 lambda结果为空则 删除 有值 put
putIfAbsent:如果 key 映射的 valuenull,则 put(k, v),否则 get(k)
remove(key,value)键、值都匹配则删除。
replace:有值就执行替换。
replaceAll :调用给定函数,替换每个元素,直到完成所有或异常。
IterableforEach:对每个元素执行给定操作,直到处理完所有元素或引发异常。
spliterator :用当前 Iterable 生成一个Spliterator
IteratorforEachRemaining :对剩下的每个元素执行指定操作。
CollectionremoveIf:删除所有符合 lambda 判断结果的元素。
stream:以此集合为源返回一个序列流。
parallelStream :返回一个并行流。
ListreplaceAll:将该列表的每个元素替换为将运算符应用于该元素的结果。
sort :根据指定的 Comparator 引发的顺序对此列表进行排序。
BitSetstream:返回此 BitSet包含处于set状态的位的索引流

1. Map

  • getOrDefault
    以前
Map<String, Integer> carInventory = new HashMap<>(); 
Integer count = 0; 
if(map.containsKey("Aston Martin"))
	count = map.get("Aston Martin"); 

Java8:
注意未映射,和映射到 null 的区别。

Integer count = map.getOrDefault("Aston Martin", 0);

map.put("aaa", null);
map.getOrDefault("aaa", 666); // null
  • computeIfAbsent

以前

public String getData(String url) 
	String data = cache.get(url); 
	if(data == null) // 检查数据是否已经缓存
		// 如果数据没有缓存,那就访问网站抓取数据
		data = getData(url); 
		// 紧接着对Map中的数据进行缓存,以备将来使用之需
		cache.put(url, data); 
	 
	return data;

Java8:

public String getData(String url) 
	return cache.computeIfAbsent(url, this::getData); 

2. 集合

removeIf 方法移除集合中满足某个谓词的所有元素。(在原集合上做修改)

List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
numbers.removeIf(x -> x % 2 == 0);
System.out.println(numbers); // [1, 3, 5]

3. 列表

replaceAll 对列表中的每一个元素执行特定的替换操作。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); 
numbers.replaceAll(x -> x * 2); 
System.out.println(numbers); // [2, 4, 6, 8, 10]

B.1.2 Collections 类

Collections类已经存在了很长的时间,它的主要功能是操作或者返回集合。Java 8中它又新增了一个方法,该方法可以返回不可修改的、同步的、受检查的或者是空的NavigableMapNavigableSet。除此之外,它还引入了checkedQueue方法,该方法返回一个队列视图,可以扩展进行动态类型检查。

B.1.3 Comparator

Comparator 接口现在同时包含了默认方法和静态方法。你可以使用第3章中介绍的静态方
Comparator.comparing 返回一个 Comparator 对象,该对象提供了一个函数可以提取排序
关键字。

新的实例方法包含了下面这些。

  1. reversed—— 对当前的 Comparator 对象进行逆序排序,并返回排序之后新的 Comparator 对象。
  2. thenComparing——(多级排序)当两个对象相同时,返回使用另一个 Comparator 进行比较的Comparator 对象。
  3. thenComparingIntthenComparingDoublethenComparingLong——这些方法的工作方式和 thenComparing 方法类似,不过它们的处理函数是特别针对某些基本数据类型(分别对应于ToIntFunctionToDoubleFunctionToLongFunction)的。

新的静态方法包括下面这些。
4. comparingIntcomparingDoublecomparingLong——它们的工作方式和 comparing 类似,但接受的函数特别针对某些基本数据类型(分别对应于ToIntFunctionToDoubleFunctionToLongFunction)。
5. naturalOrder——对 Comparable 对象进行自然排序,返回一个 Comparator 对象。
6. nullsFirstnullsLast——对空对象和非空对象进行比较,你可以指定空对象(null)比非空对象(non-null)小或者比非空对象大,返回值是一个 Comparator 对象。
7. reverseOrder——和naturalOrder().reversed()方法类似。

B.2 并发

B.2.1 原子操作

java.util.concurrent.atomic 包的主要功能是处理原子变量(atomic variable)
比如个对数字类型进行操作的类 AtomicIntegerAtomicLong 它们支持对单一变量的原子操作。

  1. getAndUpdate——以原子方式用给定的方法更新当前值,并返回变更之前的值。
  2. updateAndGet——以原子方式用给定的方法更新当前值,并返回变更之后的值。
  3. getAndAccumulate——以原子方式用给定的方法对当前及给定的值进行更新,并返回变更之前的值。
  4. accumulateAndGet——以原子方式用给定的方法对当前及给定的值进行更新,并返回变更之后的值。

下面的例子向我们展示了如何以原子方式比较一个现存的原子整型值和一个给定的观测值(比如10),并将变量设定为二者中较小的一个。

int min = atomicInteger.accumulateAndGet(10, Integer::min);

Adder和Accumulator
多线程的环境中,如果多个线程需要频繁地进行更新操作,且很少有读取的动作(比如,在统计计算的上下文中),
Java API 文档中推荐大家使用新的类 LongAdderLongAccumulatorDoubleAdder 以及 DoubleAccumulator
尽量避免使用它们对应的原子类型。这些新的类在设计之初就考虑了动态增长的需求,可以有效地减少线程间的竞争。

LongAddrDoubleAdder 类都支持加法操作,而 LongAccumulatorDoubleAccumulator 可以使用给定的方法整合多个值。比如,可以像下面这样使用 LongAdder 计算多个值的总和。

代码清单B-1 使用 LongAdder 计算多个值之和

LongAdder adder = new LongAdder();  // 使用默认构造器,初始的sum值被置为0
adder.add(10); // 在多个不同的线程中进行加法运算
// … 
long sum = adder.sum(); // 到某个时刻得出sum的值

代码清单B-2 使用 LongAccumulator 实现同样的功能。

LongAccumulator acc = new LongAccumulator(Long::sum, 0); 
acc.accumulate(10);  // 在几个不同的线程中累计计算值
// … 
long result = acc.get(); // 在某个时刻得出结果

B.2.2 ConcurrentHashMap

ConcurrentHashMap 能很好的支持并发地新增和更新操作,因为它仅对内部数据结构的某些部分上锁。同步式的 Hashtable 读写性能更高

  1. 性能

为了改善性能,要对ConcurrentHashMap的内部数据结构进行调整。典型情况下,map的条目会被存储在中,依据生成哈希值进行访问。但是,如果大量键返回相同的哈希值,由于是由List实现的,它的查询复杂度为O(n),这种情况下性能会恶化。
Java 8 中,当桶过于臃肿时,它们会被动态地替换为排序树(sorted tree),新的数据结构具有更好的查询性能(排序树的查询复杂度为O(log(n)))。注意,这种优化只有当键是可以比较的(比如String或者Number类)时才可能发生。

  1. 类流操作

ConcurrentHashMap 支持三种新的操作,这些操作和你之前在流中所见的很像:
3. forEach——对每个键值对进行特定的操作
4. reduce——使用给定的精简函数(reduction function),将所有的键值对整合出一个结果
5. search——对每一个键值对执行一个函数,直到函数的返回值为一个非空值

以上每一种操作都支持四种形式,接受使用Map.Entry以及键值对的函数:

  1. 使用键和值的操作(forEach、reduce、search)
  2. 使用键的操作(forEachKey、reduceKeys、searchKeys)
  3. 使用值的操作 (forEachValue、reduceValues、searchValues)
  4. 使用Map.Entry对象的操作(forEachEntry、reduceEntries、searchEntries)

注意,这些操作不会ConcurrentHashMap 的状态 上锁。它们只会在运行过程中对元素进行操作。 应用到这些操作上的函数不应该对任何的顺序,或者其他对象,亦或是在计算过程发生变化的值,有依赖。

除此之外,你需要为这些操作指定一个并发阈值。如果经过预估当前map的大小小于设定的阈值,操作会顺序执行
使用值1开启基于通用线程池的最大并行
使用值Long.MAX_VALUE设定程序以单线程执行操作。
下面这个例子中,我们使用 reduceValues 试图找出map中的最大值:

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); 
Optional<Integer> maxValue = Optional.of(map.reduceValues(1, Integer::max));

注意,对 int、long 和 double,它们的 reduce 操作各有不同(比如reduceValuesToInt、reduceKeysToLong等)。

  1. 计数

ConcurrentHashMap 类提供了一个新的方法,名叫 mappingCount,它返回 long 类型的 映射的数目。尽量使用这个新方法,而不是返回的 int 类型的老方法 size 。映射的数量可能 int 放不下。

  1. 集合视图

ConcurrentHashMap 类还提供了一个名为 KeySet 的新方法,该方法以 Set 的形式返回 ConcurrentHashMap 的一个视图(对map的修改会反映在该Set中,反之亦然)。
你也可以使用新的静态方法 newKeySet,由 ConcurrentHashMap 创建一个 Set

B.3 Arrays

B.3.1 使用 parallelSort

parallelSort 方法会以并发的方式对指定的数组进行排序,你可以使用自然顺序,也可以
为数组对象定义特别的 Comparator

B.3.2 使用 setAll 和 parallelSetAll

setAllparallelSetAll 方法可以以顺序的方式也可以用并发的方式,使用提供的函数计算每一个元素的值,对指定数组中的所有元素进行设置。该函数接受元素的索引,返回该索引对应的值。由于 parallelSetAll 需要并发执行,所以提供的函数必须没有任何副作用,就如第7章和第13章中介绍的那样。
举例来说,你可以使用 setAll 方法生成一个值为 0, 2, 4, 6, … 的数组:

int[] evenNumbers = new int[10];
System.out.println(Arrays.toString(evenNumbers)); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Arrays.setAll(evenNumbers, i -> i * 2);
System.out.println(Arrays.toString(evenNumbers)); // [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

B.3.3 使用 parallelPrefix

parallelPrefix 方法以并发的方式,用用户提供的二元操作符对给定数组中的每个元素进行累积计算。通过下面这段代码,你会得到这样的一些值:1, 2, 3, 4, 5, 6, 7, …

代码清单B-3 使用 parallelPrefix(arr, (累计, 当前) -> 累计 + 当前)并发地累积数组中的元素

int[] ones = new int[10];
System.out.println(Arrays.toString(ones)); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Arrays.fill(ones, 1);
System.out.println(Arrays.toString(ones)); // [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
Arrays.parallelPrefix(ones, (a, b) -> a + b);
System.out.println(Arrays.toString(ones));  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

B.4 Number 和 Math

Java 8 APINumberMath 也做了改进,为它们增加了新的方法。

B.4.1 Number

Number 类中新增的方法如下。

  1. Short、Integer、Long、FloatDouble 类提供了静态方法 sum、minmax
  2. IntegerLong 类提供了 compareUnsigned、divideUnsigned、remainderUnsigned、toUnsignedLong 方法来处理无符号数。
  3. IntegerLong 类也分别提供了静态方法 parseUnsignedInt、parseUnsignedLong 将字符解析为无符号 int、long类型。
  4. ByteShort类提供了toUnsignedInt、toUnsignedLong方法通过无符号转换将参数转化为 int、 long 类型。类似地, Integer 类现在也提供了静态方法 toUnsignedLong
  5. DoubleFloat类提供了静态方法isFinite,可以检查参数是否为有限浮点数。
  6. Boolean类现在提供了静态方法logicalAnd、logicalOr、logicalXor,可以在两个boolean之间执行and、or、xor操作。
  7. BigInteger 类提供了 byteValueExact 、 shortValueExact 、 intValueExact 、longValueExact,可以将BigInteger类型的值转换为对应的基础类型。不过,如果在转换过程中有信息的丢失,方法会抛出算术异常。

B.4.2 Math

如果 Math 中的方法在操作中出现溢出,Math 类提供了新的方法可以抛出算术异常。支持这
一异常的方法包括使用 int、long 参数的 addExact、subtractExact、multipleExact、 incrementExact、decrementExact、negateExact。此外,Math 类还新增了一个静态方法
toIntExact,可以将 long 值转换为 int 值。其他的新增内容包括静态方法 floorModfloorDivnextDown

B.5 Files

Files 类最引人注目的改变是,你现在可以用文件直接产生流。第5章中提到过新的静态方法 Files.lines,通过该方法你可以以延迟方式读取文件的内容,并将其作为一个流。此外,还有一些非常有用的静态方法可以返回流。

  1. Files.list——生成由指定目录中所有条目构成的Stream<Path>。这个列表不是递归包含的。由于流是延迟消费的,处理包含内容非常庞大的目录时,这个方法非常有用。
  2. Files.walk——和 Files.list 有些类似,它也生成包含给定目录中所有条目的Stream<Path>。不过这个列表是递归的,你可设定递归深度。注意,该遍历是依照深度优先进行的。
  3. Files.find—— 通过递归地遍历一个目录找到符合条件的条目,并生成一个Stream<Path>对象。

B.6 Reflection

附录A 中已经讨论过Java 8中注解机制的几个变化。Reflection API 的变化就是为了支撑这些改变。
除此之外,Relection 接口的另一个变化是新增了可以查询方法参数信息的API,比如,你现在可以使用新增的 java.lang.reflect.Parameter 类查询方法参数的名称和修饰符,这个类被新的 java.lang.reflect.Executable 类所引用,而 java.lang.reflect.Executable 通用函数和构造函数共享的父类。

B.7 String

底层封装的是 StringJoiner

String authors = String.join(", ", "Raoul", "Mario", "Alan"); 
System.out.println(authors); // Raou

以上是关于《Java8实战》读书笔记15:附录中描述的其他新特性的主要内容,如果未能解决你的问题,请参考以下文章

《Java8实战》读书笔记13:Java8 与 Scala

《Java8实战》读书笔记13:Java8 与 Scala

《Java8实战》读书笔记11:Java8中新的日期时间API

《Java8实战》读书笔记11:Java8中新的日期时间API

《Java8实战》读书笔记08:接口的默认方法

《Java8实战》读书笔记12:函数式编程