参考: 此文章
(一)、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用法
- 该注解只能标记在"有且仅有一个方法"的接口上,表示函数式接口。
- 该注解不是必须的,如果一个接口符合"函数式编程"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查,如果编写的不是函数式接口,但是加上了@Functionallnterface 那么编译器会报错。
- 在函数式接口使用该注解,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 分为两个部分:
- 左侧:指定了 Lambda 表达式需要的参数列表
- 右侧:指定了 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体的操作,已经有实现的方法了,可以使用方法引用!
如下三种主要使用情况:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
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;
}