函数式编程(Lambda表达式+方法引用)

本文详细介绍了函数式编程的基本思想及其在Java8中的体现,重点讲解了Lambda表达式的概念、语法、使用场景和优缺点,以及Lambda表达式的省略写法。此外,还探讨了内置的函数式接口,如Consumer、Supplier、Function和Predicate,以及如何通过方法引用来简化代码。文章通过实例展示了Lambda和方法引用在实际编程中的应用。
摘要由CSDN通过智能技术生成

目录

1.函数式编程思想

1.1.概念

1.2.优点

2.Lambda表达式

2.1.概述

2.2.Lambda的由来

2.3.Lambda表达式的语法

2.4.Lambda练习1

2.5.Lambda练习2

2.6.Lambda表达式的省略写法

2.7 Lambda表达式使用的前提

3. 内置函数式接口

3.1.内置函数式接口的由来

3.2. 消费型函数式接口Consumer

3.3. 供给型函数式接口---Supplier

3.4. 函数型函数式接口---Function,r>

3.5. 断言型函数式接口--Predicate

4. 方法引用

4.1.推荐用法

4.2. 基本格式

4.3.方法引用的由来

4.4.方法引用的类型

4.5.1.静态方法引用

4.5.2.实例方法引用

4.5.3.对象方法引用

5.方法引用总结



1.函数式编程思想

1.1.概念

面向对象思想需要关注用什么对象完成什么事情,而函数式编程思想就类似于数学中的函数。

它主要关注的是对数据进行了什么操作。

1.2.优点

  • 代码简洁,开发快速
  • 接近自然语言,易于理解
  • 易于“并发编程”

2.Lambda表达式

2.1.概述

Lambda是JDK8中一个语法。他可以对某些匿名内部类的写法进行简化。它是函数式编程思想的一个重要体现。让我们不用关注是什么对象。而是更关注我们对数据进行了什么操作。

2.2.Lambda的由来

public class Test01 {
    public static void main(String[] args) {
        //开启一个线程 该构造函数需要传递一个Runnable类型的接口参数
        Thread thread = new Thread(new My());
        thread.start(); //开启线程  执行run方法

        //匿名内部类
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("通过匿名内部类实现线程任务=====");
            }
        });
        thread1.start();//开启线程  执行匿名内部类的run方法
    }
}

class My implements Runnable {

    @Override
    public void run() {
        System.out.println("使用实现类完成线程任务=====");
    }
}

分析:

  1. Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的核心
  2. 为了指定run方法体,不得不需要Runnable的实现类
  3. 为了省去定义一个Runnable 的实现类,不得不使用匿名内部类
  4. 必须覆盖重写抽象的run方法,所有的方法名称,方法参数,方法返回值不得不都重写一遍,而且不能出错,
  5. 而实际上,我们只在乎方法体中的代码.
  6. 我们可以使用Lambda表达式来完成上面的功能。

使用Lambda表达式来完成上面的功能:

public class Test01 {
    public static void main(String[] args) {

        //Lambda表达式
        /*Runnable runnable = () -> {
            System.out.println("Lambda表达式完成线程任务");
        };
        Thread thread2 = new Thread(runnable);
        thread2.start(); //开启线程*/

        //Lambda表达式2
        Thread thread2 = new Thread(() -> {
            System.out.println("Lambda表达式完成线程任务");
        });
        thread2.start(); //开启线程
    }
}

class My implements Runnable {
    @Override
    public void run() {
        System.out.println("使用实现类完成线程任务=====");
    }
}

2.3.Lambda表达式的语法

Lambda省去了面向对象的条条框框,Lambda的标准格式由3个部分组成:

格式:( 参数列表 ) -> { 方法体 }

  • ():参数列表
  • ->:连接符 连接的是参数以及方法体。
  • {}: 方法体。

2.4.Lambda练习1

练习无参无返回值的Lambda表达式:

public class Test01 {
    public static void main(String[] args) {
        //主函数调用fun方法
        //第一种:匿名内部类方式
        User user1 = new User() {
            @Override
            public void show() {
                System.out.println("匿名内部类的show方法实现");
            }
        };
        fun(user1);

        //第二种:Lambda表达式方式 (接口必须为函数式接口)
        fun(() -> {
            System.out.println("Lambda表达式的show方法实现");
        });


    }

    //定义一个静态方法
    public static void fun(User user){
        user.show();
    }
}

//函数式接口(里面只有一个抽象方法)
@FunctionalInterface
interface User{
    public void show();
}

2.5.Lambda练习2

练习有参有返回值的Lambda表达式:

public class Test03 {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<>();
        personList.add(new Person("张三",25));
        personList.add(new Person("李四",18));
        personList.add(new Person("王五",30));
        personList.add(new Person("马六",20));
        //对集合中的元素按照年龄从小到大排序     集合工具类:Collections
        System.out.println("没排序:"+personList);
        //方法一:使用匿名内部类
        Comparator<Person> comparator = new Comparator<Person>() {
            @Override//如果是0表示相同  大于0表示o1大于o2
            public int compare(Person o1, Person o2) {//自带的参数
                return o1.getAge()-o2.getAge();
            }
        };
        Collections.sort(personList,comparator);
        System.out.println("从小到大排序后:"+personList);

        //方法二:使用Lambda表达式  (就是对函数式接口中抽象方法的简写)
        Comparator<Person> comparator2 =(o1,o2) -> {
            return o1.getAge()- o2.getAge();
        };
        Collections.sort(personList,comparator2);
        System.out.println(personList);
        
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class Person{
    private String name;
    private Integer age;
}

2.6.Lambda表达式的省略写法

 在lambda表达式的标准写法基础上,可以使用省略写法的规则为:

  1. 小括号内的参数类型可以省略[ ]
  2. 如果小括号内有且仅有一个参数,则小括号可以省略
  3. 如果大括号内有且仅有一个语句,可以同时省略大括号 和 return 关键字及语句分号

案列1:

public class Test04 {
    public static void main(String[] args) {
        //未省略
       /* U01 u = (a) ->{
            System.out.println("heelo=="+a);
        };*/
        //省略后
        U01 u = a -> System.out.println("hello=="+a);
        fun(u);

        //U02 u2 = a -> {return a * 2;};   //未省略
        U02 u2 = a -> a * 2;   //省略后  Lambda默认把最后一条语句作为返回结果
        fun02(u2);

    }


    public static void fun(U01 u01) {
        u01.show(10);
    }

    public static void fun02(U02 u02) {
        int print = u02.print(20);
        System.out.println(print);
    }
}

interface U01 {
    public void show(int a);
}

interface U02 {
    public int print(int b);
}

案列2:

public class Test004 {
    public static void main(String[] args) {
        //Lambda表达式 (toUpperCase)
        fun((str) -> str.toUpperCase());
    }

    public static void fun(ABC abc) {
        String s = abc.toUpper("hello==");//使用Lambda表达式把字符串转为大写
        System.out.println(s);
    }

    interface ABC {
        public String toUpper(String str);
    }
}

2.7 Lambda表达式使用的前提

 Lambda表达式的语法是非常简洁的,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意

  1. 方法的参数或局部变量类型必须为接口才能使用Lambda
  2. 接口中有且仅有一个抽象方法(@FunctionalInterface)

3. 内置函数式接口

要想使用lambda表达式它的前提就是必须是函数式接口

3.1.内置函数式接口的由来

案列:

public class Test05 {
    public static void main(String[] args) {

        /*Operation o = (int[] arr) -> {
            int sum = 0;
            for (int s:arr){
                sum += s ;
            }
            return sum;
        };
        fun(o);*/
 
        //简写
        fun(arr -> {
            int sum = 0;
            for (int s:arr){
                sum += s ;
            }
            return sum;
        });
    }

    public static void fun(Operation operation){
        int[] arr = {1,2,3,4,5,6,7,8,9};
        int s = operation.getSum(arr);
        System.out.println("数组和为:"+s);
    }
}

@FunctionalInterface
interface Operation{
    public int getSum(int[] arr);
}

分析:

我们知道使用Lambda表达式的前提是需要有函数式接口,而Lambda表达式使用时不关心接口名,抽象方法名。只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda表达式更加的方便,在JDK中提供了大量常用的函数式接口. 大多数无需自己再定义函数式接口,而可以直接使用jdk内置的函数式接口分成以下四类:

函数式接口参数类型返回类型说明
Consumer<T> 消费型接口Tvoidvoid accept(T t);对类型为T的对象应用操作
Supplier<T> 供给型接口TT get();返回类型为T的对象
Function<T,R>函数型接口TR

R apply(T t);对类型为T的对象应用操作,并返回类型

为R类型的对象

Predicate<T>断言型接口Tboolean

boolean test(T t);确定类型为T的对象是否满足条件,

并返回boolean类型

3.2. 消费型函数式接口Consumer

适合有参数,但是没有返回值的

根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。

案列:

public class Test06 {
    public static void main(String[] args) {
        Consumer<Double> c = t ->{
            System.out.println("今天吃饭花费:"+t+"元");
        };
        fun(c,200.0);
    }
    public static void fun(Consumer<Double> consumer , Double money){
        consumer.accept(money);
    }
}

3.3. 供给型函数式接口---Supplier

无参,需要返回值的接口类

根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建好的对象返回

案列:

public class Test07 {
    public static void main(String[] args) {
        Supplier<Integer> s = () -> new Random().nextInt(10); //Random:随机获取一为数
        fun(s);
    }

    public static void fun(Supplier<Integer> supplier) {
        Integer a = supplier.get();
        System.out.println("结果:" + a);
    }
}

3.4. 函数型函数式接口---Function<T,R>

T: 参数的泛型

R:返回值的泛型

根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回

修改(3.1.内置函数式接口的由来)中的案列:

public class Test05 {
    public static void main(String[] args) {

        /*Function<int[] , Integer> o = (int[] arr) -> {
            int sum = 0;
            for (int s:arr){
                sum += s ;
            }
            return sum;
        };
        fun(o);*/

        //简写
        fun(arr -> {
            int sum = 0;
            for (int s:arr){
                sum += s ;
            }
            return sum;
        });
    }

    public static void fun(Function<int[] , Integer> fun){
        int[] arr = {1,2,3,4,5,6,7,8,9};
        int s = fun.apply(arr);
        System.out.println("数组和为:"+s);
    }
}

修改分析:

不需要自己再定义函数式接口,直接用内置的 函数式接口就行了。因为方法有参有返回值

3.5. 断言型函数式接口--Predicate

T: 参数

boolean:返回值类型

根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果

public class Test09 {
    public static void main(String[] args) {
        Predicate<String> p = t -> t.length()>3;
        fun(p,"cj"); //字符串长度小于3为false

    }
    public static void fun(Predicate<String> predicate , String name){
        boolean test = predicate.test(name);
        System.out.println("是否成年:"+test);
    }
}

4. 方法引用

我们在使用lambda时,如果方法体中只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码

4.1.推荐用法

  • 我们在使用lambda时不需要考虑什么时候用方法引用,用哪种方法引用,方法引用的格式是什么。
  • 我们只需要在写完lambda方法发现方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可。
  • 当我们方法引用使用的多了慢慢的也可以直接写出方法引用。

4.2. 基本格式

类名或者对象名 :: 方法名

4.3.方法引用的由来

public class Test10 {
    public static void main(String[] args) {
        Consumer<int[]> c = t -> {
            int sum = 0;
            for (int a : t) {
                sum += a;
            }
            System.out.println("数组的和是:" + sum);
        };
        fun(c);

    }

    public static void fun(Consumer<int[]> consumer) {
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
        consumer.accept(arr);
    }

    //求和方法
    public static void sum(int[] arr) {
        int sum = 0;
        for (int a : arr) {
            sum += a;
        }
        System.out.println("数组的和是:" + sum);
    }
}

分析:

如果我们在Lambda中所指定的功能,已经有其他方法存在相同方案,那是否还有必要再写重复逻辑?可以直接“引 用”过去就好了----方法引用  ::  可以办到

public class Test10 {
    public static void main(String[] args) {
        //Consumer<int[]> c=(t)->Test10.sum(t); 
        Consumer<int[]> c = Test10::sum; //方法引用  //静态方法引用
        fun(c);

    }

    public static void fun(Consumer<int[]> consumer) {
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
        consumer.accept(arr);
    }

    //求和方法
    public static void sum(int[] arr) {
        int sum = 0;
        for (int a : arr) {
            sum += a;
        }
        System.out.println("数组的和是:" + sum);
    }
}

4.4.方法引用的类型

类型语法对应的Lambda表达式
静态方法引用类名::staticMethod(args)->类名.staticMethod(args)
实例方法引用inst::instMethod(args)->inst.instMethod(args)
对象方法引用类名::instMethod(inst,args)->inst.instMethod(args)
构建方法引用类名::new(args)->new 类名(args)

4.5.方法引用举例

4.5.1.静态方法引用

使用前提:如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法

public class Test11 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(); //从小到大排序
        list.add(2);
        list.add(8);
        list.add(4);
        list.add(1);

        //Comparator<Integer>comparator=(o1,o2)->o1-o2; //第一种排序方法
        //Comparator<Integer>comparator=(o1,o2)->Integer.compare(o1,o2);//第二种排序方法//Integer.compare排序
        Comparator<Integer> comparator = Integer::compareTo;//第三种排序方法  静态方法引用


        Collections.sort(list, comparator);
        System.out.println(list);
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class Person {
    private String name;
    private Integer age;
}

4.5.2.实例方法引用

使用前提: 如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。

实例方法引用特点:( ) -> 对象.普通方法( );

案例:

public class Test01 {
    public static void main(String[] args) {
        User u = new User("张三",20);

        //在这个lambda表达式中只有一条语句,而且该语句又是调用了某个方法
        //Supplier<String> s = ()->u.getName();
        //实例方法引用
        Supplier<String> s = u::getName;  //实例方法引用特点:( ) -> 对象.普通方法( );
        fun(s);
    }
    
    public static void fun(Supplier<String> supplier){
        String s = supplier.get();
        System.out.println("结果为:"+s);
    }
}

@Data
@AllArgsConstructor
class User{
    private String name;
    private Integer age;
}

4.5.3.对象方法引用

使用前提: 如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法

案例1:

public class Test02 {
    public static void main(String[] args) {
        //对象方法引用: 类名::实例方法      (参数1,参数2)->参数1.实例方法(参数2)


        //Lambda表达式   函数式接口Function<T,R>
       /* Function<String,Integer> function = (str)->{
            return str.length();
        };*/

        //使用对象方法引用
        Function<String,Integer> function = String::length;

        Integer integer = function.apply("hello~~");  //获取字符串长度
        System.out.println(integer);

    }
}

案例2:

比较两个字符串内容是否一致

public class Test02 {
    public static void main(String[] args) {
        //对象方法引用: 类名::实例方法      (参数1,参数2)->参数1.实例方法(参数2)


        //案例2:比较两个字符串内容是否一致 T , U , R
        //R apply(T t, U u);
        /*BiFunction<String,String,Boolean> bi =(t,u)->{
            return t.equals(u);
        };*/
        BiFunction<String,String,Boolean> bi = String::equals;
        Boolean apply = bi.apply("hello", "hello");
        System.out.println(apply);
    }
}

4.5.4.构造方法引用

使用前提:如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们把要重写的抽象方法中的所有的参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器。

public class Test03 {
    public static void main(String[] args) {


        //构造方法引用 : 类名::new  (参数)->new 类名(参数)
        // 供给型函数式接口---Supplier
        /*Supplier<People>supplier=()->{
            return new People();
        };*/
        Supplier<People> supplier = People::new;  //调无参构造
        People s = supplier.get();
        System.out.println(s);

        //函数型函数式接口---Function<T,R>
        //Function<String,People> function=(n)->new People(n);
        Function<String, People> function = People::new;      // 调有参构造
        People s2 = function.apply("张三");
        System.out.println(s2);
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class People {
    private String name;
}

5.方法引用总结

类型语法对应的Lambda表达式
静态方法引用类名::静态方法lambda表达式时:(参数)->类名.静态方法(参数)
实例方法引用对象::实例方法lambda表达式时:(参数)->对象.实例方法(参数)
对象引用方法  类名::实例方法 lambda表达式时:(参数1,参数2...)->参数1.实例方法(参数2...)
构造方法引用类名::new    lambda表达式时:(参数)->new 类名(参数)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值