Java 函数式接口 lambda 应用

函数式接口

理解Functional Interface(函数式接口,如下简称FI)是学习Java8 Lambda表达式的关键所在,因此放在最开始讨论。FI的定义其实很简单:任何接口,若是只包含惟一一个抽象方法,那么它就是一个FI。为了让编译器帮助咱们确保一个接口知足FI的要求(也就是说有且仅有一个抽象方法),Java8提供了@FunctionalInterface注解。举个简单的例子,Runnable接口就是一个FI,下面是它的源代码:html

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

public interface Runnable {
    public abstract void run (); 
}
复制代码

ps: 上述两种都是函数式接口,在Java 8 提供新的注解Function Interface 声明一个接口为函数式接口,声明以后这个接口必须符合函数式接口的规范。@FunctionalInterface 对于接口是否是函数式接口没有影响,但该注解知识提醒编译器去检查该接口是否仅包含一个抽象方法。java

下面的用法就是错误:bash

@Function Interface
public interface test{
    public void test1();
    public void test2();
复制代码

ps: 接口中声明的方法默认是抽象的,使用注解后,编译器自动检查发现存在两个抽象方法,会报错。app

函数式接口的规范

  1. 函数式接口里是能够包含默认方法,由于默认方法不是抽象方法,其有一个默认实现,因此是符合函数式接口的定义的;
@FunctionalInterface
    interface GreetingService {
        void sayMessage(String message);

        default void doSomeMoreWork1() {
            // Method body
        }

        default void doSomeMoreWork2() {
            // Method body
        }
    }
复制代码
  1. 函数式接口里是能够包含静态方法,由于静态方法不能是抽象方法,是一个已经实现了的方法,因此是符合函数式接口的定义的;
@FunctionalInterface
    interface GreetingService {
        public void sayMessage(String message);
        public static void printHello(){
            System.out.println("Hello");
        }
    }
复制代码
  1. 函数式接口里是能够包含Object里的public方法,这些方法对于函数式接口来讲,不被当成是抽象方法(虽然它们是抽象方法);由于任何一个函数式接口的实现,默认都继承了Object类,包含了来自java.lang.Object里对这些抽象方法的实现。
@FunctionalInterface
    interface GreetingService {
        void sayMessage(String message);
        
        @Override
        boolean equals(Object obj);
    }
复制代码

Java 内部类

为何讲内部类,由于在 lamada 表达式又与 Java 内部类应用存在类似之处,特别是匿名内部类。在学习这部分前专门又复习了一遍内部类的概念。ide

分类

成员内部类
局部内部类
静态内部类
匿名内部类函数

成员内部类

  1. 定义成员内部类后在建立该内部类的对象是不一样于普通类的,成员内部类是其外部类的属性。所以在建立时必须首先建立其外部类对象,再建立内部类的对象。内部类 对象名 = 外部类对象.new 内部类( );
  2. 外部类是不能直接使用内部类的成员和方法滴,可先建立内部类的对象,而后经过内部类的对象来访问其成员变量和方法。
  3. 可先建立内部类的对象,而后经过内部类的对象来访问其成员变量和方法。
public class Outer {
    
    private static int i = 1;
    private int j = 10;
    private int k = 20;


    public static void outerF1() {
    }

    /** * 外部类的静态方法访问成员内部类,与在外部类外部访问成员内部类同样 */
    public static void outerF4() {
        //step1 创建外部类对象
        Outer out = new Outer();
        //step2 根据外部类对象创建内部类对象
        Inner inner = out.new Inner();
        //step3 访问内部类的方法
        inner.innerF1();
    }

    public static void main(String[] args) {

        /* * outerF4();该语句的输出结果和下面三条语句的输出结果同样 *若是要直接建立内部类的对象,不能想固然地认为只需加上外围类Outer的名字, *就能够按照一般的样子生成内部类的对象,而是必须使用此外围类的一个对象来 *建立其内部类的一个对象: *Outer.Inner outin = out.new Inner() *所以,除非你已经有了外围类的一个对象,不然不可能生成内部类的对象。由于此 *内部类的对象会悄悄地连接到建立它的外围类的对象。若是你用的是静态的内部类, *那就不须要对其外围类对象的引用。 */
        Outer out = new Outer();
        Outer.Inner outin = out.new Inner();
        outin.innerF1();
    }

    public void outerF2() {
    }

    /** * 外部类的非静态方法访问成员内部类 */
    public void outerF3() {
        Inner inner = new Inner();
        inner.innerF1();
    }

    /** * 成员内部类中,不能定义静态成员 * 成员内部类中,能够访问外部类的全部成员 */
    class Inner {
        // static int innerI = 100;内部类中不容许定义静态变量
        // 内部类和外部类的实例变量能够共存
        int j = 100;
        int innerI = 1;


        void innerF1() {
            System.out.println(i);
            //在内部类中访问内部类本身的变量直接用变量名
            System.out.println(j);
            //在内部类中访问内部类本身的变量也能够用this.变量名
            System.out.println(this.j);
            //在内部类中访问外部类中与内部类同名的实例变量用外部类名.this.变量名
            System.out.println(Outer.this.j);
            //若是内部类中没有与外部类同名的变量,则能够直接用变量名访问外部类变量
            System.out.println(k);
            outerF1();
            outerF2();
        }
    }
}
复制代码
  1. 局部内部类

在方法中定义的内部类称为局部内部类。与局部变量相似,局部内部类不能有访问说明符,由于它不是外围类的一部分,可是它能够访问当前代码块内的常量,和此外围类全部的成员。学习

public class Outer {

    private int s = 100;
    private int outI = 1;

    public static void main(String[] args) {
        // 访问局部内部类必须先有外部类对象
        Outer out = new Outer();
        out.f(3);
    }

    public void f(final int k) {
        final int s = 200;
        int i = 1;
        final int j = 10;


        /**
         * 定义在方法内部
         */
        class Inner {
            // 能够定义与外部类同名的变量
            int s = 300;
            int innerI = 100;

            // static int m = 20; 不能够定义静态变量
            Inner(int k) {
                innerF(k);
            }
            void innerF(int k) {
                // java若是内部类没有与外部类同名的变量,在内部类中能够直接访问外部类的实例变量
                System.out.println(outI);
                // 能够访问外部类的局部变量(即方法内的变量),可是变量必须是final的
                System.out.println(j);
                //System.out.println(i);
                // 若是内部类中有与外部类同名的变量,直接用变量名访问的是内部类的变量
                System.out.println(s);
                // 用this.变量名访问的也是内部类变量
                System.out.println(this.s);
                // 用外部类名.this.内部类变量名访问的是外部类变量
                System.out.println(Outer.this.s);
            }
        }
        new Inner(k);
    }
}
复制代码
  1. 静态内部类(嵌套类)

若是你不须要内部类对象与其外围类对象之间有联系,那你能够将内部类声明为static。这一般称为嵌套类(nested class)。想要理解static应用于内部类时的含义,你就必须记住,普通的内部类对象隐含地保存了一个引用,指向建立它的外围类对象。然而,当内部类是static的时,就不是这样了。 要建立嵌套类的对象,并不须要其外围类的对象。 不能从嵌套类的对象中访问非静态的外围类对象。ui

public class Outer {
    private static int i = 1;
    private int j = 10;

    public static void outerF1() {
    }

    public static void main(String[] args) {
        new Outer().outerF3();
    }

    public void outerF2() {
    }

    public void outerF3() {
        // 外部类访问内部类的静态成员:内部类.静态成员
        System.out.println(Inner.inner_i);
        Inner.innerF1();
        // 外部类访问内部类的非静态成员:实例化内部类便可
        Inner inner = new Inner();
        inner.innerF2();
    }

    /**
     * 静态内部类能够用public,protected,private修饰
     * 静态内部类中能够定义静态或者非静态的成员
     */
    static class Inner {
        static int inner_i = 100;
        int innerJ = 200;

        static void innerF1() {
            // 静态内部类只能访问外部类的静态成员(包括静态变量和静态方法)
            System.out.println("Outer.i" + i);
            outerF1();
        }


        void innerF2() {
            // 静态内部类不能访问外部类的非静态成员(包括非静态变量和非静态方法)
            // System.out.println("Outer.i"+j);
            // outerF2();
        }
    }
}
复制代码

匿名内部类

这是咱们今天的主角,匿名内部类, 字面意思没有名字的类 {}。匿名内部做为最特殊的内部类,须要讲解的内容。(think in java)this

为何使用匿名内部类:编码

  1. 只用到类的一个实例
  2. 类在定义后立刻用到
  3. 类很是小(SUN推荐是在4行代码如下)
  4. 给类命名并不会致使你的代码更容易被理解

在使用匿名内部类时,要记住如下几个原则:

  1. 匿名内部类通常不能有构造方法。
  2. 匿名内部类不能定义任何静态成员、方法和类。
  3. 匿名内部类不能是public,protected,private,static。
  4. 只能建立匿名内部类的一个实例。
  5. 一个匿名内部类必定是在new的后面,用其隐含实现一个接口或实现一个类。
  6. 因匿名内部类为局部内部类,因此局部内部类的全部限制都对其生效。

你可能见过以下的代码:

List<Integer> var1 = new ArrayList<Integer>()
    {
        {
            add(1);
            add(2);
        }
    };
复制代码

就是用到匿名类的语法糖。

// 在方法中返回一个匿名内部类
public class Parcel6 {
    public static void main(String[] args) {
        Parcel6 p = new Parcel6();
        Contents c = p.cont();
    }

    public Contents cont() {
        return new Contents() {
            private int i = 11;


            public int value() {
                return i;
            }
        }; // 在这里须要一个分号
    }
}
复制代码

cont()方法将下面两个动做合并在一块儿:返回值的生成,与表示这个返回值的类的定义。 return new Contents() 可是,在到达语句结束的分号以前,你却说:“等一等,我想在这里插入一个类的定义”:
这种奇怪的语法指的是:“建立一个继承自Contents的匿名类的对象。”经过new 表达式返回的引用被自动向上转型为对Contents的引用。匿名内部类的语法是下面例子的简略形式:

class MyContents implements Contents {
    private int i = 11;

    public int value() {
        return i;
    }
}
return new MyContents();
复制代码

上述这类写法是最多见。

在Java中,一般就是编写另一个类或类库的人规定一个接口,而后你来实现这个接口,而后把这个接口的一个对象做为参数传给别人的程序,别人的程序必要时就会经过那个接口来调用你编写的函数,执行后续的一些方法。

public class CallBack {

    public static void main(String[] args) {
        CallBack callBack = new CallBack();
        callBack.toDoSomethings(100, new CallBackInterface() {
            public void execute() {
                System.out.println("个人请求处理成功了");
            }
        });

    }

    public void toDoSomethings(int a, CallBackInterface callBackInterface) {
        long start = System.currentTimeMillis();
        if (a > 100) {
            callBackInterface.execute();
        } else {
            System.out.println("a < 100 不须要执行回调方法");
        }
        long end = System.currentTimeMillis();
        System.out.println("该接口回调时间 : " + (end - start));
    }
}
public interface CallBackInterface {

    void execute();
}

复制代码

Java里的回调,能够说是匿名内部类精彩表演,优美的编码风格,真是让人陶醉~ this is so amazing 。

通过上述的铺垫引出下面的主角 lamada 表达式实现函数式接口。

Lambda语法糖

为了可以方便、快捷、幽雅的建立出FI的实例,Java8提供了Lambda表达式这颗语法糖。下面我用一个例子来介绍Lambda语法。假设咱们想对一个List按字符串长度进行排序,那么在Java8以前,能够借助匿名内部类来实现:

List<String> words = Arrays.asList("apple", "banana", "pear");
words.sort(new Comparator<String>() {
 
    @Override
    public int compare(String w1, String w2) {
        return Integer.compare(w1.length(), w2.length());
    }
 
});

复制代码

上面的匿名内部类简直能够用丑陋来形容,惟一的一行逻辑被五行垃圾代码淹没。根据前面的定义(并查看Java源代码)可知,Comparator是个FI,因此,能够用Lambda表达式来实现:

words.sort((String w1, String w2) -> {
    return Integer.compare(w1.length(), w2.length());
});
复制代码

ps: 看起来像一个匿名的方法,实际就是一个匿名类对象的引用,代码看起来更加简洁。能够认为 lambda表达式实现了接口的抽象方法,由于函数式接口默认只有一个抽象方法。

参考文献:
函数式接口概念
详解内部类

相关文章
相关标签/搜索