理解Functional Interface(函数式接口,以下简称FI)是学习Java8 Lambda表达式的关键所在,所以放在最开始讨论。FI的定义其实很简单:任何接口,如果只包含唯一一个抽象方法,那么它就是一个FI。为了让编译器帮助我们确保一个接口满足FI的要求(也就是说有且仅有一个抽象方法),Java8提供了@FunctionalInterface注解。举个简单的例子,Runnable接口就是一个FI,下面是它的源代码:
@Functional Interface public interface Runnable{ public abstract void run (); } public interface Runnable { public abstract void run (); } 复制代码
ps: 上述两种都是函数式接口,在Java 8 提供新的注解 Function Interface
声明一个接口为函数式接口,声明之后这个接口必须符合函数式接口的规范。@FunctionalInterface 对于接口是不是函数式接口没有影响,但该注解知识提醒编译器去检查该接口是否仅包含一个抽象方法。
下面的用法就是错误:
@Function Interface public interface test{ public void test1(); public void test2(); 复制代码
ps: 接口中声明的方法默认是抽象的,使用注解后,编译器自动检查发现存在两个抽象方法,会报错。
@FunctionalInterface interface GreetingService { void sayMessage(String message); default void doSomeMoreWork1() { // Method body } default void doSomeMoreWork2() { // Method body } } 复制代码
@FunctionalInterface interface GreetingService { public void sayMessage(String message); public static void printHello(){ System.out.println("Hello"); } } 复制代码
@FunctionalInterface interface GreetingService { void sayMessage(String message); @Override boolean equals(Object obj); } 复制代码
为什么讲内部类,因为在 lamada 表达式又与 Java 内部类应用存在相似之处,特别是匿名内部类。在学习这部分前专门又复习了一遍内部类的概念。
成员内部类
局部内部类
静态内部类
匿名内部类
内部类 对象名 = 外部类对象.new 内部类( );
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(); } } } 复制代码
在方法中定义的内部类称为局部内部类。与局部变量类似,局部内部类不能有访问说明符,因为它不是外围类的一部分,但是它可以访问当前代码块内的常量,和此外围类所有的成员。
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); } } 复制代码
如果你不需要内部类对象与其外围类对象之间有联系,那你可以将内部类声明为 static
。这通常称为嵌套类(nested class)。想要理解static应用于内部类时的含义,你就必须记住,普通的内部类对象隐含地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样了。
要创建嵌套类的对象,并不需要其外围类的对象。
不能从嵌套类的对象中访问非静态的外围类对象。
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)
为什么使用匿名内部类:
在使用匿名内部类时,要记住以下几个原则:
你可能见过如下的代码:
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 表达式实现函数式接口。
为了能够方便、快捷、幽雅的创建出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: 看起来像一个匿名的方法,实际就是一个匿名类对象的引用,代码看起来更加简洁。可以认为 lamada表达式实现了接口的抽象方法,因为函数式接口默认只有一个抽象方法。
参考文献:
函数式接口概念
详解内部类