《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
在两个方面对注解机制进行了改进:
- 你现在可以定义重复注解。(以前,是借助
注解容器
来实现) - 使用新版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 检查、泛型类型参数,以及 implements 和 throws 子句。
@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 集合类和接口中新增的方法
类/接口 | 新 方 法 |
---|---|
Map | getOrDefault:如果 key 找不到映射 value 。则返回给定的默认值 。forEach:两个入参的消费者,默认按 entrySet 顺序读一遍。compute:如果没有 value 就取null ,K, V 丢给 lambda 算出结果 put 并 返回 。computeIfAbsent:如果不存在,就算个值给它。 computeIfPresent:如果有值,就重新算个值给它。没有就不处理。 merge:如果为 空 就 put ,否则 lambda 的结果 为空则 删除 有值 put 。putIfAbsent:如果 key 映射的 value 为 null ,则 put(k, v) ,否则 get(k) remove(key,value): 键、值 都匹配则删除。replace:有值就执行替换。 replaceAll :调用给定函数,替换每个元素,直到完成所有或异常。 |
Iterable | forEach:对每个元素执行给定操作,直到处理完所有元素或引发异常。 spliterator :用当前 Iterable 生成一个Spliterator 。 |
Iterator | forEachRemaining :对剩下的每个元素执行指定操作。 |
Collection | removeIf:删除所有符合 lambda 判断结果的元素。stream:以此集合为源返回一个序列流。 parallelStream :返回一个并行流。 |
List | replaceAll:将该列表的每个元素替换为将运算符应用于该元素的结果。 sort :根据指定的 Comparator 引发的顺序对此列表进行排序。 |
BitSet | stream:返回此 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中它又新增了一个方法,该方法可以返回不可修改的、同步的、受检查的或者是空的NavigableMap
或NavigableSet
。除此之外,它还引入了checkedQueue
方法,该方法返回一个队列视图,可以扩展进行动态类型检查。
B.1.3 Comparator
Comparator 接口现在同时包含了默认方法和静态方法。你可以使用第3章中介绍的静态方
法 Comparator.comparing 返回一个 Comparator
对象,该对象提供了一个函数可以提取排序
关键字。
新的实例方法包含了下面这些。
- reversed—— 对当前的
Comparator
对象进行逆序排序,并返回排序之后新的Comparator
对象。 - thenComparing——(多级排序)当两个对象相同时,返回使用另一个
Comparator
进行比较的Comparator
对象。 - thenComparingInt、thenComparingDouble、thenComparingLong——这些方法的工作方式和
thenComparing
方法类似,不过它们的处理函数是特别针对某些基本数据类型(分别对应于ToIntFunction、ToDoubleFunction和ToLongFunction)的。
新的静态方法包括下面这些。
4. comparingInt、comparingDouble、comparingLong——它们的工作方式和 comparing 类似,但接受的函数特别针对某些基本数据类型(分别对应于ToIntFunction、ToDoubleFunction和ToLongFunction)。
5. naturalOrder——对 Comparable 对象进行自然排序,返回一个 Comparator
对象。
6. nullsFirst、nullsLast——对空对象和非空对象进行比较,你可以指定空对象(null)比非空对象(non-null)小或者比非空对象大,返回值是一个 Comparator
对象。
7. reverseOrder——和naturalOrder().
reversed()方法类似。
B.2 并发
B.2.1 原子操作
java.util.concurrent.atomic 包的主要功能是处理原子变量(atomic variable)
比如个对数字类型进行操作的类 AtomicInteger 和 AtomicLong 它们支持对单一变量的原子操作。
- getAndUpdate——以原子方式用给定的方法更新当前值,并返回变更之前的值。
- updateAndGet——以原子方式用给定的方法更新当前值,并返回变更之后的值。
- getAndAccumulate——以原子方式用给定的方法对当前及给定的值进行更新,并返回变更之前的值。
- accumulateAndGet——以原子方式用给定的方法对当前及给定的值进行更新,并返回变更之后的值。
下面的例子向我们展示了如何以原子方式比较一个现存的原子整型值和一个给定的观测值(比如10),并将变量设定为二者中较小的一个。
int min = atomicInteger.accumulateAndGet(10, Integer::min);
Adder和Accumulator
多线程的环境中,如果多个线程需要频繁地进行更新操作,且很少有读取的动作(比如,在统计计算的上下文中),
Java API
文档中推荐大家使用新的类 LongAdder、LongAccumulator、DoubleAdder 以及 DoubleAccumulator,
尽量避免使用它们对应的原子类型。这些新的类在设计之初就考虑了动态增长的需求,可以有效地减少线程间的竞争。
LongAddr
和 DoubleAdder
类都支持加法操作,而 LongAccumulator
和 DoubleAccumulator
可以使用给定的方法整合多个值。比如,可以像下面这样使用 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 读写性能更高
。
- 性能
为了改善性能,要对ConcurrentHashMap
的内部数据结构进行调整。典型情况下,map
的条目会被存储在桶
中,依据键
生成哈希值
进行访问。但是,如果大量键返回相同的哈希值
,由于桶
是由List
实现的,它的查询复杂度为O(n)
,这种情况下性能会恶化。
在 Java 8
中,当桶过于臃肿时,它们会被动态地替换为排序树(sorted tree)
,新的数据结构具有更好的查询性能(排序树的查询复杂度为O(log(n))
)。注意,这种优化只有当键是可以比较
的(比如String或者Number类)时才可能发生。
- 类流操作
ConcurrentHashMap 支持三种新的操作,这些操作和你之前在流中所见的很像:
3. forEach——对每个键值对进行特定的操作
4. reduce——使用给定的精简函数(reduction function),将所有的键值对整合出一个结果
5. search——对每一个键值对执行一个函数,直到函数的返回值为一个非空值
以上每一种操作都支持四种形式,接受使用键
、值
、Map.Entry
以及键值对
的函数:
- 使用键和值的操作(forEach、reduce、search)
- 使用键的操作(forEachKey、reduceKeys、searchKeys)
- 使用值的操作 (forEachValue、reduceValues、searchValues)
- 使用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
等)。
- 计数
ConcurrentHashMap
类提供了一个新的方法,名叫 mappingCount,它返回 long
类型的 映射的数目
。尽量使用这个新方法,而不是返回的 int
类型的老方法 size 。映射的数量可能 int
放不下。
- 集合视图
ConcurrentHashMap
类还提供了一个名为 KeySet 的新方法,该方法以 Set
的形式返回 ConcurrentHashMap
的一个视图(对map的修改会反映在该Set中,反之亦然)。
你也可以使用新的静态方法 newKeySet,由 ConcurrentHashMap
创建一个 Set
。
B.3 Arrays
B.3.1 使用 parallelSort
parallelSort 方法会以并发的方式对指定的数组进行排序,你可以使用自然顺序,也可以
为数组对象定义特别的 Comparator 。
B.3.2 使用 setAll 和 parallelSetAll
setAll 和 parallelSetAll 方法可以以顺序
的方式也可以用并发
的方式,使用提供的函数计算每一个元素的值
,对指定数组中的所有元素进行设置。该函数接受元素的索引
,返回该索引对应的值
。由于 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 API
对 Number
和 Math
也做了改进,为它们增加了新的方法。
B.4.1 Number
Number 类中新增的方法如下。
Short、Integer、Long、Float
和Double
类提供了静态方法sum、min
和max
。Integer
和Long
类提供了compareUnsigned、divideUnsigned、remainderUnsigned、toUnsignedLong
方法来处理无符号数。Integer
和Long
类也分别提供了静态方法parseUnsignedInt、parseUnsignedLong
将字符解析为无符号int、long
类型。Byte
和Short
类提供了toUnsignedInt、toUnsignedLong
方法通过无符号转换将参数转化为int、 long
类型。类似地,Integer
类现在也提供了静态方法toUnsignedLong
。Double
和Float
类提供了静态方法isFinite
,可以检查参数是否为有限浮点数。Boolean
类现在提供了静态方法logicalAnd、logicalOr、logicalXor
,可以在两个boolean
之间执行and、or、xor
操作。BigInteger
类提供了byteValueExact 、 shortValueExact 、 intValueExact 、longValueExact
,可以将BigInteger
类型的值转换为对应的基础类型。不过,如果在转换过程中有信息的丢失,方法会抛出算术异常。
B.4.2 Math
如果 Math 中的方法在操作中出现溢出,Math
类提供了新的方法可以抛出算术异常
。支持这
一异常的方法包括使用 int、long
参数的 addExact、subtractExact、multipleExact、 incrementExact、decrementExact、negateExact
。此外,Math
类还新增了一个静态方法
toIntExact,可以将 long
值转换为 int
值。其他的新增内容包括静态方法 floorMod、floorDiv、nextDown。
B.5 Files
Files 类最引人注目的改变是,你现在可以用文件直接产生流
。第5章中提到过新的静态方法 Files.lines,通过该方法你可以以延迟方式读取文件的内容,并将其作为一个流。此外,还有一些非常有用的静态方法可以返回流。
- Files.list——生成由指定目录中所有条目构成的
Stream<Path>
。这个列表不是递归包含的。由于流是延迟消费的,处理包含内容非常庞大的目录时,这个方法非常有用。 - Files.walk——和
Files.list
有些类似,它也生成包含给定目录中所有条目的Stream<Path>
。不过这个列表是递归的,你可设定递归深度
。注意,该遍历是依照深度优先进行的。 - 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实战》读书笔记11:Java8中新的日期时间API