jdk8新特性之函数式编程Lambda

本文深入探讨函数式接口的概念,Lambda表达式的使用,及如何利用它们进行函数式编程。覆盖了常见函数式接口如Supplier、Consumer、Predicate和Function的详细解析与实战应用,展示Lambda在提升代码效率和性能方面的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、函数式接口的概念与定义

1.1函数式接口

1.2函数式编程

1.3 定义一个函数式接口

1.4 @FunctionalInterface注解

1.5 Lambda的表达式例子

1.6 Lambda引用值,而不是变量

1.6.1 匿名内部类

1.6.2 lambda表达式引用的是值而不是变量

二、函数式接口的使用

1.1作为方法的参数使用

1.1.1 简化lambda表达式

三、函数式编程

3.1lambda的延迟执行

3.1.1性能浪费的日志案例

3.1.2使用lambda进行日志优化

3.2 使用lambda作为参数和返回值案例

3.2.1 函数式接口作为参数

3.2.2  函数式接口作为返回值类型

四、常用函数式接口

4.1 Supplier接口

4.1.2 练习题:求数组元素最大值

4.2 Consumer接口

4.2.1 accept方法

4.2.2 andThen 默认方法

4.2.3练习:格式化打印信息

4.3 Predicate接口

4.3.1 boolean test(T t)方法

4.3.2 and默认方法

4.3.3or默认方法

4.3.4 negate默认方法

4.3.5练习:集合筛选练习

4.4 Function接口

4.4.1 抽象方法Apply

4.4.2 默认的方法:andThen

4.4.3练习;函数模型拼接




一、函数式接口的概念与定义

1.1函数式接口

有且仅有一个抽象方法的接口

适用于函数编程场景的接口。

1.2函数式编程

是指用lambda或者方法引用进行编程

1.3 定义一个函数式接口

 

/*
 * 有且仅有一个抽象方法的接口称作为函数式接口
 * 当然接口中可以有其他方法(默认,静态,私有)
 */


public interface MyFunctionInter {
    public abstract void method();

}

1.4 @FunctionalInterface注解

检测接口是否是一个函数式接口,是,编译成功,否,编译失败(接口中没有抽象方法,抽象方法的个数大于一个)

@FunctionalInterface
public interface MyFunctionInter {
    public abstract void method();
//    void method1();

}

1.5 Lambda的表达式例子

  • 空括号(),没有参数,返回值为void

     Runnable noArguments = () -> System.out.println("hello");

  • 包含一个参数,可省略参数的括号和参数类型(因为有类型推断,根据上下文自动推断出类型),返回值为void

        ActionListener oneArguments = event -> System.out.println("button");

  • Lambda表达式主体不仅可以是一个表达式,也可以是一段代码块,使用大括号将代码括起来,跟普通方法遵循的规则一致,可以返回或者抛出异常来推出

Runnable multiStatement = () -> {
            System.out.println("hello");
            System.out.println("world");
        
        };

  • 可以有多个参数

        BinaryOperator< Long> add = (x,y) -> x + y;

  • 可以和显示声明类型

        BinaryOperator< Long> add = (Long x,Long y) -> x + y;

1.6 Lambda引用值,而不是变量

1.6.1 匿名内部类

没有名字的内部类,因为没有名字,所以匿名内部类只能使用一次。

前提条件:必须继承父类或实现一个接口

 abstract class Person{
        public abstract void eat();
    }
public class Demo {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Person person = new Person() {
                public void eat(){
                    System.out.println("eat haha");
                }
            };
           person.eat();

        }
}

1.6.2 lambda表达式引用的是值而不是变量

匿名内部类,需要引用它所在方法里的变量时,需要将变量声明为 final 

Lambda表达式不要求必须是final 变量  但是,该变量在既成事实上必须是final 

事实上的 final 是指只能给该变量赋值一次。换句话说,Lambda 表达式引用的是值,而不是变量 跟匿名内部类类似

String hello = "HelloWord";
        Stream.of(1,2,3).forEach(s -> System.out.println(hello + s));

给hello重新赋值

运行结果:

 

二、函数式接口的使用

函数式接口一般作为方法的参数和返回值类型使用

1.1作为方法的参数使用

首先要创建一个MyFunctionInter接口的实现类MyFunctionInterImpl

public class Demo {
//    定义一个方法,参数使用函数式接口MyFunctionInter
    public static void show(MyFunctionInter myInter) {
//        调用MyFunctionInter中的method方法
        myInter.method();
    }
    
    public static void main(String[] args) {
//        调用show()方法,方法的参数是一个接口,所以可以传入接口的实现类对象
        show(new MyFunctionInterImpl());
//       调用show()方法,方法的参数是一个接口,所以可以传入接口的匿名内部类
        show(new MyFunctionInterImpl() {
            @Override
            public void method() {
                System.out.println("使用匿名内部类,重写接口中的抽象方法");
            }
//            调用show方法,方法的参数是一个函数式接口,所以可以传入一个lambda表达式
        });
        show(() -> {
            System.out.println("使用lambda表达式重写接口中的抽象方法");
        });

    }

}
 

运行输出结果

1.1.1 简化lambda表达式

如果只有一条语句,可以省略“{}”和“;”

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

三、函数式编程

3.1lambda的延迟执行

有些场景的代码执行后,结果不一定被使用,而lambda表达式是延迟执行的,可以提升性能

3.1.1性能浪费的日志案例

public class DemoLogger {
//    定义一个根据日志的级别显示日志信息的方法
    public static void showLog(int level,String message) {
//        根据日志的等级进行判断,如果级别是一,则显示日志信息
        if(level == 1) {
            System.out.println(message);
        }
    }
    
    public static void main(String[] args) {
//        定义三个日志信息
        String msg1 = "hello";
        String msg2 = "world";
        String msg3 = "java";
//调用showlog的方法
        showLog(2, msg1+msg2+msg3);
    }

}
 

当传入level为2时,就不会显示信息

发现以上代码存在性能浪费问题

调用showLog方法,传入的第二个参数是拼接好的字符串

先把字符串拼接好,然后调用showLog方法

showLog方法如果传递的参数等级不是1,

那么就不会使用拼接后的字符串,导致性能浪费

3.1.2使用lambda进行日志优化

lambda特点;延迟加载,

lambda使用前提:必须存在函数式接口(LogMessage)

(1)创建一个函数式接口

@FunctionalInterface
public interface LogMessage {
//    定义一个拼接消息的抽象方法,返回拼接的消息
    public abstract String builderMessage();
}
 

(2)使用函数式接口传递lambda参数,输出日志信息

public class LambdaLog {
//    定义一个显示日志的方法,方法的参数传递日志的等级和LogMessage接口
    public static void show(int level,LogMessage logMessage){
//        对日志等级进行判断,如果是1级调用LogMessage中的builderMessage方法
        if(level == 1) {
            System.out.println(logMessage.builderMessage());;
        }
        
    }
    public static void main(String[] args) {
//      定义三个日志信息
      String msg1 = "hello";
      String msg2 = "world";
      String msg3 = "java";
      
//      调用show方法,LogMessage是个函数式接口,所以,可以传lambda表达式
      show(2, () ->{
          return msg1+msg2+msg3;
      });

    }

}
 

发现运行结果是一样的,那么函数式接口有什么好处呢

使用lambda表达式作为参数传递,仅仅是把参数传递给show方法中,只有曼珠条件(level==1 ),才会调用LogMessage接口中的builderMessage,只有调用方法后,才会进行字符串拼接,如果条件不满足(level !==1),builderMessage方法不会执行,所以就不会执行拼接字符串代码,就不会存在性能浪费

测试:

在使用show()中加入一条输出语句

show(2, () ->{
          System.out.println("等级为1执行");
          return msg1+msg2+msg3;
      });

等级为2,查看结果:没有任何输出,即没有执行builderMessage方法,就是没有拼接字符串

等级为1,查看结果:,执行builderMessage方法,有拼接字符串

3.2 使用lambda作为参数和返回值案例

3.2.1 函数式接口作为参数

 * java.lang.Runnable接口是一个函数式接口
 * 假设有startThread方法使用该接口作为参数,那么就可以使用lambda进行传参
 * 这种情况和Thread类的构造方法参数为Runnable没有本质别

public class DemoRunable {
//    定义一个方法startThread,方法的参数使用函数式接口Runnable
    public static void startThread(Runnable run) {
//        开启多线程
        new Thread(run).start();
    }

    public static void main(String[] args) {
//        调用startThread方法,方法的参数是一个接口,那么我们可以传递这个接口的匿名内部类
        startThread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println(Thread.currentThread().getName() + "线程启动了");
            }
        });

//        调用startThread方法,方法的参数是一个函数式接口,所以可以传递lambda表达式
//        run方法没有参数
        startThread(() -> {
            System.out.println(Thread.currentThread().getName() + "线程启动了");
        });

//        优化lambda表达式
        startThread(() -> System.out.println(Thread.currentThread().getName() + "线程启动了"));
    }

}
 

运行结果:

3.2.2  函数式接口作为返回值类型

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

public class Democompare {
//    定义一个方法,方法的返回值类型使用函数式接口Comparator
    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();
//        };
//        优化
        return (String o1,String o2) -> o2.length() - o1.length();
    }

    public static void main(String[] args) {
//        创建一个字符串数组
        String[] arr = {"bbb","c","aaaaaaa","dddd"};
//       输出排序前的数组
        System.out.println(Arrays.toString(arr));
//        调用Arrays中的sort方法,对字符串数组进行排序
//        第一个参数为数组,第二各参数为Comparator接口的实现类,回了一个lambda
        Arrays.sort(arr,getComparator());
//        输出排序后的结果
        System.out.println(Arrays.toString(arr));

    }


}
 

运行结果:

四、常用函数式接口

java.docs

https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

4.1 Supplier接口

java.util.function.Supplier<T>接口包含一个无参的方法:T.get().用来获取泛型参数指定类型的对象数据。

Supplier<T>接口称之为生产型接口,指定接口的泛型<T>是什么类型,那么接口中的get方法就会产生什么类型的数据。

public class DemoSupplier {
//    定义一个方法,方法参数传递Supplier接口,泛型执行String,get方法就返回一个string
    public static String getString(Supplier<String> supplier) {
        return supplier.get();
    }
    public static void main(String[] args) {
//        调用getString方法,方法的参数是supplier接口,锁一个以传递lambda表达式
       String s =  getString(() -> {
            return "老郭";
        });
        System.out.println(s);
//        优化
        String s1 =  getString(() -> "小郭");
        System.out.println(s1);
        
    }


}

当只有一个return 那么做优化的时候return {} ;都可以忽略掉

4.1.2 练习题:求数组元素最大值

注意:接口的泛型使用java.lang.Integer

public class SupplierTest {
//    定义一个方法,获取int型数组中元素最大值,泛型使用Integer,方法传递参数是supplier接口
    public static int getMax(Supplier<Integer> sup) {
        return sup.get();
    }

    public static void main(String[] args) {
        int[] arr = { 100, 40, 0, -50, 99 };
        int maxValue = getMax(() -> {
            int max = arr[0];
            for (int i : arr) {
                if (i > max) {
                    max = i;

                }
            }
            return max;
        });
        System.out.println(maxValue);
    }

}

4.2 Consumer接口

java.util.function.Consumer<T>接口则正好与Supplier接口相反

他不是产生一个数据,而是消费一个数据,其数据类型由泛型决定

Consumer接口包含抽象方法void accept(T t),意为消费一个指定泛型的数据

4.2.1 accept方法

Consumer接口是一个消费型接口,泛型指定什么类型,就可以使用accept方法消费什么类型的数据,具体怎么消费使用,需要自定义

/*
 * 定义一个方法,方法参数传递一个字符串的姓名和Consumer接口,泛型使用string
 */

public class DemoConsumer {
    public static void method(String name,Consumer<String> con) {
        con.accept(name);
    }
    public static void main(String[] args) {
        method("老郭",(String name) ->{
            System.out.println(name);
            
//            字符串反转 链式调用
            String reName = new StringBuffer(name).reverse().toString();
            System.out.println(reName);
        });
    }

}

4.2.2 andThen 默认方法

需要两个Cnsumer接口,把两个Consumer接口组合到一起,在对数据进行消费

例如:

Consumer<String> con1

Consumer<String> con2

String s = "hello"

con1.accept(s)

con2.accept(s)

连接两个Consumer接口,在进行消费,谁写前面,谁先消费

con1.addThen(con2).accept(s)

public class DemoAndThen {
    public static void method(String s,Consumer<String> con1,Consumer<String> con2) {
        con1.andThen(con2).accept(s);
    }
    public static void mian(String[] args) {
        method("hello", (t) ->{
            System.out.println(t.toUpperCase());
        }, (t) -> {
            System.out.println(t.toLowerCase());
        });
    }

}

4.2.3练习:格式化打印信息

public class ConsumerTest {
    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 = {"Lily,18","Bom,20","linda,12"};
        printInfo(arr, (message) -> {
            String name = message.split(",")[0];
            System.out.print("姓名" + name);
        }, (message) -> {
            String age = message.split(",")[1];
            System.out.println(".年龄" + age);
        });
    }

}
 

运行结果如下

4.3 Predicate接口

4.3.1 boolean test(T t)方法

java.util.function.Predicate<T>接口通过其boolean test(T t)对指定数据类型数据进行判断,返回true或false

public class DemoPredicate {
//    定义一个方法,参数传递一个string字符串和Predicate接口,泛型使用String,使用test对字符串进行判断
    
    public static Boolean method(String s,Predicate<String> pre) {
        return pre.test(s);
    }
    public static void main(String[] args) {
//        定义一个字符串
        String string = "asdfgh";
//        调用method方法,传递参数和lambda表达式
//       Boolean res = method(string, (String str) -> {
//            return str.length() > 5;
//        });
//        优化
        Boolean res = method(string, str -> str.length() > 5);
        System.out.println(res);
    
    }

}
 

4.3.2 and默认方法

表示并且关系,也可以用于连接两个判断条件

方法内部的两个判断条件也是使用&&运算符连接起来的

//判断字符串长度大于5且包含a
public class AndTest {
    public static Boolean method(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 = "asdfgh";
       boolean bol = method(s, (String str) ->{
            return str.length() > 5;
        }, (String str) -> {
            return str.contains("a");
        });
        System.out.println(bol);
    }

}

4.3.3or默认方法

表示或者关系,也可以用于连接两个判断条件

方法内部的两个判断条件也是使用||运算符连接起来的

用法同and()

4.3.4 negate默认方法

表示取反,用“!”

 return pre.negate(0.test(s)

4.3.5练习:集合筛选练习

/*
 * 通过Predicate接口将符合要求的字符串你筛选到集合ArrayList中
 */
public class PredicateTest {
    public static ArrayList<String> filter(String[] arr,Predicate<String> pre1,Predicate<String> pre2){
//        定义一个ArrayList集合,存储过滤后的信息
        ArrayList<String> list = new ArrayList<String>();
        for (String s : arr) {
//            使用predicate接口中的方法test对获取到的字符串进行判断
            boolean b = pre1.and(pre2).test(s);
            if(b) {
//                条件成立,把信息存储到ArrayList中
                list.add(s);
            }
        }
        return list;
    }
    public static void main(String[] args) {
        String[] arr = {"Lily,12","Bom,20","linda,12"};
       ArrayList<String> res = filter(arr, (String s) -> {
            return s.split(",")[0].length() > 3;
        }, (String s) ->{
            return s.split(",")[1].equals("12");
        });
        System.out.println(res);
    }
}
 

4.4 Function接口

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

4.4.1 抽象方法Apply

R apply(T t).根据类型T的参数获取类型R的结果

//将String类型换为R类型
public class DemoApply {
    public static void method(String s,Function<String, Integer> fun) {
        Integer in = fun.apply(s);
        System.out.println(in);
    }
    public static void mian(String[] args) {
//        定义一个字符串类型的整数
        String s = "123";
//        调用method方法,传递字符串类型的整数,和lambda表达式
        method(s,str -> Integer.parseInt(str));
    }
}
 

4.4.2 默认的方法:andThen

/*
 *把String类型转换为Integer类型,转换后加10
 *增加后的Integer类型数据转换为String类型
 *转换了两次
 *第一次;
 *String类型转换为Integer
 *可以使用Function<String,Integer> fun1
 *第二次转换Integer转换为String
 *Function<Integer,String> fun2
 *可以使用andThen把两次转换组合到一起
 *
 */

public class DemoTest {
    public static void method(String s,Function<String, Integer> fun1,Function<Integer, String> fun2) {
        String str = fun1.andThen(fun2).apply(s);
        System.out.println(str);
        
    }
    public static void main(String[] args) {
        String s = "123";
        method(s, str -> Integer.parseInt(s) + 10,
                i -> i + "");
    }
    
    
    
    

}
 

 

4.4.3练习;函数模型拼接

/*
 * 获取字符串数字年龄部分
 * Function<String,String> fun1
 * 将得到的字符串转换成int类型
 * Function<String,Integer> fun2
 * 将int类型加100
 * Function<Integer,Integer> fun3
 */

public class DemoTest {
    public static int method(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 = "gsy,18";
         int num = method(str, s -> s.split(",")[1],
                s-> Integer.parseInt(s),
                i ->i + 100);
         System.out.println(num);
         
    }

}
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值