函数式接口
理解Functional Interface(函数式接口,以下简称FI)是学习Java8 Lambda表达式的关键所在,所以放在最开始讨论。FI的定义其实很简单:任何接口,如果只包含唯一一个抽象方法,那么它就是一个FI。为了让编译器帮助我们确保一个接口满足FI的要求(也就是说有且仅有一个抽象方法),Java8提供了@FunctionalInterface注解。举个简单的例子,Runnable接口就是一个FI,下面是它的源代码:
1 | 复制代码@Functional Interface |
ps: 上述两种都是函数式接口,在Java 8 提供新的注解Function Interface
声明一个接口为函数式接口,声明之后这个接口必须符合函数式接口的规范。@FunctionalInterface 对于接口是不是函数式接口没有影响,但该注解知识提醒编译器去检查该接口是否仅包含一个抽象方法。
下面的用法就是错误:
1 | 复制代码@Function Interface |
ps: 接口中声明的方法默认是抽象的,使用注解后,编译器自动检查发现存在两个抽象方法,会报错。
函数式接口的规范
- 函数式接口里是可以包含默认方法,因为默认方法不是抽象方法,其有一个默认实现,所以是符合函数式接口的定义的;
1 | 复制代码 @FunctionalInterface |
- 函数式接口里是可以包含静态方法,因为静态方法不能是抽象方法,是一个已经实现了的方法,所以是符合函数式接口的定义的;
1 | 复制代码 @FunctionalInterface |
- 函数式接口里是可以包含Object里的public方法,这些方法对于函数式接口来说,不被当成是抽象方法(虽然它们是抽象方法);因为任何一个函数式接口的实现,默认都继承了Object类,包含了来自java.lang.Object里对这些抽象方法的实现。
1 | 复制代码 @FunctionalInterface |
Java 内部类
为什么讲内部类,因为在 lamada 表达式又与 Java 内部类应用存在相似之处,特别是匿名内部类。在学习这部分前专门又复习了一遍内部类的概念。
分类
成员内部类
局部内部类
静态内部类
匿名内部类
成员内部类
- 定义成员内部类后在创建该内部类的对象是不同于普通类的,成员内部类是其外部类的属性。因此在创建时必须首先创建其外部类对象,再创建内部类的对象。
内部类 对象名 = 外部类对象.new 内部类( );
- 外部类是不能直接使用内部类的成员和方法滴,可先创建内部类的对象,然后通过内部类的对象来访问其成员变量和方法。
- 可先创建内部类的对象,然后通过内部类的对象来访问其成员变量和方法。
1 | 复制代码public class Outer { |
- 局部内部类
在方法中定义的内部类称为局部内部类。与局部变量类似,局部内部类不能有访问说明符,因为它不是外围类的一部分,但是它可以访问当前代码块内的常量,和此外围类所有的成员。
1 | 复制代码public class Outer { |
- 静态内部类(嵌套类)
如果你不需要内部类对象与其外围类对象之间有联系,那你可以将内部类声明为
static
。这通常称为嵌套类(nested class)。想要理解static应用于内部类时的含义,你就必须记住,普通的内部类对象隐含地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样了。
要创建嵌套类的对象,并不需要其外围类的对象。
不能从嵌套类的对象中访问非静态的外围类对象。
1 | 复制代码public class Outer { |
匿名内部类
这是我们今天的主角,匿名内部类, 字面意思没有名字的类
{}
。匿名内部作为最特殊的内部类,需要讲解的内容。(think in java)
为什么使用匿名内部类:
- 只用到类的一个实例
- 类在定义后马上用到
- 类非常小(SUN推荐是在4行代码以下)
- 给类命名并不会导致你的代码更容易被理解
在使用匿名内部类时,要记住以下几个原则:
- 匿名内部类一般不能有构造方法。
- 匿名内部类不能定义任何静态成员、方法和类。
- 匿名内部类不能是public,protected,private,static。
- 只能创建匿名内部类的一个实例。
- 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
- 因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
你可能见过如下的代码:
1 | 复制代码 List<Integer> var1 = new ArrayList<Integer>() |
就是用到匿名类的语法糖。
1 | 复制代码// 在方法中返回一个匿名内部类 |
cont()方法将下面两个动作合并在一起:返回值的生成,与表示这个返回值的类的定义。
return new Contents()
但是,在到达语句结束的分号之前,你却说:“等一等,我想在这里插入一个类的定义”:
这种奇怪的语法指的是:“创建一个继承自Contents的匿名类的对象。”通过new 表达式返回的引用被自动向上转型为对Contents的引用。匿名内部类的语法是下面例子的简略形式:
1 | 复制代码class MyContents implements Contents { |
上述这类写法是最常见。
在Java中,通常就是编写另外一个类或类库的人规定一个接口,然后你来实现这个接口,然后把这个接口的一个对象作为参数传给别人的程序,别人的程序必要时就会通过那个接口来调用你编写的函数,执行后续的一些方法。
1 | 复制代码public class CallBack { |
Java里的回调,可以说是匿名内部类精彩表演,优美的编码风格,真是让人陶醉~ this is so amazing 。
经过上述的铺垫引出下面的主角 lamada 表达式实现函数式接口。
Lambda语法糖
为了能够方便、快捷、幽雅的创建出FI的实例,Java8提供了Lambda表达式这颗语法糖。下面我用一个例子来介绍Lambda语法。假设我们想对一个List按字符串长度进行排序,那么在Java8之前,可以借助匿名内部类来实现:
1 | 复制代码List<String> words = Arrays.asList("apple", "banana", "pear"); |
上面的匿名内部类简直可以用丑陋来形容,唯一的一行逻辑被五行垃圾代码淹没。根据前面的定义(并查看Java源代码)可知,Comparator是个FI,所以,可以用Lambda表达式来实现:
1 | 复制代码words.sort((String w1, String w2) -> { |
ps: 看起来像一个匿名的方法,实际就是一个匿名类对象的引用,代码看起来更加简洁。可以认为 lambda表达式实现了接口的抽象方法,因为函数式接口默认只有一个抽象方法。
参考文献:
本文转载自: 掘金