函数式接口
概念
函数式接口在JAVA中指的是:有且仅有一个抽象方法的接口就称为函数式接口。(接口当中可含有其他方法:默认、静态、私有。。。。)
函数式接口,适用于函数式编程的,在Java当中的函数式编程体现在Lambda,所以函数式接口就是用来服务lambda表达式,只有确保接口当中有且仅有一个抽象方法,那么在Java中Lambda才能顺利的进行推导。
备注:“语法糖"是指使用更加便利方便,但是原理不变的代码语法。就比如遍历集合的时候使用for -each语法,其实底层使用的事迭代器,这便是"语法糖”。
格式:
只有确保接口当中有且仅有一个抽象方法即可:
修饰符 interface InterfaceName{
//只能定义一个抽象方法
public abstract 返回值类型 方法名称(参数列表);
//还可以定义其他的非抽象方法
}
示例
public interface FunctionInterfaceOne{
public abstract void show01();
public default void show02(){
//.....
}
//void show03();此时就不是了,有且仅有一个抽象方法,才能成为函数式接口
}
@FunctionalInterFace注解(可确保接口是函数式接口(是:编译通过,否:编译不通过))
与@Override注解作用类似,Java 8 中专门为函数式接口引入一个新注解@FunctionalInterFace,该注解主要定义在接口上。一旦在接口上使用该注解,编译期将会强制检查该接口是不是一个函数式接口,该接口中是不是有且仅有一个抽象方法,如果不是,编译报错
@FunctionalInterFace
public interface FunctionInterfaceOne{
//定义一个抽象方法
void method();
//void show();
default void show02(){
}
}
用途:
对于自定义的函数式接口:一般作为方法的参数和返回值使用的。
函数式编程
能够在兼顾java的面向对象的特性基础上,使用通过lambda表达式与方法引用,为开发者打开函数式编程的大门。
Lambda表达式的延迟加载
有些场景代码运行执行后,结果不一定会被使用到,从而造成性能的浪费,而lambda表达式是延迟执行的,正好可以解决此问题,提升一定性能。
代码:
备注:实际上使用内部类也可以达到这样的效果,只是将代码操作延迟到另外一个对象当中通过调用方法来完成。后面的代码执行取决于前面的条件判断的结果。
使用Lambda作为方法的参数和返回值
在Java当中,lambda表达式是作为匿名内部类的替代品,如果一个方法的参数是一个函数式接口类型,那么可以使用Lambda表达式进行替代。
java.lang.Runnable接口就是一个函数式接口
代码如下:
public class DomeLambda{
//定义一个开启线程的方法
public static void startThread(Runnable r){
new Thread(r).start();
}
public static void main (String[] args){
startThread(() -> {
System.out.println("开启一个新线程,线程任务被执行了!")
});
startThread(() ->System.out.println("开启一个新线程,线程任务被执行了!");
}
}
如果一个方法的返回值类型是一个函数式接口,那么我们可以直接使用一个Lambda表达式
java.util.Comparator接口是一个函数个表达式
代码如下:
public class Dome02Lambda{
//定义一个方法,方法的返回值式一个函数式接口类型Comparator
public static Comparator<String> createComparator(){
//返回值就是一个函数式接口
return new Comparator(){
@Override
public int compare (String o1,String o2){
//自定义比较的规则,升序降序
//字符串长度
return 01.length-o2.length();//升序
}
}
//使用lambda
return(o1,o2)->01.length-o2.length();
}
public static void main(String[] args){
String[] strs={"aaa","a","ahgfgsj","gggg"};
Arrays.sort(strs,createComparator());
System.out.println(Arrays.toString(strs));//{"a","aaa","gggg","ahgfgsj"}
}
}
常用的函数式接口
JDK提供了大量常用的函数式接口,丰富Lambda表达式的使用场景。他们主要在java.util.function包中被提供。
Supplier接口
java.util.function.Supplier接口,该接口有且仅有一个无参的方法:T get()。用来获取一个泛型参数指定类型的对象数据。由于该接口是一个函数式接口,所以我们可以使用Lambda表达式来操作它。
Supplier接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get()方法就会生产什么类型的数据。
代码
//定义一个方法,方法参数传递一个Supplier<T>接口,泛型指定
public static String getString(Supplier<String> sup){
return sup.get();
}
//定义一个方法,方法的参数传递一个Supplier<T>接口,
public static int getNum(Supplier<Integer> sup){
return sup.get();
}
public static void main(String[] args) {
//调用getString方法,方法的参数传递Supplier<T>是一个函数式接口,那么我们就可以使用Lambda
String str = getString(() -> {
return new String("你好java");
});
System.out.println(str);
int arr[] = {10,20,30,5,7,8};
int maxNum = getNum(() -> {
//求出最大值
int max = arr[0];
for (int i : arr) {
//判断
if (max < i) {
max = i;
}
}
return max;
});
//输出最大值
System.out.println(maxNum);//50
}
Consumer接口
java.util.function.Consumer刚好和Supplier相反,他不是用来生产一个数据的,而是用来消费一个数据,数据类型由泛型来指定。
accept方法
意思就是消费一个指定泛型的数据。
代码如下:
//accept()方法
// 消费一个指定类型的数据
/*
Consumer接口是一个消费接口,泛型指定什么数据类型,使用accept方法就消费什么样的类型
至于具体怎么消费(使用),需要自定义(统计,求和,输出。。。)
*/
//定义一个方法,方法的参数传递一个Consumer<String>接口,传递一个字符串变量
public static void consumer (Consumer<String> con,String str){
//使用消费型接口对象,消费传递的字符串值
con.accept(str);
}
public static void main(String[] args) {
//来调用消费方法consumer,Consumer<String>接口是一个函数式接口,所以可以使用Lambda表达式
consumer(name->{
//把里面的abcdefg字符串改为大写输出消费的规则自定义
String s = name.toLowerCase();
//翻转字符串
String s1 = new StringBuffer(s).reverse().toString();
System.out.println(s1);//GFEDCBA
},"abcdefg");
}
默认方法:andThen
如果一个方法的参数和返回值全都是Consumer类型,那么就可以实现这样的效果:消费数据的时候,首先先做一个消费的操作,再做一个消费的操作。实现组合。可以通过Consumer接口当中的默认方法:andThen来实现。
代码如下:
//定义一个方法,方法的参数传递一个字符串和两个Consumer接口。Consumer接口的泛型指定为字符串
public static void consumers(String str , Consumer<String> con1,Consumer<String> con2){
/*con1.accept(str);
con2.accept(str);*/
//andThen连续消费 default Consumer<String> andThen
//先执行左边的Consumer--con1动作,andThen---->再次执行Consuner--con2动作
con1.andThen(con2).accept(str);
//规则 con1连接con2,先执行con1消费数据,在执行con2消费数据
}
public static void main(String[] args) {
//由于consumers方法的参数Consumer接口是一个函数式接口,可以使用Lambda表达式
consumers("java31-中国最棒-都是业界大佬",(nameone)->{
//消费规则
//截取传入的字符串
String sub = nameone.substring(0, 6);
System.out.println(sub);
},(nametwo)->{
//定义消费规则
String[] str = nametwo.split("-");
System.out.println(Arrays.toString(str));
});
}
通过查看源码得知:andThen方法不允许传入一个null对象,否则就会抛出空指针异常。
要想把两次消费的动作连接起来,需要传入两个Consumer接口,通过andThen方法实现一步一步执行消费动作。
Stream流
在java1.8中,由于Lambda表达式这种函数式编程,JDK引入了一个全新的概念"Stream流"。用于解决已有集合类库的一些弊端的。
给定你一些集合的数据
public class Dome01Stream{
public static void main(String[] args){
//构建一个集合
List<String> list = new ArratList<String>();
list.add("abc123");
list.add("aaa22");
list.add("bcd125");
list.add("abcd120");
list.add("bbb230");
//我需要字符串中包含1的元素取出来
List<String> list2 = new ArratList<String>();
for(String str:lis){
if(str.contains("1")){
list2.add(str);
}
}
//需要字符串长度不能超过6个的元素取出来
List<String> list3 = new ArrayList<String>();
for(String str:list2){
if(str.length()<=6){
list3.add(str);
}
}
//遍历查看最终想要的元素集合
for(String str:list3){
System.out.println(str)
}
}
}
当我们需要对集合中的元素进行操作的时候,总是需要对集合循环遍历,再次循环遍历…
一定需要这样做吗?不一定!
它只是用来找到你需要元素的一种方式,并不是一种目的,我只想遍历一次就能实现效果。目的就是想要取出想要的元素并且循环打印出来。以往的方式就是每次循环都需要重头开始遍历,下一次循环还是重头开始
Java1.8可以使用Lambda表达式来优化你遍历集合的方式。
代码如下:
public class Dome01Stream{
public static void main(String[] args){
//构建一个集合
List<String> list = new ArratList<String>();
list.add("abc123");
list.add("aaa22");
list.add("bcd125");
list.add("abcd120");
list.add("bbb230");
//我需要字符串中包含1的元素取出来
//需要字符串长度不能超过6个的元素取出来
//遍历查看最终想要的元素集合
list.stream()
.filter(str -> str.contains("1"))
.filter(str ->str.length()<=6)
.forEach(str ->System.out.println(str))
/*
abc123
bcd125
*/
}
}
一般我们把流式思想称之为"生产流水线"。