JDK8新特性 Lambda表达式及相关特性

函数式接口

函数式接口是1.8中的新特性,他不属于新语法,更像是一种规范

面向对象接口复习

在使用lambda表达是之前先回顾一下面向对象的接口,创建接口的关键字为interface,这里创建一个日志接口:

public interface LogService {
    void info();
}

众所周知,Java中的接口是不能直接创建实例的,因为他的抽象方法没有得到实现:

JDK8新特性 Lambda表达式及相关特性

当我们想要创建接口实例的时候一般都通过实现类来获取实例:

public class LogServiceImpl implements LogService {
    @Override
    public void info() {
        System.out.println("哈哈哈哈");
    }
}

这样我们就可以通过实现来创建这个接口的实例:

public class ApplicationMain {
    public static void main(String[] args) {
        LogService log = new LogServiceImpl();
        log.info();
    }
}

对接口中的方法进行实现除了实现类之外,还可以通过匿名类的方式来创建接口的实例:

public class ApplicationMain {
    public static void main(String[] args) {
        LogService log = new LogService() {
            @Override
            public void info() {
                System.out.println("嘿嘿嘿");
            }
        };
    }
}

什么是函数式接口

当一个接口只有一个抽象方法 ( 接口默认实现不算 ) 的时候这个接口就是函数式接口,上面的LogService就属于函数式接口,还有学习线程时的Runnable接口也属于函数式接口

函数式接口还有一个注解为@FunctionalInterface,据我了解这个注解主要起到的是检查的作用,当接口中的抽象方法数量大于1的时候就会报错:

JDK8新特性 Lambda表达式及相关特性

内置常用函数式接口

Java中内置了一些常用的函数式接口,这里介绍其中的四种,不用刻意去记,用到就明白了

函数式接口 参数 返回值 说明
Consumer T void 对类型为T的实例进行操作
Supplier T 返回类型为T的实例
Function<T, R> T R 通过对T的操作返回R的实例
Predicate T boolean 通过对T的操作返回boolean值

通过巧妙的运用函数式接口可以实现回调函数的功能

Lambda表达式

Lambda表达式是1.8中的新特性,主要针对函数式接口的匿名实现做了简化

表达式基本语法

找到上面匿名内部类创建LogService的代码:

public class ApplicationMain {
    public static void main(String[] args) {
        LogService log = new LogService() {
            @Override
            public void info() {
                System.out.println("嘿嘿嘿");
            }
        };
    }
}

因为创建的实例就是接口本身,所以new的部分就可以省略不写,接口中需要实现的抽象方法只有一个,那么也没有必要指定具体实现的方法名称,简写后如下所示:

public class ApplicationMain {
    public static void main(String[] args) {
        LogService log = ()->{
            System.out.println("哈哈哈哈");
        };
        log.info();
    }
}

Lambda表达式前面的括号( )就代表参数列表,箭头->后面的代码块就代表着实现的具体方法体,如果info方法中有参数就可以这样写:

public class ApplicationMain {
    public static void main(String[] args) {
        LogService log = (msg)->{
            System.out.println(msg);
        };
        log.info("老八秘制小汉堡");
    }
}

使用Lambda表达式创建一个线程:

public class ApplicationMain {
    public static void main(String[] args) {
        // Runnable为函数式接口,所以可以使用Lambda表达式
        new Thread(()->{
            System.out.println("另一条线程");
        }).start();
        System.out.println("Main主线程");
    }
}

表达式的简写

使用Lambda表达式代替匿名内部类的写法非常简洁,但是在满足特定条件的情况下,它还可以更简洁:

public class ApplicationMain {
    public static void main(String[] args) {
        /**
         * 在参数列表只有一个参数的情况下,()可以省略不写
         * 当代码块中只有一句代码时{}可以省略不写
         * 当函数有返回值且代码块中只有一句代码时return可以省略不写
         */
        LogService log = msg->System.out.println(msg);
        log.info("老八秘制小汉堡");
    }
}

Lambda表达式相比匿名内部类还有一个特点,当使用匿名内部类时会为匿名内部类生成一个class文件,而使用Lambda表达式时就不会额外生成class文件,这个不做重点

方法引用

方法引用是1.8中的新特性,配合Lambda表达式使用可以让代码更简洁

方法引用初体验

方法引用中需要的特殊符号为双冒号::,具体表现为以下形式:

表现形式 作用
对象::实例方法 通过对象调用实例中的方法
类::实例方法 通过类调用实例中的方法
类::静态方法 通过类调用实例中的静态方法
类::new 创建该类的实例

当Lambda表达式中只有一句调用代码,且调用的目标函数的参数列表和Lambda表达式接收的参数列表一致,那么就可以省略参数列表简写为以下这种形式:

public class ApplicationMain {
    public static void main(String[] args) {
        // 代表的就是上面表格中的第一行,对象::实例方法
        LogService log = System.out::println;
        log.info("老八秘制小汉堡");
    }
}

综合练习

针对上面的函数式接口,Lambda表达式以及方法引用做练习,代码会有些多:

/**
 * 综合练习
 *   函数式接口 + Lambda表达式 + 方法引用
 */
public class ApplicationMain {

    public static void main(String[] args) {
        // 方法引用  对象::实例方法
        strToDate("2021-2-20 22:08:45", System.out::println);
        // 方法引用  类::new
        System.out.println(dateToStr(Date::new));
        // 回调函数:取最大值
        Integer integer = strListToIntList(Arrays.asList("135", "541", "244"), list -> {
            int maxNumber = 0;
            for (Integer item : list) {
                if (item > maxNumber) {
                    maxNumber = item;
                }
            }
            return maxNumber;
        });
        System.out.println("最大值为:" + integer);
        // 实现过滤器功能
        List<Integer> list = filter(Arrays.asList(800, 200, 1200, 600, 3000, 2400), item -> item > 1000);
        System.out.println(list);
    }

    // Consumer:通过时间字符串快速获取Date对象
    public static void strToDate(String date, Consumer<Date> consumer) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date parse = null;
        try {
            parse = sdf.parse(date);
        } catch (ParseException e) {
            System.err.println("转换失败!");
        }
        consumer.accept(parse);
    }

    // Supplier:通过Date对象快速获取到时间字符串
    public static String dateToStr(Supplier<Date> supplier) {
        Date date = supplier.get();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    }

    // Function:将String集合转换为Integer,后续操作交给调用者
    public static Integer strListToIntList(List<String> list, Function<List<Integer>, Integer> function) {
        ArrayList<Integer> integerList = new ArrayList<>();
        for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
            String next = iterator.next();
            Integer integer = Integer.valueOf(next);
            integerList.add(integer);
        }
        return function.apply(integerList);
    }

    // predicate:返回大于1000的数字
    public static List<Integer> filter(List<Integer> list, Predicate<Integer> predicate) {
        ArrayList<Integer> target = new ArrayList<>();
        for (Integer item : list) {
            if (predicate.test(item)) {
                target.add(item);
            }
        }
        return target;
    }

}
上一篇:linux 服务器安装jdk8


下一篇:JDK8新特性(二) 流式编程Stream