Java8新特性
Lambda表达式详见https://www.cnblogs.com/zys2019/p/13778194.html
1.Stream API
1.1概述
Stream 是Java8中处理集合的关键抽象概念,它可以对集合进行非常复杂的操作。
1.2Stream的操作步骤
1)创建Stream:从一个数据源中获取流。 2)中间操作:对数据源的数据进行操作。 3)终止操作:执行中间操作链,并产生结果。
1.3创建流的方式
1)通过Collection集合提供的stream()或parallelStream()创建
List<Integer> list=new ArrayList<>(); Stream<Integer> stream = list.stream();
2)通过Arrays中的静态方法stream()获取数组流创建
String[] arr = {"java", "python", "c++"}; Stream<String> stream1 = Arrays.stream(arr);
3)通过Stream类的静态方法of创建
Stream<Integer> stream = Stream.of(1, 5, 7, 9, 20, 1);
4)无限流
Stream.generate(()->Math.random()).forEach(System.out::println);
System.out::println就是方法的引用,当此接口有返回值是才能使用。
1.4中间操作
多个中间操作会连接成一个流水线,只有触发终止操作时才会去真正的处理操作得到结果,否则不会进行任何的处理。
1.4.1筛选和切片
1)filter:接收Lambda,从流中排除某些操作
案例:过滤集合中所有的偶数
List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100); list.stream().filter(e -> e % 2 == 0).forEach(System.out::println);
2)distinct:筛选,通过流所生成元素的hashCode()和equals()去除重复元素。
案例:去除集合中的重复元素
List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100); list.stream().distinct().forEach(System.out::println);
需要注意的是,使用此方法,必须对集合中存储的数据类对象重新hashCode()和equals()方法。这里使用的是Integer类型,它已经重写了,如果是自定义的类型,则必须手动重写,下图就是对自定义的User类重写:
3)limit:截断流,使其元素不超过给定对象
案例:只获取集合中的前5个元素
List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100); list.stream().limit(5).forEach(System.out::println);
4)skip(n):跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
案例:只获取集合中第6个以后的元素
List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100); list.stream().skip(6).forEach(System.out::println);
1.4.2映射
1)map:接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。map在接收到流后,直接将Stream放入到一个Stream中,最终整体返回一个包含了多个Stream的Stream。
案例:遍历集合中的每一个元素,将其转为大写
List<String> list = Arrays.asList("abc","bbb","cbd","ddd"); list.stream().map(e->e.toUpperCase()).forEach(System.out::println);
2)flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。flatMap在接收到Stream后,会将接收到的Stream中的每个元素取出来放入一个Stream中,最后将一个包含多个元素的Stream返回。
案例:对上述的集合元素取出每一个元素的每一个内容
public static Stream<Character> getCharacterByString(String str) { List<Character> characterList = new ArrayList<>(); for (Character character : str.toCharArray()) { characterList.add(character); } return characterList.stream(); } @Test public void test() { List<String> list = Arrays.asList("abc","bbb","cbd","ddd"); //MyTest是类名 list.stream().flatMap(MyTest::getCharacterByString).forEach(System.out::print); }
打印结果是abcbbbcbdddd。
1.4.3排序
1)sorted():自然排序(Comparable)
案例:使用默认从小到大排序
List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100); //使用默认的排序方法,从小到大 list.stream().sorted().forEach(System.out::println);
2)sorted(Comparator com):定制排序(Comparator)
案例:自定义从大到小排序
List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100); //使用自定义的排序方法,从大到小 list.stream().sorted((e1,e2)-> e2-e1).forEach(System.out::println);
案例:使用stream与不使用stream的对比
List<String> list = new ArrayList<>(); for (int i = 0; i < 999999; i++) { list.add("测试效率" + i); } //开始时间戳 Long startTime = System.currentTimeMillis(); list.forEach(item -> { }); //开始时间戳 Long endTime = System.currentTimeMillis(); System.out.println("遍历的时间:" + (endTime - startTime));//45 //开始时间戳 Long startTime2 = System.currentTimeMillis(); list.stream().forEach(item -> { }); //开始时间戳 Long endTime2 = System.currentTimeMillis(); System.out.println("遍历的时间2:" + (endTime2 - startTime2));//10
通过它们执行的时间的对比,可以发现使用stream的效率远高于不使用stream,对代码的运行速度有很大的提高。
1.5终止操作
1.5.1查找和匹配
1)allMatch:检查是否匹配所有元素。都符合条件返回true,否则返回false
案例:判断集合中的元素是否都大于50
List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100); boolean b = list.stream().allMatch(e -> e > 50); System.out.println(b);
2)anyMatch:检查是否至少匹配一个元素。只要有一个符合就返回true,否则返回false
3)noneMatch:检查是否没有匹配所有元素
4)findFirst:返回第一个元素。返回值是Optional类型,再使用get方法获取内容。
案例:获取集合的第一个元素
List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100); Optional<Integer> first = list.stream().findFirst(); System.out.println(first.get());
5)findAny:返回当前流中的任意元素。返回值是Optional类型
6)count:返回流中元素的总个数
7)max:返回流中最大值。返回值是Optional类型
案例:获取集合中的最大值
List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100); Optional<Integer> max = list.stream().max((o1, o2) -> o1 - o2); System.out.println(max.get());
8)min:返回流中最小值。返回值是Optional类型
1.5.2归约
将流中元素反复结合起来,得到一个值。方法是reduce
案例:把1-100放到集合中并求和
List<Integer> integerList = new ArrayList<>(100); for (int i = 1; i <= 100; i++) { integerList.add(i); } final Integer reduce = integerList.stream().reduce(0, (x, y) -> x + y); System.out.println("结果为:" + reduce);//5050
1.5.3收集
将流转换为其他形式,接收一个Collector接口实现 ,用于给Stream中汇总的方法。方法是collect
案例:计算平均值
List<Integer> list = Arrays.asList(10, 30, 10, 35, 75, 21, 54, 99, 100); Double collect = list.stream().collect(Collectors.averagingInt(p -> p)); System.out.println(collect);
1.6实战演练
1.6.1 stream转集合
先看需求:现有一个包含用户对象的集合,想只获取这些用户的姓名组成一个集合,如何去做?
用户对象如下:
@Data public class User { private Integer id; private String name; private String phone; }
列表如下(演示数据):
List<User> userList = new ArrayList<>(); for (int i = 0; i < 5; i++) { User user = new User(); user.setId(i + 1); user.setName("张" + (i + 1) + "方"); userList.add(user); }
第一种方式:遍历用户对象的集合进行获取
List<String> nameList = new ArrayList<>(); userList.stream().forEach(user -> nameList.add(user.getName()));
第二种方式:使用流方式提前数据(推荐)
List<String> nameList = userList.stream().map(User::getName).collect(Collectors.toList());
可使用collect方法将stream转成集合,其效率明显优于循环方式。
2.Optional类
2.1概述
Optional类是一个容器类,代表一个值存在或不存在,主要解决的问题是空指针异常。
2.2常用方法
先创建一个实体类,供下面使用:
public class User { private String name; private Integer age; //set和get,toString方法在此略,请自行加入 }
1)Optional.of(T t):创建一个实例,返回值是Optional
当返回值是Optional时,要获取里面的内容,再使用get方法即可。当是空实例时,使用get方法就会抛出异常
User user = new User(); Optional<User> opt = Optional.of(user); System.out.println(opt.get());//User{name='null', age=null}
2)Optional.empty(T t):创建一个空的实例,返回值是Optional
获取抛出异常:
Optional<User> opt = Optional.empty(); System.out.println(opt.get());//No value present
3)Optional.ofNullable(T t):若T不为null则创建一个实例,否则创建一个空实例,返回值是Optional
User user = new User(); Optional<User> opt = Optional.ofNullable(user); System.out.println(opt.get());//User{name='null', age=null} Optional<User> opt2 = Optional.ofNullable(null); System.out.println(opt2.get());//No value present
4)isPresent():判断是否包含值,包含值返回true,否则返回false
有了这个方法,就可以对第二个方法进行改写,就不会出现异常。
Optional<User> opt = Optional.empty(); if (opt.isPresent()) { System.out.println(opt.get());//No value present }else{ System.out.println("opt不包含值"); } //opt不包含值
5)isPresent():在value值不为空时,做一些操作。
6)orElse(T t):如果调用对象包含值就返回该值,否则返回默认值t,必须和ofNullable结合使用。此方法也使用的比较多,为null时进行替换。
String nullValue = null; String notNullValue = "嘿嘿嘿"; String replaceValue = "哈哈哈"; String optional = Optional.ofNullable(nullValue).orElse(replaceValue); System.out.println(optional);//哈哈哈 String nonNullOptional = Optional.ofNullable(notNullValue).orElse(replaceValue); System.out.println(nonNullOptional);//嘿嘿嘿
7)orElseGet(Supplier s):如果调用对象包含值就返回该值,否则返回默认值s,必须和ofNullable结合使用。
String nullValue = null; String notNullValue = "嘿嘿嘿"; String replaceValue = "哈哈哈"; String optionalGet = Optional.ofNullable(nullValue).orElseGet(() -> replaceValue); System.out.println(optionalGet);//哈哈哈 String nonNullOptionalGet = Optional.ofNullable(notNullValue).orElseGet(() -> replaceValue); System.out.println(nonNullOptionalGet);//嘿嘿嘿
orElseGet和orElse达到的效果是一样的,只是他们的参数不同。
8)orElseThrow(Supplier s):如果值是null就会抛出异常。
Optional.ofNullable(null).orElseThrow(()->new Exception("用户不存在"));
9)map(Function f):如果有值对其处理,就返回处理后的Optional,否则返回空实例
public class MyTest { @Test public void testMap() { C c = null; Optional<Optional<String>> s1 = Optional.ofNullable(c).map(MyTest::getOrderNumber); System.out.println(s1);//Optional.empty c = new C("hello"); Optional<Optional<String>> s2 = Optional.ofNullable(c).map(MyTest::getOrderNumber); System.out.println(s2.get().get());//hello } private static Optional<String> getOrderNumber(C c) { return Optional.ofNullable(c).map(f -> f.getOrderNumber()); } } class C { private String orderNumber; public String getOrderNumber() { return orderNumber; } public C(String orderNumber) { this.orderNumber = orderNumber; } }
2.3实战演练
1)示例1:替换if 判断是否为空
以前的写法
User user = new User(); if (user != null) { System.out.println(user.toString()); }
现在的写法
User user = new User(); Optional.ofNullable(user).ifPresent(u -> { System.out.println(user.toString()); });
虽然现在的写法比以前的写法看起来复杂,实际上代码更优雅。
2)示例2:替换if-else 判断是否为空
以前的写法
User user = new User(); if (user != null) { System.out.println("我不是null"); } else { System.out.println("我是null"); }
现在的写法
User user = new User(); Optional.ofNullable(user).filter(u -> { System.out.println("我不是null"); return true; }).orElseGet(()->{ System.out.println("我是null"); return null; });
需要注意的是,一定要进行return。
3)示例2:替换if-else 判断集合是否为空,不为空且有数据时取第一条
以前的写法
List<String> list = new ArrayList(); list.add("123"); list.add("555"); if (list != null && list.size() > 0) { System.out.println(list.get(0)); }
现在的写法
List<String> list = new ArrayList(); list.add("123"); list.add("555"); Optional.ofNullable(list).filter(l -> l.size() > 0).filter(u -> { System.out.println(list.get(0)); return true; });
3.接口的方法新特性
在jdk1.8中,在接口中除了能定义抽象的方法外,还可以定义默认的方法和静态的方法。
3.1接口中的默认方法
默认方法使用default修饰,并且需要些方法体,当一个类实现它时,子类会继承父类的这个方法,可以对这个方法进行重写。
1)接口的定义
public interface A { default int sum(int a,int b){ return a+b; } }
2)实现类
public class A1 implements A { }
3)调用
@Test public void test10() { A a = new A1(); int sum = a.sum(10, 20); System.out.println(sum); }
执行结果是30。
有这样一种情况,当子类不仅有实现,还有继承时,如果接口的被继承的类中方法名相同时,那么子类会使用哪个类的方法呢?实际上有类优先的原则,即会先使用类的方法。原因很简单,当继承一个类时,类中的方法必须被重写,自然而然调用的方法就是这个被重写的方法。
3.2接口中的静态方法
静态方法使用static修饰,并通过类名.方法名调用:
1)接口定义
public interface A { static int sum(int a,int b){ return a+b; } }
2)调用
@Test public void test10() { int sum = A.sum(20, 12); System.out.println(sum); }
.