uRick's PKM uRick's PKM
首页
导航
  • Java
  • 数据库
书单
  • 优质好文
  • 有趣的工具
  • 分类
  • 标签
  • 归档
关于
首页
导航
  • Java
  • 数据库
书单
  • 优质好文
  • 有趣的工具
  • 分类
  • 标签
  • 归档
关于
  • 一文搞懂JVM知识
  • 多线程基础
  • JUC☞Thread Pool
  • JUC☞Locker
  • JUC☞Queue
  • NIO浅谈
  • 有趣的二进制
  • 深入理解Lambda
  • Java8新特性
    • 1. 概述
    • 2. Lambda表达式
    • 3. 函数式接口
    • 4. 方法引用和构造器引用
    • 5. Stream API
      • 5.1. Stream 三大步骤
      • 5.2. Stream流的创建方式
      • 5.3. 筛选
      • 5.4. 映射
      • 5.5. 归约
      • 5.6. 排序
      • 5.7. Stream的终止操作方法有哪些
      • 5.8. 收集
      • 5.9. 并行流
    • 6. 接口中默认方法和静态方法
    • 7. 新的时间API
    • 8. Optional
    • 9. 参考
  • 单实例多种实现与解析
  • java
uRick
2020-07-06
目录

Java8新特性

# 1. 概述

Java8 相对于以前的版本作出了很大的改进。在运行效率上提高了许多,Java对底层进行了优化,程序执行速度更快;代码更加简洁,新增了许多特性。

  • lambda表达式
  • @FunctionalInterface 函数式接口
  • 接口默认方法
  • Stream API 函数式操作
  • Optional工具解决NPE问题
  • 新的日期时间API
  • 支持重复的注解 @Repeatable
  • JVM底层使用元空间(Metaspace)替换永久代(PermGen space)
  • 支持数组并行排序
  • 改进类型推断

更多特性:https://www.oracle.com/java/technologies/javase/8-whats-new.html (opens new window)

# 2. Lambda表达式

lambda表达式使用 -> 表示,可以理解为匿名函数,支持在方法中传递方法,如:

 Consumer<Integer> con = (x) -> System.out.println("参数值为:" + x);
 con.accept(90);
1
2

lambda 表达式 左:()参数列表,且不用指定参数类型,因为jvm会自动进行参数推断,右:具体执行的功能,-> 是箭头符号或叫lambda表达式。 lambda 表达式必须要函数式接口的支持,函数式接口:指接口中只有一个抽象方法的接口接 lambda表达式必须要函数式接口(只有一个抽象方法的接口)的支持口可以用 @FunctionalInterface 来检查。

# 3. 函数式接口

  • 定义函数式接口

本质上函数式接口也是一种接口,支持继承接口、定义默认方法、静态方法;继承接口除外,是只能有一个抽象的方法。

@FunctionalInterface
public interface Compute<T, R> {
 public R getValue(T t1, T t2);
}
1
2
3
4
public static void main(String[] args) {  
    Consumer<Integer> con = (x) -> System.out.println("参数值为:" + x);  
    con.accept(2);  
  
    // 指定传递的参数,函数中添加表达式既往成计算  
  Long opr = opr(1l, 2l, (x, y) -> (x + y));  
    System.out.println(opr);  
}  
  
private static Long opr(Long t1, Long t2, Compute<Long, Long> compute) {  
    return compute.getValue(t1, t2);  
}

1
2
3
4
5
6
7
8
9
10
11
12
13
  • Java中提供的核心函数式接口
接口 参数 返回类型 说明
Predicate<T> T boolean 断言
Consumer<T> T void 消费消息,无返回值
Function<T,R> T R 消费消息,经过加工处理后,得到一个新的值
Supplier<T> 无 T 获取数据,充当工厂方法
UnaryOperator<T> T T 继承Function<T,R>,对单条消息加工处理,产生相同类型的消息
BinaryOperator<T> (T, T) T 对2个消息加工操作,产生一个相同类型的消息

示例

    /**
	 * java提供了很多函数式接口,但是主要核心接口有四个分别为Consumer、Supplier、Predicate、Function
	 */
	@Test
	public void test8() {
		// 有参数,无返回值
		Consumer<String> consumer = (x) -> System.out.println("我消费了" + x);
		consumer.accept("500美元");
	}

	@Test
	public void test9() {
		// 有返回值,无参数。
		Supplier<String> s = () -> Integer.toBinaryString(126);
		String toBinaryString = s.get();
		System.out.println(toBinaryString);
	}

	@Test
	public void test10() {
		// 有参数,有返回值
		Function<String, Integer> fun = (x) -> Integer.valueOf(x);
		Integer apply = fun.apply("123");
		System.out.println(apply);

	}

	@Test
	public void test11() {
		// 断言接口
		Predicate<String> pre = (x) -> x.equals("Lee");
		System.out.println(pre.test("lee"));
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 4. 方法引用和构造器引用

  • 对象::方法名称 必须先创建对象,这里System.out创建了一个对象
    // 有参数,无返回值
		Consumer<String> consumer = (x) -> System.out.println("我消费了" + x);
		consumer.accept("500美元");
		PrintStream print= System.out;
		Consumer<String> consumer2 = print::println;
		consumer2.accept("ClassName::staticFuncationName");
1
2
3
4
5
6
  • 类::静态方法名称
    // 有参数,有返回值
		Function<String, Integer> fun = (x) -> Integer.valueOf(x);
		Integer apply = fun.apply("123");
		System.out.println(apply);
		Function<String, Integer> fun2 = Integer::valueOf;
		System.out.println(fun2.apply("200"));
1
2
3
4
5
6
  • 类::方法名称 前提:必须是第一个参数是调用者,第二参数是被调用作为方法的参数
@Test
	public void test11() {
		// 断言接口
		BiPredicate<String, String> pre = (x, y) -> x.equals(y);
		System.out.println("未简化的:"+pre.test("lee", "lee"));

		BiPredicate<String, String> pre2 = String::equals;
		System.out.println("简化:"+pre2.test("Lee", "Tom"));
	}
1
2
3
4
5
6
7
8
9
  • 类名::new 具体调用那个构造方法,根据函数式接口中的方法参数有关
  • 数组类型::new
     // 构造方法的引用ClassName::new
		Function<String, Employee> e = (str) -> new Employee(str);
		Employee employee = e.apply("李强");
		System.out.println(employee);

		Function<String, Employee> e2 = Employee::new;
		System.out.println(e2.apply("Wang xiao er"));

		// 数组的引用 Type[]::new
		Function<Integer, String[]> s = (n) -> new String[n];
		String[] apply = s.apply(22);
		Function<Integer, String[]> s2 = String[]::new;
		String[] apply2 = s2.apply(23);
1
2
3
4
5
6
7
8
9
10
11
12
13

# 5. Stream API

# 5.1. Stream 三大步骤

  1. 创建Stream

一个数据源(如:集合、数组),获取一个流

  1. 中间操作

一个中间操作,形成一条流的流水线

  1. 终止操作

一个中间操作,执行流水线,并能生成结果

流执行序列

流中间操作方法

流终端操作方法

# 5.2. Stream流的创建方式

//default Stream<E> stream() : 返回一个顺序流
//default Stream<E> parallelStream() : 返回一个并行流 

//由给定的值创建流
Stream<String> of = Stream.of("abcd")
//通过Stream.iterate和Stream.generate创建无限流 
 Stream<Integer> generate = Stream.generate(() -> 2);
 Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2)
 //通过Arrays创建流
 Arrays.Stream();
 //通过list集合创建流
 List<Stream> l = new ArrayList<>(); l.stream();
1
2
3
4
5
6
7
8
9
10
11
12

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”

# 5.3. 筛选

方法 说明
filter(Predicate p) 接收 Lambda ,从流中排除某些元素
distinct() 通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize) 获取指定 maxSize 个数元素
skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
public void test1() {
		List<Employee> emps = new ArrayList<Employee>();
		emps.add(new Employee("James", 32));
		emps.add(new Employee("Simith", 22));
		emps.add(new Employee("Curay", 20));
		emps.add(new Employee("TT", 23));
		emps.add(new Employee("Boor", 12));
		emps.add(new Employee("Boor", 12));
		emps.add(new Employee("Boor", 12));
		emps.add(new Employee("Boor", 12));
		// 获取Stream流,可以使用Arrays工具类获取Stream流,中间操作后不做任何处理,除非发起终止操作。
		Stream<Employee> stream = emps.stream();
		// filter进行过滤处理操作,forEache迭代打印输出值,limit是截断流,截取给定数量的数据。
		// skip顾名思义,跳过给定数量的数据。
		stream.filter((x) -> x.getAge() > 20).limit(3).skip(1).forEach(System.out::println);
		//打印输出年龄大于20的数据中的前三条数据,并跳过第一条数据。
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 5.4. 映射

方法 说明
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
flatMap (Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
mapTolnt(TolntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream
@Test
	public void test2() {
	    //aa是个集合List<String> aa = Arrays.asList("aaa", "bbb", "ccc");
		// map接受一个函数作为参数,并会应用到每一个集合中所有的元素,然后产生新的元素。
		aa.stream().map((x) -> x.toUpperCase()).forEach(System.out::print);
	}
	@Test
	public void test3() {
		// map接受一个函数作为参数,并会应用到每一个集合中所有的元素,然后产生新的元素。
		// aa.stream().map((x)->Java8Stream.toUpperCase(x)).forEach(System.out::print);
		aa.stream().map(Java8Stream::toUpperCase).forEach((x) -> {
			x.forEach(System.out::println);
		});
		// flatMap 同样接受一个函数作为参数,并将流中的一个值转换为一个新的流,然后合成一个流
		aa.stream().flatMap(Java8Stream::toUpperCase).forEach(System.out::print);
	}
	public static Stream<Character> toUpperCase(String str) {
		List<Character> c = new ArrayList<>();
		for (Character v : str.toCharArray()) {
			c.add(v.toUpperCase(v));
		}
		return c.stream();
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 5.5. 归约

方法 说明
reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
List<Integer> caseUser = new ArrayList<CaseUser>() {{
    add(new CaseUser("Tom", 22));
    add(new CaseUser("Jack", 30));
    add(new CaseUser("uRick", 26));
}}.stream()
        .map(CaseUser::getAge)
        .filter(a -> a >= 25)
        .collect(Collectors.toList());

System.err.println(caseUser);

//依赖初始值countAge1
int countAge1 = 0;
for (int i = 0; i < caseUser.size(); i++) {
    countAge1 += caseUser.get(i);
}

//通过reduce非常容易实现,依赖前一个数据值做归约计算
Integer countAge = caseUser.stream().reduce(Integer::sum).get();

System.err.println(countAge);
System.err.println(countAge1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 5.6. 排序

// 排序
System.out.println("=============Sorted==================");
Stream<Employee> stream2 = emps.stream();
//根据Comparator比较器来排序,默认不带比较器情况下,按照类实现的Comparable来排序,也就是自然排序。
stream2.sorted((x, y) -> Integer.compare(x.getAge(), y.getAge())).forEach(System.out::println);
1
2
3
4
5

# 5.7. Stream的终止操作方法有哪些

方法 说明
allMatch(Predicate p) 检查是否匹配所有元素
anyMatch(Predicate p) 检查是否至少匹配一个元素
noneMatch(Predicate p) 检查是否没有匹配所有元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
@Test
	public void test4() {
		List<Employee> emps = new ArrayList<Employee>();
		emps.add(new Employee("James", 32));
		emps.add(new Employee("Simith", 22));
		emps.add(new Employee("Curay", 20));
		emps.add(new Employee("TT", 23));
		emps.add(new Employee("Boor", 12));
		emps.add(new Employee("Boor", 12));
		emps.add(new Employee("Boor", 12));
		emps.add(new Employee("Boor", 12));
		boolean allMatch = emps.stream().allMatch((x) -> x.getAge() > 20);
		System.out.println(allMatch);
		boolean anyMatch = emps.stream().anyMatch((x) -> x.getAge() > 20);
		System.out.println(anyMatch);
		boolean noneMatch = emps.stream().noneMatch((x) -> x.getAge() < 0);
		System.out.println(noneMatch);
		Optional<Employee> findFirst = emps.stream().findFirst();
		System.out.println(findFirst);
		System.out.println("=============并行流,findAny流中取出任意一个元素。当前流=========");
		Optional<Employee> findAny = emps.parallelStream().findAny();
		System.out.println(findAny);
    /**
     *  false
        true
        true
        Optional[Employee [name=James, age=32]]
        =============并行流,findAny流中取出任意一个元素。当前流======
        Optional[Employee [name=Boor, age=12]]
     * /
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
方法 说明
count() 返回流中元素总数
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 遍历集合,集合框架内部已经实现迭代方法

# 5.8. 收集

Collect(Collector c) 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法,Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、 Set、 Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,常见方法如下:

List<CaseUser> caseUserList = new ArrayList<CaseUser>() {{
    add(new CaseUser("Tom", 22));
    add(new CaseUser("Jack", 30));
    add(new CaseUser("uRick", 26));
    add(new CaseUser("Zhuzu", 26));
}};

//获取年龄List集合
List<Integer> collect = caseUserList.stream().map(CaseUser::getAge).collect(toList());

//通过","链接用户名
String joinUserName = caseUserList.stream().map(CaseUser::getName).collect(joining(","));

//根据年龄分组,然后将分组得到User,取出用户名
Map<Integer, Set<String>> ageGroupByName = caseUserList.stream().collect(groupingBy(CaseUser::getAge, mapping(CaseUser::getName, toSet())));

//按年龄分组,并将User分组转换为只读List
Map<String, List<CaseUser>> listMap = caseUserList.stream().collect(groupingBy(CaseUser::getName, collectingAndThen(toList(), Collections::unmodifiableList)));

//获得年龄最小的User
Optional<CaseUser> min = caseUserList.stream().min(Comparator.comparing(CaseUser::getAge));

//Integer collect1 = caseUserList.stream().collect(summingInt(CaseUser::getAge));
//统计count,sum,min,max,avg
IntSummaryStatistics collect1 = caseUserList.stream().collect(summarizingInt(CaseUser::getAge));

//以年龄是否大于22作为分区,获取分区数据
Map<Boolean, List<CaseUser>> partitionByage = caseUserList.stream().collect(partitioningBy(u -> u.getAge() > 22, toList()));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 5.9. 并行流

在这个数据爆发式的年代,多核处理器已随处可见,压榨多核性能,在Java8中Stream API支持并行流处理任务,他底层依赖Fork/Join框架来处理;在StreamSupport工具类提供创建并行流的方式

public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
    Objects.requireNonNull(spliterator);
    return new ReferencePipeline.Head<>(spliterator,
                                        StreamOpFlag.fromCharacteristics(spliterator),
                                        parallel);
}
1
2
3
4
5
6

在使用并行流时要注意,使用并行处理不一定比串行性能会更好。它受处理器、数据体量、数据结构影响较大。

  • 数据大小: 数据足够大、 每个数据处理管道花费的时间足够多时, 并行化处理才有意义;
  • 数据结构: 在Java8中并行处理数据是基于Fork/Join框架实现的,在集合数据结构中采用 Spliterator 来拆分数据的,那么具备良好的拆分性能的集合,使用并行处理,性能会更好。如ArrayList、数组容易拆解,而HashSet、 TreeSet、LinkedList受限于结构,拆解性能较差;同时在处理数据时尽量使用 无状态流,避免使用 有状态流,像 sorted、 distinct 和 limit 等方法就是有状态的, map、filter 和 flatMap 等是无状态的。
  • 处理器核数: 并行化本质就是利用处理器的多个核,单核也就没必要并行,并行化处理时,还要考虑能够使用几个核来提升性能;
  • 单元处理开销: 只有在拆解的流中,处理单元任务耗时较长,采用并行处理效率才能更好;
  • 拆箱装箱: 对于处理基本类型的流运算,尽量使用基本类型的流API,因为涉及拆箱装箱转换性能较差。

# 6. 接口中默认方法和静态方法

默认方法能够很好解决接口拓展问题,当接口需要拓展时不需要具体的实现类实现拓展,起着一个适配的功能。 默认方法需要default修饰,并且只有实现接口的类才能使用。 静态方法是可以被其他类共享

  • 冲突解决原则
  1. 接口默认方法的“类优先” 原则若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时,选择父类中的方法。
  2. 如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
  3. 接口冲突时,如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法), 那么必须覆盖该方法来解决冲突
  • 接口中方法定义
public interface MyInterface {
	int test();
	default void demo(){
		System.out.println("呵呵,我是Java8中默认的方法");
	};
	public static void wowo(){
	    System.out.println("我是接口中的静态方法哟。。。");
	}
}
1
2
3
4
5
6
7
8
9

# 7. 新的时间API

LocalDate、 LocalTime、 LocalDateTime 类 的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。

注: ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法

  1. Instant: 用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算
  2. Duration: 用于计算两个“时间”间隔
  3. Period: 用于计算两个“日期”间隔
  4. 时区处理
  5. Java8 中加入了对时区的支持,带时区的时间为分别为:
  • ZonedDate、 ZonedTime、 ZonedDateTime 其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式例如 : Asia/Shanghai 等
  • ZoneId: 该类中包含了所有的时区信息
  • getAvailableZoneIds() : 可以获取所有时区时区信息
  • of(id): 用指定的时区信息获取 ZoneId 对象
  1. 时间格式化 java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:预定义的标准格式、语言环境相关的格式、自定义的格式
  2. 时间矫正器
  • TemporalAdjuster: 时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。
  • TemporalAdjusters : 该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现。

# 8. Optional

Optional<T> 类(java.util.Optional)是一个容器类,代表一个值存在或不存在,原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。

方法 作用
Optional(T value) 创建Optional,value不可为空
empty() 创建一个空的Optional实例
of(T value) 创建一个Optional实例
ofNullable(T value) 若value不为null,创建Optional实例,否则创建空实例
get() 获取Optional中包装的数据,若为null,抛出异常NoSuchElementException
isPresent() 判断是否包含值
orElse(T other) 如果调用对象包含值,返回该值,否则返回other
orElseGet(Supplie supplier) 如果调用对象包含值,返回该值,否则返回supplier获取的值
map(Function mapper) 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
flatMap(Function mapper) 与map类似,要求返回值必须是0ptional
filter(Predicate predicate) 过滤数据,若为空则返回当前对账,否则返回满足条件的

# 9. 参考

  1. 《Java 8函数式编程》 [英] Richard Warburton 著 王群锋 译
#JDK#Lambda
上次更新: 2024/03/02, 14:21:03
深入理解Lambda
单实例多种实现与解析

← 深入理解Lambda 单实例多种实现与解析→

最近更新
01
从0到1:开启商业与未来的秘密
11-26
02
如何阅读一本书: 读懂一本书,精于一件事
10-25
03
深入理解Lambda
06-27
更多文章>
Theme by Vdoing | Copyright © 2019-2024 uRick | CC BY 4.0
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式