java代码之美(1)

Lambda

一、概述

1、什么是Lambda表达式

Lambda 表达式是一种匿名函数,简单地说,它是没有声明的方法,也即没有访问修饰符、返回值声明和名字。

它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java 语言的表达能力得到了提升。

2、Lambda表达式的语法

基本语法:  (parameters) -> expression

     或者:(parameters) ->{ statements; 

举例说明:

复制代码
// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)  
复制代码

3、什么是函数式接口

  再对上面进行举例说明之前,必须先来理解下函数式接口,因为Lambda是建立在函数式接口的基础上的。

 记住!

   (1)只包含一个抽象方法的接口,称为函数式接口。

  (2)你可以通过 Lambda 表达式来创建该接口的对象。

  (3)我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检测它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

在实际开发者有两个比较常见的函数式接口:Runnable接口,Comparator接口

   先举例Runnable接口相关

复制代码
public class Test {
    
    public static void main(String[] args) {
        
        // 1.1使用匿名内部类  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                System.out.println("Hello world !");  
            }  
        }).start();  
          
        // 1.2使用 lambda 获得Runnable接口对象  
        new Thread(() -> System.out.println("Hello world !")).start();  
        
//=============================================================================
        
        // 2.1使用匿名内部类  
        Runnable race1 = new Runnable() {  
            @Override  
            public void run() {  
                System.out.println("Hello world !");  
            }  
        };  
          
        // 2.2使用 lambda直接获得接口对象 
        Runnable race2 = () -> System.out.println("Hello world !");          
        
        // 直接调用 run 方法(没开新线程哦!)  
        race1.run();  
        race2.run();  
    }
}
/*输出结果
 * Hello world !
 * Hello world !
 * Hello world !
 * Hello world !
 */
复制代码

通过上面案例可以看出:通过Lambda表达式看去舒服清爽多了,2而通过匿名内部类代码总是不够整洁。

再举一个例子:使用Lambda对数组排序

复制代码
public class TestArray {
    
    public static void main(String[] args) {
        String[] players = {"zhansgan", "lisi", "wangwu", "zhaoliu",  "wangmazi"};  

        // 1.1 使用匿名内部类根据 surname 排序 players  
        Arrays.sort(players, new Comparator<String>() {  
            @Override  
            public int compare(String s1, String s2) {  
                return (s1.compareTo(s2));  
            }  
        });  
        
        // 1.2 使用 lambda 排序,根据 surname  
        Arrays.sort(players, (String s1, String s2) ->  s1.compareTo(s2));  
         
//================================================================================================
          
        // 2.1 使用匿名内部类根据 name lenght 排序 players  
        Arrays.sort(players, new Comparator<String>() {  
            @Override  
            public int compare(String s1, String s2) {  
                return (s1.length() - s2.length());  
            }  
        });  

        // 2.2使用Lambda,根据name length  
        Arrays.sort(players, (String s1, String s2) -> (s1.length() - s2.length()));  
    
//==================================================================================================    
        
        // 3.1 使用匿名内部类排序 players, 根据最后一个字母  
        Arrays.sort(players, new Comparator<String>() {  
            @Override  
            public int compare(String s1, String s2) {  
                return (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));  
            }  
        });  

        // 3.2 使用Lambda,根据最后一个字母
        Arrays.sort(players, (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)));  
    }
}
复制代码

通过上面例子我们再来思考为什么Lambda表达式需要函数式接口?其实很简单目的就是为来保证唯一。

你的Runnable接口只要一个抽象方法,那么我用() -> System.out.println("Hello world !"),就只能代表run方法,如果你下面还有一个抽象方法,那我使用Lambda表达式,

那鬼才知道要调用哪个抽象方法呢。

二、方法引用

1、基本介绍

首先注意:方法引用,不是方法调用!方法引用,不是方法调用!方法引用,不是方法调用!

函数式接口的实例可以通过 lambda 表达式、 方法引用、构造方法引用来创建。方法引用是 lambda 表达式的语法糖,任何用方法引用的地方都可由lambda表达式替换,

但是并不是所有的lambda表达式都可以用方法引用来替换。

举例

这就是一个打印集合所有元素的例子,value -> System.out.println(value) 是一个Consumer函数式接口, 这个函数式接口可以通过方法引用来替换。

复制代码
public class TestArray {
    
    public static void main(String[] args) {
         List<String> list = Arrays.asList("xuxiaoxiao", "xudada", "xuzhongzhong");
           list.forEach(value -> System.out.println(value));
    }
    /* 输出:
     * xuxiaoxiao
     * xudada
     * xuzhongzhong
     */
}
复制代码

使用方法引用的方式,和上面的输出是一样的,方法引用使用的是双冒号(::)

list.forEach(System.out::println);

2、分类

类别使用形式
静态方法引用 类名 :: 静态方法名
实例方法引用 对象名(引用名) :: 实例方法名
类方法引用 类名 :: 实例方法名
构造方法引用 类名 :: new

 

 

 

 

 

 

(1)静态方法引用

复制代码
public class Apple {

    private String name;
    private String color;
    private double weight;

    public Apple(String name, String color, double weight) {
        this.name = name;
        this.color = color;
        this.weight = weight;
    }

    public static int compareByWeight(Apple a1, Apple a2) {
        double diff = a1.getWeight() - a2.getWeight();
        return new Double(diff).intValue();
    }

    //还有getter setter toString
}    
复制代码

有一个苹果的List,现在需要根据苹果的重量进行排序。List 的 sort 函数接收一个 Comparator 类型的参数,Comparator 是一个函数式接口,接收两个参数,

返回一个int值。Apple的静态方法compareByWeight正好符合Comparator函数式接口,所以可以使用:

Apple::compareByWeight 静态方法引用来替代lambda表达式

复制代码
public class LambdaTest {

    public static void main(String[] args) {

        Apple apple1 = new Apple("红富士", "Red", 280);
        Apple apple2 = new Apple("冯心", "Yello", 470);
        Apple apple3 = new Apple("大牛", "Red", 320);
        Apple apple4 = new Apple("小小", "Green", 300);

        List<Apple> appleList = Arrays.asList(apple1, apple2, apple3, apple4);

        //lambda 表达式形式
        //appleList.sort((Apple a1, Apple a2) -> {
        //    return new Double(a1.getWeight() - a2.getWeight()).intValue();
        //});

        //静态方法引用形式(可以看出引用方法比上面的更加简单
        appleList.sort(Apple::compareByWeight);

        appleList.forEach(apple -> System.out.println(apple));

    }
}
输出:
Apple{category='红富士', color='Red', weight=280.0}
Apple{category='小小', color='Green', weight=300.0}
Apple{category='大牛', color='Red', weight=320.0}
Apple{category='冯心', color='Yello', weight=470.0}
复制代码

注意:Apple.compareByWeight是方法的调用,而Apple::compareByWeight方法引用,这两者完全不是一回事。

(2)实例方法引用

这个compareByWeight是一个实例方法

复制代码
public class AppleComparator {

    public int compareByWeight(Apple a1, Apple a2) {
        double diff = a1.getWeight() - a2.getWeight();
        return new Double(diff).intValue();
    }
}
复制代码

下面的例子通过实例对象的方法引用 comparator::compareByWeight 来代替lambda表达式

复制代码
public class LambdaTest {

    public static void main(String[] args) {

        Apple apple1 = new Apple("红富士", "Red", 280);
        Apple apple2 = new Apple("冯心", "Yello", 470);
        Apple apple3 = new Apple("哈哈", "Red", 320);
        Apple apple4 = new Apple("小小", "Green", 300);


        List<Apple> appleList = Arrays.asList(apple1, apple2, apple3, apple4);

        //lambda 表达式形式
        //appleList.sort((Apple a1, Apple a2) -> {
        //    return new Double(a1.getWeight() - a2.getWeight()).intValue();
        //});

        //实例方法引用
        AppleComparator comparator = new AppleComparator();
        appleList.sort(comparator::compareByWeight);

        appleList.forEach(apple -> System.out.println(apple));

    }
}
输出:
Apple{category='红富士', color='Red', weight=280.0}
Apple{category='小小', color='Green', weight=300.0}
Apple{category='哈哈', color='Red', weight=320.0}
Apple{category='冯心', color='Yello', weight=470.0}
复制代码

通过上面两个例子可以看到,静态方法引用和实例方法引用都是比较好理解的。

(3)类方法引用

一般来说,同类型对象的比较,应该当前调用方法的对象与另外一个对象进行比较,好的设计应该像下面: 

复制代码
public class Apple {

    private String category;
    private String color;
    private double weight;

    public Apple(String category, String color, double weight) {
        this.category = category;
        this.color = color;
        this.weight = weight;
    }
//这里和上面静态方式唯一区别就是这个参数就一个,需要实例对象调这个方法
    public int compareByWeight(Apple other) {
        double diff = this.getWeight() - other.getWeight();
        return new Double(diff).intValue();
    }

    //getter setter toString
}
复制代码

    还是之前List排序的例子,看看使用类方法引用如何写:

复制代码
public class LambdaTest {

    public static void main(String[] args) {

        Apple apple1 = new Apple("红富士", "Red", 280);
        Apple apple2 = new Apple("黄元帅", "Yello", 470);
        Apple apple3 = new Apple("红将军", "Red", 320);
        Apple apple4 = new Apple("国光", "Green", 300);


        List<Apple> appleList = Arrays.asList(apple1, apple2, apple3, apple4);

        //lambda 表达式形式
        //appleList.sort((Apple a1, Apple a2) -> {
        //    return new Double(a1.getWeight() - a2.getWeight()).intValue();
        //});

        //这里是类方法引用
        appleList.sort(Apple::compareByWeight);

        appleList.forEach(apple -> System.out.println(apple));

    }
}
输出:
Apple{category='红富士', color='Red', weight=280.0}
Apple{category='国光', color='Green', weight=300.0}
Apple{category='红将军', color='Red', weight=320.0}
Apple{category='黄元帅', color='Yello', weight=470.0}
复制代码

     这里使用的是:类名::实例方法名。首先要说明的是,方法引用不是方法调用。compareByWeight一定是某个实例调用的,就是lambda表达式的第一个参数,然后lambda

表达式剩下的参数作为 compareByWeight的参数,这样compareByWeight正好符合lambda表达式的定义。

或者也可以这样理解:

(Apple a1, Apple a2) -> { return new Double(a1.getWeight() - a2.getWeight()).intValue(); }

int compareByWeight(Apple other) 需要当前对象调用,然后与另外一个对象比较,并且返回一个int值。可以理解为lambda表达式的第一个参数 a1 赋值给当前对象, 然后 a2

赋值给 other对象,然后返回int值。

(4)构造方法引用

复制代码
public class ConstructionMethodTest {

    public String getString(Supplier<String> supplier) {
        return supplier.get();
    }

    public static void main(String[] args) {

        ConstructionMethodTest test = new ConstructionMethodTest();

        //lambda表达式形式
        System.out.println(test.getString(() -> { return new String();}));

        //构造方法引用形式
        System.out.println(test.getString(String::new));

    }
}
复制代码

getString 方法接收一个Supplier类型的参数,Supplier 不接收参数,返回一个String。lambda表达式应该这样写:

() -> { return new String();}

替换成方法引用的形式如下: 实际上调用的是String 无参构造方法。

String::new

Stream

第一次看到Stream表达式就深深把我吸引,用它可以使你的代码更加整洁而且对集合的操作效率也会大大提高,如果你还没有用到java8的Stream特性,那就说明你确实out啦。

一、概述

1、什么是Stream

Stream是一种可供流式操作的数据视图有些类似数据库中视图的概念它不改变源数据集合如果对其进行改变的操作它会返回一个新的数据集合。

总的来讲它有三大特性:在之后我们会对照着详细说明

       1、stream不存储数据

       2、stream不改变源数据

       3、stream的延迟执行特性

2、Stream优点

  1. 代码简洁,函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环。

  2. 多核友好,Java函数式编程使得编写并行程序从未如此简单,你需要的全部就是调用一下parallel()方法。

3、Stream API常用方法

Stream操作分类
中间操作(Intermediate operations) 无状态(Stateless) unordered() filter() map() mapToInt() mapToLong() mapToDouble() flatMap() flatMapToInt() flatMapToLong() flatMapToDouble() peek()
有状态(Stateful) distinct() sorted() sorted() limit() skip()
结束操作(Terminal operations) 非短路操作 forEach() forEachOrdered() toArray() reduce() collect() max() min() count()
短路操作(short-circuiting) anyMatch() allMatch() noneMatch() findFirst() findAny()

 

Stream上的所有操作分为两类:中间操作和结束操作,中间操作只是一种标记,只有结束操作才会触发实际计算。

中间操作又可以分为无状态的和有状态的:

      无状态中间操作是指元素的处理不受前面元素的影响,而有状态的中间操作必须等到所有元素处理之后才知道最终结果,比如排序是有状态操作,在读取所有元素之前并不能确定排序结果;

结束操作又可以分为短路操作和非短路操作

      短路操作是指不用处理全部元素就可以返回结果,比如找到第一个满足条件的元素。之所以要进行如此精细的划分,是因为底层对每一种情况的处理方式不同。

常用中间件

      filter:过滤流,过滤流中的元素,返回一个符合条件的Stream

      map:转换流,将一种类型的流转换为另外一种流。(mapToInt、mapToLong、mapToDouble 返回int、long、double基本类型对应的Stream)

 flatMap:简单的说,就是一个或多个流合并成一个新流。(flatMapToInt、flatMapToLong、flatMapToDouble 返回对应的IntStream、LongStream、DoubleStream流。)

 distinct:返回去重的Stream。

  sorted:返回一个排序的Stream。

    peek:主要用来查看流中元素的数据状态。

     limit:返回前n个元素数据组成的Stream。属于短路操作

     skip:返回第n个元素后面数据组成的Stream。 

结束操作

  forEach: 循环操作Stream中数据。

  toArray: 返回流中元素对应的数组对象。

  reduce: 聚合操作,用来做统计。

  collect: 聚合操作,封装目标数据。

min、max、count: 聚合操作,最小值,最大值,总数量。

   anyMatch: 短路操作,有一个符合条件返回true。

    allMatch: 所有数据都符合条件返回true。

noneMatch: 所有数据都不符合条件返回true。

    findFirst: 短路操作,获取第一个元素。

     findAny: 短路操作,获取任一元素。

forEachOrdered: 暗元素顺序执行循环操作。

 

二、各种案例说明

多举点例子,以后忘记了还可以来找自己的博客,哈哈。

首先写一个领域对象

复制代码
public class Person {
    
    private Integer  id;
    
    private String name;
    
    private String sex;
    
    private Integer age;
    
    //提供get,set,和满参构造函数
}
复制代码

1、map中间件相关例子

 

复制代码
public class TestMap {

    public static void main(String[] args) {
        List<Person> persionList = new ArrayList<Person>();
        persionList.add(new Person(1,"张三","男",38));
        persionList.add(new Person(2,"小小","女",2));
        persionList.add(new Person(3,"李四","男",65));
        persionList.add(new Person(4,"王五","女",20));
        persionList.add(new Person(5,"赵六","男",38));
        persionList.add(new Person(6,"大大","男",65));

        //1、只取出该集合中所有姓名组成一个新集合
        List<String> nameList=persionList.stream().map(Person::getName).collect(Collectors.toList());
        System.out.println(nameList.toString());

        //2、只取出该集合中所有id组成一个新集合
        List<Integer> idList=persionList.stream().mapToInt(Person::getId).boxed().collect(Collectors.toList());
        System.out.println(idList.toString());

        //3、list转map,key值为id,value为Person对象
        Map<Integer, Person> personmap = persionList.stream().collect(Collectors.toMap(Person::getId, person -> person));
        System.out.println(personmap.toString());

        //4、list转map,key值为id,value为name
        Map<Integer, String> namemap = persionList.stream().collect(Collectors.toMap(Person::getId, Person::getName));
        System.out.println(namemap.toString());

        //5、进行map集合存放,key为age值 value为Person对象 它会把相同age的对象放到一个集合中
        Map<Integer, List<Person>> ageMap = persionList.stream().collect(Collectors.groupingBy(Person::getAge));
        System.out.println(ageMap.toString());

        //6、获取最小年龄
        Integer ageMin = persionList.stream().mapToInt(Person::getAge).min().getAsInt();
        System.out.println("最小年龄为: "+ageMin);

        //7、获取最大年龄
        Integer ageMax = persionList.stream().mapToInt(Person::getAge).max().getAsInt();
        System.out.println("最大年龄为: "+ageMax);

        //8、集合年龄属性求和
        Integer ageAmount = persionList.stream().mapToInt(Person::getAge).sum();
        System.out.println("年龄总和为: "+ageAmount);
        
    }
}
复制代码

 运行结果:

 

是不是之前要好几层的for循环解决的问题,通过Stream只要一行代码就可以解决了。

这里要注意,如果你list转map的key如果不唯一,会报错,所以如果你不确定你的key是否唯一,可以改成如下:

 Map<Integer, String> map = persionList.stream().collect(
                Collectors.toMap(Person::getAge, Person::getName, (key1, key2) -> key1)
        );

 2、filter相关例子

复制代码
public class TestFilter {

    public static void main(String[] args) {
        List<Person> persionList = new ArrayList<Person>();
        persionList.add(new Person(1, "张三", "男", 8));
        persionList.add(new Person(2, "小小", "女", 2));
        persionList.add(new Person(3, "李四", "男", 25));
        persionList.add(new Person(4, "王五", "女", 8));
        persionList.add(new Person(5, "赵六", "女", 25));
        persionList.add(new Person(6, "大大", "男", 65));

        //1、查找年龄大于20岁的人数
        long  age=persionList.stream().filter(p->p.getAge()>20).count();
        System.out.println(age);

        //2、查找年龄大于20岁,性别为男的人数
       List<Person>  ageList=persionList.stream().filter(p->p.getAge()>20).filter(p->"男".equals(p.getSex())).collect(Collectors.toList());
        System.out.println(ageList.size());

    }
    /*
     *运行结果:
     *  3
     *  2
     */
}
复制代码

 3、sorted相关例子

   对于数组举例

复制代码
public class TestSort {

    String[] arr1 = {"abc","a","bc","abcd"};

    /**
     * 按照字符长度排序
     */
    @Test
    public void testSorted1_(){
        Arrays.stream(arr1).sorted(Comparator.comparing(String::length)).forEach(System.out::println);
        //输出:a、bc、abc、abcd
    }

    /**
     * 倒序
     * reversed(),java8泛型推导的问题,所以如果comparing里面是非方法引用的lambda表达式就没办法直接使用reversed()
     * Comparator.reverseOrder():也是用于翻转顺序,用于比较对象(Stream里面的类型必须是可比较的)
     * Comparator. naturalOrder():返回一个自然排序比较器,用于比较对象(Stream里面的类型必须是可比较的)
     */
    @Test
    public void testSorted2_(){
        Arrays.stream(arr1).sorted(Comparator.comparing(String::length).reversed()).forEach(System.out::println);
        //输出:abcd、abc、bc、a
        Arrays.stream(arr1).sorted(Comparator.reverseOrder()).forEach(System.out::println);
        //输出:bc、abcd、abc、a
        Arrays.stream(arr1).sorted(Comparator.naturalOrder()).forEach(System.out::println);
        //输出:a、abc、abcd、bc
    }

    /**
     * 先按照首字母排序
     * 之后按照String的长度排序
     */
    @Test
    public void testSorted3_(){
        Arrays.stream(arr1).sorted(Comparator.comparing(this::com1).thenComparing(String::length)).forEach(System.out::println);
    }
//输出:a、abc、abcd、bc public char com1(String x){ return x.charAt(0); } }
复制代码

 对于集合举例

复制代码
public class TestSort {

    public static void main(String[] args) {
        List<Person> persionList = new ArrayList<Person>();
        persionList.add(new Person(1, "张三", "男", 8));
        persionList.add(new Person(2, "小小", "女", 2));
        persionList.add(new Person(3, "李四", "男", 25));
        persionList.add(new Person(4, "王五", "女", 8));
        persionList.add(new Person(5, "赵六", "女", 25));
        persionList.add(new Person(6, "大大", "男", 65));

        //1、找到年龄最小的岁数
        Collections.sort(persionList, (x, y) -> x.getAge().compareTo(y.getAge()));
        Integer age = persionList.get(0).getAge();
        System.out.println("年龄最小的有:" + age);
        //输出:年龄最小的有:2

        //2、找到年龄最小的姓名
        String name = persionList.stream()
                .sorted(Comparator.comparingInt(x -> x.getAge()))
                .findFirst()
                .get().getName();
        System.out.println("年龄最小的姓名:" + name);
        //输出:年龄最小的姓名:小小
    }
}
复制代码

 其它的就不具体写了。以后遇到特殊的再往里面补充。

Java8 函数式接口

之前写了有关JDK8的Lambda表达式:java代码之美(1)---Java8 Lambda

函数式接口可以理解就是为Lambda服务的,它们组合在一起可以让你的代码看去更加简洁。

一、概念

1、什么是函数式接口

概念 所谓的函数式接口, 当然首先是一个接口, 然后就是在这个接口里面 只能有一个抽象方法

有关函数式接口,有个专门的注解叫:@FunctionalInterface。该注解主要特点有:

1、该注解只能标记在"有且仅有一个抽象方法"的接口上,表示函数式接口。
2、JDK8接口中的静态方法和默认方法,都不算是抽象方法。
3、接口默认继承 java.lang.Object,所以如果接口显示声明覆盖了Object中的方法,那么也不算抽象方法。
4、允许java.lang.Object中的public方法
5、该注解不是必须的,如果一个接口符合"函数式编程"定义,那么加不加该注解都没有影响。加上该注解能够 更好地让编译器进行检查,
如果编写的不是函数式接口,但是加上了@FunctionalInterface 那么编译器会报错。

2、示例

  1. 正确示例
/**
 * 函数式接口注解
 */
@FunctionalInterface
public interface PersonInterface { 
    /**
     * 1、仅有一个抽象方法
     */
     void say();
     
    /**
     * 2、java.lang.Object中的方法不算
     */
    @Override
     boolean equals(Object var1);
    
    /**
     * 3、java8 接口才可以有默认的方法实现 前提是方法名称必须使用default关键字修饰
     */
     default void defaultMethod() {
        System.out.println("haha");
    }
    
    /**
     * 4、静态方法
     */
     static void staticMethod() {
    }
}
  1. 错误示例

加上@FunctionInterface,就代表该接口是函数式接口,只能有一个抽象方法,如果有多个编译时就会直接报错。

3、为什么只能有一个抽象方法

其实这个问题很好去理解,上面说了函数式接口主要是为Lambda语法服务的,为了让代码看去更加简洁。

下面通过示例来说明

     public static void main(String[] args) {
       //上面的接口 通过Lambda表达式重新 say方法 
        PersonInterface inter = () -> System.out.println("我说什么好呢?");
        inter.say();
        //控制台输出: 我说什么好呢?
    }

通过 函数式接口 + Lambda表达式 让代码看去变的简洁,而这里的关键点在于:

 ()->{} 就是代表对say()方法的重写

如果你有个多个抽象方法, 那么()-> {} 这种写法,编译器就不知道这是重写的哪个方法了。所以这就是为什么只能有一个抽象方法的原因。

 

二、综合示例

这里再举一个综合示例,来方便理解它们。

自定义函数式接口

/**
  * 自定义函数TestFunction,提供handler接口, 传入的是A,返回的是B
  */
@FunctionalInterface
public interface MyFunction<A, B> {

    /**
      * @Description: 传入的是A 返回的是B
      */
    B handler(A a, A a1);
}

Student对象

public class Student {
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;
    
    //省略 set get toString 全参数构造函数 无参构造函数
  }

测试类

   public static void main(String[] args) {
        //1、求和 传入Integer返回Integer类型
        MyFunction<Integer, Integer> myFunction1 = (x, y) -> {
            //返回总和
            return x + y;
        };
        Integer count = myFunction1.handler(5, 10);
        System.out.println("输出总和为:" + count);


        //2、求和 传入Integer返回String类型
        MyFunction<Integer, String> myFunction2 = (x, y) -> {
            //返回总和
            return x + " + " + y + " = " + (x + y);
        };
        System.out.println(myFunction2.handler(5, 10));


        //3、对象处理 过滤对象
        List<Student> students = Arrays.asList(new Student("小明", 3), new Student("小白", 13), new Student("小黄", 18));
        MyFunction<Integer, List<Student>> myFunction3 = (x, y) -> {
            //这里通过java8 的stream来过滤 年龄大于x 且小于y的对象
            List<Student> studentList = students.stream().filter(student -> student.getAge() > x && student.getAge() < y).collect(Collectors.toList());
            return studentList;
        };

        List<Student> list = myFunction3.handler(5, 15);
        //遍历集合 输出对象
        list.forEach(x -> System.out.println(x));
    }

运行结果

从运行结果可以很明显看出,集合对象经过过滤只剩下一个满足条件的了。

Java8 Function、Consumer、Supplier

有关JDK8新特性之前写了三篇博客:

1、java代码之美(1)---Java8 Lambda

2、java代码之美(2)---Java8 Stream

3、java代码之美(13)--- Predicate详解

这一篇我们来了解JDK8已经定义好的几个函数式接口。

一、概述

Jdk8之后新增的一个重要的包 : java.util.function

该包下所有的接口都是函数式接口, 按分类主要分为四大接口类型: FunctionConsumerPredicateSupplier。有关Predicate这里不再讲解,因为上面有单独写过一篇博客。

延伸如下

这里也仅仅是展示一部分,我们看看java.util.function包下

 

二、Consumer

作用 一听这名字就知道是消费某个对象,没有返回值。

1、源码

在源码中只有两个方法,一个抽象方法,一个默认方法。

@FunctionalInterface
public interface Consumer<T> {

    /**
     * 抽象方法:传入一个指定泛型的参数,无返回值
     */
    void accept(T t);

    /**
     * 如同方法名字一样andThen,类似一种相加的功能(下面会举例说明)
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

2、使用示例

    public static void main(String[] args) {
        testConsumer();
        testAndThen();
    }
    /**
     * 一个简单的平方计算
     */
    public static void testConsumer() {
        //设置好Consumer实现方法
        Consumer<Integer> square = x -> System.out.println("平方计算 : " + x * x);
        //传入值
        square.accept(2);
    }
    /**
     * 定义3个Consumer并按顺序进行调用andThen方法
     */
    public static void testAndThen() {
        //当前值
        Consumer<Integer> consumer1 = x -> System.out.println("当前值 : " + x);
        //相加
        Consumer<Integer> consumer2 = x -> { System.out.println("相加 : " + (x + x)); };
        //相乘
        Consumer<Integer> consumer3 = x -> System.out.println("相乘 : " + x * x);
        //andThen拼接
        consumer1.andThen(consumer2).andThen(consumer3).accept(1);
    }

运行结果

单个这样消费看去并没啥意义,但如果是集合操作就有意义了,所以Jdk8的Iterator接口就引入了Consumer。

3、JDK8使用

Iterable接口的forEach方法需要传入Consumer,大部分集合类都实现了该接口,用于返回Iterator对象进行迭代。

public interface Iterable<T> {
    //forEach方法传入的就是Consumer
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
}

我们在看给我们带来的便利

    public static void main(String[] args) {
        //假设这里有个集合,集合里的对象有个status属性,现在我想对这个属性赋值一个固定值
        List<Pension> pensionList = new ArrayList<>();
        //1、传统的通过for循环添加
        for (Pension pension : pensionList) {
            pension.setStatus(1);
        }
        //2、通过forEach的Consumer添加
        pensionList.forEach(x -> x.setStatus(1));
    }

这样一比较是不是代码简洁了点,这就是Consumer是我们代码带来简洁的地方。

 

三、Supplier

作用 提前定义可能返回的一个指定类型结果,等需要调用的时候再获取结果。

1、源码

@FunctionalInterface
public interface Supplier<T> {

    /**
     * 只有这一个抽象类
     */
    T get();
}

源码非常简单。

2、JDK8使用

在JDK8中Optional对象有使用到

Optional.orElseGet(Supplier<? extends T>) //当this对象为null,就通过传入supplier创建一个T返回。

我们看下源码

 public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }

使用示例

  public static void main(String[] args) {
        Person son = null;
        //先判断son是否为null,如果为不为null则返回当前对象,如果为null则返回新创建的对象
        BrandDTO optional = Optional.ofNullable(son).orElseGet(() -> new Person());

    }

这样代码是不是又简单了。有关Optional这里就不多说,接下来会单独写一篇博客。

 

四、Function

作用 实现一个”一元函数“,即传入一个值经过函数的计算返回另一个值。

1、源码

    @FunctionalInterface
    public interface Function<T, R> {
        
        /**
         * 抽象方法: 根据一个数据类型T加工得到一个数据类型R
         */
        R apply(T t);

        /**
         * 组合函数,调用当前function之前调用
         */
        default <V> java.util.function.Function<V, R> compose(java.util.function.Function<? super V, ? extends T> before) {
            Objects.requireNonNull(before);
            return (V v) -> apply(before.apply(v));
        }

        /**
         * 组合函数,调用当前function之后调用
         */
        default <V> java.util.function.Function<T, V> andThen(java.util.function.Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t) -> after.apply(apply(t));
        }

        /**
         *  静态方法,返回与原函数参数一致的结果。x=y
         */
        static <T> java.util.function.Function<T, T> identity() {
            return t -> t;
        }
    }

2、使用示例

public static void main(String[] args) {
        applyTest();
        andThenTest();
        composeTest();
        test();
    }

    /**
     * 1、apply 示例
     */
    private static void applyTest() {
        //示例1:定义一个funciton,实现将String转换为Integer
        Function<String, Integer> function = x -> Integer.parseInt(x);
        Integer a = function.apply("100");
        System.out.println(a.getClass());
        // 结果:class java.lang.Integer
    }

    /**
     * 2、andThen 示例
     */
    private static void andThenTest() {
        //示例2:使用andThen() 实现一个函数 y=10x + 10;
        //先执行 10 * x
        Function<Integer, Integer> function2 = x -> 10 * x;
        //通过andThen在执行 这里的x就等于上面的10 * x的值
        function2 = function2.andThen(x -> x + 10);
        System.out.println(function2.apply(2));
        //结果:30

    }

    /**
     * 3、compose 示例
     */
    private static void composeTest() {
        //示例3:使用compose() 实现一个函数 y=(10+x)2;
        Function<Integer, Integer> function3 = x -> x * 2;
        //先执行 x+10 在执行(x+10)*2顺序与上面相反
        function3 = function3.compose(x -> x + 10);
        System.out.println(function3.apply(3));
        //结果:26
    }

    /**
     * 4、综合示例
     */
    private static void test() {

//示例4:使用使用compose()、andThen()实现一个函数 y=(10+x)*2+10;
        //执行第二步
        Function<Integer, Integer> function4 = x -> x * 2;
        //执行第一步
        function4 = function4.compose(x -> x + 10);
        //执行第三步
        function4 = function4.andThen(x -> x + 10);
        System.out.println(function4.apply(3));
       //结果:36

    }

3、JDK8使用

有两个地方很常用

1、V HashMap.computeIfAbsent(K , Function<K, V>) // 简化代码,如果指定的键尚未与值关联或与null关联,使用函数返回值替换。
2、<R> Stream<R> map(Function<? super T, ? extends R> mapper); // 转换流

computeIfAbsent使用示例

Map<String, List<String>> map = new HashMap<>();
List<String> list;

// java8之前写法
list = map.get("key");
if (list == null) {
    list = new LinkedList<>();
    map.put("key", list);
}
list.add("11");

// 使用 computeIfAbsent 可以这样写 如果key返回部位空则返回该集合 ,为空则创建集合后返回
list = map.computeIfAbsent("key", k -> new ArrayList<>());
list.add("11");

stream中map使用示例

  public static void main(String[] args) {
        List<Person> persionList = new ArrayList<Person>();
        persionList.add(new Person(1,"张三","男",38));
        persionList.add(new Person(2,"小小","女",2));
        persionList.add(new Person(3,"李四","男",65));

        //1、只取出该集合中所有姓名组成一个新集合(将Person对象转为String对象)
        List<String> nameList=persionList.stream().map(Person::getName).collect(Collectors.toList());
        System.out.println(nameList.toString());
        }

代码是不是又简洁了。

总结 这些函数式接口作用在我看来,就是定义一个方法,方法中有个参数是函数式接口,这样的话函数的具体实现则由调用者来实现。这就是函数式接口的意义所在。

一般我们也会很少去定义一个方法,方法参数包含函数接口。我们更重要的是学会使用JDk8中带有函数式接口参数的方法,来简化我们的代码。

Java8 Optional

一句话介绍Optional类:使用JDK8的Optional类来防止NullPointerException(空指针异常)问题

一、前言

在我们开放过程中,碰到的异常中NullPointerException必然是排行第一的。所以在平时编码中,我们会时时的判断null

public void saveCity(City city) {
        if (city != null) {
            String cityName = city.getCityName();
            if (cityName != null) {
                String code = cityDao.findCodeByName(cityName);
                city.setCode(code);
                cityDao.save(city);
            }
        }
    }

虽然上面代码变得更加安全,但是过多嵌套 if 语句降低代码整体可读性,提高复杂度。我们可以优化下代码

    public void saveCity(City city) {
        if (city == null) {
            return;
        }
        String cityName = city.getCityName();
        if (cityName == null) {
            return;
        }
        String code = cityDao.findCodeByName(cityName);
        city.setCode(code);
        cityDao.save(city);
    }

这样还可以,但我们通过Optional变的更简洁

    public void saveCity(City city) {
        //就一行 city不为空返回 城市名称 否则直接返回空
        Optional<String> roleOpt = Optional.ofNullable(city).map(City::getCityName);
        //如果容器中 不为空
        if (roleOpt.isPresent()) {
            String code = cityDao.findCodeByName(roleOpt.get());
            city.setCode(code);
            cityDao.save(city);
        }
    }

这样,我们仅需要对我们关心的做一次校验,省却了前面的一系列的检验操作。

 

二、Optional API

概念 Optiona本质是一个容器,容器中存在为null或者不包含非null值的容器对象。提供了一系列的方法供我们判断该容器里的对象是否存在。

1、JDK源码

/**
 * final修饰代表不能被子类继承
 */
public final class Optional<T> {
    /**
     * 创建一个空容器
     */
    private static final java.util.Optional<?> EMPTY = new java.util.Optional<>();

    /**
     * 传入的值
     */
    private final T value;

    /**
     * 构造函数私有化 说明不能被外部new
     */
    private Optional() {
        this.value = null;
    }

    /**
     * 私有化构造函数
     */
    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }

    /**
     * 获取空容器
     */
    public static <T> java.util.Optional<T> empty() {
        @SuppressWarnings("unchecked")
        java.util.Optional<T> t = (java.util.Optional<T>) EMPTY;
        return t;
    }


    /**
     * 传入的对象不能为空 否则抛异常
     */
    public static <T> java.util.Optional<T> of(T value) {
        return new java.util.Optional<>(value);
    }

    /**
     * 传入的对象可以为空
     */
    public static <T> java.util.Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

    /**
     * 获取容器对象的方法 注意 如果用这个方法则代表容器中一定有对象,否则抛异常
     */
    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

    /**
     * 判断容器对象是否为空
     */
    public boolean isPresent() {
        return value != null;
    }

    /**
     * 如果容器对象为空 则返回当前对象
     */
    public T orElse(T other) {
        return value != null ? value : other;
    }

    //==========有关下面这几个JDK8自带的函数式接口的作用,上一篇博客有详细说明,这里就不多说了。

    /**
     * 传入Consumer编程式接口参数
     */
    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }

    /**
     * 传入Predicate编程式接口参数
     */
    public java.util.Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
    }

    /**
     * 传入Function编程式接口参数
     */
    public <U> java.util.Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return java.util.Optional.ofNullable(mapper.apply(value));
        }
    }

    /**
     * 传入Function编程式接口参数
     */
    public <U> java.util.Optional<U> flatMap(Function<? super T, java.util.Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

    /**
     * 传入Supplier编程式接口参数
     */
    public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }

    /**
     * 传入Supplier编程式接口参数
     */
    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }
}

2、创建Optional对象

通过上面源码可以看出,Optional的构造函数都是私有化的,无法直接new对象。它这边提供了3个静态方法获取对象。

1、创建一个一定是空的Optional容器

Optional<Car> optCar = Optional.empty();

2、创建一个一定是非空值Optional容器(传入的对象不可以为null,否则抛出NullPointerException)

Optional<Car> optUser = Optional.of(user);

3、创建一个可能是空也可能不为空的Optional容器(传入的对象可以为null)

Optional<Car> optUser = Optional.ofNullable(user);

3、总结常用方法

1、isPresent()        //有值则返回true
2、get():             //值存在时返回值,否则抛出一个NoSuchElement异常(所以调这个,一般先判断上面方法返回是否为true)
3、orElse(T other)    //值存在时返回值,否则返回一个默认值
4、ifPresent(Consumer<T> block)             //会在值存在的时候执行给定的代码块
5、orElseThrow(Supplier<? extends X> exceptionSupplier)  //与get()类似,不同的是可以自定义异常类型
6、orElseGet(Supplier<? extends T> other)   //orElse方法的延迟调用版,Supplier方法只有在Optional对象不含值时才执行调用
7、map/flatMap/filter                       //与Stream中用法类似

 

三、完整的示例

这里写一个针对以上API都涉及到的Demo,这个例子明白了,那么Optional的使用也就都清楚了。

代码

public class OptionalDemo {
    public static void main(String[] args) {
        //1、创建Optional实例,传入的对象不能为null
        Optional<String> nameOptional = Optional.of("张三");

        //2、创建Optional实例,传入对象可以为null,也可以不weinull
        Optional emptyOptional = Optional.ofNullable(null);

        //3、isPresent方法用来检查Optional实例是否有值。
        if (nameOptional.isPresent()) {
            //调用get()返回Optional值。
            System.out.println("1、" + nameOptional.get());
        }

        try {
            //4、在Optional实例上调用get()抛出NoSuchElementException。
            System.out.println("2、" + emptyOptional.get());
        } catch (NoSuchElementException ex) {
            System.out.println("3、异常" + ex.getMessage());
        }

        //
        //5、如果Optional值不为空,lambda表达式会处理并在其上执行操作。(这里x代表就是nameOptional中的对象)
        nameOptional.ifPresent((x) -> {
            System.out.println("4、字符串长度为: " + x.length());
        });

        //6、如果有值orElse方法会返回Optional实例,没值则返回当前值
        System.out.println("5、"+ emptyOptional.orElse("如果是空容器则返回李四"));
        System.out.println("6、"+nameOptional.orElse("如果是空容器则返回王五"));

        //7、orElseGet与orElse类似,区别在于传入的参数不同,一个是直接传入对象,这个是传入Supplier函数式接口
        System.out.println("7、" + emptyOptional.orElseGet(() -> "李四"));
        System.out.println("8、" + nameOptional.orElseGet(() -> "王五"));

        try {
            //8、如果是空容器,则可以抛出自定义异常。
            emptyOptional.orElseThrow(() -> new NullPointerException("空容器异常"));
        } catch (Throwable ex) {
            System.out.println("9、" + ex.getMessage());
        }

        Optional<String> ageOptional = Optional.of("10");
        //9、这里入参是Function,所以可以转换容器中的对象 好比将String对象转为Integer对象
        Optional<Integer> age = ageOptional.map((value) -> Integer.parseInt(value));
        /**
         * 10、flatMap与map(Funtion)非常相似,不同在于 map返回可以将String对象转为Integer对象,但flatMap转换后一定还是String对象
         */
        Optional<String> upperName = nameOptional.flatMap((value) -> Optional.of(value.toUpperCase()));

        //11、filter方法检查Optiona值是否满足给定条件。如果满足返回Optional实例值,否则返回空Optional。
        Optional<String> longName = nameOptional.filter((value) -> value.length() > 6);
        System.out.println("10、" + longName.orElse("longName容器的名字长度小于6位"));

        //12、另一个示例,Optional满足给定条件。
        Optional<String> anotherName = Optional.of("乌啦啦市长公主");
        Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
        System.out.println("11、" + shortName.orElse("anotherName容器的名字长度小于6位"));

    }
}

运行结果

 

Java8 LocalDateTime

在java8之前我们在处理时间的时候都是用的Date,但它其实有很明显的缺点。

1.我们也会对日期做一些操作,比如加几天、加几分,当月的最后一天等等。有些计算实现比较复杂。
2.也会用SimpleDateFormat来格式化日期。但SimpleDateFormat是线程不安全的。

所以现在一般都推荐使用LocalDateTime 它是线程安全的,并且性能更好,代码更简洁。

一、示例

新时间日期API常用、重要对象主要有下面三个:

LocalDate : 只含年月日的日期对象
LocalTime :只含时分秒的时间对象
LocalDateTime : 同时含有年月日时分秒的日期对象

下面会通过示例来一一理解它们。

1、创建实例

    public static void main(String[] args) {
        //1、获取当前日期
        LocalDate now = LocalDate.now();
        System.out.println("当前时间 = " + now);
        //输出: 当前时间 = 2020-07-06

        //2、获取指定日期(参数依次 年、月、日)
        LocalDate localDate = LocalDate.of(2020, 6, 30);
        System.out.println("年月日 = " + localDate);
        //输出: 年月日 = 2020-06-30

        //3、获取当前时间
        LocalTime localTime = LocalTime.now();
        System.out.println("localTime = " + localTime);
        //输出: localTime = 22:32:45.994

        //4、获取指定时间(参数依次 时、分、秒、纳秒
        LocalTime localTimeOf = LocalTime.of(12, 24, 12, 4444);
        System.out.println("localTimeOf = " + localTimeOf);
        //输出: localTimeOf = 12:24:12.000004444

        //5、获取当前年月日,时分秒都有的日期
        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println("localDateTime = " + localDateTime);
        //输出: localDateTime = 2020-07-06T22:32:45.994

        //6、获取指定年月日,时分秒都有的日期(参数依次 年、月、日、时、分)
        LocalDateTime localDateTimeOf = LocalDateTime.of(2020, 7, 30, 12, 12);
        System.out.println("localDateTimeOf = " + localDateTimeOf);
        //输出: localDateTimeOf = 2020-07-30T12:12

         //7、日期+时间 组成 包含年月日,时分秒都有的日期
        LocalDateTime of = LocalDateTime.of(LocalDate.now(), LocalTime.now());
        System.out.println("of = " + of);
        //输出: of = 2020-07-06T22:32:45.995
    }

2、计算日期和时间

日期时间的加减
  • 对于LocalDate,只有精度大于或等于日的加减,如年、月、日;
  • 对于LocalTime,只有精度小于或等于时的加减,如时、分、秒、纳秒;
  • 对于LocalDateTime,则可以进行任意精度的时间相加减;

加法操作

public static void main(String[] args) {
            
        //获取当前时间
        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println("当前时间 = " + localDateTime);

        //1、加1年
        LocalDateTime plusYears = localDateTime.plusYears(1L);
        System.out.println("plusYears = " + plusYears);
        //输出: plusYears = 2021-07-06T22:46:49.196
        
        //2、加1个月
        LocalDateTime plusMonths = localDateTime.plusMonths(1L);
        System.out.println("plusMonths = " + plusMonths);
        //输出: plusMonths = 2020-08-06T22:46:49.196
        
        //3、加一天
        LocalDateTime plusDays = localDateTime.plusDays(1L);
        System.out.println("plusDays = " + plusDays);
        //输出: plusDays = 2020-07-07T22:46:49.196
        
        //4、加1个小时
        LocalDateTime plusHours = localDateTime.plusHours(1L);
        System.out.println("plusHours = " + plusHours);
        //输出: plusHours = 2020-07-06T23:46:49.196
        
        //5、加10分
        LocalDateTime plusMinutes = localDateTime.plusMinutes(10L);
        System.out.println("plusMinutes = " + plusMinutes);
        //输出: plusMinutes = 2020-07-06T22:56:49.196
        
        //6、加200毫秒
        LocalDateTime plusSeconds = localDateTime.plusSeconds(200L);
        System.out.println("plusSeconds = " + plusSeconds);
        //输出: plusSeconds = 2020-07-06T22:50:09.196
    }

也可以用另外一种方式

     LocalDateTime nextMonth = localDateTime.plus(1, ChronoUnit.MONTHS);
     LocalDateTime nextYear = localDateTime.plus(1, ChronoUnit.YEARS);
     LocalDateTime nextWeek = localDateTime.plus(1, ChronoUnit.WEEKS);

减法操作

    public static void main(String[] args) {
        //获取当前时间
        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println("当前时间 = " + localDateTime);
        //输出: 当前时间 = 2020-07-06T22:53:38.264

        //1、减1年
        LocalDateTime minusYears = localDateTime.minusYears(1L);
        System.out.println("minusYears = " + minusYears);
        //输出: minusYears = 2019-07-06T22:53:38.264

        //2、减1个月
        LocalDateTime minusMonths = localDateTime.minusMonths(1L);
        System.out.println("minusMonths = " + minusMonths);
        //输出: minusMonths = 2020-06-06T22:53:38.264

        //3、减一天
        LocalDateTime minusDays = localDateTime.minusDays(1L);
        System.out.println("minusDays = " + minusDays);
        //输出: minusDays = 2020-07-05T22:53:38.264

        //4、减1个小时
        LocalDateTime minusHours = localDateTime.minusHours(1L);
        System.out.println("minusHours = " + minusHours);
        //输出: minusHours = 2020-07-06T21:53:38.264

        //5、减10分
        LocalDateTime minusMinutes = localDateTime.minusMinutes(10L);
        System.out.println("minusMinutes = " + minusMinutes);
        //输出: minusMinutes = 2020-07-06T22:43:38.264

        //6、减200毫秒
        LocalDateTime minusSeconds = localDateTime.minusSeconds(200L);
        System.out.println("minusSeconds = " + minusSeconds);
        //输出: minusSeconds = 2020-07-06T22:50:18.264
    }

也可以用另外一种方式

        LocalDateTime lastMonth = localDateTime.minus(1, ChronoUnit.MONTHS);
        LocalDateTime lastYear = localDateTime.minus(1, ChronoUnit.YEARS);
        LocalDateTime lastWeek = localDateTime.minus(1, ChronoUnit.WEEKS);

注意从代码中可以看到,这些 plus() 和 minus() 方法,是不会改变原date和time的实例的,返回的是新的实例。

3、比较日期和时间

当我们想知道给定的时间或日期是在另一个时间/日期之前还是之后,我们就可以用到isBefore()isAfter()方法,如下所示:

 public static void main(String[] args) {
       public static void main(String[] args) {
        LocalDate ld1 = LocalDate.of(2020, 7, 6);
        LocalDate ld2 = LocalDate.of(2020, 7, 7);

        boolean after = ld1.isAfter(ld2);
        System.out.println("ld1是否在ld2之后 = " + after);
        //输出:  ld1是否在ld2之后 = false

        boolean before = ld1.isBefore(ld2);
        System.out.println("ld1是否在ld2之前 = " + before);
        //输出:  ld1是否在ld2之前 = true

        LocalDateTime ldt1 = LocalDateTime.of(2020, 7, 7, 12, 12);
        LocalDateTime ldt2 = LocalDateTime.of(2020, 7, 7, 14, 12);

        boolean after1 = ldt1.isAfter(ldt2);
        System.out.println("ldt1是否在ldt2之后 = " + after1);
        //输出:  ldt1是否在ldt2之后 = false

        boolean before1 = ldt1.isBefore(ldt2);
        System.out.println("ldt1是否在ldt2之后 = " + before1);
        //输出:  ldt1是否在ldt2之后 = true

        //时间相减
        Duration duration = Duration.between(ldt1, ldt2);
        //两个时间差的天数
        long days = duration.toDays();
        System.out.println("days = " + days);
        //输出: days = 0

        //小时数差
        long hours = duration.toHours();
        System.out.println("hours = " + hours);
        //输出: hours = 2

        //分钟数差
        long minutes = duration.toMinutes();
        System.out.println("minutes = " + minutes);
        //输出: minutes = 120

        //毫秒数差
        long millis = duration.toMillis();
        System.out.println("millis = " + millis);
        //输出: millis = 7200000

        //纳秒数差
        long nanos = duration.toNanos();
        System.out.println("nanos = " + nanos);
        //输出: nanos = 7200000000000
    }

4、在String和日期之间转换

在以前使用java.util.Date的时候,我们一般使用 SimpleDateFormat 去完成日期/时间和字符串的转换,在新的日期时间API中,我们使用全新的 DateTimeFormatter

如果你遵循ISO标准在日期/时间和字符串之间进行转换,那么这个事情会变得很容易,因为在 DateTimeFormatter 中,已经内置了ISO标准的格式。我们来看看代码

日期转时间

public static void main(String[] args) {
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println("ldt = " + ldt);
        //输出: ldt = 2020-07-07T18:32:34.757

        String format1 = ldt.format(DateTimeFormatter.ISO_DATE);
        System.out.println("format1 = " + format1);
        //输出: format1 = 2020-07-07

        String format2 = ldt.format(DateTimeFormatter.BASIC_ISO_DATE);
        System.out.println("format2 = " + format2);
        //输出:  format2 = 20200707
        
        String format3 = ldt.format(DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("format3 = " + format3);
        //输出: format3 = 2020-07-07T18:32:34.757

        String format4 = ldt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        System.out.println("format4 = " + format4);
        //输出: format4 = 2020-07-07T18:32:34.757

        String format = ldt.format(DateTimeFormatter.ofPattern("d-M-y"));
        System.out.println("format = " + format);
        //输出: format = 7-7-2020

        String format5 = ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        System.out.println("format5 = " + format5);
        //输出: format5 = 2020-07-07 18:32:34
        
        String format6 = ldt.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒"));
        System.out.println("format6 = " + format6);
        //输出: format6 = 2020年07月07日18时32分34秒
      }

String转日期

public static void main(String[] args) {
        LocalDate ld = LocalDate.parse("2020-07-07");
        System.out.println("ld = " + ld);
        //输出: ld = 2020-07-07

        String str = "2020-07-07 22:24:33";
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime ldt = LocalDateTime.parse(str,dateTimeFormatter);
        System.out.println("ldt = " + ldt);
        //输出: ldt = 2020-07-07T22:24:33
      }

5、其它

有的时候,你需要进行一些更加复杂的操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。这时,你可以使用重载版本的with方法,向其传递一个提供了更多定制化选择的TemporalAdjuster对象,更 加 灵 活 地 处 理 日 期。

日期处理

  public static void main(String[] args) {
        LocalDate date = LocalDate.parse("2020-07-07");
        //获取这个月的第一个周末的时间
        System.out.println(date.with(TemporalAdjusters.dayOfWeekInMonth(1, DayOfWeek.SUNDAY)));
        //输出: 2020-07-05

        //获取上个月的最后一周末的时间
        System.out.println(date.with(TemporalAdjusters.dayOfWeekInMonth(0, DayOfWeek.SUNDAY)));
        //输出: 2020-06-28

        //获取这个月的倒数第一个周末的时间
        System.out.println(date.with(TemporalAdjusters.dayOfWeekInMonth(-1, DayOfWeek.SUNDAY)));
        //输出: 2020-07-26

        //获取这个月的第一个周末的时间,上面的dayOfWeekInMonth更灵活,可以定义第几周
        System.out.println(date.with(TemporalAdjusters.firstInMonth(DayOfWeek.SUNDAY)));
        //输出: 2020-07-05

        //明年的第一天
        System.out.println(date.with(TemporalAdjusters.firstDayOfNextYear()));
        //输出: 2021-01-01
        
        //获取下个周5的时间
        System.out.println(date.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)));
        //输出: 2020-07-10
        
        //获取本月最后一天
        System.out.println(date.with(TemporalAdjusters.lastDayOfMonth()));
        //输出: 2020-07-31

        //获取本月第一天
        System.out.println(date.with(TemporalAdjusters.firstDayOfMonth()));
        //输出: 2020-07-01
      }

其它还有许多,具体查看api

时间处理

public static void main(String[] args) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        //一天开始时间
        LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
        String format = todayStart.format(dateTimeFormatter);
        System.out.println("format = " + format);
        //输出: format = 2020-07-07 00:00:00
        
        //一天结束时间
        LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
        String format1 = todayEnd.format(dateTimeFormatter);
        System.out.println("format1 = " + format1);
        //输出: format1 = 2020-07-07 23:59:59

        //一天中午时间
        LocalDateTime todayMid = LocalDateTime.of(LocalDate.now(), LocalTime.NOON);
        String format2 = todayMid.format(dateTimeFormatter);
        System.out.println("format2 = " + format2);
        //输出: format2 = 2020-07-07 12:00:00
      }

举了这么多例子,在实际开发中应该足够用了。

 

二、完整示例

这里整理一个完整的时间工具类,可以在实际工作中进行运用它

public class DateTimeUtils {

    public static final String DATETIME_FORMATTER = "yyyy-MM-dd HH:mm:ss";

    public static final String DATE_FORMATTER = "yyyy-MM-dd";

    public static final String DATE_FORM = "yyyy-MM";


    //1、 ==================1、获取当天,当月最早时间和最晚时间 ========================

    /**
     * 获取当天的开始时间
     * 示例:2020-08-21T00:00
     */
    public static LocalDateTime getDayStart(LocalDateTime time) {
        return time.withHour(0).withMinute(0).withSecond(0).withNano(0);
    }

    /**
     * 获取当天的结束时间
     * 示例:2020-08-21T23:59:59.999999999
     */
    public static LocalDateTime getDayEnd(LocalDateTime time) {
        return time.withHour(23).withMinute(59).withSecond(59).withNano(999999999);
    }


    /**
     * 获取一个月的开始时间
     * 示例: 2020-08-01T00:00
     */
    public static LocalDateTime getMonthStart(LocalDateTime time) {
        return time.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
    }

    /**
     * 获取一个月的结束时间
     * 示例: 2020-08-31T23:59:59.999999999
     */
    public static LocalDateTime getMonthDayEnd(LocalDateTime time) {
        return time.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
    }


    /**
     * 获取当天的开始时间
     * 示例:2020-08-21T00:00
     */
    public static LocalDateTime getDayStart(LocalDate time) {
        return LocalDateTime.of(time, LocalTime.MIN);
    }

    /**
     * 获取一天的结束时间
     * 示例:2020-08-21T23:59:59.999999999
     */
    public static LocalDateTime getDayEnd(LocalDate time) {
        return LocalDateTime.of(time, LocalTime.MAX);
    }


    /**
     * 获取当月的开始时间
     * 示例: 2020-08-01T00:00
     */
    public static LocalDateTime getMonthStart(LocalDate time) {
        return LocalDateTime.of(time.with(TemporalAdjusters.firstDayOfMonth()), LocalTime.MIN);
    }

    /**
     * 获取当月的结束时间
     * 示例: 2020-08-31T23:59:59.999999999
     */
    public static LocalDateTime getMonthDayEnd(LocalDate time) {
        return LocalDateTime.of(time.with(TemporalAdjusters.firstDayOfMonth()), LocalTime.MAX);
    }


    // =============================== 2、时间转字符串 =====================================

    /**
     * 获取 当前 日期时间字符串(yyyy-MM-dd HH:mm:ss)
     *
     * @return
     */
    public static String getCurrentDateTimeStr() {
        return DateTimeFormatter.ofPattern(DATETIME_FORMATTER).format(LocalDateTime.now());
    }

    /**
     * 获取 当前 日期字符串(yyyy-MM-dd)
     *
     * @return
     */
    public static String getCurrentDateStr() {
        return DateTimeFormatter.ofPattern(DATE_FORMATTER).format(LocalDateTime.now());
    }

    /**
     * 获取 当前 时间字符串(yyyy-MM)
     *
     * @return
     */
    public static String getCurrentTimeStr() {
        return DateTimeFormatter.ofPattern(DATE_FORM).format(LocalDateTime.now());
    }

    /**
     * 获取 指定 日期时间字符串(yyyy-MM-dd HH:mm:ss)
     *
     * @return
     */
    public static String getCurrentDateTimeStr(LocalDateTime localDateTime) {
        if (localDateTime == null) {
            return StringUtils.EMPTY;
        }
        return DateTimeFormatter.ofPattern(DATETIME_FORMATTER).format(localDateTime);
    }

    /**
     * 获取 指定 日期字符串(yyyy-MM-dd)
     *
     * @return
     */
    public static String getCurrentDateStr(LocalDateTime localDateTime) {
        if (localDateTime == null) {
            return StringUtils.EMPTY;
        }
        return DateTimeFormatter.ofPattern(DATE_FORMATTER).format(localDateTime);
    }

    /**
     * 获取 指定 时间字符串(yyyy-MM)
     *
     * @return
     */
    public static String getCurrentTimeStr(LocalDateTime localDateTime) {
        if (localDateTime == null) {
            return StringUtils.EMPTY;
        }
        return DateTimeFormatter.ofPattern(DATE_FORM).format(localDateTime);
    }


    /**
     * 获取 指定 日期字符串(yyyy-MM-dd)
     *
     * @return
     */
    public static String getCurrentDateStr(LocalDate localDate) {
        if (localDate == null) {
            return StringUtils.EMPTY;
        }
        return DateTimeFormatter.ofPattern(DATE_FORMATTER).format(localDate);
    }


    // =============================== 3、字符串转时间 =====================================


    /**
     * 将时间字符串转为自定义时间格式的LocalDateTime
     * 字符串格式: yyyy-MM-dd HH:mm:ss
     */
    public static LocalDateTime convertStringToLocalDateTime(String time) {
        return LocalDateTime.parse(time, DateTimeFormatter.ofPattern(DATETIME_FORMATTER));
    }

    /**
     * 字符串格式: yyyy-MM-dd
     */
    public static LocalDate convertStringToLocalDate(String time) {
        return LocalDate.parse(time, DateTimeFormatter.ofPattern(DATE_FORMATTER));
    }


    /**
     * 将时间字符串转为自定义时间格式的LocalDateTime
     *
     * @param time   需要转化的时间字符串
     * @param format 自定义的时间格式
     * @return
     */
    public static LocalDateTime convertStringToLocalDateTime(String time, String format) {
        return LocalDateTime.parse(time, DateTimeFormatter.ofPattern(format));
    }


    // ============================== 4、求两个时间的时间差 ==============================

    /**
     * 获取两个日期的 天数 差
     */
    public static long betweenLessDay(LocalDateTime startTime, LocalDateTime endTime) {
        //时间相减
        Duration duration = Duration.between(startTime, endTime);
        //两个时间差的天数
        return duration.toDays();
    }

    /**
     * 获取两个日期的 小时 差
     */
    public static long betweenLessHour(LocalDateTime startTime, LocalDateTime endTime) {
        Duration duration = Duration.between(startTime, endTime);
        return duration.toHours();
    }


    /**
     * 获取两个日期的 分钟 差
     */
    public static long betweenLessMinutes(LocalDateTime startTime, LocalDateTime endTime) {
        Duration duration = Duration.between(startTime, endTime);
        return duration.toMinutes();
    }

    /**
     * 获取两个日期的 秒 差
     */
    public static long betweenLessMillis(LocalDateTime startTime, LocalDateTime endTime) {
        Duration duration = Duration.between(startTime, endTime);
        return duration.toMillis();
    }


    // ================================ 5. long和LocalDateTime互转 ============================

    /**
     * 将long类型的timestamp转为LocalDateTime
     *
     * @param timestamp
     * @return
     */
    public static LocalDateTime convertTimestampToLocalDateTime(long timestamp) {
        return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
    }

    /**
     * 将LocalDateTime转为long类型的timestamp
     *
     * @param localDateTime
     * @return
     */
    public static long convertLocalDateTimeToTimestamp(LocalDateTime localDateTime) {
        return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
    }
}

 

 

posted @ 2022-02-25 14:19  hanease  阅读(55)  评论(0编辑  收藏  举报