JAVA学习12——函数式接口、函数式编程、常用函数式接口(Supplier、Function、Predicate、Consumer)

1.函数式接口概述

函数式接口在java中指的是,有且仅有一个抽象方法的接口(但可以有其他静态方法、默认方法、私有方法等)。函数式接口适用于函数式编程场景的接口,Java中就是Lambda。
tips:(1)”语法糖“,指使用更加方便,原理不变的代码语法;(2)@Override注解的作用,是检查方法是否为重写方法,是则编译成功,否则编译失败。 (3)同样,可以在函数式接口前,添加一个@FunctionalInterface注解,其作用是可以检测接口是否是一个函数式接口,是的话就编译成功,否则编译失败。

函数式接口的格式

修饰符 interface 接口名称 {
	public abstract 返回值类型 方法名称(可选参数信息);
	//其他非抽象方法内容
}

1.1 函数式接口使用

/*
	函数式接口的使用:一般可以作为方法的参数和返回值类型
*/
public class Demo {
	//定义一个方法,参数使用函数式接口MyFunctionalInterface
	public static void show(MyFunctionalInterface myInter) {
		myInter.method();
	}

	public static void main(String[] args) {
		//方式一:调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象
		show(new MyFunctionalInterfaceImpl());

		//方式二:调用show方法,方法的参数是一个接口,所以我们可以传递接口的匿名内部类
		show(new MyFunctionalInterface() {
			@Override
			public void method() {
				System.out.println(“使用匿名内部类重写接口中的抽象方法");
			}
		});

		//调用show方法,方法的参数是一个函数式接口,所以可以用Lambda表达式
		show(()->{
			System.out.println("使用Lambda表达式重写接口中的抽象方法");
		});

		//简化lambda表达式
		show(()->System.out.println("使用Lambda表达式重写接口中的抽象方法"));
	}

	public static void main(String[] args) {
		sho
	}
}

注意,匿名内部类创建时,编译时会在当前目创建一个XXX$1.class对应匿名内部类中的内容和方法;而如果使用Lambda表达式时,则不会在编译时创建这么一个文件,因此效率要高一些。

2.函数式编程

2.1 案例一:日志案例

public class Demo {
	public static void showLog(int level, String message) {
		if(level == 1) {
			System.out.println(messgae);
		}
	}
	
	public static void main(String[] args) {
		 String msg1 = "Hello";
		 String msg2 = "World";
		 showLog(1,msg1+msg2);
	}
}

上面的代码,存在性能浪费的问题。showLog方法中,传递的第二个参数String message,是一个拼接后的字符串,如果此时,level等级不为1,那么不会执行showLog中的主要内容,因此String就浪费了。

上面这种浪费,是可以用Lambda表达式优化的。可优化的原因,是因为Lambda表示式有延迟加载的特点。优化代码如下。


//先定义一个函数式接口
@FunctionalInterface
public interface MessageBuilder {
	public abstract String builderMessage();
}

//
public class Demo {
	//优化在于,第二个参数改为函数式接口
	public static void showLog(int level, MessageBuilder mb) {
		if(level == 1) {
			System.out.println(mb.builderMessage());
		}
	}
	public static void main(String[] args) {
		 String msg1 = "Hello";
		 String msg2 = "World";
		 //调用showLog方法,参数MessageBuilder是一个函数式接口,可以选择传递Lambda表达式
		 showLog(1,()->{
		 	System.out.println("不满足条件不执行");
		 	return msg1+msg2;
		 });
		 /*
			使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中;
			只有满足level=1, 才会调用MessageBuidder中的方法,否则不执行。
			所以不存在性能浪费。  
		*/
	}
}

2.2 案例二:函数式接口作为方法的参数

例如java.lang.Runnable接口就是一个函数式接口,假设一个startThread方法使用该接口作为参数,那么可以使用lambda表达式进行传参。这种情况实际上和Thread类的构造方法参数为Runnable,没有本制区别。

public class Demo {
	public static void startThread(Runnable  run) {
		new Thread(run).start();
	}

	public static void main(String[] args) }
		startThread(new Runnable() {
			@Override
			public void run() {
				System.out.println("测试");
			}
		});

		//Lambda表达式
		startThread(()->{
			System.out.println("测试");
		});

		//优化Lambda
		startThread(()->System.out.println("测试"));
	}
}

2.3 案例三:函数式接口作为返回值类型

如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一个方法来获取一个java.util.Comparator接口类型的对象,作为排序器的参数时,就可以调用此方法获取。

public class Demo {
	public static Comparator<String> getComparator() {
		return new Comparator<String>() {
			@Override
			public int compare(String o1,String o2) {
				//按照字符串的降序顺序
				return o2.length() - o1.length();
			}
		}

		//用lambda表示式
		return (String o1, String o2)-> {
			return o2.length() - o1.length();
		}

		//继续优化Lambda
		return (o1,o2) -> o2.length() - o1.length();
	}
	public static void main(String[] args) {
		String[] arr = {"aaa","v"};
		//作为排序器的参数时,就可以调用此方法获取
		Arrays.sort(arr,getComparator());
	}
}

3. 常用的函数式接口

JDK提供了大量常用的函数式接口,以丰富Lambda的典型使用场景,它们主要在java.util.function包中被提供。

3.1 Supplier接口

Supplier接口,被称为生产型接口,指定接口的泛型是什么类型,那么接口中get方法就会产生什么类型数据。基本使用如下:

public class Demo {
//定义一个方法,方法的参数传递Supplier<T>接口,泛型指定
//String,get方法就会返回一个String

	public static String getString(Supplier<String> sup) {
		return sup.get();
	}

	public static void main(String[] args) {
		String s = getString(() -> {
			return "Lucy";
		});
		//优化lambda
		String s2 = getString(()->"Lucy");
	}
}

练习:求数组元素最大值
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用java.lang.Integer类

public class Demo {
	//定义一个方法,用于获取int类型数组中元素的最大值,方法的参数传递Supplier接口
	public static int getMax(Supplier<Integer> sup) {
		return sup.get();
	}
	public static void main(String[] args) {
		int[] arr = {100,0,20,2};
		//调用getMax方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
		int max = getMax(()->{
			int max = arr[0];
			for(int i :arr) {
				if(i>max) {
					max = i;
				}
			}
			return max;
		}
	}
}

3.2 Consumer接口

与Supplier接口相反,这是消费一个数据。Consumer接口中包含抽象方法void accept(T t),消费一个指定泛型的数据,至于具体怎么消费使用,需要自定义(输出,计算)。

Consumer中的accept没有返回值,有返回值的是Supplier中的get方法。

public class Demo {
	public static void method(String name, Consumer<String> con) {
		con.accept(name);
	}

	public static void main(String[] args) {
		method("赵丽颖",(String name) -> {
			String reName = new StringBuffer(name).reverse().toString();
			Sytem.out.println(reName);
		}
	}
}

Consumer接口中,还有一个默认方法,andThen。如果一个方法的参数和返回值全是Consumer类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再进行一个操作,实现组合(实际中,是对要进行操作拆分的操作,使用andThen这一个功能)。
例如:

Consumer<String> con1;
Consumer<String> con2;
String s = "hello";
con1.accept(s);
con2.accept(2);
//上面代码,可以用andThen方法简化
con1.andThen(con2).accept(s); //谁在前,谁先消费

实例:

public class Demo{
	public static void method(String s, Consumer<String> con1, Consumer<String> con2) {
		con1.accept(s);
		con2.accept(2);
		//或者用下面的andThen
		con1.andThen(con2).accept(s);
	}

	public static void main(String[] args) {
		method("Hello",
						(t) - > {
							System.out.println(t.toUpperCase());
						},
						(t) -> {
							System.out.println(t.toLowerCase());
						}
					);
	}
}

下面是一个练习,内容是格式化打印信息,要求打印姓名的动作作为i的一个Consumer接口的Lambda实例,打印性别作为第二个,然后将两个Consumer接口拼接在一起。

public class Demo {
	public static void printInfo(String arr, Consumer<String> con1, Consumer<String> con2) {
		for(String message:arr) {
			con1.andThen(con2).accept(message);
		}
	}

	public static void main(String[] args) {
		String[] arr = {"Lucy, W", "Jordan,M"};
		printInfo(arr,(message) -> {
			String name = message.split(",")[0];
			System.out.println("姓名:"+name);
		}, (message) ->{
			String age = message.split(",")[1];
			System.out.println(". 年龄:" + age);
		});
	}	
}

3.3 Predicate接口

有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值得结果,就需要用Predicate接口。

Predicate接口中包含一个抽象方法,boolean test(T t),用于条件判断场景。

public class Demo {
	public static boolean checkString(String s, Predicate<String> pre) {
		return pre.test(s);
	}

	public static void main(String[] args) {
		String s = "abcd";

		boolean b = checkString(s, (String str) -> {
			return str.length()>5;
		});
		//lambda优化简写
		boolean b = checkString(s,str->str.length()>5);
	}
}

注意,这里再记录一下lambda表达式省略得条件。可以省略的内容:
1)(参数列表):括号中,参数列表的数据类型,可以省略不写
2)(参数列表):括号中的参数如果只有一个,那么类型和()都可以省略;
3)(一些代码):如果{}中的代码只有一行 ,无论是否有返回值,都可以省略({},return,分号)
注意:要省略{},则return和分号必须一起省略。

Predicate接口中,还有一个默认方法and,它得目的是将两个Predicate条件使用与逻辑连接起来,实现并且得效果。

/*
需求:判断一个字符串,有两个判断得条件:
1.判断字符串得长度是否大于5;
2.判断字符串是否包含a;
*/
public class Demo {
	public static boolean checkString(String s, Predicate<String> pre1, Predicate<String> pre2) {
		//return pre1.test(s) && pre2.test(s);
		return pre1.and(pre2).test(s);
	}

	public static void main(String[] args) {
		String s = "abcdef";
		boolean b = checkString(s, (String str) - > {
			return str.length() > 5;
		}, (String str) -> {
			return str.contains("a");
		});
		System.out.println(b); 
	}
}

Predicate接口中,还有两个默认方法or和negate。
or表示或的关系;negate表示取反(非)的关系。

//基本内容和and方法得示例代码相似
pre1.or(pre2).test(s);

//与return !pre.test(s);相同
return pre.negate().test(s);

综合练习,集合信息的筛选,通过Predicate接口的拼装,将符合要求的字符串筛选到集合ArrayList中,需要满足是女生,姓名为4个字。

//有两个判断条件,所以需要连个Predicate接口
public class Demo {
	public static ArrayList<String> filter(String[] arr, Predicate<String> pre1, Predicate<String> pre2) {
		ArrayList<String> list = new ArrayList<>();
		for(String s : arr) {
			boolean b = pre1.and(pre2).test(s);
			if(b) {
				list.add(s);
			}
		}
		return list;
	}

	public static void main(String[] args) {
		String[] array = {"Lucy,W", "Jordan,M"};
		ArrayList<String> list = filter(array,(String s) -> {
			return s.split(",")[1].equals("W");
		}, (String s) -> {
			return s.split(",")[0].length() == 4;
		});
	}
}

3.4 Function接口

java.util.function.Function<T,R>接口,用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者为后置条件,即一个转换。

//Function接口中,最主要的抽象方法为R apply(T t),根据类
//型T的参数,获取类型R的结果,例如将String类型转换为Integer类型。

public class Demo{
	public static void change(String s, Function<String,Integer> fun) {
		Integer in = fun.apply(s);
		int in = fun.apply(s); //自动拆箱 Integer->int
		System.out.println(in);
	}

	public static void main(String[] args) {
		String s = "1234";
		change(s, (String str) -> {
			return Integer.parseInt(str);
		});
		//简化lambda
		change(s,str -> Integer.ParseInt(str));
	}
}

Function接口中有一个默认andThen方法,用来进行组合操作。例如我们要把String类型转为Integer类型,然后把转换后的结果+10,之后再把新的Integer类型,转为String。

public class Demo {
	public static void change(String s, Function<String,Integer> fun1, Function<Integer, String> fun2) {
		String ss = fun1.andThen(fun2).apply(s);
		System.out.println(ss);
	}

	public static void main(String[] args) {
		String s = "123";
		change(s,(String str) -> {
			return Integer.parseInt(str)+10;
		}, (Integer i) -> {
			return i+"";
		});
		//对上面的Lambda表达式进行优化
		change(s,str->Integer.parseInt(str)+10, i->i+"");
	}
}

综合案例,自定义函数模型拼接。将字符串年龄部分,得到字符串,将字符串转换为int类型,再将int的数字夹100,得到结果。因此需要三个Function接口。

public class Demo {
	public static int change(String s, Function<String,String> fun1, Function<String, Integer> fun2, Function<Integer,Integer> fun3) {
		return fun1.andThen(fun2).andThen(fun3).apply(s);
	}

	public static void main(String[] args) {
		String str = "Lucy,22";
		change(str,(String s) -> {
			return s.split(",")[1];
		}, (String s)->{
			return Integer.parseInt(s); 
		} ,(Integer i) -> {
			return i+100;
		});
		System.out.println(num);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值