1、Lambda 入门
1.1、从一个需求例子开始
需求:对一个整型数组进行排序,使用冒泡排序,如何进行优化,如何灵活实现排序规则。
1.2、方案一:在 sort 方法中增加一个参数控制排序规则
/**
*
* @param datas 数组
* @param sortType true 表示升序,false 表示降序
*/
public static void sort(int[] datas,boolean sortType){
for (int i = 0; i < datas.length; i++) {
for (int j = i+1; j < datas.length; j++) {
if(sortType){
//两两元素比较,前一个数大于后一个数则交换位置,最终实现升序
if(datas[i]>datas[j]){
//交换位置
int temp = datas[i];
datas[i] = datas[j];
datas[j] = temp;
}
}else{
//两两元素比较,前一个数小于后一个数则交换位置,最终实现降序
if(datas[i]<datas[j]){
//交换位置
int temp = datas[i];
datas[i] = datas[j];
datas[j] = temp;
}
}
}
}
}
问题:代码冗余严重!!
1.3、方案二:将元素比较的代码抽取到一个接口中
public interface SortCompare {
boolean compare(int a,int b);
}
public class SortCompareImpl implements SortCompare{
@Override
public boolean compare(int a, int b) {
//前一个是否大于后一个数
return a>b;
}
}
就可以使用一个方法实现不同的排序规则,方法的代码不再繁琐
//增加一个参数,就是排序比较接口的对象
public static void sort(int[] datas,SortCompare sortCompare){
for (int i = 0; i < datas.length; i++) {
for (int j = i+1; j < datas.length; j++) {
if(sortCompare.compare(datas[i],datas[j])){
//交换位置
int temp = datas[i];
datas[i] = datas[j];
datas[j] = temp;
}
}
}
}
问题:为了封装一行比较代码就需要定义多个接口实现类,这个过程繁琐的。
1.4、方案 3:匿名内部类实现比较接口
//匿名内部类,实现升序
sort(datas, new SortCompare() {
@Override
public boolean compare(int a, int b) {
return a>b;
}
});
优点:不用再定义独立的类,代码逻辑也很简洁。
问题:封装一行比较的代码,但是需要定义一些无关的代码。
1.5、Lambda表达式实现
sort(datas,(int a,int b)->a<b);//降序
Lambda 是一种匿名内部类的简化形式。
Lamdba 表达式可以使编程过程更高效。
语法: (参数,参数) –> {方法体代码}
1、 参数列表
2、 箭头
3、 方法体代码
2、Lambda语法规则
2.1、Lambda 表达式语法格式
Lambda 是一种匿名内部类的简化形式。
注意:不是所有的匿名内部类可以使用 Lambda 表达式来实现的。
Lambda 表达式只能代替有一个抽 象方法的接口所对应的匿名内部类。
Lamdba 表达式可以使编程过程更高效。
语法: (参数,参数) –> {方法体代码}
1、 参数列表
2、 箭头
3、 方法体代码
2.2、Lambda表达式测试
1、 无参、无返值 2、 有参、有返回值
定义 Fun1、Fun2 两个接口。
2.3、函数式接口
什么是函数接口?
只有一个抽象方法的接口叫函数式接口。
Lambda 表达式只能赋值给函数式接口的引用变量。
2.4、 简化形式
1、 参数类型可以省略。 注意:如果有多个参数,参数类型要么都省略,要么都不省略
2、 如果函数体只有一条语句可以省略大括号和分号及 return
3、 如果参数只有一个,可以省略参数列表的小括号
2.5、函数式编程思想
函数式编程和面向对象编程对比:
1) 核心不同
面向对象编程,它的核心是对象。
函数式编程,它的核心是函数。方法就是函数。
2) 目标不同
面向对象,是需要分析对象的属性,对象的行为。
函数式编程,首先确定要做什么事,也就确定了函数的功能。
3、java.util.function 函数式接口
3.1、认识函数式接口
函数式接口,是只有一个抽象方法的接口叫函数式接口。
比较常用的几个接口
接口 | 抽象方法 | 说明 |
Function<T, R> |
R apply(T t); |
接收一个参数,有返回值,接收参数类型T,返回参数类型R |
Consumer<T> |
void accept(T t); |
接收一个参数,无返回值 |
Supplier<T> |
T get(); |
没有参数,有返回值 |
Predicate<T> |
boolean test(T t); |
接收一个参数,返回布尔值 |
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
@FunctionalInterface 它是一个注解,标记在这个接口上就表示该接口是一个函数式接口。
在一个函数式接口中不标记@FunctionalInterface,也是可以的!
3.2、Function 函数式接
@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));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
andThen 方法: 原型:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); }
参数:也是一个 Function 函数式接口类型
结果:也是一个 Function 函数式接口类型
内容:return (T t) -> after.apply(apply(t));
//测试andThen方法
public static void testAndThen(){
//前边的一个操作
Function<String,String> before = s1 -> "www."+s1;
//后边的一个操作
Function<String,String> after = s1 -> s1+".com";
//调用before的andThen方法,实现前后执行两个操作
String pbteach = before.andThen(after).apply("pbteach");
System.out.println(pbteach);
}
3.3、BiFunction 函数式接口
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
apply 方法有两个参数,一个返回值
public class TestBiFunction {
public static void main(String[] args) {
//求两个数的和
BiFunction<Integer, Integer, Integer> fun1 = (x, y) -> x + y;
//求两个数的乘积
BiFunction<Integer, Integer, Integer> fun2 = (x, y) -> x * y;
//调用方法
System.out.println(operate(fun1, 1, 2));
System.out.println(operate(fun2, 1, 2));
//简化
System.out.println(operate((x, y) -> x * y, 1, 2));
}
//实现两个数运算
public static int operate(BiFunction<Integer, Integer, Integer> fun, int n1, int n2) {
return fun.apply(n1, n2);
}
}
3.4、Consumer 接口
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
Consumer 是一个消费接口 ,接收一个数据进行消费。
public class TestConsumer {
//一个整型数组
static int[] datas = {1,2,3,4,5,6,7};
//静态变量
static int sum = 0;
public static void main(String[] args) {
//定义一个Consumer接口对象
Consumer<Integer> fun1 = n -> System.out.println(n);
//判断奇偶
Consumer<Integer> fun2 = n ->{
if(n % 2 == 0){
System.out.println(n + "是偶数");
}else{
System.out.println(n + "是奇数");
}
};
//求累加和
Consumer<Integer> fun3 = n -> sum+=n;
//调用consumer
consumer(fun1,datas);
System.out.println("===========判断奇偶=========");
consumer(fun2,datas);
System.out.println("===========求累加和=========");
consumer(fun3,datas);
System.out.println("sum="+sum);
}
//消费方法
public static void consumer(Consumer<Integer> fun,int[] datas){
for (int i = 0; i < datas.length; i++) {
//调用accept方法进行消费,一次消费一个数据
fun.accept(datas[i]);
}
}
}
3.5、Predicate 函数式接口
@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> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
public class TestPredicate {
//一个整型数组
static int[] datas = {1,2,3,4,5,6,7};
public static void main(String[] args) {
//定义Lambda表达式,实现奇偶判断
Predicate<Integer> filter1 = n ->{
if(n % 2 ==0){
return true;
}else{
return false;
}
};
System.out.println("========奇偶判断=======");
int[] datas1 = doFilter(filter1, datas);
for (int i = 0; i < datas1.length; i++) {
System.out.println(datas1[i]);
}
System.out.println("========大于5的数=======");
//定义一个过虑器
Predicate<Integer> filter2 = n -> n>5;
int[] datas2 = doFilter(filter2, datas1);
for (int i = 0; i < datas2.length; i++) {
System.out.println(datas2[i]);
}
}
/**
* 对数据进行过虑
* @param filter 过虑器
* @param datas 待过虑的数组
* @return 保留数据的数组
*/
public static int[] doFilter(Predicate<Integer> filter,int[] datas){
//新建一个数组,用于存储保留数据
int[] result = new int[datas.length];
//保留数组的下标
int n=0;
for (int i = 0; i < datas.length; i++) {
if(filter.test(datas[i])){
//保留数据
result[n++] = datas[i];
}
}
//最后只返回一个正好包含保留数据容量的数组
//参数1:原始数组,参数2:需要保留数组长度
int[] newResult = Arrays.copyOf(result, n);
return newResult;
}
}
3.6、Supplier 函数接口
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Supplier 接口表示供应,和 Consumer 相反,Supplier 是一个生产接口,T get()没有参数,只有结果。
public class TestSupplier {
static long sequence = 0;
//生成不同类型的编号,比如:订单号、课程编号等,采用方法是:随机,顺序编号,时间编号
//定义随机数的生成器,Math.abs获取绝对值
static Supplier<Long> randomNum = () -> Math.abs(new Random().nextLong());
//顺序编号
static Supplier<Long> sequenceNum = () -> ++sequence;
//时间编号,获取当前时间纳秒值
static Supplier<Long> timeNum = () -> System.nanoTime();
public static void main(String[] args) {
System.out.println("=========生产随机数========");
for (int i = 0; i < 10; i++) {
System.out.println(randomNum.get());
}
System.out.println("=========生产顺序编号========");
for (int i = 0; i < 10; i++) {
System.out.println(sequenceNum.get());
}
System.out.println("=========生产时间编号========");
for (int i = 0; i < 10; i++) {
System.out.println(timeNum.get());
}
}
}
4、方法引用
4.1、方法引用入门
Lambda 表达式要对应一个函数式接口,具体依据函数式接口中的抽象方法来定义
Lambda。使用 Lambda 表达式进行函数式编程,使用 Lambda 表达式所定义是一个函数,在 Java 中方法就是函数。
什么是方法引用?
Java 中的方法可以当作一个引用作为函数来使用,它叫方法引用。方法引用是一种将方法应用于函 数式编程的方法,方法引用可以代替 Lambda。
简写为: 将 Java 中方法作一个引用,代替 Lambda 表达式。
1、 定义一个函数式接口
public interface Fun2 {
//有参数有返回值
int handler(int a,int b);
}
2、 使用 Lambda 表达式测试
//使用 Lambda 表达式测试
doFun2((x,y)->{
int z = x * y;
return z;
});
依据函数式接口中的抽象方法来定义 Lambda 表达式.
3、使用方法引用代替 Lambda 表达式
1) 定义一个方法
如何定义一个可以代替上边这个 Lambda 表达式的方法呢? 依据函数式接口中的抽象方法来定义 一个可以代替 Lambda 表达式的方法。
public class Utils {
//实现两个数的乘积
public static int product(int a,int b){
return a * b;
}
}
2) 可以使用方法引用来代替 Lambda
//使用方法引用
doFun2(Utils::product);
Utils::product 表示引用了 Utils 类中的 product 方法,::符号为引用运算符,它所在的表达式被称为 方法引用。
小结: 一个函数式接口,可以使用 Lambda 表达式来实现,也可以使用方法引用实现。 最关键的是知道如何定义一个 Lambda 和方法引用。
一个方法引用所引用的方法必须和函数式接口中的抽象方法的参数、返回值类型一致
4.2、静态方法引用
类型 | 语法 | 对应的lambda表达式 |
静态方法引用 | 类名::staticMethod | (args) -> 类名.staticMethod(args) |
特定对象实例方法引用 | instance::instanceMethod | (args) -> instance.instanceMethod(args) |
任意对象实例方法引用 | 类名::instanceMethod | (instance,args) -> instance.instanceMethod(args) |
构造方法引用 | 类名::new | (args) -> new 类名(args) |
1、 确定函数式接口
@FunctionalInterface
public interface BiFunction<T, U, R> {
/**
* Applies this function to the given arguments.
*
* @param t the first function argument
* @param u the second function argument
* @return the function result
*/
R apply(T t, U u);
}
2、 使用方法引用
public class TestStaticFun {
public static void main(String[] args) {
//使用Lambda表达式实现
System.out.println(operate((x,y)->x+y,1,2));
//使用方法引用,求两个数的和
System.out.println(operate(Integer::sum,1,2));
//测试当函数式接口的抽象方法的返回值为void时,仍然可以使用一个返回值不为void的方法引用
Consumer<String> fun = TestStaticFun::toUpperCase;
fun.accept("www.pbteach.com");
}
//实现两个数的操作
public static int operate(BiFunction<Integer,Integer,Integer> fun,int n1,int n2){
return fun.apply(n1,n2);
}
/**
* 将字符串转大写
* @param s 输入一个字符串
* @return 转大写后的字符串
*/
public static String toUpperCase(String s){
System.out.println("s="+s);
//将字符串转在大写
return s.toUpperCase();
}
}
4.3、抽象方法为 void 的情况
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
根据前边的知识可知,要想一个方法引用和上边的 Consumer 对应上,这个方法的参数类型和返回 值类型要和函数式接口的抽象方法一致。void accept(T t);
所引用的方法不管是否有返回值都可以和函数式接口对应上。
使用上边的函数式接口来测试,用一个不为 void 返回值的方法引用,和 Consumer 接口对应上。 还是建议:定义一个方法引用,依据函数式接口中的抽象方法来定义,让引用方法的参数和返回值 类型与函数式接口中的抽象方法的参数和返回值一致!!!
4.4、特定实例方法引用
指: 引用某个对象的方法,叫特定实例方法的引用。
//特定实例方法引用,引用某个对象的实例方法
public static void test1(){
//创建一个对象
PbStudent s1 = new PbStudent();
s1.setNickname("攀博课堂");
//引用 s1 对象的 getNickname 方法,方法引用赋值给 Supplier 函数接口 ,它的 T get();抽象方
法与 getNickname 一致
Supplier<String> fun1 = s1::getNickname;
//获取学生昵称,fun1.get()相当于调用了 s1 对象的 getNickname
System.out.println(fun1.get());
//引用 s1 对象的 setNickname
Consumer<String> fun2 = s1::setNickname;
System.out.println("设置学生昵称");
fun2.accept("攀博课堂 www.pbteach.com");
System.out.println("学生新昵称:"+fun1.get());
}
4.5、任意对象实例方法引用
它和特定对象实例方法引用区别:
特定对象实例方法:
引用某个类型的某个对象的实例方法。
例如:s1::getNickname,表示引用 s1 对象的 getNickname 方法。
语法:实例名::实例方法名
任意对象实例方法:
引用某个类型的所有对象的实例方法。
例如:PbStudent::getNickname,表示引用 PbStudent 这个类型的所有对象的 getNickname 方法。
语法:类名::实例方法名
任意对象实例方法引用规则:
1、固定有第一个参数,是实例的类型。
2、 从第二个开始才是实例方法的参数。
//任意实例方法引用
public static void test2(){
//引用 PbStudent 类型的任意对象的 setNickname 方法
BiConsumer<PbStudent, String> setNickname = PbStudent::setNickname;
//引用 PbStudent 类型的任意对象的 getNickname 方法
Function<PbStudent,String> getNickname = PbStudent::getNickname;
//通过 setNickname 方法引用设置昵称
PbStudent s1 = new PbStudent();
setNickname.accept(s1,"攀博课堂");
//通过 getNickname 方法引用获取昵称
String nickName = getNickname.apply(s1);
System.out.println(nickName);
// System.out.println(s1.getNickname());
}
一个案例: 如何使用任意对象实例方法引用,引用下边的 String 类下的 charAt 方法。
实现:获取字符串某个索引位置上的字符。
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
分析:charAt 方法有一个参数,一个返回值。 如果要使用任意对象实例方法引用,找一个函数式接口,它的抽象方法,参数有两个,必须有返回值。
//测试任意对象实例方法的引用,测试 String 类型下的 charAt 方法
public static void test3(){
//引用 String 类型下的 charAt 方法
BiFunction<String,Integer,Character> fun = String::charAt;
System.out.println(fun.apply("www.pbteach.com",4));
System.out.println(fun.apply("www.pbteach.com",5));
}
4.6、构造方法引用
1) 无参构造方法 语法:类名::new。
引用的无参构造方法,对应哪个函数式接口,这个函数式接口的抽象方法没有参数,必须有一个返 回值。
使用 Supplier 函数式接口。
//无参构造方法引用
public static void test1(){
//引用 PbStudent 类型的无参构造方法
Supplier<PbStudent> fun = PbStudent::new;
//创建学生对象
PbStudent pbStudent = fun.get();
System.out.println(pbStudent);
}
2)有参构造方法
语法:类名::new 引用有参构造方法,根据函数式接口确定要引用哪个有参构造方法。
//有参构造方法引用
public static void test2(){
//引用 PbStudeng 类型下的 pbstudent(String nickname)构造方法。
Function<String,PbStudent> fun = PbStudent::new;
//通过方法引用调用有参构造方法
PbStudent pbStudent = fun.apply("www.pbteach.com");
System.out.println("昵称:"+pbStudent.getNickname());
//引用 PbStudeng 类型下的 PbStudent(String id,String nickName)
BiFunction<String,String,PbStudent> fun2 = PbStudent::new;
PbStudent pbStudent1 = fun2.apply("100", "攀博课堂");
System.out.println(pbStudent1.getId()+" "+pbStudent1.getNickname());
}