Lambda表达式-方法引用-函数式接口

(一)Lambda表达式

函数式编程思想概述
面向对象思想强调“必须通过对象的形式来做事情”
函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”
而我们要学习的Lambda表达式就是函数式思想的体现

Lambda表达式的标准格式:
组成Lambda表达式的三要素: 形式参数,箭头,代码块
(形式参数)->{代码块}
():里面没有内容,可以看成是方法形式参数为空
->:用箭头指向后面要做的事情
{}:包含一段代码,我们称之为代码块,可以看成是方法体中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//匿名内部类
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("多线程启动了");
}
}).start();

//Lambda表达式
//Runnable接口里面有且只有一个抽象方法
new Thread(() -> {
System.out.println("多线程启动了");
}).start();

//省略
new Thread(() -> System.out.println("多线程启动了")).start();

Lambda表达式的使用前提:
①有一个接口
②接口中有且仅有一个抽象方法

Lambda 表达式的省略模式:
省略规则:
参数类型可以省略,但是有多个参数的情况下,不能只省略一个;
如果参数有且仅有一个,那么小括号可以省略;
如果代码块的语句只有一条,可以省略大括号和分号,甚至是return。

Lambda 表达式的注意事项:
使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
必须有上下文环境,才能推导出Lambda对应的接口
根据局部变量的赋值得知Lambda对应的接口: Runnabler = ()->System.out.println(“Lambda表达式”);
根据调用方法的参数得知Lambda对应的接口: new Thread(() -> System.out.printin(“Lambda表达式).start();

Lambda 表达式和匿名内部类的区别:

  1. 所需类型不同
    匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
    Lambda表达式:只能是接口
  2. 使用限制不同
    如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
    如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
  3. 实现原理不同
    匿名内部类: 编译之后,产生一个单独的.class字节码文件
    Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成

(二)方法引用

在使用 Lambda 表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿参数做操作
那么考虑一种情况:如果我们在 Lambda 中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑呢?
答案肯定是没有必要
那我们又是如何使用已经存在的方案的呢?
这就是我们要讲解的方法引用,我们是通过方法引用来使用已经存在的方案

方法引用符::

::该符号为引用运算符,而它所在的表达式被称为方法引用

Lambda 表达式: usePrintable(s -> System.out.println(s));
分析:拿到参数s之后通过Lambda表达式,传递给System.out.println方法去处理
方法引用: usePrintable(System.out::println);
分析:直接使用System.out中的println方法来取代Lambda,代码更加的简洁

如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导
如果使用方法引用,也是同样可以根据上下文进行推导
方法引用是Lambda的孪生兄弟

Lambda 表达式支持的方法引用

常见的引用方式:
(1)引用类方法(类的静态方法)
(2)引用对象的实例方法(类中的成员方法)
(3)引用类的实例方法(类中的成员方法)
(4)引用构造器(引用构造方法)

(1)引用类方法

引用类方法,其实就是引用类的静态方法

格式:类名::静态方法
范例: Integer::parseInt
    Integer类的方法: public static int parseInt(String s)将此String转换为int类型数据

1
2
3
//引用类方法
//Lambda表达式被(类方法)替代的时候,它的形式参数全部传递给静态方法作为参数
useConverter(Integer::parseInt);

(2)引用对象的实例方法

引用对象的实例方法,其实就引用类中的成员方法

格式:对象::成员方法
范例:“HelloWorld”::toUpperCase
    String类中的方法: public String toUpperCase()将此String所有字符转换为大写

1
2
3
4
//Lambda表达式被(对象的实例方法)替代的时候,它的形式参数全部传递给该方法作为参数
usePrinter(s -> System.out.printIn(s.toUpperCase()));//Lambda
PrintString ps = new PritString();//引用类的实例方法
usePrinter(ps:printUpper);

(3)引用类的实例方法

引用类的实例方法,其实就是引用类中的成员方法

格式:类名::成员方法
范例:String::substring
    String类中的方法: public String substring(int beginIndex,int endIndex)
    从beginIndex开始到endIndex结束,截取字符串,返回一个子串,子串的长度为 endIndex-beginIndex

1
2
3
4
5
//Lambda表达式被类的实例方法替代的时候
//第一个参数作为调用者
//后面的参数全部传递给该方法作为参数
useMyString((s,x,y) -> s.substring(x,y));//Lambda
useMyString(String::substring);//引用类的实例方法

(4)引用构造器

引用构造器,其实就是引用构造方法

格式:类名::new
范例: Student::new

1
2
3
//Lambda表达式被构造器替代的时候,它的形式参数全部传递给构造器作为参数
useStudentBuilder((name , age) -> new Student(name,age));//Lambda
useStudentBuilder(Student::new);//引用构造器

(三)函数式接口

把已有的方法作为参数传递,提高复用性和可读性

函数式接口:有且有一个抽象方法的接口
Java中的函数式编程体现就是Lambda表达式,所以函数式接口就是可以适用于Lambda使用的接口
只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导

@Functionallnterface
放在接口定义的上方:如果接口是函数式接口,编译通过;如果不是,编译失败
我们自己定义函数式接口的时候,@Functionallnterface是可选的,就算我不写这个注解,只要保证满足函数式接口定义的条件,也照样是函数式接口。但是,建议加上该注解

函数式接口作为方法的参数
如果方法的参数是一个函数式接口,我们可以使用Lambda表达式作为参数传递

1
startThread(() -> System.out.println(Thread.currentThread().getName() +"线程启动了"));

函数式接口作为方法的返回值
如果方法的返回值是一个函数式接口,我们可以使用Lambda表达式作为结果返回

1
2
3
private static Comparator<String> getComparator(){
return (s1, s2) -> s1.length() - s2.length();
}

常用的函数式接口

Java 8在java.util.function包下预定义了大量的函数式接口供我们使用
Supplier接口
Consumer接口
Predicate接口
Function接口
Java 内置四大核心函数式接口.png

(1)Supplier接口

Supplier< T >:包含一个无参的方法
代表结果供应商(该接口主要是生产数据的)
(提供一个值的操作,不接受任何参数并返回一个值)

Modifier and Type 方法 描述
T get() 获得结果

该方法不需要参数,它会按照某种实现逻辑(由Lambda表达式实现)返回一个数据
Supplier接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的 get 方法就会生产什么类型的数据供我们使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SupplierTest{ 
public static void main(String[] args) {
//定义一个int数组
int[] arr = {1950283746};

int maxValue = getMax(()-> {
int max = arr[0];
for(int i=1; i<arr.length; i++){
if(arr[i] > max) {
max = arr[i];
}
}
return max;
});

System.out.printn(maxValue);
}

//返回一个int数组中的最大值
private static int getMax(Supplier<Integer> sup) {
return sup.get();
}

(2)Consumer接口

Consumer接口也被称为消费型接口,它消费的数据的数据类型由泛型指定
接收一个输入参数并且不返回任何结果的操作(消费型接口:拿到数据做操作且不返回结果)

Modifier and Type 方法 描述
void accept(T t) 对给定的参数执行此操作
default Consumer< T > andThen(Consumer<? super T> after) 返回一个组成的Consumer,依次执行此操作,然后执行after操作(即会执行两个操作,本操作和参数指定的操作)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ConsumerTest {
public static void main(String[] args){
String[] strArray = {"林青霞,30","张曼玉,35","王祖贤,33"};

printInfo(strArray,
str -> System.out.print("姓名:"+ str.split(",")[0]),
str -> System.out.println(",年龄: " + Integer.parseInt(str.split(",")[1])));

private static void printInfo(String[] strArray, Consumer<String> con1, Consumer<String> con2) {
for(String str : strArray){
con1.andThen(con2).accept(str); //相当于将以下两步合并到一步
//con1.accept(str);
//con2.accept(str);
}
}

(3)Predicate接口

Predicate< T >接口通常用于判断参数是否满足指定的条件
Predicate:接收一个输入参数并返回一个布尔值的操作,用于判断条件

Modifier and Type 方法 描述
boolean test(T t) 对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值
default Predicate< r > and(Predicate<? super T> other) (与)返回一个组合的谓词,表示该谓词与另一个谓词的短路逻辑AND
default Predicate< T > or(Predicate<? super T> other) (或)返回一个组合的谓词,表示该谓词与另一个谓词的短路逻辑或
default Predicate< T > negate() (非)返回表示此谓词的逻辑否定的谓词
static < T > Predicate< T > isEqual(Object targetRef) 返回一个谓词,根据 objects.equals (object,object)测试两个参数是否相等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class PredicateTest{
public static void main(String[] args){
String[] strArray = {"林青霞,30","柳岩,34""张曼玉,35""貂蝉,31""王祖贤,33"};
ArrayList<String> array = myFilter(strArray,
s -> s.split(",")[0].length() > 2,
s -> Integer.parseInt(s.split(",")[1]) > 33);

for(String str:array){
System.out.println(str);
}
}


private static ArrayList<String> myFilter(String[] strArray,Predicate<String> pre1,Predicate<String> pre2){
ArrayList<String> array = new ArrayList<>();
for (String str:strArray) {
if(pre1.and(pre2).test(str)){//对str这样一个字符串,做一个组合的条件判断,必须同时满足pre1和pre2两个条件
array.add(str);
}
}
return array;
}

}

(4)Function接口

Function< T,R >:接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新的值
T-函数输入的类型
R-函数结果的类型
(接收一个输入参数并返回结果的操作)

Modifier and Type 方法 描述
R apply(T t) 将此函数应用于给定的参数
default < V > Function< T,V > andThen(Function<? super R,? extends V> after) 返回一个组合函数,首先将该函数应用于其输入,然后将after函数应用于结果
default < V > Function< V,R > compose(Function<? super V,? extendsT> before) 返回一个组合函数,首先将before函数应用于其输入,然后将此函数应用于结果
static cT> Fuetion< T,T > identity() 返回一个总是返回其输入参数的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class FunctionTest{
public static void main(String[] args){
String s = "林青霞,30"
convert(s,
ss -> ss.split(",")[1],
ss -> Integer.parseInt(ss), // Integer::parseInt(可以用方法引用)
i -> i + 70);

}
public static void convert(String s,Function<String,String> fun1,Function<String,Integer> fun2,Function<Integer,Integer> fun3){
int i = fun1.andThen(fun2).andThen(fun3).apply(s);
System.out.println(i);
}
}

Kotlin

Kotlin 中 lambda 表达式的特性在 Java 中的对应情况:

  1. 简化的语法:
    Kotlin 中的 lambda 表达式可以省略参数类型和返回类型,例如:

    1
    val sum: (Int, Int) -> Int = { x, y -> x + y }

    在 Java 中,需要显式声明参数类型和返回类型,例如:

    1
    Function2<Integer, Integer, Integer> sum = (x, y) -> x + y;
  2. 默认参数名 it
    Kotlin 中的单个参数 lambda 表达式可以使用默认参数名 it,例如:

    1
    2
    val numbers = listOf(1, 2, 3)
    numbers.forEach { println(it) }

    在 Java 中,需要显式命名参数,例如:

    1
    2
    List<Integer> numbers = Arrays.asList(1, 2, 3);
    numbers.forEach(number -> System.out.println(number));
  3. 尾随 lambda 表达式:
    Kotlin 中可以使用尾随 lambda 表达式的简化语法,例如:

    1
    2
    3
    performAction {
    println("Action performed!")
    }

    在 Java 中,需要使用匿名内部类,例如:

    1
    2
    3
    performAction(() -> {
    System.out.println("Action performed!");
    });
  4. 函数类型作为参数或返回值:
    Kotlin 中支持函数类型作为参数或返回值,例如:

    1
    2
    3
    fun operateNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
    }

    在 Java 中,需要使用接口或函数式接口来实现类似的功能。

  5. 集合操作的高阶函数:
    Kotlin 的标准库提供了丰富的高阶函数,如 mapfilterreduce 等,可以直接在集合上使用 lambda 表达式,例如:

    1
    2
    val numbers = listOf(1, 2, 3, 4, 5)
    val evenNumbers = numbers.filter { it % 2 == 0 }

    在 Java 中,需要使用传统的循环或者使用 Stream API 来实现类似的功能。

  6. 函数式编程支持:
    Kotlin 对函数式编程提供了更好的支持,例如函数合成、柯里化等特性,使得函数组合更加方便。在 Java 中,这些功能可能需要借助第三方库或者手动实现。

这些例子展示了 Kotlin 中 lambda 表达式的一些特性在 Java 中的对应情况,显示了 Kotlin 在函数式编程和 lambda 表达式方面的优势。

1
2
3
4
5
fun addClickListener(callback:(WaveAnimationView) -> Unit){
setOnClickListener{
callback(this)
}
}
Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2023-2025 Annie
  • Visitors: | Views:

嘿嘿 请我吃小蛋糕吧~

支付宝
微信