前言:最近摸索了JDK8的一些新特性,特此总结一下。Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。
JDK8有很多新特性,我在此就讨论以下特性:
一、接口的默认方法
二、Lambda 表达式
三、方法引用
三、Predicate接口(断言,判断)
四、Function接口(转化,传递)
五、Supplier接口(创造,生产,无中生有)
六、Consumer接口(消费者,消费,吞噬)
七、Stream 接口(容器)
八、Optional 类
使用 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
}
}
函数的定义:给定一个数集A,假设其中的元素为x。现对A中的元素x施加对应法则f,记作f(x),得到另一数集B。假设B中的元素为y。则y与x之间的等量关系可以用y=f(x)表示。我们把这个关系式就叫函数关系式,简称函数A -> B。
(1)举几个简单例子:
() -> 5 // 返回5
(int a) -> {return a * a;} //计算并返回a的平方
(a) -> {return a * a;} //省略参数类型int
(a) -> {a * a} //省略返回关键字return
a -> a * a //省略()和{}
(2)再来看一个排序的例子,要求按照字符串的长度由短到长排序:
不用Lambda 表达式:
List list = Arrays.asList("Hello", "World", "I", "am", "Java");
list.sort(new Comparator() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
System.out.println(list); // 结果:[I, am, Java, Hello, World]
再来看用Lambda 表达式:
List 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 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
(3)“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法,因为默认方法不算抽象方法,所以你也可以给你的函数式接口添加默认方法。我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解(这个注解不加也可以,加上只是为了让编译器检查),编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。
这样,我们就可以解决上面那个疑惑了,查看Comparator接口的源代码,我们可以看到这个接口是加了@FunctionalInterface注解的,故就是函数式接口,但是他有两个抽象方法,compare()和equals(),其实equals()方法是Object提供的。
实际上下面这两部分代码等价于上面的那些,只不过上面的是用的匿名类。
List list = Arrays.asList("Hello", "World", "I", "am", "Java");
Comparator c = (s1, s2) -> s1.length() - s2.length();
list.sort(c);
System.out.println(list); // 结果:[I, am, Java, Hello, World]
//和上面的等价
List list = Arrays.asList("Hello", "World", "I", "am", "Java");
Comparator c = new Comparator() {
@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 允许使用 :: 关键字来传递方法(静态方法和非静态方法)。
下面一一举例示例:
(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 t1 = Integer::parseInt; //接收一个String类型,返回Integer类型
System.out.println(t1.run("100")); //将字符串"100"转化为整数100,结果:100
test t2 = Integer::toBinaryString; //接收一个Integer类型,返回String类型
System.out.println(t2.run(100)); //将整数100转化为二进制数,结果:1100100
}
}
@FunctionalInterface
interface test {
//此方法可以代表任何一个参数(T类型),一个返回值(R类型)的方法
public R run(T t);
}
(5)接口中带泛型的时候特殊例子, 可以使用 类名::非静态方法 的形式引用方法
public class CSDN {
public static void main(String[] args) {
//注意:如果这里泛型类型不是apple 那么就不能引用apple中的方法
//可以引用apple 类中任意方法 只要满足一点:该方法没有参数
//将来run方法中就会调用apple 类型对象的此处引用的方法
test t = apple::getColor;
t.run(new apple());
}
public void CSDN_run() {
System.out.println("CSDN_run()...");
}
}
@FunctionalInterface
interface test {
public void run(T t);
}
class apple {
public void getColor() {
System.out.println("red");
}
}
看官方API,主要有and():类比逻辑与,isEqual():判断是否相等,negate():类比逻辑非,or():类比逻辑或,test():根据给定的参数计算Predicate,这几个方法
import java.util.function.Predicate;
public class CSDN {
public static void main(String[] args) {
Predicate p1 = str -> str.length() == 9; // 字符串长度是否等于9
Predicate p2 = str -> str.startsWith("j"); // 是否以j开头
Predicate p3 = p1.and(p2); // 字符串是否长度为9并且以j开头
Predicate p4 = p1.or(p2); // 字符串是否长度为9或者以j开头
Predicate p5 = p1.negate(); // 字符串长度是否不等于9
Predicate 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
}
}
看API,Function接口有andThen(),apply(),compose(),identity()这几个方法,其中只有apply一个抽象方法,所以Function接口是函数式接口。
(1)先看R apply(T t)方法,Function
假设有一个需求:给定一个名字“CSDN”,创建名字为“CSDN”的老师:
先创建Teacher类并添加构造器,get,set以及toString方法,根据Function
import java.util.function.Function;
public class CSDN {
public static void main(String[] args) {
Function 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 f1 = Teacher::new;//匹配Teacher类的有参构造函数
Function 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 f1 = Teacher::new; //匹配Teacher类的有参构造函数
Function 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 the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static Function identity() {
return t -> t;
}
大概意思就是输入什么就返回什么,所以其中两个泛型要一样。看示例:
import java.util.function.Function;
public class CSDN {
public static void main(String[] args) {
Function identity = Function.identity();//
String str = identity.apply("ECJTU");
System.out.println(str); // ECJTU
}
}
看API,只有一个抽象方法T get(),没有参数,只返回一个T类型的结果。和Function接口用法相似。
注意这里的Supplier
import java.util.function.Supplier;
public class CSDN {
public static void main(String[] args) {
Supplier 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接口有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 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 c = System.out::println;
c.andThen(str -> System.out.println("after:" + str)).accept("hello world!"); //hello world!
//after:hello world!
}
}
下面来总结一下Function,Supplier,Consumer这三个接口:
另外需要注意的接口:,其用法和上面介绍的接口使用方式类同:
看到这些方法,应该就知道它的功能很强大了,常见的操作可以归类如下。
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
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 s1 = Stream.of(1, 8, 100, 77, 89, 4, 10);
// 2.使用数组构建
Integer[] integers = new Integer[] { 1, 8, 100, 77, 89, 4, 10 };
Stream s2 = Stream.of(integers);
// 或者使用Arrays.stream
Stream s3 = Arrays.stream(integers);
// 3.使用集合构建(不支持map)
List list = Arrays.asList(integers);
Stream s4 = list.stream();
}
}
对于基本数值型,目前有三种对应的包装类型 Stream:IntStream、LongStream、DoubleStream。当然我们也可以用 Stream
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转为其他类型
Stream s1 = Stream.of(1, 8, 100, 77, 89, 4, 10);
Integer[] integers = s1.toArray(Integer[]::new);
Stream s1 = Stream.of("c", "c++", "java");
String str = s1.collect(Collectors.joining()).toString();
System.out.println(str);
Stream stream = Stream.of("hello","world","tom");
List list1 = stream.collect(Collectors.toList());
List list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set3 = stream.collect(Collectors.toSet());
Set set4 = stream.collect(Collectors.toCollection(HashSet::new));
(3)forEach(),遍历,比之前的for循环代码简洁许多。
import java.util.stream.Stream;
public class CSDN {
public static void main(String[] args) {
Stream 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 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 s1 = Stream.of("c", "c++", "Java", "hadoop", "php");
long count = s1.count();
System.out.println(count); // 5
(6)limit(maxSize),截取长度不超过maxSize的Stream,返回类型为Stream。
Stream 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 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 s1 = Stream.of("c", "c++", "Java", "hadoop", "php");
// 原始方法
List list = new ArrayList();
Iterator iterator = s1.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
for (int i = 0; i < s.length(); i++) {
list.add(s.charAt(i) + "");
}
}
Stream 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 |
import java.util.stream.Stream;
public class CSDN {
public static void main(String[] args) {
Stream s1 = Stream.of(1, 2, 4, 9, 10);
Integer sum = s1.reduce(0, Integer::sum); // 第一个参数是初值
System.out.println(sum); // 26
}
}
上面是给了初值0的,如果没有给,那么返回Optional类型:
Optional |
reduce(BinaryOperator |
import java.util.Optional;
import java.util.stream.Stream;
public class CSDN {
public static void main(String[] args) {
Stream s1 = Stream.of(1, 2, 4, 9, 10);
Optional 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 stream = StreamSupport.stream(Arrays.asList(nums).spliterator(), false);
Map 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
*/
看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 op1 = Optional.of(i1); //抛异常
Optional 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 op1 = Optional.of(i1); //抛异常
Optional op2 = Optional.ofNullable(i1); //不抛异常
Optional 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 op2 = Optional.ofNullable(i1);
Optional 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 op2 = Optional.ofNullable(i1);
Optional op3 = Optional.of(i2);
System.out.println(op2.isPresent()); // false
System.out.println(op3.isPresent());// true
}
}
(5)void ifPresent(Consumer
import java.util.Optional;
public class CSDN {
public static void main(String[] args) {
Integer i1 = null;
Integer i2 = 10;
Optional op2 = Optional.ofNullable(i1);
Optional 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 op1 = Optional.ofNullable(i1);
Optional 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 Optional 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 op2 = Optional.ofNullable(i1);
Optional op3 = Optional.of(i2);
Function function = Integer::toBinaryString; // 转为二进制
Optional map = op3.map(function);
System.out.println(map.get()); // 1010
//或者简写为这一句
System.out.println(op3.map(Integer::toBinaryString).get());
}
}