一、Lambda表达式

在了解 Lambda 之前,首先回顾以下Java的方法。

Java的方法分为实例方法,例如:Integerequals()方法:

public final class Integer{
boolean equals(Object o){
...
}
}

还有静态方法,例如:IntegerparseInt()的方法:

public final class Integer{
public static int parseInt(String s){
...
}
}

无论是实例方法还是静态方法,本质上都相当于过程式语言的函数。例如C函数:

char* strcpy(char* dest, char* src);

只不过Java的实例方法隐含地传入了一个this变量,即实例方法总是有一个隐含参数this

函数式编程(Functional Programming)是把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。历史上研究函数式编程的理论是Lambda演算,所以我们经常把支持函数式编程的编码风格称为Lambda表达式。

1. Lambda表达式及优点

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

Java程序中,我们遇到了许多单方法的接口,即:一个接口只定义了一个方法:

  • Comparator
  • Comsumer
  • Runnable
  • Callable

Runnable为例,当我们想要创建新的线程,或者要调用run()方法时,需要创建Runnable实例,以匿名类方式编写如下:

//匿名内部类
Runnable r1 = new Runnable(){
@Override
public void run(){
System.out.println("Hello World!");
}
};

上方写法比较繁琐,将单方法的接口转换成Lambda表达式,为:

Runnable r1 = () -> System.out.println("Hello Lambda");

2. Lambda语法

在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 ->, 该操作符被称为Lambda操作符箭头操作符。它将 Lambda 分为两个部分:

  • ->左侧:指定了Lambda表达式需要的参数列表(接口中的抽象方法的形参列表)。
  • ->右侧:指定了Lambda体,是抽象方法的实现逻辑,也即Lambda表达式要执行的功能。(其实就是重写抽象方法的方法体)

语法格式

  1. 没有参数、没有返回值

    Runnable r1 = () -> {
    System.out.println("Hello Lambda!");
    };
  2. 需要一个参数、但是没有返回值

    Consumer<String> consumer = (String s) -> {
    System.out.println(s);
    };
  3. 数据类型可以省略,因为可以有编译器推断出,称为类型推断

    Consumer<String> con = (s) -> {
    System.out.println(s);
    }; //之后的那个`String`可以不写,由于类型推断可以自动识别
    ArrayList<String> list = new ArrayList<String>();
    //数组中
    int[] arr = new int[]{1,2,3};
    //也可以写成,因为类型推断,不需要再写`new int[]`
    int[] arr = {1,2,3};
  4. 若只需要一个参数时,参数的小括号可以省略

    Consumer<String> con1 = s -> {
    System.out.println(s);
    };
  5. 若需要两个或两个以上的参数,多条执行语句,并且有返回值

    Comparator<Integer> com2 = (o1, o2) -> {
    System.out.println(o1);
    System.out.println(o2);
    return o1.compareTo(o2);
    };
  6. Lambda体只有一条语句时,return{}若有,都省略

    Comparator<Integer> com3 = (o1, o2) -> o1.compareTo(o2);

Lambda表达式的本质:作为接口的实例(失去了接口就失去了意义)。

而且这个接口只有一个抽象方法,叫做函数式接口(单方法接口)

3. 类型推断

上述Lambda表达式中的参数类型都是由编译器推断得出的。

Lambda 表达式中无需指定类型,程序依然可以编译,这是因为javac根据程序的上下文,在后台推断出了参数的类型。

Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的类型推断

二、函数式接口

什么是函数式接口

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

  • 你可以通过Lambda表达式来创建该接口的对象。(若Lambda表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
  • 我们可以在一个接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口。同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。

不加@FunctionalInterface,实际上也是函数式接口,

但是当显式的加@FunctionalInterface之后,就不可以声明两个或两个以上个方法。

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

如何理解函数式接口

简单说:在Java8中,Lambda表达式就是一个函数式接口的实例。

这就是Lambda表达式和函数式接口的关系,也就是说,只要一个对象是函数式接口的实例,那么该对象接可以用Lambda表达式来表示。

所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。

@FunctionalInterface
public interface Runnable{
public abstract void run();
}

自定义函数式接口

@FunctionalInterface
public interface MyNumber{
public double getValue();
}

在函数式接口中使用泛型:

@FunctionalInterface
public interface MyFunction<T>{
public T getValue(T t);
}

Lambda参数作为参数传递

public static String method(MyFunctionInterface mf, String str){
return mf.method(str);
}
public static void main(String[] args){
String abcd = "abcd";
String string = method(new MyFunctionInterface(){
@Override
public String method(String str){
return str.toUpperCase();
}
}, abcd);
System.out.println(string);
}

转换成Lambda表达式的形式:

public static void main(String[] args){
String abcd = "abcd";
String string = method(str -> str.toUpperCase(), abcd);
}

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

Java内置的函数式接口

简单的两个例子:

//定义一个以函数式接口为参数的方法
public void happyTime(double money, Consumer<Double> consumer){
consumer.accept(money);
} @Test
public void test(){
happyTime(500, new Consumer<Double>(){
@Override
public void accept(Double money){
System.out.println("消费:" + money);
}
});
//转换成Lambda表达式的形式
happyTime(500, x -> System.out.println("消费:" + x));
}
public List<String> filterString(List<String> list, Predicate<String> predicate){
ArrayList<String> filter = new ArrayList<>();
for (String str : list){
if (predicate.test(str)){
filter.add(str);
}
}
return filter;
} @Test
public void test(){
List<String> list = Arrays.asList("beijing","tianjin","nanjing","dongjing","xijing","pujing");
List<String> jing = filterString(list, new Predicate<String>(){
@Override
public boolean test(String s){
return s.contaions("jing");
}
});
//转换成Lambda的形式
jing = filterString(list, str -> str.contains("jing"));
jing.forEach(System.out::println);
}

其余的函数式接口,相当于是以上四种的变种:

三、方法引用和构造器引用

方法引用

对方法引用的理解

方法引用可以看作是Lambda表达式深层次的表达。

换句话说:方法引用本质上就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的另一种写法。

具体来说:什么时候可以使用方法引用?

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用。
  • 所谓方法引用,是指如果某个方法的参数列表和返回值和函数式接口的抽象方法恰好一致,就可以直接传入方法引用。

方法引用的语法要求及格式:

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

方法引用主要有三种使用情况:

  • 情况一:对象::实例方法名
  • 情况二:类::静态方法名
  • 情况三:类::实例方法名

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

简单几个小例子:

Consumer<String> con = (x) -> System.out.println(x);
//等同于:
Consumer<String> con2 = System.out::println;
//---------------------------------------------------------
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
//等同于
Comparator<Integer> com2 = Integer::compare;
int value = com.compare(12,21);
//---------------------------------------------------------
BiPredicate<String, String> bp = (x, y) -> x.equals(y);
//等同于
BiPredicate<String, String> bp1 = String::equals;
boolean flag = bp1.test("hello", "hi");

方法引用的简单使用

使用Lambda表达式,我们可以不必编写FunctionalInterface接口的实现类,从而简化代码,实际上除了Lambda表达式,我们还可以直接传入方法引用,例如:

public class Main{
public static void main(String[] args){
String[] array = new String[]{"Apple","Orange","Banana","Lemon"};
Arrays.sort(array, Main::cmp);
System.out.println(String.join(",", array));
}
public static int cmp(String s1, String s2){
return s1.compareTo(s2);
}
}

上述代码在Arrays.sort()中直接传入了静态方法cmp的引用,用Main::cmp表示,其中Main是类名,cmp是静态方法名,属于类::静态方法名类型。

因为Comparator<String>接口定义的方法是int compare(String, String),和静态方法int cmp(String, String)相比,除了方法名之外,方法参数列表和返回类型相同,因此,我们说两者的签名一致,可以把方法名作为Lambda表达式传入:

Comparator<String> comparator = (o1, o2) -> o1.compareTo(o2);
Arrays.sort(array, comparator);
//但实际上`int cmp(String, String)`与`comparator`的抽象方法的签名相同
Arrays.sort(array, Main::cmp);

在这里,方法签名只看参数类型返回值类型,不看方法名称,也不看类的继承关系。

我们将代码改成如下格式:

public class Main{
public static void main(String[] args){
String[] array = new String[]{"Apple","Orange","Banana","Lemon"};
Arrays.sort(array, String::compareTo);
System.out.println(String.join(",", array));
}
}

String::compareTo方法也符合Lambda定义。观察String.compareTo()的方法定义:

public final class String{
public int compareTo(String o){
...
}
}

这个方法的签名只有一个参数,为什么和int Comparator<String>.compare(String, String)能匹配呢?

因为实例方法有一个隐含的this参数,String类的compareTo()方法在实际调用的时候,第一个隐含参数总是传入this,相当于静态方法,所以情况三:类::实例方法,就是说的此类情景:

public static int compareTo(this, String o);

当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName

所以String.compareTo()方法也可以作为方法引入传入。

构造器引用

除了可以引用静态方法实例方法之外,我们还可以引用构造方法

简单看一个小例子:如果要把一个List<String>转换成为List<Person>,传统的做法代码如下:

class Person{
String name;
public Person(String name){
this.name = name;
}
public String toString(){
return "Person:" + this.name;
}
}
List<String> names = List.of("Bob", "Alice", "Tim");
List<Person> persons = new ArrayList<>();
for(String name : names){
persons.add(new Person(name));
}

要更简单地实现StringPerson的转换,我们可以引用Person的构造方法:

public class Main{
public static void main(String[] args){
List<String> names = List.of("Bob","Alice","Tim");
List<Person> persons = names.stream().map(Person::new).collect(Collectors.toList());
System.out.println(persons);
}
}

构造器引用ClassName::new

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

可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致,且方法的返回值即为构造器对应类的对象。

Function<Integer, MyClass> function = (n) -> new MyClass(n);
//等同于:
Function<Integer, MyClass> function = MyClass::new;

数组引用

数组引用格式type[]::new

Function<Integer, Integer[]> function = (n) -> new Integer[n];
//等同于:
Function<Integer, Integer[]> function = Integer[]::new;

四、Stream API

什么是Stream

Java8中有两大最为重要的改变。第一个是Lambda表达式,另外一个则是Stream API

Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤、映射数据等操作。

使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。

也可以使用Stream API来并行执行操作,简言之:Stream API提供了一种高效且易于使用的处理数据的方式。

Stream实际上是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

为什么要使用Stream

实际开发中,项目中多数数据源都是来自于MySQL、Oracle等,但现在数据源可以更多了,有MongDB、Redis,而这些NoSQL的数据就需要Java层面去处理。

StreamCollection集合的区别:

  • Collection是一种静态的内存数据结构,而Stream是有关计算的。
  • 前者是主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算。
  1. Stream自己不会存储元素
  2. Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream
  3. Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行

Stream的操作三个步骤:

1. 创建Stream流

我们通过四种方法可以获得Stream流:

  1. 通过集合创建Stream流,Java8中的Collection接口被扩展,提供了两个获取流的方法:

    default Stream<E> stream():返回一个顺序流;
    default Stream<E> parallelStream():返回一个并行流;
    //通过集合创建Stream
    List<Employee> employees = EmployeeData.getEmployees();
    //default Stream<E> stream():返回一个顺序流
    Stream<Employee> stream = employees.stream();
    //default Stream<E> parallelStream():返回一个并行流
    Stream<Employee> parallelStream = employees.parallelStream();
  2. 通过数据创建Stream流,Java8中的Arrays的静态方法stream()可以获取数组流:

    static <T> Stream<T> stream(T[] array):返回一个流

    重载形式,能够处理对应的基本类型的数组:

    public static IntStream stream(int[] array);
    public static LongStream stream(long[] array);
    public static DoubleStream stream(double[] array);
    //基本数据类型
    int[] arr = new int[]{1,2,3,4,5,6};
    IntStream intStream = Arrays.stream(arr); //其余类型采用泛型
    Employee e1 = new Employee(1001, "Tom");
    Employee e2 = new Employee(1002, "Jerry");
    Employee[] arr1 = new Employee[]{e1, e2};
    Stream<Employee> stream = Arrays.stream(arr1);
  3. 通过Streamof()方法,通过显示值创建一个流,它可以接收任意数量的参数:

    public static<T> Stream<T> of(T... values):返回一个流
    Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);

    创建Stream最简单的方式,传入可变参数即可创建一个能输出确定元素的Stream流。但实际上没有什么实际用途,但是在测试的时候很方便。

  4. 通过调用静态方法Stream.iterate()Stream.generate()创建无限流:

    //迭代
    public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f);
    //生成
    public static<T> Stream<T> generate(Supplier<T> s);
    @Override
    public void test(){
    //迭代
    Stream<Integer> stream = Stream.iterate(0, x -> x + 2);
    stream.limit(10).forEach(System.out::println); //生成
    Stream<Double> stream1 = Stream.generate(Math::random);
    stream1.limit(10).forEach(System.out::println);
    }

2. 中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为惰性求值

1. 筛选与切片

filter(Predicate p):接收`Lambda`,从流中排除某些元素;
distinct():筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素;
limit(long maxSize):截断流,使其元素不超过给定数量;
skip(long n):跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与 limit(n) 互补。
@Test
public void test1(){
List<Employee> list = EmployeeData.getEmployees();
Stream<Employee> employeeStream = list.stream();
//filter(Predicate p) 接收 Lambda , 从流中排除某些元素
//查询员工表中薪资大于7000的员工信息
employeeStream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);
System.out.println("-----------------------------------------------");
//limit(long maxSize) 截断流,使其元素不超过给定数量
//不能再这样使用,因为上方执行了终止操作,不能再返回中间操作
//employeeStream.limit(3).forEach(System.out::println);
list.stream().limit(3).forEach(System.out::println);
System.out.println("-----------------------------------------------");
//skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
list.stream().skip(3).forEach(System.out::println);
System.out.println("-----------------------------------------------");
//distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
list.add(new Employee(1010, "刘强东", 40, 8000));
list.add(new Employee(1010, "刘强东", 40, 8000));
list.add(new Employee(1010, "刘强东", 40, 8000));
list.add(new Employee(1010, "刘强东", 40, 8000));
list.add(new Employee(1010, "刘强东", 40, 8000));
list.stream().distinct().forEach(System.out::println);
System.out.println("-----------------------------------------------");
}

2. 映射

map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
@Test
public void test2(){
//map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
List<String> strings = Arrays.asList("aa", "bb", "cc");
strings.stream().map(String::toUpperCase).forEach(System.out::println);
System.out.println(strings);
System.out.println("------------------------------------------------");
List<Employee> employees = EmployeeData.getEmployees();
employees.stream().filter(x -> x.getName().length() > 3).map(Employee::getName).forEach(System.out::println);
//练习:
System.out.println("------------------------------------------------");
Stream<Stream<Character>> streamStream = strings.stream().map(StreamAPITest2::fromStringToStream);
streamStream.forEach(s ->{
s.forEach(System.out::println);
});
System.out.println("------------------------------------------------");
//flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
Stream<Character> characterStream = strings.stream().flatMap(StreamAPITest2::fromStringToStream);
characterStream.forEach(System.out::println);
} /**
* 将String字符串转换成为 Character的stream流
*/
public static Stream<Character> fromStringToStream(String str){
ArrayList<Character> list = new ArrayList<>();
for (Character c : str.toCharArray()){
list.add(c);
}
return list.stream();
}

3. 排序

sorted():产生一个新流,其中按自然顺序排序
sorted(Comparator com):产生一个新流,其中按比较器顺序排序
@Test
public void test3(){
//sorted() 产生一个新流,其中按`自然顺序`排序
List<Integer> list = Arrays.asList(1, 3, 2, 7, 9, 5, 6);
list.stream().sorted().forEach(System.out::println);
//抛异常,employee类没有实现Comparable接口
List<Employee> employees = EmployeeData.getEmployees();
//employees.stream().sorted().forEach(System.out::println);
//sorted(Comparator com) 产生一个新流,其中`按比较器顺序`排序
employees.stream().sorted((e1, e2) -> {
int ageValue = Integer.compare(e1.getAge(), e2.getAge());
if (ageValue != 0){
return ageValue;
}else{
return -Double.compare(e1.getSalary(), e2.getSalary());
}
}).forEach(System.out::println);
System.out.println("--------------------------------------");
employees.stream().sorted(Comparator.comparingInt(Employee::getAge)).forEach(System.out::println);
}

3. 终止操作(终端操作)

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void

流进行了终止操作后,不能再次使用。

1. 匹配与查找

allMatch(Predicate p):检查是否匹配所有元素
anyMatch(Predicate p):检查是否至少匹配一个元素
noneMatch(Predicate p):检查是否没有匹配所有元素
findFirst():返回第一个元素
findAny():返回当前流中的任意元素
count():返回流中元素总数
max(Comparator c):返回流中最大值
min(Comparator c):返回流中最小值
forEach(Consumer c):内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)
//allMatch(Predicate p):检查是否匹配所有元素
//练习:是否所有的员工年龄都大于18岁
List<Employee> employees = EmployeeData.getEmployees();
boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
System.out.println(allMatch);
//anyMatch(Predicate p):检查是否至少匹配一个元素
//是否存在员工的工资>10000
boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
System.out.println(anyMatch);
//noneMatch(Predicate p):检查是否没有匹配所有元素
//是否存在员工姓`雷`
boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
System.out.println(noneMatch);
//findFirst():返回第一个元素
Optional<Employee> employee = employees.stream().findFirst();
System.out.println(employee);
//findAny():返回当前流中的任意元素
Optional<Employee> employee1 = employees.parallelStream().findAny();
System.out.println(employee1);
//count():返回流中元素总数
long count = employees.size();
long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
System.out.println(count);
//max(Comparator c):返回流中最大值
//返回最高的工资
Optional<Double> maxSalary = employees.stream().map(Employee::getSalary).max(Double::compareTo);
System.out.println(maxSalary);
//min(Comparator c):返回流中最小值
//返回工资最低的员工
Optional<Employee> minSalaryEmployee = employees.stream().min(Comparator.comparingDouble(Employee::getSalary));
System.out.println(minSalaryEmployee);
//forEach(Consumer c):内部迭代
employees.stream().forEach(System.out::println);
//是集合的遍历操作
employees.forEach(System.out::println);

2. 规约

reduce(T iden, BinaryOperator b):可以将流中元素反复结合起来,得到一个值。返回T
reduce(BinaryOperator b):可以将流中元素反复结合起来,得到一个值。返回Optional<T>
//reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T
//练习:计算1-10的自然数的和
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream().reduce(0, Integer::sum);
System.out.println(sum);
//reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
//练习:计算公司中所有员工的工资总和
List<Employee> employees = EmployeeData.getEmployees();
Optional<Double> salarySum = employees.stream().map(Employee::getSalary).reduce(Double::sum);
System.out.println(salarySum);

3. 收集

collect(Collector c):将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
  • Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)。
  • 另外,Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

//查找工资大于6000的员工,返回一个list
List<Employee> employees = EmployeeData.getEmployees();
List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
employeeList.forEach(System.out::println);

并行流

将一个内容分为多个数据块,使用不同的线程分别处理每个数据块的流,并且含有工作窃取,即一个核对应的任务完成后,会从其他核偷取任务来执行,大大提高了执行效率。

  • 使用parallel切换为并行流

  • 使用sequential切换为串行流

在之前有一个ForkJoin框架,这个而框架就实现了对一个任务的拆分和合并,大大提高了处理速度。

在Java8之后我们可以使用并行流来处理(底层仍然是ForkJoin框架)

Instant start = Instant.now();
LongStream.range(0, 100000000000L)
.parallel()
.reduce(0, Long::sum);
Instant end = Instant.now();
System.out.println(Duration.between(start, end).toMillis());

运算一千亿个数循环相加,用时19s,CPU利用率基本占满。

Optional类

为什么要使用Optional类

到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。

以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染。

它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java8类库的一部分。

什么是Optional类

Optional(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。

原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。

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

Optional类提供的方法

Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

创建Optional类对象的方法

Optional.of(T t):创建一个Optional实例,t必须为非空,否则会报空指针错误;
Optional.empty():创建一个空的Optional实例;
Optional.ofNullable(T t):创建一个Optional实例,t可以为空;
Boy boy = new Boy();
//boy = null;
//创建一个Optional实例, t必须为非空,如果为空会报空指针错误
Optional<Boy> boy1 = Optional.of(boy);
System.out.println(boy1); //创建一个空的Optional实例
Optional<Object> empty = Optional.empty();
System.out.println(empty); //创建Optional类对象的方法,t可以为空
boy = null;
Optional<Boy> boy2 = Optional.ofNullable(boy);
System.out.println(boy2);

判断Optional容器是否包含对象

boolean isPresent():判断是否包含对象;
void ifPresent(Consumer<? super T> consumer):如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给他;
Boy boy = new Boy();
boy = null;
Optional<Boy> boy2 = Optional.ofNullable(boy); //该Optional类是否包含对象
System.out.println(boy2.isPresent()); //该Optional类是否为空
System.out.println(boy2.isEmpty()); //当Optional不为空时,执行consumer的实现代码
boy2.ifPresent(System.out::println);

获取Optional容器的对象

T get():如果调用对象包含值,返回该值,否则抛异常;
T orElse(T other):如果有值将其返回,否则返回指定的other对象;
T orElseGet(Supplier<? extends T> other):如果有值则将其返回,否则返回由Supplier接口实现提供的对象;
T orElseThrow(Supplier<? extends T> exceptionSupplier):如果有值则将其返回,否则抛出由Supplier接口实现提供的异常;
Boy boy = new Boy("guosiliang",20);
Optional<Boy> boy1 = Optional.ofNullable(boy); //T get():如果调用对象包含值,返回该值,如果为空,则抛出异常
System.out.println(boy1.get());
System.out.println("--------------------------"); //T orElse(T other): other算是备胎,如果有值将其返回,如果没有值则返回other
Boy boy2 = null;
Optional<Boy> boy21 = Optional.ofNullable(boy2);
Boy boy3 = boy21.orElse(boy);
System.out.println(boy3);
System.out.println("--------------------------"); //T orElseGet(Supplier<? extends T> other):如果有值将其返回,否则返回由Supplier接口实现提供的对象
Optional<Boy> boy22 = Optional.ofNullable(boy2);
Boy boy4 = boy22.orElseGet(Boy::new);
System.out.println(boy4);
System.out.println("--------------------------"); //T orElseThrow(Supplier<? extends X> exceptionSupplier):如果有值将其返回,否则抛出由Supplier接口实现提供的异常
boy2 = new Boy();
Optional<Boy> boy23 = Optional.ofNullable(boy2);
Boy boy5 = null;
try {
boy5 = boy23.orElseThrow(Exception::new);
} catch (Exception e) {
e.printStackTrace();
}

Java基础-Java8新特性的更多相关文章

  1. java基础---java8 新特性

    1. 函数式接口 函数式接口主要指只包含一个抽象方法的接口,如:java.lang.Runnable(java1.0).java.util.Comparator接口(java1.4)等. Java8提 ...

  2. 【Java】Java8新特性

    文章目录 Java8新特性 Lambda表达式的使用 语法格式一:无参,无返回值 语法格式二:Lambda 需要一个参数,但是没有返回值. 语法格式三:数据类型可以省略,因为可由编译器推断得出,称为& ...

  3. 漫漫人生路-学点Jakarta基础-Java8新特性 Stream/Lambda

    背景 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利.高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk dat ...

  4. Java基础20:Java8新特性终极指南

    更多内容请关注微信公众号[Java技术江湖] 这是一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM.SpringBoot.MySQL.分布式.中间件.集群.Linux ...

  5. 夯实Java基础系列21:Java8新特性终极指南

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  6. Java基础之java8新特性(1)Lambda

    一.接口的默认方法.static方法.default方法. 1.接口的默认方法 在Java8之前,Java中接口里面的默认方法都是public abstract 修饰的抽象方法,抽象方法并没有方法实体 ...

  7. 【Java基础】Java8 新特性

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

  8. 【Java学习笔记之二十八】深入了解Java8新特性

    前言: Java8 已经发布很久了,很多报道表明java8 是一次重大的版本升级.在Java Code Geeks上已经有很多介绍Java 8新特性的文章,例如Playing with Java 8 ...

  9. Java学习之==>Java8 新特性详解

    一.简介 Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级.Java 8是 Java 自 Java 5(发布于2004年)之后的最重要的版本.这个版本包含语言.编译器.库. ...

随机推荐

  1. Selenium自动化实现web自动化-1

    框架搭建 基于maven+jdk8+junit5+seleium 构建 <dependencies> <dependency> <groupId>org.junit ...

  2. C# Dapper基本三层架构使用 (二、Model)

    我们将数据存放在数据库中,数据表的结构,我们通常会用一个类来抽象,表的属性就是类的属性,我们通常将表的一行存储在一个类中. 在Java中,通常将其称为实体类Entity,在C#中,通常将其称为Mode ...

  3. Java中HashCode()和equals()的作用

    引言 我们知道Java中的集合(Collection)大致可以分为两类,一类是List,再有一类是Set. 前者集合内的元素是有序的,元素可以重复:后者元素无序,但元素不可重复. 这里就引出一个问题: ...

  4. HCNP Routing&Switching之IS-IS路由聚合和认证

    前文我们了解了IS-IS路由渗透和开销相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15302382.html:今天我们来聊一聊IS-IS路由聚合和认 ...

  5. input竖直的输入框,文字从上到下排列

    有的时候可能会有这样的需求,一个竖直的输入框,输入信息,文字也是从上到下排列: (但是在移动端或用轮播swiper时不起作用,可以用textarea代替input) <!DOCTYPE html ...

  6. dedecms内页调用内容当中第一张图片

    在includes/extend.func.php末尾添加方法: //取第一张图地址 function firstimg($str_pic) { $str_sub=substr($str_pic,0, ...

  7. Linux文件(夹)属性与权限

    文件属性与权限,文件权限设置 参考资料:鸟哥的Linux私房菜 用户与用户组 Linux系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这 ...

  8. django setting.py配置文件解读-02

    定义项目目录常量 import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR ...

  9. 1.3redis小结--配置php reids拓展

    1.执行php文件 输出phpinfo();  <?php phpinfo(); 2.根据PHPinfo的信息确定需要下载的 php_redis.dll , php_igbinary.dll 版 ...

  10. 鸿蒙内核源码分析(用栈方式篇) | 程序运行场地谁提供的 | 百篇博客分析OpenHarmony源码 | v20.04

    百篇博客系列篇.本篇为: v20.xx 鸿蒙内核源码分析(用栈方式篇) | 程序运行场地谁提供的 | 51.c.h .o 精读内核源码就绕不过汇编语言,鸿蒙内核有6个汇编文件,读不懂它们就真的很难理解 ...