目录
一. 函数式接口
1.1 概念和用法
- 有且只有一个抽象方法的接口。(可以有默认方法和静态方法)
- 一个函数式接口通常用
@FunctionalInterface
注解来标记。这个标记不是强制性的,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。但它可以帮助编译器检查是否确实只定义了一个抽象方法。(给编译器用的)
代码示例如下
//函数式接口
@FunctionalInterface
public interface MyFunction {
void show();
}
如果写两个方法就会报错,如下图
接下来,用自己写的函数式接口作为参数,来测试一下。
注:代码中用到了匿名内部类和lambda表达式。其实在上面的代码中可以发现,执行的doSomething函数其实就是执行了show()方法,只是先执行的doSomething方法,然后再执行了匿名内部类中重写的show方法。
1.2 匿名内部类介绍
概念
在Java中创建类实例的一种方式,它不需要显式地定义类名。匿名内部类通常用于实现接口或继承类的情况,特别是当类只需要在一个地方使用时。
特点:
-
没有类名:匿名内部类没有定义自己的类名,它是直接在代码中通过实现接口或继承类的方式定义并实例化的。
-
访问外部类成员:匿名内部类可以直接访问其外部类的成员变量和方法,包括私有成员。这是因为匿名内部类持有一个指向其外部类实例的引用。
-
必须实现接口或继承类:匿名内部类必须实现一个接口或继承一个抽象类(如果该类有抽象方法)。如果实现的是接口,则必须实现接口中的所有抽象方法;如果继承的是类,则可以重写父类的方法。
-
编译时生成隐式类名:在编译时,Java编译器会为匿名内部类生成一个隐式的类名,这个类名通常是外部类名加上一个数字(如
Outer$1
),以便于在JVM中唯一标识这个类。 -
定义与实例化同时进行:匿名内部类的定义和实例化是同时进行的,这使得代码更加简洁。它通常用于创建只需要使用一次的类实例。
1.3 常用的函数式接口
Consumer(消费型接口)
接口中方法:void accept(T t):消费使用掉任意的一个数据。
作用:
- 当某个方法可以接收一个数据,并且处理这个数据,处理完成之后,不需要返回任何数据; 这个方法需要当 做数据来进行传递,就使用消费型接口.
- 以前只能传递要处理的数据,现在也可以传递处理数据的方式(指的就是传消费型接口的实现类对象)
- 以后如果定义一个函数式接口,抽象方法,只有一个参数,没有返回值,就没必要自己定义了,直接使用消费 型接口即可.
案例:
定义出一个方法功能, 客户预计消费500元现金, 每一个客户对于500元的消费都不同, 将客户的消费方式实现出来
1) 客户1 : 花了400元, 买了一把大宝剑
2) 客户2 : 花了300元, 买了一堆化妆品
3) 客户3 : 花了200元, 买了一双球鞋
4) .... 还有无限的客户有不同种消费方式
1.定义函数式接口
2.编写测试代码
注:为什么匿名内部类可以使用在{ }之后再调用方法?
当你创建一个匿名内部类时,实际上是创建了一个实现了特定接口或继承了特定类的新类。一旦实例被创建,它就可以使用所有的实例方法。在这种情况下,创建匿名内部类的实例后,你可以立即调用
accept
方法,因为你已经定义了这个方法。
Supplier(供给型接口)
概念
- 这个接口用于表示一个没有参数但是返回一个结果的操作。
Supplier
的主要作用是提供一个对象实例或者计算并返回一个值,但不接受任何输入参数。- 接口方法 T get():提供返回任意类型数据的规则
接口代码
@FunctionalInterface
public interface Supplier<T> {
T get();
}
作用
- 如果需要定义方法,可以生产一个需要的数据,又不需要提供参数; 这个方法需要当做数据来进行传递,那 么就可以使用供给型接口。
- 以前我们只能传递数据,现在可以传递生产数据的方式(指的就是供给型型接口的实现类对象)
使用场景
- 延迟加载:当需要延迟计算或获取某个值时,可以使用
Supplier
来实现。例如,只有当真正需要某个昂贵资源的时候才去创建它。 - 依赖注入:在依赖注入框架中,可以使用
Supplier
来提供一个对象的创建逻辑。 - 线程安全的对象创建:在多线程环境中,使用
Supplier
配合Optional
可以保证对象创建的线程安全性。
代码示例:
package com.its.ex.Supplier;
import java.util.function.Supplier;
public class supplierExample {
public static void main(String[] args) {
// 创建一个Supplier实例,相当于下面的代码,只是用lambda表达式简写了。
/*
* Supplier<String> supplier = new Supplier<String>(){
@Override
public String get() {
return "Hello, Supplier!";
}
};
* */
Supplier<String> supplier = () -> "Hello, Supplier!";
// 获取并打印结果
System.out.println(supplier.get());
}
}
案例介绍
定义出一个方法功能, 能给客户返回出一个ArrayList<Integer>类型容器, 容器中装几个数据由客户决定, 容器中承装的数据有什么规律, 由客户决定, 方法主要给客户返回一个符合客户要求的容器 。
1) 客户1: 5个数据,都是30-80之间的随机数
2) 客户2: 8个数据,1-100之间的随机偶数
3) ...
代码书写如下:
package com.its.ex.Supplier;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
public class supplierExample {
//现实客户要求List<Integer>类型容器,定义getList方法,返回值为List<Integer>,传递count参数,
//由用户决定容器中值的数量
public static List<Integer> getList(int count, Supplier<Integer> supplier) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < count; i++) {
list.add(supplier.get()); //通过供给型接口给值
}
return list;
}
public static void main(String[] args) {
/*
* 函数式接口supplier中get的方法用于返回一个30-80的随机数,
* 而getList的方法,用于返回获取多少个随机数
* */
//客户1 返回30-80之间的随机数
Supplier<Integer> supplier = () -> {
Random r = new Random();
int i = r.nextInt(51) + 30;
return i;
};
List<Integer> list = getList(5, supplier);
//list.forEach(System.out::println);
//客户2 返回1-100之间的随机偶数
List<Integer> list2 = getList(8, () -> {
Random r = new Random();
int f = 1;
while (f % 2 != 0) {
f = r.nextInt(100) + 1;
}
return f;
});
list2.forEach(System.out::println);
}
}
这个接口的精辟之处在于它可以根据不同用户的需求实现不同的方法,满足不同的需求,而且书写简单。
Function(函数型接口)
概念
- 这个接口用于表示一个接受某种类型的参数并产生结果的操作。
- 接口中的抽象方法 R apply(T t):提供把一个数据类型转换为另一个数据类型规则 参与转化为结果
- 两个默认方法:1.compose() : 括号中的内容在调用函数之前运行。 2.andThen() 括号中的内容在调用函数结束之后运行
- 一个静态方法static identity() ,方法直接返回传入的参数
接口代码
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
<V> Function<V, R> compose(Function<? super V, ? extends T> before);
<V> Function<T, V> andThen(Function<? super R, ? extends V> after);
static <T> Function<T, T> identity() {
return t -> t;
}
}
作用
- 作用:如果需要定义一个方法,接收一个数据,将数据进行处理,完成之后,还能返回一个结果; 这个方法还要当 做数据传递,就可以使用函数型接口
- 以前我们只能传递处理好之后的数据,或者将原始数据传入方法,现在可以传入数据,数据的处理方式.(指 的就是函数型型接口的实现类对象)
使用场景
- 数据转换:将一种类型的数据转换成另一种类型。
- 函数组合:可以通过
compose
和andThen
方法来组合多个函数。 - 数据处理流水线:在数据流处理中,多个
Function
可以串行连接起来形成复杂的数据处理流程。
案例1
定义两个函数型接口参数和返回值分别都是int类型,分别执行 参数+2 和 参数*2并返回
代码编写
package com.its.ex.function;
import java.util.function.Function;
public class FunctionExample {
public static void main(String[] args) {
//创建实例func1 参数 +2 第一个Integer参数的类型,第二个参数是返回值的类型
Function<Integer,Integer> func1 = (x) -> {
return x+2;
};
//创建实例func2
Function<Integer,Integer> func2 = (x) -> {
return x*2;
};
//测试apply
Integer result1 = func1.apply(10);//结果10+2
Integer result2 = func2.apply(10);//结果10*2
System.out.println(result1 + "<=====>"+result2);
}
}
案例2
定义一个方法功能, 根据整数x,计算出对应整数y, x数据由客户给出, y数据的计算方式根据客户要求决定
1) 客户1 : y为x的2倍
2) 客户2 : y与x相等
3) 客户3 : y为x的平方
代码演示
package com.its.ex.function;
import java.util.function.Function;
public class FunctionExample {
//传递一个整数x,y根据需要让客户去调整
public static int work(int x , Function<Integer,Integer> function){
return function.apply(x);
}
public static void main(String[] args) {
//客户1 y为x的2倍
int result1 = work(10, (x) -> x * 2);
//客户2 y与x相等
int result2 = work(10, (x) -> x );
//客户3 y为x的平方
int result3 = work(10, (x) -> x * x);
//参看结果
System.out.println(result1+ " "+ result2 + " "+result3);
}
}
注: 代码中()-> { }是lambda表达式的写法(用于函数式接口的简写),下面将会介绍。
案例3
定义一个方法功能, 根据字符串x,计算出对应整数y, 需要对y继续处理,需要得到一个z数据, 最终用户要的 数据是z.
1) 客户1 : x为"6" , 计算出x转换成整数后y, 得到y的2倍结果 12
2) 客户2 : x为"-2", 计算出x转换成整数后y, 得到y+1的结果
代码演示(与案例2类似,中间多了一个数据类型的转换)
package com.its.ex.function;
import java.util.function.Function;
public class FunctionExample {
//通用方法
public static int fun(String x, Function<Integer, Integer> function) {
//转换成Integer类型
int i = Integer.parseInt(x);
//调用apply()方法处理
return function.apply(i);
};
public static void main(String[] args) {
int res1 = fun("6", (x) -> x * 2);
System.out.println("res1 = " + res1);
int res2 = fun("-2", (x) -> x + 1);
System.out.println("res2 = " + res2);
}
}
Predicate 【断言型接口】
概念
- 这个接口用于表示一个接收某种类型的参数并返回布尔值的操作。
- 抽象方法boolean test(T t) 接口主要用于判断一个特定的条件是否满足,因此非常适合用于过滤操作和其他需要基于条件做出决策的场景。
- 包含几个默认方法,如
and
、or
和negate
,以及一个静态方法isEqual
。
接口代码
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> object.equals(targetRef);
}
}
作用
- 如果需要定义一个方法,接收一个数据,判断数据是否合法,返回一个boolean结果; 还可以把方法当做数 据传递,就可以使用断言型接口
- 以前我们只能传递过滤好的数据,而现在既可以传递原始数据,也可以传递过滤的条件(过滤条件其实就是 断言型接口的实现类对象)
使用场景
- 数据过滤:在集合操作中,
Predicate
常用于过滤元素。 - 条件组合:通过
and
、or
和negate
方法可以组合多个条件。 - 条件检查:用于对单个元素进行条件检查
案例
定义出一个方法, 需要客户提供一个容器ArrayListInteger>, 根据客户的需求, 将容器中符合条件的数 据筛选出来, 将筛选出的数据放置在新容器中返回给客户。
1) 客户1 : 要求容器中的所有数, 都能被2整除
2) 客户2 : 要求所有的数据都不大于100
代码演示
package com.its.ex.predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class PredicateExample {
/*
* 这个方法第第一个参数是定义客户的容器
* 第二个参数用于判断集合中的摸个元素是不是符合用户的规定
* */
public static List<Integer> filters(List<Integer> x, Predicate<Integer>
predicate) {
//定义新的集合
List<Integer> list = new ArrayList<>();
//断言判断是否符合规定(test方法根据用户的实际需要去写)
x.forEach(i -> {
boolean b = predicate.test(i);
if (b) {
list.add(i);
}
});
return list;
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(10086);
list.add(-10);
list.add(25);
list.add(33);
list.add(6);
list.add(90);
//客户1 : 要求容器中的所有数, 都能被2整除
List<Integer> list1 = filters(list, x -> {
return x % 2 == 0;
});
System.out.println(list1);
//客户2:要求所有的数据都不大于100
List<Integer> list2 = filters(list, x -> {
return x < 100;
});
// System.out.println(list2);
}
}
二. Lambda表达式
2.1 概念
- Lambda表达式是Java 8引入的一个重要特性,它提供了一种简洁的方式来表示一个函数或方法。Lambda表达式允许你在代码中直接定义一个匿名函数,而不必显式地声明一个新的类或实现一个接口。
- Lambda表达式的语法由三部分组成:
参数列表:位于圆括号内的参数列表。如果参数类型可以从上下文中推断出来,那么可以省略类型。
箭头符号:
->
分隔参数列表和函数体。函数体:执行的实际代码块。如果函数体只有一条语句,可以省略大括号;如果是一条表达式,还可以省略分号。
- Lambda表达式不会单独编译,本质是一个值,表 示匿名内部类的对象.。
2.2 用法
格式
(被重写的形参列表)-> {
被重写的方法体代码;
}
注:只能简化函数式接口的匿名内部类。
简写规则
- 参数类型可以省略不写,空参和多参数的参数列表的 括号()不能省略。
- 如果只有一个参数,参数类型可以省略,参数列表的()也可以省略。
- 如果有多个参数,参数类型要么同时省略,要么同时不省略。
- 如果lambda表达式中的方法体只有一行代码,可以省略大括号 {},同时不要分号 ;,如果这行代码是 return 语句,也必须去掉 return 不写。
代码演示:
2.3 方法引用
概念
- 方法引用是对函数式接口的实现类对象获取的另一种方式,都是用来体现函数式接口的实现类对象生成的新方式。
- 如果发现一个函数式接口的实现方法,也就是抽象方法的重写已经在其他类中有相同格式成员方法,或者静态方法的时候(其他类中成员方法格式或静态方法格式与抽象方法格式相同了),,那么我们就不用使用 lambda表达式提供实现了,直接引用其他类中的成员方法或者静态方法.这就叫做方法的引用。
使用前提:
- 必须是是一个函数式接口
- 必须有上下文推断
- 抽象方法"无返回值"的情况:只推断函数式接口中抽象方法参数部分和要引用的方法参数部分是否一致. 也就和要引用的方法返回值类型无关的.
- 抽象方法有返回值的情况:引用的方法参数列表要一致,返回值类型也要一致. (没有返回值看参数。有返回值看参数和返回值是否一致)
引用方法
- 静态方法引用: 类名 :: 静态方法名
- 成员方法引用: 对象名 :: 普通方法名
- 构造方法的引用: 类名 :: new
代码演示
【注】:其实就是一句话:直接引用现有方法而不是编写整个方法体。(现有的方法:java程序中已经写好的方法,不需要你重新写了,直接引用过来就好了。)
1.静态方法的引用
用lambda表达式实现这两个接口
再定义两个函数式接口,和一个普通类,在普通类中定义两个方法,一个静态,一个非静态
2.普通方法的引用
3构造方法的引用
2.4 匿名内部类和Lambda表达式的区别
标签:函数,接口,参数,Supplier,方法,public,表达式,Lambda From: https://blog.csdn.net/qq_62555748/article/details/140720555
- 共同点:
都是对函数式匿名实现
- 不同点:
- 匿名内部类可以单独存在, Lambda不能单独存在
- 匿名内部类会参与编译(生成字节码文件), Lambda不会,它仅仅是一个值