Java(七)JDK1.8新特性

  本系列文章:
    Java(一)数据类型、变量类型、修饰符、运算符
    Java(二)分支循环、数组、字符串、方法
    Java(三)面向对象、封装继承多态、重写和重载、枚举
    Java(四)内部类、包装类、异常、日期
    Java(五)反射、克隆、泛型、语法糖、元注解
    Java(六)IO、NIO、四种引用
    Java(七)JDK1.8新特性

  JDK1.8增加了一些新特性,常用的有:Lambda表达式、函数式接口、方法引用与构造器引用、Stream API、接口的默认方法与静态方法、新日期API。应用最多的是:Lambda表达式和Stream API。
  JDK1.8的优点:速度更快、代码更少(增加了新的语法Lambda表达式)、强大的Stream API、便于并行和最大化减少空指针异常Optional。

一、Lambda表达式

1.1 Lambda语法

  • 从匿名内部类到Lambda表达式
      看个使用匿名内部类比较两个Integer类型数据大小的例子。
@Test
public void test1(){
  	Comparator<Integer> com = new Comparator<Integer>() {
    	@Override
    	public int compare(Integer o1, Integer o2) {
      		return Integer.compare(o1, o2);
   		}
 	};
  	TreeSet<Integer> treeSet = new TreeSet<>(com);
}

  上述代码中,有用的是return Integer.compare(o1, o2);,代码比较冗余。如果用Lambda表达式实现上面的功能,代码就会简单很多:

	Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
	TreeSet<Integer> treeSet = new TreeSet<>((x, y) -> Integer.compare(x, y));

  Lambda表达式是一个匿名函数。Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),代码看起来更简洁易懂。
  Lambda表达式同时还提升了对集合的迭代、遍历、过滤数据的操作。

  • Lambda表达式语法
      Lambda表达式在Java语言中引入了 “->” 操作符, “->” 操作符被称为Lambda表达式的操作符或者箭头操作符,它将Lambda表达式分为两部分:
      1、左侧部分指定了Lambda表达式需要的所有参数(Lambda表达式本质上是对接口的实现,Lambda表达式的参数列表本质上对应着接口中方法的参数列表)。
      2、右侧部分指定了Lambda体,即Lambda表达式要执行的功能(Lambda体本质上就是接口方法具体实现的功能)。
      Lambda表达式语法示例:
	(parameters) -> expression
	(parameters) ->{ statements; }

  Lambda表达式的重要特征:

1、可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
2、可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
3、可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
4、可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。

  Lambda使用示例:

	//1.无参,无返回值,Lambda体只有一条语句
 	Runnable r = () -> System.out.println("Hello Lambda");
  	new Thread(r).start();
  	//2.Lambda表达式需要一个参数,并且无返回值
  	Consumer<String> consumer = (x) -> System.out.println(x);
  	consumer.accept("Hello Lambda");
  	//3.Lambda只需要一个参数时,参数的小括号可以省略
  	Consumer<String> consumer = x -> System.out.println(x);
  	consumer.accept("Hello Lambda");
  	//4.Lambda需要两个参数,并且有返回值
  	Comparator<Integer> comparator = (x, y) -> {
    	System.out.println("函数式接口");
    	return Integer.compare(x, y);
 	};
 	//5.当Lambda体只有一条语句时,return和大括号可以省略
 	Comparator<Integer> comparator = (x, y) ->  Integer.compare(x, y);
 	//6.Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器能够
 	//通过上下文推断出数据类型,这就是“类型推断”
 	BinaryOperator<Integer> bo = (a, b) -> {
  		return a + b;
	};

1.2 Lambda使用

  Lambda表达式应用场景:任何有函数式接口的地方。只有一个抽象方法(Object类中的方法除外)的接口是函数式接口。就像Runnable接口中,只有一个run方法。

  • 1、Runnable
      最常见的匿名内部类之一应该就是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","scala","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、遍历
      遍历集合中的元素也是lambda表达式的常见场景之一,示例:
        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));
  • 4、过滤
      用lambda表达式充当过滤条件,筛选出一部分元素,也是常见的操作。示例:
/*实体类*/
public class Student {
    private String name;
    private int age;
    private int score;

    public Student() {}

    public Student(String name, int age, int score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    public String getName() {  return name; }
    public void setName(String name) {  this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) {   this.age = age; }
    public int getScore() {  return score; }
    public void setScore(int score) {   this.score = score;  }

    @Override
    public String toString() {
        return "student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

/*过滤接口*/
public interface StudentFilter {
    boolean compare(Student student);
}

/*客户端*/
public class Test {
    public static void main(String[] args) {
        ArrayList<Student> list = new ArrayList<Student>();
        list.add(new Student("zhangsan",14,67));
        list.add(new Student("lisi",13,89));
        list.add(new Student("wangwu",15,97));
        list.add(new Student("maliu",12,63));
        list.add(new Student("zhaoqi",17,75));

        getByFilter(list,(e)->e.getAge()>14 );
        getByFilter(list, (e)->e.getScore()>75);
        System.out.println("-------------------");
        getByFilter(list, (e)->e.getName().length()>5);
    }

    public static void getByFilter(ArrayList<Student> students, StudentFilter filter){
        ArrayList<Student> list = new ArrayList<>();
        for(Student student:students){
            if(filter.compare(student)){
                list.add(student);
            }
        }
        for(Student student:students){
            System.out.println(student);
        }
    }
}

1.3 方法引用

  当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用。方法引用就是操作符“::”将方法名和对象或类的名字分隔开来。
  方法引用,可以理解为Lambda表达式的另外一种表现形式。
  方法引用主要有三种语法格式:

类型语法Lambda表达式的写法
静态方法引用类名::静态方法(args) -> 类名.staticMethod(args)
实例方法引用对象::实例方法(args) -> inst.instMethod(args)
构造方法引用类名::new(args) -> new 类名(args)
  • 1、静态方法引用
      如果函数式接口的实现恰好可以通过调用一个静态方法来实现,那么就可以使用静态方法引用。静态方法引用的lambda写法示例:
	Comparator<Integer> com1 = (x, y) -> Integer.compare(x, y);
	System.out.println(com1.compare(1, 2));  //-1

	Comparator<Integer> com2 = Integer::compare;
	System.out.println(com2.compare(2, 1));   //1
  • 2、实例方法引用
      如果函数式接口的实现恰好可以通过调用一个实例的实例方法来实现,那么就可以使用实例方法引用。实例方法引用的lambda写法:
    //Lambda表达式的形式
    PrintStream ps = System.out;
    Consumer<String> con1 = (s) -> ps.println(s);
    con1.accept("aaa");  //aaa

    //方法引用的形式
    Consumer<String> con2 = ps::println;
    con2.accept("bbb");  //bbb
  • 3、构造方法引用
      如果函数式接口的实现恰好可以通过调用一个类的构造方法来实现,那么就可以使用构造方法引用。构造器方法引用示例:
	//lambda表达式形式
    Supplier<List> sup1 = () -> new ArrayList();
    
	//方法引用形式
    Supplier<List> sup2 = ArrayList::new;

  数组构造器方法引用:

interface test {
    public String[] run(int length);
}
 
public class blog {
    public static void main(String[] args) {
   		//相当于: test t2 = (length) -> new String[length];
        test t2 = String[]::new;
        String[] arr = t2.run(5);
    }
}

二、默认方法

2.1 接口中的默认方法和静态方法

  JDK1.8中接口中允许包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用default关键字修饰 。
  JDK1.8中接口中允许添加静态方法,使用方式接口名.方法名。
  接口示例:

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");
    }
}

  测试类示例:

public class TestDemo {

    public static void main(String[] args)  {
        HelloImpl helloImpl = new HelloImpl();
        //对于子类实现的方法,通过实例对象来调用
        //normal method: say hi
        helloImpl.sayHi();
        //default方法,只能通过实例对象来调用
        //default mehtod: say byebye
        helloImpl.sayByebye();
        //静态方法,通过接口名.方法名()来调用
        //static method: say hello
        IHello.sayHello();
    }
}

2.2 默认方法的原则

  在JDK1.8中,默认方法具有“类优先”的原则。若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时,遵循如下的原则。

  • 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{
}

  创建一个SubClassTest类,对SubClass类进行测试:

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接口,由于MyFunction接口和MyInterface接口中都存在getName()默认方法,所以,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是一组用来处理数组、集合的API。

   Stream特性:

  1. 不是数据结构,不会存储元素
  2. 不支持索引访问;
  3. 惰性求值/延迟执行,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算
  4. 支持并行
  5. 很容易生成数组或集合(List,Set);
  6. 支持过滤,查找,转换,汇总,聚合等操作;
  7. 不会修改原来的数据源,它会将操作后的数据保存到另外一个对象中(保留意见:毕竟peek方法可以修改流中元素)。

  Stream操作的三个步骤:

  1. 创建Stream。一个数据源(如: 集合、数组), 获取一个流。
  2. 中间操作。一个中间操作链,对数据源的数据进行处理。
  3. 终止操作。一个终止操作,执行中间操作链,并产生结果 。

3.1 Stream的创建*

  • 1、获取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();
  • 2、通过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);
  • 3、通过Stream类的静态方法of()获取数组流
      可以使用静态方法 Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数。Stream的of()方法:
	public static<T> Stream<T> of(T t) {
	  	return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
	}
	@SafeVarargs
	@SuppressWarnings("varargs")
	public static<T> Stream<T> of(T... values) {
	  	return Arrays.stream(values);
	}

  在Stream类中,提供了两个of()方法,一个只需要传入一个泛型参数,一个需要传入一个可变泛型参数。示例:

	String[] array = { "张三", "李四", "王五", "赵六" };
	Stream<String> stream = Stream.of(array);
	//of方法的参数其实是一个可变参数,所以支持数组
	Stream<String> stream1 = Stream.of(1, 2, 3, 4, 5);

3.2 Stream的中间操作

  多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为“惰性求值”。
Stream的中间操作在整体上可以分为:筛选与切片、映射、排序。

3.2.1 筛选与切片*
方法作用
filter(Predicatep)接收Lambda,从流中排除某些元素
distinct根据元素的hashCode()和equals()去除重复元素
limit(long maxSize)截断流,使其不超过给定数量
skip(long n)跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流

  测试数据:

protected List<Employee> list = Arrays.asList(
  	new Employee("张三", 18, 9999.99),
  	new Employee("李四", 38, 5555.55),
  	new Employee("王五", 60, 6666.66),
  	new Employee("赵六", 8, 7777.77),
  	new Employee("田七", 58, 3333.33)
);

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
	  private static final long serialVersionUID = -9079722457749166858L;
	  private String name;
	  private Integer age;
	  private Double salary;
}
  • 1、filter
      功能:用于接收Lambda表达式,从流中排除某些元素。filter方法定义:
	Stream<T> filter(Predicate<? super T> predicate);

  filter()方法是根据Predicate接口中定义的test()方法的返回结果来过滤数据的,如果test()方法的返回结果为true,符合规则;如果test()方法的返回结果为false,则不符合规则。
  filter使用示例:

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() 去 除重复元素。
	//方法定义:
	Stream<T> distinct();
	//示例
	list.stream().distinct().forEach(System.out :: println);

  distinct需要实体中重写hashCode()和equals()方法才可以使用。

3.2.2 映射*
方法作用
map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的DoubleStream
mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的IntStream
mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的LongStream
  • map
      接收一个函数作为参数,该函数会被应用到每个元 素上,并将其映射成一个新的元素。方法定义:
	<R> Stream<R> map(Function<? super T, ? extends R> mapper);

  常用map获取对象中的某些/个属性值,再存放到新的集合中。
  示例:

	//将流中每一个元素都映射到map的函数中,每个元素执行这个函数,再返回
	List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
	list.stream()
		.map((e) -> e.toUpperCase())
		.forEach(System.out::printf);
	//获取Person中的每一个人得名字name,再返回一个集合
	List<String> names = this.list.stream()
		.map(Person ::getName)
		.collect(Collectors.toList());
3.2.3 排序*
方法作用
sorted()产生一个新流,按自然顺序排序
sorted(Comparator com)产生一个新流,按比较器顺序排序

  Stream接口中的方法定义:

	Stream<T> sorted();
	Stream<T> sorted(Comparator<? super T> comparator);

  示例:

	// 自然排序
	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());

3.3 Stream的终止操作

  终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如: List、 Integer,甚至是void 。
  Stream的终止操作可以分为:查找与匹配、规约和收集。
  测试数据:

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
	  private static final long serialVersionUID = -9079722457749166858L;
	  private String name;
	  private Integer age;
	  private Double salary;
	  private Stauts stauts;
	  public enum Stauts{
	      WORKING,
	      SLEEPING,
	      VOCATION
	 }
}

	protected List<Employee> employees = Arrays.asList(
	  	new Employee("张三", 18, 9999.99, Employee.Stauts.SLEEPING),
	  	new Employee("李四", 38, 5555.55, Employee.Stauts.WORKING),
	  	new Employee("王五", 60, 6666.66, Employee.Stauts.WORKING),
	  	new Employee("赵六", 8, 7777.77, Employee.Stauts.SLEEPING),
	  	new Employee("田七", 58, 3333.33, Employee.Stauts.VOCATION)
	);
3.3.1 查找与匹配*
方法作用
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否匹配至少一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中元素总数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer c)内部迭代(使用COllection接口,需要用户去做迭代,称为外部迭代)
  • 1、allMatch
      表示检查是否匹配所有元素。
      示例:
	boolean match = employees.stream()
		.allMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
	System.out.println(match);

  只有所有的元素都匹配条件时,allMatch()方法才会返回true。

  • 2、anyMatch
      表示检查是否至少匹配一个元素。
      示例:
	boolean match = employees.stream()
		.anyMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
	System.out.println(match);

  只要有任意一个元素符合条件,anyMatch()方法就会返回true。

  • 3、noneMatch
      表示检查是否没有匹配所有元素。
      示例:
	boolean match = employees.stream()
		.noneMatch((e) -> Employee.Stauts.SLEEPING.equals(e.getStauts()));
	System.out.println(match);
  • 4、findFirst
      表示返回第一个元素。
      示例:
	Optional<Employee> op = employees.stream()
		.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
		.findFirst();
	System.out.println(op.get());
  • 5、findAny
      返回当前流中的任意元素。
      示例:
	Optional<Employee> op = employees.stream()
		.filter((e) -> Employee.Stauts.WORKING.equals(e.getStauts()))
		.findFirst();
	System.out.println(op.get());
  • 6、count
      返回流中元素总数。
      示例:
	long count = employees.stream().count();
	System.out.println(count);
  • 7、max
      返回流中最大值。nal max(Comparator<? super T> comparator);
&emsp;&emsp;示例:
```java
	Optional<Employee> op = employees.stream()
		.max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
	System.out.println(op.get());
  • 8、min
      返回流中最小值。
      示例:
	Optional<Double> op = employees.stream()
		.map(Employee::getSalary)
		.min(Double::compare);
	System.out.println(op.get());
  • 9、forEach
      表示内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反, StreamAPI 使用内部迭代)。
      示例:
	employees.stream().forEach(System.out::println);
3.3.2 规约
方法作用
reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值。 返回 T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。 返回Optional

  reduce方法的功能,简单来说就是根据一定的规则将Stream中的元素进行计算后返回一个唯一的值,示例:

	Integer reduce = Arrays.asList(1, 2, 3, 4, 5)
		.stream()
		.reduce((a, b) -> a + b)
		.get();
	System.out.print(reduce);   //15
3.3.3 收集*
方法作用
collect(Collector c)将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

  Collector接口中方法的实现决定了如何对流执行收集操作(如收集到 List、 Set、 Map)。 Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例。
常用的有:

方法返回类型作用
toListList把流中元素收集到List
toSetSet把流中元素收集到Set

  示例:

	//将过滤后的元素存储到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存在诸多问题,其中有:

  • 1、非线程安全
      java.util.Date是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  • 2、时区处理麻烦
      日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但这些类也有线程安全等问题。

  JDK1.8中,新的java.time包涵盖了所有处理日期、时间、时区等操作。

4.1 LocalDate、LocalTime和LocalDateTime*

  LocalDate、 LocalTime、 LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。

   ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法。

方法描述
now()静态方法,根据当前时间创建对象
of()静态方法,根据指定日期/时间创建对象
plusDays、plusWeeks、plusMonths、plusYears向当前LocalDate对象添加几天、 几周、 几个月、 几年
minusDays、minusWeeks、minusMonths、minusYears从当前 LocalDate 对象减去几天、 几周、 几个月、 几年
plus、minus添加或减少一个 Duration 或 Period
withDayOfMonth、withDayOfYear、withMonth、withYear将月份天数、年份天数、月份、年份修 改为指定的值并返回新的LocalDate对象
getDayOfMonth获得月份天数(1-31)
getDayOfYear获得年份天数(1-366)
getDayOfWeek获得星期几(返回一个 DayOfWeek 枚举值)
getMonth获得月份, 返回一个 Month 枚举值
getMonthValue获得月份(1-12)
getYear获得年份
until获得两个日期之间的Period对象, 或者指定ChronoUnits的数字
isBefore、isAfter比较两个LocalDate
isLeapYear判断是否是闰年

  示例:

	// 获取当前系统时间
	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);   

4.2 Instant

  用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算 。
  示例:

	// 默认获取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);

4.3 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());    

4.4 Duration和Period

  DateTimeFormatter 类:该类提供了三种格式化方法:预定义的标准格式、
语言环境相关的格式和自定义的格式。
  示例:

	DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ISO_DATE;
	LocalDateTime localDateTime = LocalDateTime.now();
	String strDate1 = localDateTime.format(dateTimeFormatter1);
	// 2019-10-27
	System.out.println(strDate1);
	
	// Date -> String
	DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
	String strDate2 = dateTimeFormatter2.format(localDateTime);
	// 2019-10-27 14:36:11
	System.out.println(strDate2);
	
	// String -> Date
	LocalDateTime localDateTime1 = localDateTime.parse(strDate2,dateTimeFormatter2);
	// 2019-10-27T14:37:39
	System.out.println(localDateTime1);

4.5 时区

  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*

5.1 常用方法

  在开发过程中,NullPointerException可谓是随时随处可见,为了避免空指针异常,常常需要进行一些防御式的检查,所以在代码中常常可见if (null != obj)这样的判断。

  在JDK1.8中,Java提供了一个Optional类,Optional类能让我们省掉繁琐的非空的判断。Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
  Optional类中的方法:

方法作用
Optional.of(T t)把指定的值封装为Optional对象,如果指定的值为null,则抛出NullPointerException
Optional.empty()创建一个空的 Optional 实例
Optional.ofNullable(T t)若 t 不为 null,创建 Optional 实例,否则创建空实例
get如果创建的Optional中有值存在,则返回此值,否则抛出NoSuchElementException
orElse(T t)如果调用对象包含值,返回该值,否则返回t
orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回 s 获取的值
orElseThrow如果创建的Optional中有值存在,则返回此值,否则抛出一个由指定的Supplier接口生成的异常
filter如果创建的Optional中的值满足filter中的条件,则返回包含该值的Optional对象,否则返回一个空的Optional对象
map(Function f)如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
flagMap(Function mapper)如果创建的Optional中的值存在,就对该值执行提供的Function函数调用,返回一个Optional类型的值,否则就返回一个空的Optional对象 / 与 map 类似,要求返回值必须是Optional
isPresent如果创建的Optional中的值存在,返回true,否则返回false
ifPresent如果创建的Optional中的值存在,则执行该方法的调用,否则什么也不做
5.1.1 创建Optional类*
  • 1、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)
  • 2、ofNullable
    //为指定的值创建Optional对象,不管所传入的值为null不为null,创建的时候都不会报错
    Optional<String> nullOptional = Optional.ofNullable(null);
    Optional<String> nullOptional2 = Optional.ofNullable("two");
  • 3、empty
    //创建一个空的String类型的Optional对象
    Optional<String> emptyOptional = Optional.empty();
5.1.2 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()));
5.1.3 orElse和orElseGet*
  • 1、orElse
      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"));
  • 2、orElseGet
      如果创建的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"));
  • 两者的区别
	String text;
	System.out.println("Using orElseGet:");
	String defaultText = Optional.ofNullable(text).orElseGet(this::getDefaultName);
	assertEquals("binghe", defaultText);
	System.out.println("Using orElse:");
	defaultText = Optional.ofNullable(text).orElse(getDefaultName());
	assertEquals("binghe", defaultText);

  示例中,我们的Optional对象中包含的都是一个空值,结果:

Using orElseGet:
Getting default name…
Using orElse:
Getting default name…

  两个Optional对象中都不存在value,因此执行结果相同。

	String name = "binghe001";
	System.out.println("Using orElseGet:");
	String defaultName = Optional.ofNullable(name).orElseGet(this::getDefaultName);
	assertEquals("binghe001", defaultName);
	System.out.println("Using orElse:");
	defaultName = Optional.ofNullable(name).orElse(getDefaultName());
	assertEquals("binghe001", defaultName);

  结果:

Using orElseGet:
Using orElse:
Getting default name…

  当使用orElseGet()方法时,getDefaultName()方法并不执行,因为Optional中含有值,而使用orElse时则照常执行。所以可以看到,当值存在时,orElse相比于orElseGet,多创建了一个对象。如果创建对象时,存在网络交互,那系统资源的开销就比较大了。

5.1.4 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)
5.1.5 get

  如果创建的Optional对象中有值存在则返回此值,如果没有值存在,则会抛出NoSuchElementException异常。

    Optional<String> stringOptional = Optional.of("three");
    //three
    System.out.println(stringOptional.get());
    Optional<String> emptyOptional = Optional.empty();
    System.out.println(emptyOptional.get());

  抛异常:

Exception in thread "main" java.util.NoSuchElementException: No value present
	at java.util.Optional.get(Optional.java:135)
	at com.test.TestDemo.main(TestDemo.java:23)
5.1.6 filter

  如果创建的Optional中的值满足filter中的条件,则返回包含该值的Optional对象,否则返回一个空的Optional对象:

    Optional<String> stringOptional = Optional.of("zhangsan");
    //zhangsan
    System.out.println(stringOptional.filter(e -> e.length() > 5).orElse("王五"));
    
    stringOptional = Optional.empty();
    //lisi
    System.out.println(stringOptional.filter(e -> e.length() > 5).orElse("lisi"));

  使用filter()方法会过滤掉我们不需要的元素。不过Optional中的filter方法和Stream中的filter方法是有点不一样的,Stream中的filter方法是对一堆元素进行过滤,而Optional中的filter方法只是对一个元素进行过滤,可以把Optional看成是最多只包含一个元素的Stream。

5.1.7 map

  如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()。

    //map方法执行传入的lambda表达式参数对Optional实例的值进行修改,修改后的返回值仍然是一个Optional对象
    Optional<String> stringOptional = Optional.of("zhangsan");
    //ZHANGSAN
    System.out.println(stringOptional.map(e -> e.toUpperCase()).orElse("失败"));

    stringOptional = Optional.empty();
    //失败
    System.out.println(stringOptional.map(e -> e.toUpperCase()).orElse("失败"));
5.1.8 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("失败"));

5.2 使用Optional处理空指针

  NullPointerException最可能出现的场景归为以下 5 种:

  1. 参数值是Integer等包装类型,使用时因为自动拆箱出现了空指针异常;
  2. 字符串比较出现空指针异常;
  3. 诸如ConcurrentHashMap这样的容器不支持Key和Value为null,强行put null的Key或 Value会出现空指针异常;
  4. A对象包含了B,在通过A对象的字段获得B之后,没有对字段判空就级联调用B的方法出现空指针异常;
  5. 方法或远程服务返回的List不是空而是null,没有进行判空就直接调用 List 的方法出现空指针异常。

  可以尝试利用JDK1.8的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。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值