JavaSEJDK8-17新特性

JDK8-17新特性

如何学习新特性?

  • 语法层面(多关注):

    • 比如JDK5中的自动拆箱、自动装箱、enum、泛型
    • 比如JDK8中的lambda表达式、接口中的默认方法、静态方法
    • 比如JDK10中局部变量的类型推断
    • 比如JDK12中的switch
    • 比如JDK13中的文本块
  • API层面:

    • 比如JDK8中的Stream、Optional、新的日期时间、HashMap的底层结构
    • 比如JDK9中String的底层结构
    • 新的 / 过时的 API
  • 底层优化

    • 比如JDK8中永久代被元空间替代、新的JS执行引擎

    • 比如新的垃圾回收器、GC参数、JVM的优化

1 JDK8新特性

1.1 Lambda表达式

1.1.1 Lambda表达式格式

Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符箭头操作符。它将 Lambda 分为两个部分:

  • 左侧:指定了 Lambda 表达式需要的参数列表
  • 右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即 Lambda 表达式要执行的功能。

**语法格式一:**无参,无返回值

@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 需要一个参数,但是没有返回值。

@Test
public void test2(){
    //未使用Lambda表达式
    Consumer<String> con = new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    };
    con.accept("谎言和誓言的区别是什么?");

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

    //使用Lambda表达式
    Consumer<String> con1 = (String s) -> {
        System.out.println(s);
    };
    con1.accept("一个是听得人当真了,一个是说的人当真了");

}

**语法格式三:**数据类型可以省略,因为可由编译器推断得出,称为“类型推断”

@Test
public void test3(){
    //语法格式三使用前
    Consumer<String> con1 = (String s) -> {
        System.out.println(s);
    };
    con1.accept("一个是听得人当真了,一个是说的人当真了");

    System.out.println("*******************");
    //语法格式三使用后
    Consumer<String> con2 = (s) -> {
        System.out.println(s);
    };
    con2.accept("一个是听得人当真了,一个是说的人当真了");

}

**语法格式四:**Lambda 若只需要一个参数时,参数的小括号可以省略

@Test
public void test4(){
    //语法格式四使用前
    Consumer<String> con1 = (s) -> {
        System.out.println(s);
    };
    con1.accept("一个是听得人当真了,一个是说的人当真了");

    System.out.println("*******************");
    //语法格式四使用后
    Consumer<String> con2 = s -> {
        System.out.println(s);
    };
    con2.accept("一个是听得人当真了,一个是说的人当真了");


}

**语法格式五:**Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值

@Test
public void test5(){
    //语法格式五使用前
    Comparator<Integer> com1 = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
        }
    };

    System.out.println(com1.compare(12,21));
    System.out.println("*****************************");
    //语法格式五使用后
    Comparator<Integer> com2 = (o1,o2) -> {
        System.out.println(o1);
        System.out.println(o2);
        return o1.compareTo(o2);
    };

    System.out.println(com2.compare(12,6));


}

**语法格式六:**当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略

@Test
public void test6(){
    //语法格式六使用前
    Comparator<Integer> com1 = (o1,o2) -> {
        return o1.compareTo(o2);
    };

    System.out.println(com1.compare(12,6));

    System.out.println("*****************************");
    //语法格式六使用后
    Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);

    System.out.println(com2.compare(12,21));

}

@Test
public void test7(){
    //语法格式六使用前
    Consumer<String> con1 = s -> {
        System.out.println(s);
    };
    con1.accept("一个是听得人当真了,一个是说的人当真了");

    System.out.println("*****************************");
    //语法格式六使用后
    Consumer<String> con2 = s -> System.out.println(s);

    con2.accept("一个是听得人当真了,一个是说的人当真了");

}
1.1.2 Lambda表达式本质
  1. Lambda表达式是接口实现类的对象
  2. Lambda表达式是一个匿名函数

1.2 函数式接口

1.2.1 函数式接口的定义

只包含一个抽象方法(Single Abstract Method,简称SAM)的接口,称为函数式接口。

1.2.2 需要函数式接口的原因

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

1.2.3 api中函数式接口所在的包

jdk8中声明的函数式接口都在java.util.function包下。

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

->的左边:lambda形参列表,参数类型可省略。若形参只有一个,则一对()也可省略。

->的右边:lambda体,着重写抽象方法的方法体。若方法体只有一行执行语句,则以对{}也可省略,若还有return关键字,则必须一并省略。

1.3 方法引用

1.3.1 方法引用的理解
  • 方法引用,可以看作是基于lambda表达式的进一步刻画
  • 当需要提供一个函数式接口的实例时,我们可是使用lambda表达式提供此实例
    • 在满足一定的条件下,可以使用方法引用或构造器引用替换lambda表达式
1.3.2 方法引用的本质

方法应用作为函数式接口的实例-----》“万事万物皆对象”

1.3.3 方法引用格式

类(或对象)::方法名

1.3.4 具体使用情况说明
  • 情况1:对象::实例方法

    • 函数式接口中的抽象方法a在被重写时在内部调用了某一个对象的方法b。如果方法a的形参列表、返回值类型与方法b的形参列表、返回值类型都相同(或一致),则我们可以使用方法b实现对方法a的覆盖、替换。此覆盖、替换即为方法引用。
    • 注意:此方法b是非静态方法,需对象调用
  • 情况2:类::静态方法

    • 函数式接口中的抽象方法a在被重写时在内部调用了某一个类的方法b。如果方法a的形参列表、返回值类型与方法b的形参列表、返回值类型都相同(或一致),则我们可以使用方法b实现对方法a的覆盖、替换。此覆盖、替换即为方法引用。
  • 情况3:类:: 实例方法

    • 函数式接口中的抽象方法a在被重写时使用了某一个对象的方法b。如果方法a的返回值类型与方法b的返回值类型相同,同时方法a的形参列表中有n个参数,方法b的形参列表有n-1个参数,且方法a的第1个参数作为方法b的调用者,且方法a的后n-1参数与方法b的n-1参数匹配(类型相同或满足多态场景也可以)。此覆盖、替换即为方法引用。
    • 此方法b是非静态方法,需要对象调用。但是形式上,写出对象所属的类。

1.4 构造器引用

1.4.1 构造器引用格式

格式:类名::new

1.4.2 构造器引用说明
  • 调用了类名对应类中中的某一个确定的构造器
  • 具体调用的是类中的哪一个构造器,取决于函数式接口抽象方法的形参列表。

1.5 数组引用

1.5.1 数组引用格式

格式:数组类型名::new

1.6 Stream API

1.6.1 Stream API vs 集合框架
  • Stream API关注的是多个数据的计算(排序、查找、过滤、映射、遍历等),面向cpu的。

    集合关注的是数据的存储,面向内存的。

  • Stream API之于集合,类似于SQL之于数据表的查询。

1.6.2 使用说明

①Stream 自己不会存储元素。

②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。

③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。即一旦执行终止操作,就执行中间操作链,并产生结果。

④ Stream一旦执行了终止操作,就不能再调用其它中间操作或终止操作了。

1.6.3 Stream执行流程
  • 步骤1:Stream实例化

    方式一:通过集合

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

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

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

    @Test
    public void test01(){
        List<Integer> list = Arrays.asList(1,2,3,4,5);
        
        //JDK1.8中,Collection系列集合增加了方法
        Stream<Integer> stream = list.stream();
    }
    

    方式二:通过数组

    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)
    @Test
    public void test02(){
        String[] arr = {"hello","world"};
        Stream<String> stream = Arrays.stream(arr); 
    }
    
    @Test
    public void test03(){
        int[] arr = {1,2,3,4,5};
        IntStream stream = Arrays.stream(arr);
    }
    

    方式三:通过Stream的of()

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

    • public static Stream of(T… values) : 返回一个流
    @Test
    public void test04(){
        Stream<Integer> stream = Stream.of(1,2,3,4,5);
        stream.forEach(System.out::println);
    }
    
  • 步骤2:一系列中间操作

    1-筛选与切片

    方 法描 述
    filter(Predicatep)接收 Lambda , 从流中排除某些元素
    distinct()筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
    limit(long maxSize)截断流,使其元素不超过给定数量
    skip(long n)跳过元素,返回一个扔掉了前 n 个元素的流。
    若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补

    2-映 射

    方法描述
    map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
    mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
    mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
    mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
    flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

    3-排序

    方法描述
    sorted()产生一个新流,其中按自然顺序排序
    sorted(Comparator com)产生一个新流,其中按比较器顺序排序

    代码举例:

    package com.atguigu.stream;
    
    import org.junit.Test;
    
    import java.util.Arrays;
    import java.util.stream.Stream;
    
    public class StreamMiddleOperate {
    	@Test
        public void test01(){
            //1、创建Stream
            Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
    
            //2、加工处理
            //过滤:filter(Predicate p)
            //把里面的偶数拿出来
            /*
             * filter(Predicate p)
             * Predicate是函数式接口,抽象方法:boolean test(T t)
             */
            stream = stream.filter(t -> t%2==0);
    
            //3、终结操作:例如:遍历
            stream.forEach(System.out::println);
        }
        @Test
        public void test02(){
            Stream.of(1,2,3,4,5,6)
                    .filter(t -> t%2==0)
                    .forEach(System.out::println);
        }
        @Test
        public void test03(){
            Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                    .distinct()
                    .forEach(System.out::println);
        }
        @Test
        public void test04(){
            Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                    .limit(3)
                    .forEach(System.out::println);
        }
        @Test
        public void test05(){
            Stream.of(1,2,2,3,3,4,4,5,2,3,4,5,6,7)
                    .distinct()  //(1,2,3,4,5,6,7)
                    .filter(t -> t%2!=0) //(1,3,5,7)
                    .limit(3)
                    .forEach(System.out::println);
        }
        @Test
        public void test06(){
            Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                    .skip(5)
                    .forEach(System.out::println);
        }
        @Test
        public void test07(){
            Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                    .skip(5)
                    .distinct()
                    .filter(t -> t%3==0)
                    .forEach(System.out::println);
        }
        @Test
        public void test08(){
            long count = Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                    .distinct()
                    .peek(System.out::println)  //Consumer接口的抽象方法  void accept(T t)
                    .count();
            System.out.println("count="+count);
        }
        @Test
        public void test09(){
            //希望能够找出前三个最大值,前三名最大的,不重复
            Stream.of(11,2,39,4,54,6,2,22,3,3,4,54,54)
                    .distinct()
                    .sorted((t1,t2) -> -Integer.compare(t1, t2))//Comparator接口  int compare(T t1, T t2)
                    .limit(3)
                    .forEach(System.out::println);
        }
        @Test
        public void test10(){
            Stream.of(1,2,3,4,5)
                    .map(t -> t+=1)//Function<T,R>接口抽象方法 R apply(T t)
                    .forEach(System.out::println);
        }
        @Test
        public void test11(){
            String[] arr = {"hello","world","java"};
    
            Arrays.stream(arr)
                    .map(t->t.toUpperCase())
                    .forEach(System.out::println);
        }
        @Test
        public void test12(){
            String[] arr = {"hello","world","java"};
            Arrays.stream(arr)
                    .flatMap(t -> Stream.of(t.split("|")))//Function<T,R>接口抽象方法 R apply(T t)  现在的R是一个Stream
                    .forEach(System.out::println);
        } 
    }
    
    
  • 步骤3:执行终止操作

    • 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。

    • 流进行了终止操作后,不能再次使用。

    1-匹配与查找

    方法描述
    allMatch(Predicate p)检查是否匹配所有元素
    **anyMatch(Predicate p) **检查是否至少匹配一个元素
    noneMatch(Predicate p)检查是否没有匹配所有元素
    findFirst()返回第一个元素
    findAny()返回当前流中的任意元素
    count()返回流中元素总数
    max(Comparator c)返回流中最大值
    min(Comparator c)返回流中最小值
    forEach(Consumer c)内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。
    相反,Stream API 使用内部迭代——它帮你把迭代做了)

    2-归约

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

    备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。

    3-收集

    方 法描 述
    collect(Collector c)将流转换为其他形式。接收一个 Collector接口的实现,
    用于给Stream中元素做汇总的方法

    Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。

    另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

    方法返回类型作用
    toListCollector<T, ?, List>把流中元素收集到List
    List<Employee> emps= list.stream().collect(Collectors.toList());
    
    方法返回类型作用
    toSetCollector<T, ?, Set>把流中元素收集到Set
    Set<Employee> emps= list.stream().collect(Collectors.toSet());
    
    方法返回类型作用
    toCollectionCollector<T, ?, C>把流中元素收集到创建的集合
    Collection<Employee> emps =list.stream().collect(Collectors.toCollection(ArrayList::new));
    
    方法返回类型作用
    countingCollector<T, ?, Long>计算流中元素的个数
    long count = list.stream().collect(Collectors.counting());
    
    方法返回类型作用
    summingIntCollector<T, ?, Integer>对流中元素的整数属性求和
    int total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
    
    方法返回类型作用
    averagingIntCollector<T, ?, Double>计算流中元素Integer属性的平均值
    double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary));
    
    方法返回类型作用
    summarizingIntCollector<T, ?, IntSummaryStatistics>收集流中Integer属性的统计值。如:平均值
    int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
    
    方法返回类型作用
    joiningCollector<CharSequence, ?, String>连接流中每个字符串
    String str= list.stream().map(Employee::getName).collect(Collectors.joining());
    
    方法返回类型作用
    maxByCollector<T, ?, Optional>根据比较器选择最大值
    Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
    
    方法返回类型作用
    minByCollector<T, ?, Optional>根据比较器选择最小值
    Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
    
    方法返回类型作用
    reducingCollector<T, ?, Optional>从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值
    int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
    
    方法返回类型作用
    collectingAndThenCollector<T,A,RR>包裹另一个收集器,对其结果转换函数
    int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
    
    方法返回类型作用
    groupingByCollector<T, ?, Map<K, List>>根据某属性值对流分组,属性为K,结果为V
    Map<Emp.Status, List<Emp>> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus));
    
    方法返回类型作用
    partitioningByCollector<T, ?, Map<Boolean, List>>根据true或false进行分区
    Map<Boolean,List<Emp>> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage));
    

    举例:

    package com.atguigu.stream;
        
    import java.util.List;
    import java.util.Optional;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
        
    import org.junit.Test;
        
    public class StreamEndding {
        @Test
        public void test01(){
            Stream.of(1,2,3,4,5)
                    .forEach(System.out::println);
        }
        @Test
        public void test02(){
            long count = Stream.of(1,2,3,4,5)
                    .count();
            System.out.println("count = " + count);
        }
        @Test
        public void test03(){
            boolean result = Stream.of(1,3,5,7,9)
                    .allMatch(t -> t%2!=0);
            System.out.println(result);
        }
    	@Test
        public void test04(){
            boolean result = Stream.of(1,3,5,7,9)
                    .anyMatch(t -> t%2==0);
            System.out.println(result);
        }
    	@Test
        public void test05(){
            Optional<Integer> opt = Stream.of(1,3,5,7,9).findFirst();
            System.out.println(opt);
        }
    	@Test
        public void test06(){
            Optional<Integer> opt = Stream.of(1,2,3,4,5,7,9)
                    .filter(t -> t%3==0)
                    .findFirst();
            System.out.println(opt);
        }
    	@Test
        public void test07(){
            Optional<Integer> opt = Stream.of(1,2,4,5,7,8)
                    .filter(t -> t%3==0)
                    .findFirst();
            System.out.println(opt);
        }
        @Test
        public void test08(){
            Optional<Integer> max = Stream.of(1,2,4,5,7,8)
                    .max((t1,t2) -> Integer.compare(t1, t2));
            System.out.println(max);
        }
        @Test
        public void test09(){
            Integer reduce = Stream.of(1,2,4,5,7,8)
                    .reduce(0, (t1,t2) -> t1+t2);//BinaryOperator接口   T apply(T t1, T t2)
            System.out.println(reduce);
        }
        @Test
        public void test10(){
            Optional<Integer> max = Stream.of(1,2,4,5,7,8)
                    .reduce((t1,t2) -> t1>t2?t1:t2);//BinaryOperator接口   T apply(T t1, T t2)
            System.out.println(max);
        }
        @Test
        public void test11(){
            List<Integer> list = Stream.of(1,2,4,5,7,8)
                    .filter(t -> t%2==0)
                    .collect(Collectors.toList());
        
            System.out.println(list);
        }   
    }
    

    5.5 Java9新增API

    新增1:Stream实例化方法

    ofNullable()的使用:

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

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

6 JDK9-17新特性(下)

讲师:尚硅谷-宋红康(江湖人称:康师傅)

官网:http://www.atguigu.com


6. 新语法结构

新的语法结构,为我们勾勒出了 Java 语法进化的一个趋势,将开发者从复杂、繁琐的低层次抽象中逐渐解放出来,以更高层次、更优雅的抽象,既降低代码量,又避免意外编程错误的出现,进而提高代码质量和开发效率。

6.1 Java的REPL工具: jShell命令

JDK9的新特性

Java 终于拥有了像Python 和 Scala 之类语言的REPL工具(交互式编程环境,read - evaluate - print - loop):jShell。以交互式的方式对语句和表达式进行求值。即写即得快速运行

利用jShell在没有创建类的情况下,在命令行里直接声明变量,计算表达式,执行语句。无需跟人解释”public static void main(String[] args)”这句"废话"。

使用举例

  • 调出jShell

在这里插入图片描述

  • 获取帮助

在这里插入图片描述

  • 基本使用

在这里插入图片描述

  • 导入指定的包

在这里插入图片描述

  • 默认已经导入如下的所有包:(包含java.lang包)

在这里插入图片描述

  • 只需按下 Tab 键,就能自动补全代码

在这里插入图片描述

  • 列出当前 session 里所有有效的代码片段

在这里插入图片描述

  • 查看当前 session 下所有创建过的变量

在这里插入图片描述

  • 查看当前 session 下所有创建过的方法

在这里插入图片描述

Tips:我们还可以重新定义相同方法名和参数列表的方法,即对现有方法的修改(或覆盖)。

  • 使用外部代码编辑器来编写 Java 代码

在这里插入图片描述

从外部文件加载源代码【HelloWorld.java】

/**
 * Created by songhongkang
 */
public void printHello() {
    System.out.println("马上2023年了,尚硅谷祝所有的谷粉元旦快乐!");
}
printHello();

  • 使用/open命令调用

在这里插入图片描述

  • 退出jShell

在这里插入图片描述

6.2 异常处理之try-catch资源关闭

在JDK7 之前,我们这样处理资源的关闭:

@Test
public void test01() {
    FileWriter fw = null;
    BufferedWriter bw = null;
    try {
        fw = new FileWriter("d:/1.txt");
        bw = new BufferedWriter(fw);

        bw.write("hello");
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (bw != null) {
                bw.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            if (fw != null) {
                fw.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

JDK7的新特性

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

格式:

try(资源对象的声明和初始化){
    业务逻辑代码,可能会产生异常
}catch(异常类型1 e){
    处理异常代码
}catch(异常类型2 e){
    处理异常代码
}

说明:

1、在try()中声明的资源,无论是否发生异常,无论是否处理异常,都会自动关闭资源对象,不用手动关闭了。

2、这些资源实现类必须实现AutoCloseable或Closeable接口,实现其中的close()方法。Closeable是AutoCloseable的子接口。Java7几乎把所有的“资源类”(包括文件IO的各种类、JDBC编程的Connection、Statement等接口…)都进行了改写,改写后资源类都实现了AutoCloseable或Closeable接口,并实现了close()方法。

3、写到try()中的资源类的变量默认是final声明的,不能修改。

举例:

//举例1
@Test
public void test02() {
    try (
        FileWriter fw = new FileWriter("d:/1.txt");
        BufferedWriter bw = new BufferedWriter(fw);
    ) {
        bw.write("hello");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

//举例2
@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的新特性

try的前面可以定义流对象,try后面的()中可以直接引用流对象的名称。在try代码执行完毕后,流对象也可以释放掉,也不用写finally了。

格式:

A a = new A();
B b = new B();
try(a;b){
    可能产生的异常代码
}catch(异常类名 变量名){
    异常处理的逻辑
}

举例:

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

6.3 局部变量类型推断

JDK 10的新特性

局部变量的显示类型声明,常常被认为是不必须的,给一个好听的名字反而可以很清楚的表达出下面应该怎样继续。本新特性允许开发人员省略通常不必要的局部变量类型声明,以增强Java语言的体验性、可读性。

  • 使用举例
//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();

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

代码举例:

声明一个成员变量,并初始化值为null

在这里插入图片描述

声明一个数组变量,并为数组静态初始化(省略new的情况下)
在这里插入图片描述

没有初始化的方法内的局部变量声明
在这里插入图片描述

方法的返回值类型

在这里插入图片描述

方法的参数类型

在这里插入图片描述

构造器的参数类型
在这里插入图片描述

作为catch块中异常类型
在这里插入图片描述

Lambda表达式中函数式接口的类型

在这里插入图片描述

方法引用中函数式接口的类型

在这里插入图片描述

注意:

  • var不是一个关键字,而是一个类型名,将它作为变量的类型。不能使用var作为类名。

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

6.4 instanceof的模式匹配

JDK14中预览特性:

instanceof 模式匹配通过提供更为简便的语法,来提高生产力。有了该功能,可以减少Java程序中显式强制转换的数量,实现更精确、简洁的类型安全的代码。

Java 14之前旧写法:

if(obj instanceof String){
    String str = (String)obj; //需要强转
    .. str.contains(..)..
}else{
    ...
}

Java 14新特性写法:

if(obj instanceof String str){
    .. str.contains(..)..
}else{
    ...
}

举例:

/**
 * instanceof的模式匹配(预览)
 *
 * @author shkstart
 * @create 上午 11:32
 */
public class Feature01 {
    @Test
    public void test1(){

        Object obj = new String("hello,Java14");
        obj = null;//在使用null 匹配instanceof 时,返回都是false.
        if(obj instanceof String){
            String str = (String) obj;
            System.out.println(str.contains("Java"));
        }else{
            System.out.println("非String类型");
        }

        //举例1:
        if(obj instanceof String str){ //新特性:省去了强制类型转换的过程
            System.out.println(str.contains("Java"));
        }else{
            System.out.println("非String类型");
        }
    }
}

// 举例2
class InstanceOf{

    String str = "abc";

    public void test(Object obj){

        if(obj instanceof String str){//此时的str的作用域仅限于if结构内。
            System.out.println(str.toUpperCase());
        }else{
            System.out.println(str.toLowerCase());
        }

    }

}

//举例3:
class Monitor{
    private String model;
    private double price;

//    public boolean equals(Object o){
//        if(o instanceof Monitor other){
//            if(model.equals(other.model) && price == other.price){
//                return true;
//            }
//        }
//        return false;
//    }


    public boolean equals(Object o){
        return o instanceof Monitor other && model.equals(other.model) && price == other.price;
    }

}

JDK15中第二次预览:

没有任何更改。

JDK16中转正特性:

在Java16中转正。

6.5 switch表达式

传统switch声明语句的弊端:

  • 匹配是自上而下的,如果忘记写break,后面的case语句不论匹配与否都会执行; —>case穿透
  • 所有的case语句共用一个块范围,在不同的case语句定义的变量名不能重复;
  • 不能在一个case里写多个执行结果一致的条件;
  • 整个switch不能作为表达式返回值;
//常见错误实现
switch(month){
    case 3|4|5://3|4|5 用了位运算符,11 | 100 | 101结果是 1117
        System.out.println("春季");
        break;
    case 6|7|8://6|7|8用了位运算符,110 | 111 | 1000结果是111115
        System.out.println("夏季");
        break;
    case 9|10|11://9|10|11用了位运算符,1001 | 1010 | 1011结果是101111
        System.out.println("秋季");
        break;
    case 12|1|2://12|1|2 用了位运算符,1100 | 1 | 10 结果是1111,是15
        System.out.println("冬季");
        break;
    default:
        System.out.println("输入有误");
}

JDK12中预览特性:

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

  • 同时将多个 case 合并到一行,显得简洁、清晰,也更加优雅的表达逻辑分支。

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

举例:

Java 12之前

/**
 * @author shkstart
 * @create 下午 4:47
 */
public class SwitchTest {
    public static void main(String[] args) {
        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);

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

switch 语句如果漏写了一个 break,那么逻辑往往就跑偏了,这种方式既繁琐,又容易出错。

Java 12中:

/**
 * @author shkstart
 * @create 下午 10:38
 */
public class SwitchTest1 {
    public static void main(String[] args) {
        Fruit fruit = Fruit.GRAPE;
        switch(fruit){
            case PEAR -> System.out.println(4);
            case APPLE,MANGO,GRAPE -> System.out.println(5);
            case ORANGE,PAPAYA -> System.out.println(6);
            default -> throw new IllegalStateException("No Such Fruit:" + fruit);
        };
    }
}

更进一步:

/**
 * @author shkstart
 * @create 2019 下午 10:44
 */
public class SwitchTest2 {
    public static void main(String[] args) {
        Fruit fruit = Fruit.GRAPE;
        int 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中二次预览特性:

JDK13中引入了yield语句,用于返回值。这意味着,switch表达式(返回值)应该使用yield,switch语句(不返回值)应该使用break。

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

在以前:

@Test
public void testSwitch1(){
    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);
}

在JDK13中:

@Test
public void testSwitch2(){
    String x = "3";
    int i = switch (x) {
        case "1" -> 1;
        case "2" -> 2;
        default -> {
            yield 3;
        }
    };
    System.out.println(i);
}

或者

@Test
public void testSwitch3() {
    String x = "3";
    int i = switch (x) {
        case "1":
            yield 1;
        case "2":
            yield 2;
        default:
            yield 3;
    };
    System.out.println(i);
}

JDK14中转正特性:

这是JDK 12和JDK 13中的预览特性,现在是正式特性了。

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

旧写法:

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

模式匹配新写法:

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

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

6.6 文本块

现实问题:

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

JDK13的新特性

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

基本使用:

"""
line1
line2
line3
"""

相当于:

"line1\nline2\nline3\n"

或者一个连接的字符串:

"line1\n" +
"line2\n" +
"line3\n"

如果字符串末尾不需要行终止符,则结束分隔符可以放在最后一行内容上。例如:

"""
line1
line2
line3"""

相当于

"line1\nline2\nline3"

文本块可以表示空字符串,但不建议这样做,因为它需要两行源代码:

String empty = """
""";

举例1:普通文本

原有写法:

 String text1 = "The Sound of silence\n" +
                "Hello darkness, my old friend\n" +
                "I've come to talk with you again\n" +
                "Because a vision softly creeping\n" +
                "Left its seeds while I was sleeping\n" +
                "And the vision that was planted in my brain\n" +
                "Still remains\n" +
                "Within the sound of silence";

System.out.println(text1);

使用新特性:

String text2 = """
                The Sound of silence
                Hello darkness, my old friend
                I've come to talk with you again
                Because a vision softly creeping
                Left its seeds while I was sleeping
                And the vision that was planted in my brain
                Still remains
                Within the sound of silence
                """;
System.out.println(text2);

举例2:HTML语句

<html>
  <body>
      <p>Hello, 尚硅谷</p>
  </body>
</html>

将其复制到Java的字符串中,会展示成以下内容:

"<html>\n" +
"    <body>\n" +
"        <p>Hello, 尚硅谷</p>\n" +
"    </body>\n" +
"</html>\n";

即被自动进行了转义,这样的字符串看起来不是很直观,在JDK 13中:

"""
<html>
  <body>
      <p>Hello, world</p>
  </body>
</html>
""";

举例3:SQL语句

select employee_id,last_name,salary,department_id
from employees
where department_id in (40,50,60)
order by department_id asc

原有方式:

String sql = "SELECT id,NAME,email\n" +
                "FROM customers\n" +
                "WHERE id > 4\n" +
                "ORDER BY email DESC";

使用新特性:

String sql1 = """
                SELECT id,NAME,email
                FROM customers
                WHERE id > 4
                ORDER BY email DESC
                """;

举例4:JSON字符串

原有方式:

String myJson = "{\n" +
                "    \"name\":\"Song Hongkang\",\n" +
                "     \"address\":\"www.atguigu.com\",\n" +
                "    \"email\":\"shkstart@126.com\"\n" +
                "}";
System.out.println(myJson);

使用新特性:

String myJson1 = """
                {
                    "name":"Song Hongkang",
                     "address":"www.atguigu.com",
                    "email":"shkstart@126.com"
                }""";
System.out.println(myJson1);

JDK14中二次预览特性

JDK14的版本主要增加了两个escape sequences,分别是 \ <line-terminator>\s escape sequence

举例:

/**
 * @author shkstart
 * @create 下午 7:13
 */
public class Feature05 {
    //jdk14新特性
    @Test
    public void test5(){
        String sql1 = """
                SELECT id,NAME,email
                FROM customers
                WHERE id > 4
                ORDER BY email DESC
                """;
        System.out.println(sql1);

        // \:取消换行操作
        // \s:表示一个空格
        String sql2 = """
                SELECT id,NAME,email \
                FROM customers\s\
                WHERE id > 4 \
                ORDER BY email DESC
                """;
        System.out.println(sql2);
    }
}

JDK15中功能转正

6.7 Record

背景

早在2019年2月份,Java 语言架构师 Brian Goetz,曾写文抱怨“Java太啰嗦”或有太多的“繁文缛节”。他提到:开发人员想要创建纯数据载体类(plain data carriers)通常都必须编写大量低价值、重复的、容易出错的代码。如:构造函数、getter/setter、equals()、hashCode()以及toString()等。

以至于很多人选择使用IDE的功能来自动生成这些代码。还有一些开发会选择使用一些第三方类库,如Lombok等来生成这些方法。

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

record 是一种全新的类型,它本质上是一个 final 类,同时所有的属性都是 final 修饰,它会自动编译出 public gethashcodeequalstoString、构造器等结构,减少了代码编写量。

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

  • 获取成员变量的简单方法,比如例题中的 name() 和 partner() 。注意区别于我们平常getter()的写法。
  • 一个 equals 方法的实现,执行比较时会比较该类的所有成员属性。
  • 重写 hashCode() 方法。
  • 一个可以打印该类所有成员属性的 toString() 方法。
  • 只有一个构造方法。

此外:

  • 还可以在record声明的类中定义静态字段、静态方法、构造器或实例方法。

  • 不能在record声明的类中定义实例字段;类不能声明为abstract;不能声明显式的父类等。

举例1(旧写法):

class Point {
    private final int x;
    private final int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    int x() {
        return x;
    }

    int y() {
        return y;
    }

    public boolean equals(Object o) {
        if (!(o instanceof Point)) return false;
        Point other = (Point) o;
        return other.x == x && other.y == y;
    }

    public int hashCode() {
        return Objects.hash(x, y);
    }

    @Override
    public String toString() {
        return "Point{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

举例1(新写法):

record Point(int x, int y) { }

举例1:

public record Dog(String name, Integer age) {
}
public class Java14Record {

    public static void main(String[] args) {
        Dog dog1 = new Dog("牧羊犬", 1);
        Dog dog2 = new Dog("田园犬", 2);
        Dog dog3 = new Dog("哈士奇", 3);
        System.out.println(dog1);
        System.out.println(dog2);
        System.out.println(dog3);
    }
}

举例2:

/**
 *  Record类型的演示
 *
 * @author shkstart
 * @create 下午 6:13
 */
public class Feature07 {
    @Test
    public void test1(){
        //测试构造器
        Person p1 = new Person("罗密欧",new Person("zhuliye",null));
        //测试toString()
        System.out.println(p1);
        //测试equals():
        Person p2 = new Person("罗密欧",new Person("zhuliye",null));
        System.out.println(p1.equals(p2));

        //测试hashCode()和equals()
        HashSet<Person> set = new HashSet<>();
        set.add(p1);
        set.add(p2);

        for (Person person : set) {
            System.out.println(person);
        }

        //测试name()和partner():类似于getName()和getPartner()
        System.out.println(p1.name());
        System.out.println(p1.partner());

    }

    @Test
    public void test2(){
        Person p1 = new Person("zhuyingtai");

        System.out.println(p1.getNameInUpperCase());

        Person.nation = "CHN";
        System.out.println(Person.showNation());

    }
}
/**
 * @author shkstart
 * @create 下午 6:20
 */
public record Person(String name,Person partner) {

    //还可以声明静态的属性、静态的方法、构造器、实例方法

    public static String nation;

    public static String showNation(){
        return nation;
    }

    public Person(String name){
        this(name,null);
    }

    public String getNameInUpperCase(){
        return name.toUpperCase();
    }
    //不可以声明非静态的属性
//    private int id;//报错
}

//不可以将record定义的类声明为abstract的
//abstract record Order(){
//
//}

//不可以给record定义的类声明显式的父类(非Record类)
//record Order() extends Thread{
//
//}

JDK15中第二次预览特性

JDK16中转正特性

最终到JDK16中转正。

记录不适合哪些场景

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

6.8 密封类

背景:

在 Java 中如果想让一个类不能被继承和修改,这时我们应该使用 final 关键字对类进行修饰。不过这种要么可以继承,要么不能继承的机制不够灵活,有些时候我们可能想让某个类可以被某些类型继承,但是又不能随意继承,是做不到的。Java 15 尝试解决这个问题,引入了 sealed 类,被 sealed 修饰的类可以指定子类。这样这个类就只能被指定的类继承。

JDK15的预览特性:

通过密封的类和接口来限制超类的使用,密封的类和接口限制其它可能继承或实现它们的其它类或接口。

具体使用:

  • 使用修饰符sealed,可以将一个类声明为密封类。密封的类使用保留关键字permits列出可以直接扩展(即extends)它的类。

  • sealed 修饰的类的机制具有传递性,它的子类必须使用指定的关键字进行修饰,且只能是 finalsealednon-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表示可以允许任何类继承

JDK16二次预览特性

JDK17中转正特性

7. API的变化

7.1 Optional类

JDK8的新特性

到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google在著名的Guava项目引入了Optional类,通过检查空值的方式避免空指针异常。受到Google的启发,Optional类已经成为Java 8类库的一部分。

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

Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

  • 创建Optional类对象的方法:

  • static Optional empty() :用来创建一个空的Optional实例

    • static Optional of(T value) :用来创建一个Optional实例,value必须非空
    • static <T> Optional<T> ofNullable(T value) :用来创建一个Optional实例,value可能是空,也可能非空
  • 判断Optional容器中是否包含对象:

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

  • T get(): 如果调用对象包含值,返回该值。否则抛异常。T get()与of(T value)配合使用

  • T orElse(T other) :orElse(T other) 与ofNullable(T value)配合使用,如果Optional容器中非空,就返回所包装值,如果为空,就用orElse(T other)other指定的默认值(备胎)代替

  • T orElseGet(Supplier<? extends T> other) :如果Optional容器中非空,就返回所包装值,如果为空,就用Supplier接口的Lambda表达式提供的值代替

  • T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果Optional容器中非空,就返回所包装值,如果为空,就抛出你指定的异常类型代替原来的NoSuchElementException

举例:

package com.atguigu.optional;

import java.util.Optional;

import org.junit.Test;

public class TestOptional {
	@Test
    public void test1(){
        String str = "hello";
        Optional<String> opt = Optional.of(str);
        System.out.println(opt);
    }
    @Test
    public void test2(){
        Optional<String> opt = Optional.empty();
        System.out.println(opt);
    }
    @Test
    public void test3(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);
        System.out.println(opt);
    }
    @Test
    public void test4(){
        String str = "hello";
        Optional<String> opt = Optional.of(str);

        String string = opt.get();
        System.out.println(string);
    }
    @Test
    public void test5(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);
//		System.out.println(opt.get());//java.util.NoSuchElementException: No value present
    }
    @Test
    public void test6(){
        String str = "hello";
        Optional<String> opt = Optional.ofNullable(str);
        String string = opt.orElse("atguigu");
        System.out.println(string);
    }
    @Test
    public void test7(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);
        String string = opt.orElseGet(String::new);
        System.out.println(string);
    }
    @Test
    public void test8(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);
        String string = opt.orElseThrow(()->new RuntimeException("值不存在"));
        System.out.println(string);
    }
    @Test
    public void test9(){
        String str = "Hello1";
        Optional<String> opt = Optional.ofNullable(str);
        //判断是否是纯字母单词,如果是,转为大写,否则保持不变
        String result = opt.filter(s->s.matches("[a-zA-Z]+"))
                .map(s -> s.toUpperCase()).orElse(str);
        System.out.println(result);
    }
}

这是JDK9-11的新特性

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

7.2 String存储结构和API变更

这是JDK9的新特性。

产生背景:

Motivation
The current implementation of the String class stores characters in a char array, using two bytes (sixteen bits) for each character. Data gathered from many different applications indicates that strings are a major component of heap usage and, moreover, that most String objects contain only Latin-1 characters. Such characters require only one byte of storage, hence half of the space in the internal char arrays of such String objects is going unused.

使用说明:

Description

We propose to change the internal representation of the String class from a UTF-16 char array to a byte array plus an encoding-flag field. The new String class will store characters encoded either as ISO-8859-1/Latin-1 (one byte per character), or as UTF-16 (two bytes per character), based upon the contents of the string. The encoding flag will indicate which encoding is used.

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

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    @Stable
    private final byte[] value;
	...
}

拓展:StringBuffer 与 StringBuilder

那StringBuffer 和 StringBuilder 是否仍无动于衷呢?

String-related classes such as AbstractStringBuilder, StringBuilder, and StringBuffer will be updated to use the same representation, as will the HotSpot VM’s intrinsic string operations.

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

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

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

String源码:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence,Constable, ConstantDesc {

java.lang.constant.Constable接口定义了抽象方法:

public interface Constable {
	Optional<? extends ConstantDesc> describeConstable();
}

Java 12 String 的实现源码:

/**
 * Returns an {@link Optional} containing the nominal descriptor for this
 * instance, which is the instance itself.
 *
 * @return an {@link Optional} describing the {@linkplain String} instance
 * @since 12
 */
@Override
public Optional<String> describeConstable() {
	return Optional.of(this);
}

很简单,其实就是调用 Optional.of 方法返回一个 Optional 类型。

举例:

private static void testDescribeConstable() {
	String name = "尚硅谷Java高级工程师";
	Optional<String> optional = name.describeConstable();
	System.out.println(optional.get());
}

结果输出:

尚硅谷Java高级工程师

JDK12新特性:String新增方法

String的transform(Function)

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

或者

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

对应的源码:

/**
* This method allows the application of a function to {@code this}
* string. The function should expect a single String argument
* and produce an {@code R} result.
* @since 12
*/
public <R> R transform(Function<? super String, ? extends R> f) {
 return f.apply(this);
}

在某种情况下,该方法应该被称为map()。

举例:

private static void testTransform() {
	System.out.println("======test java 12 transform======");
	List<String> list1 = List.of("Java", " Python", " C++ ");
	List<String> list2 = new ArrayList<>();
	list1.forEach(element -> list2.add(element.transform(String::strip)
								  .transform(String::toUpperCase)
								  .transform((e) -> "Hi," + e))
				 );
	list2.forEach(System.out::println);
}

结果输出:

======test java 12 transform======
Hi,JAVA
Hi,PYTHON
Hi,C++

如果使用Java 8的Stream特性,可以如下实现:

private static void testTransform1() {
        System.out.println("======test before java 12 ======");
        List<String> list1 = List.of("Java  ", " Python", " C++ ");

        Stream<String> stringStream = list1.stream().map(element -> element.strip()).map(String::toUpperCase).map(element -> "Hello," + element);
        List<String> list2 = stringStream.collect(Collectors.toList());
        list2.forEach(System.out::println);
    }

7.3 JDK17:标记删除Applet API

Applet API 提供了一种将 Java AWT/Swing 控件嵌入到浏览器网页中的方法。不过,目前 Applet 已经被淘汰。大部分人可能压根就没有用过 Applet。

Applet API 实际上是无用的,因为所有 Web 浏览器供应商都已删除或透露计划放弃对 Java 浏览器插件的支持。Java 9 的时候,Applet API 已经被标记为过时,Java 17 的时候终于标记为删除了。

具体如下:

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

8. 其它结构变化

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

在java 8 中,标识符可以独立使用“_”来命名:

String _ = "hello";
System.out.println(_);

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

在这里插入图片描述

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

看下面的代码。

// 编译
javac JavaStack.java

// 运行
java JavaStack

我们的认知里,要运行一个 Java 源代码必须先编译,再运行。而在 Java 11 版本中,通过一个 java 命令就直接搞定了,如下所示:

java JavaStack.java

注意点:

  • 执行源文件中的第一个类,第一个类必须包含主方法。

8.3 GC方面新特性

GC是Java主要优势之一。 然而,当GC停顿太长,就会开始影响应用的响应时间。随着现代系统中内存不断增长,用户和程序员希望JVM能够以高效的方式充分利用这些内存, 并且无需长时间的GC暂停时间。

8.3.1 G1 GC

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

JDK10 : 为G1提供并行的Full GC

G1最大的亮点就是可以尽量的避免full gc。但毕竟是“尽量”,在有些情况下,G1就要进行full gc了,比如如果它无法足够快的回收内存的时候,它就会强制停止所有的应用线程然后清理。

在Java10之前,一个单线程版的标记-清除-压缩算法被用于full gc。为了尽量减少full gc带来的影响,在Java10中,就把之前的那个单线程版的标记-清除-压缩的full gc算法改成了支持多个线程同时full gc。这样也算是减少了full gc所带来的停顿,从而提高性能。

你可以通过-XX:ParallelGCThreads参数来指定用于并行GC的线程数。

JDK12:可中断的 G1 Mixed GC

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

8.3.2 Shenandoah GC

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

在这里插入图片描述

Shenandoah 垃圾回收器是 Red Hat 在 2014 年宣布进行的一项垃圾收集器研究项目 Pauseless GC 的实现,旨在针对 JVM 上的内存收回实现低停顿的需求

据 Red Hat 研发 Shenandoah 团队对外宣称,Shenandoah 垃圾回收器的暂停时间与堆大小无关,这意味着无论将堆设置为 200 MB 还是 200 GB,都将拥有一致的系统暂停时间,不过实际使用性能将取决于实际工作堆的大小和工作负载。

Shenandoah GC 主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等。

这是一个实验性功能,不包含在默认(Oracle)的OpenJDK版本中。

Shenandoah开发团队在实际应用中的测试数据:

在这里插入图片描述

JDK15:Shenandoah垃圾回收算法转正

Shenandoah垃圾回收算法终于从实验特性转变为产品特性,这是一个从 JDK 12 引入的回收算法,该算法通过与正在运行的 Java 线程同时进行疏散工作来减少 GC 暂停时间。Shenandoah 的暂停时间与堆大小无关,无论堆栈是 200 MB 还是 200 GB,都具有相同的一致暂停时间。

Shenandoah在JDK12被作为experimental引入,在JDK15变为Production;之前需要通过-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC来启用,现在只需要-XX:+UseShenandoahGC即可启用

8.3.3 革命性的 ZGC

JDK11:引入革命性的 ZGC

ZGC,这应该是JDK11最为瞩目的特性,没有之一。

ZGC是一个并发、基于region、压缩型的垃圾收集器。

ZGC的设计目标是:支持TB级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%。 将来还可以扩展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于DRAM和冷对象置于NVMe闪存),或压缩堆。

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

JDK14:ZGC on macOS和windows

  • JDK14之前,ZGC仅Linux才支持。现在mac或Windows上也能使用ZGC了,示例如下:

    -XX:+UnlockExperimentalVMOptions -XX:+UseZGC
    
  • ZGC与Shenandoah目标高度相似,在尽可能对吞吐量影响不大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟。

在这里插入图片描述

在这里插入图片描述

JDK15:ZGC 功能转正

ZGC是Java 11引入的新的垃圾收集器,经过了多个实验阶段,自此终于成为正式特性。

但是这并不是替换默认的GC,默认的GC仍然还是G1;之前需要通过-XX:+UnlockExperimentalVMOptions -XX:+UseZGC来启用ZGC,现在只需要-XX:+UseZGC就可以。相信不久的将来它必将成为默认的垃圾回收器。

ZGC的性能已经相当亮眼,用“令人震惊、革命性”来形容,不为过。未来将成为服务端、大内存、低延迟应用的首选垃圾收集器。

怎么形容Shenandoah和ZGC的关系呢?异同点大概如下:

  • 相同点:性能几乎可认为是相同的
  • 不同点:ZGC是Oracle JDK的,根正苗红。而Shenandoah只存在于OpenJDK中,因此使用时需注意你的JDK版本

JDK16:ZGC 并发线程处理

在线程的堆栈处理过程中,总有一个制约因素就是safepoints。在safepoints这个点,Java的线程是要暂停执行的,从而限制了GC的效率。

回顾:

我们都知道,在之前,需要 GC 的时候,为了进行垃圾回收,需要所有的线程都暂停下来,这个暂停的时间我们称为 Stop The World

而为了实现 STW 这个操作, JVM 需要为每个线程选择一个点停止运行,这个点就叫做安全点(Safepoints)

而ZGC的并发线程堆栈处理可以保证Java线程可以在GC safepoints的同时可以并发执行。它有助于提高所开发的Java软件应用程序的性能和效率。

9. 小结与展望

随着云计算和 AI 等技术浪潮,当前的计算模式和场景正在发生翻天覆地的变化,不仅对 Java 的发展速度提出了更高要求,也深刻影响着 Java 技术的发展方向。传统的大型企业或互联网应用,正在被云端、容器化应用、模块化的微服务甚至是函数(FaaS, Function-as-a-Service)所替代。

**Java 需要在新的计算场景下,改进开发效率。**比如,Java 代码虽然进行了一些类型推断等改进,更易用的集合 API 等,但仍然给开发者留下了过于刻板、形式主义的印象,这是一个长期的改进方向。

Java虽然标榜面向对象编程,却毫不顾忌的加入面向接口编程思想,又扯出匿名对象的概念,每增加一个新的东西,对Java的根本(面向对象思想)的一次冲击。

士,不可不弘毅,任重而道远。

oah 团队对外宣称,Shenandoah 垃圾回收器的暂停时间与堆大小无关,这意味着无论将堆设置为 200 MB 还是 200 GB,都将拥有一致的系统暂停时间,不过实际使用性能将取决于实际工作堆的大小和工作负载。

Shenandoah GC 主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等。

这是一个实验性功能,不包含在默认(Oracle)的OpenJDK版本中。

Shenandoah开发团队在实际应用中的测试数据:

[外链图片转存中…(img-eZttex5Z-1759220481220)]

JDK15:Shenandoah垃圾回收算法转正

Shenandoah垃圾回收算法终于从实验特性转变为产品特性,这是一个从 JDK 12 引入的回收算法,该算法通过与正在运行的 Java 线程同时进行疏散工作来减少 GC 暂停时间。Shenandoah 的暂停时间与堆大小无关,无论堆栈是 200 MB 还是 200 GB,都具有相同的一致暂停时间。

Shenandoah在JDK12被作为experimental引入,在JDK15变为Production;之前需要通过-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC来启用,现在只需要-XX:+UseShenandoahGC即可启用

8.3.3 革命性的 ZGC

JDK11:引入革命性的 ZGC

ZGC,这应该是JDK11最为瞩目的特性,没有之一。

ZGC是一个并发、基于region、压缩型的垃圾收集器。

ZGC的设计目标是:支持TB级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%。 将来还可以扩展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于DRAM和冷对象置于NVMe闪存),或压缩堆。

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

JDK14:ZGC on macOS和windows

  • JDK14之前,ZGC仅Linux才支持。现在mac或Windows上也能使用ZGC了,示例如下:

    -XX:+UnlockExperimentalVMOptions -XX:+UseZGC
    
  • ZGC与Shenandoah目标高度相似,在尽可能对吞吐量影响不大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟。

[外链图片转存中…(img-Vm7lu7HY-1759220481220)]

[外链图片转存中…(img-CE3tSW3n-1759220481220)]

JDK15:ZGC 功能转正

ZGC是Java 11引入的新的垃圾收集器,经过了多个实验阶段,自此终于成为正式特性。

但是这并不是替换默认的GC,默认的GC仍然还是G1;之前需要通过-XX:+UnlockExperimentalVMOptions -XX:+UseZGC来启用ZGC,现在只需要-XX:+UseZGC就可以。相信不久的将来它必将成为默认的垃圾回收器。

ZGC的性能已经相当亮眼,用“令人震惊、革命性”来形容,不为过。未来将成为服务端、大内存、低延迟应用的首选垃圾收集器。

怎么形容Shenandoah和ZGC的关系呢?异同点大概如下:

  • 相同点:性能几乎可认为是相同的
  • 不同点:ZGC是Oracle JDK的,根正苗红。而Shenandoah只存在于OpenJDK中,因此使用时需注意你的JDK版本

JDK16:ZGC 并发线程处理

在线程的堆栈处理过程中,总有一个制约因素就是safepoints。在safepoints这个点,Java的线程是要暂停执行的,从而限制了GC的效率。

回顾:

我们都知道,在之前,需要 GC 的时候,为了进行垃圾回收,需要所有的线程都暂停下来,这个暂停的时间我们称为 Stop The World

而为了实现 STW 这个操作, JVM 需要为每个线程选择一个点停止运行,这个点就叫做安全点(Safepoints)

而ZGC的并发线程堆栈处理可以保证Java线程可以在GC safepoints的同时可以并发执行。它有助于提高所开发的Java软件应用程序的性能和效率。

9. 小结与展望

随着云计算和 AI 等技术浪潮,当前的计算模式和场景正在发生翻天覆地的变化,不仅对 Java 的发展速度提出了更高要求,也深刻影响着 Java 技术的发展方向。传统的大型企业或互联网应用,正在被云端、容器化应用、模块化的微服务甚至是函数(FaaS, Function-as-a-Service)所替代。

**Java 需要在新的计算场景下,改进开发效率。**比如,Java 代码虽然进行了一些类型推断等改进,更易用的集合 API 等,但仍然给开发者留下了过于刻板、形式主义的印象,这是一个长期的改进方向。

Java虽然标榜面向对象编程,却毫不顾忌的加入面向接口编程思想,又扯出匿名对象的概念,每增加一个新的东西,对Java的根本(面向对象思想)的一次冲击。

士,不可不弘毅,任重而道远。

在 Windows 7 系统上配置 Java 运行环境需要设置环境变量,以便操作系统能够识别 Java 命令。以下是详细的配置步骤: ### 配置步骤 #### 1. 安装 JDK 首先,需要安装 Java 开发工具包(JDK)。可以从 Oracle 官网下载 JDK 安装包,下载地址为 [http://www.oracle.com/technetwork/java/javase/downloads/index-jsp-138363.html#javasejdk](http://www.oracle.com/technetwork/java/javase/downloads/index-jsp-138363.html#javasejdk)。选择适合 Windows 7 的 JDK 版本进行安装。 #### 2. 设置 `JAVA_HOME` 环境变量 安装完成后,需要设置 `JAVA_HOME` 环境变量,该变量指向 JDK 的安装目录。 - 右键点击“计算机”,选择“属性”。 - 点击“高级系统设置”。 - 在“高级”选项卡中,点击“环境变量”。 - 在“系统变量”部分,点击“新建”。 - 变量名:`JAVA_HOME` - 变量值:JDK 的安装路径,例如 `C:\Java\jdk1.8.0_121`。 #### 3. 配置 `Path` 环境变量 接下来,需要将 JDK 的 `bin` 目录添加到系统的 `Path` 环境变量中,以便在命令行中可以直接运行 Java 工具。 - 在“系统变量”部分,找到 `Path` 变量,点击“编辑”。 - 在变量值的末尾添加以下内容(假设 JDK 安装路径为 `C:\Java\jdk1.8.0_121`): ``` ;C:\Java\jdk1.8.0_121\bin;C:\Java\jdk1.8.0_121\jre\bin ``` #### 4. 配置 `CLASSPATH` 环境变量 `CLASSPATH` 环境变量用于指定 Java 类库的路径。 - 在“系统变量”部分,点击“新建”。 - 变量名:`CLASSPATH` - 变量值:`. ;C:\Java\jdk1.8.0_121\lib\dt.jar;C:\Java\jdk1.8.0_121\lib\tools.jar`(注意 `.` 表示当前目录,必须包含在内)。 #### 5. 验证配置 配置完成后,可以通过命令行验证环境变量是否设置成功。 - 按下 `Win + R` 键,输入 `cmd` 并回车。 - 在命令行中输入以下命令: ``` java -version ``` 如果显示类似以下信息,说明 Java 环境变量配置成功: ``` java version "1.8.0_121" ``` ### 示例代码 以下是一个简单的 Java 程序,用于测试 Java 环境是否配置成功。 ```java public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } } ``` - 将上述代码保存为 `HelloWorld.java`。 - 在命令行中,进入 `HelloWorld.java` 所在目录,执行以下命令: ``` javac HelloWorld.java java HelloWorld ``` 如果输出 `Hello, World!`,说明 Java 环境配置成功。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值