JDK1.8中的特性:
- 接口方法
- lambda表达式
- 方法引用
- Stream API
接口增强
JDK1.8中引入了一种新的机制:接口可以支持在声明方法的同时,提供实现。
主要通过两种方式可以完成这种操作:
- 默认方法
- 静态方法
实现接口的类,可以继承这些方法并直接使用,就不再需要强制在实现类中的去实现(重写)接口中的方法了。这种机制可以使我们更平滑地进行接口的优化和演进。
方法调用的判断规则:
- 类中声明的方法优先级最高。
类或父类中,声明的方法要高于任何默认方法的优先级 - 如果无法依据第一条进行判断,那么子接口的优先级更高
例如,如果 B 接口继承了 A接口 ,那么 B 就比 A 更加具体,优先级更高
所以,在上述例子中,B是子接口,优先级别更高,调用test方法后输出:defaut method test in B. - 最后,如果还是无法判断,那么继承了多个接口的类,必须通过实现(重写)方法来确定方法的
调用
class A{
public void test(){
System.out.println("Default Method test in A");
}
}
interface B {
default void test(){
System.out.println("default method test in B");
}
}
class C extends A implements B{
public static void main(String[] args){
C c = new C();
c.test();
}
}
//运行结果:default method test in A
//因为A是类,B是接口,A里面的test方法优先级更高
interface A{
default void test(){
System.out.println("Default Method test in A");
}
}
interface B {
default void test(){
System.out.println("default method test in B")
}
}
//如下代码编译会报错。
/*class C implements A,B{
public static void main(String[] args){
C c = new C();
c.test();
}
}*/
//如果C需要同时实现A和B接口,那么必须显示覆盖
class C implements A,B{
public void test(){
//如果在C中需要显示访问A/B的默认方法,可以使用接口名.super.方法名();
A.super.test();
B.super.test();
//或者自己编写test方法的实现
}
}
静态方法
public interface StaticMethod{
public static void test(){
System.out.println("我是StaticMethod接口中的静态方法!");
}
}
class A implements StaticMethod{}
class Test{
public static void main(String args[]){
//运行成功
StaticMethod.test();
//编译报错:
//Static method may be invoked on containing interface class only
//A.test();
}
}
注意,接口中的静态方法,只能使用当前接口的名字来调用
Lambda表达式
行为参数化 在java代码中,我们如果需要运算,那么大部分我们的过程是确定的,但是实际参与运算的数却不确定。
在实际项目中,用户需求的变动,是很正常的一件事情,所以在上述的案例中,我们不断的增加方法,来解决用户新的需求,但是在整个过程中,我们复制了大量的相同的代码。
这几个方法之间相同的地方,例如:
方法的修饰符
方法的返回类型
方法的参数列表
方法中对数据的遍历
这几个方法之间不同的地方,例如:
方法的名字
方法中遍历数据后的核心操作
需要注意的是,方法的名字其实是无关紧要的,它只是在调用时,用到的一个标示符,最关键的是,对这个对数据的核心操作,也就是代码中核心的行为操作。 这时候,我们可以将这个核心的行为操作,进行抽象,变成一个参数
interface Action{
int action(int result,int i);
}
public static int calculate(int a,int b, Action cal){
int result = a;
for(int i = a+1;i<=b;i++){
result = cal.action(result,i);
}
return result;
}
class Test{
public static void main(String[] args){
int result = 0;
/*
//忽略掉之前匿名内部类形式中,不重要的部分
Action add = (int result,int i) -> {
return result + i;
};
*/
//简化写法
Action add = (result,i) -> result + i;
result = calculate(3,5,add);
//去掉中间变量add
//相当于,第三个参数,我们传了一个核心的计算行为
//这个核心的计算行为,就是对Action接口中action方法的实现
//result = calculate(3,5,(result,next) -> result + next);
//[3,5]之间,累加的结果
System.out.println(result);
}
}
常用接口
JDK1.8中,已经定了一些会常用到的函数式接口,这些函数式接口都定义在 java.lang.function 包中,
例如 Predicate 、 Consumer 、 Function 、 Supplier 、 UnaryOperator 和BinaryOperator 等。
注意,如果需要,也可以自己定义类似的函数式接口,并不是必须要使用这些定义好的接口。
Predicate
java.util.function.Predicate 接口定义了一个名叫 test 的抽象方法,它接受泛型T 对象,
并返回一个 boolean 类型的结果
例如:定义一个方法,用来过滤数组中所有符合要求的数据,选出大于50的数据
public class Test {
public static void main(String[] args) {
Test t = new Test();
Integer[] arr = {12,3,43,123,34,6,56,7};
arr = t.filter(arr,e->e>50);
System.out.println(Arrays.toString(arr));
}
public Integer[] filter(Integer[] arr, Predicate<Integer> p){
List<Integer> list = new ArrayList<>();
for(Integer i: arr){
//判断当前数据是否符合要求
if(p.test(i)){
list.add(i);
}
}
//把集合转为Integer类型数组
return list.toArray(new Integer[list.size()]);
}
}
//运行结果: [123, 56]
Predicate 接口中,还定义了一些默认方法和静态方法:
and()
or()
negate()
在使用该接口来做判断的时候,经常需要几个条件同时成立,或者其中一个条件成立,或者求反。
在这种情况下,除了可以在代码中选择使用&&,||,!之外,也可以分别使用这三个方法来代替。
public class Test {
public static void main(String[] args) {
Test t = new Test();
Integer[] arr = {12,3,43,123,34,6,56,7};
//条件1,数据大于10
Predicate<Integer> p1 = e -> e>10;
//条件2,数据小于50
Predicate<Integer> p2 = e -> e<50;
//俩个条件同时成立
Predicate<Integer> p = p1.and(p2);
arr = t.filter(arr,p);
System.out.println(Arrays.toString(arr));
}
public Integer[] filter(Integer[] arr, Predicate<Integer> p){
List<Integer> list = new ArrayList<>();
for(Integer i: arr){
//判断当前数据是否符合要求
if(p.test(i)){
list.add(i);
}
}
//把集合转为Integer类型数组
return list.toArray(new Integer[list.size()]);
}
}
//运行结果:[12, 43, 34]
public class Test {
public static void main(String[] args) {
Test t = new Test();
Integer[] arr = {12,3,43,123,34,6,56,7};
//条件1,数据小于10
Predicate<Integer> p1 = e -> e<10;
//条件2,数据大于50
Predicate<Integer> p2 = e -> e>50;
//俩个条件成立一个即可
Predicate<Integer> p = p1.or(p2);
arr = t.filter(arr,p);
System.out.println(Arrays.toString(arr));
}
public Integer[] filter(Integer[] arr, Predicate<Integer> p){
List<Integer> list = new ArrayList<>();
for(Integer i: arr){
if(p.test(i)){
//判断当前数据是否符合要求
list.add(i);
}
}
//把集合转为Integer类型数组
return list.toArray(new Integer[list.size()]);
}
}
//运行结果: [3, 123, 6, 56, 7]
public class Test {
public static void main(String[] args) {
Test t = new Test();
Integer[] arr = {12,3,43,123,34,6,56,7};
//条件1,数据大于10
Predicate<Integer> p1 = e -> e>10;
//获取条件1相反的数据
Predicate<Integer> p = p1.negate();
arr = t.filter(arr,p);
System.out.println(Arrays.toString(arr));
}
public Integer[] filter(Integer[] arr, Predicate<Integer> p){
List<Integer> list = new ArrayList<>();
for(Integer i: arr){
//判断当前数据是否符合要求
if(p.test(i)){
list.add(i);
}
}
//把集合转为Integer类型数组
return list.toArray(new Integer[list.size()]);
}
}
//运行结果:[3, 6, 7]
//JDK1.8中,给 Collection 集合增加了默认方法: removeIf
//此方法就使用了 Predicate 作为自己的参数,来移除符合条件的数据,实现如下:
public class Test {
public static void main(String[] args) {
Collection<String> col = new ArrayList<String>();
col.add("abc");
col.add("hello");
col.add("world");
col.removeIf((str)->str.contains("o"));
System.out.println(col);
}
}
//运行结果:[abc]
Consumer
java.util.function.Consumer 接口:
定义一个方法,用来对学生对象进行操作
public class Test {
public static void main(String[] args) {
Test t = new Test();
Student stu = new Student("tom");
//操作1,给stu对象的name属性值加前缀
Consumer<Student> consumer1 = (s) -> s.name = "briup_"+s.name;
//操作2,给stu对象的name属性值加后缀
Consumer<Student> consumer2 = (s) -> s.name =
s.name+"_"+System.currentTimeMillis();
//操作3,给stu对象的name属性值,先加前缀,再加后缀
Consumer<Student> consumer3 = consumer1.andThen(consumer2);
//如果传入consumer1,表示只加前缀
//如果传入consumer2,表示只加后缀
//如果传入consumer3,表示先加前缀,再加后缀
t.operStu(stu,consumer3);
System.out.println(stu.name);
}
public void operStu(Student stu, Consumer<Student> consumer){
consumer.accept(stu);
}
}
class Student{
String name;
public Student(String name) {
this.name = name;
}
}
//运行结果:briup_tom_1598282322884
JDK1.8中,给Collection集合增加了默认方法: forEach 用来遍历集合
public class Test {
public static void main(String[] args) {
Collection<String> col = new ArrayList<String>();
col.add("abc");
col.add("hello");
col.add("world");
//去掉中间变量,直接把Lambda表达式当前参数传入到forEach方法中
col.forEach((t)->System.out.println(t));
}
}
//运行结果:
abc
hello
world
Function
java.util.function.Function<T, R> 接口,
public class Test {
public static void main(String[] args) {
String str = "a-b-c-a-b-c";
//传入字符串,返回数组,操作为把字符串按照 "-" 进行分割为字符串数组
// "a-b-c-a-b-c" 转换为 {"a","b","c","a","b","c"}
Function<String,String[]> f1 = s -> s.split("-");
//传入字符串数组,返回Set<String>集合,目的是去除数组中重复的数据,存放把结果存放到Set集合中
//{"a","b","c","a","b","c"} 转换为集合 [a, b, c]
Function<String[], Set<String>> f2 = arr -> {
Set<String> set = new HashSet<>();
for(String string : arr){
set.add(string);
}
return set;
};
//刚好,f1函数的结果,作为f2函数的参数,f1和f2组合成f3函数
//f3函数表示传入字符串,最后返回Set<String>集合
//其实内部是先将字符串交给f1函数转换数组,在将数组交给f2函数转换Set集合
//通过上面列出的andThen源码也可以看出是这个效果
Function<String,Set<String>> f3 = f1.andThen(f2);
Set<String> set = f3.apply(str);
System.out.println(set);
}
}
//运行结果:
[a, b, c]
理解了andThen方法,那么compose方法也就理解:f1.andThen(f2),f1.compose(f2)
俩个方法的区别是,把f2操作放在f1操作之前还是之后的问题
方法引用
使用Lambda表达式,可以表示一个函数,这个函数由 “参数列表、函数主体、返回类型” 这三部分组 成,同时这个函数还是一个函数式接口中,唯一的抽象方法的实现
方法引用三大类:方法/构造器:类名::new/数组:类型[]::new
方法:
静态:类名::方法名/实例
实例:类名::非静态方法名
构造方法引用:类名::new
使用对象引用方法:对象::方法名
数组构造:数组类型::new
Optional
在Java8之前,一个函数可能因为代码逻辑问题,最终返回一个null,这时候程序中很可能出现空指针异常。而在Java8中,不推荐返回 null ,而是返回 Optional
Optional 中常用的方法: of ofNullable isPresent ifPresent get orElse orElseGet map flatMap filter
Stream
java.util.stream.Stream 接口,表示能应用在一组元素上,一次执行的操作序列,也就是可以对一组数据进行连续的多次操作。
Stream在使用的时候,需要指定一个数据源,比如 java.util.Collection 的子类, List 或者 Set都可以,但是 Map 类型的集合不支持。
Stream是对集合功能的增强,它提供了各种非常便利、高效的聚合操作,可以大批量数据操作,同时再结合Lambda表达式,就可以极大的提高编程效率。
Stream的API提供了串行和并行两种模式进行操作数据。
Stream操作分为中间操作或者最终操作两种:
中间操作,返回Stream本身,这样就可以将多个操作依次串起来
例如, map、flatMap、filter、distinct、sorted、peek、limit、skip、parallel、
sequential、unordered
最终操作,返回一特定类型的计算结果
例如, forEach、forEachOrdered、toArray、reduce、collect、min、max、count、
anyMatch、allMatch、noneMatch、findFirst、findAny、iterator
基本类型
对于基本数值类型,有专门的三种Stream类型:
IntStream
LongStream
DoubleStream
虽然也可以用Stream类型,并指定泛型:
Stream<\Integer>
Stream<\Long>
Stream<\Double>
但是,在数据量较大的时候,自动拆箱/装箱会比较消耗性能,所以提供了上面三种专门针对基本类型的
Stream
Stream转其他
使用Stream的API对数据操作后,还可以把结果转换为其他类型
一个Stream在代码中,只能使用一次,再次使用就会报错
Stream操作主要分为中间操作或者最终操作:
最终操作,就是把Stream处理完了,已经有结果了,之后就不能再使用这个Stream了
中间操作,对Stream进行一个中间操作后,还可以对其进行下一步的继续操作