Java 8 哪些新特性

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

2. Lambda表达式

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

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

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

3. 函数式接口

  • 定义函数式接口

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

@FunctionalInterface
public interface Compute<T, R> {
 public R getValue(T t1, T t2);
}
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);  
}

  • 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"));
	}

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");
  • 类::静态方法名称
    // 有参数,有返回值
		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"));
  • 类::方法名称 前提:必须是第一个参数是调用者,第二参数是被调用作为方法的参数
@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"));
	}
  • 类名::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);

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();

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

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的数据中的前三条数据,并跳过第一条数据。
	}

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();
	}

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);

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);

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]]
     * /
	}
方法 说明
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()));

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);
}

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

  • 数据大小: 数据足够大、 每个数据处理管道花费的时间足够多时, 并行化处理才有意义;
  • 数据结构: 在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("我是接口中的静态方法哟。。。");
	}
}

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 著 王群锋 译
posted @ 2025-09-14 00:07  uRick  阅读(47)  评论(0)    收藏  举报