一 Lambda 使用
Lambda表达式应用场景:任何有函数式接口的地方,只有一个抽象方法(Object类中的方法除外)的接口是函数式接口。就像Runnable接口中,只有一个run方法。
- 1、Runnable
//在JDK1.8之前的写法 new Thread(new Runnable() { public void run() { System.out.println("The runable is using!"); } }).start(); //在JDK1.8之后可以使用lambda表达式 new Thread(() -> System.out.println("It's a lambda function")).start();
- 2、排序
对集合进行排序
List<String> list = Arrays.asList("java","javascript","phpa","python"); //在JDK1.8之前的写法 Collections.sort(list, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.length()-o2.length(); } }); //在JDK1.8之后可以使用lambda表达式 Collections.sort(list,(a,b)->a.length()-b.length());
- 3.遍历
遍历集合中的元素
List<String> languages = Arrays.asList("Java","Python","C++"); //在JDK1.8之前的写法 for(String language:languages) { System.out.println(language); } //在JDK1.8之后可以使用lambda表达式 languages.forEach(x -> System.out.println(x));
二 默认方法
- 1.接口中的默认方法和静态方法
jdk1.8接口中允许包含具有具体实现的方法,该方法成为“默认方法”。默认方法使用defaut关键字修饰。
接口示例: public interface IHello { void sayHi(); //接口中的static方法 static void sayHello(){ System.out.println("static method: say hello"); } //接口中的default方法 default void sayByebye(){ System.out.println("default mehtod: say byebye"); } } 实现类示例: public class HelloImpl implements IHello { @Override public void sayHi() { System.out.println("normal method: say hi"); } }
- 默认方法的原则
默认具有“类优先”的原则,若一个接口中定义了一个默认的方法,而另一个父类或接口中又定义了一个同名的方法时,遵循如下的原则
- 1.选择父类中的方法---如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略
示例: public interface MyFunction{ default String getName(){ return "MyFunction"; } } public class MyClass{ public String getName(){ return "MyClass"; } } //创建SubClass类继承MyClass类,并实现MyFunction接口 public class SubClass extends MyClass implements MyFunction{ } 创建示例: public class SubClassTest{ @Test public void testDefaultFunction(){ SubClass subClass = new SubClass(); System.out.println(subClass.getName()); //MyClass } } 测试示例: public class SubClassTest{ @Test public void testDefaultFunction(){ SubClass subClass = new SubClass(); System.out.println(subClass.getName()); //MyClass } }
- 2.接口冲突,如果一个父类接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法,那么必须覆盖来解决冲突。
public interface MyFunction{ default String getName(){ return "function"; } } public interface MyInterface{ default String getName(){ return "interface"; } }
实现类MyClass同时实现MyFunction接口和MyInterface接口,里面同样的方法冲突,所以,MyClass必须覆盖getName的方法来解决冲突
//示例1 public class MyClass{ //方法返回的是:interface @Override public String getName(){ return MyInterface.super.getName(); } } //示例2 public class MyClass{ //方法返回的是:function @Override public String getName(){ return MyFunction.super.getName(); } }
三、Stream*
Stream是Java8中处理集合的关键抽象概念,它可以 指定你希望对集合进行的操作,可以执行非常复杂的查找,过滤和映射数据等操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。
- Stream的特性:
1.不是数据结构,不会存储元素;
2.不支持索引访问;
3.惰性求值/延迟执行,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算;
4.支持并行;
5.很容易生成数组或集合(List,Set);
6.支持过滤,查找,转换,汇总,聚合等操作;
7.不会修改原来的数据源,它会将操作后的数据保存到另外一个对象中(保留意见:毕竟peek方法可以修改流中元素)
- Stream的创建*
获取Stream(常用)
在JDK1.8中, 集合接口有两个方法来生成流:
1)stream() ,为集合创建串行流;
2)parallelStream(),为集合创建并行流。stream()方法更常用一些。
所有的Collection集合都可以通过stream默认方法获取流。Collection接口中加入了default方法stream用来获取流,所以其所有实现类均可获取流
//List集合获取流 List<String> list = new ArrayList<>(); Stream<String> stream1 = list.stream(); Stream<String> stream2 = list.parallelStream(); //Set集合获取流 Set<String> set = new HashSet<>(); Stream<String> stream3 = set.stream(); Stream<String> stream4 = set.parallelStream();
Map接口不是Collection的子接口,且其K-V数据结构不符合元素的单一特性,所以获取对应的流需要分key,value或者entry等情况
Map<String, String> map = new HashMap<>(); Stream<String> keyStream = map.keySet().stream(); Stream<String> valueStream = map.values().stream(); Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
通过Arrays中静态方法stream()获取数组流
//获取数组流的重载方法 public static <T> Stream<T> stream(T[] array) public static Stream stream(T[] array) public static IntStream stream(int[] array) public static LongStream stream(long[] array) public static DoubleStream stream(double[] array) //示例 Integer[] nums = new Integer[]{1,2,3,4,5,6,7,8,9}; Stream<Integer> numStream = Arrays.stream(nums);
通过Stream类静态方法of()获取数组流,可以使用静态方法Stream.of()通过显示值创建一个流,它可以接收任意数量的参数。
Stream类中,提供了两个of()方法,一个只需要传入一个泛型参数,一个需要传入一个可变泛型参数
示例:
String[] array = { "张三", "李四", "王五", "赵六" }; Stream<String> stream = Stream.of(array); //of方法的参数其实是一个可变参数,所以支持数组 Stream<String> stream1 = Stream.of(1, 2, 3, 4, 5);
Stream的中间操作
中间操作在整体上可以分为:筛选与切片、映射、排序
1.filter
功能:用于接收Lambda表达式,从六种排除某些元素。
Stream<Person> stream = list.stream().filter((e) -> { System.out.println("Stream API 中间操作"); //过滤出年龄大于30的数据 return e.getAge() > 30; });
2.limit
//功能:截断流,使其元素不超过给定数量。方法定义: Stream<T> limit(long maxSize); //过滤之后取2个值 list.stream().filter((e) -> e.getAge() >30 ) .limit(2).forEach(System.out ::println);
3.skip
//跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流 //方法定义: Stream<T> skip(long n); //跳过前2个值 list.stream().skip(2).forEach(System.out :: println);
4.distinct--需要重写hashCode() 和equals()方法才可以使用
//筛选,通过流所生成元素的 hashCode() 和 equals() 去 除重复元素。 //方法定义: Stream<T> distinct(); //示例 list.stream().distinct().forEach(System.out :: println);
排序*
// 自然排序 Arrays.asList(1,2,3,4,5,6) .stream() .sorted((a,b)->b-a) .forEach(System.out::println); 结果: 6 5 4 3 2 1 定制排序: //定制排序 List<Employee> persons1 = list.stream().sorted((e1, e2) -> { if (e1.getAge() == e2.getAge()) { return 0; } else if (e1.getAge() > e2.getAge()) { return 1; } else { return -1; } }).collect(Collectors.toList());
查找与匹配*
allMatch:表示检查是否匹配所有元素
只有所有的元素都匹配条件时, allMatch()方法才会返回true
boolean match = employees.stream() .allMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts())); System.out.println(match);
anyMatch:表示检查是否至少匹配一个元素。只要有任意一个元素符合条件,anyMatch()方法就会返回true
boolean match = employees.stream() .anyMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts())); System.out.println(match);
noneMatch:表示检查是否没有匹配所有元素
boolean match = employees.stream() .noneMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts())); System.out.println(match);
findFirst:表示返回第一个元素
Optional<Employee> op = employees.stream() .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())) .findFirst(); System.out.println(op.get());
findAny :返回当前流中任意元素
Optional<Employee> op = employees.stream() .filter((e) -> Employee.Stauts.WORKING.equals(e.getStauts())) .findFirst(); System.out.println(op.get());
count:返回流中元素总数
long count = employees.stream().count(); System.out.println(count);
max:返回流中最大值
示例: Optional<Employee> op = employees.stream() .max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())); System.out.println(op.get());
min:返回流中最小值
Optional<Double> op = employees.stream() .map(Employee::getSalary) .min(Double::compare); System.out.println(op.get());
forEach:表示内部迭代
employees.stream().forEach(System.out::println);
规约
reduce方法的功能,简单来说就是根据一定的规则将stream中的元素进行计算后返回一个唯一的值。
Integer reduce = Arrays.asList(1, 2, 3, 4, 5) .stream() .reduce((a, b) -> a + b) .get(); System.out.print(reduce); //15
收集
Collect接口中方法的实现决定了如何对流执行收集操作,(List,Set,Map)Collectors 实用类提供了很多静态方法,可以方便创建常见收集器实例。
//将过滤后的元素存储到List List<Integer> collect = Arrays.asList(1, 2, 3, 4, 5) .stream() .filter(x -> x % 2 == 0) .collect(Collectors.toList()); System.out.print(collect.toString()); //[2, 4]
新日期类
在旧版的Java中,日期时间API存在问题,其中:
非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一
时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但这些类也有线程安全等问题。
LocalDate、 LocalTime、 LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。 ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法。
具体的示例
// 获取当前系统时间 LocalDateTime localDateTime1 = LocalDateTime.now(); // 2019-10-27T13:49:09.483 System.out.println(localDateTime1); // 指定日期时间 LocalDateTime localDateTime2 = LocalDateTime.of(2019, 10, 27, 13, 45,10); System.out.println(localDateTime2); // 2019-10-27T13:45:10 LocalDateTime localDateTime3 = localDateTime1 // 加三年 .plusYears(3) // 减三个月 .minusMonths(3); // 2022-07-27T13:49:09.483 System.out.println(localDateTime3); // 2019 System.out.println(localDateTime1.getYear()); // 10 System.out.println(localDateTime1.getMonthValue()); // 27 System.out.println(localDateTime1.getDayOfMonth()); // 13 System.out.println(localDateTime1.getHour()); // 52 System.out.println(localDateTime1.getMinute()); // 6 System.out.println(localDateTime1.getSecond()); LocalDateTime localDateTime4 = LocalDateTime.now(); // 2019-10-27T14:19:56.884 System.out.println(localDateTime4); LocalDateTime localDateTime5 = localDateTime4.withDayOfMonth(10); // 2019-10-10T14:19:56.884 System.out.println(localDateTime5);
Instant:用于“时间戳”的运算,它是以Unix元年开始所经历的描述进行运算的
// 默认获取UTC时区 Instant instant1 = Instant.now(); // 2019-10-27T05:59:58.221Z System.out.println(instant1); // 偏移量运算 OffsetDateTime offsetDateTime = instant1.atOffset(ZoneOffset.ofHours(8)); // 2019-10-27T13:59:58.221+08:00 System.out.println(offsetDateTime); // 获取时间戳 // 1572156145000 System.out.println(instant1.toEpochMilli()); // 以Unix元年为起点,进行偏移量运算 Instant instant2 = Instant.ofEpochSecond(60); // 1970-01-01T00:01:00Z System.out.println(instant2);
Duration和Period
Duration是用于计算两个“时间”间隔,Period是用于计算两个“日期”间隔
Instant instant_1 = Instant.now(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Instant instant_2 = Instant.now(); Duration duration = Duration.between(instant_1, instant_2); System.out.println(duration.toMillis()); // 1000 LocalTime localTime_1 = LocalTime.now(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } LocalTime localTime_2 = LocalTime.now(); // 1000 System.out.println(Duration.between(localTime_1, localTime_2).toMillis()); LocalDate localDate_1 = LocalDate.of(2018,9, 9); LocalDate localDate_2 = LocalDate.now(); Period period = Period.between(localDate_1, localDate_2); // 1 System.out.println(period.getYears()); // 1 System.out.println(period.getMonths()); // 18 System.out.println(period.getDays());
时区
Java8 中加入了对时区的支持,带时区的时间分别为:ZonedDate、 ZonedTime、ZonedDateTime。其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式,例如 : Asia/Shanghai 等。
// 通过时区构建LocalDateTime LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("America/El_Salvador")); // 2019-10-27T00:46:21.268 System.out.println(localDateTime1); // 以时区格式显示时间 LocalDateTime localDateTime2 = LocalDateTime.now(); ZonedDateTime zonedDateTime1 = localDateTime2.atZone(ZoneId.of("Africa/Nairobi")); // 2019-10-27T14:46:21.273+03:00[Africa/Nairobi] System.out.println(zonedDateTime1);
Optional*
在开发过程中,NullPointerException可谓是随时随处可见,为了避免空指针异常,常常需要进行一些防御式的检查,所以在代码中常常可见if (null != obj)这样的判断。
在JDK1.8中,Java提供了一个Optional类,Optional类能让我们省掉繁琐的非空的判断。Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
Optional类中的方法:
- of
//创建一个值为one的String类型的Optional Optional<String> ofOptional = Optional.of("one"); //如果我们用of方法创建Optional对象时,所传入的值为null,则抛出NullPointerException Optional<String> nullOptional = Optional.of(null); 异常信息: Exception in thread "main" java.lang.NullPointerException at java.util.Objects.requireNonNull(Objects.java:203) at java.util.Optional.<init>(Optional.java:96) at java.util.Optional.of(Optional.java:108) at com.test.TestDemo.main(TestDemo.java:25)
- ofNullable
//为指定的值创建Optional对象,不管所传入的值为null不为null,创建的时候都不会报错 Optional<String> nullOptional = Optional.ofNullable(null); Optional<String> nullOptional2 = Optional.ofNullable("two");
empty
//创建一个空的String类型的Optional对象 Optional<String> emptyOptional = Optional.empty();
isPresent
可以使用这个isPresent()方法检查一个Optional对象中是否有值,只有值非空才返回true
//在Java8之前,我们一般使用如下方式来检查空值 if(name != null){ System.out.println(name.length); } //在Java8中,我们就可以使用如下方式来检查空值 Optional<String> opt = Optional.of("asd"); opt.ifPresent(name -> System.out.println(name.length()));
orElse和orElseGet*
orElse()方法用来返回Optional对象中的默认值,它被传入一个“默认参数‘。如果对象中存在一个值,则返回它,否则返回传入的“默认参数”。
Optional<String> stringOptional = Optional.of("four"); //four System.out.println(stringOptional.orElse("five")); Optional<String> emptyOptional = Optional.empty(); //six System.out.println(emptyOptional.orElse("six"));
如果创建的Optional中有值存在,则返回此值,否则返回一个由Supplier接口生成的值。orElseGet与orElse()方法类似,但是这个函数不接收一个“默认参数”,而是一个函数接口
Optional<String> stringOptional = Optional.of("seven"); //seven System.out.println(stringOptional.orElseGet(() -> "eight")); Optional<String> emptyOptional = Optional.empty(); //nine System.out.println(emptyOptional.orElseGet(() -> "nine"));
orElseThrow:
如果创建的Optional中有值存在,则返回此值,否则抛出一个由指定的Supplier接口生成的异常:
public class TestDemo { public static void main(String[] args) throws InterruptedException { Optional<String> stringOptional = Optional.of("张三"); System.out.println(stringOptional.orElseThrow(CustomException::new)); Optional<String> emptyOptional = Optional.empty(); System.out.println(emptyOptional.orElseThrow(CustomException::new)); } } class CustomException extends RuntimeException { private static final long serialVersionUID = -4399699891687593264L; public CustomException() { super("自定义异常"); } public CustomException(String message) { super(message); } } 结果: 张三 Exception in thread "main" com.test.CustomException: 自定义异常 at java.util.Optional.orElseThrow(Optional.java:290) at com.test.TestDemo.main(TestDemo.java:26)
map:如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()。
flagMap:如果创建的Optional中的值存在,就对该值执行提供的Function函数调用,返回一个Optional类型的值,否则就返回一个空的Optional对象。
flatMap与map(Funtion)方法类似,区别在于flatMap中的mapper返回值必须是Optional,map方法的mapping函数返回值可以是任何类型T。调用结束时,flatMap不会对结果用Optional封装。
//map方法中的lambda表达式返回值可以是任意类型,在map函数返回之前会包装为Optional。 //但flatMap方法中的lambda表达式返回值必须是Optionl实例 Optional<String> stringOptional = Optional.of("zhangsan"); //lisi System.out.println(stringOptional.flatMap(e -> Optional.of("lisi")).orElse("失败")); stringOptional = Optional.empty(); //失败 System.out.println(stringOptional.flatMap(e -> Optional.empty()).orElse("失败"));
使用Optional处理空指针
1、对于Integer的判空
可以使用Optional.ofNullable来构造一个Optional,然后使用orElse(0)把null 替换为默认值再进行+1操作。
2、对于 String 和字面量的比较
可以把字面量放在前面,比如"OK".equals(s),这样即使s是null也不会出现空指针异常;而对于两个可能为null的字符串变量的equals比较,可以使用Objects.equal,它会做判空处理。
3、对于 ConcurrentHashMap,既然其Key和Value都不支持null,修复方式就是不要把null存进去
HashMap的Key和Value可以存入null,而ConcurrentHashMap看似是HashMap的线程安全版本,却不支持null值的Key和Value,这是容易产生误区的一个地方。
4、对于类似fooService.getBarService().bar().equals(“OK”)的级联调用
需要判空的地方有很多,包括fooService、getBarService()方法的返回值,以及bar方法返回的字符串。如果使用if-else来判空的话可能需要好几行代码,但使用Optional的话一行代码就够了。
5、对于返回的List
由于不能确认其是否为null,所以在调用size方法获得列表大小之前,同样可以使用Optional.ofNullable包装一下返回值,然后通过orElse(Collections.emptyList())实现在List为null的时候获得一个空的List,最后再调用size方法。
private List<String> rightMethod(FooService fooService, Integer i, String s, String t) { log.info("result {} {} {} {}", Optional.ofNullable(i).orElse(0) + 1, "OK".equals(s), Objects.equals(s, t), new HashMap<String, String>().put(null, null)); Optional.ofNullable(fooService) .map(FooService::getBarService) .filter(barService -> "OK".equals(barService.bar())) .ifPresent(result -> log.info("OK")); return new ArrayList<>(); } @GetMapping("right") public int right(@RequestParam(value = "test", defaultValue = "1111") String test) { return Optional.ofNullable(rightMethod(test.charAt(0) == '1' ? null : new FooService(), test.charAt(1) == '1' ? null : 1, test.charAt(2) == '1' ? null : "OK", test.charAt(3) == '1' ? null : "OK")) .orElse(Collections.emptyList()).size(); }
使用判空方式或Optional方式来避免出现空指针异常,不一定是解决问题的最好方式,空指针没出现可能隐藏了更深的Bug