JDK1.8 新的特性

简介: JDK1.8 新的特性

一 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

目录
相关文章
|
3月前
|
容器
jdk8新特性-详情查看文档
jdk8新特性-详情查看文档
46 3
|
3月前
|
编解码 安全 Java
jdk8新特性-接口和日期处理
jdk8新特性-接口和日期处理
|
4月前
|
Java API
JDK8到JDK25版本升级的新特性问题之使用Collectors.teeing()来计算一个列表中学生的平均分和总分如何操作
JDK8到JDK25版本升级的新特性问题之使用Collectors.teeing()来计算一个列表中学生的平均分和总分如何操作
|
4月前
|
Java API Apache
JDK8到JDK24版本升级的新特性问题之在Java中,HttpURLConnection有什么局限性,如何解决
JDK8到JDK24版本升级的新特性问题之在Java中,HttpURLConnection有什么局限性,如何解决
|
4月前
|
Oracle Java 关系型数据库
JDK8到JDK29版本升级的新特性问题之未来JDK的升级是否会成为必然趋势,如何理解
JDK8到JDK29版本升级的新特性问题之未来JDK的升级是否会成为必然趋势,如何理解
|
4月前
|
Oracle 安全 Java
JDK8到JDK28版本升级的新特性问题之在Java 15及以后的版本中,密封类和密封接口是怎么工作的
JDK8到JDK28版本升级的新特性问题之在Java 15及以后的版本中,密封类和密封接口是怎么工作的
|
4月前
|
Java API 开发者
JDK8到JDK17版本升级的新特性问题之SpringBoot选择JDK17作为最小支持的Java lts版本意味着什么
JDK8到JDK17版本升级的新特性问题之SpringBoot选择JDK17作为最小支持的Java lts版本意味着什么
133 0
JDK8到JDK17版本升级的新特性问题之SpringBoot选择JDK17作为最小支持的Java lts版本意味着什么
|
3月前
|
Java 编译器 API
JDK8新特性--lambda表达式
JDK8的Lambda表达式是Java语言的一大进步。它为Java程序提供了更多的编程方式,让代码更加简洁,也让函数式编程的概念在Java中得到了体现。Lambda表达式与Java 8的其他新特性,如Stream API、新的日期时间API一起,极大地提高了Java编程的效率和乐趣。随着时间的流逝,Java开发者对这些特性的理解和应用将会越来越深入,进一步推动Java语言和应用程序的发展。
15 0
|
4月前
|
算法 Java iOS开发
JDK8到JDK27版本升级的新特性问题之JDK 17中G1在资源占用方面有何变化
JDK8到JDK27版本升级的新特性问题之JDK 17中G1在资源占用方面有何变化
|
4月前
|
XML JSON Java
JDK8到JDK26版本升级的新特性问题之在JDK 13中,字符串文本块改进字符串嵌入是如何实现的
JDK8到JDK26版本升级的新特性问题之在JDK 13中,字符串文本块改进字符串嵌入是如何实现的