前言:最近摸索了JDK8的一些新特性,特此总结一下。Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。
JDK8有很多新特性,我在此就讨论以下特性:
一、接口的默认方法
使用 default关键字就可以给接口添加一个非抽象的方法实现。
interface cal {
default public int calculate(int a, int b) { // 使用 default关键字,代表非抽象方法
return a + b;
}
}
public class CSDN {
public static void main(String[] args) {
cal c = new cal() {}; // 这里没有重写calculate方法,也可以重写
System.out.println(c.calculate(3, 5)); // 3 + 5 = 8
cal c1 = new cal() {
@Override
public int calculate(int a, int b) { // 重写cal接口的calculate方法
return a - b;
}
};
System.out.println(c1.calculate(3, 2)); // 3 - 2 = 1
}
}
另外,接口还可以存在静态方法,可以使用 接口名.静态方法名 的形式直接调用:
interface cal {
public static int calculate(int a, int b) {// static不能与default同时使用
return a + b;
}
}
public class CSDN {
public static void main(String[] args) {
System.out.println(cal.calculate(3, 5)); // 3 + 5 = 8
}
}
二、Lambda 表达式
- Lambda 表达式,是Java 8 最重要新特性,是为函数式编程提供基础。
- 函数式编程本质上是想用表达式去表达一切行为,动词抽象世界只是这个思想的表达出来的现象。
- 函数式编程的特点:函数的参数也是函数,函数返回的也是函数。
- Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
- 使用 Lambda 表达式可以使代码变的更加简洁紧凑。
函数的定义:给定一个数集A,假设其中的元素为x。现对A中的元素x施加对应法则f,记作f(x),得到另一数集B。假设B中的元素为y。则y与x之间的等量关系可以用y=f(x)表示。我们把这个关系式就叫函数关系式,简称函数A -> B。
- Lambda 表达式与之类似,语法为()->{},()里面为参数(可选),{}里可以返回语句,也可以是表达式等。
- 如果{}里面就一句代码,{}可省去。
- 同理,如果只有一个参数,()也可省去。
- ()中的参数类型可省去,编译器自动识别类型。
- 如果{}只有一个表达式返回值则可以省略return关键字,编译器会自动返回值。
(1)举几个简单例子:
() -> 5 // 返回5
(int a) -> {return a * a;} //计算并返回a的平方
(a) -> {return a * a;} //省略参数类型int
(a) -> {a * a} //省略返回关键字return
a -> a * a //省略()和{}
(2)再来看一个排序的例子,要求按照字符串的长度由短到长排序:
不用Lambda 表达式:
List<String> list = Arrays.asList("Hello", "World", "I", "am", "Java");
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
System.out.println(list); // 结果:[I, am, Java, Hello, World]
再来看用Lambda 表达式:
List<String> list = Arrays.asList("Hello", "World", "I", "am", "Java");
list.sort((String s1, String s2) -> {
return s1.length() - s2.length();
});
System.out.println(list); // 结果:[I, am, Java, Hello, World]
化简一下,能省的都省了,看,是不是很简洁,比原始的代码简单了很多,这就是典型的函数作为参数。
List<String> list = Arrays.asList("Hello", "World", "I", "am", "Java");
list.sort((s1, s2) -> s1.length() - s2.length());
System.out.println(list); // 结果:[I, am, Java, Hello, World]
提问:上面的那个例子为什么可以用(s1, s2) -> { ...}代替new Comparator<String>() {...}呢,不知道你们有没有疑惑,反正我学的时候疑惑了很久,为了解决这个问题,下面介绍一下另一个概念------函数式接口。
(3)“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法,因为默认方法不算抽象方法,所以你也可以给你的函数式接口添加默认方法。我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解(这个注解不加也可以,加上只是为了让编译器检查),编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。
这样,我们就可以解决上面那个疑惑了,查看Comparator接口的源代码,我们可以看到这个接口是加了@FunctionalInterface注解的,故就是函数式接口,但是他有两个抽象方法,compare()和equals(),其实equals()方法是Object提供的。
实际上下面这两部分代码等价于上面的那些,只不过上面的是用的匿名类。
List<String> list = Arrays.asList("Hello", "World", "I", "am", "Java");
Comparator<String> c = (s1, s2) -> s1.length() - s2.length();
list.sort(c);
System.out.println(list); // 结果:[I, am, Java, Hello, World]
//和上面的等价
List<String> list = Arrays.asList("Hello", "World", "I", "am", "Java");
Comparator<String> c = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
};
list.sort(c);
System.out.println(list); // 结果:[I, am, Java, Hello, World]
接下来,我们自己写一个函数式接口来测试一下:
public class CSDN {
public static void main(String[] args) {
test t = () -> System.out.println("调用了run()方法"); // 这里自动匹配到test接口的run方法,可以理解为实现了run()方法
t.run(); // 调用一下run()方法
}
}
@FunctionalInterface
interface test {
public void run();
}
三、方法引用
Java 8 允许使用 :: 关键字来传递方法(静态方法和非静态方法)。
- 静态方法引用 ContainingClass::staticMethodName
- 特定对象的方法引用 containingObject::instanceMethodName
- 构造器引用 ClassName::new
下面一一举例示例:
(1)静态方法引用 ContainingClass::staticMethodName
能够引用的前提:test接口中有且只有一个方法,并且参数类型和个数要和被引用的那个静态方法(这里是CSDN_run()方法)一致,返回值类型也要一致。
引用CSDN的CSDN_run()静态方法:
public class CSDN {
public static void main(String[] args) {
test t = CSDN::CSDN_run; //可以理解为CSDN_run()就是test接口run()方法的实现
t.run();
}
public static void CSDN_run() {
System.out.println("CSDN_run()....");
}
}
@FunctionalInterface
interface test {
public void run();
}
引用Integer的toString静态方法,先看源代码:
// JDK源码,Integer类的toString()方法
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
类型要一致,所以测试代码如下:
public class CSDN {
public static void main(String[] args) {
test t = Integer::toString;
System.out.println(t.run(100));
}
}
@FunctionalInterface
interface test {
public String run(int i);
}
(2)特定对象的方法引用 containingObject::instanceMethodName
public class CSDN {
public static void main(String[] args) {
CSDN csdn = new CSDN();
test t = csdn::CSDN_run;
System.out.println(t.run("张三"));
}
public String CSDN_run(String name) {
return name + "CSDN_run()...";
}
}
@FunctionalInterface
interface test {
public String run(String name);
}
(3)构造器引用 ClassName::new
public class CSDN {
public static void main(String[] args) {
test t = People::new; // 可以理解为调用run()方法即调用People的构造器
People p = t.run("张三");
System.out.println(p); // People [name=张三]
test1 t1 = People::new;
People p1 = t1.run();
p1.setName("李四");
System.out.println(p1); // People [name=李四]
}
}
@FunctionalInterface
interface test {
public People run(String name); // 这里的参数类型和返回值类型和People的有参构造器一致,会自动匹配有参构造器
}
@FunctionalInterface
interface test1 {
public People run(); // 这里的参数类型和返回值类型和People的无参构造器一致,会自动匹配无参构造器
}
class People {
private String name;
public People() {
}
public People(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "People [name=" + name + "]";
}
}
(4)lambda表达式与泛型接口的联用
public class CSDN {
public static void main(String[] args) {
test<String, Integer> t1 = Integer::parseInt; //接收一个String类型,返回Integer类型
System.out.println(t1.run("100")); //将字符串"100"转化为整数100,结果:100
test<Integer, String> t2 = Integer::toBinaryString; //接收一个Integer类型,返回String类型
System.out.println(t2.run(100)); //将整数100转化为二进制数,结果:1100100
}
}
@FunctionalInterface
interface test<T, R> {
//此方法可以代表任何一个参数(T类型),一个返回值(R类型)的方法
public R run(T t);
}
(5)接口中带泛型的时候特殊例子, 可以使用 类名::非静态方法 的形式引用方法
public class CSDN {
public static void main(String[] args) {
//注意:如果这里泛型类型不是apple 那么就不能引用apple中的方法
//可以引用apple 类中任意方法 只要满足一点:该方法没有参数
//将来run方法中就会调用apple 类型对象的此处引用的方法
test<apple> t = apple::getColor;
t.run(new apple());
}
public void CSDN_run() {
System.out.println("CSDN_run()...");
}
}
@FunctionalInterface
interface test<T> {
public void run(T t);
}
class apple {
public void getColor() {
System.out.println("red");
}
}
三、Predicate接口(断言,判断)
看官方API,主要有and():类比逻辑与,isEqual():判断是否相等,negate():类比逻辑非,or():类比逻辑或,test():根据给定的参数计算Predicate,这几个方法
import java.util.function.Predicate;
public class CSDN {
public static void main(String[] args) {
Predicate<String> p1 = str -> str.length() == 9; // 字符串长度是否等于9
Predicate<String> p2 = str -> str.startsWith("j"); // 是否以j开头
Predicate<String> p3 = p1.and(p2); // 字符串是否长度为9并且以j开头
Predicate<String> p4 = p1.or(p2); // 字符串是否长度为9或者以j开头
Predicate<String> p5 = p1.negate(); // 字符串长度是否不等于9
Predicate<String> p6 = Predicate.isEqual("Java"); // 字符串是否等于Java
System.out.println(p1.test("aaa")); // false
System.out.println(p2.test("java")); // true
System.out.println(p3.test("jjjaaabbb"));// true
System.out.println(p4.test("ja"));// true
System.out.println(p5.test("123456789"));// false
System.out.println(p6.test("java"));// false
}
}
四、Function接口(转化,传递)
看API,Function接口有andThen(),apply(),compose(),identity()这几个方法,其中只有apply一个抽象方法,所以Function接口是函数式接口。
(1)先看R apply(T t)方法,Function<T, R>可以理解为:传递一个T类型的参数进去,返回一个R类型的结果,而apply(T t)即给Function传递参数T t,然后返回R类型的结果,apply()是抽象方法,所以我们要实现它。
假设有一个需求:给定一个名字“CSDN”,创建名字为“CSDN”的老师:
先创建Teacher类并添加构造器,get,set以及toString方法,根据Function<T, R>的意思,我们很容易构建出Function<String, Teacher>即为我们需要的,传进去一个字符串,返回一个Teacher类型的对象,Function<String, Teacher> f1 = Teacher::new即实现apply方法,然后调用它即可:
import java.util.function.Function;
public class CSDN {
public static void main(String[] args) {
Function<String, Teacher> f1 = Teacher::new;//匹配Teacher类的有参构造函数
Teacher t1 = f1.apply("CSDN");
System.out.println(t1); // Teacher [name=CSDN]
}
}
class Teacher {
private String name;
public Teacher() {
}
public Teacher(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher [name=" + name + "]";
}
}
(2)compose(),表示在某个方法之前执行,返回一个组合函数,该函数首先将before函数应用于其输入,然后将此函数应用于结果。
假设刚才那个需求变了,传一个string进去,创建一个名字为ECJTU——string(即加上前缀)的Teacher对象:
可以分为两步来解决这个问题,第一步,将传进来的string处理一下(加上前缀ECJTU——),第二步,创建Teacher对象。
其中f1.compose(f2).apply("CSDN")可以理解为:在f1调用apply()前先执行f2,然后f2的执行结果作为参数传递给apply()。
import java.util.function.Function;
public class CSDN {
public static void main(String[] args) {
Function<String, Teacher> f1 = Teacher::new;//匹配Teacher类的有参构造函数
Function<String, String> f2 = str -> "ECJTU——"+str;
Teacher t1 = f1.compose(f2).apply("CSDN");
System.out.println(t1); // Teacher [name=ECJTU——CSDN]
}
}
class Teacher {
private String name;
public Teacher() {
}
public Teacher(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher [name=" + name + "]";
}
}
(3)andThen() ,和compose相反,在某个方法之后执行。
我们来求一下上一步所创建的Teacher对象的名字的长度:
import java.util.function.Function;
public class CSDN {
public static void main(String[] args) {
Function<String, Teacher> f1 = Teacher::new; //匹配Teacher类的有参构造函数
Function<Teacher, Integer> f2 = teacher->teacher.getName().length();
Integer t1 = f1.andThen(f2).apply("CSDN");
System.out.println(t1); // 4
}
}
class Teacher {
private String name;
public Teacher() {
}
public Teacher(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher [name=" + name + "]";
}
}
(4)identity(),先看源码:
/**
* Returns a function that always returns its input argument.
*
* @param <T> the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static <T> Function<T, T> identity() {
return t -> t;
}
大概意思就是输入什么就返回什么,所以其中两个泛型要一样。看示例:
import java.util.function.Function;
public class CSDN {
public static void main(String[] args) {
Function<String, String> identity = Function.identity();//
String str = identity.apply("ECJTU");
System.out.println(str); // ECJTU
}
}
五、Supplier接口(创造,生产,无中生有)
看API,只有一个抽象方法T get(),没有参数,只返回一个T类型的结果。和Function接口用法相似。
注意这里的Supplier<Teacher> s = Teacher::new;会自动匹配到Teacher无参构造器,因为没有传参数,而Function是匹配有参构造器,然后每次调用一次get()就会执行一次构造函数,相当于new新对象。
import java.util.function.Supplier;
public class CSDN {
public static void main(String[] args) {
Supplier<Teacher> s = Teacher::new; // 匹配Teacher类的无参构造函数
Teacher t1 = s.get(); // 调用一次get()就会执行一次构造函数
t1.setName("张三");
Teacher t2 = s.get();
t2.setName("李四");
System.out.println(t1); // Teacher [name=张三]
System.out.println(t2); // Teacher [name=李四]
}
}
class Teacher {
private String name;
public Teacher() {
}
public Teacher(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher [name=" + name + "]";
}
}
六、Consumer接口(消费者,消费,吞噬)
Consumer接口有void accept(T t)方法和andThen()方法,也是函数式接口。
(1)void accept(T t),接收一个T类型的参数t,然后在里面执行各种操作(比如输出),没有返回值。
import java.util.function.Consumer;
public class CSDN {
public static void main(String[] args) {
/*
* System.out::println是通过对象::方法名 引用方法
* System.out作为PrintStream打印流类的的对象
*/
Consumer<String> c = System.out::println;
c.accept("hello world!"); // hello world!
}
}
(2)andThen(),和Function中的andThen()是一样的意思,不再解释。
import java.util.function.Consumer;
public class CSDN {
public static void main(String[] args) {
Consumer<String> c = System.out::println;
c.andThen(str -> System.out.println("after:" + str)).accept("hello world!"); //hello world!
//after:hello world!
}
}
下面来总结一下Function,Supplier,Consumer这三个接口:
- Function<T, R> 接口 R apply(T t); 有参数有返回值
- Supplier<T> 接口 T get(); 没参数有返回值
- Consumer<T> 接口 void accept(T t); 有参数没返回值
另外需要注意的接口:,其用法和上面介绍的接口使用方式类同:
- BiConsumer<T, U>接口 void accept(T t, U u): 将俩个参数传入,没有返回值
- BinaryOperator<T>接口 T apply(T t1, T t2) :将两个T作为输入,返回一个T作为输出,reduce 归约
- BiFunction<T, U, R>接口 R apply(T t, U u) : 将一个T和一个U输入,返回一个R作为输出
- BinaryOperator接口继承了BiFunction接口,public interface BinaryOperator<T> extends BiFunction<T,T,T>
七、Stream 接口(容器)
- java.util.Stream 表示能应用在一组元素上依次执行的操作序列。
- Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来(链式编程)。
- Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。
- Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。
- Stream API 借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作。
看到这些方法,应该就知道它的功能很强大了,常见的操作可以归类如下。
- Intermediate:中间操作
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
- Terminal: 最终操作
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
- Short-circuiting: 短路操作,中途停止
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
(1)Stream对象的构建(使用Stream.of()方法)
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class CSDN {
public static void main(String[] args) {
// 1. 通过值构建
Stream<Integer> s1 = Stream.of(1, 8, 100, 77, 89, 4, 10);
// 2.使用数组构建
Integer[] integers = new Integer[] { 1, 8, 100, 77, 89, 4, 10 };
Stream<Integer> s2 = Stream.of(integers);
// 或者使用Arrays.stream
Stream<Integer> s3 = Arrays.stream(integers);
// 3.使用集合构建(不支持map)
List<Integer> list = Arrays.asList(integers);
Stream<Integer> s4 = list.stream();
}
}
对于基本数值型,目前有三种对应的包装类型 Stream:IntStream、LongStream、DoubleStream。当然我们也可以用 Stream<Integer>、Stream<Long> 、Stream<Double>,但是自动拆箱装箱会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。示例IntStream:
import java.util.stream.IntStream;
public class CSDN {
public static void main(String[] args) {
IntStream is = IntStream.of(new int[] {6, 8, 8});
is.forEach(System.out::println); // 6 ,8, 8
is = IntStream.range(1, 5);
is.forEach(System.out::println);// 1, 2, 3, 4
is = IntStream.rangeClosed(1, 5);
is.forEach(System.out::println); // 1, 2, 3, 4, 5
}
}
(2)Stream转为其他类型
- 转为数组(<A> A[] toArray(IntFunction<A[]> generator))(IntFunction为函数式接口,下面的代码即引用Integer的构造函数):
Stream<Integer> s1 = Stream.of(1, 8, 100, 77, 89, 4, 10);
Integer[] integers = s1.toArray(Integer[]::new);
- 转为String :
Stream<String> s1 = Stream.of("c", "c++", "java");
String str = s1.collect(Collectors.joining()).toString();
System.out.println(str);
- 转换为Collection
Stream<String> stream = Stream.of("hello","world","tom");
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set<String> set3 = stream.collect(Collectors.toSet());
Set<String> set4 = stream.collect(Collectors.toCollection(HashSet::new));
(3)forEach(),遍历,比之前的for循环代码简洁许多。
- void forEach(Consumer<? super T> action),需要传一个类型为Consumer的参数给forEach(),这里我们用str -> System.out.print(str + " ")作为参数传进去,即输出。
import java.util.stream.Stream;
public class CSDN {
public static void main(String[] args) {
Stream<String> s1 = Stream.of("c", "c++", "Java", "hadoop", "php");
s1.forEach(str -> System.out.print(str + " "));// c c++ Java hadoop php
s1.forEach(System.out::println);// 简写
}
}
特别注意 : 一个 Stream 只可以使用一次,上面代码用了两次s1.forEach(),则抛异常:java.lang.IllegalStateException: stream has already been operated upon or closed。
(4)filter(),过滤不符合要求的元素,返回类型还是Stream。
Stream<String> s1 = Stream.of("c", "c++", "Java", "hadoop", "php");
// 取以c开头的
s1.filter(str -> str.startsWith("c")).forEach(System.out::println);// c c++
(5)count(),返回元素个数,返回类型为long类型。
Stream<String> s1 = Stream.of("c", "c++", "Java", "hadoop", "php");
long count = s1.count();
System.out.println(count); // 5
(6)limit(maxSize),截取长度不超过maxSize的Stream,返回类型为Stream。
Stream<String> s1 = Stream.of("c", "c++", "Java", "hadoop", "php");
s1.limit(4).forEach(System.out::println);// c c++ Java hadoop
(7)map(Function<? super T,? extends R> mapper),对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素,转换一般都是一对一。如下演示将其中元素变为大写:
Stream<String> s1 = Stream.of("c", "c++", "Java", "hadoop", "php");
s1.map(String::toUpperCase).forEach(System.out::println); //C C++ JAVA HADOOP PHP
其他map(mapToDouble,mapToInt,mapToLong)用法类似。
(8)flatMap(Function<? super T,? extends Stream<? extends R>> mapper),flatMap的参数为Function,Function的参数为Stream类型,可用于一对一,一对多,多对多转换。
示例:将s1平铺成一个一个字符,分别用原始方法和flatMap方法对比代码复杂度:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
public class CSDN {
public static void main(String[] args) {
Stream<String> s1 = Stream.of("c", "c++", "Java", "hadoop", "php");
// 原始方法
List<String> list = new ArrayList<String>();
Iterator<String> iterator = s1.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
for (int i = 0; i < s.length(); i++) {
list.add(s.charAt(i) + "");
}
}
Stream<String> new_s1 = list.stream();
new_s1.forEach(System.out::println);
// flatMap方法
s1.flatMap(s -> Arrays.stream(s.split(""))).forEach(System.out::println);
}
}
其他flatMap(flatMapToDouble,flatMapToInt,flatMapToLong)用法类似。
(9)reduce(),归约。这是一个最终操作,允许通过指定的函数来将stream中的多个元素规约合并为一个元素。
把stream中的元素累加:
T | reduce(T identity, BinaryOperator<T> accumulator) |
import java.util.stream.Stream;
public class CSDN {
public static void main(String[] args) {
Stream<Integer> s1 = Stream.of(1, 2, 4, 9, 10);
Integer sum = s1.reduce(0, Integer::sum); // 第一个参数是初值
System.out.println(sum); // 26
}
}
上面是给了初值0的,如果没有给,那么返回Optional类型:
Optional<T> | reduce(BinaryOperator<T> accumulator) |
import java.util.Optional;
import java.util.stream.Stream;
public class CSDN {
public static void main(String[] args) {
Stream<Integer> s1 = Stream.of(1, 2, 4, 9, 10);
Optional<Integer> sum = s1.reduce(Integer::sum);
System.out.println(sum.get()); // 26
}
}
还有其他的方法就不一一列举了,用法都很相似,像sorted(),min(),max(),peek()等。
示例:(统计一个数组中数字出现的个数)
Integer[] nums = { 1, 2, 0, 7, 1, 2, 1, 1, 0, 10, 13, 14, 19, 10, 2, 0 };
Stream<Integer> stream = StreamSupport.stream(Arrays.asList(nums).spliterator(), false);
Map<Integer, Long> map = stream.collect(Collectors.groupingBy(i -> i, Collectors.counting()));
map.forEach((k, v) -> System.out.println(k + ":" + v));
/*
0:3
1:4
2:3
19:1
7:1
10:2
13:1
14:1
*/
八、Optional 类
- Optional 类,把对于null处理的行为转化成函数式编程的语法结构,Optional 不是接口而是一个类,这是个用来防止NullPointerException异常的辅助类型。
- Optional 被定义为一个简单的容器,其值可能是null或者不是null。在Java8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。
- 这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
看API,有这些方法:
(1)of(),静态方法,通过Optional.of(value)来调用。of方法为非null的值创建一个Optional对象,of方法是通过工厂方法创建Optional类。需要注意,创建对象时传入的参数不能为null,如果传入参数为null,则抛出NullPointerException 。
import java.util.Optional;
public class CSDN {
public static void main(String[] args) {
Integer i1 = null;
Integer i2 = 10;
Optional<Integer> op1 = Optional.of(i1); //抛异常
Optional<Integer> op2 = Optional.of(i2);
}
}
(2)ofNullable(),静态方法,通过Optional.ofNullable(value)来调用。ofNullable方法为指定的值创建一个Optional对象,如果指定的值为null,则返回一个空的Optional对象。ofNullable与of方法相似,唯一的区别是可以接受参数为null的情况。
import java.util.Optional;
public class CSDN {
public static void main(String[] args) {
Integer i1 = null;
Integer i2 = 10;
Optional<Integer> op1 = Optional.of(i1); //抛异常
Optional<Integer> op2 = Optional.ofNullable(i1); //不抛异常
Optional<Integer> op3 = Optional.of(i2);
}
}
(3)get(),如果Optional有值则将其返回,否则抛出NoSuchElementException。
import java.util.Optional;
public class CSDN {
public static void main(String[] args) {
Integer i1 = null;
Integer i2 = 10;
Optional<Integer> op2 = Optional.ofNullable(i1);
Optional<Integer> op3 = Optional.of(i2);
System.out.println(op3.get()); // 10
System.out.println(op2.get());// 抛异常
}
}
(4)boolean ifPresent(),如果有值返回true,否则返回false。
import java.util.Optional;
public class CSDN {
public static void main(String[] args) {
Integer i1 = null;
Integer i2 = 10;
Optional<Integer> op2 = Optional.ofNullable(i1);
Optional<Integer> op3 = Optional.of(i2);
System.out.println(op2.isPresent()); // false
System.out.println(op3.isPresent());// true
}
}
(5)void ifPresent(Consumer<T> c),如果有值就进行括号里面的动作,否则什么也不做。
import java.util.Optional;
public class CSDN {
public static void main(String[] args) {
Integer i1 = null;
Integer i2 = 10;
Optional<Integer> op2 = Optional.ofNullable(i1);
Optional<Integer> op3 = Optional.of(i2);
op2.ifPresent(System.out::println);// 什么也不做
op3.ifPresent(System.out::println);// 输出op3的值:10
}
}
(6)orElse(otherValue),如果Optional对象存在值则返回这个值,否则返回括号中指定的值。
示例计算两数之和:
import java.util.Optional;
public class CSDN {
public static void main(String[] args) {
Integer i1 = null;
Integer i2 = 10;
Optional<Integer> op1 = Optional.ofNullable(i1);
Optional<Integer> op2 = Optional.ofNullable(i2);
// 计算i1+i2
int a = op1.orElse(0);
int b = op2.orElse(0);
System.out.println(a + b); // 10
}
}
(7)map(),如果有值,则对其执行调用mapper函数得到返回值,没值不做操作 。
public<U> Optional<U> map(Function<? super T, ? extends U> mapper),参数为Function。
import java.util.Optional;
import java.util.function.Function;
public class CSDN {
public static void main(String[] args) {
Integer i1 = null;
Integer i2 = 10;
Optional<Integer> op2 = Optional.ofNullable(i1);
Optional<Integer> op3 = Optional.of(i2);
Function<Integer, String> function = Integer::toBinaryString; // 转为二进制
Optional<String> map = op3.map(function);
System.out.println(map.get()); // 1010
//或者简写为这一句
System.out.println(op3.map(Integer::toBinaryString).get());
}
}