JDK8-17新特性

JDK8-17新特性

前言

idea的快捷键,查看当前类/接口的关系图
新特性学习简介

一、java版本迭代概述

1.1 版本及新特性介绍

1、Java版本发布特点:
JEP(JDK Enhancement Proposals):jdk 改进提案。
LTS(Long-term Support)即长期支持。即出了bug,会被修复。目前可选LTS版本8、11、17

2、JDK各个版本下载链接:https://www.oracle.com/java/technologies/downloads/archive/

3、JDK比较重要的时间节点和版本说明

1996  JDK1.0
2004  JDK5.0 最重要的一个里程碑式的版本
2014  JDK8.0 排第二的里程碑式的版本  ----> LTS
2017.9 JDK9.0  从此版本开始,每半年发布一个新的版本
2018.9 JDK11  ----> LTS
2021.9 JDK17 ----> LTS

3、如何学习新特性?

  • 角度1:新的语法规则(多关注):
    • JDK5:自动装箱、自动拆箱、enum、泛型
    • JDK7:try-catch变化、
    • JDK8:Lambda表达式、函数式接口、方法引用/构造器引用/数组引用、接口的增强–接口中的默认方法/静态方法、注解(重复、类型)、通用目标类型推断(Lambda表达式中的类型依赖于上下文环境,是由编译器推断出来的)
    • JDK9:java的REPL工具(交互式编程环境),以交互式的方式对语句和表达式进行求值。即写即得、快速运行。使用 jShell命令。
    • JDK10:局部变量的类型推断
    • JDK12:switch表达式
    • JDK13:文本块
    • JDK14:record
    • JDK15:密封类
  • 角度2:增加、过时、删除API
    • JDK8:Stream API、Optional、新的日期时间API、HashMap的底层结构
    • JDK9:String的底层结构
      StringBuilder、ArrayList、新的日期时间的API、0ptional等
  • 角度3:底层的优化:比如JVM的优化、GC参数、新的垃圾回收器
    • JDK8:永久代被元空间替代、Nashorn引擎替代Rhino引擎
    • JDK11:GraaIVM代替Nashorn引擎,它是一个运行时平台,支持java和其他基于java字节码的语言、其他语言:js、Ruby、Python、LLVM,性能式Nashorn的2倍以上。

Java8新特性关于JDK的更新:集合的流式操作、并发、Arrays、Number和Math、IO/NIO的改进、Reflection获取形参名、String的join()方法、Files

1.2 java8新特性简介

1、java8的新特性:Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。

2、java8新特性优点
Lambda表达式:减少代码量
Stream API:并行流、串行流——作用:便于对数据进行并行操作,Stream API可以声明性地通过parallel()与sequential()在并行流和顺序流之间进行切换。
Optional类:可以减少空指针异常
Nashorn引擎允许JVM上运行JS应用

二、java8新特性

2.1 Lambda表达式

调用方法,该方法是函数式接口,满足一定条件下就可以使用lambda表达式,满足一定条件下就可以使用方法引用。

1、Lambda表达式的定义&解决的问题?
Lambda定义:它是一个匿名函数,理解为一段可以传递的代码(将代码像数据一样进行传递),优点:紧凑、提升java语言的表达能力。
解决匿名内部类的冗余:以Runnable的匿名内部类为例:new Thread(new Runnable(){}).start();还有覆盖重写run方法(包括方法名、方法参数、方法返回值),只有方法体才是关键所在。

2、Lambda的语法格式:Lambda形参列表 -> Lambda体
->:Lambda操作符或箭头操作符
左侧:Lambda形参列表,对应着要重写的接口中的抽象方法的形参列表
右侧:Lambda体,对应着接口的实现类要重写的方法的方法体

3、Lambda表达式的本质:
一方面,Lambda表达式作为接口的实现类的对象 ——>”万事万物皆对象“
另一方面,Lambda表达式是一个匿名函数

4、Lambda具体要怎么用?
省略匿名内部类,简化重写的方法,具体Lambda表达式形式:重写的方法的形参列表 -> 重写的方法的方法体

5、lambda表达式的语法规则总结

  • -> 的左边:lambda形参列表,参数的类型可以省略(类型推断)。如果形参只有一个,则()也可以省略。如果是无参的那么只写()。
  • -> 的右边:lambda体,对应着重写的方法的方法体。如果方法体中只有一行执行语句,则{}可以省略;如果有return关键字,则必须一并省略

几种类型:无参无返回值、有参无返回值、有参有返回值,且多条执行语、有参且一条执行语句
如果只有一个执行语句,那么可以省略{}

@Test
public void test1(){
    //改写前: 未使用Lambda表达式
    Runnable r1 = new Runnable() {
        @Override
        public void run() {
            System.out.println("我爱北京天安门");
        }
    };
    r1.run();
    System.out.println("***********************");
    
    //Lambda表达式改写后: 
    Runnable r2 = () -> {
    	System.out.println("我爱故宫");
    };
    r2.run();
    
    //Lambda表达式改写后: 只有一条执行语句,可以省略{}
    Runnable r3 = () -> System.out.println("我爱故宫");
    r3.run();
}

@Test
public void test2(){
    //改写前: 未使用Lambda表达式
    Comparator<Integer> com1 = new Comparator<Integer>() {
        @Override
        public void compare(Integer o1, Integer o2) {
            return o1.compareTo(o2);
        }
    };
    System.out.println(com1.compare(12,21));
    System.out.println("***********************");
    
    //改法:Lambda表达式+类型推断+一个形参省略()+一条执行语句省略{}
    Comparator<Integer> com2 = (o1, o2) -> o1.compareTo(o2);
    System.out.println(com2.compare(12,6));
}

2.2 类型推断

1、"类型推断"定义: javac 根据程序的上下文,在后台推断出了参数的类型。
举例1:Lambda 表达式的类型依赖于上下文环境,Lambda 表达式中无需指定类型,程序依然可以编译,其参数类型都是由编译器推断得出的。
举例2:创建集合对象时

创建集合对象时,使用的类型推断

@Test
public void test() {
    //类型推断1
    ArrayList<String> list = new ArrayList<>();
    //类型推断2
    int[] arr = {1, 2, 3};
}

2.3 函数式接口

回顾Lambda表达式的本质:
一方面,Lambda表达式作为接口的实现类的对象 ——>”万事万物皆对象“
另一方面,Lambda表达式是一个匿名函数

1、什么是函数式接口?
如果接口中只声明有一个抽象方法,则此接口是函数式接口。
在接口上使用注解@FunctionalInterface检查它是否是一个函数式接口。javadoc中包含声明说明他是否是一个函数式接口。

为什么需要函数式接口?
因为只有给函数式接口提供实现类的对象时,我们才可以使用lambda表达式。

2、api中函数式接口所在的包
在java.util.function包下,定义了Java 8 的丰富的函数式接口。

3、Java内置函数式接口
(1)之前的函数式接口

  • java.lang.Runnable:void run()
  • java.lang.Iterable< T>:Iterator< T> iterate()
  • java.lang.Comparable< T>:int compareTo(T t)
  • java.util.Comparator< T>:int compare(T t1, T t2)

(2)四大核心函数式接口

称谓函数式接口用对应的抽象方法特点
消费型接口Consumer< T>void accept(T t)有形参,返回值void
供给型接口Supplier< T>T get()无参,有返回值
函数型接口Function< T, R>R apply(T t)既有参数又有返回值
判断型接口Predicate< T>boolean test(T t)有参,返回值boolean

内置接口代码示例:①Supplier接口;②Consumer接口,其中lambda表达式作为参数传递,要求接收lambda表达式的参数类型必须是与该lambda表达是兼容的函数式接口的类型;③Function函数式接口:将一个字符串首字母转为大写
回顾知识点一:ArrayList的foreach方法: public void forEach(Consumer<? super E> action)
知识点二:ArrayList的removeIf方法:public boolean removeIf(Predicate<? super E> filter)

/**
* 测试Supplier函数式接口使用示例
* 
*/
import java.util.function.Supplier;
@Test
public void test() {
	/** 理解为函数式接口Supplier中的抽象方法get(), 返回一个实现接口的对象, 通过该对象调用重写的方法get()
	String get(){
		return "尚硅谷";
	}*/
	//lambda表达式写法
	Supplier<String> supplier = () -> "尚硅谷";
	System.out.println(supplier.get());
}

/**
* 测试Consumer函数式接口使用示例, 作为ArrayList的foreach方法的参数传递
*/
public class TestPredicate {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("atguigu");

        System.out.println("删除之前:");
        /**重写Consumer接口的抽象方法void accept(T t);
        void accept(String t){
        	System.out.println(t);
        }*/
        //labda表达式写法
        list.forEach(t-> System.out.println(t));
        //方法引用写法
        list.forEach(System.out :: println);

        /**重写Predicate接口的抽象方法boolean test(T t);
        boolean test(String s){
        	return s.contains("o"));
        }*/
        //用于删除集合中满足filter指定的条件判断的。
        //删除包含o字母的元素
        list.removeIf(s -> s.contains("o"));

        System.out.println("删除包含o字母的元素之后:");
        list.forEach(t-> System.out.println(t));
    }
}

public class TestFunction {
    public static void main(String[] args) {
        //使用Lambda表达式实现Function<T,R>接口,可以实现将一个字符串首字母转为大写的功能。
        Function<String,String> fun = s -> s.substring(0,1).toUpperCase() + s.substring(1);
        System.out.println(fun.apply("hello"));
    }
}

2.4 方法引用/构造器引用/数组引用

理解:lambda表达式简化函数式接口的变量或形参赋值的语法。方法引用和构造器引用进一步简化lambda表达式。
1、方法引用的理解:可以看作是基于lambda表达式的进一步刻画
当需要提供一个函数式接口的实例时,可以使用lambda表达式提供此实例。
当满足一定的条件的情况下,还可以使用方法引用或者构造器引用替换lambda表达式。

2、方法引用的本质:
方法引用作为了函数式接口的实例。 ———》“万事万物皆对象”
方法引用就是lambda表达式,即函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是lambda表达式的一个语法糖。

3、方法引用格式:
类(或对象) :: 方法名
:: :引用操作符,在英文半角状态下输入,且中间不能有空格。
左边:类/对象
右边:方法名

4、什么时候使用方法引用?具体使用情况说明?
要求1:lambda体只有一条语句,并且都是通过调用一个对象/类现有的方法来完成的。
要求2:

  • 情况1:对象 :: 实例方法
    要求:函数式接口中的抽象方法a与其内部实现时调用的对象的某个方法b的形参列表和返回值类型都相同(或一致)。此时可以考虑使用方法b实现对方法a的替换、覆盖。此替换、覆盖即为方法引用。

  • 情况2:类 :: 静态方法
    要求:函数式接口中的抽象方法a与其内部实现时调用的类的某个静态方法b的形参列表和返回值类型都相同(或一致)。此时可以考虑使用方法b实现对方法a的替换、覆盖。此替换、覆盖即为方法引用。

  • 情况3:类 :: 实例方法
    函数式接口中的抽象方法a与其内部实现时调用的对象的某个方法b的返回值类型相同。同时,抽象方法a中有n个参数,方法b中有n-1个参数,且抽象方法a的第1个参数作为方法b的调用者,且抽象方法a的后n-1个参数与方法b的n-1个参数的类型相同(或一致)。则可以考虑使用方法b实现对抽象方法a的替换、覆盖。此替换、覆盖即为方法引用。
    注意点:方法b是非静态的方法,需要对象调用。但是形式上,写出对象a所属的类。

方法引用举例

public class MethodRefTest {

	// 情况一:对象 :: 实例方法
	//Consumer中的void accept(T t)
	//PrintStream中的void println(T t)
	@Test
	public void test1() {
		Consumer<String> con1 = str -> System.out.println(str);
		con1.accept("北京");

		System.out.println("*******************");
		PrintStream ps = System.out;
		Consumer<String> con2 = ps::println;
		con2.accept("beijing");
	}
	
	//Supplier中的T get()
	//Employee中的String getName()
	@Test
	public void test2() {
		Employee emp = new Employee(1001,"Tom",23,5600);

		Supplier<String> sup1 = () -> emp.getName();
		System.out.println(sup1.get());

		System.out.println("*******************");
		Supplier<String> sup2 = emp::getName;
		System.out.println(sup2.get());

	}

	// 情况二:类 :: 静态方法
	//Comparator中的int compare(T t1,T t2)
	//Integer中的int compare(T t1,T t2)
	@Test
	public void test3() {
		Comparator<Integer> com1 = (t1,t2) -> Integer.compare(t1,t2);
		System.out.println(com1.compare(12,21));

		System.out.println("*******************");

		Comparator<Integer> com2 = Integer::compare;
		System.out.println(com2.compare(12,3));

	}
	
	//Function中的R apply(T t)
	//Math中的Long round(Double d)
	@Test
	public void test4() {
		Function<Double,Long> func = new Function<Double, Long>() {
			@Override
			public Long apply(Double d) {
				return Math.round(d);
			}
		};

		System.out.println("*******************");

		Function<Double,Long> func1 = d -> Math.round(d);
		System.out.println(func1.apply(12.3));

		System.out.println("*******************");

		Function<Double,Long> func2 = Math::round;
		System.out.println(func2.apply(12.6));
	}

	// 情况三:类 :: 实例方法  (有难度)
	// Comparator中的int comapre(T t1,T t2)
	// String中的int t1.compareTo(t2)
	@Test
	public void test5() {
		Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
		System.out.println(com1.compare("abc","abd"));

		System.out.println("*******************");

		Comparator<String> com2 = String :: compareTo;
		System.out.println(com2.compare("abd","abm"));
	}

	//BiPredicate中的boolean test(T t1, T t2);
	//String中的boolean t1.equals(t2)
	@Test
	public void test6() {
		BiPredicate<String,String> pre1 = (s1,s2) -> s1.equals(s2);
		System.out.println(pre1.test("abc","abc"));

		System.out.println("*******************");
		BiPredicate<String,String> pre2 = String :: equals;
		System.out.println(pre2.test("abc","abd"));
	}
	
	// Function中的R apply(T t)
	// Employee中的String getName();
	@Test
	public void test7() {
		Employee employee = new Employee(1001, "Jerry", 23, 6000);


		Function<Employee,String> func1 = e -> e.getName();
		System.out.println(func1.apply(employee));

		System.out.println("*******************");
		Function<Employee,String> func2 = Employee::getName;
		System.out.println(func2.apply(employee));
	}

}

5、构造器引用
(1)构造器引用定义:当lambda表达式是创建一个对象,且满足lambda表达式形参,正好是给创建这个对象的构造器的实参列表,

(2)构造器引用的格式:
类名 :: new

(3)说明:
调用了类名对应的类中的某一个确定的构造器
具体调用的是类中的哪一个构造器?取决于函数式接口的抽象方法的形参列表

6、数组引用
(1)当Lambda定义:表达式是创建一个数组对象,并且满足Lambda表达式形参,正好是给创建这个数组对象的长度,就可以数组构造引用。
(2)格式:数组类型名 :: new

构造器引用、数组引用的示例
Employee类定义了属性:id、name、age、salary

/**
* 构造器引用
*/
public class ConstructorRefTest {
	//构造器引用
    //Supplier中的T get()
    //Employee的空参构造器:Employee()
    @Test
    public void test1(){
    	//匿名内部类重写仅有的一个抽象方法
        Supplier<Employee> sup = new Supplier<Employee>() {                                  
            @Override
            public Employee get() {
                return new Employee();
            }
        };
        
        //lambda表达式
        Supplier<Employee>  sup1 = () -> new Employee();
        System.out.println(sup1.get());

		//构造器引用
        Supplier<Employee>  sup2 = Employee :: new;
        System.out.println(sup2.get());
    }

	//Function中的R apply(T t)
    @Test
    public void test2(){
    	//lambda表达式
        Function<Integer,Employee> func1 = id -> new Employee(id);
        Employee employee = func1.apply(1001);
        System.out.println(employee);
		
		//构造器引用
        Function<Integer,Employee> func2 = Employee :: new;
        Employee employee1 = func2.apply(1002);
        System.out.println(employee1);
    }

	//BiFunction中的R apply(T t,U u)
    @Test
    public void test3(){
    	//lambda表达式
        BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
        System.out.println(func1.apply(1001,"Tom"));

		//构造器引用
        BiFunction<Integer,String,Employee> func2 = Employee :: new;
        System.out.println(func2.apply(1002,"Tom"));

    }

}

/**
* 数组引用
*/
//Function中的R apply(T t)
@Test
public void test4(){
    //lambda表达式
    Function<Integer,String[]> func1 = length -> new String[length];
    String[] arr1 = func1.apply(5);
    System.out.println(Arrays.toString(arr1));
	
	//数组引用
    Function<Integer,String[]> func2 = String[] :: new;
    String[] arr2 = func2.apply(10);
    System.out.println(Arrays.toString(arr2));
}

2.5 Stream API

地位:Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中,这是目前为止对Java类库最好的补充。
为什么需要stream api?不同的数据源关系型(MySQL、Oracle等)、非关系型(MongDB、Redis等),非关系型这些需要Java层面去处理。

1、Stream api VS 集合框架Collection
stream api关注的是多个数据的计算(排序、查找、过来、映射、遍历等),面向cpu,通过cpu实现计算。
集合是一种静态的内存数据结构,关注的是数据的存储,面向内存,存储在内存中。
stream api之于集合,类似于SQL之于数据表的查询。

Stream定义:Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

2、使用说明:

  • Stream 自己不会存储元素。
  • Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。即一旦执行终止操作,就执行中间操作链,并产生结果。
  • Stream一旦执行了终止操作,就不能再调用其它中间操作或终止操作了。

3、stream 执行流程
Stream操作的三个步骤

  • 步骤1:stream的实例化:通过一个数据源(比如集合、数组)获取一个流
    • 具体做法:Collection集合获取顺序流、并行流;数组工具类Arrays获取一个流;通过Stream类的静态方法of()获取流;通过Stream类的静态方法iterator()和generate()获取无限流。
  • 步骤2:一系列的中间操作:每次处理都会返回一个持有结果的新Stream类型的对象,可对数据源的数据进行n次处理。
    • 筛选与切片:排除、去重、截断、跳过元素
    • 映射:接收一个函数作为参数,该函数会被应用到每个元素上。
    • 排序:产生一个新流,自然排序、定制排序
  • 步骤3:执行终止操作:执行中间操作链,最终产生结果并结束Stream。终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
    • 匹配与查找:匹配所有/至少一个/没有匹配所有元素、返回第一个/任意元素、返回最大值/最小值/元素总数、内部迭代forEach
    • 归约:将六种元素反复结合起来得到一个值。
    • 收集:将流转换为其他形式,用于给Stream中元素做汇总的方法。

步骤1:创建Stream实例:集合、数组、显示创建流、创建无限流

  • 方式一通过集合:获取顺序流、并行流,java8中collection接口被扩展,提供了获取流的方法。
    • default Stream< E> stream() : 返回一个顺序流
    • default Stream< E> parallelStream() : 返回一个并行流
  • 方式二通过数组:获取数组流。Java8 中的 Arrays 的静态方法 stream() 可以获取数组流。
    • static < T> Stream< T> stream(T[] array): 返回一个流
    • public static IntStream stream(int[] array):返回一个int类型的流
    • public static LongStream stream(long[] array):返回一个long类型的流
    • public static DoubleStream stream(double[] array):返回一个double类型的流
  • 方式三通过Stream类的静态方of():可以接受任意数量的参数
    • public static< T> Stream< T> of(T… values) : 返回一个流
  • 方式四创建无限流:静态方法 Stream.iterate() 和 Stream.generate()
    • 迭代:public static< T> Stream< T> iterate(final T seed, final UnaryOperator< T> f)
    • 生成:public static< T> Stream< T> generate(Supplier< T> s)
/**
* 法1: 通过集合创建stream实例
*/
@Test
public void test01(){
    List<Integer> list = Arrays.asList(1,2,3,4,5);
    Stream<Integer> stream1 = list.stream();//获取顺序流
    Stream<Integer> stream2 = list.parallelStream();//获取并行流
}
/**
* 法2: 通过集合创建stream实例
*/
@Test
public void test02(){
    String[] arr1 = {"hello","world"};
    Stream<String> stream = Arrays.stream(arr1); //获取一个数组流

	int[] arr2 = {1,2,3,4,5};
    IntStream stream = Arrays.stream(arr2);//获取int类型的数组流
}
/**
* 法3: 通过Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。
*/
@Test
public void test03(){
    Stream<Integer> stream = Stream.of(1,2,3,4,5);
    stream.forEach(System.out::println);
}
/**
* 法4: 创建无限流
*/
@Test
public void test04() {
	// 迭代
	// public static<T> Stream<T> iterate(final T seed, final
	// UnaryOperator<T> f)
	Stream<Integer> stream = Stream.iterate(0, x -> x + 2);
	stream.limit(10).forEach(System.out::println);

	// 生成
	// public static<T> Stream<T> generate(Supplier<T> s)
	Stream<Double> stream1 = Stream.generate(Math::random);
	stream1.limit(10).forEach(System.out::println);
}

步骤二:一系列的中间操作

  • 筛选与切片:
    • filter(Predicatep):接收 Lambda,从流中排除某些元素
    • distinct():筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
    • limit(long maxSize):截断流,使其元素不超过给定数量
    • skip(long n):跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补。
  • 映射:
    • map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
    • mapToDouble(ToDoubleFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的
    • DoubleStream。mapToInt(ToIntFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
    • mapToLong(ToLongFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
    • flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
  • 排序:
    • sorted():产生一个新流,其中按自然顺序排序
    • sorted(Comparator com):产生一个新流,其中按比较器顺序排序
import java.util.stream.Stream;
public class StreamAPITest1 {
    //1-筛选与切片
    @Test
    public void test1() {
//        filter(Predicate p)——接收 Lambda,从流中排除某些元素。
        //练习:查询员工表中薪资大于7000的员工信息
        List<Employee> list = EmployeeData.getEmployees();
        Stream<Employee> stream = list.stream();
        stream.filter(emp -> emp.getSalary() > 7000).forEach(System.out::println);

        System.out.println();
//        limit(n)——截断流,使其元素不超过给定数量。
        //错误的。因为stream已经执行了终止操作,就不可以再调用其它的中间操作或终止操作了。
//        stream.limit(2).forEach(System.out::println);
        list.stream().filter(emp -> emp.getSalary() > 7000).limit(2).forEach(System.out::println);

        System.out.println();
//        skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
        list.stream().skip(5).forEach(System.out::println);

        System.out.println();
//        distinct()——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
        list.add(new Employee(1009, "马斯克", 40, 12500.32));
        list.add(new Employee(1009, "马斯克", 40, 12500.32));
        list.add(new Employee(1009, "马斯克", 40, 12500.32));
        list.add(new Employee(1009, "马斯克", 40, 12500.32));

        list.stream().distinct().forEach(System.out::println);

    }

    //2-映射
    @Test
    public void test2() {
        //map(Function f)——接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
        //练习:转换为大写
        List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
        //方式1:
        list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
        //方式2:
        list.stream().map(String :: toUpperCase).forEach(System.out::println);

        //练习:获取员工姓名长度大于3的员工。
        List<Employee> employees = EmployeeData.getEmployees();
        employees.stream().filter(emp -> emp.getName().length() > 3).forEach(System.out::println);

        //练习:获取员工姓名长度大于3的员工的姓名。
        //方式1:
        employees.stream().filter(emp -> emp.getName().length() > 3).map(emp -> emp.getName()).forEach(System.out::println);
        //方式2:
        employees.stream().map(emp -> emp.getName()).filter(name -> name.length() > 3).forEach(System.out::println);
        //方式3:
        employees.stream().map(Employee::getName).filter(name -> name.length() > 3).forEach(System.out::println);
    }

    //3-排序
    @Test
    public void test3() {
        //sorted()——自然排序
        Integer[] arr = new Integer[]{345,3,64,3,46,7,3,34,65,68};
        String[] arr1 = new String[]{"GG","DD","MM","SS","JJ"};

        Arrays.stream(arr).sorted().forEach(System.out::println);
        System.out.println(Arrays.toString(arr));//arr数组并没有因为升序,做调整。

        Arrays.stream(arr1).sorted().forEach(System.out::println);

        //因为Employee没有实现Comparable接口,所以报错!
//        List<Employee> list = EmployeeData.getEmployees();
//        list.stream().sorted().forEach(System.out::println);

        //sorted(Comparator com)——定制排序
        List<Employee> list = EmployeeData.getEmployees();
        list.stream().sorted((e1,e2) -> e1.getAge() - e2.getAge()).forEach(System.out::println);

        //针对于字符串从大大小排列
        Arrays.stream(arr1).sorted((s1,s2) -> -s1.compareTo(s2)).forEach(System.out::println);
//        Arrays.stream(arr1).sorted(String :: compareTo).forEach(System.out::println);
    }
}

终止操作:终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
补充知识点:遍历list的方式:①使用Iterator ②增强for ③一般for ④forEach()

  • 匹配与查找
    • allMatch(Predicate p):检查是否匹配所有元素
    • anyMatch(Predicate p) :检查是否至少匹配一个元素
    • noneMatch(Predicate p):检查是否没有匹配所有元素
    • findFirst():返回第一个元素
    • findAny():返回当前流中的任意元素
    • count():返回流中元素总数
    • max(Comparator c):返回流中最大值
    • min(Comparator c):返回流中最小值
    • forEach(Consumer c):内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)
  • 归约:备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。
    • reduce(T identity, BinaryOperator b):可以将流中元素反复结合起来,得到一个值。返回 T
    • reduce(BinaryOperator b):可以将流中元素反复结合起来,得到一个值。返回 Optional< T>
  • 收集
    • collect(Collector c):将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法。
      说明:Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例
import java.util.stream.Collectors;
public class StreamAPITest2 {
    //1-匹配与查找
    @Test
    public void test1(){
//        allMatch(Predicate p)——检查是否匹配所有元素。
//          练习:是否所有的员工的年龄都大于18
        List<Employee> list = EmployeeData.getEmployees();
        System.out.println(list.stream().allMatch(emp -> emp.getAge() > 18));

//        anyMatch(Predicate p)——检查是否至少匹配一个元素。
        //练习:是否存在年龄大于18岁的员工
        System.out.println(list.stream().anyMatch(emp -> emp.getAge() > 18));
//         练习:是否存在员工的工资大于 10000
        System.out.println(list.stream().anyMatch(emp -> emp.getSalary() > 10000));

//        findFirst——返回第一个元素
        System.out.println(list.stream().findFirst().get());
    }
    @Test
    public void test2(){
        // count——返回流中元素的总个数
        List<Employee> list = EmployeeData.getEmployees();
        System.out.println(list.stream().filter(emp -> emp.getSalary() > 7000).count());
//        max(Comparator c)——返回流中最大值
        //练习:返回最高工资的员工
        System.out.println(list.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));

//        练习:返回最高的工资:
        //方式1:
        System.out.println(list.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())).get().getSalary());
        //方式2:
        System.out.println(list.stream().map(emp -> emp.getSalary()).max((salary1, salary2) -> Double.compare(salary1, salary2)).get());
        System.out.println(list.stream().map(emp -> emp.getSalary()).max(Double::compare).get());

//        min(Comparator c)——返回流中最小值
//        练习:返回最低工资的员工
        System.out.println(list.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
//        forEach(Consumer c)——内部迭代
        list.stream().forEach(System.out::println);

        //针对于集合,jdk8中增加了一个遍历的方法
        list.forEach(System.out::println);
        //针对于List来说,遍历的方式:① 使用Iterator ② 增强for ③ 一般for ④ forEach()
    }
    //2-归约
    @Test
    public void test3(){
//        reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。返回 T
//        练习1:计算1-10的自然数的和
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        System.out.println(list.stream().reduce(0, (x1, x2) -> x1 + x2));
        System.out.println(list.stream().reduce(0, (x1, x2) -> Integer.sum(x1,x2)));
        System.out.println(list.stream().reduce(0, Integer::sum));
        System.out.println(list.stream().reduce(10, (x1, x2) -> x1 + x2));
//        reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
//        练习2:计算公司所有员工工资的总和
        List<Employee> employeeList = EmployeeData.getEmployees();
        System.out.println(employeeList.stream().map(emp -> emp.getSalary()).reduce((salary1, salary2) -> Double.sum(salary1, salary2)));
        System.out.println(employeeList.stream().map(emp -> emp.getSalary()).reduce(Double::sum));
    }
    //3-收集
    @Test
    public void test4(){
        List<Employee> list = EmployeeData.getEmployees();
//        collect(Collector c)——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
//        练习1:查找工资大于6000的员工,结果返回为一个List或Set
        List<Employee> list1 = list.stream().filter(emp -> emp.getSalary() > 6000).collect(Collectors.toList());
        list1.forEach(System.out::println);
        System.out.println();
        list.forEach(System.out::println);
        System.out.println();
//        练习2:按照员工的年龄进行排序,返回到一个新的List中
        List<Employee> list2 = list.stream().sorted((e1, e2) -> e1.getAge() - e2.getAge()).collect(Collectors.toList());
        list2.forEach(System.out::println);
    }
}

补充:jdk9新增API(Stream实例化方法)

1、Stream实例化方法
ofNullable()的使用:Java 8 中 Stream 不能完全为null,否则会报空指针异常。而 Java 9 中的 ofNullable 方法允许我们创建一个单元素 Stream,可以包含一个非空元素,也可以创建一个空 Stream。

ofNullable()使用、iterator()重载的使用

/**
* ofNullable()使用
*/
//报NullPointerException
//Stream<Object> stream1 = Stream.of(null);
//System.out.println(stream1.count());

//不报异常,允许通过
Stream<String> stringStream = Stream.of("AA", "BB", null);
System.out.println(stringStream.count());//3

//不报异常,允许通过
List<String> list = new ArrayList<>();
list.add("AA");
list.add(null);
System.out.println(list.stream().count());//2
//ofNullable():允许值为null
Stream<Object> stream1 = Stream.ofNullable(null);
System.out.println(stream1.count());//0

Stream<String> stream = Stream.ofNullable("hello world");
System.out.println(stream.count());//1

/**
* iterator()重载的使用
*/
//原来的控制终止方式:
Stream.iterate(1,i -> i + 1).limit(10).forEach(System.out::println);

//现在的终止方式:
Stream.iterate(1,i -> i < 100,i -> i + 1).forEach(System.out::println);

三、jdk8以后的新语法特性

3.1 [jdk9] Java的REPL工具:jShell命令

1、定义:Java的REPL工具(交互式编程环境,read - evaluate - print - loop):jShell。以交互式的方式对语句和表达式进行求值。即写即得,快速运行。
使用方式:利用jShell在没有创建类的情况下,在命令行里直接声明变量,计算表达式,执行语句。无需解释public static void main(String[] args)。
2、使用示例

基本使用:直接声明变量,计算表达式。具体可以输入java定义(变量、方法、类等等)、java表达式、java语句或导入、小块的java代码即片段
自动补全代码:Tab键
jshell:调出jshell
/exit:退出jshell
/help:获取帮助
/help intro:jshell工具的简介
/imports:查看已导入的包
/list:列出当前 session 里所有有效的代码片段
/vars:查看当前 session 下所有创建过的变量
/methods:查看当前 session 下所有创建过的方法,注意还可重写现有方法
/history:键入的内容的历史记录
/edit:用外部代码编辑器来编写 Java 代码。/edit会打开外部代码编辑器,/edit 方法名,则会打开已存在的这个方法
/open 完整文件路径:从外部文件加载源代码

3.2 [jdk7&9] 异常处理之try-catch资源关闭

1、作用:简化资源的关闭操作,在此之前必须在finally中关闭资源

  • jdk7新特性:在try的后面可以增加一个(),在括号中可以声明流对象并初始化。try中的代码执行完毕,会自动把流对象释放,就不用写finally了。
    注意点:try()中声明的资源,无论是否发生 / 处理异常,都会自动关闭资源对象;这些资源类必须实现AutoCloseable或Closeable接口,实现其中的close()方法;这些资源类的变量默认final不能修改。
  • jdk9新特性:try的前面可以定义流对象,try后面的()中可以直接引用流对象的名称。在try代码执行完毕后,流对象也可以释放掉,也不用写finally了。

如下PrintStream流的结构分析
jdk7中try-catch新特性的要求:try()中声明的资源类必须实现AutoCloseable或Closeable接口,实现其中的close()方法。jdk7几乎改写了所有的资源类(文件IO的各种类、JDBC编程的Connection、Statements等接口)。

打印流的结构分析

/**
* jdk7新特性
*/
try(资源对象的声明和初始化){
    业务逻辑代码,可能会产生异常
}catch(异常类型1 e){
    处理异常代码
}catch(异常类型2 e){
    处理异常代码
}
/**
* jdk9新特性——可读性更高
*/
A a = new A();
B b = new B();
try(a;b){
    可能产生的异常代码
}catch(异常类名 变量名){
    异常处理的逻辑
}

jdk7he jdk9关于try-catch新特性举例:

/**
* jdk7的举例
*/
@Test
public void test03() {
    //从d:/1.txt(utf-8)文件中,读取内容,写到项目根目录下1.txt(gbk)文件中
    try (
        FileInputStream fis = new FileInputStream("d:/1.txt");
        InputStreamReader isr = new InputStreamReader(fis, "utf-8");
        BufferedReader br = new BufferedReader(isr);

        FileOutputStream fos = new FileOutputStream("1.txt");
        OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk");
        BufferedWriter bw = new BufferedWriter(osw);
    ) {
        String str;
        while ((str = br.readLine()) != null) {
            bw.write(str);
            bw.newLine();
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
/**
* jdk9的说明
*/
@Test
public void test04() {
    InputStreamReader reader = new InputStreamReader(System.in);
    OutputStreamWriter writer = new OutputStreamWriter(System.out);
    try (reader; writer) {
        //reader是final的,不可再被赋值
        //   reader = null;

    } catch (IOException e) {
        e.printStackTrace();
    }
}

3.3 [jdk10] 局部变量的类型推断

1、定义:允许开发人员省略通常不必要的局部变量类型声明,以增强Java语言的体验性、可读性。

局部变量的类型推断可以使用的场景
var不是一个关键字,而是一个类型名,将它作为变量的类型。不能使用var作为类名。编译器负责推断出类型,并把结果写入字节码文件

//1.局部变量的实例化
var list = new ArrayList<String>();

var set = new LinkedHashSet<Integer>();

//2.增强for循环中的索引
for (var v : list) {
    System.out.println(v);
}

//3.传统for循环中
for (var i = 0; i < 100; i++) {
    System.out.println(i);
}

//4. 返回值类型含复杂泛型结构
var iterator = set.iterator();
//Iterator<Map.Entry<Integer, Student>> iterator = set.iterator();

HashMap<String, Integer> map = new HashMap<>();
var ent = map.entrySet();

局部变量类型推断不适用的场景举例

//1. 声明一个变量
var i;
var j = 0;
//2. 声明一个数组变量, 静态初始化
var arr = new int[]{1,2,3};
var arr1 = {1,2,3};
//3. 方法的形参类型 \ 返回值类型 \ 没有初始化的方法内的局部变量声明
public var add(var m, var n){
	var k;
}
//4. 作为catch块中异常类型
try{
	System.out.println(10 / 0);
}catch(var e){
	e.printStackTrace();
}
//5. Lambda表达式中函数式接口的类型
Comparator<String> com = (s1, s2) -> s1.compareTo(s2);
var com = (s1, s2) -> s1.compareTo(s2);
//6. 方法引用中函数式接口的类型
var com = String :: compareTo;

3.4 [jdk14/15/6] instanceof的模式匹配

instance的模式匹配新特性:jdk14中预览,jdk15中第二次预览,jdk16中转正
1、作用:instanceof 模式匹配通过提供更为简便的语法,可以减少Java程序中显式强制转换的数量,实现更精确、简洁的类型安全的代码。

/**
* 类中equals重写举例
*/
class Monitor{
    private String model;
    private double price;
    /**
    * equals写法一: instanceof的模式匹配的新特性之前
    */
    public boolean equals(Object o){
        if(o instanceof Monitor){
        	Monitor other = (Monitor)o;
            if(this.model.equals(other.model) && this.price == other.price){
                return true;
            }
        }
        return false;
    }
    /**
    * equals写法二: instanceof的模式匹配的新特性
    * instanceof的模式匹配: 匹配和类型转换放一起了
    */
    public boolean equals(Object o){
        if(o instanceof Monitor other){//新特性:省去了强制类型转换的过程
            if(this.model.equals(other.model) && this.price == other.price){
                return true;
            }
        }
        return false;
    }
    /**
    * equals写法三: 进一步简化
    */
    public boolean equals(Object o){
        return o instanceof Monitor other && this.model.equals(other.model) && this.price == other.price;
    }
}

3.5 [jdk12/13/14/17] switch表达式 / 模式匹配

switch表达式:jdk12中预览;jdk13中第二次预览新特性,引入了yield语句,用于返回值;jdk14转正特性。
switch的模式匹配:jdk17的预览特性。

1、传统switch声明语句的弊端:

  • case穿透:匹配是自上而下的,如果没有break,后面的case语句不论匹配与否都会执行;
  • 所有的case语句共用一个块范围,在不同的case语句定义的变量名不能重复;
  • 不能在一个case里写多个执行结果一致的条件;
  • 整个switch不能作为表达式返回值;

2、jdk12中预览新特性——switch表达式:——注意switch是一条语句最后是;结尾

  • 对switch声明语句进行扩展,使用case L ->来替代以前的break;语句。
  • 可以在一个case里写多个执行结果一致的条件,用","分隔。
  • 同一个 switch 结构里不能混用 -> : ,否则编译错误。
  • 整个switch可以作为表达式返回值,因为switch就是一条语句。

3、jdk13中第二次预览switch表达式,引入yield语句
作用:yield用于返回值,即switch表达式需要返回值应该使用yield,不需要返回值使用break;(可以使用case L -> 替换)。
yield和return的区别:return会直接跳出当前循环或者方法,而yield只会跳出当前switch块。

注意区分:
(1)在jdk12的预览特性在不使用yield语句的情况下也可以有返回值,但是只能有一条,在既想有多条逻辑语句,又想有返回值的情况下可以使用yield语句。
(2)在case中只有一条语句且是返回值,那么建议使用case L -> ,否则使用yield语句用于返回值。它们两可以混用。

4、jdk17的预览特性:switch的模式匹配
直接在 switch 上支持 Object 类型,这就等于同时支持多种类型,使用模式匹配得到具体类型,大大简化了语法量。

o instanceof Integer i:合二为一,那么到switch-case中的表示方式如下:
switch(o){
	case Integer i -> 语句;
}

switch表达式的新特性前后使用变化:jdk12switch表达式预览,jdk13switch表达式第二次预览,引入yield语句用于返回值。

enum Fruit {
    PEAR, APPLE, GRAPE, MANGO, ORANGE, PAPAYA;
}
public class SwitchTest {
	/**
	* jdk12之前的switch传统表达式
	* 多个执行结果一致的条件: 利用case穿透性
	*/
    @Test
    public void test01(){
        int numberOfLetters;
        Fruit fruit = Fruit.APPLE;
        switch (fruit) {
            case PEAR:
                numberOfLetters = 4;
                break;
            case APPLE:
            case GRAPE:
            case MANGO:
                numberOfLetters = 5;
                break;
            case ORANGE:
            case PAPAYA:
                numberOfLetters = 6;
                break;
            default:
                throw new IllegalStateException("No Such Fruit:" + fruit);
        }
        System.out.println(numberOfLetters);
    }

	/**
	* jdk12的switch表达式预览——注意switch是一条语句
	* 多个执行结果一致的条件: 可以写在一个case里面
	* case L -> 替代break;
	*/
	@Test
    public void test02(){
        int numberOfLetters;
        Fruit fruit = Fruit.APPLE;
        switch (fruit) {
            case PEAR -> numberOfLetters = 4;
            case APPLE, GRAPE, MANGO -> numberOfLetters = 5;
            case ORANGE, PAPAYA -> numberOfLetters = 6;
            default -> throw new IllegalStateException("No Such Fruit:" + fruit);
        };
        System.out.println(numberOfLetters);
    }
    /**
	* jdk12的switch表达式预览——注意switch是一条语句
	* 使用变量接收switch表达式的结果
	*/
	@Test
    public void test03(){
        Fruit fruit = Fruit.APPLE;
        int numberOfLetters = switch (fruit) {
            case PEAR -> 4;
            case APPLE, GRAPE, MANGO -> 5;
            case ORANGE, PAPAYA -> 6;
            default -> throw new IllegalStateException("No Such Fruit:" + fruit);
        };
        System.out.println(numberOfLetters);
    }
    /**
	* jdk13的switch表达式第二次预览
	* yield语句用于返回值
	* yield与在case中只有一条返回值语句时,yield语句和case L -> 返回值,此时可以混用。
	*/
	@Test
    public void test04(){
        Fruit fruit = Fruit.APPLE;
        int numberOfLetters = switch (fruit) {
            case PEAR -> {
                System.out.println("PEAR");//有返回值的同时还可以有其他业务语句
                yield 4;
            }
            case APPLE, GRAPE, MANGO -> {
                System.out.println("APPLE, GRAPE, MANGO");
                yield 5;
            }
            case ORANGE, PAPAYA -> 6;//只有一条语句用于返回值, 可以采取这种形式
            default -> {
                System.out.println("不存咋!!!");
                throw new IllegalStateException("No Such Fruit:" + fruit);
            }
        };
        System.out.println(numberOfLetters);
    }
}

switch的模式匹配:jdk17的预览特性。

/**
* jdk17之前
*/
static String formatter(Object o) {
    String formatted = "unknown";
    if (o instanceof Integer i) {
        formatted = String.format("int %d", i);
    } else if (o instanceof Long l) {
        formatted = String.format("long %d", l);
    } else if (o instanceof Double d) {
        formatted = String.format("double %f", d);
    } else if (o instanceof String s) {
        formatted = String.format("String %s", s);
    }
    return formatted;
}
/**
* jdk17的预览特性: switch表达式的模式匹配
*/
static String formatterPatternSwitch(Object o) {
    return switch (o) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> o.toString();
    };
}

3.6 [jdk13/14/15] 文本块

jdk13中预览特性;jdk14中第二次预览特性,增加了两个escape sequences,分别是 \ < line-terminator>与\s escape sequence;jdk15中转正新特性

文本块的使用场景:使用String类型表达HTML,XML,SQL或JSON等格式的字符串,字符串中需要进行转义和连接操作,然后才能编译该代码,这种表达方式难以阅读并且难以维护。

1、jdk13预览特性:文本块的写法:“”"作为文本块的开始符和结束符,不需要任何转义。

2、jdk14第二次预览特性:增加两个escape sequences:\ 表示取消换行操作、\s 表示一个空格

//新特性之前
String code1 = "line1\nline2\nline3\n"
String code2 = "line1\n" +
"line2\n" +
"line3\n";
//新特性文本块:
String code3 = """
line1
line2
line3  //如果"""放在这里表示:字符串末尾不需要行终止符===="line1\nline2\nline3"
""";

jdk13预览,jdk14第二次预览,jdk15转正新特性,以SQL语句为例


public class TextCode {
	//文本块新特性之前的写法
	@Test
    public void test01(){
		String sql = "SELECT id,NAME,email\n" +
                "FROM customers\n" +
                "WHERE id > 4\n" +
                "ORDER BY email DESC";
    	}
    	System.out.println(sql);
    }
    //jdk13预览特性写法
    @Test
    public void test02(){
		String sql = """
                SELECT id,NAME,email
                FROM customers
                WHERE id > 4
                ORDER BY email DESC
                """;
    	}
    	System.out.println(sql);
    }
    //jdk14新特性
    @Test
    public void test5(){
        // \:取消换行操作
        // \s:表示一个空格
        String sq = """
                SELECT id,NAME,email \
                FROM customers\s\
                WHERE id > 4 \
                ORDER BY email DESC
                """;
        System.out.println(sql);
    }
}

3.7 [jdk14/15/16] Record

jdk14预览,jdk15第二次预览,jdk16转正特性。

1、Record类:实现简单的数据载体类,本质上是一个final类,所有属性都是final修饰,会自动编译出hashCode、equals、toString、构造器、public 属性名(即访问器)等结构,减少代码量。

2、record类中可 / 不可声明的:
可以重写以上会自动编译出的方法,覆盖它们。注意构造器不能额外添加,只能有一种。
可以定义静态字段、静态方法、构造器或实例方法。
不能定义实例字段;类不能声明为abstract;不能声明显式的父类(非Record类);record类也不能有子类,因为它是被final修饰的

3、Record类不适用场景
record类目的将数据建模为数据,也不是 JavaBeans 的直接替代品,因为record的方法不符合 JavaBeans 的 get 标准。另外 JavaBeans 通常是可变的,而记录是不可变的。尽管它们的用途有点像,但记录并不会以某种方式取代 JavaBean。

源码Record的定义

public abstract class Record {
	protected Record() {}
	@Override
    public abstract boolean equals(Object obj);
    @Override
    public abstract int hashCode();
    @Override
    public abstract String toString();
}	

使用Record类的本质上 VS 使用Record类
注意点:

  • 只有一个构造器;
  • 成员变量final修饰
  • 访问器用来获取成员变量,以属性名命名,注意与JavaBean的get标准区分;
  • equals比较所有属性;
  • toString打印出该类所有成员属性;
/**
* Record类的定义
*/
record Point(int x, int y) { }
/**
* Record类本质上
*/
class Point {
	//实例属性
    private final int x;
    //构造器: 只能有一个
    Point(int x) {
        this.x = x;
    }
    //get方法: 注意区分JavaBean标准的get方法
    int x() {
        return x;
    }
    public boolean equals(Object o) {
        if (!(o instanceof Point)) return false;
        Point other = (Point) o;
        return other.x == x;
    }
    public int hashCode() {
        return Objects.hash(x);
    }
    @Override
    public String toString() {
        return "Point{" +
                "x=" + x +
                '}';
    }
}

3.8 [jdk15/16/17] 密封类

jdk15预览,jdk16第二次预览,jdk17转正特性。

1、使用场景:
final修饰类:该类只有能或不能被继承两种情况。
sealed修饰类:该类可以指定子类继承。

2、使用方式
sealed修饰符将一个类声明为密封类,使用保留关键字permits列出可以直接扩展它的类。
密封类具有传递性,子类使用指定的关键字修饰,且只能是final、sealed、non-sealed

  • non-sealed:可以允许任何类继承
  • final:不能再被继承了
  • sealed:密封类只能被指定的类继承
package com.atguigu.java;
public abstract sealed class Shape permits Circle, Rectangle, Square {...}

public final class Circle extends Shape {...} //final表示Circle不能再被继承了

public sealed class Rectangle extends Shape permits TransparentRectangle, FilledRectangle {...}

public final class TransparentRectangle extends Rectangle {...}

public final class FilledRectangle extends Rectangle {...}

public non-sealed class Square extends Shape {...} //non-sealed表示可以允许任何类继承

四、jdk8以后的API层面变化

4.1 [jdk8/9/10/11] Optional类

1、Optional类的作用:避免空指针异常
Optional是一个容器类,可以保存类型T的值,代表这个值存在,如果是null表示这个值存在。

2、创建Optionall类对象及相关方法(jdk8)

  • 创建Optional类对象:
    • static < T> Optional< T> empty():创建一个空的Optional实例
    • static < T> Optional< T> of(T value) :value必须非空
    • static < T> Optional< T> ofNullable(T value):value可以为空
  • 取出Optional容器内的对象
    • T get():取不到就会抛异常 == orElseThrow
    • T orElse(T other):取不到就用other代替
    • T orElseGet(Supplier<? extends T> other):取不到就用Supplier接口的Lambda表达式提供的值代替
    • T orElseThrow(Supplier<? extends X> exceptionSupplier):取不到就抛出指定的异常
  • 判断Optional容器中是否包含对象:
    • boolean isPresent()
    • void ifPresent(Consumer<? super T> consumer) :如果存在,就对它进行Consumer指定的操作

3、JDK9-11的新特性:前两个JDK 9、JDK 11、JDK 10

  • ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction):value非空,执行参数1功能;如果value为空,执行参数2功能
  • Optional< T> or(Supplier<? extends Optional<? extends T>> supplier):value非空,返回对应的Optional;value为空,返回形参封装的OptionalJDK 9Stream< T> stream():value非空,返回仅包含此value的Stream;否则,返回一个空的Stream
  • boolean isEmpty():判断value是否为空
  • T orElseThrow():value非空,返回value;否则抛异常NoSuchElementException

Optional的使用说明

@Test
public void testOptional(){
    String star1 = "小明";
    star1 = null;
    String star2 = "小红";
    star2 = null;
    //System.out.println(star1.toString());会出现空指针异常

    Optional<String> op1 = Optional.ofNullable(star1);//创建Optional类的对象, star的值可以为空

    boolean bol = op1.isPresent();
    op1.isPresent(a1 -> System.out.println(a1));

    String str1 = op1.get();//取出Optional容器内部的值, 如果为空, 就抛出异常
    String str4 = op1.orElseThrow();//取出Optional容器内的值, 如果为空, 就抛出异常  == get()方法
    String str5 = op1.orElseThrow(() ->new RuntimeException("值不存在"));//取出Optional容器内的值, 如果为空, 就抛出指定的异常
    
    String str2 = op1.orElse(star2);//取出Optional容器内的值, 如果为空, 就用star2指定的默认值代替
    String str3 = op1.orElseGet(()-> "replace");//取出Optional容器内的值, 如果为空, 就用Supplier接口的Lambda表达式提供的值代替

    System.out.println(str4);
}

4.2 String存储结构和API变更

1、jdk7关于String存储的问题
String 不用 char[] 来存储,改成了 byte[] 加上编码标记,节约了一些空间。
StringBuffer 与 StringBuilder也做了相应的改变。

2、jdk11:新增一系列字符串处理方法

//判断字符串是否为空白
"  ".isBlank(); // true
//去除首尾空白
"  Javastack ".strip(); // "Javastack"
//去除尾部空格
"  Javastack ".stripTrailing(); // " Javastack"
//去除首部空格
"  Javastack ".stripLeading(); // "Javastack "
//复制字符串
"Java".repeat(3);//  "JavaJavaJava"
//行数统计
"A\nB\nC".lines().count();  // 3

3、jdk12:String实现了Constable接口——体现它是常量
String新增方法:transform(Function)

var result = "foo".transform(input -> input + " bar");
System.out.println(result); //foo bar

对应的源码

public <R> R transform(Function<? super String, ? extends R> f) {
 return f.apply(this);
}

4.3 标记删除Applet API

1、 Applet 已经被淘汰:Applet API 提供了一种将 Java AWT/Swing 控件嵌入到浏览器网页中的方法。
因为所有 Web 浏览器供应商都已删除或透露计划放弃对 Java 浏览器插件的支持。Java 9 被标记为过时,Java 17 标记为删除。

java.applet.Applet
java.applet.AppletStub
java.applet.AppletContext
java.applet.AudioClip
javax.swing.JApplet
java.beans.AppletInitializer

五、jdk8以后的其他结构变化

5.1 JDK9:UnderScore(下划线)使用的限制

jdk8种标识符可以独立使用“_”来命名,jdk9中规定不可以单独作为命名标识符了。

5.2 JDK11:更简化的编译运行程序

1、jdk11之前:运行一个 Java 源代码必须先编译,再运行。
jdk11及之后:通过一个 java 命令即可。注意点:执行源文件中的第一个类,第一个类必须包含主方法。

/**
* jdk11之前
*/
// 编译
javac JavaStack.java
// 运行
java JavaStack

/**
* jdk11及之后
*/
java JavaStack.java

5.3 GC方面新特性

回顾:
我们都知道,在之前,需要 GC 的时候,为了进行垃圾回收,需要所有的线程都暂停下来,这个暂停的时间我们称为 Stop The World。
而为了实现 STW 这个操作, JVM 需要为每个线程选择一个点停止运行,这个点就叫做安全点(Safepoints)。

1、GC:当GC停顿太长,就会开始影响应用的响应时间。
希望JVM能够以高效的方式充分利用这些内存, 并且无需长时间的GC暂停时间。

2、G1 GC的演变历程:

  • jdk9 G1 GC:可以尽量的避免full gc,但是如果它无法足够快的回收内存的时候,它就会强制停止所有的应用线程然后清理。full gc的算法标记-清除-压缩。
  • JDK10 : 为G1提供并行的Full GC。单线程版的标记-清除-压缩的full gc算法改成了支持多个线程同时full gc。可以减少full gc带来的停顿,从而提高性能。
    可以通过-XX:ParallelGCThreads参数来指定用于并行GC的线程数。
  • jdk12:可中断的G1 Mixed GC。增强G1,自动返回未用堆内存给操作系统

3、Shenandoah GC:

  • JDK12:Shenandoah GC:低停顿时间的GC
    (1)是 Red Hat 在 2014 年宣布进行的一项垃圾收集器研究项目 Pauseless GC 的实现,旨在针对 JVM 上的内存收回实现低停顿的需求。
    (2)Shenandoah 垃圾回收器的暂停时间与堆大小无关,这意味着无论将堆设置为 200 MB 还是 200 GB,都将拥有一致的系统暂停时间,不过实际使用性能将取决于实际工作堆的大小和工作负载。
    (3)Shenandoah GC 主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等。
  • JDK15:Shenandoah垃圾回收算法转正
    Shenandoah垃圾回收算法终于从实验特性转变为产品特性,这是一个从 JDK 12 引入的回收算法,该算法通过与正在运行的 Java 线程同时进行疏散工作来减少 GC 暂停时间。Shenandoah 的暂停时间与堆大小无关,无论堆栈是 200 MB 还是 200 GB,都具有相同的一致暂停时间。
    Shenandoah在JDK12被作为experimental引入,在JDK15变为Production;之前需要通过-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC来启用,现在只需要-XX:+UseShenandoahGC即可启用

4、革命性的 ZGC

  • JDK11:引入革命性的 ZGC
    ZGC是一个并发、基于region、压缩型的垃圾收集器。
    ZGC的设计目标是:支持TB级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%。 将来还可以扩展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于DRAM和冷对象置于NVMe闪存),或压缩堆。
  • JDK13:ZGC:将未使用的堆内存归还给操作系统
  • JDK14:ZGC on macOS和windows
    JDK14之前,ZGC仅Linux才支持。现在mac或Windows上也能使用ZGC了
    ZGC与Shenandoah目标高度相似,在尽可能对吞吐量影响不大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟。
  • JDK15:ZGC 功能转正:这并不是替换默认的GC,默认的GC仍然还是G1;之前需要通过-XX:+UnlockExperimentalVMOptions -XX:+UseZGC来启用ZGC,现在只需要-XX:+UseZGC就可以。相信不久的将来它必将成为默认的垃圾回收器。
    对比Shenandoah和ZGC:①相同点:性能几乎可认为是相同的;②不同点:ZGC是Oracle JDK的,根正苗红。而Shenandoah只存在于OpenJDK中,因此使用时需注意你的JDK版本
  • JDK16:ZGC 并发线程处理:在线程的堆栈处理过程中,总有一个制约因素就是safepoints。在safepoints这个点,Java的线程是要暂停执行的,从而限制了GC的效率。
    而ZGC的并发线程堆栈处理可以保证Java线程可以在GC safepoints的同时可以并发执行。它有助于提高所开发的Java软件应用程序的性能和效率。

六、企业真题

1、谈谈java8新特性

  • lambda表达式、Stream API
  • jdk7的对比:元空间、HashMap、新的日期时间API、接口变化等。

2、JDK1.8在数据结构上发生了哪些变化 ?

  • 使用元空间替代永久代。 (方法区:jvm规范中提到的结构。
    • HotSpot来讲,jdk7:方法区的落地体现:永久代。 jdk8:方法区的落地体现:元空间。
  • HashMap底层结构

3、你说的了解 Java的新特性 ,你说说JDK8改进的地方?

4、JDK1.8用的是哪个垃圾回收器?
Parallel GC --> jdk9:默认使用G1GC --> ZGC (低延迟)

5、Lambda表达式有了解吗,说说如何使用的
在给函数式接口提供实例时,都可以考虑使用lambda表达式。

6、什么是函数式接口?有几种函数式接口
java.util.function包下定义了丰富的好函数式接口。有4类基础的函数式接口:
消费型接口:Consumer< T> void accept(T t)
供给型接口:Supplier< T> T get()
函数型接口:Function< T,R> R apply(T t)
判断型接口:Predicate< T> boolean test(T t)

7、创建Stream的方式
三种。
Collection集合获取顺序流、并行流;数组工具类Arrays获取一个流;通过Stream类的静态方法of()获取流;通过Stream类的静态方法iterator()和generate()获取无限流。

8、你讲讲stream表达式是咋用的,干啥的?
Stream API 关注的是多个数据的计算(排序、查找、过滤、映射、遍历等),面向CPU的。
集合关注的数据的存储,面向内存的。
Stream API 之于集合,类似于SQL之于数据表的查询。

9、集合用Stream流怎么实现过滤?
filter(Predicate predicate)

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值