1.函数式接口
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
注解@FunctionalInterface可以检测接口是否是一个函数式接口。
(在合作开发中,避免同事修改此接口造成错误)
2.Lambda 表达式的语法
基本形式:Lambda 表达式由三部分组成:参数、->符号、方法体
(参数列表)->{方法体}
如:
// 2个int型整数,返回和值
(int x, int y) -> {x + y}
// 无参数,返回值为字符串“姓名:zzy”
() -> "姓名:zzy";
// 1个String参数,直接打印结果,方法引用
(System.out::println);
// 或
(String s) -> System.out.print(s)
// 1个参数(数字),返回2倍值
x -> 2 * x;
// 2个参数(数字),返回差值
(x, y) -> x – y
/**
* @author zzy
* @createDate: 2022-01-17 16:10:18
* @description: 自定义的函数式接口测试
*/
public class FunctionADemo {
//函数式接口
@FunctionalInterface
interface FunctionA {
/**
* @description: 接口方法默认抽象方法
* @params: 有参数, 有返回值
* @return:
*/
public abstract Integer function(Integer x, Integer y);
}
public static void main(String[] args) {
//以前使用匿名内部类的方式创建对象
FunctionA functionA = new FunctionA() {
@Override
public Integer function(Integer x, Integer y) {
System.out.println("我是匿名内部类");
return 0;
}
};
//使用Lambda的方式---------------------------------------------------------
//1.有返回值,方法体有多行
FunctionA functionA1 = (Integer x, Integer y) -> {
System.out.println("我是减法");
return x - y;
};
//2.有返回值,方法体有多行,参数类型可以省略
FunctionA functionA2 = (x, y) -> {
System.out.println("我是加法");
return x + y;
};
//4.乘法
// 如果 Lambda 表达式的方法体只有一条语句时,可以省略花括号{}和return
FunctionA functionA3 = (x, y) -> x * y;
//4.除法
// 如果 Lambda 表达式的方法体只有一条语句时,可以省略花括号{}
// 如果 Lambda 表达式的方法体只有一条语句,且为返回值的时候,可以省略 return
FunctionA functionA4 = (x, y) -> x / y;
System.out.println(functionA.function(10, 2));
System.out.println(functionA1.function(10, 2));
System.out.println(functionA2.function(10, 2));
System.out.println(functionA3.function(10, 2));
System.out.println(functionA4.function(10, 2));
}
}
2.1.语法总结
- 使用Lambda表达式必须具有函数式接口
- 使用Lambda必须具有上下文推断。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
- 参数的类型可以明确声明,也可以不声明,由 JVM 隐式的推断,例如
(int a)
和(a)
效果相同 - Lambda 表达式的参数可以是零个或多个
- 当参数只有一个,且类型可推导时,可以省略括号,例如
(a)
与a
效果相同 - 如果 Lambda 表达式的方法体只有一条语句时,可以省略花括号{}
- 如果 Lambda 表达式的方法体只有一条语句,且为返回值的时候,可以省略 return
- Lambda表达式的返回值永远是函数式接口,记住这个本质
3.Lambda作用域
注意!!!! Lambda 表达式并不会引入新的作用域,这一点和匿名内部类是不同的。
也就是说,Lambda 表达式主体内使用的 this 关键字和其所在的类实例相同。
4.方法引用
1.类::静态方法
//类::静态方法
@FunctionalInterface
public interface ImTheOne {
String handleString(String a, String b);
}
class OneClass {
//静态方法
public static String concatString(String a, String b) {
return a + b;
}
}
class ImTheTest {
public static void main(String[] args) {
//相当于以下效果,直接把类的静态方法写在Lambda体里
ImTheOne theOne2 = (a, b) -> OneClass.concatString(a, b);
String result1 = theOne2.handleString("123", "456");
System.out.println(result1);
/*******************
* 1.静态方法引用
* 类::静态方法
* 把OneClass类的concatString()方法作为了Lambda表达式的Lambda体
* */
ImTheOne theOne = OneClass::concatString;
String result2 = theOne.handleString("abc", "def");
System.out.println(result2);
}
2.对象::普通方法
//对象::普通方法
@FunctionalInterface
interface ImTheTwo {
Integer handleInteger(Integer a, Integer b);
}
class OneClass {
//普通方法
public Integer adding(Integer a, Integer b) {
return a + b;
}
}
class ImTheTest {
public static void main(String[] args) {
/*******************
* 2.对象::实例方法,
* 先定义了OneClass的一个对象,然后把对象的adding()方法作为Lambda体。
* */
OneClass oneClass = new OneClass();
ImTheTwo theTwo = oneClass::adding;
Integer result3 = theTwo.handleInteger(1, 2);
System.out.println(result3);
}
3.类::普通方法
//类::普通方法
@FunctionalInterface
interface ImTheThree<T> {
String handleString(T a, String b);
}
class ThreeClass {
public String concatString(String a) {
return a;
}
public String startHandleString(ImTheThree<ThreeClass> three, String str) {
String result = three.handleString(this, str);
return result;
}
}
class ImTheTest {
public static void main(String[] args) {
/*******************
* 3.类::实例方法,
* 当一个对象调用一个方法,方法的参数中包含一个函数式接口,该函数式接口的第一个参数类型是这个对象的类,
* 那么这个函数式接口可用方法引用代替,并且替换用的方法可以不包含函数式接口的第一个参数(调用对象的类)。
*
* */
ThreeClass threeClass = new ThreeClass();
String result4 = threeClass.startHandleString(ThreeClass::concatString, "123");
System.out.println(result4);
//相当于以下效果
ThreeClass threeClass2 = new ThreeClass();
ImTheThree theThree2 = (a, b) -> threeClass2.concatString(b);
String result5 = theThree2.handleString(theThree2, "123");
System.out.println(result5);
}
}
当一个对象调用一个方法,方法的参数中包含一个函数式接口,
该函数式接口的第一个参数类型是这个对象的类,那么这个函数式接口可用方法引用代替,
并且替换用的方法可以不包含函数式接口的第一个参数(调用对象的类)。
步骤 | 描述 | 代码对照 |
---|---|---|
1 | 当一个对象调用一个方法 | oneClass对象,调用startHandleString(ImTheThree<ThreeClass> three, String str)方法。 |
2 | 方法的参数中包含一个函数式接口 | startHandleString方法中有个参数是ImTheOne<ThreeClass> ImTheThree<T>是一个函数式接口,在本例中使用的泛型是ThreeClass类 |
3 | 该函数式接口的第一个参数类型是这个对象的类 | ImTheThree<T>这个函数式接口的唯一抽象方法是handleString(T t, String a, String b) 第一个参数就是ImTheOne定义时的泛型类,也就是ThreeClass类,也就是步骤1时调用者threeClass对象所属的类。 |
4 | 那么这个函数式接口可用方法引用代替 | 对于ImTheThree<ThreeClass>这个参数,可以用ThreeClass::concatString这个方法引用来代替 |
5 | 并且替换用的方法可以不包含函数式接口的第一个参数 | 函数式接口中的handleString方法参数列表是 (T t, String a),2个参数,而方法引用中的concatString方法参数是(String a),比handleString少了第一个T参数, 此时java会认为第一个参数就是方法的调用者ThreeClass对象。 |
另外,这种模式下方法引用的方法必须是调用者对象所属类中的方法,也就是concatString方法必须定义在ThreeClass中。
4.构造器::new
这种模式被称为构造方法引用
,或构造器引用
。
构造方法也是方法,构造方法引用实际上表示一个函数式接口中的唯一方法引用了一个类的构造方法,引用的是那个参数相同的构造方法。
//类::new 构造器引用
@FunctionalInterface
interface ImTheFour {
TargetClass getTargetClass(String a);
}
//构造器引用
@Data
class TargetClass {
String oneString;
public TargetClass() {
}
public TargetClass(String a) {
oneString = a;
}
}
class ImTheTest {
public static void main(String[] args) {
/*******************
* 4.类::new,
* 构造方法引用实际上表示一个函数式接口中的唯一方法引用了一个类的构造方法,引用的是那个参数相同的构造方法。
* 那么这个函数式接口可用方法引用代替,并且替换用的方法可以不包含函数式接口的第一个参数(调用对象的类)。
*
* */
ImTheFour imTheFour = TargetClass::new;
TargetClass targetClass1 = imTheFour.getTargetClass("123");
System.out.println(targetClass1.oneString);//输出123
ImTheFour imTheFour2 = (a) -> new TargetClass("abc");
TargetClass targetClass2 = imTheFour2.getTargetClass("123");
System.out.println(targetClass2.oneString); //输出abc,因为getTargetClass使劲调用的是 new
//TargetClass("abc")
}
}
5.数组引用
数组引用算是构造器引用的一种,可以引用一个数组的构造
//数组引用
@FunctionalInterface
interface ImTheFive<T> {
T getArr(int a);
}
class ImTheTest {
public static void main(String[] args) {
/*******************
* 4.数组::new,
* 数组引用算是构造器引用的一种
* 使用数组引用时,函数式接口中抽象方法必须是有参数的,而且参数只能有一个,必须是数字类型int或Integer,这个参数代表的是将来生成数组的长度。
* */
ImTheFive imTheFive = int[]::new;
int[] stringArr = (int[]) imTheFive.getArr(5);
System.out.println(stringArr.length);
}
}
四大函数式接口
要使用lambda表达式,我们就要创建一个函数式接口,那每次用lambda表达式的时候岂不是很麻烦,这时候,java给我们内置了四大核心函数式接口(常用)
。
简单类比,方便理解:
- Consumer类似get方法
- Supplier类似set方法
- Function类似toString方法
- Predicate类似equals方法
详情暂不介绍了,理解了Lamabda表达式,这些慢慢就会用了