匿名内部类适合那些只需要使用一次的类,比如在对按钮等进行事件监听的时候会用到
Lambda表达式(lambda expression)是一个匿名函数,即没有函数名的函数。
lambda表达式大量替代匿名内部类的使用,简化代码的同时,更突出了原来匿名内部类中最重要的那部分包含真正逻辑的代码。
一、匿名内部类
定义
匿名内部类没有名字,其定义格式如下:
| 12
 3
 4
 
 | new 父类构造器(参数列表)|实现接口()  {
 
 }
 
 | 
如下所示为一个典型的常用内部类:
| 12
 3
 4
 5
 6
 
 | new View.OnClickListener(){@Override
 public void onClick(View v) {
 
 }
 };
 
 | 
匿名内部类适合那些只需要使用一次的类,比如在对按钮等进行事件监听的时候会用到,但是上面例子中的OnClickListener是一个接口,对接口进行new显然是错误的。
下面来还原一个完整的匿名内部类应该就很清楚了。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | abstract class Person {public abstract void eat();
 }
 
 class Child extends Person {
 public void eat() {
 System.out.println("eat something");
 }
 }
 
 public class Demo {
 public static void main(String[] args) {
 Person p = new Child();
 p.eat();
 }
 }
 
 | 
Person是一个抽象类,Child继承这个类成为一个子类,main函数里实例化这个类,其中我们需要在匿名内部类中隐藏Child这个类,如下所示:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | abstract class Person {public abstract void eat();
 }
 
 public class Demo {
 public static void main(String[] args) {
 Person p = new Person() {
 public void eat() {
 System.out.println("eat something");
 }
 };
 p.eat();
 }
 }
 
 | 
我们可以看到,这样相当于在实例化的时候直接重写了Child这个类,这就是匿名内部类。
必须继承一个父类或者实现一个接口,没有class关键字,直接使用new生成一个对象的引用。如下所示:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 
 | public abstract class Bird {private String name;
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
 public abstract int fly();
 }
 
 public class Test {
 public void test(Bird bird){
 System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");
 }
 public static void main(String[] args) {
 Test test = new Test();
 test.test(new Bird() {
 public int fly() {
 return 10000;
 }
 public String getName() {
 return "大雁";
 }
 });
 }
 }
 ------------------
 Output:
 大雁能够飞 10000米
 
 | 
test方法接收一个Bird类型参数,但是我们都知道一个抽象类是不能进行实例化的,也就是直接new,所以我们必须要有一个实现类才可以进行new操作,但是由于它是一个抽象类,所以通过匿名内部类创建一个Bird实例。又因为匿名内部类不能是抽象类,所以我们必须要实现抽象父类或者接口里的所有抽象方法才行。
注意事项
- 使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
- 匿名内部类中是不能定义构造函数的。
- 匿名内部类中不能存在任何的静态成员变量和静态方法。
- 匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
匿名内部类的初始化
它没有构造函数,name怎么进行相应初始化呢?
使用构造代码块进行初始化,可以达到一个构造器的效果。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 
 | public class OutClass {public InnerClass getInnerClass(final int age,final String name){
 return new InnerClass() {
 int age_ ;
 String name_;
 
 {
 if(0 < age && age < 200){
 age_ = age;
 name_ = name;
 }
 }
 public String getName() {
 return name_;
 }
 
 public int getAge() {
 return age_;
 }
 };
 }
 
 public static void main(String[] args) {
 OutClass out = new OutClass();
 
 InnerClass inner_1 = out.getInnerClass(201, "chenssy");
 System.out.println(inner_1.getName());
 
 InnerClass inner_2 = out.getInnerClass(23, "chenssy");
 System.out.println(inner_2.getName());
 }
 }
 
 | 
补充:Java中的代码块
代码块就是用{}包起来的代码,进行封装,形成一个独立的数据体,用于实现特定的算法。代码块不能单独运行,必须要有一个运行主体。
Java的代码块主要分为四种、
- 普通代码块
 普通代码块是不能够单独存在的,它必须要紧跟在方法名后面。同时也必须要使用方法名调用它。
| 12
 3
 4
 5
 
 | public class Test {public void test(){
 System.out.println("普通代码块");
 }
 }
 
 | 
- 静态代码块
 想到静态我们就会想到static,静态代码块就是用static修饰的用{}括起来的代码段,它的主要目的就是对静态属性进行初始化。
| 12
 3
 4
 5
 
 | public class Test {static{
 System.out.println("静态代码块");
 }
 }
 
 | 
- 同步代码块
 使用 synchronized 关键字修饰,并使用“{}”括起来的代码片段,它表示同一时间只能有一个线程进入到该方法块中,是一种多线程保护机制。
- 构造代码块
 在类中直接定义没有任何修饰符、前缀、后缀的代码块即为构造代码块。我们明白一个类必须至少有一个构造函数,构造函数在生成对象时被调用。构造代码块和构造函数一样同样是在生成一个对象时被调用。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | public class Test {
 
 
 {
 System.out.println("执行构造代码块...");
 }
 
 
 
 
 public Test(){
 System.out.println("执行无参构造函数...");
 }
 
 
 
 
 
 public Test(String id){
 System.out.println("执行有参构造函数...");
 }
 }
 
 | 
上面定义了一个非常简单的类,该类包含无参构造函数、有参构造函数以及构造代码块,同时在上面也提过代码块是没有独立运行的能力,他必须要有一个可以承载的载体,那么编译器会如何来处理构造代码块呢?编译器会将代码块按照他们的顺序(假如有多个代码块)插入到所有的构造函数的最前端,这样就能保证不管调用哪个构造函数都会执行所有的构造代码块。上面代码等同于如下形式:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | public class Test {
 
 
 public Test(){
 System.out.println("执行构造代码块...");
 System.out.println("执行无参构造函数...");
 }
 
 
 
 
 
 public Test(String id){
 System.out.println("执行构造代码块...");
 System.out.println("执行有参构造函数...");
 }
 
 }
 
 | 
运行结果如下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | public static void main(String[] args) {new Test();
 System.out.println("----------------");
 new Test("1");
 }
 ------------
 Output:
 执行构造代码块...
 执行无参构造函数...
 ----------------
 执行构造代码块...
 执行有参构造函数...
 
 | 
从上面的运行结果可以看出在new一个对象的时候总是先执行构造代码,再执行构造函数,但是有一点需要注意构造代码不是在构造函数之前运行的,它是依托构造函数执行的。
各个代码块执行顺序为:静态代码块 > 构造代码块 > 构造函数。
lambda表达式
“Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。
Java8发布已经有一段时间了,这次发布的改动比较大,很多人将这次改动与Java5的升级相提并论。Java8其中一个很重要的新特性就是lambda表达式,允许我们将行为传到函数中。想想看,在Java8之前我们想要将行为传入函数,仅有的选择就是匿名内部类。Java8发布以后,lambda表达式将大量替代匿名内部类的使用,简化代码的同时,更突出了原来匿名内部类中最重要的那部分包含真正逻辑的代码。
下面看看一些常用写法:
替代匿名内部类
毫无疑问,lambda表达式用得最多的场合就是替代匿名内部类,而实现Runnable接口是匿名内部类的经典例子。lambda表达式的功能相当强大,用()->就可以代替整个匿名内部类!
这是我们上面刚说的匿名内部类
| 12
 3
 4
 5
 6
 7
 8
 
 | public void oldRunable() {new Thread(new Runnable() {
 @Override
 public void run() {
 System.out.println("The old runable now is using!");
 }
 }).start();
 }
 
 | 
下面我们看看怎么用表达式写出来
| 12
 3
 4
 5
 6
 7
 
 | public void runable() {new Thread(() -> System.out.println("It's a lambda function!")).start();
 }
 
 输出结果
 The old runable now is using!
 It's a lambda function!
 
 | 
使用lambda表达式对集合进行迭代
Java的集合类是日常开发中经常用到的,甚至说没有哪个java代码中没有使用到集合类…而对集合类最常见的操作就是进行迭代遍历了。
虽然Java里没用过foreach,但是我在c#里提过。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | public void iterTest() {List<String> languages = Arrays.asList("java","scala","python");
 
 for(String each:languages) {
 System.out.println(each);
 }
 
 languages.forEach(x -> System.out.println(x));
 languages.forEach(System.out::println);
 }
 
 | 
用lambda表达式实现map
至于什么是map可以百度以下。
一提到函数式编程,一提到lambda表达式,怎么能不提map…没错,java8肯定也是支持的。
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | public void mapTest() {List<Double> cost = Arrays.asList(10.0, 20.0,30.0);
 cost.stream().map(x -> x + x*0.05).forEach(x -> System.out.println(x));
 }
 
 输出结果
 10.5
 21.0
 31.5
 
 | 
map函数可以说是函数式编程里最重要的一个方法了。map的作用是将一个对象变换为另外一个。在我们的例子中,就是通过map方法将cost增加了0.05倍的大小然后输出。
用lambda表达式实现map与reduce
既然提到了map,又怎能不提到reduce。reduce与map一样,也是函数式编程里最重要的几个方法之一…map的作用是将一个对象变为另外一个,而reduce实现的则是将所有值合并为一个。
| 12
 3
 4
 5
 6
 7
 8
 
 | public void mapReduceTest() {List<Double> cost = Arrays.asList(10.0, 20.0,30.0);
 double allCost = cost.stream().map(x -> x+x*0.05).reduce((sum,x) -> sum + x).get();
 System.out.println(allCost);
 }
 
 输出结果
 63.0
 
 | 
filter操作
filter也是我们经常使用的一个操作。在操作集合的时候,经常需要从原始的集合中过滤掉一部分元素。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | public void filterTest() {List<Double> cost = Arrays.asList(10.0, 20.0,30.0,40.0);
 List<Double> filteredCost = cost.stream().filter(x -> x > 25.0).collect(Collectors.toList());
 filteredCost.forEach(x -> System.out.println(x));
 
 }
 
 输出结果
 30.0
 40.0
 
 | 
与函数式接口Predicate配合
除了在语言层面支持函数式编程风格,Java 8也添加了一个包,叫做 java.util.function。它包含了很多类,用来支持Java的函数式编程。其中一个便是Predicate,使用 java.util.function.Predicate 函数式接口以及lambda表达式,可以向API方法添加逻辑,用更少的代码支持更多的动态行为。Predicate接口非常适用于做过滤。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 
 | public static void filterTest(List<String> languages, Predicate<String> condition) {languages.stream().filter(x -> condition.test(x)).forEach(x -> System.out.println(x + " "));
 }
 
 public static void main(String[] args) {
 List<String> languages = Arrays.asList("Java","Python","scala","Shell","R");
 System.out.println("Language starts with J: ");
 filterTest(languages,x -> x.startsWith("J"));
 System.out.println("\nLanguage ends with a: ");
 filterTest(languages,x -> x.endsWith("a"));
 System.out.println("\nAll languages: ");
 filterTest(languages,x -> true);
 System.out.println("\nNo languages: ");
 filterTest(languages,x -> false);
 System.out.println("\nLanguage length bigger three: ");
 filterTest(languages,x -> x.length() > 4);
 }
 
 运行结果
 Language starts with J:
 Java
 
 Language ends with a:
 Java
 scala
 
 All languages:
 Java
 Python
 scala
 Shell
 R
 
 No languages:
 
 Language length bigger three:
 Python
 scala
 Shell
 
 | 
可以看到,Stream API的过滤方法也接受一个Predicate,这意味着可以将我们定制的 filter() 方法替换成写在里面的内联代码,这也是lambda表达式的魔力。