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!!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/439171.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Windows系统安装MongoDB并结合内网穿透实现公网访问本地数据库

文章目录 前言1. 安装数据库2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射2.3 测试随机公网地址远程连接 3. 配置固定TCP端口地址3.1 保留一个固定的公网TCP端口地址3.2 配置固定公网TCP端口地址3.3 测试固定地址公网远程访问 前言 MongoDB是一个基于分布式文件存储的数…

Zookeeper基础知识:成功分布式系统的关键

文章目录 一、引言二、Zookeeper介绍三、Zookeeper安装四、Zookeeper架构【重点】4.1 Zookeeper树形结构4.2 znode类型4.3 Zookeeper的监听通知机制 五、Zookeeper常用操作5.1 zk常用命令5.2 Java连接Zookeeper5.3 Java操作Znode节点5.4 监听通知机制 六、Zookeeper集群【重点】…

基于AI软件平台 HEGERLS智能托盘四向车机器人物流仓储解决方案持续升级

随着各大中小型企业对仓储需求的日趋复杂&#xff0c;柔性、离散的物流子系统也不断涌现&#xff0c;各种多类型的智能移动机器人、自动化仓储装备大量陆续的应用于物流行业中&#xff0c;但仅仅依靠传统的物流技术和单点的智能化设备&#xff0c;已经无法更有效的应对这些挑战…

【学习心得】websocket协议简介并与http协议对比

一、轮询和长轮询 在websocket协议出现之前&#xff0c;要想实现服务器和客户端的双向持久通信采取的是Ajax轮询。它的原理是每隔一段时间客户端就给服务器发送请求找服务器要数据。 让我们通过一个生活化的比喻来解释轮询和长轮询假设你正在与一位不怎么主动说话的老大爷&…

学习人工智能:吴恩达《AI for everyone》2019 第3周:实现智能音箱和自动驾驶的几个步骤;无监督学习;增强学习

吴恩达 Andrew Ng&#xff0c; 斯坦福大学前教授&#xff0c;Google Brain项目发起人、领导者。 Coursera 的联合创始人和联合主席&#xff0c;在 Coursera 上有十万用户的《机器学习》课程&#xff1b;斯坦福大学计算机科学前教授。百度前副总裁、前首席科学家&#xff1b;谷…

开发知识点-Apache Struts2框架

Apache Struts2 介绍S2-001S2CVE-2023-22530介绍 Apache Struts2是一个基于MVC(模型-视图-控制器)设计模式的Web应用程序框架,它是Apache旗下的一个开源项目,并且是Struts1的下一代产品。Struts2是在Struts1和WebWork的技术基础上合并出来的全新web框架,其核心是WebWork。…

网络入侵检测系统之Suricata(十)--ICMP实现详解

ICMP协议 Common header 0 1 2 40 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 0 1 2 3 4--------------------------------| Type | Code | Checksum |-----…

VUE_自适应布局-postcss-pxtorem,nuxt页面自适配

postcss-pxtorem是一个PostCSS插件&#xff0c;用于将CSS中的像素值转换为rem单位&#xff0c;以实现响应式布局和适配不同屏幕尺寸的需求。 它的适配原理是将CSS中的像素值除以一个基准值&#xff0c;通常是设计稿的宽度&#xff0c;然后将结果转换为rem单位。这样&#xff0…

根据用户名称实现单点登录

一、参数格式 二、后端实现 Controller层 public class IAccessTokenLoginController extends BaseController {Autowiredprivate ISysUserService sysUserService;Autowiredprivate ISingleTokenServiceImpl tokenService;/*** 登录方法** return 结果*/PostMapping("/l…

AI智能分析网关V4智慧园区视频智能监管方案

一、背景需求分析 随着科技的不断发展&#xff0c;智慧园区建设已成为现代城市发展的重要方向。通过智能化技术提高园区的运营效率、降低成本、增强环境可持续性等具有重要作用。视频智能监管作为智慧园区安全管理体系的重要组成部分&#xff0c;对于提高园区的安全管理水平和…

女神节快乐,谁说程序猿不懂浪漫, 50多份表白代码收好~

谁说程序猿不懂浪漫&#x1f497; 今天是女神节&#xff0c;祝各位女神节日快乐&#xff01; 在 GitHub 上有个表白代码收藏馆 Awesome-Love-Code&#xff0c;收集了 50 多份表白代码。 GitHub&#xff1a;github.com/sun0225SUN/Awesome-Love-Code 分享给有需要的人。 Web Py…

提高数字化处理质量和效率:重视OCR软件的识别准确率

在当今数字化时代&#xff0c;纸质文件的数字化处理变得尤为重要。而作为纸质文件数字化的关键工具之一&#xff0c;OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;软件的识别准确率对于将大量纸质文件转为Excel具有至关重要的地位。本文将…

必看——怎么把HTTP升级成为HTTPS

现在很多朋友的网站都从原来的HTTP升级成了HTTPS&#xff0c;这种情况就是因为给网站安装了SSL证书的原因&#xff0c;使用了HTTPS协议。安装完SSL证书之后&#xff0c;网站就不会再被浏览器提示不安全&#xff0c;也不会显示连接不安全打不开网站的情况了。而是有一个绿色的小…

分析:如何多线程运行测试用例

这是时常被问到的问题&#xff0c;尤其是UI自动化的运行&#xff0c;过程非常耗时&#xff0c;所以&#xff0c;所以多线程不失为一种首先想到的解决方案。 多线程是针对的测试用例&#xff0c;所以和selenium没有直接关系&#xff0c;我们要关心的是单元测试框架。 unittest …

【C++干货基地】六大默认成员函数: This指针 | 构造函数 | 析构函数

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…

YOLO目标检测——森林火灾烟雾检测数据集【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;森林火灾监控与预警标注说明&#xff1a;使用lableimg标注软件标注&#xff0c;标注框质量高&#xff0c;含voc(xml)、coco(json)和yolo(txt)三种格式标签&#xff0c;分别存放在不同文件夹下&#xff0c;可以直接用于YOLO系列的目标检测。其他&#x…

3D-Genome | Hi-C互作矩阵归一化指南

Hi-C 是一种基于测序的方法&#xff0c;用于分析全基因组染色质互作。它已广泛应用于研究各种生物学问题&#xff0c;如基因调控、染色质结构、基因组组装等。Hi-C 实验涉及一系列生物化学反应&#xff0c;可能会在输出中引入噪声。随后的数据分析也会产生影响最终输出噪声&…

PandasAI—让AI做数据分析

安装 pip install pandasai !pip install --upgrade pandas pandasai 导入依赖项 import pandas as pdfrom pandasai import PandasAIfrom pandasai.llm.openai import OpenAI使用pandas创建一个数据框 df pd.DataFrame({"country": ["United States",…

VisionPro 判断圆是不是无限接近圆或存在缺陷

项目上可能需要判断圆是否是无限接近圆或者判断圆边缘是否存在缺陷等。 第一种方法&#xff1a;找圆工具和点到点的距离计算圆边缘上的点到圆心距离的最大值和最小值的差值。 #region namespace imports using System; using System.Collections; using System.Drawing; usin…

Batch Nomalization 迁移学习

Batch Nomalization 1.Batch Nomalization原理 图像预处理过程中通常会对图像进行标准化处理&#xff0c;这样能够加速网络的收敛。就是按照channel去求均值和方差&#xff0c;然后原数据减均值除标准差&#xff0c;使我们的feature map满足均值为0&#xff0c;方差为1的分布…