Java8(JDK1.8) 新特性介绍
目录
一、Lamdba表达式
二、函数式接口
三、方法引用和构造引用
四、Stream API流
五、接口中的新增 默认方法和静态方法
六、新时间日期API
七、Optional
八、其他特性
详细介绍可参考B站视频:动态-哔哩哔哩
一、Lamdba表达式
- Lamdba优缺点
优点:
1.代码更加简洁
2.减少匿名内部类的创建,节省资源
3.使用时不用去记忆所使用的接口和抽象函数
缺点:
1.不易于后期维护,必须熟悉lambda表达式和抽象函数中参数的类型
2.可读性差
- Lambda表达式使用前提:
1. 方法的参数或局部变量类型必须为接口才能使用Lambda
2. 接口中有且仅有一个抽象方法(@FunctionalInterface)
- Lambda和匿名内部类的对比
二、函数式接口
- 函数式接口的由来
public class demo {public static void main(String[] args) {fun1((arr)->{int sum = 0 ;for (int i : arr) {sum += i;}return sum;});}public static void fun1(Operator operator){int[] arr = {1,2,3,4};int sum = operator.getSum(arr);System.out.println("sum = " + sum);}
}
/*** 函数式接口*/
@FunctionalInterface
interface Operator{int getSum(int[] arr);
}
- 常用函数式接口
在JDK中帮我们提供的有函数式接口,主要是在 java.util.function 包中。
Supplier(供给型或生产型接口):无参有返回值的接口,对于的Lambda表达式需要提供一个返回数据的类型。
Consumer(消费型接口):该接口中的方法可以接收一个参数,接收的参数类型由泛型指定,对参数的操作 方式根据该接口的实现类决定,不需要返回值。
扩展方法 --> andThen 。如果一个方法的参数和返回值全部是Consumer类型,那么就可以实现效果,消费一个数据的时候,先通过调用者对象处理参数,将处理的结果再通过f对象处理,将两个处理的结果进行返回。
Function(函数型接口):有参有返回值的接口,Function接口是根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有参数有返回值。
扩展方法:同样具有andThen,另外compose方法的作用顺序和andThen方法刚好相反而静态方法identity则是,输入什么参数就返回什么参数(参考源码)
Predicate(断言型接口):有参且返回值为Boolean的接口。
扩展方法:
and(Predicate p):先将参数通过调用者判断真假,再将参数通过p判断真假,全真为真,否则为假
or(Predicate p):全假为假,否则为真
negate():取反
三、方法引用和构造引用
符号表示: :: 符号说明:双冒号为方法引用运算符,而它所在的表达式被称为 方法引用 应用场景:如果Lambda 表达式所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方 法引用。 常见的引用方式: 方法引用在JDK8中使用是相当灵活的,有以下几种形式: 1. instanceName :: methodName 对象 :: 方法名 2. ClassName::staticMethodName 类名 :: 静态方法 3. ClassName :: methodName 类名 :: 普通方法 4. ClassName :: new 类名 :: new 调用的构造器 5. TypeName[] :: new String[] :: new 调用数组的构造器四、Stream API流
java8提供的stream api可以让程序员像操作数据库一样操作集合,通过各种条件对集合进行一次性的过滤,省去对集合的反复过滤存储塞选的过程。
1.Stream的特点:
- 元素是特定类型的对象,形成一个队列。
- java中的Stream并不会存储元素,而是按需计算。
- 数据来源可以是集合,数组,I/O channel,产生器generator和IntStream等
- 聚合操作类似sql语句一样的操作,比如filter、map、reduce、find、match、sorted等。
- 根据Collection获取
public static void main(String[] args) {List list = new ArrayList<>();list.stream();Set set = new HashSet<>();set.stream();Vector vector = new Vector();vector.stream();}
注:Map没有实现Collection接口,可通过获取对应的key或value集合获取
- 通过Stream的of方法
public static void main(String[] args) {Stream a1 = Stream.of("a1", "a2", "a3");String[] arr1 = {"aa","bb","cc"};Stream arr11 = Stream.of(arr1);Integer[] arr2 = {1,2,3,4};Stream arr21 = Stream.of(arr2);arr21.forEach(System.out::println);// 注意:基本数据类型的数组是不行的int[] arr3 = {1,2,3,4};Stream.of(arr3).forEach(System.out::println);}
3.Stream 常用方法:
终结方法:返回值类型不再是 Stream 类型的方法,不再支持链式调用。 非终结方法:返回值类型仍然是 Stream 类型的方法,支持链式调用。
| 方法名 | 方法作用 | 返回值类型 | 方法种类 |
| count | 统计个数 | long | 终结 |
| forEach | 逐一处理 | void | 终结 |
| filter | 过滤 | Stream | 函数拼接 |
| limit | 取用前几个 | Stream | 函数拼接 |
| skip | 跳过前几个 | Stream | 函数拼接 |
| nap | 映射 | Stream | 函数拼接 |
| concat | 组合 | Stream | 函数拼接 |
- Stream只能操作一次
- Stream方法返回的是新的流
- Stream不调用终结方法,中间的操作不会执行
- 通过List接口中的parallelStream方法来获取
- 通过已有的串行流转换为并行流(parallel)
public void test02(){List list = new ArrayList<>();// 通过List 接口 直接获取并行流Stream integerStream = list.parallelStream();// 将已有的串行流转换为并行流Stream parallel = Stream.of(1, 2, 3).parallel();} /*** 并行流操作*/@Testpublic void test03(){Stream.of(1,4,2,6,1,5,9).parallel() // 将流转换为并发流,Stream处理的时候就会通过多线程处理.filter(s->{System.out.println(Thread.currentThread() + " s=" +s);return s > 2;}).count();}
在多线程的处理下,数据安全问题处理:
- 加同步锁
- 使用线程安全的容器
- 通过Stream中的toArray/collect操作
/*** 使用线程安全的容器*/@Testpublic void test03(){Vector v = new Vector();Object obj = new Object();IntStream.rangeClosed(1,1000).parallel().forEach(i->{synchronized (obj){v.add(i);}});System.out.println(v.size());}/*** 将线程不安全的容器转换为线程安全的容器*/@Testpublic void test04(){List listNew = new ArrayList<>();// 将线程不安全的容器包装为线程安全的容器List synchronizedList = Collections.synchronizedList(listNew);Object obj = new Object();IntStream.rangeClosed(1,1000).parallel().forEach(i->{synchronizedList.add(i);});System.out.println(synchronizedList.size());}/*** 我们还可以通过Stream中的 toArray方法或者 collect方法来操作* 就是满足线程安全的要求*/@Testpublic void test05(){List listNew = new ArrayList<>();Object obj = new Object();List list = IntStream.rangeClosed(1, 1000).parallel().boxed().collect(Collectors.toList());System.out.println(list.size());}
- Fork join 了解,参考
https://www.cnblogs.com/buptlyh/p/16596907.html
全网最全Fork-Join讲解(从概括到实战)_fork join_王翔:的博客-CSDN博客
五、接口中的新增 默认方法和静态方法
- 默认方法:
当类A、B、C、D都实现了接口xxInterface,此时在接口xxInterface中新增一个方法,那么A、B、C、D都必须实现这个方法,但是想象一下,如果有数百个类实现了一个接口,那么几乎不可能更改所有这些类中的代码。这就是为什么在Java8中,我们有了一个新概念“默认方法”。这些方法可以添加到任何现有接口中,我们不需要强制在实现类中实现这些方法,因此我们可以在不破坏代码的情况下将这些默认方法添加到现有接口中。我们只需要在新增的方法中使用default即可。
语法规则:
interface 接口名{
修饰符 default 返回值类型 方法名{
方法体;
}
}
使用方式:
1. 实现类直接调用接口的默认方法
2. 实现类重写接口的默认方法
- 静态方法
作用也是为了接口的扩展。
语法规则:
interface 接口名{
修饰符 static 返回值类型 方法名{
方法体;
}
}
使用方式:
接口中的静态方法在实现类中是不能被重写的,调用的话只能通过接口类型来实现: 接口名.静态方法名();
- 默认方法和静态方法两者的区别介绍
1. 默认方法通过实例调用,静态方法通过接口名调用
2. 默认方法可以被继承,实现类可以直接调用接口默认方法,也可以重写接口默认方法
3. 静态方法不能被继承,实现类不能重写接口的静态方法,只能使用接口名调用
六、新时间日期API
1. 旧版日期时间的问题 在旧版本中 JDK 对于日期和时间这块的时间是非常差的。 /*** 旧版日期时间设计的问题*/@Testpublic void test01() throws Exception{// 1.设计不合理Date date = new Date(2021,05,05);System.out.println(date);// 2.时间格式化和解析操作是线程不安全的SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");for (int i = 0; i < 50; i++) {new Thread(()->{// System.out.println(sdf.format(date));try {System.out.println(sdf.parse("2021-05-06"));} catch (ParseException e) {e.printStackTrace();}}).start();}} - 设计不合理,在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间的,而 java.sql.Date仅仅包含日期,此外用于格式化和解析的类在java.text包下。
- 非线程安全,java.util.Date是非线程安全的,所有的日期类都是可变的,这是java日期类最大的问题之一。
- 时区处理麻烦,日期类并不提供国际化,没有时区支持。
- LocalDate :表示日期,包含年月日,格式为 2019-10-16
- LocalTime :表示时间,包含时分秒,格式为 16:38:54.158549300
- LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
- DateTimeFormatter :日期时间格式化类。
- Instant:时间戳,表示一个特定的时间瞬间。
- Duration:用于计算2个时间(LocalTime,时分秒)的距离
- Period:用于计算2个日期(LocalDate,年月日)的距离
- ZonedDateTime :包含时区的时间
- ThaiBuddhistDate:泰国佛教历
- MinguoDate:中华民国历
- JapaneseDate:日本历
- HijrahDate:伊斯兰历
2.1 日期时间的常见操作
LocalDate, LocalTime 以及 LocalDateTime 的操作。/*** JDK8 日期时间操作*/@Testpublic void test01(){// 1.创建指定的日期LocalDate date1 = LocalDate.of(2021, 05, 06);System.out.println("date1 = "+date1);// 2.得到当前的日期LocalDate now = LocalDate.now();System.out.println("now = "+now);// 3.根据LocalDate对象获取对应的日期信息System.out.println("年:" + now.getYear());System.out.println("月:" + now.getMonth().getValue());System.out.println("日:" + now.getDayOfMonth());System.out.println("星期:" + now.getDayOfWeek().getValue());}/*** 时间操作*/@Testpublic void test02(){// 1.得到指定的时间LocalTime time = LocalTime.of(5,26,33,23145);System.out.println(time);// 2.获取当前的时间LocalTime now = LocalTime.now();System.out.println(now);// 3.获取时间信息System.out.println(now.getHour());System.out.println(now.getMinute());System.out.println(now.getSecond());System.out.println(now.getNano());}/*** 日期时间类型 LocalDateTime*/@Testpublic void test03(){// 获取指定的日期时间LocalDateTime dateTime =LocalDateTime.of(2020, 06, 01, 12, 12, 33, 213);System.out.println(dateTime);// 获取当前的日期时间LocalDateTime now = LocalDateTime.now();System.out.println(now);// 获取日期时间信息System.out.println(now.getYear());System.out.println(now.getMonth().getValue());System.out.println(now.getDayOfMonth());System.out.println(now.getDayOfWeek().getValue());System.out.println(now.getHour());System.out.println(now.getMinute());System.out.println(now.getSecond());System.out.println(now.getNano());} 2.2 日期时间的修改和比较 /*** 日期时间的修改*/@Testpublic void test01(){LocalDateTime now = LocalDateTime.now();System.out.println("now = "+now);// 修改日期时间 对日期时间的修改,对已存在的LocalDate对象,创建了它模板// 并不会修改原来的信息LocalDateTime localDateTime = now.withYear(1998);System.out.println("now :"+now);System.out.println("修改后的:" + localDateTime);System.out.println("月份:" + now.withMonth(10));System.out.println("天:" + now.withDayOfMonth(6));System.out.println("小时:" + now.withHour(8));System.out.println("分钟:" + now.withMinute(15));// 在当前日期时间的基础上 加上或者减去指定的时间System.out.println("两天后:" + now.plusDays(2));System.out.println("10年后:"+now.plusYears(10));System.out.println("6个月后 = " + now.plusMonths(6));System.out.println("10年前 = " + now.minusYears(10));System.out.println("半年前 = " + now.minusMonths(6));System.out.println("一周前 = " + now.minusDays(7));}/*** 日期时间的比较*/@Testpublic void test02(){LocalDate now = LocalDate.now();LocalDate date = LocalDate.of(2020, 1, 3);// 在JDK8中要实现 日期的比较 isAfter isBefore isEqual 通过这几个方法来直接比较System.out.println(now.isAfter(date)); // trueSystem.out.println(now.isBefore(date)); // falseSystem.out.println(now.isEqual(date)); // false} 注意:在进行日期时间修改的时候,原来的LocalDate对象是不会被修改,每次操作都是返回了一个新的 LocalDate对象,所以在多线程场景下是数据安全的。 2.3 格式化和解析操作 在 JDK8 中我们可以通过 java.time.format.DateTimeFormatter 类可以进行日期的解析和格式化操作 /*** 日期格式化*/@Testpublic void test01(){LocalDateTime now = LocalDateTime.now();// 指定格式 使用系统默认的格式 2021-05-27T16:16:38.139DateTimeFormatter isoLocalDateTime =DateTimeFormatter.ISO_LOCAL_DATE_TIME;// 将日期时间转换为字符串String format = now.format(isoLocalDateTime);System.out.println("format = " + format);// 通过 ofPattern 方法来指定特定的格式DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM-dd HH:mm:ss");String format1 = now.format(dateTimeFormatter);// 2021-05-27 16:16:38System.out.println("format1 = " + format1);// 将字符串解析为一个 日期时间类型LocalDateTime parse = LocalDateTime.parse("1997-05-06 22:45:16",dateTimeFormatter);// parse = 1997-05-06T22:45:16System.out.println("parse = " + parse);} 2.4 Instant 类 在JDK8 中给我们新增一个 Instant 类 ( 时间戳 / 时间线 ) ,内部保存了从 1970 年 1 月 1 日 00:00:00 以来的秒和纳秒 /*** Instant 时间戳* 可以用来统计时间消耗*/@Testpublic void test01() throws Exception{Instant now = Instant.now();System.out.println("now = " + now);// 获取从1970年一月一日 00:00:00 到现在的 纳秒System.out.println(now.getNano());Thread.sleep(5);Instant now1 = Instant.now();System.out.println("耗时:" + (now1.getNano() - now.getNano()));} 2.5 计算日期时间差 JDK8 中提供了两个工具类 Duration/Period :计算日期时间差 - Duration:用来计算两个时间差(LocalTime)
- Period:用来计算两个日期差(LocalDate)
/*** 计算日期时间差*/@Testpublic void test01(){// 计算时间差LocalTime now = LocalTime.now();LocalTime time = LocalTime.of(22, 48, 59);System.out.println("now = " + now);// 通过Duration来计算时间差Duration duration = Duration.between(now, time);System.out.println(duration.toDays()); // 0System.out.println(duration.toHours()); // 6System.out.println(duration.toMinutes()); // 368System.out.println(duration.toMillis()); // 22124240// 计算日期差LocalDate nowDate = LocalDate.now();LocalDate date = LocalDate.of(1997, 12, 5);Period period = Period.between(date, nowDate);System.out.println(period.getYears()); // 23System.out.println(period.getMonths()); // 5System.out.println(period.getDays()); // 22} 2.6 时间校正器 有时候我们可以需要如下调整:将日期调整到" 下个月的第一天 " 等操作。这时我们通过时间校正器效果可能会更好。 - TemporalAdjuster:时间校正器
- TemporalAdjusters:通过该类静态方法提供了大量的常用TemporalAdjuster的实现。
/*** 时间校正器*/@Testpublic void test02(){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;};// 我们可以通过TemporalAdjusters 来实现// LocalDateTime nextMonth = now.with(adJuster);LocalDateTime nextMonth =now.with(TemporalAdjusters.firstDayOfNextMonth());System.out.println("nextMonth = " + nextMonth);} 2.7 日期时间的时区 Java8 中加入了对时区的支持, LocalDate 、 LocalTime 、 LocalDateTime 是不带时区的,带时区的日 期时间类分别为:ZonedDate 、 ZonedTime 、 ZonedDateTime 。 其中每个时区都对应着 ID , ID 的格式为 “ 区域 / 城市 ” 。例如 : Asia/Shanghai 等。 ZoneId :该类中包含了所有的时区信息 /*** 时区操作*/@Testpublic void test01() {// 1.获取所有的时区id// ZoneId.getAvailableZoneIds().forEach(System.out::println);// 获取当前时间 中国使用的 东八区的时区,比标准时间早8个小时LocalDateTime now = LocalDateTime.now();System.out.println("now = " + now); // 2021-05-27T17:17:06.951// 获取标准时间ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC());System.out.println("bz = " + bz); // 2021-05-27T09:17:06.952Z// 使用计算机默认的时区,创建日期时间ZonedDateTime now1 = ZonedDateTime.now();System.out.println("now1 = " + now1); //2021-05-27 T17:17:06.952 + 08:00[Asia / Shanghai]// 使用指定的时区创建日期时间ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Marigot"));System.out.println("now2 = " + now2);} JDK新的日期和时间API的优势: - 新版日期时间API中,日期和时间对象是不可变,操作日期不会影响原来的值,而是生成一个新的 实例
- 提供不同的两种方式,有效的区分了人和机器的操作
- TemporalAdjuster可以更精确的操作日期,还可以自定义日期调整期
- 线程安全
七、Optional
这个Optional 类注意是解决空指针的问题 1. 以前对 null 的处理 @Testpublic void test01(){//String userName = "张三";String userName = null;if(userName != null){System.out.println("字符串的长度:" + userName.length());}else{System.out.println("字符串为空");}} 2. Optional类 Optional是一个没有子类的工具类, Optional 是一个可以为 null 的容器对象,它的主要作用就是为了避 免Null 检查,防止 NullpointerException。 3. Optional 的基本使用 Optional 对象的创建方式 /*** Optional对象的创建方式*/@Testpublic void test02(){// 第一种方式 通过of方法 of方法是不支持null的Optional op1 = Optional.of("zhangsan");//Optional 4. Optional的常用方法 /*** Optional中的常用方法介绍* get(): 如果Optional有值则返回,否则抛出NoSuchElementException异常* get()通常和isPresent方法一块使用* isPresent():判断是否包含值,包含值返回true,不包含值返回false* orElse(T t):如果调用对象包含值,就返回该值,否则返回t* orElseGet(Supplier s):如果调用对象包含值,就返回该值,否则返回 Lambda表达式的返回值*/@Testpublic void test03(){Optional op1 = Optional.of("zhangsan");Optional op2 = Optional.empty();// 获取Optional中的值if(op1.isPresent()){String s1 = op1.get();System.out.println("用户名称:" +s1);}if(op2.isPresent()){System.out.println(op2.get());}else{System.out.println("op2是一个空Optional对象");}String s3 = op1.orElse("李四");System.out.println(s3);String s4 = op2.orElse("王五");System.out.println(s4);String s5 = op2.orElseGet(()->{return "Hello";});System.out.println(s5);} @Testpublic void test04(){Optional op1 = Optional.of("zhangsan");Optional op2 = Optional.empty();// 如果存在值 就做什么op1.ifPresent(s-> System.out.println("有值:" +s));op1.ifPresent(System.out::println);} /*** 自定义一个方法,将Person对象中的 name 转换为大写 并返回*/@Testpublic void test05(){Person p = new Person("zhangsan",18);Optional op = Optional.of(p);String name = getNameForOptional(op);System.out.println("name="+name);}/*** 根据Person对象 将name转换为大写并返回* 通过Optional方式实现* @param op* @return*/public String getNameForOptional(Optional op){if(op.isPresent()){String msg = //op.map(p -> p.getName())op.map(Person::getName)//.map(p -> p.toUpperCase()).map(String::toUpperCase).orElse("空值");return msg;}return null;}/*** 根据Person对象 将name转换为大写并返回* @param person* @return*/public String getName(Person person){if(person != null){String name = person.getName();if(name != null){return name.toUpperCase();}else{return null;}}else{return null;}}
八、其他特性
1. 重复注解 自从Java 5 中引入 注解 以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注 解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK 8 引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK 8 中使用 @Repeatable 注解定义重复注解。 1.1 定义一个重复注解的容器 @Retention(RetentionPolicy.RUNTIME)public @interface MyAnnotations {MyAnnotation[] value();} 1.2 定义一个可以重复的注解 @Repeatable(MyAnnotations.class)@Retention(RetentionPolicy.RUNTIME)public @interface MyAnnotation {String value();} 1.3 配置多个重复的注解 @MyAnnotation("test1")@MyAnnotation("test2")@MyAnnotation("test3")public class AnnoTest01 {@MyAnnotation("fun1")@MyAnnotation("fun2")public void test01(){}} 1.4 解析得到指定的注解 /*** 解析重复注解* @param args*/public static void main(String[] args) throws NoSuchMethodException {// 获取类中标注的重复注解MyAnnotation[] annotationsByType =AnnoTest01.class.getAnnotationsByType(MyAnnotation.class);for (MyAnnotation myAnnotation : annotationsByType) {System.out.println(myAnnotation.value());}// 获取方法上标注的重复注解MyAnnotation[] test01s = AnnoTest01.class.getMethod("test01").getAnnotationsByType(MyAnnotation.class);for (MyAnnotation test01 : test01s) {System.out.println(test01.value());}} 2. 类型注解 JDK 8 为 @Target 元注解新增了两种类型: TYPE_PARAMETER , TYPE_USE 。 - TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。 类型参数声明如: 、
- TYPE_USE :表示注解可以再任何用到类型的地方使用。
@Target(ElementType.TYPE_PARAMETER)
public @interface TypeParam {
} 使用: public class TypeDemo01 <@TypeParam T> {public <@TypeParam K extends Object> K test01(){return null;}} TYPE_USE @Target(ElementType.TYPE_USE)
public @interface NotNull {
} 使用 public class TypeUseDemo01 {public @NotNull Integer age = 10;public Integer sum(@NotNull Integer a,@NotNull Integer b){return a + b;}}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
