前言

★ 这里是小冷的博客
✓ 优质技术好文见专栏
个人公众号,分享一些技术上的文章,以及遇到的坑
当前系列:Java8 新特性 系列
源代码 git 仓库 代码Git 仓库地址

Java8 新特性

函数式接口(Functional)

通过上面的 Lambda表达式的学习,我们认识了 新的语法,支持这种语法的接口

  • 只包含一个抽象方法的接口,称为函数式接口
  • 你只可以通过 Lambda表达式,来创建该接口的对象,(Lambda表达式抛出一个抛出一个检查异常(即,运行时异常),这个衣长需要在目标接口的抽象方法上进行声明)
  • 我们可以在接口上使用@FunctionalInterface注解,这样做可以检查这个接口是不是函数式接口,同时javadoc也会包含一条声明说明这个接口是一个函数式接口,
  • java.util.funcion包下定义了Java8丰富的函数式接口

Lambda的表达式本质其实就是函数式接口的实例

什么是函数式接口呢?

Runnable接口就是一个很典型的函数式接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

这里我们自己定一个

@FunctionalInterface
public interface MyinterFace {
    void method1();
}

Java内置四大核心函数式接口

函数式接口参数类型返回类型用途
Consumer 消费型接口Tvoid对类型为t的对象应用操作,包含方法void accept(T,t)
Supperlier供给型接口T返回类型为T的对象,包含方法 T get()
Function<T,R>TR对类型为T的对象应用操作,并返回结果,结果是R类型的对象,包含方法 R apply(T t)
PredicateTboolean确定类型为T的对象是否满足某个约束,并返回boolean值,包含方法 boolean test(T t)

理论+实践

我们以 Consumer 与 Predicate举例子

/**
 * @author : <h2>冷环渊</h2>
 * @date : 2021/12/11
 * @context: <h4>
 * 消费型接口 Consumer<T> Accept(T t)
 * 供给型接口 Supplier<T> T get()
 * 函数式接口 Function<T> R apply(T t)
 * 断定型接口 Predicate<T> boolean test(T t)
 * </h4>
 */
public class LambdaTest2 {


    /**
     * @author 冷环渊 Doomwatcher
     * @context: Consumer<T> 使用
     * @date: 2021/12/11 14:37
     * @param
     * @return: void
     */
    @Test
    public void Test1() {
        happyTime(500, new Consumer<Double>() {
            @Override
            public void accept(Double aDouble) {
                System.out.println("天上人间的水今天很贵:" + aDouble);
            }
        });
        System.out.println("****************************");
        happyTime(400, money -> System.out.println("学习太累了,去天上人间买了瓶矿泉水,价格为:" + money));
    }

    /**
     * @author: 冷环渊 Doomwatcher
     * @context:
     * @date: 2021/12/11 14:33
     * @param money
     * @param con
     * @return void
     */
    public void happyTime(double money, Consumer<Double> con) {

        con.accept(money);
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context: 断言 Predicate 的使用
     * @date: 2021/12/11 14:39
     * @param
     * @return: void
     */
    @Test
    public void Test2() {
        //传统写法
        List<String> filterList = Arrays.asList("北京", "天津", "南京", "东京", "江南");
        List<String> filterStr = filterString(filterList, new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.contains("京");
            }
        });
        System.out.println(filterStr);
        System.out.println("*******************************");
        // Lambda表达式 写法
        List<String> filterList1 = Arrays.asList("北京", "天津", "南京", "东京", "江南");
        List<String> filterStr1 = filterString(filterList1, s -> s.contains("京"));
        System.out.println(filterStr1);
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context:
     * @date: 2021/12/11 14:49
     * @param list
     * @param pre
     * @return: java.util.List<java.lang.String>
     */
    public List<String> filterString(List<String> list, Predicate<String> pre) {
        ArrayList<String> filterlist = new ArrayList<>();
        for (String s : list) {
            if (pre.test(s)) {
                filterlist.add(s);
            }
        }
        return filterlist;
    }
}

方法引用与构造器引用

方法引用

  • 当前要传递给 Lambda体的操作,已经有了实现的方法了,可以使用方法引用
  • 方法引用可以看做是Lambda表达式的深层次表达,换句话说,方法引用就是Lambda表达式,只不过和之前我们自己编写不同,这里通过方法的名字来指向一个方法,可以认为是Lambda表达式的一种语法糖
  • 要求:实现接口的抽象方法的参数列表和返回值类型。必须与方法引用的方法的参数列表和返回值保持一致
  • 格式:使用操作符“::”将类或者对象与方法名字分开来
  • 如下是三种主要的使用情况
    • 对象::实例方法名
    • 类::静态方法名
    • 类::实例方法名

coding

准备两个类 一个是list集合,一个是对象

Employee get/set 和 tostring 为防止无用篇幅过长,这里我们简写只看一下对象属性即可

package Lambda.MethodReferences;

/**
 *
 * @author : <h2>冷环渊</h2>
 * @date : 2021/12/11
 * @context:<h4>提供用于测试的对象</h4>
 */
public class Employee {

    private int id;
    private String name;
    private int age;
    private double salary;
\
}

EmployeeData

package Lambda.MethodReferences;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author : <h2>冷环渊</h2>
 * @date : 2021/12/11
 * @context:<h4>提供用于测试的数据</h4>
 */
public class EmployeeData {

    public static List<Employee> getEmployees() {
        List<Employee> list = new ArrayList<>();

        list.add(new Employee(1001, "马化腾", 34, 6000.38));
        list.add(new Employee(1002, "马云", 12, 9876.12));
        list.add(new Employee(1003, "刘强东", 33, 3000.82));
        list.add(new Employee(1004, "雷军", 26, 7657.37));
        list.add(new Employee(1005, "李彦宏", 65, 5555.32));
        list.add(new Employee(1006, "比尔盖茨", 42, 9500.43));
        list.add(new Employee(1007, "任正非", 26, 4333.32));
        list.add(new Employee(1008, "扎克伯格", 35, 2500.32));

        return list;
    }

}

方法引用 test

/**
 * @author : <h2>冷环渊</h2>
 * @date : 2021/12/11
 * @context:<h4>
 * 方法引用的使用
 *
 * 1.使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
 * 2.方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以
 *   方法引用,也是函数式接口的实例。
 * 3. 使用格式:  类(或对象) :: 方法名
 * 4. 具体分为如下的三种情况:
 *    情况1     对象 :: 非静态方法
 *    情况2     类 :: 静态方法
 *    情况3     类 :: 非静态方法
 * 5. 方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的
 *    形参列表和返回值类型相同!(针对于情况1和情况2)
 *
 * </h4>
 *
 */
public class MethodRefTest {

    // 情况一:对象 :: 实例方法
    //Consumer中的void accept(T t)
    //PrintStream中的void println(T t)
    @Test
    public void test1() {
        Consumer<String> con1 = str -> System.out.println(str);
        con1.accept("北京");

        System.out.println("*******************");
        PrintStream ps = System.out;
        Consumer<String> con2 = ps::println;
        con2.accept("beijing");
    }

    //Supplier中的T get()
    //Employee中的String getName()
    @Test
    public void test2() {
        Employee emp = new Employee(1001, "Tom", 23, 5600);

        Supplier<String> sup1 = () -> emp.getName();
        System.out.println(sup1.get());

        System.out.println("*******************");
        Supplier<String> sup2 = emp::getName;
        System.out.println(sup2.get());

    }

    // 情况二:类 :: 静态方法
    //Comparator中的int compare(T t1,T t2)
    //Integer中的int compare(T t1,T t2)
    @Test
    public void test3() {
        Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1, t2);
        System.out.println(com1.compare(12, 21));

        System.out.println("*******************");

        Comparator<Integer> com2 = Integer::compare;
        System.out.println(com2.compare(12, 3));

    }

    //Function中的R apply(T t)
    //Math中的Long round(Double d)
    @Test
    public void test4() {
        Function<Double, Long> func = new Function<Double, Long>() {
            @Override
            public Long apply(Double d) {
                return Math.round(d);
            }
        };

        System.out.println("*******************");

        Function<Double, Long> func1 = d -> Math.round(d);
        System.out.println(func1.apply(12.3));

        System.out.println("*******************");

        Function<Double, Long> func2 = Math::round;
        System.out.println(func2.apply(12.6));
    }

    // 情况三:类 :: 实例方法  (有难度)
    // Comparator中的int comapre(T t1,T t2)
    // String中的int t1.compareTo(t2)
    @Test
    public void test5() {
        Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);
        System.out.println(com1.compare("abc", "abd"));

        System.out.println("*******************");

        Comparator<String> com2 = String::compareTo;
        System.out.println(com2.compare("abd", "abm"));
    }

    //BiPredicate中的boolean test(T t1, T t2);
    //String中的boolean t1.equals(t2)
    @Test
    public void test6() {
        BiPredicate<String, String> pre1 = (s1, s2) -> s1.equals(s2);
        System.out.println(pre1.test("abc", "abc"));

        System.out.println("*******************");
        BiPredicate<String, String> pre2 = String::equals;
        System.out.println(pre2.test("abc", "abd"));
    }

    // Function中的R apply(T t)
    // Employee中的String getName();
    @Test
    public void test7() {
        Employee employee = new Employee(1001, "Jerry", 23, 6000);


        Function<Employee, String> func1 = e -> e.getName();
        System.out.println(func1.apply(employee));

        System.out.println("*******************");


        Function<Employee, String> func2 = Employee::getName;
        System.out.println(func2.apply(employee));
    }

}

构造方法引用

通过 简化 的方式,来调用不同的构造器

一、构造器引用

  • 和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。
  • 抽象方法的返回值类型即为构造器所属的类的类型

二、数组引用

  • 大家可以把数组看做是一个特殊的类,则写法与构造器引用一致。
/**
 *
 * @author : <h2>冷环渊</h2>
 * @date : 2021/12/11
 * @context:<h4>
 *
 * 一、构造器引用
 *      和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。
 *      抽象方法的返回值类型即为构造器所属的类的类型
 *
 * 二、数组引用
 *     大家可以把数组看做是一个特殊的类,则写法与构造器引用一致。
 * </h4>
 */
public class ConstructorRefTest {
    //构造器引用
    //Supplier中的T get()
    //Employee的空参构造器:Employee()
    @Test
    public void test1() {

        Supplier<Employee> sup = new Supplier<Employee>() {
            @Override
            public Employee get() {
                return new Employee();
            }
        };
        System.out.println("*******************");

        Supplier<Employee> sup1 = () -> new Employee();
        System.out.println(sup1.get());

        System.out.println("*******************");

        Supplier<Employee> sup2 = Employee::new;
        System.out.println(sup2.get());
    }

    //Function中的R apply(T t)
    @Test
    public void test2() {
        Function<Integer, Employee> func1 = id -> new Employee(id);
        Employee employee = func1.apply(1001);
        System.out.println(employee);

        System.out.println("*******************");

        Function<Integer, Employee> func2 = Employee::new;
        Employee employee1 = func2.apply(1002);
        System.out.println(employee1);

    }

    //BiFunction中的R apply(T t,U u)
    @Test
    public void test3() {
        BiFunction<Integer, String, Employee> func1 = (id, name) -> new Employee(id, name);
        System.out.println(func1.apply(1001, "Tom"));

        System.out.println("*******************");

        BiFunction<Integer, String, Employee> func2 = Employee::new;
        System.out.println(func2.apply(1002, "Tom"));

    }

    //数组引用
    //Function中的R apply(T t)
    @Test
    public void test4() {
        Function<Integer, String[]> func1 = length -> new String[length];
        String[] arr1 = func1.apply(5);
        System.out.println(Arrays.toString(arr1));

        System.out.println("*******************");

        Function<Integer, String[]> func2 = String[]::new;
        String[] arr2 = func2.apply(10);
        System.out.println(Arrays.toString(arr2));

    }
}

总结

  • 全新的语法带来了很多的便捷,理解起来可能相对麻烦
  • 这里在改变语法为Lambda的时候,可以自己找找可以省去,那一些部分
  • 思考,为什么构造器引用可以根据数量和类型去找到对应的构造器
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐