Java8 Lambda表达式与Stream API (一):Lambda表达式

你要知道的Java8 匿名内部类、函数式接口、lambda表达式与Stream API都在这里

转载请注明出处 //www.greatytc.com/p/c832d2c6dd81

本文主要讲解Java8 Stream API,但是要讲解这一部分需要匿名内部类、lambda表达式以及函数式接口的相关知识,本文将分为两篇文章来讲解上述内容,读者可以按需查阅。

本文是该系列博文的第一篇Java 匿名内部类、lambda表达式与函数式接口,主要讲解Java匿名内部类lambda表达式以及函数式接口,第二篇文章Java Stream API主要讲解Java Stream API

匿名内部类

匿名内部类适用于那些只需要使用一次的类,比如设计模式下的命令模式,往往通过定义一系列接口进行调用,有时有的命令只会执行一次就不再执行,这个时候如果单独定义一个类就显得过于复杂并且编译会生成这个类的.class文件,不利于管理,这种情况下使用匿名内部类就能够很好的简化编程并且不会编译生成单独的.class文件。

接下来看一个例子:

interface Programmer
{
    void listLanguages();
    void introduceMyself();
}

class MyProgrammer implements Programmer
{
    public void listLanguages() {
        System.out.println("Objective-C Swift Python Go Java");
    }

    public void introduceMyself() {
        System.out.println("My Name is Jiaming Chen");
    }
}

public class HelloWorld
{   
    static void info(Programmer programmer)
    {
        programmer.listLanguages();
        programmer.introduceMyself();
    }
    
    public static void main(String[] args)
    {   
        info(new MyProgrammer());
    }
}

上面这个例子为了执行info函数定义了一个实现了Programmer接口的类MyProgrammer,如果它只执行一次这样就显得过于复杂,如果采用匿名内部类就会在很大程度上简化编程,首先介绍一下匿名内部类的基础语法:

new 需要实现的接口() | 父类构造器()
{
    //需要实现的方法或重载父类的方法
}

匿名内部类的语法很简单,必须要实现一个接口或者继承一个类,可以看到使用了new关键词,因此在创建匿名内部类的同时会创建一个该类的实例,并且只能创建一个实例,创建完成后这个匿名内部类就不能再使用,因此,匿名内部类不能是抽象类,由于匿名内部类没有类名所以也不能定义构造函数,但是可以在定义匿名内部类的时候调用父类的有参构造器也可以定义初始化块用于初始化父类的成员变量。下面这个栗子是将上述代码修改为匿名内部类的实现方式:

class MyProgrammer implements Programmer
{
    public void listLanguages() {
        System.out.println("Objective-C Swift Python Go Java");
    }

    public void introduceMyself() {
        System.out.println("My Name is Jiaming Chen");
    }
}

public class HelloWorld
{   
    static void info(Programmer programmer)
    {
        programmer.listLanguages();
        programmer.introduceMyself();
    }
    
    public static void main(String[] args)
    {   
        info(new Programmer(){
            public void listLanguages() {
                System.out.println("Objective-C Swift Python Go Java");
            }

            public void introduceMyself() {
                System.out.println("My Name is Jiaming Chen");
            }
        });
    }
}

通过对比发现,使用匿名内部类比重新定义一个新类更加简洁,在创建匿名内部类的时候可以调用父类的有参构造函数,栗子如下:

abstract class Programmer
{
    protected String name;
    
    public Programmer(String name)
    {
        this.name = name;
    }
    
    public abstract void listLanguages();
    public abstract void introduceMyself();
}

public class HelloWorld
{   
    static void info(Programmer programmer)
    {
        programmer.listLanguages();
        programmer.introduceMyself();
    }
    
    public static void main(String[] args)
    {   
        int age = 22;
        Programmer p = new Programmer("Jiaming Chen"){
            public void listLanguages()
            {
                System.out.println("Objective-C Swift Python Go Java");
            }
            public void introduceMyself()
            {
                System.out.println("My Name is " + this.name + " and I'm " + age + " years old.");
                //age = 2;
            }
        };
        info(p);
        //age = 23;
    }
}

上述栗子首先定义了一个抽象父类,并且该抽象父类只有一个构造函数,因此在创建匿名内部类的时候需要显示调用该构造函数,这样就可以在匿名内部类内部使用父类定义的成员变量了,匿名内部类也可以使用外部变量,在Java8中上述栗子中的age会自动声明为final类型,这称为effectively final,只要匿名内部类访问了一个局部变量,这个局部变量无论是否被final修饰它都会自动被声明为final类型,不允许任何地方进行修改,Java与其他语言相比在闭包内访问外部变量的局限更大,因为只能是final类型,比如OCblock内部也可以捕获外部变量,swift也有一套闭包值捕获机制,都可以对捕获的值进行修改或权限的设置,Java的局限性太大。

lambda表达式

Java8引入了lambda表达式,在其他语言中,比如pythonswift都支持lambda表达式,这个特性用起来也非常方便和简洁。接下来举一个常见的对一个列表进行排序的例子:

class MyComparator implements Comparator<String>
{
    public int compare(String o1, String o2)
    {
        return o1.compareTo(o2);
    }
}

public class HelloWorld
{   
    public static void main(String[] args)
    {   
        ArrayList<String> list = new ArrayList<>();
        list.add("Objective-C");
        list.add("Swift");
        list.add("Python");
        list.add("Golang");
        list.add("Java");
        list.sort(new MyComparator());
        list.forEach(System.out::println);
        /*
        输出:(下面的代码的输出结果是一致的,不再赘述)
        Golang
        Java
        Objective-C
        Python
        Swift
        */
    }
}

在开发中经常会对一个集合类型进行排序操作,调用集合的sort方法时需要传入一个实现了Comparator接口的参数,因此上述栗子就定义了一个类MyComparator并且这个类实现了Comparator接口,因此可以创建一个MyComparator的对象用于排序操作,不难发现这样做非常复杂需要重新定义一个全新的类,经过前文的介绍这里完全可以用匿名内部类来代替,关于最后一行代码list.forEach(System.out::println);在后文会介绍,这里先卖个关子,它的作用就是遍历整个集合并输出。接下来看一下使用匿名内部类实现的方式:

public class HelloWorld
{   
    public static void main(String[] args)
    {   
        ArrayList<String> list = new ArrayList<>();
        list.add("Objective-C");
        list.add("Swift");
        list.add("Python");
        list.add("Golang");
        list.add("Java");
        list.sort(new Comparator<String>(){
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }           
        });
        list.forEach(System.out::println);
    }
}

结果同上,显而易见,采用了匿名内部类更加的方便了,代码简洁明了,那有没有再简介一点的办法呢?答案当然是肯定的,那就是使用lambda表达式,栗子如下:

public class HelloWorld
{   
    public static void main(String[] args)
    {   
        ArrayList<String> list = new ArrayList<>();
        list.add("Objective-C");
        list.add("Swift");
        list.add("Python");
        list.add("Golang");
        list.add("Java");
        list.sort((String o1, String o2)->{
            return o1.compareTo(o2);
        });
        list.forEach(System.out::println);
    }
}

通过上述代码发现,sort函数传入的参数是一个lambda表达式,整个代码非常类似前文中我们实现的compare函数,但是我们不需要再写new Comparator<String()这样繁琐的代码,也不需要写函数名,因此lambda表达式可以很好的替代匿名内部类,不难发现lambda表达式由三部分组成:

  • 首先是参数列表,这个参数列表与要实现的方法的参数列表一致,因为系统已经知道了你需要实现哪一个方法因此可以省略形参类型,当只有一个参数时也可以省略圆括号,但是当没有形参时圆括号不可以省略
  • 接下来是一个->符号,该符号用于分隔形参列表与函数体,该符号不允许省略。
  • 最后就是代码体了,如果代码体只有一行代码就可以省略掉花括号,并且如果方法需要有返回值连return关键词都可以省略,系统会自动将这一行代码的结果返回。

通过上面的讲解,我们就可以写一个更加简洁lambda表达式了,栗子如下:

public class HelloWorld
{   
    public static void main(String[] args)
    {   
        ArrayList<String> list = new ArrayList<>();
        list.add("Objective-C");
        list.add("Swift");
        list.add("Python");
        list.add("Golang");
        list.add("Java");
        list.sort((s1, s2)->s1.compareTo(s2));
        list.forEach(System.out::println);
    }
}

上面的代码我们省略了形参的类型,由于只有一行我们同时省略了花括号和return语句,整个代码相比使用匿名内部类更加简洁了。到这里有同学可能会问了,lambda表达式是怎么知道我们实现的是接口的哪一个方法?

lambda表达式的类型也被称为目标类型 target type,该类型必须是函数式接口 Functional Interface函数式接口代表有且只有一个抽象方法,但是可以包含多个默认方法或类方法的接口,因此使用lambda表达式系统一定知道我们实现的接口的哪一个方法,因为实现的接口有且只有一个抽象方法供我们实现。

函数式接口可以使用注释@FunctionalInterface来要求编译器在编译时进行检查,是否只包含一个抽象方法。Java提供了大量的函数式接口这样就能使用lambda表达式简化编程。lambda表达式的目标类型必须是函数式接口lambda表达式也只能为函数式接口创建对象因为lambda表达式只能实现一个抽象方法。

前文介绍了在使用lambda表达式时,如果代码体只有一行代码可以省略花括号,如果有返回值也可以省略return关键词,不仅如此,lambda表达式在只有一条代码时还可以引用其他方法或构造器并自动调用,可以省略参数传递,代码更加简洁,引用方法的语法需要使用::符号。lambda表达式提供了四种引用方法和构造器的方式:

  • 引用对象的方法 类::实例方法
  • 引用类方法 类::类方法
  • 引用特定对象的方法 特定对象::实例方法
  • 引用类的构造器 类::new

举个栗子:

public class HelloWorld
{   
    public static void main(String[] args)
    {   
        ArrayList<String> list = new ArrayList<>();
        list.add("Objective-C");
        list.add("Swift");
        list.add("Python");
        list.add("Golang");
        list.add("Java");
        //list.sort((s1, s2)->s1.compareTo(s2));
        list.sort(String::compareTo);
        list.forEach(System.out::println);
    }
}

对比上述两行代码,第一个sort函数传入了一个lambda表达式用于实现Comparator接口的compare函数,由于该实现只有一条代码,因此可以省略花括号以及return关键字。第二个sort方法则直接引用了对象的实例方法,语法规则为类::实例方法,系统会自动将函数式接口实现的方法的所有参数中的第一个参数作为调用者,接下来的参数依次传入引用的方法中即自动进行s1.compareTo(s2)的方法调用,明显第二个sort函数调用更加简洁明了。

最后一行代码list.forEach(System.out::println);则引用了类方法,集合类的实例方法forEach接收一个Consumer接口对象,该接口是一个函数式接口,只有一个抽象方法void accept(T t);,因此可以使用lambda表达式进行调用,这里引用System.out的类方法println,引用语法类::类方法,系统会自动将实现的函数式接口方法中的所有参数都传入该类方法并进行自动调用。

再举一个栗子:

@FunctionalInterface
interface Index
{
    int index(String subString);
}

@FunctionalInterface
interface Generator
{
    String generate();
}

public class HelloWorld
{   
    static int getIndex(Index t, String subString)
    {
        return t.index(subString);
    }
    
    static String generateString(Generator g)
    {
        return g.generate();
    }
    
    public static void main(String[] args)
    {   
        String str = "Hello World";
        System.out.println(getIndex(str::indexOf, "e"));
        System.out.println(generateString(String::new).length());
    }
}

这个栗子似乎没有任何实际意义,就是为了演示引用特定对象的实例方法和引用类的构造器。接口IndexGenerator都是函数式接口,因此可以使用lambda表达式。对于getIndex方法需要传入一个实现Index接口的对象和一个子串,在调用时首先定义了一个字符串Hello World,然后引用了这个对象的实例方法indexOf,这个时候系统会自动将这个特定对象作为调用者然后将所有的参数因此传入该实力方法。
引用构造器的方法也很简单类::new,不再赘述。

总结

本文主要讲解匿名内部类lambda表达式函数式接口以及lambda表达式方法引用构造器引用,通过几个例子逐步深入,逐步简化代码的编写,可以发现Java8提供的lambda表达式是如此的强大。接下来的一篇文章会对Java8新增的Stream API进行讲解,Stream的流式API支持并行,对传统编程方式进行了改进,可以编写出更简洁明了的高性能代码。有兴趣的读者可以阅读Java Stream API

备注

由于作者水平有限,难免出现纰漏,如有问题还请不吝赐教。

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

推荐阅读更多精彩内容

  • 简介 概念 Lambda 表达式可以理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主...
    刘涤生阅读 3,193评论 5 18
  • lambda表达式(又被成为“闭包”或“匿名方法”)方法引用和构造方法引用扩展的目标类型和类型推导接口中的默认方法...
    183207efd207阅读 1,467评论 0 5
  • 注:之前关于Java8的认知一直停留在知道有哪些修改和新的API上,对Lambda的认识也是仅仅限于对匿名内部类的...
    mualex阅读 2,816评论 1 4
  • 原文链接: Lambdas 原文作者: shekhargulati 译者: leege100 lambda表达式是...
    忽来阅读 6,572评论 8 129
  • 对于大数据时代为我们创造的各种便利,我是很感激的。我有一个习惯,每周末都会定期删除收藏夹,保证我打开第一次以后还想...
    落花成琳阅读 308评论 2 2