Java8新特性
Java发展历史
目录
java发展历史
sun 91年绿色计划,开发一种可以在机顶盒、冰箱等电子消费产品上运行的程序框架 Java语言前身-Oak(橡树)
14年3月到17年9月Java 8
OpenJDK和OracleJDK
java由Sun公司开发,OpenJDK是06年把java开源形成的项目,由Sun和Java社区提供支持
OracleJDK完全由Oracle公司开发,是基于OpenJDK开发的商业版本
Lambda表达式
产生背景
比如说我们在创建线程的时候我们用new Thread的方式,该方式的参数是Runnable接口
一个方法的参数是一个接口,在使用时,我们需要new该接口的实现类,并且实现接口中的方法。方法名、返回值、参数列表等等都需要重写一遍且不可有错误,但我们关注的仅仅是方法的方法体代码。
Lambda定义
简化了匿名内部类的使用,可以理解为是一段可传递的代码,使用的时候该接口必须是函数式接口
Lambda表达式是一种语法糖,简化了匿名内部类的冗余代码。并且每一个表达式必须有一个函数式接口与之对应。(语法糖就是指编程语言中可以更容易的表达一个操作的语法,他可以使这个操作变得更清晰更简便。)
Lambda的语法规则
(参数类型 参数名称)->{
代码题;
}
方法的参数或局部变量类型必须为接口
接口中有且仅有一个抽象方法
Lambda表达式的省略写法
1、方法体只有一行代码,可省略{}同时省略;
2、参数类型可以省略
3、只有一个参数,()也可以省略
@FunctionalInterface注解
信息性注解,用来指示接口是函数式接口。
函数式接口,就是只有一个抽象方法,其他方法有默认实现。
@Lambda表达式的原理
匿名内部类的本质是在编译时生成一个Class文件。类名$.class文件我们可以通过反编译工具来看看生成的代码(SJad工具)但写有Lambda表达式生成的文件反编译报错,需要使用JDK自带的javap工具(可以对字节码进行反汇编操作) javap -c -p 文件.calss c对代码进行反汇编 p显示所有类和成员
总结:
匿名内部类编译时候就是生成一个class文件
Lambda表达式程序运行时动态生成class文件会新增一个私有静态方法+一个内部类
其中内部类实现了函数式接口,并且在实现方法中会调用生成的静态方法
私有静态方法的方法体就是我们写的Lambda表达式的方法体。
从而达到实现了函数接口,又执行了我们写的方法体。
接口中新增方法
前后对比
接口中的方法类型:
JDK8之前:静态常量、抽象方法
JDK8之后:静态常量、抽象方法、默认方法(default)、静态方法(static)
默认方法
产生背景
jdk8以前接口中只能有抽象方法和静态常量,但是如果在接口中新增抽象方法,所有实现类中就都必须实现该方法,非常不利于接口扩展
默认方法语法规则
default String test(){ 方法体 }
静态方法
产生背景
也是为了利于接口的扩展
静态方法语法规则
接口中的静态方法在实现类中不可以被重写,调用只能通过接口名.静态方法名();
函数式接口
产生背景
Lambda表达式使用前提是得有函数式接口,JDK中提供了大量常用函数式接口
定义
只有一个抽象方法的接口,只需要一个函数必须要实现
函数式接口类型
JDK提供的函数式接口主要在java.util.function包中
Supplier
无参有返回值 T get()
Consumer
有参无返回值 void receive(T t)
要实现 消费一个数据,首先做一个操作,然后再做一个操作,实现组合。可以使用Consumer接口中的 默认方法——andThen()
Function
有参有返回 R apply(T t)
Predicate
有参返回Boolean boolean test(T t)
方法引用
产生背景
解决Lambda表达式冗余,如果Lambda表达式方法体中的操作已经有现成的方法实现,则可以直接引用该方法
方法引用语法格式
符号表示::
如果Lambda表达式所要实现的方案,已经有其他方法存在相同代码,就可以引用
对象名::方法名 类名::静态方法 类名::普通方法 new类名::new 构造器
Stream API
产生背景
对集合元素进行操作,除了必须的增删改查,最多的操作就是遍历集合,针对不同需求可能需要多次遍历,为了更高效处理引入流
Stream定义
这里的Stream和IO流没半毛钱关系。
流式思想类似于工厂车间流水线,每个相同的商品相当于元素中的集合,流水线可以对集合中的元素进行多道工序的加工操作。
Stream的功能有筛选、切片、映射、查找、去重、统计、匹配、规约
Stream的获取方式
根据Collection获取
Collection接口加入了一个新的默认方法stream,Collection所有的实现都可以通过调用改默认方法来获取Stream流
但是Map接口没有实现Collection,我们可以获取对应的key、value集合
map=new HashMap<>(); key=map.keySet().stream(); value=map.values().stream(); //entry map.entrySet.stream();
通过Stream的of()
数组对象不可能添加默认方法,所有Stream接口中提供了静态方法of()
Stream<String> a=Stream.of("aaa","bbb");
Stream使用注意
Stream只能操作一次
Stream方法返回的是新流
Stream不调用终结方法,中间的操作都不会执行
Stream常用方法
count、forEach、filter、limit、skip、map(映射)、concat(组合)、match(匹配)、find(查找)
方法分类为:终结方法count、forEach、match(返回值类型不再是Stream,不支持链式调用)、非终结方法
Stream.of("a","b","c") .filter((s)->s.contains("a")) .forEach(System.out :: println)
Stream常用方法实例
map
map是将一个流中的数据映射到另一个流,T类型转化成R类型
Stream.of("a","b","c") .map(Integer :: parseInt) .forEach(System.out::println)
sorted
sorted是将数据排序
Stream.of("1","2","3") .map(Integer::parseInt) .sorted()//升序 //降序.sorted((o1,o2)->o2-o1) .forEach(Systeam.out::println)
match 匹配
anyMatch任一满足条件、allMatch元素都满足、noneMatch元素都不满足
Stream.of("1","2","3") .map(Integer::parseInt) .allmatch(s -> s>0)//返回false
find查找
findFirst查找第一个元素
findAny查找任何元素
Optional<String> first = Stream.of("1", "3", "4", "5", "6", "1", "7").findFirst(); System.out.println(first.get()); Optional<String> any = Stream.of("1", "3", "4", "5", "6", "1", "7").findAny(); System.out.println(any.get());
max、min
Optional<Integer> max = Stream.of("1", "3", "4", "5", "6", "1", "7") .map(Integer :: parseInt) .max((o1, o2) -> o1 - o2); System.out.println(max.get()); System.out.println("-----------------------"); Optional<Integer> min = Stream.of("1", "3", "4", "5", "6", "1", "7") .map(Integer :: parseInt) .min((o1, o2) -> o1 - o2); System.out.println(min.get());
reduce
所有数据归纳为一个数据
Integer sum = Stream.of(4, 5, 6, 9, 2, 3) //identity默认值 //第一次时,会将默认值赋值给x //之后每次会将上一次操作结果赋值给x,y就是每次从数据中获取到元素 .reduce(0, (x ,y) -> { System.out.println("x=" + x + ",y=" + y); return x+y; }); System.out.println(sum); System.out.println("------------");
map、reduce组合
Integer count = Stream.of("张", "李", "刘", "王", "刘" ).map(ch -> "刘".equals(ch) ? 1 : 0) .reduce(0, Integer :: sum); System.out.println("count=" + count);
Stream结果收集
结果收集到集合中
收集到List
.collect(Collectors.toList());
.collect(Collectors.toCollection(ArrayList::new));
收集到Set
.collect(Collectors.toSet());
.collect(Collectors.toCollection(HashSet::new));
结果收集到数组中
.toArray() 返回类型Object[]
.toArray(String[]::new) 返回类型 String[]
对流中的数据做聚合计算
我们使用Stream流处理数据后,可以像数据库打的聚合函数一样对某个字段进行操作,比如获得最大、最小、求和、平均值、数据统计等等
.collect(Collectors.minBy((p1,p2)->p1.getAge()-p2.getAge()));
对流中数据做分组操作
Stream流处理数据后,可以根据某个属性将数据分组
.collect(Collectors.groupingBy(Student::getName));
.collect(Collectors.groupingBy(Student::getAge()>=18 ? "成年":"未成年"));
对流中数据做分区操作
根据值是否为true,把集合中的数据分割为两个列表,一个true列表,一个false列表
.collect(Collectors.partitioningBy(s->s.getAge()>18));
对流中数据做拼接
根据指定连接符,将所有元素连接成一个字符串
.collect(Collectors.joining("_","开始","结束"));
开始a_b_c结束
并行的Stream流
前面使用的Stream都是串行,也就是在一个线程上面执行
并行流定义
parallelStream其实就是一个并行执行的流,它通过默认的ForkJoinPool,可以提高多线程任务的速度
获取并行流
List接口中的parallelStream方法
lsit.parallelStream();
串行流转为并行流
Stream.of(1,2,3).parallel();
并行流VS串行流
对大规模数据做流操作,并行流效率最高
线程安全问题
list中有一千个元素
list.parallelStream().forEach(listNew::add); 可能会抛出异常
解决方案
加同步锁 synchronized
使用线程安全的容器 Vector、List<Integer> synchronizedList = Collections.synchronizedList(listNew);
通过Stream中的toArray/collect操作
Optional类
产生背景
这个Optional类是解决空指针的问题
之前对null值得处理if(xxx!=null)
定义
optional是一个没有子类的工具类,optional是一个可以为null的容器对象
获得Optional对象
Optional.of("wang");//方法不支持null
Optional.ofNullable("wang");//方法支持null
Optional.empty();//直接创建一个空的Optional对象
Optional常用方法
public String getNameForOptional(Optional<Student> op){ if(op.isPresent()){ String msg = op.map(Student::getName) .map(String::toUpperCase) .orElse("空值"); return msg; } return null;
新时间日期API
产生背景
在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间,java.sql.Date仅仅包含日期。用于格式化和解析的类在java.text包下
java.util.Date线程不安全,所有日期类可变
时区处理麻烦
JDK8新日期时间API
线程安全,日期时间API位于java.time包
关键类:
1、LocalDate:表示日期2022-12-24
2、LocalTime:表示时间16:55:55.158549300
3、LocalDateTime 表示日期时间 2022-12-24T6:55:55.158549300
4、DateTimeFormatter日期时间格式化类
5、Instant 时间戳,表示一个特定的时间瞬间
6、Duration 计算2个时间的距离(LocalTime)
7、Period 计算2个日期的距离(LocalDate)
8、ZonedDateTime 包含时区的时间
日期时间常见操作
//LocalDate LocalDate.of(2022,12,24); LocalDate.now(); //LocalTime LocalTime.of(9,12,33,12345); LocalTime.now(); //LocalDateTime LocalDateTime.of(2022,12,24,9,12,33,12346); LocalDateTime.now();
格式化和解析操作
通过java.time.format.DateTimeFormatter类进行日期解析和格式化操作
LocalDateTime now = LocalDateTime.now(); //指定格式:使用系统默认的格式 DateTimeFormatter isoLocalDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME; //将日期时间转换为字符串 String format1 = now.format(isoLocalDateTime); System.out.println("format1=" + format1); //通过 ofPattern 方法来指定特定的格式 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String format2 = now.format(dateTimeFormatter); System.out.println("format2=" + format2); //将字符串解析为一个 日期时间类型 LocalDateTime parse = LocalDateTime.parse("1991-05-13 22:22:22", dateTimeFormatter); System.out.println("parse=" + parse);
Instant类
JDK8新增了一个Instant类(时间戳/时间线),内部保存了从1970年1月1日00:00:00以来的秒和纳秒
Instant now=Instant.now();
时间校正器
将日期调整到【下个月的第一天】等操作
public void test01() { LocalDateTime now = LocalDateTime.now(); //将当前的日期调整到下个月的一号 TemporalAdjuster adjuster = (temporal) -> { LocalDateTime dateTime = (LocalDateTime) temporal; LocalDateTime nextMonth = dateTime.plusMonths(1).withDayOfMonth(1); System.out.println("nextMonth=" + nextMonth); return nextMonth; }; LocalDateTime nextMonth1 = now.with(adjuster); System.out.println("nextMonth1=" + nextMonth1); //我们可以通过 TemporalAdjusters 来实现 LocalDateTime nextMonth2 = now.with(TemporalAdjusters.firstDayOfNextMonth()); System.out.println("nextMonth2=" + nextMonth2); }
小结
新版日期时间API中,日期和时间对象不可变,操作日期不会影响原来的值,而是生成一个新的实例
TemporalAdjuster可以更精确的操作日期,还可以自定义日期调整期
线程安全
其他新特性
重复注解
产生背景
注解广泛应用,但是在同一个地方不能多次使用同一个注解
JDK8引入了重复注解的概念使用@Repeatable可以定义重复注解
类型注解
学习资料:波波老师的史上最全Java8新特性详解_小学生波波的博客-CSDN博客