JDK8新特性之Lambda表达式(一)

JDK8新特性之Lambda表达式(一)

函数式接口

在了解Lambda表达式之前我们必须了解一下函数式接口,Lambda表达式实际上是依赖于函数式接口的。

定义

如果一个接口中,只声明了一个抽象方法,那么这个接口就成为函数式接口。
我们一般使用@FunctionalInterface来标注,这个注解的作用是检验一个接口是否是函数式接口,这个注解是非必须的,换句话说如果一个不加这个注解但是只有一个抽象方法那么这个接口仍然是函数式接口。但是但最好在接口上使用注解@FunctionalInterface进行声明,以免团队的其他人员错误地往接口中添加新的方法。
需要注意的是,在JDK8后接口中允许有方法实现,像默认方法、静态方法这些都不属于抽象方法。还有就是重写 Object 类里的方法不会导致函数式接口失效,如果一个接口声明了抽象方法,但该抽象方法重写了 Object 类里的一个公有方法,那么对于 Java 编译器来说,它并不会认为该方法符合函数式接口的抽象方法(即不把该方法当作函数式接口的抽象方法)。因为接口的实现类都会直接或间接继承 Object 这个根类,所以在函数式接口中定义与 Object 类中签名一样的方法是不会导致函数式接口失效的。

代码示例

下面通过几个例子来说明:
只有一个抽象方法的函数式接口:


/**
 * @Author winston
 * @Date 2021/9/11
 */
// 声明函数式接口的注解
@FunctionalInterface
public interface FunctionInterfaceExample {
    /**
     * 抽象方法
     */
    public void demo01();

    /**
     * 接口中的静态方法,不属于抽象方法
     */
    public static void staticMethod(){
        System.out.println("静态方法");
    }

    /**
     * 接口中的默认方法,不属于抽象方法
     */
    default void defaultMethod(){
        System.out.println("默认方法");
    }
}

当我们在上面定义的函数式接口中再增加一个抽象方法时,如果加了@FunctionalInterface注解的话,实际上编译是会报错的,因为这就不属于函数式接口了,下面这个代码就会编译报错,大家可以试一下


/**
 * @Author winston
 * @Date 2021/9/11
 */
// 声明函数式接口的注解
@FunctionalInterface
public interface FunctionInterfaceExample {
    /**
     * 抽象方法
     */
    public void demo01();

    /**
     * 抽象方法2
     */
    public void demo02();
    /**
     * 接口中的静态方法
     */
    public static void staticMethod(){
        System.out.println("静态方法");
    }

    /**
     * 接口中的默认方法
     */
    default void defaultMethod(){
        System.out.println("默认方法");
    }
}

重写 Object 类里的方法不会导致函数式接口失效,下面这个代码表明上看上去是一个抽象方法,实际上它是重写了Object类里面的方法,因此是不会报错的

/**
 * @Author winston
 * @Date 2021/9/11
 */
// 声明函数式接口的注解
@FunctionalInterface
public interface FunctionInterfaceExample {
    /**
     * 抽象方法
     */
    public void demo01();

    /**
     * 重写object中的方法
     */
    public String toString();
    /**
     * 接口中的静态方法
     */
    public static void staticMethod(){
        System.out.println("静态方法");
    }

    /**
     * 接口中的默认方法
     */
    default void defaultMethod(){
        System.out.println("默认方法");
    }
}

Lambda表达式

Lambda表达式介绍

首先我们先了解一下什么是Lambda表达式,在JDK8中引入的一种新的语法元素和操作符,这个符号为->,该操作符被称为Lambda操作符或者叫箭头操作符,它将Lambd分为两个部分:
左侧:指定了Lambda表达式需要的参数列表
右侧:指定了Lambda体,是抽象方法的实现逻辑,也就是Lambda表达式要执行的功能
Lambda表达式的本质是作为函数式接口的实现

Lambda表达式语法

语法格式一:无参、无返回值
假设我们接口中有个抽象方法print

@FunctionalInterface
public interface LambdaDemo1 {

    public void print();

}

传统使用匿名函数实现的方式:

public static void main(String[] args) {
        LambdaDemo1 lambdaDemo1 = new LambdaDemo1() {
            @Override
            public void print() {
                System.out.println("我是LambdaDemo1的print方法");
            }
        };
        lambdaDemo1.print();
    }

使用Lambda表达式的方式:

  public static void main(String[] args) {
        LambdaDemo1 lambdaDemo1 = ()->{
            System.out.println("我是LambdaDemo1的print方法");
        };
        lambdaDemo1.print();
    }
    
    public static void main(String[] args) {
        // 方法体只有一行代码时可以省略{}
        LambdaDemo1 lambdaDemo1 = () -> System.out.println("我是LambdaDemo1的print方法");
        lambdaDemo1.print();
    }

语法格式二:有参、无返回值

@FunctionalInterface
public interface LambdaInterface1 {

    public void print(String str);

}

传统使用匿名函数实现的方式:

 public static void main(String[] args) {
        LambdaInterface1 lambdaInterface1 = new LambdaInterface1() {
            @Override
            public void print(String str) {
                System.out.println(str);
            }
        };
        lambdaInterface1.print("哈哈哈");
    }

使用Lambda表达式的方式:

public static void main(String[] args) {
        LambdaInterface1 lambdaInterface1 = (String str) -> {
            System.out.println(str);
        };
        lambdaInterface1.print("哈哈哈");
    }

在上面Lambda表达式的基础上我们还可以将参数数据类型进行省略,因为参数类型可由编译器推断出来(因为LambdaInterface1是函数式接口有且只有一个抽象方法,因此实现的时候只能实现该方法,所以对应的参数也必然是该接口的print方法参数,所以可以省略参数类型)

 public static void main(String[] args) {
        // 省略方法参数类型
        LambdaInterface1 lambdaInterface1 = (str) -> {System.out.println(str)};
        lambdaInterface1.print("哈哈哈");

    }

当方法参数只有一个时()可以省略,当方法体代码只有一句时{}可以省略,我们在上面例子的基础上进行改造

public static void main(String[] args) {
        // 省略方法参数中的()和方法体的{}
        LambdaInterface1 lambdaInterface1 = str -> System.out.println(str);
        lambdaInterface1.print("哈哈哈");

    }

语法格式三:Lambda需要两个或者两个以上的参数,多条执行语句,并且可能有返回值
我们以JDK自带的Comparator接口为例,传统写法:

 public static void main(String[] args) {
        Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                System.out.println(o1);
                System.out.println(o2);
                return o1.compareTo(o2);
            }
        };
        int result = comparator.compare(2, 3);
        
    }

使用lambda表达式的写法:

 public static void main(String[] args) {
        // 不是一个参数的时候()不能省略,由于类型推断参数类型可以省略,方法体中只有一行代码时{}可以省略
        Comparator<Integer> comparator = (o1, o2) -> {
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
        };
        int result = comparator.compare(2, 3);
    }

Lambda表达式使用小结

在使用Lambda的接口必须是函数式接口
在->左侧:Lambda形参列表的参数类型可以省略,如果Lambda形参列表只有一个参数,其()也可以省略
在->右侧:Lambda体应该使用一对{}包裹,如果Lambda体里面只有一句执行语句(可能是return语句)那么{}可以省略

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

在JDK8中为我们提供了几种类型的函数式接口,它们分别是消费型接口Consumer<T>,供给型接口Supplier<T>,函数型接口Function<T>,断定型接口Predicate<T>,下面分别介绍这几种接口的基本使用。

消费型接口Consumer

参数类型T,返回类型void,对类型T的对象应用操作,包含方法void accept(T t),简单来说就是接收一个T,然后将它进行消费。
Consumer接口

@FunctionalInterface
public interface Consumer<T> {

    // 消费方法
    void accept(T t);

        // 这个是用来需要两个Consumer接口,可以把两个Consumer接口组合到一起,在对数据进行消费
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        // 这一步就是相当于对accept方法的具体实现,返回一个consumer
        return (T t) -> { accept(t); after.accept(t); };
    }
}

使用accept方法

public class LambdaDemoTest {
    public static void main(String[] args) {
        Consumer<String> consumer = str -> System.out.println("打印信息:" + str);
        consumer.accept("哈哈哈");
    }
}

使用andThen方法组合消费

 public static void main(String[] args) {
        Consumer<String> consumer1 = str -> System.out.println("consumer1打印信息:" + str);
        Consumer<String> consumer2 = str -> System.out.println("consumer2打印信息:" + str);

        consumer1.andThen(consumer2).accept("哈哈哈");
    }

效果:

consumer1打印信息:哈哈哈
consumer2打印信息:哈哈哈
供给型接口Supplier

无参,返回值类型T,简单来说就是会提供一个特定对象,Supplier接口包含方法T get()
Supplier接口

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

通过使用Lambda表达式来使用get方法

public static void main(String[] args) {
       // 想要一个默认值是0的Double对象
        Supplier<Double> supplier = ()-> new Double("0");
        Double zeroDouble = supplier.get();
    }
函数型接口Function

入参T,返回值R,简单来说就是将入参T转成结果R
Function接口(这里主要是R apply(T t)方法,其他方法先省略)

@FunctionalInterface
public interface Function<T, R> {
   ...
    R apply(T t);
   ....
}

通过Lambda表达式使用apply方法

 public static void main(String[] args) {
        // 想要将传入的Integer类型转成String类型
        Function<Integer,String> function = (paramInteger) -> String.valueOf(paramInteger);
        String strResult = function.apply(12);
    }
断定型接口Predicate

入参T,返回值是boolean,简单来说就是根据入参做判断处理
Predicate接口(这里主要是boolean test(T t)方法,其他方法先省略)

@FunctionalInterface
public interface Predicate<T> {
   ...
    boolean test(T t);
   ....
}

这里我们举个例子,给定一个存有数字的List,我们要过滤掉小于10的数字
不使用lambda表达式的方式使用test方法进行过滤:

public static void main(String[] args) {
        List<Integer> numList = new ArrayList<>();
        numList.add(3);
        numList.add(11);
        numList.add(10);
        numList.add(5);
        numList.add(18);
        numList.add(7);
        numList.add(22);

        /**
         * 自定义方法,用来过滤获取想要的结果集
         * 这里的第一个参数是要进行过滤的List
         * 第二个参数是写过滤规则的Predicate接口,使用了匿名内部类的实现方式实现test方法
         */
        filterNum(numList, new Predicate<Integer>() {
            @Override
            public boolean test(Integer num) {
                if(10 < num){
                    return true;
                }
                return false;
            }
        });
    }

    /**
     *
     * @param numList 要过滤的Integer集合
     * @param integerPredicate 过滤条件
     * @return
     */
    private static List<Integer> filterNum(List<Integer> numList, Predicate<Integer> integerPredicate) {
        List<Integer> result = new ArrayList<>();
        for (Integer num : numList) {
            // 如果满足条件的就放到result集合中
            boolean flag = integerPredicate.test(num);
            if (flag) {
                result.add(num);
            }
        }

        return result;
    }

使用lambda表达式的方式使用test方法进行过滤:

 public static void main(String[] args) {
        List<Integer> numList = new ArrayList<>();
        numList.add(3);
        numList.add(11);
        numList.add(10);
        numList.add(5);
        numList.add(18);
        numList.add(7);
        numList.add(22);

        /**
         * 自定义方法,用来过滤获取想要的结果集
         * 这里的第一个参数是要进行过滤的List
         * 第二个参数是写过滤规则的Predicate接口,这里使用的是lambda表达式的方式
         */
        filterNum(numList,num -> {
            if(10 < num){
                return true;
            }
            return false;
        });
    }

    /**
     *
     * @param numList 要过滤的Integer集合
     * @param integerPredicate 过滤条件
     * @return
     */
    private static List<Integer> filterNum(List<Integer> numList, Predicate<Integer> integerPredicate) {
        List<Integer> result = new ArrayList<>();
        for (Integer num : numList) {
            // 如果满足条件的就放到result集合中
            boolean flag = integerPredicate.test(num);
            if (flag) {
                result.add(num);
            }
        }

        return result;
    }

小结

刚开始接触Lambda表达式的话可能会觉得不太好理解,实际上Lambda表达式就是函数式接口的实现,通过上面的一些例子我们可以看出我们能够通过使用传统的匿名内部类来实现这些函数式接口,Lambda表达式通过一系列的简化操作减少了代码的数量,看起来简洁了很多但是对于他人的理解难度却是增加了不少,大家还是要慢慢学习Lambda表达式。其次就是JAVA内置四大核心函数式接口,当我们需要用到上面所说的功能时可以考虑使用而不是自己新建一个接口。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容