JavaSE-10(JDK8 新特性-万字总结)

新特性概述

  • Lambda 表达式
  • 函数式接口
  • 引用
  • Stream API
  • 接口中的默认方法 / 静态方法
  • 新时间日期 API
  • 其他新特性

Lambda表达式/匿名函数

在 Java 中,匿名函数通常是指 Lambda 表达式。

Lambda 表达式允许你以一种简洁、紧凑的方式编写匿名函数,而不必创建命名的方法或类。

它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理 .

演变过程:

  • 垃圾代码 --> 匿名内部类 --> Lambda表达式

匿名内部类:

在 Java 中,匿名内部类是一种特殊的内部类,它没有显式的类名,并且通常在定义的同时创建一个实例。

匿名内部类通常用于简化代码,尤其是在实现接口或抽象类时。

匿名内部类的语法通常是在创建对象的同时定义类,并且使用关键字 new 来实例化对象。

其基本语法如下:

interface SomeInterface {
    void doSomething();
}

class Main {
    public static void main(String[] args) {
        // 创建匿名内部类实例
        SomeInterface obj = new SomeInterface() {
            @Override
            public void doSomething() {
                System.out.println("Doing something...");
            }
        };
        
        // 调用匿名内部类的方法
        obj.doSomething();
    }
}

//在这个例子中,我们定义了一个接口 SomeInterface,然后使用匿名内部类创建了一个实现该接口的对象,并重写了 doSomething 方法。通过 new SomeInterface() 创建了匿名内部类的实例,并且直接实现了接口中的方法。

匿名内部类通常在需要创建临时的、一次性的对象时使用,因为它们没有名字,只能用于创建单个实例。

通常情况下,如果你需要创建多个实例或者需要重复使用类的话,最好还是定义一个独立的类。

但对于一些简单的逻辑或者临时的需求,匿名内部类是一种非常方便的选择,可以减少代码的复杂性。

需要注意的是,匿名内部类不能定义静态成员、静态初始化块或构造函数。此外,匿名内部类中可以访问外部类的成员和方法.

基本语法:

Lambda表达式由逗号分隔的参数列表->符号语句块三部分组成 .

Lambda 表达式的基本语法如下:

(parameters) -> expression

或者是:

(parameters) -> { statements; }

其中,parameters 是参数列表,expression 是单个表达式,statements 是代码块。

示例:

例如 实现Runnable接口:

public class Test02 {
    int num = 10; //jdk 1.7以前 必须final修饰
    
    @Test
    public void test01(){
        //匿名内部类实现Runnable接口
        new Runnable() {
            @Override
            public void run() {
                //在局部类中引用同级局部变量
                //只读
                System.out.println("Hello World" + num);
            }
        };
    }

    @Test
    public void test02(){
        //Lambda语法糖实现Runnable接口
         Runnable runnable = () -> {
             System.out.println("Hello Lambda");
         };
    }
}

有一个参数,无返回值:

@Test
public void test03(){
    Consumer<String> consumer = (a) -> System.out.println(a);
    consumer.accept("我觉得还行!");
}

有一个参数,无返回值 (小括号可以省略不写):

@Test
public void test03(){
    Consumer<String> consumer = a -> System.out.println(a);
    consumer.accept("我觉得还行!");
}

有两个及以上的参数,有返回值,并且 Lambda 体中有多条语句:

@Test
public void test04(){
    Comparator<Integer> comparator = (a, b) -> {
        System.out.println("比较接口");
        return Integer.compare(a, b);
    };
}

有两个及以上的参数,有返回值,并且 Lambda 体中只有1条语句 (大括号 与 return 都可以省略不写):

@Test
public void test04(){
    Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b);
}

总结

在这里插入图片描述

口诀:左右遇一省括号,左侧推断类型省

函数式接口

简介

学习链接: 参考的学习链接

函数式接口的提出是为了给Lambda表达式的使用提供更好的支持。

函数接口指的是只有一个抽象方法的接口,这样的接口可以隐式转换为Lambda表达式。

当然接口中可以包含其他的方法(默认,静态,私有) .

[默认方法和静态方法]不会破坏函数式接口的定义 , 如下所示:

@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();
    // 默认方法
    default void defaultMethod() {}    
    // 静态方法
    static void staticMethod(){}
}

基本使用:

函数式接口是一个只有一个抽象方法的接口。Java 8引入了@FunctionalInterface注解,用于标记这样的接口。

@FunctionalInterface
interface MyInterface {
    void myMethod();
}

函数式接口可以被隐式转换为lambda表达式。即函数式接口可以作为Lambda表达式的类型。

Java 8 引入了Lambda表达式,它提供了一种更简洁的方式来创建实现函数式接口的实例。

@FunctionalInterface
interface MyFunction {
    void myMethod();
}

// 使用Lambda表达式创建函数式接口的实例
MyFunction myFunction = () -> System.out.println("My Method");
myFunction.myMethod();

例如:

在这里插入图片描述

上述Lambda表达式中,() -> System.out.println("...") 代表了 MyInterface 接口的抽象方法 myMethod 的实现。

Lambda表达式在这里提供了更为简洁的语法,避免了显式地创建匿名内部类的语法。

Lambda表达式特别适合函数式接口,因为它们能够以更紧凑的形式表示单一抽象方法的实现

在 Java 8 引入Lambda表达式之前,函数式接口通常通过匿名内部类实现,而Lambda表达式则提供了更加简洁的替代方案。

内置的四大函数式接口

Java 8引入了一些内置的函数式接口,以简化常见的函数式编程任务,用于在函数式编程中执行通用的操作。

在java.util.function包下,Java 内置核心四大函数式接口,可以使用lambda表达式。

函数式接口参数类型返回类型用途
Consumer 消费型接口Tvoid对类型为T的对象应用操作:void accept(T t)
Supplier 提供型接口T返回类型为T的对象:T get()
Function<T, R> 函数型接口TR对类型为T的对象应用操作,并返回结果为R类型的对象:R apply(T t)
Predicate 断言型接口Tboolean确定类型为T的对象是否满足某约束,并返回boolean值:boolean test(T t)

函数型接口

简介

有一个输入,一个输出。

@FunctionalInterface
//T代表输入,R代表输出
public interface Function<T, R> {
    // ...
}

demo:

package com.oddfar.function;

import java.util.function.Function;

/**
 * @author carson
 */
public class Demo01 {
    public static void main(String[] args) {
        其中<String>是函数式接口函数输入参数的类型,<Integer>是输出类型
//        Function<String,Integer> function = new Function<String,Integer>() {
//            @Override
//            public Integer apply(String s) {
//                return s.length();
//            }
//        };

        Function<String,Integer> function = (s)->{return  s.length();};

        System.out.println(function.apply("abc"));
    }
}
apply方法:

根据类型T的参数获取类型R的结果 .

demo:

/**
 * Function<T, R> : 函数式接口,有参有返回值
 * java.util.function<T,R>接口用来根据一个类型的数据得到另一个类型的数据
 */
public class FunctionTest {
    //定义一个方法
    //方法的参数传递一个字符串类型的整数
    //方法的参数传递一个Function接口,泛型使用<String,Integer>
    //使用Function接口中的方法apply,把字符串类型的整数,转换为Integer类型的整数
    private Integer change(String s, Function<String, Integer> function) {
        Integer apply = function.apply(s);
        return apply;
    }

    @Test
    public void test() {
        //定义一个字符串类型的整数
        String s = "1234";
        //调用change方法传递字符串类型的整数,和Lambda表达式
        Integer change = change(s, str -> Integer.parseInt(str));
        System.out.println(change instanceof Integer);
    }
}
andThen方法:

Function接口中的默认方法andThen,用来进行组合操作 . 先转换第一个, 再转换第二个.

demo:

/**
 * Function<T, R> : 函数式接口,有参有返回值
 * java.util.function<T,R>接口用来根据一个类型的数据得到另一个类型的数据
 */
public class FunctionTest {
    //定义一个方法
    //参数传一个字符串类型的整数
    //参数在传两个Function接口
    //一个泛型使用Function<String,Integer>
    //一个泛型使用Function<Integer,String>
    private String change(String s, Function<String, Integer> fun, Function<Integer, String> fun2) {
        System.out.println("apply...");
        return fun.andThen(fun2).apply(s);
    }

    @Test
    public void test2() {
        //定义一个字符串类型的整数
        String s = "123";
        //调用change方法,传递字符串和两个Lambda表达式
        String change = change(s,
                str -> Integer.parseInt(s) + 10,
                integer -> String.valueOf(integer)
        );
        System.out.println(change + ", " + change instanceof String);
    }
}

断定型接口

简介

有参有返回值, 对某种类型的数据进行判断 , 返回boolean类型 .

public class Demo {
    public static void main(String[] args) {
//        Predicate<String> predicate = new Predicate<String>() {
//            @Override
//            public boolean test(String s) {
//                return s.isEmpty();
//            }
//        };
        Predicate<String> predicate = (s)->{return s.isEmpty();};

        System.out.println(predicate.test("abc"));

    }
}

test方法:

oolean test(T t):用于对指定类型数据进行判断, 符合条件返回true,不符合条件返回false

boolean test(T t);

demo:

/**
 * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
 */
public class PredicateTest {
    //定义一个方法
    //参数传递一个String类型的字符串
    //传递一个Predicate接口,泛型使用String
    //使用predicate中的方法test对字符串进行判断,并把判断的结果返回
    public boolean checkString(String s, Predicate<String> predicate) {
        // 对某种数据类型的数据进行判断,结果返回一个boolean值
        return predicate.test(s);
    }

    @Test
    public void test() {
        //定义一个字符串
        String s = "abcdef";
        //调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式
        //对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回
        boolean b = checkString(s, str -> str.length() > 5);
        System.out.println(b);
    }
}  
and方法:

可以连接多个判断的条件 . 连接的多个判断条件都为true才返回true, 否则false.

demo:

/**
 * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
 */
public class PredicateTest {
    //定义一个方法,方法的参数,传递一个字符串
    //传递俩个Predicate接口
    //1、判断字符串的长度是否大于5  2、判断字符串中是否包含a  两个条件必须同时满足,使用&&运算符连接两个条件
    private boolean checkString(String s, Predicate<String> predicate, Predicate<String> predicate2) {
		//等价于return pre1.test(s)&&pre2.test(s);
        return predicate.and(predicate2).test(s); 
    }

    @Test
    public void test2() {
        //定义一个字符串
        String s = "asdfgi";
        //调用checkString方法,参数传递字符串和两个Lambda表达式
        boolean b = checkString(s,
                str -> !StringUtils.isEmpty(str) && str.length() > 5,
                str -> !StringUtils.isEmpty(str) && str.contains("a"));
        System.out.println(b);
    }
}
or方法:

可以连接多个判断的条件 . 连接的多个判断条件有一个为true就返回true, 都为false才返回false.

demo:

/**
 * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
 */
public class PredicateTest {
    @Test
    public void test2() {
        //定义一个字符串
        String s = "asdfgi";
        //调用checkStringOr方法,参数传递字符串和两个Lambda表达式
        boolean b = checkStringOr(s,
                str -> !StringUtils.isEmpty(str) && str.length() > 5,
                str -> !StringUtils.isEmpty(str) && str.contains("a")
        );
        System.out.println(b);
    }

    //定义一个方法,方法的参数,传递一个字符串
    //传递俩个Predicate接口
    //1、判断字符串的长度是否大于5  2、判断字符串中是否包含a  满足一个条件即可,使用||运算符连接两个条件
    private boolean checkStringOr(String s, Predicate<String> predicate, Predicate<String> predicate2) {
        return predicate.or(predicate2).test(s);
    }
}
negate方法:

取反:非真则假,非假则真 .

demo:

/**
 * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
 */
public class PredicateTest {
    //定义一个方法,方法的参数,传递一个字符串
    public boolean checkStringNegate(String s, Predicate<String> predicate) {
        //使用Predicate接口判断字符串的长度是否大于5
        //return !pre.test(s);
        return predicate.negate().test(s);
    }

    @Test
    public void testNegate() {
        //定义一个字符串
        String s = "asdfg";
        //调用checkString方法,参数传递字符串和Lambda表达式
        boolean b = checkStringNegate(s, str -> str.length() > 5);
        System.out.println(b);
    }
}

消费型接口

简介:

有一个输入参数,参数类型由泛型决定 ,没有返回值。

@FunctionalInterface
public interface Consumer<T> {
    //....
}

accept方法:

表示消费一个指定泛型类型的数据 .

void accept(T t);

demo:

/**
 * Consumer<T>:消费型接口,有参无返回值
 */
public class ConsumerTest {
    //定义一个方法
    //方法的参数传递一个字符串的姓名
    //方法的参数传递Consumer接口,泛型使用String
    //可以使用Consumer接口消费字符串的姓名
    private void accept(String name, Consumer<String> consumer) {
        consumer.accept(name);
    }

    @Test
    public void test() {
        //对传递的字符串进行消费
        //消费方式,把字符串进行反转输出
        accept("admin", (name) -> {
            String reName = new StringBuilder(name).reverse().toString();
            System.out.println(reName);
        });
    }
}
andThen方法:

Consumer接口的默认方法andThen , 可以把两个Consumer接口组合到一起,再对数据进行消费 ( 谁写前边,谁先消费 ).

demo:

/**
 * Consumer<T>:消费型接口,有参无返回值
 */
public class ConsumerTest {
    //定义一个方法,方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型使用字符串
	private void andThen(String s, Consumer<String> c1, 
                         Consumer<String> c2) {
		//使用andThen方法,把两个Consumer接口连接到一起,先消费c1再消费c2
		c1.andThen(c2).accept(s);
	}
    
    @Test
    public void test2() {
        andThen("admin",
                (name) -> {
                    System.out.println(name);
                },
                (name) -> {
                    //消费方式,把字符串转换为大写输出
                    System.out.println(name.toUpperCase(Locale.ROOT));
                });
    }
}

消费型接口使用示例:
public class Demo03 {
    public static void main(String[] args) {

//        Consumer<String> consumer = new Consumer<String>() {
//            @Override
//            public void accept(String s) {
//                System.out.println(s);
//            }
//        };
        Consumer<String> consumer = s -> { System.out.println(s); };
        consumer.accept("abc");
    }

}

Consumer 表示在单个输入参数上执行的操作。

顾名思义,它是“消费者的含义”,接受参数而不返回值.

平时我们打印字符串,本质也是接受一个参数并打印出来,我们一般想都不想,会这样写:

在这里插入图片描述

一旦你用了 Consumer之后,感觉更加优雅一些:

Consumer c = System.out::println;

c.accept("hello world");
c.accept("hello codesheep");
c.accept("bilibili cheers");

而且 Consumer还可以用联用,达到多重处理的效果,比如:

c.andThen(c).andThen(c).accept("hello");// 会连续打印3次hello

供给型接口

简介

没有输入参数,只有返回参数

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

demo:

public class Demo04 {
    public static void main(String[] args) {
//        Supplier<String> supplier = new Supplier<String>() {
//            @Override
//            public String get() {
//                return null;
//            }
//        };
        Supplier<String> supplier = () -> { return "abc";};
        System.out.println(supplier.get());
    }

}
get方法:

用来获取一个泛型参数指定类型的对象数据。

意味着对应的Lambda表达式需要"对外提供"一个符合泛型类型的对象数据。

demo:

/**
 * Supplier<T>:供给型接口,无参有返回值
 */
public class SupplierTest {

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

    @Test
    public void test() {
        //定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String,get方法就会返回一个String
        String s = getString(() -> "人才");
        System.out.println(s);
    }

    private Integer getMax(Supplier<Integer> supplier) {
        //定义一个方法,用于获取int类型数组中元素的最大值,方法的参数传递Supplier接口,泛型使用包装类Integer
        return supplier.get();
    }

    @Test
    public void test2() {
        //定义一个int类型的数组,并赋值
        int[] arr = {100, 78, -887, 66, 90};
        //调用getMax方法,方法参数Supplier是一个函数式接口,可以传递Lambda表达式
        int maxValue = getMax(() -> {
            int max = arr[0];
            for (int a : arr) {
                if (a > max) {
                    max = a;
                }
            }
            return max;
        });
        System.out.println(maxValue);
    }
}

自定义函数式接口

定义函数式接口:

@FunctionalInterface
public interface MyFunctionalInterface {
    //定义一个抽象方法, public abstract可以省略
    public abstract void method();
}

接口实现类方式实现接口方法:

public class MyFunctionalInterfaceImpl implements MyFunctionalInterface {
    @Override
    public void method() {
        System.out.println(">>>MyFunctionalInterfaceImpl");
    }
}

函数式接口的使用示例:

/**
 * 函数式接口的使用测试
 */
public class MyFunctionalInterfaceTest {
    @Test
    public void test() {
        // 调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象
        show(new MyFunctionalInterfaceImpl());
        // 调用show方法,方法的参数是一个接口,所以可以传递接口的匿名内部类
        show(new MyFunctionalInterface() {
            @Override
            public void method() {
                System.out.println(">>>MyFunctionalInterface的匿名内部类实现");
            }
        });
        // 调用show方法,方法的参数是一个函数式接口,可以传递Lambda表达式
        show(() -> System.out.println(">>>MyFunctionalInterface的lambda表达式实现"));
    }

    // 定义一个方法,参数使用函数式接口MyFunctionalInterInterface
    public void show(MyFunctionalInterface myFunctionalInterface) {
        myFunctionalInterface.method();
    }
}

Lambda实现函数式接口时的延迟加载

使用函数式接口, 应用Lambda表达式方式实现可以达到延迟加载的目的, 优化提升系统性能.

举例:

场景描述: 程序运行根据日志级别输出日志信息.

若是如下的实现方式:

/**
 * 根据不同日志级别输出日志信息
 */
public class LogPrintTest {
    @Test
    public void test() {
        //定义三个日志信息
        String msg1 = "Hello ";
        String msg2 = "World ";
        String msg3 = "Java";
        //调用showLog方法,传递日志级别和日志信息
        printLog(2, msg1 + msg2 + msg3);
    }

    //定义一个根据日志的级别,显示日志信息的方法
    public void printLog(int level, String message) {
        //对日志的等级进行判断,如果式1级别,那么输出日志信息
        if (level == 1) {
            System.out.println(message);
        }
    }
}

分析:

以上代码存在性能浪费, 调用printLog方法,第二个参数是一个拼接后的字符串, 先把字符串拼接好,然后在调用printLog方法打印日志信息. printLog方法中如果传递的日志等级不是1, 也会进行字符串的拼接, 导致不必要的资源浪费.

若使用lambda表达式优化:

/**
 * 根据不同日志级别输出日志信息
 */
public class LogPrintTest {
    @Test
    public void test2() {
        //定义三个日志信息
        String msg1 = "Hello ";
        String msg2 = "World ";
        String msg3 = "Java";
        //调用showLog方法,参数MessageBulider是一个函数式接口,所以可以传递Lambda表达式
        showLog(1, () -> msg1 + msg2 + msg3);
    }

    //定义一个显示日志的方法,方法的参数传递日志的等级和Message接口
    public void showLog(int level, MessageBulider mb) {
        //对日志的等级进行判断,如果是1级,则调用MessageBulider接口中的BuilderMessage方法
        if (level == 1) {
            System.out.println(mb.builderMessage());
        }
    }

    // 定义一个函数式接口
    @FunctionalInterface
    public interface MessageBulider {
        //定义一个拼接消息的抽象方法,返回拼接的消息
        String builderMessage();
    }
}

分析:

使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中 , 只有满足日志等级是1的条件, 才会传递第二个参数(接口MessageBuilder中的方法builderMessage拼接日志信息); 如果不满足日志等级是1的条件, 那么MessageBuilder接口中的方法builderMessage也不会执行, 拼接字符串的代码也不会执行, 所以不会存在性能的浪费

函数式接口作为方法的参数

Java中的Lambda表达式可以被当作是匿名内部类的替代品。

如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。

使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。

示例:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
/**
 * lambda表达式作为方法的参数
 */
public class LambdaTest2 {
    @Test
    public void test() {
        //Lambda表达式实现函数式接口
        startThread(() -> System.out.println(">>>lambda表达式实现线程任务执行!"));
        //匿名内部类实现函数式接口
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(">>>匿名内部类方式实现线程任务执行!");
            }
        });
    }

    //定义一个方法startThread,方法的参数使用函数式接口Runnable
    private void startThread(Runnable task) {
        //开启多线程
        new Thread(task).start();
    }
}

引用

方法引用

Java 8 引入了方法引用(Method Reference)的概念,它提供了一种更简洁的语法来表示已经存在的方法。

方法引用是一种简化Lambda表达式的语法。它可以直接引用已经存在的方法,而不需要重新编写Lambda表达式。

**定义:**若 Lambda 表达式体中的内容已有方法实现,则我们可以使用“方法引用”

也可以理解为方法引用是lambda表达式的另外一种表现形式, 并且其语法比lambda表达式更加简单 .

注意: Lambda表达式中传递的参数一定是方法引用中的那个方法可以接收的参数类型 , 否则会抛出异常

demo:

/**
 * 方法的基本引用
 */
public class PrintFunctionalTest {
    @Test
    public void test() {
        // 方式1: lambda表达式实现了函数式接口, 进行字符串的输出打印
        this.printstring("test print", str -> System.out.println(str));

        // 方式2: 方法引用方式实现字符串的输出打印
        // 引用方法的参数就是lambda表达式的参数
        //双冒号:: 为引用运算符 , 它所在的表达式被称为方法引用 . 如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号::来引用该方法作为Lambda表达式的替代者。
        this.printstring("test print by qout", System.out::println);
    }
    
	//定义一个方法,方法的参数传递PrintFunctionInterface接口
    private void printstring(String str, PrintFunctionInterface printFunctionInterface) {
        printFunctionInterface.printStr(str);
    }

    // 定义一个函数式接口
    @FunctionalInterface
    public interface PrintFunctionInterface {
        //定义打印字符串的抽象方法
        void printStr(String str);
    }
}

具体的语法格式及示例:

方法引用的基本语法是使用双冒号(::)将方法的名称与对象或类的名称分隔开。它有几种不同的形式:

  • 对象 :: 成员方法 【引用特定对象的成员方法】

    @Test
    public void test01(){
        PrintStream ps = System.out;
        //lambda
        Consumer<String> con1 = (s) -> ps.println(s);
        con1.accept("aaa");
    	//引用:
        Consumer<String> con2 = ps::println;
        con2.accept("bbb");
    }
    
  • 类 :: 静态方法 【引用静态方法】

    @Test
    public void test02(){
        //lambda
        Comparator<Integer> com1 = (x, y) -> Integer.compare(x, y);
        System.out.println(com1.compare(1, 2));
    	//引用:在这个例子中,`Integer::compare 是对 `compare` 静态方法的引用。
        Comparator<Integer> com2 = Integer::compare;
        System.out.println(com2.compare(2, 1));
    }
    
  • 类 :: 成员方法 【引用特定类的成员方法】

    @Test
    public void test03(){
        //lambda
        BiPredicate<String, String> bp1 = (x, y) -> x.equals(y);
        System.out.println(bp1.test("a","b"));
    	//引用,这里: String::equals 引用了 String 类的实例方法 equals。
        BiPredicate<String, String> bp2 = String::equals;
        System.out.println(bp2.test("c","c"));
    }
    
  • super: : 成员方法 【super引用父类成员方法】

    因为有子父类关系,所以存在一个关键字super,代表父类,所以直接使用Super调用父类的成员方法.

    public class Parent {
        void sayHello() {
            System.out.println("hello, i'm parent");
        }
    }
    
    /**
     * super引用父类成员方法
     */
    public class SuperReferenceTest extends Parent {
    
        @Test
        public void test() {
            //调用method方法,方法的参数Greetable是一个函数式接口,所以可传递Lambda
            greet(() -> {
                Parent parent = new Parent();
                parent.sayHello();
            });
    
            // 直接使用super调用
            greet(() -> {
                super.sayHello();
            });
    
            // supper优化lambda表达式, 方法引用
            greet(super::sayHello);
        }
    
        //定义一个方法参数传递Greetable接口
        public void greet(Greetable greetable) {
            greetable.greet();
        }
    
        @Override
        void sayHello() {
            System.out.println("hello, i'm children");
        }
    
        // 定义打招呼的函数式接口
        @FunctionalInterface
        interface Greetable {
            void greet();
        }
    }
    
  • this : : 成员方法 【this引用本类成员方法】

    public class Parent {
        void sayHello() {
            System.out.println("hello, i'm parent");
        }
    }
    
    /**
     * super引用父类成员方法
     */
    public class SuperReferenceTest extends Parent {
    
        @Test
        public void test2() {
            // 方式1
            greet(() -> {
                SuperReferenceTest children = new SuperReferenceTest();
                children.sayHello();
            });
            // 方式2
            greet(() -> this.sayHello());
            // 方式三  this引用
            greet(this::sayHello);
        }
    
        //定义一个方法参数传递Greetable接口
        public void greet(Greetable greetable) {
            greetable.greet();
        }
    
        @Override
        void sayHello() {
            System.out.println("hello, i'm children");
        }
    
        // 定义打招呼的函数式接口
        @FunctionalInterface
        interface Greetable {
            void greet();
        }
    }
    

构造器引用

格式:

  • ClassName :: new

示例:

在这里插入图片描述

在这个例子中,ArrayList::new 是对 ArrayList 构造方法的引用,等价于 Lambda 表达式 () -> new ArrayList<>()

有参构造器的引用:

/**
 * 构造器的方法引用
 */
public class ConstructRefrenceTest {
	/**
     * 有参构造方法的引用
     */
    @Test
    public void test() {
        // 方式1: lambda表达式, 自己new对象实例化, 传入name参数
        this.printName("crysw", name -> new Person(name));
        // 方式2: 传入的name参数就是构造方法的参数, 可以使用类的构造器引用来替换lambda表达式实现
        this.printName("crysw agin", Person::new);
    }

    private void printName(String name, PersonBuilder personBuilder) {
        personBuilder.builderPerson(name);
        System.out.println(name);
    }

    //定义一个创建Person对象的函数式接口
    @FunctionalInterface
    interface PersonBuilder {
        //定义一个方法,根据传递的姓名,创建Person对象返回
        Person builderPerson(String name);
    }

    // 定义一个Person类
    public class Person {
        private String name;

        public Person() {
        }

        public Person(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

无参构造器的引用:

/**
 * 无参构造方法的引用
 */
@Test
public void test2() {
	// 方式1:
	Person person = this.getPerson(() -> new Person());
	System.out.println(">>" + person);
	person = this.getPerson(Person::new);
	System.out.println(">>>" + person);
}

private Person getPerson(Supplier<Person> supplier) {
	return supplier.get();
}

**注意:**需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致

数组引用

格式Type[]::new

demo1:

/**
 * 数组的构造器引用
 */
public class ArrayRefrenceTest {

    @Test
    public void test() {
        // 方式1: lambda表达式,传递length参数构造数组对象
        int[] array = this.createArray(2, (length) -> new int[length]);
        System.out.println(">>" + array.length);
        // 方式2: 传递的length参数就是构造数组对象的参数, 可以使用数组的构造器引用
        array = this.createArray(2, int[]::new);
        System.out.println(">>>" + array.length);
    }

    //定义一个方法,方法的参数传递创建数组的长度和ArrayBuilder接口
    //方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回
    private int[] createArray(int length, ArrayBuilder arrayBuilder) {
        return arrayBuilder.builderArray(length);
    }

    @FunctionalInterface
    interface ArrayBuilder {
        //定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
        int[] builderArray(int length);
    }
}

demo2:

@Test
public void test2() {
    // 方式1: lambda表达式, 通过传递的参数构建String数组
	String[] strArr = this.getString(10, integer -> new String[integer]);
	System.out.println(">>" + strArr.length);
    // 方式2: String数组构造方法的引用实现
	strArr = this.getString(20, String[]::new);
	System.out.println(">>>" + strArr.length);
}

// 定义一个方式, 参数传入函数接口, 将Integer类型参数转换为一个String[interger]类型返回
private String[] getString(Integer integer, Function<Integer, String[]> function) {
	return function.apply(integer);
}

总结

  1. 当Lambda表达式仅仅是调用一个现有方法时,可以使用方法引用,使代码更简洁。
  2. 方法引用使得代码更容易理解,特别是对于一些简单的操作。
  3. 在函数式接口中,方法引用可用于提供接口方法的实现。

方法引用是Lambda表达式的一种补充,能够让代码更加简洁、清晰,并且降低出错的概率。


Stream API

简介

在这里插入图片描述

Stream API 不仅仅指的是 stream() 这个方法,而是整个流式处理框架。

为什么选择Stream流?

在 Java8 之前,主要通过 for 循环或者 Iterator 迭代来重新排序合并数据,又或者通过重新定义 Collections.sorts 的 Comparator 方法来实现,这两种方式对于大数据量系统来说,效率并不是很理想。

Stream 的聚合操作与数据库 SQL 的聚合操作 sorted、filter、map 等类似。我们在应用层就可以高效地实现类似数据库 SQL 的 聚合操作了,而在数据操作方面,Stream 不仅可以通过串行的方式实现数据操作,还可以通过并行的方式处理大批量数据,提高数据 的处理效率。

stream() 方法是用于将集合转换为 Stream 的入口方法,但 Stream API 包括了一系列的中间操作(intermediate operations)和终端操作(terminal operations),用于对流进行各种处理。

在这里插入图片描述

操作的三个步骤

在这里插入图片描述

创建流

  /**
* 创建流
*/

@Test
public void test01(){
    /**
    * 集合流
    *  - Collection.stream() 穿行流
    *  - Collection.parallelStream() 并行流
    */
    List<String> list = new ArrayList<>();
    Stream<String> stream1 = list.stream();

    //数组流
    //Arrays.stream(array)
    String[] strings = new String[10];
    Stream<String> stream2 = Arrays.stream(strings);

    //Stream 静态方法[可以获取数组对应的流]
    //Stream.of(...)
    Stream<Integer> stream3 = Stream.of(1, 2, 3);

    //无限流
    //迭代
    Stream<Integer> stream4 = Stream.iterate(0, (i) -> ++i+i++);
    stream4.forEach(System.out::println);

    //生成
    Stream.generate(() -> Math.random())
        .limit(5)
        .forEach(System.out::println);
}

中间操作

中间操作会返回一个新的流,一个流可以后面跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤. 然后会返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),仅仅调用到这类方法,并没有真正开始流的遍历, 而是在终结操作开始的时候才真正开始执行。

在这里插入图片描述

筛选与切片:

  • filter:接收 Lambda ,从流中排除某些元素
  • limit:截断流,使其元素不超过给定数量
  • skip(n):跳过元素,返回一个舍弃了前n个元素的流;若流中元素不足n个,则返回一个空流;与 limit(n) 互补
  • distinct:筛选,通过流所生成的 hashCode() 与 equals() 取除重复元素

demo:

List<Employee> emps = Arrays.asList(
    new Employee(101, "Z3", 19, 9999.99),
    new Employee(102, "L4", 20, 7777.77),
    new Employee(103, "W5", 35, 6666.66),
    new Employee(104, "Tom", 44, 1111.11),
    new Employee(105, "Jerry", 60, 4444.44)
);

@Test
public void test01(){
    emps.stream()
        .filter((x) -> x.getAge() > 35)
        .limit(3) //短路?达到满足不再内部迭代
        .distinct()
        .skip(1)
        .forEach(System.out::println);
}

映射:

  • map:接收 Lambda ,将元素转换为其他形式或提取信息;接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素

    @Test
    public void test02(){
        List<String> list = Arrays.asList("a", "b", "c");
        list.stream()
            .map((str) -> str.toUpperCase())
            .forEach(System.out::println);
    }
    
    List<String> result = list.stream()
                             .filter(s -> s.startsWith("a"))
                             .map(String::toUpperCase)
                             .distinct()
                             .collect(Collectors.toList());
    
  • flatMap:接收一个函数作为参数,将流中每一个值都换成另一个流,然后把所有流重新连接成一个流

    public Stream<Character> filterCharacter(String str){
        List<Character> list = new ArrayList<>();
        for (char c : str.toCharArray()) {
            list.add(c);
        }
    
        return list.stream();
    }
    
    @Test
    public void test03(){
        List<String> list = Arrays.asList("a", "b", "c");
        Test02 test02 = new Test02();
        list.stream()
            .flatMap(test02::filterCharacter)
            .forEach(System.out::println);
    }
    

排序:

  • sorted():自然排序

    @Test
    public void test04(){
        List<Integer> list = Arrays.asList(1,2,3,4,5);
        list.stream()
            .sorted() //comparaTo()
            .forEach(System.out::println);
    }
    
  • sorted(Comparator c):定制排序

    @Test
    public void test05(){
        emps.stream()
            .sorted((e1, e2) -> { //compara()
                if (e1.getAge().equals(e2.getAge())){
                    return e1.getName().compareTo(e2.getName());
                } else {
                    return e1.getAge().compareTo(e2.getAge());
                }
            })
            .forEach(System.out::println);
    }
    

终止操作

终止操作是指返回最终的结果。一个流只能有一个终止操作,当这个操作执行后,这个流就被使用完毕了,无法再被操作。

查找/匹配:

  • allMatch:检查是否匹配所有元素
  • anyMatch:检查是否至少匹配一个元素
  • noneMatch:检查是否没有匹配所有元素
  • findFirst:返回第一个元素
  • findAny:返回当前流中的任意元素
  • count:返回流中元素的总个数
  • max:返回流中最大值
  • min:返回流中最小值

demo1:

long count = list.stream()
                .filter(s -> s.startsWith("a"))
                .count();

demo2:

public enum Status {
    FREE, BUSY, VOCATION;
}

@Test
public void test01(){
    List<Status> list = Arrays.asList(Status.FREE, Status.BUSY, Status.VOCATION);

    boolean flag1 = list.stream()
        .allMatch((s) -> s.equals(Status.BUSY));
    System.out.println(flag1);

    boolean flag2 = list.stream()
        .anyMatch((s) -> s.equals(Status.BUSY));
    System.out.println(flag2);

    boolean flag3 = list.stream()
        .noneMatch((s) -> s.equals(Status.BUSY));
    System.out.println(flag3);

    // 避免空指针异常
    Optional<Status> op1 = list.stream()
        .findFirst();
    // 如果Optional为空 找一个替代的对象
    Status s1 = op1.orElse(Status.BUSY);
    System.out.println(s1);

    Optional<Status> op2 = list.stream()
        .findAny();
    System.out.println(op2);

    long count = list.stream()
        .count();
    System.out.println(count);
}

规约/收集:

  • 归约:reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中的数据反复结合起来,得到一个值

    /**
    * Java:
    *  - reduce:需提供默认值(初始值)
    */
    @Test
    public void test01(){
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        //将流中的元素通过指定的操作进行累积。在这里,初始值是0,累积操作是将两个元素相加
        Integer integer = list.stream()
            .reduce(0, (x, y) -> x + y);
        System.out.println(integer);
    }
    
  • 收集:collect 将流转换成其他形式;接收一个 Collector 接口的实现,用于给流中元素做汇总的方法

    List<Employee> emps = Arrays.asList(
      new Employee(101, "Z3", 19, 9999.99),
      new Employee(102, "L4", 20, 7777.77),
      new Employee(103, "W5", 35, 6666.66),
      new Employee(104, "Tom", 44, 1111.11),
      new Employee(105, "Jerry", 60, 4444.44)
    );
    
    @Test
    public void test02(){
      //放入List
      List<String> list = emps.stream()
          .map(Employee::getName)
          .collect(Collectors.toList()); 
      list.forEach(System.out::println);
      
      //放入Set
      Set<String> set = emps.stream()
          .map(Employee::getName)
          .collect(Collectors.toSet());
      set.forEach(System.out::println);
    
      //放入LinkedHashSet
      LinkedHashSet<String> linkedHashSet = emps.stream()
          .map(Employee::getName)
          .collect(Collectors.toCollection(LinkedHashSet::new));
      linkedHashSet.forEach(System.out::println);
    }
    
    @Test
    public void test03(){
      //总数
      Long count = emps.stream()
          .collect(Collectors.counting());
      System.out.println(count);
    
      //平均值
      Double avg = emps.stream()
          .collect(Collectors.averagingDouble(Employee::getSalary));
      System.out.println(avg);
    
      //总和
      Double sum = emps.stream()
          .collect(Collectors.summingDouble(Employee::getSalary));
      System.out.println(sum);
    
      //最大值
      Optional<Employee> max = emps.stream()
          .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
      System.out.println(max.get());
    
      //最小值
      Optional<Double> min = emps.stream()
          .map(Employee::getSalary)
          .collect(Collectors.minBy(Double::compare));
      System.out.println(min.get());
    }
    
    @Test
    public void test04(){
      //分组
      Map<Integer, List<Employee>> map = emps.stream()
          .collect(Collectors.groupingBy(Employee::getId));
      System.out.println(map);
    
      //多级分组
      Map<Integer, Map<String, List<Employee>>> mapMap = emps.stream()
          .collect(Collectors.groupingBy(Employee::getId, Collectors.groupingBy((e) -> {
              if (e.getAge() > 35) {
                  return "开除";
              } else {
                  return "继续加班";
              }
          })));
      System.out.println(mapMap);
      
      //分区
      Map<Boolean, List<Employee>> listMap = emps.stream()
          .collect(Collectors.partitioningBy((e) -> e.getSalary() > 4321));
      System.out.println(listMap);
    }
    
    @Test
    public void test05(){
      //总结
      DoubleSummaryStatistics dss = emps.stream()
          .collect(Collectors.summarizingDouble(Employee::getSalary));
      System.out.println(dss.getMax());
      System.out.println(dss.getMin());
      System.out.println(dss.getSum());
      System.out.println(dss.getCount());
      System.out.println(dss.getAverage());
      
      //连接
      String str = emps.stream()
          .map(Employee::getName)
          .collect(Collectors.joining("-")); //可传入分隔符
      System.out.println(str);
    }
    

并行流

简介

在jdk1.8新的stream包中针对集合的操作也提供了并行操作流和串行操作流。

  • 并行流:就是把一个内容分成几个数据块,并用不同的线程分别处理每个数据块的流

  • Java 8 中将并行进行了优化,我们可以很容易的对数据进行操作;Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与串行流之间切换,集合中的 parallelStream ()方法开启一个并行流处理。

  • jdk1.8并行流使用的是fork/join框架进行并行操作.

    Fork/Join 框架:Java7就出现的框架, 在必要的情况下,将一个大任务拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行汇总( join )

    在一般的线程池中, 如果一个线程正在执行的任务由于某些原因无法继续运行, 那么该线程会处于等待(阻塞)状态.

    在fork/join框架中,如果某个子任务由于等待另外一个子任务的完成而无法继续运行. 那么处理该子任务的线程会主动寻找其他尚未运行的子任务来执行. 这种方式减少了线程的等待时间, 提高了性能.。

Java 8 并行流 / 串行流:

@Test
public void test03(){
  //串行流(单线程):切换为并行流 parallel()
  //并行流:切换为串行流 sequential()
  LongStream.rangeClosed(0, 100000000L)
      .parallel() //底层:ForkJoin
      .reduce(0, Long::sum);

}

常用流转换方法

  • boolean isParallel():判断一个流是否是并行流,如果是则返回 true,否则返回 false。
  • S sequential():基于调用流返回一个顺序流,如果调用流本身就是顺序流,则返回其本身。
  • S parallel():基于调用流,返回一个并行流,如果调用流本身就是并行流,则返回其本身。
  • S unordered():基于调用流,返回一个无序流。
  • S onClose(Runnable closeHandler):返回一个新流,closeHandler 指定了该流的关闭处理程序,当关闭该流时,将调用这个处理程序。
  • void close():从 AutoCloseable 继承来的,调用注册关闭处理程序,关闭调用流(很少会被使用到)。

demo:

@Test
public void test3() {
	List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    // 顺序流执行
	list.stream().forEach(System.out::print); // 12345
	System.out.println("\n");
    // 并行流, 多核多线程处理, 打印结果不是顺序打印
	list.parallelStream() 
			.forEach(System.out::print); // 34521
	System.out.println("\n");
}

总结

Stream API 的核心理念是声明式编程,它允许你通过一系列的链式调用来定义对数据集的处理流程。这种方式使得代码更为清晰、简洁,并且在某些情况下可以更容易地进行并行处理。

//eg
package com.oddfar.stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class User {
    private int id;
    private String userName;
    private int age;
    //get、set、有参/无参构造器、toString
}


/**
 * 题目:请按照给出数据,找出同时满足以下条件的用户
 * 也即以下条件:
 * 1、全部满足偶数ID
 * 2、年龄大于24
 * 3、用户名转为大写
 * 4、用户名字母倒排序
 * 5、只输出一个用户名字 limit
 */
public class StreamDemo {
    public static void main(String[] args) {
        User u1 = new User(11, "a", 23);
        User u2 = new User(12, "b", 24);
        User u3 = new User(13, "c", 22);
        User u4 = new User(14, "d", 28);
        User u5 = new User(16, "e", 26);
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);

        /**
         * 1. 首先我们需要将 list 转化为stream流
         * 2. 然后将用户过滤出来,这里用到一个函数式接口 Predicate<? super T>,我们可以使用lambda表达式简化
         * 3. 这里面传递的参数,就是Stream流的泛型类型,也就是User,所以,这里可以直接返回用户id为偶数的用户信息;
         * 4. 通过forEach进行遍历,直接简化输出 System.out::println
         */
        list.stream()
                .filter(u -> {
                    return u.getId() % 2 == 0;
                })
                .filter(u -> {
                    return u.getAge() > 24;
                })
                .map(u -> {
                    return u.getUserName().toUpperCase();
                })
                //.sorted() //默认正排序 自己用 compareTo 比较
                .sorted((o1, o2) -> {
                    return o2.compareTo(o1);
                })
                .limit(1)
                .forEach(System.out::println);


        // map解释:
        List<Integer> list2 = Arrays.asList(1, 2, 3);
        list2 = list2.stream().map(x -> {
            return x * 2;
        }).collect(Collectors.toList());

        for (Integer element : list2) {
            System.out.println(element);
        }
        
    }

}

Optional

简介

在Java 8之前,Google Guava引入了Optionals类来解决这个空指针异常NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。

定义:Optional本质是个容器,你可以将你的变量交由它进行封装,这样我们就不用显式对原变量进行 null值检测,防止出现各种空指针异常。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

Optional仅仅是一个容器:存放T类型的值或者null。它提供了方法来避免显式的null检查.

Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。

常用方法:

  • Optional.empty() : 创建一个空的 Optional 实例
  • Optional.of(T t) : 创建一个非空Optional实例, 如果传递的参数是null,抛出NullPointerException.
  • Optional.ofNullable(T t):若参数T t不为null,创建一个非空Optional实例,否则创建一个空实例.
  • isPresent() : 判断是否包含值
  • ifPresent(Consumer<? super T> consumer) :如果存在值,则使用该值调用指定的消费者,否则不执行任何操作。
  • T get(): 如果调用对象包含值,返回该值,否则抛异常
  • orElse(T t) : 如果Optional实例包含值,返回该值,否则返回默认值T t.
  • orElseGet(Supplier<? extends T> other) :如果Optional实例包含, 获取Optional对象的值,如果值不存在则执行指定的Supplier函数来获取默认值。
  • map(Function<? super T, ? extends U> mapper): 如果有值则用Function对其处理,并返回处理后的Optional,否则返回 Optional.empty()
  • flatMap(Function mapper):与 map 类似,要求返回值必须是Optional

示例:

Optional.of(T t):

@Test
public void test01(){
    Optional<Employee> op = Optional.of(new Employee());
    Employee employee = op.get();
}

Optional.empty(T t):

@Test
public void test02(){
    Optional<Employee> op = Optional.empty();
    Employee employee = op.get();
}

Optional.ofNullable(T t):

@Test
public void test03(){
    Optional<Employee> op = Optional.ofNullable(new Employee());
    Employee employee = op.get();
}

isPresent():

@Test
public void test03(){
    Optional<Employee> op = Optional.ofNullable(new Employee());
    if (op.isPresent()) {
        Employee employee = op.get();
    }
}

再比如:

我们想写一个获取学生某个课程考试分数的函数:getScore():

在这里插入图片描述

这么多嵌套的 if 判空多少有点扎眼!为此我们必须引入 Optional类

public Integer getScore(Student student){
    return Optional.ofNullable(student).map(Student::getSubject).map(Subject.score).orElse(null);
}

demo:

demo1:

@Test
public void test() {
	Integer value1 = null;
	Integer value2 = new Integer(10);
	// Optional.ofNullable - 允许传递为 null 参数
	Optional a = Optional.ofNullable(value1);
	// Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException (NPE)
	Optional b = Optional.of(value2);
	System.out.println("求和:" + sum(a, b));
	System.out.println(a.toString());
	System.out.println(b.toString());
}

private Integer sum(Optional<Integer> a, Optional<Integer> b) {
	// Optional.isPresent - 判断值是否存在
	System.out.println("第一个参数值存在: " + a.isPresent());
	System.out.println("第二个参数值存在: " + b.isPresent());
	// Optional.orElse - 如果值存在,返回它,否则返回默认值
	Integer value = a.orElse(new Integer(20));
	//Optional.get - 获取值,值需要存在
	Integer value2 = b.get();
	return value + value2;
}

demo2:

@Test
public void test2() {
	Optional<String> fullName = Optional.ofNullable("admin");
	System.out.println("Full Name is set? " + fullName.isPresent());
	System.out.println("Full Name: " + fullName.orElseGet(() -> "[none]"));
	System.out.println(fullName.map(s -> "Hey " + s + "!").orElse("Hey Stranger!"));
}
// 执行结果
Full Name is set? true
Full Name: admin
Hey admin!

demo3:

private void print(String s) {
	// 如果参数s构建的Optional实例不为空, 则执行指定的lambda进行消费
	Optional.ofNullable(s).ifPresent(System.out::println);
}

private Integer getLength(String s) {
    // 如果参数s构建的Optional实例不为空, 则继续转换获取s的长度, 如果为空返回-1
	return Optional.ofNullable(s).map(String::length).orElse(-1);
}

@Test
public void test3() {
	String strA = "abcd ";
	String strB = null;
	print(strA); // abcd
	print(strB); // 没有操作被执行
	System.out.println(getLength(strA)); // 4
	System.out.println(getLength(strB)); // -1
    // java.util.NoSuchElementException: No value present
    System.out.println(Optional.empty().get()); 
    // java.lang.NullPointerException
    System.out.println(Optional.of(null));
    // java.util.NoSuchElementException: No value present
    System.out.println(Optional.ofNullable(null).get());
}

接口

Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。

默认方法

在JDK 8之前,接口中只能定义抽象方法,而不能有具体的实现。JDK 8引入了接口的默认方法,允许在接口中定义具体的方法实现。这样一来,接口的实现类就不需要全部实现接口中的方法,可以选择性地重写。

public interface MyFun {

    default String getName(){
        return "libo";
    }

    default Integer getAge(){
        return 22;
    }
}

默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写.

例子代码如下:

private interface Defaulable {
    // 默认方法
    default String notRequired() { 
        return "Default implementation"; 
    }        
}

// 继承接口的默认方法
private static class DefaultableImpl implements Defaulable {
}

// 重写接口的默认方法
private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}

//Defaulable接口使用关键字default定义了一个默认方法notRequired()。DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。

在这里插入图片描述

尽管默认方法有这么多好处,但在实际开发中应该谨慎使用. 在复杂的继承体系中,默认方法可能引起歧义和编译错误。

静态方法

Java 8带来的另一个有趣的特性是在接口中可以定义静态方法.

public interface MyFun {

    static void getAddr(){
        System.out.println("addr");
    }

    static String Hello(){
        return "Hello World";
    }
}

最佳实践

下面示例整合了默认方法和静态方法的使用:

/**
 * 接口中的默认方法和静态方法
 */
public class DefaultNStaticInterfaceTest {

    @Test
    public void test() {
        Defaulable defaulable = DefaulableFactory.create(DefaultableImpl::new);
        System.out.println(defaulable.notRequired());
        defaulable = DefaulableFactory.create(OverridableImpl::new);
        System.out.println(defaulable.notRequired());
    }

    private interface DefaulableFactory {
        // 静态方法
        static Defaulable create(Supplier<Defaulable> supplier) {
            return supplier.get();
        }
    }

    private interface Defaulable {
        // 默认方法
        default String notRequired() {
            return "Default implementation";
        }
    }

    // 继承接口的默认方法
    private static class DefaultableImpl implements Defaulable {
    }

    // 重写接口的默认方法
    private static class OverridableImpl implements Defaulable {
        @Override
        public String notRequired() {
            return "Overridden implementation";
        }
    }
}

新的日期时间API

JDK 8引入了新的日期和时间API,提供了更加方便和易用的日期和时间处理方式。它解决了旧的Date和Calendar类在设计上的一些问题,并且提供了更多的功能。

LocalDateTime

LocalDateTime是一个不可变的日期时间对象,代表日期时间,通常被视为年 - 月 - 日 - 时 - 分 - 秒。 也可以访问其具体日期和时间字段.

获取当前的日期时间:

LocalDateTime date = LocalDateTime.now();
System.out.println("当前时间: " + date);

System.out.println(date.getYear());
System.out.println(date.getMonthValue());
System.out.println(date.getDayOfMonth());
System.out.println(date.getHour());
System.out.println(date.getMinute());
System.out.println(date.getSecond());
System.out.println(date.getNano());

手动创建一个LocalDateTime实例:

LocalDateTime date2 = LocalDateTime.of(2017, 12, 17, 9, 31, 31, 31);
//LocalDateTime date3 = date2.withMonth(10).withDayOfMonth(10).withYear(2012);
System.out.println(date2);
// 进行加操作,得到新的日期实例
LocalDateTime date3 = date2.plusDays(12);
System.out.println(date3);
// 进行减操作,得到新的日期实例
LocalDateTime date4 = date3.minusYears(2);
System.out.println(date4);

LocalDate

LocalDate是一个不可变的日期对象,表示日期,通常被视为年月日。 也可以访问其具体的日期字段。

创建具体的LocalDate实例:

// 12 december 2014
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("date3: " + date3);
// 解析日期字符串
LocalDate parseDate = LocalDate.parse("2014-12-12");
System.out.println("parseDate: " + parseDate);

LocalTime

LocalTime是一个不可变的时间对象,代表一个时间,通常被看作是小时 -分-秒。

创建LocalTime实例:

// 22 小时 15 分钟
LocalTime date4 = LocalTime.of(22, 15);
System.out.println("date4: " + date4);
// 解析时间字符串
LocalTime date5 = LocalTime.parse("20:15:30");
System.out.println("date5: " + date5);

ZonedDateTime

ZonedDateTime是具有时区的日期时间的不可变表示。 此类存储所有日期和时间字段.

创建ZonedDateTime对象:

// 获取当前时间日期
ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
System.out.println("date1: " + date1); // date1: 2015-12-03T10:15:30+08:00[Asia/Shanghai]
// 获取时区
System.out.println(date1.getZone().getId()); // Asia/Shanghai
ZoneId id = ZoneId.of("Europe/Paris"); 
System.out.println("ZoneId: " + id); // Europe/Paris

Instant

Instant表示在时间线上建立单个瞬时点。 这可能用于在应用程序中记录事件时间戳。

// 时间戳  1970年1月1日00:00:00 到某一个时间点的毫秒值
// 默认获取UTC时区
Instant ins = Instant.now();
System.out.println(ins);
//  下面两个等价
long x = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
System.out.println(x);
long x1 = System.currentTimeMillis();
System.out.println(x1);

 System.out.println(Instant.now().toEpochMilli());        
 System.out.println(
     Instant.now().atOffset(ZoneOffset.ofHours(8)).toInstant().toEpochMilli()
 );

Duration

Duration用于计算两个时间之间的间隔 .

计算Instance:

@Test
public void test6() throws InterruptedException {
	// Duration:计算两个时间之间的间隔
	Instant ins1 = Instant.now();
	TimeUnit.MILLISECONDS.sleep(1000);
	Instant ins2 = Instant.now();
	Duration dura = Duration.between(ins1, ins2);
	System.out.println(dura);
	System.out.println(dura.toMillis());
}

计算LocalTime:

@Test
public void test7() throws InterruptedException {
	// Period:计算两个日期之间的间隔
	LocalTime localTime = LocalTime.now();
	TimeUnit.MILLISECONDS.sleep(1000);
	LocalTime localTime2 = LocalTime.now();
	Duration du2 = Duration.between(localTime, localTime2);
	System.out.println(du2);
	System.out.println(du2.toMillis());
}

计算LocalDateTime:

@Test
public void test8() throws InterruptedException {
	LocalDateTime localDateTime = LocalDateTime.now();
	TimeUnit.MILLISECONDS.sleep(1000);
	LocalDateTime localDateTime2 = LocalDateTime.now();
	Duration du2 = Duration.between(localDateTime, localDateTime2);
	System.out.println(du2);
	System.out.println(du2.toMillis());
}

Period

Period:计算两个日期之间的间隔.

@Test
public void test9() throws InterruptedException {
	LocalDate localDate = LocalDate.now();
	TimeUnit.MILLISECONDS.sleep(1000);
	LocalDate localDate1 = LocalDate.now();
	Period p = Period.between(localDate, localDate1);
	System.out.println(p);
	System.out.println(p.getYears() + "-" + p.getMonths() + "-" + p.getDays());
}

总结

  • 之前使用的java.util.Date月份从0开始,我们一般会+1使用,很不方便,java.time.LocalDate月份和星期都改成了enum.
  • java.util.Date和SimpleDateFormat都不是线程安全的,而LocalDate和LocalTime和最基本的String一样,是不变类型,不但线程安全,而且不能修改。
  • java.util.Date是一个“万能接口”,它包含日期、时间,还有毫秒数,更加明确需求取舍
  • 新接口更好用的原因是考虑到了日期时间的操作,经常发生往前推或往后推几天的情况。用java.util.Date配合Calendar要写好多代码,而且一般的开发人员还不一定能写对。

The End!!创作不易,欢迎点赞/评论!!欢迎关注个人地球号/GZH!!

在这里插入图片描述

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值