在线视频:尚硅谷2024最新Java入门视频教程(下部)-JDK新特性 课程资料:尚硅谷2024新版Java基础 随堂代码:https://gitee.com/an_shiguang/learn-java
重点内容: Lambda表达式 Stream流
Lambda表达式 函数式编程思想和Lambda表达式定义格式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1. 面向对象思想:是Java的核心编程思想 强调的是找对象,帮我们做事儿 比如:去北京 -> 强调的是怎么去,火车,高铁,飞机,汽车,自行车,腿儿 2. jdk8开始又了个新的思想:函数式编程思想: 强调的是结果,不强调过程 比如:去北京 -> 只强调去了还是没去 3. Lambda表达式: 可推导的就是可省略的 a.定义格式: ()->{} b.各部分解释: () : 重写方法的参数位置 -> : 将参数传递到方法体中 {} : 重写方法的方法体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Demo01Lambda { public static void main (String[] args) { new Thread (new Runnable () { @Override public void run () { System.out.println("我执行了" ); } }).start(); System.out.println("============" ); new Thread (()-> System.out.println("我执行了" )).start(); ArrayList<String> list = new ArrayList <>(); } }
Lambda表达式使用前提 1 2 3 1. 必须是函数式接口做方法参数传递2. 啥叫函数式接口: 有且只有一个抽象方法的接口,用@FunctionalInterface 去检测
1 2 3 4 @FunctionalInterface public interface USB { void open () ; }
Lambda表达式省略规则 1 2 3 4 5 6 7 8 9 10 11 12 13 1. Lambda表达式怎么写(涛哥给的新手秘籍) a.观察是否是函数式接口做方法参数传递 b.如果是,考虑使用Lambda表达式 c.调用方法,以匿名内部类的形式传递实参 d.从new 接口开始到重写方法的方法名结束,选中,删除,别忘记再删除一个右半个大括号 e.在重写方法的参数后面,方法体的大括号前面加上 -> 2. 省略规则: a.重写方法的参数类型可以干掉 b.如果重写方法只有一个参数,所在的小括号可以干掉 c.如果方法体中只有一句话,那么所在的大括号以及分号可以干掉 d.如果方法体中只有一句话并且带return 的,那么所在的大括号,分号以及return 可以干掉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class Person { private String name; private Integer age; public Person () { } public Person (String name, Integer age) { this .name = name; this .age = age; } public String getName () { return name; } public void setName (String name) { this .name = name; } public Integer getAge () { return age; } public void setAge (Integer age) { this .age = age; } @Override public String toString () { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class Demo03Lambda { public static void main (String[] args) { ArrayList<Person> list = new ArrayList <>(); list.add(new Person ("张三" ,10 )); list.add(new Person ("李四" ,8 )); list.add(new Person ("王五" ,9 )); System.out.println("=============Lambda==========" ); System.out.println("===========Lambda表达式简化形式==========" ); Collections.sort(list,(o1, o2)-> o1.getAge()-o2.getAge()); System.out.println(list); } }
函数式接口 1 2 3 4 1. 函数式接口: 有且只有一个抽象方法的接口 2. 检测: @FunctionalInterface
1 2 3 4 5 @FunctionalInterface public interface USB { void open (String s) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Test01 { public static void main (String[] args) { method(new USB () { @Override public void open (String s) { System.out.println(s+"开启了" ); } }); System.out.println("=====Lambda====" ); method((String s)->{ System.out.println(s+"开启了" ); }); System.out.println("=====Lambda简化版====" ); method(s-> System.out.println(s+"开启了" )); } public static void method (USB usb) { usb.open("鼠标" ); } }
Supplier 1 2 3 4 5 6 7 8 9 10 11 12 1. Supplier接口 java.util.function.Supplier<T>接口,它意味着"供给" ->我们想要什么就给什么 2. 方法: T get () -> 我们想要什么,get方法就可以返回什么 3. 需求: 使用Supplier接口作为方法的参数 用Lambda表达式求出int 数组中的最大值 4. 泛型: <引用数据类型>-> 规定了我们操作的数据是什么类型 <>中只能写引用数据类型,不能写基本数据类型
基本类型
包装类
byte
Byte
short
Short
int
Integer
long
Long
float
Float
double
Double
char
Character
boolean
Boolean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Demo01Supplier { public static void main (String[] args) { method(new Supplier <Integer>() { @Override public Integer get () { int [] arr = {4 ,3 ,4 ,6 ,7 }; Arrays.sort(arr); return arr[arr.length-1 ]; } }); System.out.println("==================" ); method(()-> { int [] arr = {4 ,3 ,4 ,6 ,7 }; Arrays.sort(arr); return arr[arr.length-1 ]; }); } public static void method (Supplier<Integer> supplier) { Integer max = supplier.get(); System.out.println("max = " + max); } }
Consumer 1 2 3 4 5 java.util.function.Consumer<T>->消费型接口->操作 方法: void accept (T t) ,意为消费一个指定泛型的数据 "消费" 就是"操作" ,至于怎么操作,就看重写accept方法之后,方法体怎么写了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Demo02Consumer { public static void main (String[] args) { method(new Consumer <String>() { @Override public void accept (String s) { System.out.println(s.length()); } },"abcdefg" ); System.out.println("==================" ); method(s-> System.out.println(s.length()),"abcdefg" ); } public static void method (Consumer<String> consumer,String s) { consumer.accept(s); } }
Function 1 2 3 java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据 方法: R apply (T t) 根据类型T参数获取类型R的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Demo03Function { public static void main (String[] args) { method(new Function <Integer, String>() { @Override public String apply (Integer integer) { return integer+"" ; } },100 ); System.out.println("===================" ); method(integer -> integer+"" ,200 ); } public static void method (Function<Integer,String> function,Integer number) { String s = function.apply(number); System.out.println("s = " + (s+1 )); } }
Predicate 1 2 java.util.function.Predicate<T>接口。->判断型接口 boolean test (T t) ->用于判断的方法,返回值为boolean 型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Demo04Predicate { public static void main (String[] args) { method(new Predicate <String>() { @Override public boolean test (String s) { return s.length()==7 ; } },"abcdefg" ); System.out.println("===================" ); method(s -> s.length()==7 ,"abcd" ); } public static void method (Predicate<String> predicate,String s) { boolean test = predicate.test(s); System.out.println("test = " + test); } }
Stream流 1 1. Stream流中的"流" 不是特指"IO流" ,它是一种"流式编程" (编程方式),可以看做是"流水线"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public class Demo01Stream { public static void main (String[] args) { ArrayList<String> list = new ArrayList <>(); list.add("张无忌" ); list.add("张三丰" ); list.add("张大彪" ); list.add("吕不韦" ); list.add("张三" ); list.add("赵姬" ); list.add("张翠山" ); list.add("嫪毐" ); Stream<String> stream = list.stream(); System.out.println("======================" ); stream.filter(s -> s.startsWith("张" )).filter(s -> s.length()==3 ).forEach(s -> System.out.println(s)); } }
Stream的获取 1 2 3 4 5 1. 针对集合:Collection中的方法 Stream<E> stream () 2. 针对数组:Stream接口中的静态方法: static <T> Stream<T> of (T... values)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Demo02Stream { public static void main (String[] args) { ArrayList<String> list = new ArrayList <>(); list.add("张三" ); list.add("李四" ); list.add("王五" ); Stream<String> stream = list.stream(); System.out.println(stream); Stream<String> stream1 = Stream.of("金莲" , "三上" , "松下" ); System.out.println(stream1); } }
Stream的方法 Stream中的forEach方法: void forEach(Consumer<? super T> action); 1 2 3 4 forEach : 逐一处理->遍历 void forEach (Consumer<? super T> action) ;注意:forEach方法是一个[终结方法],使用完之后,Stream流不能用了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private static void foreach () { Stream<String> stream1 = Stream.of("金莲" , "三上" , "松下" ); System.out.println("==============" ); stream1.forEach(s-> System.out.println(s)); }
Stream中的long count()方法 1 2 1. 作用:统计元素个数2. 注意:count也是一个终结方法
1 2 3 4 5 6 7 8 private static void count () { Stream<String> stream1 = Stream.of("金莲" , "三上" , "松下" ,"柳岩" ); long count = stream1.count(); System.out.println("count = " + count); }
Stream中的Stream<T> filter(Predicate<? super T> predicate)方法 1 2 1. 方法:Stream<T> filter (Predicate<? super T> predicate) 方法,返回一个新的Stream流对象2. 作用:根据某个条件进行元素过滤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private static void filter () { Stream<String> stream1 = Stream.of("金莲" , "三上" , "松下" ,"柳岩" ,"张无忌" ); System.out.println("============" ); stream1.filter(s -> s.length()==2 ).forEach(s -> System.out.println(s)); }
Stream<T> limit(long maxSize):获取Stream流对象中的前n个元素,返回一个新的Stream流对象 1 1. Stream<T> limit (long maxSize) :获取Stream流对象中的前n个元素,返回一个新的Stream流对象
1 2 3 4 5 6 7 private static void limit () { Stream<String> stream1 = Stream.of("金莲" , "三上" , "松下" ,"柳岩" ,"张无忌" ); stream1.limit(3 ).forEach(s -> System.out.println(s)); }
Stream<T> skip(long n): 跳过Stream流对象中的前n个元素,返回一个新的Stream流对象 1 Stream<T> skip (long n) : 跳过Stream流对象中的前n个元素,返回一个新的Stream流对象
1 2 3 4 5 6 7 private static void skip () { Stream<String> stream1 = Stream.of("金莲" , "三上" , "松下" ,"柳岩" ,"张无忌" ); stream1.skip(2 ).forEach(s -> System.out.println(s)); }
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b):两个流合成一个流 1 1. 方法:static <T> Stream<T> concat (Stream<? extends T> a, Stream<? extends T> b) :两个流合成一个流
1 2 3 4 5 6 7 8 9 10 private static void concat () { Stream<String> stream1 = Stream.of("金莲" , "三上" , "松下" ,"柳岩" ,"张无忌" ); Stream<String> stream2 = Stream.of("涛哥" , "涛哥1" , "涛哥2" ,"涛哥3" ); Stream.concat(stream1, stream2).forEach(s -> System.out.println(s)); }
将Stream流变成集合 1 从Stream流对象转成集合对象,使用Stream接口方法collect()
1 2 3 4 5 6 7 8 private static void collect () { Stream<String> stream1 = Stream.of("金莲" , "三上" , "松下" ,"柳岩" ,"张无忌" ); List<String> list = stream1.collect(Collectors.toList()); System.out.println(list); }
dinstinct方法 1 2 Stream<T> distinct() 元素去重复,依赖hashCode和equals方法
1 2 3 4 5 6 7 8 9 10 11 private static void distinct () { Stream<Person> stream = Stream.of(new Person ("张三" , 10 ), new Person ("李四" , 12 ), new Person ("张三" , 10 )); stream.distinct().forEach(person -> System.out.println(person)); }
转换流中的类型 1 Stream<R> map (Function<T,R> mapper) -> 转换流中的数据类型
1 2 3 4 5 6 7 8 9 10 11 12 private static void map () { Stream<Integer> stream = Stream.of(1 , 2 , 3 , 4 , 5 , 6 ); stream.map(new Function <Integer, String>() { @Override public String apply (Integer integer) { return integer+"" ; } }).forEach(s -> System.out.println(s+1 )); }
Stream流练习 1 2 3 4 5 6 7 8 9 10 11 1. 第一个队伍只要名字为3 个字的成员姓名;2. 第一个队伍筛选之后只要前3 个人;3. 第二个队伍只要姓张的成员姓名;4. 第二个队伍筛选之后不要前2 个人;5. 将两个队伍合并为一个队伍;6. 打印整个队伍的姓名信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class Demo04Stream { public static void main (String[] args) { ArrayList<String> one = new ArrayList <>(); one.add("迪丽热巴" ); one.add("宋远桥" ); one.add("苏星河" ); one.add("老子" ); one.add("庄子" ); one.add("孙子" ); one.add("洪七公" ); ArrayList<String> two = new ArrayList <>(); two.add("古力娜扎" ); two.add("张无忌" ); two.add("张三丰" ); two.add("赵丽颖" ); two.add("张二狗" ); two.add("张天爱" ); two.add("张三" ); Stream<String> teamA = one.stream(); Stream<String> teamB = two.stream(); Stream.concat(teamA.filter(s -> s.length() == 3 ).limit(3 ),teamB.filter(s -> s.startsWith("张" )).skip(2 )).forEach(s -> System.out.println(s)); } }
方法引用 方法引用的介绍 1 2 3 4 5 1. 概述:引用方法2. 啥时候使用: a.被引用的方法要写在重写方法里面 b.被引用的方法从参数上,返回值上要和所在重写方法一致,而且引用的方法最好是操作重写方法的参数值的 c.干掉重写方法的参数;干掉->;干掉被引用方法的参数 -> 将被引用方法的.改成::
方法引入的体验 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Demo01Method { public static void main (String[] args) { Stream<String> stream = Stream.of("明日" , "三上" , "金莲" , "松下" , "有菜" ); System.out.println("===================" ); System.out.println("===================" ); stream.forEach(System.out::println); } }
对象名–引用成员方法 1 2 3 4 5 6 7 8 9 10 1. 使用对象名引用成员方法 格式: 对象::成员方法名 2. 需求: 函数式接口:Supplier java.util.function.Supplier<T>接口 抽象方法: T get () 。用来获取一个泛型参数指定类型的对象数据。 Supplier接口使用什么泛型,就可以使用get方法获取一个什么类型的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class Demo02Method { public static void main (String[] args) { method(new Supplier <String>() { @Override public String get () { return " abc " .trim(); } }); System.out.println("================" ); method(()->" abc " .trim()); System.out.println("================" ); method(" abc " ::trim); } public static void method (Supplier<String> supplier) { String s = supplier.get(); System.out.println("s = " + s); } }
类名–引用静态方法 1 2 3 类名--引用静态方法 格式: 类名::静态成员方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Demo03Method { public static void main (String[] args) { method(new Supplier <Double>() { @Override public Double get () { return Math.random(); } }); System.out.println("==================" ); method(()->Math.random()); System.out.println("==================" ); method(Math::random); } public static void method (Supplier<Double> supplier) { Double aDouble = supplier.get(); System.out.println("aDouble = " + aDouble); } }
类–构造引用 1 2 3 4 5 6 7 8 9 1. 类--构造方法引用 格式: 构造方法名称::new 2. 需求: 函数式接口:Function java.util.function.Function<T,R>接口 抽象方法: R apply (T t) ,根据类型T的参数获取类型R的结果。用于数类型转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Demo04Method { public static void main (String[] args) { method(new Function <String, Person>() { @Override public Person apply (String s) { return new Person (s); } },"涛哥" ); method(s -> new Person (s),"金莲" ); method(Person::new ,"三上" ); } public static void method (Function<String,Person> function,String name) { Person person = function.apply(name); System.out.println(person); } }
数组–数组引用 1 2 3 4 5 数组--数组引用 格式: 数组的数据类型[]::new int []::new 创建一个int 型的数组 double []::new 创建于一个double 型的数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class Demo05Method { public static void main (String[] args) { method(new Function <Integer, int []>() { @Override public int [] apply(Integer integer) { return new int [integer]; } },10 ); System.out.println("=================" ); method(integer-> new int [integer],10 ); System.out.println("==================" ); method(int []::new ,10 ); } public static void method (Function<Integer,int []> function,Integer len) { int [] arr = function.apply(len); System.out.println(arr.length); } }
Java9-17新特性 JDK版本的选择 历经曲折的Java 9在4次跳票后,终于在2017年9月21日发布。从Java 9这个版本开始,Java 的计划发布周期是6个月,这意味着Java的更新从传统的以特性驱动的发布周期,转变为以时间驱动的发布周期,并逐步地将Oracle JDK原商业特性进行开源。针对企业客户的需求,Oracle将以3年为周期发布长期支持版本(Long Term Support,LTS),最近的LTS版本就是Java 11和Java17了,其他都是过渡版本
在Java 17正式发布之前,Java开发框架Spring率先在官博宣布,Spring Framework 6和Spring Boot 3计划在2022年第四季度实现总体可用性的高端基线:
1、Java 17+(来自 Spring Framework 5.3.x 线中的 Java 8-17)
2、Jakarta EE 9+(来自Spring框架5.3.x 线中的 Java EE 7-8)
3.Spring 官方说明:https://spring.io/blog/2022/01/20/spring-boot-3-0-0-m1-is-now-available
Springboot3.0 是需要用Java17和Spring6.0为基础建设。如果从企业选型最新Springboot3.0作为架构来说,它搭配jdk17肯定是标配了。
针对于Spring 6,官网的说明会弃用java8以9为最低版本,而且兼容tomcat10+。
4.JDK17针对于GC方面作出了优化,以及做了性能的提高
a.在吞吐量方面,Parallel 中 JDK 8 和 JDK 11 差距不大,JDK 17 相较 JDK 8 提升 15% 左右;G1 中 JDK 17 比 JDK 8 提升 18%;ZGC 在 JDK 11引入,JDK 17 对比JDK 11 提升超过 20%
b. 在 GC 延迟方面,JDK 17 的提升更为明显。在 Parallel 中 JDK 17 对比 JDK 8 和JDK 11 提升 40%;在 G1 中,JDK 11 对比 JDK 8 提升 26%,JDK 17 对比 JDK 8 提升接近 60%!ZGC 中 JDK 17 对比 JDK 11 提升超过 40%
从GC性能角度去看,JDK 11对比JDK 8延迟提升不到40%;反观JDK 17对比JDK 8 延迟提升 60%,吞吐量提升 18%;可以看到JDK17的提升还是非常明显的
由于JDK对性能提升方面都是自动的,所以我们可以直接学习JDK新特性中的语法和API。我们要知道的是下面的语法不都是从JDK17才开始有的,但是JDK17都支持这些语法和API。
1.查看jdk版本: java -version
接口的私有方法 Java8版本接口增加了两类成员:
Java9版本接口又新增了一类成员:
为什么JDK1.9要允许接口定义私有方法呢?因为我们说接口是规范,规范是需要公开让大家遵守的
私有方法 :因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方法由共同的代码可以抽取,而这些共同的代码抽取出来的方法又只希望在接口内部使用,所以就增加了私有方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public interface USB { private void open () { System.out.println("私有非静态方法" ); } private static void close () { System.out.println("私有静态方法" ); } public default void methodDef () { open(); close(); } }
1 2 public class UsbImpl implements USB {}
1 2 3 4 5 public class Test01 { public static void main (String[] args) { new UsbImpl ().methodDef(); } }
钻石操作符与匿名内部类结合 自Java 9之后我们将能够与匿名实现类共同使用钻石操作符,即匿名实现类也支持类型自动推断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class Person { private String name; private Integer age; public Person () { } public Person (String name, Integer age) { this .name = name; this .age = age; } public String getName () { return name; } public void setName (String name) { this .name = name; } public Integer getAge () { return age; } public void setAge (Integer age) { this .age = age; } @Override public String toString () { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Test02 { public static void main (String[] args) { ArrayList<Person> list = new ArrayList <>(); list.add(new Person ("张三" ,10 )); list.add(new Person ("李四" ,8 )); list.add(new Person ("王五" ,20 )); Collections.sort(list, new Comparator <Person>() { @Override public int compare (Person o1, Person o2) { return o1.getAge()-o2.getAge(); } }); System.out.println(list); } }
Java 8的语言等级编译会报错:“’<>’ cannot be used with anonymous classes。”Java 9及以上版本才能编译和运行正常。
try..catch升级 之前我们讲过JDK 1.7引入了try-with-resources的新特性,可以实现资源的自动关闭,此时要求:
该资源必须实现java.io.Closeable接口
在try子句中声明并初始化资源对象
该资源对象必须是final的
1 2 3 4 5 try (IO流对象1 声明和初始化;IO流对象2 声明和初始化){ 可能出现异常的代码 }catch (异常类型 对象名){ 异常处理方案 }
JDK1.9又对try-with-resources的语法升级了
该资源必须实现java.io.Closeable接口
在try子句中声明并初始化资源对象,也可以直接使用已初始化的资源对象
该资源对象必须是final的
1 2 3 4 5 6 7 8 IO流对象1 声明和初始化; IO流对象2 声明和初始化; try (IO流对象1 ;IO流对象2 ){ 可能出现异常的代码 }catch (异常类型 对象名){ 异常处理方案 }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class Test03 { public static void main (String[] args) throws IOException { method02(); } private static void method02 () throws IOException { FileWriter fw = new FileWriter ("module24\\io.txt" ); try (fw){ fw.write("你好" ); }catch (Exception e){ e.printStackTrace(); } } private static void method01 () { try (FileWriter fw = new FileWriter ("module24\\io.txt" )){ fw.write("你好" ); }catch (Exception e){ e.printStackTrace(); } } }
局部变量类型自动推断 jdk10之前,我们定义局部变量都必须要明确数据的数据类型,但是到了JDK10,出现了一个最为重要的特性,就是局部变量类型推断
,顾名思义,就是定义局部变量时,不用先确定具体的数据类型了,可以直接根据具体数据推断出所属的数据类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Test04 { public static void main (String[] args) { var i = 10 ; System.out.println("i = " + i); var j = "helloworld" ; System.out.println("j = " + j); var arr = new int []{1 ,2 ,3 ,4 ,5 }; for (var element : arr) { System.out.println(element); } } }
switch表达式 switch表达式在Java 12中作为预览语言出现,在Java 13中进行了二次预览,得到了再次改进,最终在Java 14中确定下来。另外,在Java17中预览了switch模式匹配。
传统的switch语句在使用中有以下几个问题。
(1)匹配是自上而下的,如果忘记写break,那么后面的case语句不论匹配与否都会执行。
(2)所有的case语句共用一个块范围,在不同的case语句定义的变量名不能重复。
(3)不能在一个case语句中写多个执行结果一致的条件,即每个case语句后只能写一个常量值。
(4)整个switch语句不能作为表达式返回值。
1、Java12的switch表达式 Java 12对switch语句进行了扩展,将其作为增强版的switch语句或称为switch表达式,可以写出更加简化的代码。
允许将多个case语句合并到一行,可以简洁、清晰也更加优雅地表达逻辑分支。
可以使用-> 代替 :
->写法默认省略break语句,避免了因少写break语句而出错的问题。
->写法在标签右侧的代码段可以是表达式、代码块或 throw语句。
->写法在标签右侧的代码块中定义的局部变量,其作用域就限制在代码块中,而不是蔓延到整个switch结构。
同一个switch结构中不能混用“→”和“:”,否则会有编译错误。使用字符“:”,这时fall-through规则依然有效,即不能省略原有的break语句。”:”的写法表示继续使用传统switch语法。
案例需求:
请使用switch-case结构实现根据月份输出对应季节名称。例如,3~5月是春季,6~8月是夏季,9~11月是秋季,12~2月是冬季。
Java12之前写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Test public void test1 () { int month = 3 ; switch (month) { case 3 : case 4 : case 5 : System.out.println("春季" ); break ; case 6 : case 7 : case 8 : System.out.println("夏季" ); break ; case 9 : case 10 : case 11 : System.out.println("秋季" ); break ; case 12 : case 1 : case 2 : System.out.println("冬季" ); break ; default : System.out.println("月份输入有误!" ); } }
Java12之后写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 private static void method02 () { int month = 5 ; switch (month) { case 12 , 1 , 2 -> System.out.println("冬季" ); case 3 , 4 , 5 -> System.out.println("春季" ); case 6 , 7 , 8 -> System.out.println("夏季" ); case 9 , 10 , 11 -> System.out.println("秋季" ); default -> System.out.println("有毛病呀,没有这个月份" ); } } private static void method01 () { int month = 5 ; switch (month){ case 12 ,1 ,2 : System.out.println("冬季" ); break ; case 3 ,4 ,5 : System.out.println("春季" ); break ; case 6 ,7 ,8 : System.out.println("夏季" ); break ; case 9 ,10 ,11 : System.out.println("秋季" ); break ; default : System.out.println("有毛病呀,没有这个月份" ); break ; } }
2、Java13的switch表达式 Java 13提出了第二个switch表达式预览,引入了yield语句,用于返回值。这意味着,switch表达式(返回值)应该使用yield语句,switch语句(不返回值)应该使用break语句。
案例需求:判断季节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 private static void method04 () { int month = 5 ; var seson = switch (month) { case 12 , 1 , 2 -> { yield "冬季" ; } case 3 , 4 , 5 -> { yield "春季" ; } case 6 , 7 , 8 -> { yield "夏季" ; } case 9 , 10 , 11 -> { yield "秋季" ; } default -> { yield "有毛病" ; } }; System.out.println("seson = " + seson); } private static void method03 () { int month = 5 ; String season = "" ; switch (month) { case 12 , 1 , 2 : season = "冬季" ; break ; case 3 , 4 , 5 : season = "春季" ; break ; case 6 , 7 , 8 : season = "夏季" ; break ; case 9 , 10 , 11 : season = "秋季" ; break ; default : season = "有毛病" ; break ; } System.out.println("season = " + season); }
文本块 预览的新特性文本块在Java 15中被最终确定下来,Java 15之后我们就可以放心使用该文本块了。
1、Java13文本块 JDK 12引入了Raw String Literals特性,但在其发布之前就放弃了这个特性。这个JEP与引入多行字符串文字(文本块)在意义上是类似的。Java 13中引入了文本块(预览特性),这个新特性跟Kotlin中的文本块是类似的。
现实问题
在Java中,通常需要使用String类型表达HTML,XML,SQL或JSON等格式的字符串,在进行字符串赋值时需要进行转义和连接操作,然后才能编译该代码,这种表达方式难以阅读并且难以维护。
文本块就是指多行字符串,例如一段格式化后的XML、JSON等。而有了文本块以后,用户不需要转义,Java能自动搞定。因此,文本块将提高Java程序的可读性和可写性。
目标
简化跨越多行的字符串,避免对换行等特殊字符进行转义,简化编写Java程序。
增强Java程序中字符串的可读性。
举例
会被自动转义,如有一段以下字符串:
1 2 3 4 5 <html > <body > <p > Hello, 尚硅谷</p > </body > </html >
将其复制到Java的字符串中,会展示成以下内容:
1 2 3 4 5 "<html>\n" +" <body>\n" +" <p>Hello, 尚硅谷</p>\n" +" </body>\n" +"</html>\n" ;
即被自动进行了转义,这样的字符串看起来不是很直观,在JDK 13中,就可以使用以下语法了:
1 2 3 4 5 6 7 """ <html> <body> <p>Hello, world</p> </body> </html> """ ;
使用“”“作为文本块的开始符和结束符,在其中就可以放置多行的字符串,不需要进行任何转义。看起来就十分清爽了。
文本块是Java中的一种新形式,它可以用来表示任何字符串,并且提供更多的表现力和更少的复杂性。
(1)文本块由零个或多个字符组成,由开始和结束分隔符括起来。
开始分隔符由三个双引号字符表示,后面可以跟零个或多个空格,最终以行终止符结束。
文本块内容以开始分隔符的行终止符后的第一个字符开始。
结束分隔符也由三个双引号字符表示,文本块内容以结束分隔符的第一个双引号之前的最后一个字符结束。
以下示例代码是错误格式的文本块:
1 2 3 String err1 = """""" ;String err2 = """ """ ;
如果要表示空字符串需要以下示例代码表示:
1 2 3 String emp1 = "" ;String emp2 = """ """ ;
(2)允许开发人员使用“\n”“\f”和“\r”来进行字符串的垂直格式化,使用“\b”“\t”进行水平格式化。如以下示例代码就是合法的。
1 2 3 4 5 6 7 String html = """ <html>\n <body>\n <p>Hello, world</p>\n </body>\n </html>\n """ ;
(3)在文本块中自由使用双引号是合法的。
1 2 3 4 5 String story = """ Elly said,"Maybe I was a bird in another life." Noah said,"If you're a bird , I'm a bird." """ ;
instanceof模式匹配 instanceof的模式匹配在JDK14、15中预览,在JDK16中转正。有了它就不需要编写先通过instanceof判断再强制转换的代码。
1 2 3 public abstract class Animal { public abstract void eat () ; }
1 2 3 4 5 6 7 8 9 10 11 public class Dog extends Animal { @Override public void eat () { System.out.println("狗啃骨头" ); } public void lookDoor () { System.out.println("狗会看门" ); } }
从JDK14开始,我们不需要单独强转,直接省略强转的过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Test07 { public static void main (String[] args) { Dog dog = new Dog (); method1(dog); } public static void method (Animal animal) { if (animal instanceof Dog) { Dog dog = (Dog) animal; dog.eat(); dog.lookDoor(); } } public static void method1 (Animal animal) { if (animal instanceof Dog dog) { dog.eat(); dog.lookDoor(); } } }
Record类 Record类在JDK14、15预览特性,在JDK16中转正。
record是一种全新的类型,它本质上是一个 final类,同时所有的属性都是 final修饰,它会自动编译出get、hashCode 、比较所有属性值的equals、toString 等方法,减少了代码编写量。使用 Record 可以更方便的创建一个常量类。
1.注意:
Record只会有一个全参构造
重写的equals方法比较所有属性值
可以在Record声明的类中定义静态字段、静态方法或实例方法(非静态成员方法)。
不能在Record声明的类中定义实例字段(非静态成员变量);
类不能声明为abstract;
不能显式的声明父类,默认父类是java.lang.Record类
因为Record类是一个 final类,所以也没有子类等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public record Person (String name) { static int i; public static void method () { } public void method01 () { } }
1 2 3 4 5 6 7 8 9 public class Test01 { public static void main (String[] args) { Person person = new Person ("张三" ); Person person1 = new Person ("张三" ); System.out.println(person); System.out.println(person.equals(person1)); } }
密封类 其实很多语言中都有密封类
的概念,在Java语言中,也早就有密封类
的思想,就是final修饰的类,该类不允许被继承。而从JDK15开始,针对密封类
进行了升级。
Java 15通过密封的类和接口来增强Java编程语言,这是新引入的预览功能并在Java 16中进行了二次预览,并在Java17最终确定下来。这个预览功能用于限制超类的使用,密封的类和接口限制其他可能继承或实现它们的其他类或接口。
1 2 3 4 5 6 【修饰符】 sealed class 密封类 【extends 父类】【implements 父接口】 permits 子类{ } 【修饰符】 sealed interface 接口 【extends 父接口们】 permits 实现类{ }
密封类用 sealed 修饰符来描述,
使用 permits 关键字来指定可以继承或实现该类的类型有哪些
一个类继承密封类或实现密封接口,该类必须是sealed
、non-sealed
、final
修饰的。
sealed修饰的类或接口必须有子类或实现类
1 2 3 4 5 6 7 8 public sealed class Animal permits Dog,Cat{} public non-sealed class Dog extends Animal {} public non-sealed class Cat extends Animal {}
1 2 3 4 5 6 7 8 9 10 11 12 package com.atguigu.sealed ;import java.io.Serializable;public class TestSealedInterface {} sealed interface Flyable permits Bird { } non-sealed class Bird implements Flyable { }
其他 陆续在新版本变化的API有很多,因篇幅问题不能一一列举。
Java 9带来了很多重大的变化,其中最重要的变化是Java平台模块系统的引入。众所周知,Java发展已经超过20年,Java和相关生态在不断丰富的同时也越来越暴露出一些问题。
(1)当某些应用很简单时。夸张地说,如若仅是为了打印一个“helloworld”,那么之前版本的JRE中有一个很重要的rt.jar(如Java 8的rt.jar中有60.5M),即运行一个“helloworld”,也需要一个数百兆的JRE环境,而这在很多小型设备中是很难实现的。
(2)当某些应用很复杂,有很多业务模块组成时。我们以package的形式设计和组织类文件,在起初阶段还不错,但是当我们有数百个package时,它们之间的依赖关系一眼是看不完的,当代码库越来越大,创建越复杂,盘根错节的“意大利面条式代码”的概率会呈指数级增长,这给后期维护带来了麻烦,而可维护性是软件设计和演进过程中最重要的问题。
(3)一个问题是classpath。所有的类和类库都堆积在classpath中。当这些JAR文件中的类在运行时有多个版本时,Java的ClassLoader就只能加载那个类的某个版本。在这种情形下,程序的运行就会有歧义,有歧义是一件非常坏的事情。这个问题总是频繁出现,它被称为“JAR Hell”。
(4)很难真正对代码进行封装, 而系统并没有对不同部分(也就是 JAR 文件)之间的依赖关系有明确的概念。每个公共类都可以被类路径下的其他类访问到,这样就会在无意中使用了并不想被公开访问的API。
模块就是为了修复这些问题存在的。模块化系统的优势有以下几点。
模块化的主要目的是减少内存的开销。
只需要必要模块,而非全部JDK模块,可简化各种类库和大型应用的开发和维护。
改进Java SE平台,使其可以适应不同大小的计算设备。
改进其安全性、可维护性。用模块管理各个package,其实就是在package外再裹一层,可以通过声明暴露某个package,不声明默认就是隐藏。因此,模块化系统使代码组织上更安全,因为它可以指定哪些部分可以暴露,哪些部分需要隐藏。
更可靠的配置,通过明确指定的类的依赖关系代替以前易错的路径(class-path)加载机制。模块必须声明对其他模块的显示依赖,并且模块系统会验证应用程序所有阶段的依赖关系:编译时、链接时和运行时。假设一个模块声明对另一个模块的依赖,并且第二个模块在启动时丢失,JVM检测到依赖关系丢失,在启动时就会失败。在Java 9之前,当使用缺少的类型时,这样的应用程序只会生成运行时错误而不是启动时错误。
Java 9是一个庞大的系统工程,从根本上带来了一个整体改变,包括JDK的安装目录,以适应模块化的设计。
大家可以发现在Java 9之后,API的组织结构也变了。
原来Java 8的API,包是顶级的封装,Java 8的API结构如图所示。
而Java 9的API,模块是顶级的封装,模块之下才是包,如java.base模块,Java 9的API中Java SE部分的模块结构如图所示。
总结 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 模块24重点回顾: 1.Lambda表达式 a.格式: () -> {} b.使用条件:必须有函数式接口作为方法参数传递(有且只有一个抽象方法的接口叫做函数式接口,带@FunctionalInterface) 2.新手怎么入门: a.观察是否是函数式接口作为方法参数传递 b.如果是,考虑使用Lambda表达式 c.调用方法,以匿名内部类形式传递实参 d.从new接口开始到重写方法的方法名结束,选中,删除,别忘记删除右半个大括号 e.在重写方法的参数后面以及方法体之间加-> 3.省略规则: a.重写方法的参数类型可以干掉 b.如果重写方法只有一个参数,所在的小括号可以干掉 c.如果方法体只有一句话,那么所在的大括号以及分号可以干掉 d.如果方法体中只有一句话并且带return,那么所在的大括号,return,分号都干掉 4.Stream流:流水线的流 a.获取1:调用Collection中的方法stream() -> 针对集合 b.获取2:调用Stream接口中的of(可变参数) -> 针对数组 5.方法: forEach:遍历 count:统计 filter:过滤 limit:获取前n个 skip:跳过前n个 concat:流合并 collect:流转集合 distinct:去重复 map:转换流中的数据类型 6.方法引用: a.被引用的方法要写在重写的方法里面 b.被引用的方法从参数上,返回值上要和所在重写方法一致,而且引用的方法最好操作重写方法的参数 c.干掉重写方法的参数,干掉->,干掉被引用方法的参数,将.改成::