101.【用了Optional,同事都表示看不懂!】


参考: 此文章

(一)、Optional

1.Optional的概念

java.util.Optional 是java8中引进的一个新的类,它可以对可能缺失的值进行建模,而不是直接将null赋值给变量

它是用来规范我们开发的API,使其语义更加的明确,使用Optional修饰的对象,表示该对象可能为null。在一定程度上避免了空指针的问题

为什么要用Optional?

java中非常讨厌的一点就是nullpoint,碰到空指针就会出错抛Exception,然后需要逐行检查是哪个对象为空,带来大量的不必要精力损耗,抛出NPE错误不是用户操作的错误,而是开发人员的错误,应该被避免,那么只能在每个方法中加入非空检查,阅读性和维护性都比较差。

2.Optional类用法

Optional类的Javadoc描述如下:这是一个可以为null的容器对象。
如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象
如果值不存在则isPresent()方法会返回false,调用get()方法会NPE

创建Optional类对象的方法:

  • Optional.of(T t) : 创建一个 Optional 实例,t必须非空
  • Optional.empty() : 创建一个空的 Optional 实例
  • Optional.ofNullable(T t):t可以为null

1、创建一个空的

	//使用Optional.empty()方法创建一个空的Car类型的Optional对象。
  Optional<Student> optionalCar = Optional.empty();

2、创建一个非空值的Optional,如果car为null的话,直接抛出空指针异常(参考上面的图片)。

 Car car = new Car();
 Optional<Car> car1 = Optional.of(car);

3、创建一个可以为null的Optional,该方法支持car为null,但是会在用到car的地方抛出异常,但不是空指针异常。

   Car car = new Car();
   Optional<Car> car2 = Optional.ofNullable(car);

判断Optional容器中是否包含对象:

  • boolean isPresent() : 判断是否包含对象
  • void ifPresent(Consumer<? super T> consumer) :如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它

获取Optional容器的对象:

  • T get(): 如果调用对象包含值,返回该值,否则抛异常
  • T orElse(T other) :如果有值则将其返回,否则返回指定的other对象
  • T orElseGet(Supplier<? extends T> other) :如果有值则将其返回,否则返回由Supplier接口实现提供的对象
  • T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值则将其返回,否则抛出由Supplier接口实现提供的异常

过滤:

  • Optional filter(Predicate<? super predicate):如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional

映射

  • Optional map(Function<? super T,? extends U> mapper):如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。
  • Optional flatMap(Function<? super T, Optional> mapper):如果值存在,就对该值执行提供的mapping函数调用,返回一个Optional类型的值,否则就返回一个空的Optional对象

3.创建Optional类

   @Test
    public void testOption() {
        // 1.声明一个空Option
        Optional<Object> empty = Optional.empty();
        // 2. 根据一个非空值创建Optional
        Student student = new Student();
        Optional<Object> of = Optional.of(student);
        // 3. 根据可接受的null 进行创建
        Student student1=null;
        Optional<Student> student11 = Optional.ofNullable(student1);
    }

在这里插入图片描述

4.判断Optional容器中是否包含对象

isPresent不带参数,判断是否为空,ifPresent可以选择带一个消费函数的实例。(isPresent和ifPresent一个是 is 一个是 if 注意一下哈

    @Test
    public void testOption() {

        // 1. isPresent是否有值

        Student student = new Student("吉士先生",21,90);
        Optional<Student> student1 = Optional.ofNullable(student);
        if (student1.isPresent()){
            System.out.println(student.getName());
        }

        // 2. ifPresent有一个Customer匿名内部类

        student1.ifPresent(new Consumer<Student>() {
            @Override
            public void accept(Student student) {
                System.out.println(student.getName());
            }
        });

        // 3. 匿名内部类,进行lambda处理

        student1.ifPresent(s-> System.out.println(s.getName()));

    }

在这里插入图片描述

5.获取Optional容器的对象

    @Test
    public void testOption() throws Exception {

        Student student = null;

        Optional<Student> student1 = Optional.ofNullable(student);
        // 使用get一定要注意,假如student对象为空,get是会报错的
        // java.util.NoSuchElementException: No value present
        Student student3 = student1.get();

        // 当student为空的时候,返回我们新建的这个对象,有点像三目运算的感觉
        Student student2 = student1.orElse(new Student("李明", 20, 16));

        // orElseGet就是当student为空的时候,返回通过Supplier供应商匿名内部类 创建的对象
        student1.orElseGet(new Supplier<Student>() {
            @Override
            public Student get() {
                return new Student("李明2", 21, 17);
            }
        });

        //利用Lambda 实现供应商匿名内部类   {}和return 可以被省略
        student1.orElseGet(()->new Student("李明3", 20, 16));
        

        // orElseThrow就是当student为空的时候,可以抛出我们指定的异常
        student1.orElseThrow(()->new Exception("抛出我们指定的异常"));
    }

6.过滤

    @Test
    public void testOption() throws Exception {

        Student student = new Student("吉士先生",3,20);
        Optional<Student> os1 = Optional.ofNullable(student);
        os1.filter(new Predicate<Student>() {  // 进行过滤
            @Override
            public boolean test(Student student) {
                return student.getName().equals("吉士先生");
            }
        }).ifPresent(new Consumer<Student>() {  //假如说存在值
            @Override
            public void accept(Student student) {
                System.out.println("Ok");
            }
        });

        os1.filter((s)->s.getName().equals("吉士先生")).ifPresent(d-> System.out.println("OK"));
    }

在这里插入图片描述

7. 映射

Map 源码信息

    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }
    @Test
    public void testOption() throws Exception {

        Student student = new Student("吉士先生",3,20);
        Optional<Student> os1 = Optional.ofNullable(student);
        os1.map(new Function<Student, Object>() {  //如果不为空,那么就+1岁
            @Override
            public Object apply(Student student) {
                student.setAge(student.getAge()+1);
                return student;
            }
        });
        System.out.println(student);
        os1.map(s->{s.setAge(s.getAge()+1);return s;});
        System.out.println(student);

    }

在这里插入图片描述
这块的map说实话对lambda不是很熟练的 理解起来是很绕脑子的。

这里的map实际上就是用的Function函数,Function函数是有两个参数的,第一个是入参数据类型,第二个是返回数据类型。Function函数作用就是传入一个对象,然后返回一个对象,返回的对象类型可以自己设置。

T 就是代表实例的泛型数据类型,就是谁调用的 入参 必须跟调用者泛型的数据类型一样。
U 就是自己说了算,调用完map之后返回什么数据类型,那么U就设置什么

源码

public static Optional<Integer> stringToInt(String s) {
     try {
         return Optional.of(Integer.parseInt(s));
     } catch (NumberFormatException e) {
         e.printStackTrace();
         return Optional.empty();
     }
 }
Optional.ofNullable(props.getProperty(name))
        .flatMap(OptionalUtils::stringToInt)
        .filter(i -> i>0)
        .orElse(0);

(二)、匿名内部类

1.什么是匿名内部类?

我们都知道 Lambda 是一个匿名函数,使用Lambda可以帮我们简化匿名内部类的使用,而匿名内部类在实际开发当中并不怎么用,很多人对他的印象其实并不深,所以我们有必要先了解清楚匿名内部类。

什么是匿名内部类。

匿名内部类,就是没有名字的一种嵌套类。它是Java对类的定义方式之一。

为什么要使用匿名内部类?

在实际开发中,我们常常遇到这样的情况:一个接口/类的方法的某个实现方式在程序中只会执行一次,但为了使用它,我们需要创建它的实现类/子类去实现/重写。此时可以使用匿名内部类的方式,可以无需创建新的类,减少代码冗余

匿名内部类只可以使用在接口上吗

不是的。匿名内部类可以用在具体类、抽象类、接口上,且对方法个数没有要求

2.自定义匿名内部类实现

假设当前有一个接口,接口中只有一个方法:

package com.example.springboot01hello.dao;

public interface interface1 {
    void show();
}

为了使用该接口的show方法,我们需要去创建一个实现类,同时书写show方法的具体实现方式

package com.example.springboot01hello.dao;

public class interfaceImpl implements interface1{
    @Override
    public void show() {
        System.out.println("我是实现类");
    }
}

如果实现类InterfaceImpl全程只使用一次,那么为了这一次的使用去创建一个类,未免太过麻烦。我们需要一个方式来帮助我们摆脱这个困境。匿名内部类则可以很好的解决这个问题

我们使用匿名内部类

    @Test
    public void test3(){
        interface1 interface1 = new interface1() {  //实列化一个接口
            @Override
            public void show() {
                System.out.println("这里是一个匿名内部类");
            }
        };
        interface1.show();
    }

注意:匿名内部类不能有普通的静态变量声明,只能有静态常量。

3.线程匿名内部类实现

通常,我们也习惯用匿名内部类的方式创建并启动线程

通过下面示例会发现:Thread是个普通类,可以用匿名内部类,Runnable是个接口他也可以使用匿名内部类创建。

    @Test
    public void test3(){
        new Thread(){  //线程实现匿名内部类, new Thread(){Ctrl+H}
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"创建线程1");
            }
        }.start(); //开启线程


        Runnable runnable = new Runnable() { // 利用Runnable开启线程
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "创建线程2");
            }
        };
        new Thread(runnable).start(); //开启线程
    }

在这里插入图片描述

(三)、函数式(Function)接口

1.函数式接口概述

什么是函数式(Functional)接口

函数式接口就是只有一个方法的接口,同时,只有函数式接口可以使用Lambda表达式

在java.util.function包下定义了Java 8 的丰富的函数式接口

Java当中的lambda是函数式编程吗

在函数式编程语言当中,Lambda表达式的类型是函数。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。

2.经典函数式接口

Runnable接口就是一个典型的函数式接口,在上面创建线程的时候也用到了,他是可以用 匿名内部类 的形式进行实例化的。
Runable是一个典型的函数式接口

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

new Thread(()-> System.out.println(Thread.currentThread().getId()+"<-当前的线程名字")).start();

        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getId());
        };

3.自定义函数式接口

package com.example.springboot01hello.dao;

@FunctionalInterface //表明我们这里是函数式接口,可设可不设。设置的话会提升性能
public interface interface1 {
    void show();
}

函数式接口中使用泛型:

package com.example.springboot01hello.dao;

@FunctionalInterface //表明我们这里是函数式接口,可设可不设。设置的话会提升性能
public interface interface1<T> {
    public  T show(T t);
}

4.@FunctionlInterface用法

  1. 该注解只能标记在"有且仅有一个方法"的接口上,表示函数式接口。
  2. 该注解不是必须的,如果一个接口符合"函数式编程"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查,如果编写的不是函数式接口,但是加上了@Functionallnterface 那么编译器会报错
  3. 在函数式接口使用该注解,javadoc 也会包含一条声明,说明这个接口是一个函数式接口

5.四大核心函数式接口

特别重要!!!

在这里插入图片描述

6.其他函数式接口

在这里插入图片描述

(四)、Lambda表达式

1. Lambda表达式概述

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码

Lambda表达式 和 函数式接口 的关系

Lambda表达式就是一个函数式接口的实例(注意是实例,Object a = new Object()这个a只是个引用变量,而new Object这是一个实例)。只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示

Lambda表达式 和 匿名内部类 的关系

函数式接口是可以使用匿名内部类进行创建实例,而lambda可以简化使用匿名内部类创建

注意: 使用lambda简化匿名内部类的前提条件是,接口必须是函数式接口。而匿名内部类创建实例却不一定非得是函数式接口,甚至都不一定是接口,还可以是普通类。
在这里插入图片描述

匿名内部类、函数式接口、lambda表达式 这三个关系一定要搞清楚。

2.Lambda 语法

Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符

为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:

  1. 左侧:指定了 Lambda 表达式需要的参数列表
  2. 右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即Lambda表达式要执行的功能。

3.匿名内部类 转换 Lambda

从匿名类到 Lambda 的转换举例1:

通过下面示例我们能发现一个问题:使用匿名内部类,没有变量引用也可以创建,而lambda则不可以,他必须要有变量引用。这个变量引用可以是直接的变量引用,也可以是方法内参数传递,Lambda表达式就是一个函数式接口的实例

注意俩字 “实列”!!!!!!

假如没有变量引用例如:

() -> System.out.println("我是一个函数式"); 只有这个肯定是不行的,需要靠前面的TestInterface变量引用来做类型推断。

    @Test
    public void test3() {

        interface1 interface0 = new interface1() {
            @Override
            public void show() {
                System.out.println("我是匿名内部类函数");
            }
        };
        interface0.show();

        interface1 interface2 = () -> {
            System.out.println("我是Lambda表达式");
        };
        interface2.show();
    }

在这里插入图片描述

4.匿名内部类 转换 Lambda 2

通过下面这个示例会发现,他其实就是在不断的优化,尽可能的让我们开发人员少写代码。他是如何做到一步一步省略的?其实这就是Java语法糖

    @Test
    public void test3() {
        // 1.匿名内部类创建线程

        Runnable runnable = new Runnable() { // 利用Runnable开启线程
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "创建线程1");
            }
        };
        new Thread(runnable).start(); //开启线程

        // 2.省去Runnable变量,等同于上方
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "创建线程2");
            }
        }).start(); //开启线程

        // 3.使用Lambda表达式,实现多线程,省去了new Runnable和重写的方法名字
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "创建线程3");
        }).start();

        // 4.优化Lambda,去掉大括号
        new Thread(() ->  System.out.println(Thread.currentThread().getName() + "创建线程3")).start();
    }

在这里插入图片描述

5.Lambda语法格式

代码说明:使用Lambda表达式。一般的格式是()-> 0,如果0里面只有一行代码,则0可以省略。
->左边的()表示参数列表,如果有形参,则在()中添加形参,->右边0表示具体逻辑。如果方法体返回值是void,则甚至可以在0中不写任何逻辑(当然也要结合场景)。返回值如果有值,则需要写具体的逻辑, return处理后的值。

在这里插入图片描述
在这里插入图片描述

6. 类型推断

Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,由编译器推断出来的。这就是所谓的“类型推断”。
在这里插入图片描述
说白了就是利用泛型类的特性,直接可以让我们省去类型。

代码示例:

接口

package com.example.springboot01hello.dao;

@FunctionalInterface //表明我们这里是函数式接口,可设可不设。设置的话会提升性能
public interface interface1<String> {
    void show(java.lang.String str);
}

测试

    @Test
    public void test3() {

        // 匿名内部类创建 带有泛型参数的
        interface1<String> interface0 = new interface1<String>() {
            @Override
            public void show(String str) {
                System.out.println(str);
            }
        };
        interface0.show("hello");
        // 使用lambda进行改造
        interface1<String> stringinterface1 = (String str) -> {
            System.out.println(str);
        };
        stringinterface1.show("world");
        // 使用lambda进行再次优化, 去掉类型和()与{}
        interface1<String> stringinterface3 = str -> System.out.println(str);

        stringinterface3.show("jsxs");

    }

在这里插入图片描述

7.参数传递

我们都知道lambda是一个实例,使用lambda的时候必须要有变量引用,除变量引用外,这样进行方法内的参数传递也是可以的

(1).代码实列一
@FunctionalInterface
interface MyFunc<T> {
    T getValue(T t);
}

public class Test {
    public static void main(String[] args) {
        
        // str的小括号是可以去掉的
        String abc = toUpperString((str) -> str.toUpperCase(), "abc");
        // String abc = toUpperString(str -> str.toUpperCase(), "abc");
        System.out.println(abc);
    }

    public static String toUpperString(MyFunc<String> consumer, String str) {
        return consumer.getValue(str);
    }
}

将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型 必须是 与该 Lambda 表达式兼容的函数式接口的类型。

(2).代码示例二

Java当中很多都是基于参数传递来使用lambda的,就拿集合的forEach来说。
在这里插入图片描述

import java.util.Arrays;
import java.util.List;

public class jsxs1 {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        list.forEach(b-> System.out.println(b));
    }
}

在这里插入图片描述

8.引用

(1).方法引用

什么是方法引用?

方法引用也是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖

什么时候使用方法引用?

当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!

如下三种主要使用情况:

  1. 对象::实例方法名
  2. 类::静态方法名
  3. 类::实例方法名

格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。

ClassName::methodName

使用要求

实现接口的抽象方法的参数列表 和 返回值类型,必须与方法引用的方法的 参数列表 和 返回值类型 保持一致

代码示例一:

Consumer消费型函数,也就是只有入参,没有返回值的。

import java.util.function.Consumer;

public class jsxs1 {
    public static void main(String[] args) {
        // 消费者内部匿名类
        Consumer consumer = new Consumer() {
            @Override
            public void accept(Object o) {
                System.out.println("消费者接口");
            }
        };

        // 创建lamda
        Consumer consumer1 = (x) -> System.out.println(x);

        consumer1.accept("hello");
        // 方法引用
        Consumer consumer2=System.out::print;
        consumer2.accept("world");
    }
}

在这里插入图片描述

代码示例二:
Comparator 比较器 函数,有两个入参,和一个返回值。

import java.util.Comparator;

public class jsxs1 {
    public static void main(String[] args) {

        // 1.匿名内部类创建,<Integer>这个泛型一定要带,不然下面就只能是object
        new Comparator<Integer>(){
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1,o2);
            }
        };

        // lambda方式创建,<Integer>这个泛型一定要带,不然报错
        Comparator<Integer> comparator1 = (a, b) -> Integer.compare(a, b);
        System.out.println(comparator1.compare(1, 2));
        // 方法引用,<Integer>这个泛型一定要带,不然报错
        Comparator<Integer> compare = Integer::compare;
        System.out.println(compare.compare(6, 4));  // 判断前面的值是否大于后面的值
    }
}

在这里插入图片描述

代码示例三:

BiPredicate是一个判定型的 函数,有两个入参,和一个Boolean返回值。

import java.util.Comparator;
import java.util.function.BiPredicate;

public class jsxs1 {
    public static void main(String[] args) {

        // 这里的<String, String>类型不能丢,因为下面用的是String,如果不声明类型就是Object
        BiPredicate biPredicate = new BiPredicate<String, String>() {
            @Override
            public boolean test(String o, String o2) {
                return o.equals(o2);
            }
        };
        
        // 这里变量没有声明类型,那么就是Object,equals也是调用的Object的
        BiPredicate biPredicate1 = (a, b) -> a.equals(b);

        // 这里类型必须要写,因为后面写明了调用String的equals
        BiPredicate<String, String> biPredicate2 = String::equals;
    }
}
(2).构造器引用

格式: ClassName::new

与函数式接口相结合,自动与函数式接口中方法兼容。

要求构造器参数列表要与接口中抽象方法的 参数列表一致!且方法的返回值即为构造器对应类的对象。

import java.util.function.Function;

class Teacher {
    private String name;

    public String getName() {
        return name;
    }

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

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

public class Test4 {
    public static void main(String[] args) {
        // 匿名内部类
        Function function = new Function<String, Teacher>() {
            @Override
            public Teacher apply(String name) {
                return new Teacher(name);
            }
        };

        // lambda
        Function<String, Teacher> function1 = (a) -> new Teacher(a);

        // 构造器引用
        Function<String, Teacher> function2 = Teacher::new;
    }
}
(3).数组引用

格式: type[] :: new

public static void main(String[] args) {
    // 匿名内部类
    Function function = new Function<Integer, Integer[]>() {
        @Override
        public Integer[] apply(Integer x) {
            return new Integer[x];
        }
    };

    // lambda
    Function<Integer, Integer[]> function1 = a -> new Integer[a];

    // 数组引用
    Function<Integer, Integer[]> function2 = Integer[]::new;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吉士先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值