JDK8

JDK1.8新特性(1)

函数式接口,lambda表达式

1.接口方法

(1).默认方法

在JDK1.8中,接口里面可以定义默认方法。

定义语法:

interface interfaceName{
    default returnType methodName(arg-list){}
}

默认方法有两大优势

1.可以让接口更优雅的升级,减少使用人员的负担,不必随着接口方法的增加,从而修改实现代码,因为默认方法在子类中不用实现

2.可以让实现类中省去很多不必要方法的空实现

接口继承及默认方法冲突

java中一个类只能继承一个父类,但是可以实现多个接口,由于Java8中接口中的方法可以直接进行实现,那么类中也可以从多个接口继承这些方法。

例如:

interface A{
default void test(){
	System.out.println("Default Method test in A");
	}
} 
interface B extends A{
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();
	}
} //运行结果:
default method test in B

方法调用的判断规则

1.类中声明的方法优先级最高类或父类中,声明的方法要高于任何默认方法的优先级

2.如果无法依据第一条进行判断,那么子接口的优先级更高

3.最后,如果还是无法判断,那么继承了多个接口的类,必须同个实现(重写)方法来确定方法的调用

例如:一个类C,继承父类A,实现接口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方法优先级更高

例如:一个类C,实现两个接口A,B,但是A和B之间没有子父接口关系

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方法的实现
	}
}
(2)静态方法

在JDK1.8中接口还可以定义静态方法。和类中定义的静态方法类似,接口中的静态方法可以直接通过接口名.静态方法名 的形式进行访问。

语法:

interface interfaceName{
    static returnType methodName(arg-list){
        //实现代码
    }
}

访问:InterfaceName.methodName();

接口中的静态方法只能使用当前接口的名字来调用

2.Lambda

Lambda表达式是JDK1.8新增的一种语法,以确保Java代码中可以支持函数式编程。

(1)行为参数化

在java代码中,我们如果需要运算,那么大部分我们的过程是确定的,但是实际参与运算的数却不确定。

例如,根据用户输入的两个数,求两个数之间所有数据的和

public static int sum(int a,int b){
int result = a;
for(int i = a+1;i<=b;i++){
result += i;
	} 
    return result;
}

如果后来用户的需求,变成求两个数之间所有数据的乘积

public static int multiply(int a,int b){
int result = a;
for(int i = a+1;i<=b;i++){
result *= i;
	} 
    return result;
}

如果后来用户的需求,成求两个数之间所有能被3整除的数据的和

public static int addAllByCondition(int a,int b){
	int result = a;
		for(int i = a+1;i<=b;i++){
			if(i%3==0){	
				result += i;
			}
		} 
    	return result;
}

在实际项目中,用户需求的变动,是很正常的一件事情,所以在上述的案例中,我们不断的增加方法,来解决用户新的需求,但是在整个过程中,我们复制了大量的相同的代码

这几个方法之间相同的地方,例如:

  • 方法的修饰符
  • 方法的返回类型
  • 方法的参数列表
  • 方法对数据的遍历

这几个方法之间不同的地方,例如:

  • 方法的名字

  • 方法中遍历数据后的核心操作

    **需要注意的是,方法的名字其实是无关紧要的,它只是在调用时,用到的一个标示符,最关键的是,对这个对数据的核心操作,也就是代码中核心的行为操作。 **

这时候我们可以将这个核心的行为操作,进行抽象,编程一个参数

public static int calculate(int a,int b, 此处传递一个计算行为){
int result = a;
for(int i = a+1;i<=b;i++){
调用计算行为,操作当前数据
	} 
    return result;
}

注意,方法名改为了calculate,代表了所有的计算情况,每种情况由一个具体的 计算行为 来控制, 而这个 计算行为 就是方法中需要接收的参数。(行为参数化)

我们可以将这个计算行为,用接口的形式进行定义:

interface Action{
    int action(int result,int i);
}

这时候calculate方法就可以这样进行定义:

public static int calucate(int a,int b,Action action){
    int result = a;
    for(int i = a +1;i<=b;i++){
    result = cal.action(result,i)
    }
    return result;
}

具体的使用方式:

//定义累加的核心操作行为
class Add implements Action{
public int action(int result,int i){
return result+i;
	}
} 
//定义累乘的核心操作行为
class Multiply implements Action{
public int action(int result,int i){
return result*i;
	}
} 	
class Test{
	public static void main(String[] args){
	int result = 0;
	result = calculate(3,5,new Add());
	//[3,5]之间,累加的结果
	System.out.println(result);
result = calculate(3,5,new Multiply());
//[3,5]之间,累乘的结果
System.out.println(result)
}

上述代码中,将我们要执行的核心计算操作,定义成了一个数,传给了 calculate 方法我们可以通过这个参数,给方法传递不同的行为,来实现不同操作,这就是行为参数化

Java中,不允许孤立的代码存在,我们要想将行为(核心操作代码)传递给 calculate 方法,就必须要将这些核心操作代码,包装在一个实现了Action的类中。
为了减少声明和定义类,Java提供了匿名内部类的实现,来简化我们刚才的调用过程:

class Test{
	public static void main(String[] args){
	int result = 0;
	result = calculate(3,5,new Action(){
		public int action(int result,int i){
			return result+next;
	}
});
//[3,5]之间,累加的结果
	System.out.println(result);
	result = calculate(3,5,new Action(){
		public int action(int result,int i){
			return result*next;
	}
});
//[3,5]之间,累乘的结果
	System.out.println(result)
	}
}

上述代码中,使用了匿名内部类的方式,虽然简化了之前的代码,但是每次调用还是编写了很多相同代码,例如 :

  • new Action(){}

  • public int action(int result ,int next){}

    这个new对象的操作,还有action方法声明的操作,每次都是重复的,其实我们真正的关心的只有三点 :

  • 方法的参数列表

  • 方法中的核心操作代码

  • 方法的返回类型

也就是,传入指定的参数,通过核心计算,给出最后结果

例如:

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);
	}
}
//运行结果:
(2)函数式编程

函数式编程,和面向对象编程,以及面向过程编程一样,他们都是编程的一种方式。

函数式编程是面向数学的抽象,将计算过程描述为一种表达式求值。简单说,函数式程序就是一个表达式。严格意义上的表达式,就是由数据和操作符按照一定的规则,组合在一起形成的序列,并且所有的表达式都是有返回结果的,这是这里所说的表达式和代码语句的最大的区别。但是在java中,是允许函数式编程中没有任何返回值的,因为java中有关键字 void ,但是在其他一些专门的函数式编程语言中,是没有 void 的。

所以,jdk1.8中,可以把Action接口的实现,简写为:

/*
Action add = (int result,int next) -> {
return result + next;
};
*/
//简化写法
Action add = (result,next) -> result + next;

(result,next) -> result + next; 在JDK1.8中,这部分就称之为Lambda表达式。

(3)Lambda概述

Lambda表达式,可以用来表示一个函数,它只关注函数的参数列表,函数主体、返回类型,并且可以将此函数作为一个参数,进行传递。

在Java中,Lambda表达式还有另一个存在的意义,那就是作为一个接口的实现类对象

例如:

public class Test {
	public static void main(String[] args) {
		Action a = (str) -> str.length();
		System.out.println(a.test("hello"));
	}
} 
interface Action{
	public int test(String str);
}

可以看出,Lambda表达式,虽然可以通过(参数列表,函数主体、返回类型)三部分来表示一个具体的函数操作,但是它必须是依托在一个接口才行,所以Lambda表达式就是对接口中抽象方法的实现。

(4) Lambda使用

接口中有且只有一个抽象方法的时候,才可以使用Lambda表达式来对其进行实现。

  • 函数式接口

有且只有一个抽象方法的接口,就是函数式接口。该接口中,也允许有其他的默认方法和静态方法。

JDK1.8中,针对函数式接口,新增了一个注解@FunctionalInterface ,用来检查被标注的接口,是不是一个函数式接口,如果不是,那么编译器会报错。但是,该注解不是必须要用的,它只是会让编辑器帮我们检查一下而已,以免出现接口中抽象方法的个数不是1的情况。

  • Lambda语法

lamb表达式的格式为:()->{}

  • ()表示参数列表
  • ->后面跟的是函数主体
  • {}函数主体,表达式的返回值,由这个函数主体的代码来决定

函数式接口中,抽象方法常见的有以下几种情况:

1.函数式接口中,抽象方法无参,无返回值

public class Test {
	public static void main(String[] args) {
/**
* Action action1 = new Action(){
* *
public void run(){
* *
}
* };
*/
	Action action1 = () -> {};
/**
* Action action2 = new Action(){
* *
public void run(){
* System.out.println("hello");
* }
* };
*/
//函数主体中,如果只有一句代码,那么可以省去大括号
	Action action2 = () -> 					System.out.println("hello");
/**
* Action action3 = new Action(){
* *
public void run(){
* int a = 1;
* int b = 2;
* System.out.println(a+b);
* }
* };
*/
//函数主体中,如果有多句代码,那么大括号必须要写
	Action action3= () -> {
		int a = 1;
		int b = 2;
		System.out.println(a+b);
		};
	}
} 
interface Action{
	public void run();
}

2.函数式接口中,抽象方法有参,无返回值

public class Test {
	public static void main(String[] args) {
/**
* Action action = new Action(){
* *
public void run(int a){
* *
}
* };
1 2 3 4 5 6 7 8 9
10*/
	Action action = (int a) -> {};
/**
* Action action1 = new Action(){
* *
public void run(int a){
* *
}
* };
*/
//只有一个参数时,可以不加小括号
//并且参数的类型,JVM运行时会做自动推断的,及时不写,它也知道是int
	Action action1 = a -> {};
/**
* Action action2 = new Action(){
* *
public void run(int a){
* System.out.println(a);
* }
* };
*/
	Action action2 = a -> System.out.println(a);
/**
* Action action3 = new Action(){
* *
public void run(int a){
* a++;
* }
* };
*/
	Action action3 = a -> a++;
}
} 
//抽象方法是一个参数的情况
interface Action{
	public void run(int a);
}
public class Test {
	public static void main(String[] args) {
/**
* Action action1 = new Action(){
* *
public void run(int a,int b){
* *
}
* };
*/
	Action action1 = (a,b) -> {};
		}
}
//抽象方法是多个参数的情况
interface Action{
public void run(int a,int b);
}

3.函数式接口中,抽象方法 无参,有返回值

public class Test {
	public static void main(String[] args) {
/**
* Action action1 = new Action(){
* *
public void run(){
* return 1;
* }
* };
*/
//如果就一句代码,大括号可以去掉,return关键字也会省去
	Action action1 = () -> 1;
/**
* Action action2 = new Action(){
* *
public void run(){
* int num = 10;
* return (int)(Math.random()*num);
* }
* };
*/
	Action action2 = () -> {
		int num = 10;
		return (int)(Math.random()*num);
		};
	}
}
interface Action{
	public int run();
}

4.函数式接口中,抽象方法有参,有返回值

public class Test {
	public static void main(String[] args) {
/**
* Action action1 = new Action(){
* *
public int run(int a,int b){
* return a+b;
* }
* };
*/
	Action action1 = (a,b) -> a + b;
/**
* Action action2 = new Action(){
* *
public int run(int a,int b){
* int num = a+b;
* return (int)(Math.random()*num);
* }
* };
*/
	Action action2 = (a,b) -> {
		int num = a+b;
		return (int)(Math.random()*num);
		};
	}
} 
interface Action{
	public int run(int a,int b);
}

**注意,Lambda表达式中的参数列表,里面的参数可以不写类型,因为JVM在运行时会自动推断,当然,如果直接手动写上去,也完全没有问题。 **

  • 常用接口

    JDK1.8中,已经定了一些会常用到的函数式接口,这些函数式接口都定义在 java.lang.function 包中,例如 Predicate 、 Consumer 、 Function 、 Supplier 、 UnaryOperatorBinaryOperator 等。
    注意,如果需要,也可以自己定义类似的函数式接口,并不是必须要使用这些定义好的接口

1.Predicate

java.util.function.Predicate 接口定义了一个名叫test 的抽象方法,它接受泛型T 对象,并返回一个 boolean 类型的结果

@FuntionalInterface
public interface Predicate<T>{
    boolean test(T t);
}

此外Predicate接口中,还定义了一些默认方法和静态方法:

  • and()

  • or()

  • negate()

    在使用该接口来做判断的时候,经常需要几个条件同时成立,或者其中一个条件成立,或者求反。
    在这种情况下,除了可以在代码中选择使用&&,||,!之外,也可以分别使用这三个方法来代替。

2.Consumer

==java.util.function.Consumer==接口:

@FunctionalInterface
public interface Consumer<T> {
  void accept(T t);
default Consumer<T> andThen(Consumer<?superT>after{
  Objects.requireNonNull(after);
  return (T t) -> { accept(t); after.accept(t); 		};
  }
}

例如,定义一个方法,用来对学生对象进行操作

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用来遍历集合,定义如下:

image-20200910000420454

例如:

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

3.Function

java.util.function.Function<T, R> 接口

@FunctionalInterface
public interface Function<T, R> {
	R apply(T t);
	default <V> Function<V, R> compose(Function<? super V, ? extends T> before)
	{
	Objects.requireNonNull(before);
	return (V v) -> apply(before.apply(v));
	}
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
	Objects.requireNonNull(after);
	return (T t) -> after.apply(apply(t));
	} 
 //Returns a function that always returns its input argument.
	static <T> Function<T, T> identity() {
	return t -> t;
	}
}

例如:

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]

静态方法identity,可以直接返回一个function对象,传入一个参数直接把该参数返回,不做任何操作,这是一个泛型方法

例如

public class Test {
	public static void main(String[] args) {
		Function<String,String> f = Function.identity();
		//传入hello,返回hello
		String result = f.apply("hello");
		System.out.println(result);
	}
} 
//运行结果:hello

4.Supplier

java.util.function.Supplier 接口

@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
* *
@return a result
*/
T get();
}

可以看出,Supplier接口中的方法,不需要参数,可以根据我们指定的算法,返回一个数据

JDK1.8中,还专门定义了一些针对基本类型的函数式接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8iMtFjKD-1600181042008)(\images\image-20200910000420454.png)]

3.类型推断

使用Lambda表达式,相当于给函数式接口生成一个实例,但是Lambda表达式本身,并不包含这个接口的任何信息

之所以Lambda表达式中没有接口的任何信息,JVM还能将其和接口匹配的上,那是因为:

  • 我们在使用Lambda表达式的时候,JVM是会通过上下文自动推断它所属接口类型的
  • 并且接口中只有一个抽象方法,自然也能匹配成功该表达式所对应实现的抽象方法
  • 类似的,JVM还能自动推断出Lambda表达式中参数的类型

4.重载分析

如果类中的方法进行了重载,那么在使用Lambda表达式的时候,很可能给它的类型推断带来问题。

例如

public class Test {
	public static void main(String[] args) {
		//编译报错,因为俩个方法都符合
		test(1,num -> num>0);
	} 
   	public static void test(int a,Predicate<Integer> p){
	} 
    public static void test(int a,Function<Integer,Boolean> f){
	}
}

可以看出,这时候编译报错,因为表达式 num -> num>0 对于俩个方法都符合
既符合 Predicate 的实现,也符合 Function<Integer,Boolean> 的实现

这时候可以做类型转换,来解决这个问题 但是,这种情况很少出现,我们应该在方法重载的时候就提前注意到这个问题,或者给方法起不同的名字。

5.局部变量

如果在Lambda表达式中,使用了局部变量,那么这个局部变量就一定要使用 final 修饰符进行修饰,这方面的语法要求,和之前学习的匿名内部类保持一致

注意,JDK1.8中,被匿名内部类、局部内部类、Lambda表示访问的局部变量,会默认加上final 修饰符

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值