java8新特征

Java 8 (又称为 JDK 8 或 JDK1.8) 是 Java 语言开发的一个主要版本。 Java 8
是 oracle 公司于 2014 年 3 月发布,可以看成是自 Java 5 以来最具革命性的版
本。Java 8 为 Java 语言、编译器、类库、开发工具与 JVM 带来了大量新特性

Lambda 表达式

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递
的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代
码。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到了提升

个人感觉类似 js 里的箭头函数

操作符 ->

格式 参数列表 -> 方法体

代码举例

无参,无返回值

@Test
public void test4() {
  // 普通格式
  Runnable r1 = new Runnable() {
    @Override
    public void run() {
      System.out.println("哈哈哈哈");
    }
  };
  r1.run();

  // lambda 表达式
  Runnable r2 = () -> {
    System.out.println("lambda");
  };
  r2.run();
}

需要一个参数,无返回值

@Test
public void test5() {
  // 普通格式
  Consumer<String> consumer1 = new Consumer<String>() {
    @Override
    public void accept(String s) {
      System.out.println(s);
    }
  };
  consumer1.accept("哈哈哈");

  // lambda 表达式
  Consumer<String> consumer2 = (String s) -> {
    System.out.println(s);
  };
  consumer2.accept("lambda");

  /* 基于上面可优化
		数据类型可以省略,因为可由编译器推断得出,称为 类型推断
 		Lambda 若只需要一个参数时,参数的小括号可以省略
	*/
  Consumer<String> consumer3 = s -> {
    System.out.println(s);
  };
  consumer3.accept("lambda");
}

需要两个或以上的参数,多条执行语句,并且有返回值

@Test
public void test6() {
  // 普通格式
  Comparator<Integer> comparator1 = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
      System.out.println("o1: " + o1 + " o2: " + o2);
      return o1.compareTo(o2);
    }
  };
  int compare1 = comparator1.compare(3, 6);
  System.out.println(compare1);

  // lambda 表达式
  Comparator<Integer> comparator2 = (o1, o2) -> {
    System.out.println("o1: " + o1 + " o2: " + o2);
    return o1.compareTo(o2);
  };
  int compare2 = comparator2.compare(6, 3);
  System.out.println(compare2);
}

lambda 表达式只有一条语句 可以省略大括号 返回值即是这一条语句

@Test
public void test7() {
  // 优化上面
  Runnable r2 = () -> System.out.println("lambda");

  Consumer<String> consumer3 = s -> System.out.println(s);

  Comparator<Integer> comparator2 = (o1, o2) -> o1.compareTo(o2);
}

函数式接口

包含一个抽象方法的接口,称为函数式接口。当然该接口可以包含其他非抽象方法,通过 Lambda 表达式来创建该接口的对象

接口上使用 @FunctionalInterface 注解,检查它否是一个函数式接口。javadoc 也会包含一条声明,说明这个接口是一个函数式接口

java.util.function 包下定义了 Java 8 的丰富的函数式接口

四大核心函数式接口

函数式接口 称谓 参数类型 用途
Consumer 消费型接口 T 对类型为 T 的对象应用操作,包含方法:void accept(T t)
Supplier 供给型接口 返回类型为 T 的对象,包含方法:T get()
Function<T,R> 函数型接口 T 对类型为T的对象应用操作,并返回结果,结果是 R 类型的对象。包含方法:Rapply(T t)
Predicate 判断型接口 T 确定类型为 T 的对象是否满足某约束,并返回 boolean 值。包含方法:boolean test(T t)

消费型接口

特点:有形参,但是返回值类型是 void

接口名 抽象方法 描述
BiConsumer<T,U> void accept(T t, U u) 接收两个对象用于完成功能
DoubleConsumer void accept(double value) 接收一个 double 值
IntConsumer void accept(int value) 接收一个 int 值
ObjLongConsumer void accept(int value) 接收一个 对象 和 long 类型

还有好几个…Consumer,LongConsumer,ObjDoubleConsumer,ObjIntConsumer 大致和上面一样,见名知意

供给型接口

特点:无参,但是有返回值

接口名 抽象方法 描述
BooleanSupplier boolean getAsBoolean() 返回布尔值
DoubleSupplier double getAsDouble() 返回 double
IntSupplier int getAsInt() 返回 int
LongSupplier long getAsLong() 返回 long

函数型接口

特点:既有参数又有返回值

接口名 抽象方法 描述
UnaryOperator T apply(T t) 接收一个 T 类型对象,返回一个 T 类型对象结果
DoubleFunction R apply(double value) 接收一个 double 值,返回一个 R 类型对象
ToIntFunction int applyAsInt(T value) 接收一个 T 类型对象,返回一个 int
IntToDoubleFunction double applyAsDouble(int value) 接收一个 int 值,返回一个 double 结果
IntUnaryOperator int applyAsInt(int operand) 接收一个 int 值,返回一个 int 结果
BiFunction<T,U,R> R apply(T t, U u) 接收一个 T 类型和一个 U 类型对象,返回一个 R 类型对象结果
BinaryOperator T apply(T t, T u) 接收两个 T 类型对象,返回一个 T 类型对象结果
ToLongBiFunction<T,U> long applyAsLong(T t, U u) 接收一个 T 类型和一个 U 类型对象,返回一个 long

还有更多,根本写不完,大致形如 xxFunction,xxtoxxFunction, xxOperator ToxxFunction等等,见名知意

更多查看 菜鸟教程

判断型接口

特点:有参,但是返回值类型是 boolean 结果

接口名 抽象方法 描述
BiPredicate<T,U> boolean test(T t, U u) 接收两个对象
DoublePredicate boolean test(double value) 接收一个 double 值
IntPredicate boolean test(int value) 接收一个 int 值
LongPredicate boolean test(long value) 接收一个 long 值

代码举例

消费型

@Test
public void test8(){
  List<String> list = Arrays.asList("1212", "qwe", "wads");
  // default void forEach(Consumer<? super T> action)
  // 普通方法 匿名内部类
  list.forEach(new Consumer<String>() {
    @Override
    public void accept(String element) {
      System.out.println(element);
    }
  });
  // lambda 表达式
  list.forEach(s -> System.out.println(s));
}

供给型

public void test9(){
  // 普通方法 匿名内部类
  Supplier<String> supplier1 = new Supplier() {
    @Override
    public Object get() {
      return "hello world";
    }
  };

  // lambda
  Supplier<String> supplier2 = ()->"hello lambda";

  System.out.println(supplier1.get());
  System.out.println(supplier2.get());
}

判断型

@Test
public void test10(){
  List<String> aslist = Arrays.asList("123", "456", "789");
  ArrayList<String> list = new ArrayList<>(aslist);
  // 去除包含 456 的值   普通方法 匿名内部类
  list.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
      return s.contains("456");
    }
  });
  // lambda 去除包含3
  list.removeIf(s->s.contains("3"));
  list.forEach(t-> System.out.println(t));
}

函数型

@Test
public void test11(){
  //可以实现将一个字符串首字母转为大写的功能
  // 普通方法 匿名内部类
  Function<String,String> fun = new Function<String, String>() {
    @Override
    public String apply(String s) {
      return s.substring(0,1).toUpperCase() + s.substring(1);
    }
  };
  String world = fun.apply("world");
  System.out.println(world);

  //使用 Lambda 表达式实现 Function<T,R>接口
  Function<String,String> function = s -> s.substring(0,1).toUpperCase() + s.substring(1);
  String hello = function.apply("hello");
  System.out.println(hello);
}

方法引用与构造器引用

方法引用

Lambda 表达式是可以简化函数式接口的变量或形参赋值的语法。而方法引用
和构造器引用是为了简化 Lambda 表达式的

方法引用是 Lambda 表达式的一个语法糖

方法引用操作符::,将类(或对象) 与 方法名分隔开来

三种情况

  1. 对象 :: 实例方法名
  2. 类 :: 静态方法名
  3. 类 :: 实例方法名

使用前提

Lambda 体只有一句语句,并且是通过调用一个对象的/类现有的方法来完成的

针对情况一:对象 :: 实例方法名

函数式接口中的抽象方法 a 在被重写时使用了某一个对象的方法b。如果方法 a 的形参列表、返回值类型与方法 b 的形参列表、返回值类型都相同,则我们可以使用方法 b 实现对方法 a 的重写、替换

例如

Consumer 中的 void accept(T t)
PrintStream 中的 void println(T t)

@Test
public void test13() {
  // lambda 表达式
  Consumer<String> consumer1 = s -> System.out.println(s);
  consumer1.accept("hello");

  // 方法引用
  PrintStream ps = System.out;
  Consumer<String> consumer2 = ps::println;
  consumer2.accept("world");
}

Supplier 中的 T get()
Employee 中的 String getName()

@Test
public void test14() {
  Employee employee = new Employee(1001, "zs", '1', 18, 23445);

  // lambda 表达式
  Supplier<String> supplier1 = () -> employee.getName();
  String name1 = supplier1.get();
  System.out.println(name1);
  // 方法引用
  Supplier<String> supplier2= employee::getName;
  String name2 = supplier2.get();
  System.out.println(name2);
}

针对情况二:类 :: 静态方法

函数式接口中的抽象方法 a 在被重写时使用了某一个类的静态方法 b。如果方法 a 的形参列表、返回值类型与方法 b 的形参列表、返回值类型都相同,则我们可以使用方法 b 实现对方法 a 的重写、替换

例如

Comparator 中的 int compare(T t1,T t2)
Integer 中的 int compare(T t1,T t2)

@Test
public void test15(){
  // lambda 表达式
  Comparator<Integer> comparator = (t1,t2)-> Integer.compare(t1,t2);
  System.out.println(comparator.compare(3, 6));
  // 方法引用
  Comparator<Integer> comparator1 =Integer::compareTo;
  System.out.println(comparator1.compare(6, 3));

}

Function 中的 R apply(T t)
Math 中的 Long round(Double d)

@Test
public void test16(){
  // lambda 表达式
  Function<Double,Long> func = t->Math.round(t);
  System.out.println(func.apply(12.1));
  // 方法引用
  Function<Double,Long> func1 = Math::round;
  System.out.println(func1.apply(12.8));
}

针对情况三:类 :: 实例方法

函数式接口中的抽象方法 a 在被重写时使用了某一个对象的方法b。如果方法 a 的返回值类型与方法 b 的返回值类型相同,同时方法 a 的形参列表中有 n 个参数,方法 b 的形参列表有 n-1 个参数,且方法 a 的第 1 个参数作为方法 b 的调用者,且方法 a 的后 n-1 参数与方法 b 的 n-1 参数匹配(类型相同或满足多态场景也可以)

Comparator 中的 int compare(T t1,T t2)
String 中的 int t1.compareTo(t2)

@Test
public void test17() {
  // lambda 表达式
  Comparator<String> comparator = (t1, t2) -> t1.compareTo(t2);
  System.out.println(comparator.compare("1", "2"));

  // 方法引用
  Comparator<String> comparator1 = String::compareTo;
  System.out.println(comparator1.compare("2", "1"));

}

BiPredicate 中的 boolean test(T t1, T t2);
String 中的 boolean t1.equals(t2)

@Test
public void test18(){
  // lambda 表达式
  BiPredicate<String,String> biPredicate = (t1,t2)-> t1.equals(t2);
  System.out.println(biPredicate.test("111", "111"));

  // 方法引用
  BiPredicate<String,String> biPredicate2 = String::equals;
  System.out.println(biPredicate2.test("111", "222"));

}

Function 中的 R apply(T t)
Employee 中的 String getName()

@Test
public void test19(){
  Employee employee = new Employee(1001, "zs", '1', 18, 23445);
  // lambda 表达式
  Function<Employee,String> ef = t->t.getName();
  System.out.println(ef.apply(employee));

  // 方法引用
  Function<Employee,String> ef1 = Employee::getName;
  System.out.println(ef1.apply(employee));

}

构造器引用

当 Lambda 表达式是创建一个对象,并且满足 Lambda 表达式形参,正好是给创建这个对象的构造器的实参列表,就可以使用构造器引用

格式:类名::new

代码举例

Supplier 中的 T get()
Employee 的空参构造器:Employee()

@Test
public void test20(){
  // lambda 表达式
  Supplier<Employee> supplier1 = ()-> new Employee();
  System.out.println(supplier1.get());

  // 方法引用
  Supplier<Employee> supplier2=Employee::new;
  System.out.println(supplier2.get());
}

BiFunction 中的 R apply(T t,U u)
Employee 的构造器:Employee(int id, String name)

public void test21(){
  // lambda 表达式
  BiFunction<Integer,String,Employee> biFunction1 = (t1,t2)->new Employee(t1,t2);
  System.out.println(biFunction1.apply(1, "zs"));

  // 方法引用
  BiFunction<Integer,String,Employee> biFunction2 = Employee::new;
  System.out.println(biFunction2.apply(2, "ls"));
}

数组构造引用

当 Lambda 表达式是创建一个数组对象,并且满足 Lambda 表达式形参,正好是给创建这个数组对象的长度,就可以数组构造引用

格式:数组类型名::new

代码举例

数组引用
Function 中的 R apply(T t)

@Test
public void test22(){
  // lambda 表达式
  Function<Integer,String[]> function1 = len -> new String[len];
  String[] arr1 = function1.apply(10);
  System.out.println(Arrays.toString(arr1));

  // 方法引用
  Function<Integer,String[]> function2 = String[]::new;
  String[] arr2 = function2.apply(6);
  System.out.println(Arrays.toString(arr2));
}

Stream API

Stream API ( java.util.stream) 把真正的函数式编程风格引入到 Java 中。这是目前为止
对 Java 类库最好的补充,因为 Stream API 可以极大提供 Java 程序员的生产力,让程
序员写出高效率、干净、简洁的代码

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

注意点:

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

创建步骤

  1. 创建 Stream 一个数据源(如:集合、数组),获取一个流
  2. 中间操作,处理返回 Stream对象
  3. 终止操作
  4. 终止操作 返回值类型就不再是 Stream 了,因此一旦执行终止操作,就结束整个 Stream 操作

创建 stream 实例的方法

集合:Java8 中的 Collection 接口被扩展,提供了两个获取流的方法

  • default Stream stream() : 返回一个顺序流
  • default Stream parallelStream() : 返回一个并行流

数组:Java8 中的 Arrays 的静态方法 stream() 可以获取数组流

  • static Stream stream(T[] array): 返回一个流
  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)

Stream 的 of():调用 Stream 类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数

  • public static Stream of(T… values) : 返回一个流

创建无限流:使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流

  • 迭代 public static Stream iterate(final T seed, final UnaryOperator f)
  • 生成 public static Stream generate(Supplier s)

代码如下:

@Test
public void test1(){
  // 通过集合
  List<String> list = Arrays.asList("123", "222", "333");
  Stream<String> stream = list.stream();

  // 通过数组
  String[] arr = {"1","2","3"};
  Stream<String> stream1 = Arrays.stream(arr);
  System.out.println(stream1);

  // 通过 Stream 的 of()
  Stream<String> stream2 = Stream.of("1", "2", "3");

  // 创建无限流
  // 迭代 偶数
  //  iterate(final T seed, final UnaryOperator f)
  Stream<Integer> stream3 = Stream.iterate(0, x -> x + 2);
  stream3.limit(10).forEach(System.out::println);
  // 生成 随机数
  //  generate(Supplier<T> s)
  Stream<Double> stream4 = Stream.generate(Math::random);
  stream4.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) 产生一个新流,其中按比较器顺序排序

代码举例

@Test
public void test2(){
  // 1. 通过 Stream 的 of() 创建流
  Stream<String> stream = Stream.of("123", "223", "333","233","256");
  // 2 .中间操作 filter 过滤 返回包含2的值的新stream
  // filter(Predicate p)
  stream = stream.filter(t -> t.contains("2"));
  //  limit 截断流,使其元素不超过给定数量
  // 取 3 个
  stream = stream.limit(3);
  // ... 可以加更多操作

  // 3. 终止操作 例如循环遍历
  stream.forEach(System.out::println);

}

@Test
public void test3(){
  //希望能够找出前三个最大值,前三名最大的,不重复
  Stream.of(11,2,39,4,54,6,2,22,3,3,4,54,54)
    // 去除重复
    .distinct()
    // 排序 int compare(T t1, T t2)
    .sorted((t1,t2)->-Integer.compare(t1,t2))
    // 取前3
    .limit(3)
    // 遍历
    .forEach(System.out::println);

  // 转大写
  String[] arr ={"hello","java","mysql","spring"};
  Arrays.stream(arr).map(String::toUpperCase).forEach(System.out::println);
}

终止操作

终止操作会从流的中间中间操作(流水线)生成结果,进行了终止操作后,不能再次使用

终止操作的方法

匹配与查找

方法 描述
allMatch | anyMatch | noneMatch (Predicate p) 检查是否匹配 所有 | 至少一个 | 没有匹配所有 的元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素总数
max | min (Comparator c) 返回流中最 大 | 小 值
forEach(Consumer c) 内部迭代

归约

方法 描述
reduce(T identity,BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T (累积器)
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional

收集

方法 描述
collect(Collector c) 将流转换为其他形式。接收一个 Collector 接口的实现,例如将流搜集到 list, map等等

Collectors 类

Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例

方法 描述
toList/Set/Collection() 把流中元素收集到 List / Set / Collection
counting() 计算流中元素的个数
summingInt() 对流中元素的整数属性求和
averagingInt() 计算流中元素 Integer 属性的平均值
summarizingInt() 收集流中 Integer 属性的统计值。如:平均值
joining() 连接流中每个字符串
max/minBy() 根据比较器选择最大/小值
reducing() 从一个作为累加器的初始值开始,利用 BinaryOperator 与流中元素逐个结合,从而归约成单个值
collectingAndThen() 包裹另一个收集器,对其结果转换函数
groupingBy() 根据某属性值对流分组,属性为K,结果为 V
partitioningBy() 根据 true 或 false 进行分区

代码举例

@Test
public void test4(){
  // 1. 通过 Stream 的 of() 创建流
  Stream<String> stream = Stream.of("123", "233", "222","233","256");
  // 2 .中间操作
  //  limit 截断流,使其元素不超过给定数量
  // 取 3 个
  stream = stream.limit(3);

  // 3. 终止操作 例如 用allMatch 看当前 stream 都是否包含 2
  boolean allMatch = stream.allMatch(t -> t.contains("2"));
  System.out.println(allMatch); // true

  // reduce
  // 将 stream 的值从0开始累加
  Integer reduce = Stream.of(1,2,4,5,7,8)
    .reduce(0, (t1,t2) -> t1+t2);
  System.out.println(reduce); // 27
  // stream 的值取最大值 三元运算符
  Optional<Integer> max = Stream.
    of(3, 4, 5, 6, 1, 2, 3).
    reduce((t1, t2) -> t1 > t2 ? t1 : t2);
  System.out.println(max); //Optional[6]

  // collect 利用Collectors创建常见收集器实例
  // toList/Set/Collection()
  List<Integer> c = Stream.of(1, 2, 3, 4, 5, 6).collect(Collectors.toList());
  System.out.println(c); //[1, 2, 3, 4, 5, 6]


  ArrayList<Employee> employees = new ArrayList<>();
  employees.add(new Employee(1,"java"));
  employees.add(new Employee(2,"py"));
  employees.add(new Employee(3,"php"));
  employees.add(new Employee(4,"c++"));
  //  平均值 averagingInt()
  Double c1 = employees.stream().collect(Collectors.averagingInt(Employee::getId));
  System.out.println(c1); // 2.5
  // 统计值 summarizingInt()
  IntSummaryStatistics c2 = employees.stream().collect(Collectors.summarizingInt(Employee::getId));
  System.out.println(c2); // IntSummaryStatistics{count=4, sum=10, min=1, average=2.500000, max=4}

  // joining 连接流每一个字符串
  String c3 = employees.stream().map(Employee::getName).collect(Collectors.joining());
  System.out.println(c3); //javapyphpc++

  // reducing 累加器
  Integer c4 = employees
    .stream()
    .collect(Collectors.reducing(0, Employee::getId, Integer::sum));
  System.out.println(c4); //10

}

java9 新增Stream 实例化方法 ofNullable()

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

//ofNullable():允许值为 null
Stream<Object> stream1 = Stream.ofNullable(null);
System.out.println(stream1.count());//0

iterator()重载的使用:

// 创建无限流
@Test
public void test5(){
  // 旧方法 limit 限制
  Stream.iterate(1,i -> i + 1).limit(10).forEach(System.out::println);
	// 新 参数 i -> i < 10 限制
  Stream.iterate(1,i -> i < 10,i -> i + 1).forEach(System.out::println);
}

新语法特征

try-catch

(JDK7 的新特性)在 try 的后面可以增加一个(),在括号中可以声明流对象并初始化。try 中的代码
执行完毕,会自动把流对象释放,就不用写 finally 了

例如

@Test
public void test02() {
  try (
    FileWriter fw = new FileWriter("hello.txt");
    BufferedWriter bw = new BufferedWriter(fw);
  ) {
    bw.write("hello");
  } catch (IOException e) {
    e.printStackTrace();
  }
}
  1. 在 try()中声明的资源,无论是否发生异常,无论是否处理异常,都会自动关闭资源对象
  2. 这些资源实现类必须实现 AutoCloseable 或 Closeable 接口,实现其中的 close() 方法
  3. 写到 try()中的资源类的变量默认是 final 声明的,不能修改

局部变量类型推断

JDK 10 的新特性 局部变量的类型推断

例如

//1.局部变量的实例化
var list = new ArrayList<String>();
var set = new LinkedHashSet<Integer>();
// for循环
for (var v : list) {
  System.out.println(v);
}
for (var i = 0; i < 100; i++) {
  System.out.println(i);
}
//  返回值类型含复杂泛型结构
var iterator = set.iterator();

不适用场景:

  • 声明一个成员变量,数组变量(静态初始化)
  • 方法的返回值类型,参数类型
  • 没有初始化的方法内的局部变量声明
  • 作为 catch 块中异常类型
  • Lambda 表达式,方法引用中函数式接口的类型

注:这不是 JavaScript。var 并不会改变 Java 是一门静态类型语言的事实。编译器负责推断出类型,并把结果写入字节码文件,就好像是开发人员自己敲入类型一样

instanceof

(JDK14) instanceof 模式匹配提供更简便的语法 自动添加强转

例如

public void test6(){
  Object obj = new String("hello");
  // 旧方法
  if(obj instanceof String){
    // 强转
    String str = (String) obj;
    // 使用 String类的方法
    System.out.println(str.contains("hello"));
  }else System.out.println("type not String");

  // 新特征 省去强转换的过程
  if(obj instanceof String str){
    // 使用 String类的方法
    System.out.println(str.contains("hello"));
  }else System.out.println("type not String");
}

switch 表达式

(JDK12) 对 switch 声明语句进行扩展,使用 case L ->来替代以前的 break;,省去了 break 语句,避免了因少写 break 而出错

注意:保持兼容性,case 条件语句中依然可以使用字符: ,但是同一个 switch 结构里不能混用->: ,否则编译错误

enum Fruit {
  PEAR, APPLE, GRAPE, MANGO, ORANGE, PAPAYA;
}

public void test{
  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);

  // java 12 写法
  // 将多个 case 合并到一行 整个 switch 作为表达式返回值
  numberOfLetters = switch (fruit) {
    case PEAR -> 4;
    case APPLE, MANGO, GRAPE -> 5;
    case ORANGE, PAPAYA -> 6;
    default -> throw new IllegalStateException("No Such Fruit: " + fruit);
  };
  System.out.println(numberOfLetters);
}

JDK13 中引入了 yield 语句,用于返回值

这意味着,switch 表达式(返回值)应该使用 yield,switch 语句(不返回值)应该使用 break

yield 和 return 的区别在于:return 会直接跳出当前循环或者方法,而 yield 只会跳出当前 switch 块

public void test(){
  String x = "3";
  int i;
  // 原写法
  switch (x) {
    case "1":
      i=1;
      break;
    case "2":
      i=2;
      break;
    default:
      i = x.length();
      break;
  }
  System.out.println(i);
  
  // java13 写法
  int i = switch (x) {
    case "1" -> 1;
    case "2" -> 2;
    default -> {
      yield 3;
    }
  };

  int i = switch (x) {
    case "1":
      yield 1;
    case "2":
      yield 2;
    default:
      yield 3;
  };

  System.out.println(i);
}

JDK17 的预览特性:switch 的模式匹配

在 switch 上支持 Object 类型,这就等于同时支持多种类型,使用模式匹
配得到具体类型,大大简化了语法量

// 旧写法
public String fm(Object obj){
  String formatted = "unknown";
  if(obj instanceof Integer i){
    formatted= String.format("int %d", i);
  } else if (obj instanceof String s) {
    formatted= String.format("String %s", s);
  }else if (obj instanceof Double d) {
    formatted= String.format("Double %f", d);
  }
  return formatted;
}
// switch 的模式匹配
public String newFm(Object obj){
  return switch (obj){
    case Integer i ->String.format("int %d", i);
    case String s -> String.format("String %s", s);
    case Double d-> String.format("Double %f", d);
    default -> obj.toString();
  };
}

文本块

JDK13 的新特性
使用 “”” 作为文本块的开始符和结束符,在其中就可以放置多行的字符串,不需要进行任何转义。因此,文本块将提高 Java 程序的可读性和可写性

例如

line1

line2

line3

写成 String 字符串

// 原方法
String str = "line1\nline2\nline3\n";
//  使用"""
String str1 = """
              line1
              line2
              line3
              """;

例如 JSON 字符串

{
"name":"zs",
"address":"guangdong",
"email":"zs@126.com"
}
String myJson = "{\n" +
  "
  \"name\":\"Song Hongkang\",\n" +
  "
  \"address\":\"www.atguigu.com\",\n" +
  "
  \"email\":\"shkstart@126.com\"\n" +
  "}";
// 新特征
String myJson1 = """
{
  "name":"Song Hongkang",
  "address":"www.atguigu.com",
  "email":"shkstart@126.com"
}""";

JDK14 中二次预览特性

\ :取消换行操作
\s :表示一个空格

record

JDK14 中预览特性:神说要用 record,于是就有了。实现一个简单的数据载体类,为了避免编写:构造函数,访问器,equals(),hashCode () ,toString ()等,Java 14 推出 record

当你用 record 声明一个类时,该类将自动拥有以下功能:

  1. 获取成员变量的简单方法
  2. 一个 equals 方法的实现,执行比较时会比较该类的所有成员属性
  3. 重写 hashCode() 方法
  4. toString() 方法
  5. 只有一个构造器
  6. 可以自己定义静态字段、静态方法、构造器或实例方法

例如

recordv Dog(String name,int age){};
@Test
public void test7(){
  Dog dog1 = new Dog("aa", 3);
  Dog dog2 = new Dog("bb", 2);
  // toString
  System.out.println(dog1.toString()); //Dog[name=aa, age=3]
  // getName -> name
  System.out.println(dog1.name()); // aa
  System.out.println(dog2.age()); // 2
}

最终JDK16 中转正特性

record 的设计目标是提供一种将数据建模为数据的好方法。它也不是JavaBeans 的直接替代品另外 JavaBeans 通常是可变的,而记录是不可变的。尽管它们的用途有点像,但记录并不会以某种方式取代 JavaBean

密封类

java 15,引入了 sealed 类,被 sealed 修饰的类可以指定子类。这样这个类就只能被指定的类继承\

具体使用:

  • 使用修饰符 sealed,可以将一个类声明为密封类。密封的类使用保留关键字permits 列出可以直接扩展(即 extends)它的类。
  • sealed 修饰的类的机制具有传递性,它的子类必须使用指定的关键字进行修饰,且只能是 final、sealed、non-sealed 三者之一

例如

// Circle Rectangle Square 类 可以继承Shape
public abstract sealed class Shape permits Circle, Rectangle, Square{...}
 //non-sealed 表示可、以允许任何类继承
public non-sealed class Square extends Shape {...} 

JDK17 中转正特性

api 变化

Optional 类

Optional<T> 类(java.util.Optional) 是一个容器类,它可以保存类型 T 的值,代
表这个值存在。或者仅仅保存 null,表示这个值不存在。如果值存在,则
isPresent()方法会返回 true,调用 get()方法会返回该对象

创建 Optional 类对象的方法:

  • Optional empty() :用来创建一个空的 Optional 实例
  • Optional of(T value) :用来创建一个 Optional 实例,value 必须非空
  • Optional ofNullable(T value):用来创建一个Optional 实例,可空,可非空

判断 Optional 容器中是否包含对象:

  • isPresent() : 判断 Optional 容器中的值是否存在
  • ifPresent(Consumer<? super T> consumer) :判断 Optional 容器中的值是否存在,如果存在,就对它进行 Consumer 指定的操作,如果不存在就不做

获取 Optional 容器的对象:

  • T get(): 如果调用对象包含值,返回该值。否则抛异常。T get()与 of(T value)配合使用
  • T orElse(T other):与 ofNullable(T value)配合使用,如果Optional 容器中非空,就返回所包装值,如果为空,就用 orElse(T other) other 指定的默认值(备胎)代替
  • T orElseThrow(Supplier<? extends X> exceptionSupplier):如果 Optional 容器中非空,就返回所包装值,如果为空,就抛出你指定的异常类型代替原来的 NoSuchElementException
@Test
public void test8(){
  Employee employee = new Employee(10, "zs");
  Optional<Employee> employee1 = Optional.of(employee);
  System.out.println(employee1);
  Optional<Object> empty = Optional.empty();
  System.out.println(empty);

  Optional<Employee> employee2 = Optional.ofNullable(employee);
  System.out.println(employee2);

  Employee employee3 = employee2.get();

  Employee employee4 = employee2.orElse(new Employee());

}

新特征

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

String 存储结构和 API 变更

jdk 9 String 再也不用 char[] 来存储啦,改成了 byte[] 加上编码标记,节约
了一些空间

JDK11 新特性:新增了一系列字符串处理方法

方法 描述
isBlank() 判断字符串是否为空白
strip() 去除首尾空白
stripTrailing() 去除尾部空格
Leading() 去除首部空格
repeat(int n) 复制n次字符串
lines().count() 行数统计

JDK12 新特性:String 实现了 Constable 接口

就是调用 Optional.of 方法返回一个 Optional 类型

private static void testDescribeConstable() {
  String name = "Java";
  Optional<String> optional = name.describeConstable();
  System.out.println(optional.get()); //Java
}

JDK12 新特性:String 新增方法

transform(Function)

var result = "foo".transform(input -> input + " bar").transform(String::toUpperCase)
System.out.println(result); //FOO BAR

其他变化

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

java 9 中规定_不再可以单独命名标识符了,如果使用,会报错

GC 方面新特性:

JDK9 以后默认的垃圾回收器是 G1GC

JDK10 : 为 G1 提供并行的 Full GC

JDK11:引入革命性的 ZGC

JDK12:可中断的 G1 Mixed GC

JDK12:增强 G1,自动返回未用堆内存给操作系统

JDK12:Shenandoah GC:低停顿时间的 GC

JDK13:ZGC:将未使用的堆内存归还给操作系统

JDK14:ZGC on macOS 和 windows

JDK15:Shenandoah 垃圾回收算法转正

JDK15:ZGC 功能转正

JDK16:ZGC 并发线程处理


参考资料

尚硅谷Java基础