Java1.8新特性
简介
Java 8是Java的一个重大版本,发布了许多非常有用的新特性,包含了各个方面,语言、编译器、库、工具以及JVM等
语言相关新特性
Lambda表达式与函数式接口
函数式接口
简介
Single Abstract Method,是一种特殊类型的接口,该接口有且只能有一个抽象方法
特点
- 有且只能有一个抽象方法
- 提供了一个新的注解@FunctionalInterface,如果接口被这个注解标注,就说明该接口是函数式接口,若多于一个抽象方法,在编译时就会报错(该注解不是必须的)
- 默认方法以及静态方法并不会影响函数式接口的定义,可以随意使用
- 函数式接口支持继承,可以继承多个父接口,但是每个父接口只能有一个抽象方法,且必须是相同的抽象方法
意义
java是面向对象的,但是为了写起来方便,需要向一个方法传递一个方法,但是实际上并不能传递方法,而是传递了只有一个抽象方法的接口的实现类的对象,这样就做到类似传递方法了,其实lambda就是一个对象,函数式接口就是为了Lambda表达式而生的
内置函数式接口
-
Consumer 消费性接口
方法:void accept(T t)
特点:只接受参数用于消费,无返回值
//创建消费对象 Consumer consumer = (a)-> System.out.println(a); //调用 accept(T t) 接受值 consumer.accept(”hello world“); hello world #运行结果
-
Supplier 供给型接口
方法:T get()
特点:不接受参数,只有返回值用于供给
//创建供给对象 Supplier supplier = ()->"我是供货商"; //调用get()方法实现供给 System.out.println(supplier.get()); 我是供货商 #运行结果
-
Function 函数式接口
方法:R apply(T t)
特点:既可以接受参数,又可以接受,同时实现了消费和供给
//创建函数对象 Function<String, String> function = (a)->a.toUpperCase(); //调用方法apply()实现供给和消费 System.out.println(function.apply("goodgoodstudy daydayup")); GOODGOODSTUDY DAYDAYUP #运行结果
-
Predicate 断言式接口
方法:boolean test(T t)
特点:用于进行判断,返回判断结果
//创建断言对象 Predicate<String> predicate = (a)->a.equals("isSuccess"); //调用方法 System.out.println(predicate.test("isSuccess")); System.out.println(predicate.test("isFalse")); #运行结果 true false
自定义函数式接口
//定义一个函数式接口(消费式)
@FunctionalInterface #用于检查编译错误(可以添加,也可以不添加)
public interface FunctionInterfaceTest1 {
void consumer(String str);
}
//测试
//创建对象
FunctionInterfaceTest1 functionInterfaceTest1 = (x)-> System.out.println(x);
//调用方法
functionInterfaceTest1.consumer("冲啊");
冲啊 #运行结果
其它内置也可以按此方式实现;
Lambda 表达式
简介
允许将行为传递进入函数里面(代替了匿名内部类)
特点
- lambda体中调用方法的参数列表和返回值类型,要和函数式接口中抽象方法的参数列表和返回值类型保持一致
- 接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法(可以有默认/静态方法)
- 可以大大减少代码量,使代码变得更加简洁紧凑
格式
- (parameters)-> expression
- (parameters)->{statements;}
使用场景
1、对集合的迭代、操作等(相关见Streams)
List list = Arrays.asList(1,2,3,45,1);
list.forEach(x-> System.out.println(x));
2、代替匿名内部类
接口的默认方法
定义
由 default 修饰的默认方法
特点
-
只能在接口中定义默认方法
-
默认方法不能被直接调用,可以由实现类调用(接口无法直接创建对象,自然无法调用)
-
当一个实现类实现了多个接口,多个接口里都有相同的默认方法时,实现类必须重写该默认方法,否则编译错误
-
实现类使用super关键字指定使用哪个接口的默认方法(当出现第三条冲突时,又想使用其中的一个默认方法时)
public class Moren1 implements Moren,Moren23{ @Override public String nn() { return Moren23.super.nn(); } }
方法的引用
简介
方法引用可以使开发者可以直接引用方法、构造方法或者实例对象。方法引用一般和Lambda表达式配合使用,可以使得java类的构造方法看起来紧凑而简洁
使用条件
- 方法引用所引用的方法的参数列表必须要和函数式接口中抽象方法的参数列表相同(完全一致)
- 方法引用所引用的方法的的返回值必须要和函数式接口中抽象方法的返回值相同(完全一致)
格式
- 静态方法得引用:类::静态方法名
- 类得成员方法引用:类::实例方法名
- 实例对象得成员方法引用:对象::实例方法名
- 构造器引用:类::new
测试
-
创建测试类
public class Person { private int age; private String name; public Person() { } public Person(int age) { this.age = age; } public Person(int age, String name) { this.age = age; this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public static void eat(String food){ System.out.println("正在吃"+food); } public String doIt(String name){ return name+"正在做运动"; } @Override public String toString() { return "ReferenceClass{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
-
构造器引用
#一般构造器引用都是用来给方法提供需要的对象,可以提高扩展性,所以可以使用内置的供给型接口,将对象交给他进行传递供给,使用时记住方法引用所引用的方法的参数列表必须要和函数式接口中抽象方法的参数列表相同; //在普通Lambda得引用(使用哪个构造方法在new对象的时候自己选择) Supplier personSupplier = ()->new Person(18,"孔子"); System.out.println(personSupplier.get()); ReferenceClass{age=18, name='孔子'} #运行结果 //使用构造器引用(必须有相应的构造函数) //获取通过无参构造创建的对象,不需要传递参数,可以使用供给型Supplier接口 Supplier personSupplier1 = Person::new; System.out.println(personSupplier1.get()); ReferenceClass{age=0, name='null'} #运行结果 //获取通过有参构造创建的对象,需要传递参数,并且有返回值,是不是很熟悉,内置函数式接口的Function接口(只能接受一个参数) Function<Integer, Person> function = Person::new; System.out.println(function.apply(17)); ReferenceClass{age=17, name='null'} #运行结果 //如果需要接受俩个参数,可以使用BiFunction接口 BiFunction<Integer,String,Person> biFunction = Person::new; System.out.println(biFunction.apply(14,"墨子")); ReferenceClass{age=14, name='墨子'} #运行结果 //若还需要更多的参数,可以自定义实现一个函数式接口,同以上一样调用即可(下面为需要3个参数的函数式接口) @FunctionalInterface public interface ThreeFunction<T,U,A,R> { R apply(T t,U u,A a); }
Tip:数组的创建与对象相同,格式为:String[]::new,可以使用Function接口,传入数组的长度即可;
-
实例对象得成员方法引用
//创建对象 Person person = new Person(); //引用方法 Function<String,String> function1 = person::doIt; System.out.println(function1.apply("老子")); 老子正在做运动 #测试结果
-
静态方法得引用
//引用静态方法 Consumer<String> consumer = Person::eat; consumer.accept("粑粑"); 正在吃粑粑 #测试结果
-
类得成员方法引用
//引用方法 BiFunction<Person,String,String> function2 = Person::doIt;//等价于 = (x,y)->x.doIt(y) System.out.println(function2.apply(new Person(),"孟子")); 孟子正在做运动 #测试结果
Tip:参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数,第三个参数为返回值(若为供给型可以不加)
重复注解
自从Java 5中引入注解以来,这个特性开始变得非常流行,并在各个框架和项目中被广泛使用。不过,注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解
库的新特性
Optional
Java 应用中最常见的 bug 就是空值异常,为了避免空指针异常,经常需要做一些判断来进行检查,比如说 if(obj != null) 。Java 8 之前,Google Guava 引入了 Optionals 类来解决 NullPointerException,从而避免源码被各种 null 检查污染,以便开发者写出更加整洁的代码。Java 8 也将 Optional 加入了官方库;
相关方法
of
作用:把指定的值封装为Optional对象,如果指定的值为null,则抛出NullPointerException
Optional<String> optional = Optional.of("嘉嘉");
System.out.println(optional);
Optional<String> optional1 = Optional.of(null);
# 测试结果
Optional[嘉嘉]
Exception in thread "main" java.lang.NullPointerException
ofNullable
作用:把指定的值封装为Optional对象,如果指定的值为null,则创建一个空的Optional对象
Optional<String> optional2=Optional.ofNullable("嘉仪");
System.out.println(optional2);
Optional<String> optional1 = Optional.ofNullable(null);
System.out.println(optional1);
#测试结果
Optional[嘉仪]
Optional.empty
empty
作用:创建一个空的String类型的Optional对象
Optional<String> optional3 = Optional.empty();
System.out.println(optional3);
#测试结果
Optional.empty
get
作用:如果创建的Optional中有值存在,则返回此值,否则抛出NoSuchElementException
String string = optional2.get();
System.out.println(string);
#测试结果
嘉仪
orElse
作用:如果创建的Optional中有值存在,则返回此值,否则返回一个默认值
System.out.println(optional.orElse("行行"));
System.out.println(optional1.orElse("行行"));
#测试结果
嘉嘉
行行
orElseGet
作用:如果创建的Optional中有值存在,则返回此值,否则返回一个由Supplier接口生成的值
System.out.println(optional.orElseGet(()->"行行"));
System.out.println(optional1.orElseGet(()->"行行"));
#测试结果
嘉嘉
行行
orElseThrow
作用:如果创建的Optional中有值存在,则返回此值,否则抛出一个由指定的Supplier接口生成的异常
System.out.println(optional.orElseThrow(NullPointerException::new));
System.out.println(optional1.orElseThrow(NullPointerException::new));
#测试结果
嘉嘉
Exception in thread "main" java.lang.NullPointerException
filter
作用:如果创建的Optional中的值满足filter中的条件,则返回包含该值的Optional对象,否则返回一个空的Optional对象
System.out.println(optional.filter(x->x.length()>=2).orElse("失败"));
System.out.println(optional.filter(x->x.length()>2).orElse("失败"));
#测试结果
嘉嘉
失败
map
作用:如果创建的Optional中的值存在,对该值执行提供的Function函数调用
Optional<String> Optional5 = Optional.of("haha");
System.out.println(Optional5.map(e -> e.toUpperCase()).orElse("失败"));
#测试结果
HAHA
Streams
简介
允许以声明性方式处理数据集合,可以把Stream流看作是遍历数据集合的一个高级迭代器,将支持数据处理操作的源,生成元素序列,数据源可以是集合,数组或IO资源;
优点
- 代码以声明性方式进行书写,表达清楚,说明了具体的作用是什么
- 可以把几个基础操作连接起来,来表达复杂数据处理的流水线,同时保证代码清晰可读
特点
- 流不存储数据值,流的目的是处理数据,它是关于算法与计算的
- 如果把集合作为流的数据源,创建流时不会导致数据流动;;如果流的终止操作需要值时,流会从集合中获取值;流只使用一次
- 流中心思想是延迟计算,流直到需要时才计算值
- 不会改变数据源,只会产生新的值或者集合
- 延迟执行特性,只有调用终端操作时,中间操作才会执行
流的操作
-
中间操作
每次返回一个新的流,可以有多个
-
终端操作
每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值
流的分类
-
顺序流(stream):stream是顺序流,由主线程按顺序对流执行操作(通过parallel()方法转换为并行流)
-
并行流(parallelStream):内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求,如果流中的数据量足够大,并行流可以加快处理速度
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LBX18it2-1623857453433)(https://i.loli.net/2021/06/16/d79e4Oh1PZoXwgt.png)]
创建流
1、通过 java.util.Collection.stream() 方法用集合创建流
List list = Arrays.asList(1,1,2,3,4);
Stream<String> stream = list.stream();
2、使用java.util.Arrays.stream(T[] array)方法用数组创建流
String[] strings = {"hang","jia","yi"};
Stream<String> stream1 = Arrays.stream(strings);
3、使用Stream的静态方法:of()、iterate()、generate()
Stream<Integer> stream2 = Stream.of(1,2,3,1,2,3); #自定义数据
Stream<Integer> stream3 = Stream.iterate(2,x->x+3).limit(3); #通过迭代生成数据,2:起始值,x->x+3:每次加3,limit(3):限定数据的数量为3个;最终的数据为2,5,8
Stream stream4 = Stream.generate(()->Math.random()).limit(3); #生成3个随机数
StreamAPI
遍历(终端操作)
语法:foreach(Consumer);
List list = Arrays.asList(1,1,2,3,4);
Stream<Integer> stream = list.stream();
stream.forEach(x-> System.out.print(x));
11234 #运行结果
匹配(终端操作)
语法:findFirst
作用:获取第一个元素
String[] strings = {"hang","jia","yi"};
Stream<String> stream1 = Arrays.stream(strings);
Optional optional = stream1.findFirst();
System.out.println(optional);
Optional[hang] #测试结果
语法:findAny()
作用:串行地情况下,一般会返回第一个结果,如果是并行的情况,那就不能确保是第一个
Optional optiona2 = stream1.findAny();
System.out.println(optiona2);
Optional[hang] #测试结果
语法:anyMatch()
作用:判断元素是否存在
Boolean b = stream1.anyMatch(x->x.length()>5);
System.out.println(b);
false #测试结果
规约(终端操作)
语法:reduce()
作用:是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作
//求和
System.out.println(stream.reduce((sum,x)->sum+x));
Optional[11] #测试结果
//求乘积
System.out.println(stream.reduce((sum,x)->sum*x));
Optional[24] #测试结果
//求最值
System.out.println(stream.reduce(Integer::max));
System.out.println(stream.reduce(Integer::min));
Optional[4] #测试结果
Optional[1] #测试结果
聚合(终端操作)
语法:max(Comparator com),min(Comparator com),count()
作用:统计数据
//求最大值
System.out.println(stream.max(Integer::compareTo));
Optional[4] #测试结果
//求最小值
System.out.println(stream.min(Integer::compareTo));
Optional[1] #测试结果
//求数量
System.out.println(stream.count());
5 #测试结果
筛选(中间操作)
语法:filter(Predicate pre)
作用:按条件进行筛选
stream.filter(x->x>2).forEach(x-> System.out.print(x)); #筛选大于2的数据
34 #测试结果
映射(中间操作)
语法:map(Function fun)
作用:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SeMmEmnH-1623857453435)(https://i.loli.net/2021/06/16/mtKYwVbazoFQRhv.png)]
stream.map(x->x+2).forEach(x-> System.out.print(x));
33456 #测试结果
语法:flapMap()
作用:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UO4jGrVq-1623857453436)(https://i.loli.net/2021/06/16/GD7NlB5diMqnO2E.png)]
List<String> list1 = Arrays.asList("j,i,a","y,i");
list1.stream().flatMap(x->{
String[] arr = x.split(",");
Stream<String> stream5 = Arrays.stream(arr);
return stream5;
}).forEach(x-> System.out.print(x));
jiayi #测试结果
收集(中间操作)
作用:使用Collectors提供了一系列用于数据统计的静态方法
-
统计
System.out.println(stream.collect(Collectors.counting())); #统计数据总数 5 #测试结果 System.out.println(stream.collect(Collectors.averagingInt(x->x))); #求平均数 2.2 #测试结果
-
分组
语法:partitioningBy(Function)
作用:将stream按条件分为两个Map;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0G0sbCt4-1623857453438)(https://i.loli.net/2021/06/16/jfcHvMpUitYobDZ.png)]
//进行分组,返回俩个Map,通过true/false获取 Map<Boolean,List<Integer>> map = stream.collect(Collectors.partitioningBy(x->x>2)); map.get(true).forEach(x-> System.out.print(x)); map.get(false).forEach(x-> System.out.print(x)); 34 #测试结果 112 #测试结果
语法:groupingBy(Function)
作用:将集合分为多个Map,有单级分组(一个条件)和多级分组(多个条件)
#单级分组 //进行分组,返回多个Map,通过数字获取 Map<Integer, List<Integer>> map = stream.collect(Collectors.groupingBy(x->x)); map.get(1).forEach(x-> System.out.println(x)); 11 #测试结果 #多级分组 //实现多级分组,可以使用一个由双参数版本的Collectors.groupingBy工厂方法创建收集器,除了普通的分类函数外,还可以接受collector类型的第二个参数。进行二级分组可以把内层的groupingby传递给外层 Map<Integer,Map<Integer, List<Integer>>> map = stream.collect(Collectors.groupingBy(x->x,Collectors.groupingBy(x->x))); #粗略演示怎么分,第一个Integer为外层的筛选条件,第二个为内层的筛选条件
-
结合
语法:joining
作用:可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串
String str = stream1.collect(Collectors.joining("/","{","}")); #第一个参数:连接字符串,第二个:开始字符,第三个:结尾字符 System.out.println(str); {hang/jia/yi} #输出字符
-
归集
# toList/toSet/toMap分别返回List、Set、Map集合
排序(中间操作)
语法:sorted
作用:排序
List list = stream.sorted(Integer::compareTo).collect(Collectors.toList());
Date/Time API(JSR 310)
Java 8引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题。java.util.Date和后来的java.util.Calendar一直没有解决这个问题(甚至令开发者更加迷茫)。
因为上面这些原因,诞生了第三方库Joda-Time,可以替代Java的时间管理API。Java 8中新的时间和日期管理API深受Joda-Time影响,并吸收了很多Joda-Time的精华。新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象;