匿名内部类适合那些只需要使用一次的类,比如在对按钮等进行事件监听的时候会用到
Lambda表达式(lambda expression
)是一个匿名函数,即没有函数名的函数。
lambda表达式大量替代匿名内部类的使用,简化代码的同时,更突出了原来匿名内部类中最重要的那部分包含真正逻辑的代码。
一、匿名内部类
定义
匿名内部类没有名字,其定义格式如下:
1 2 3 4
| new 父类构造器(参数列表)|实现接口() { }
|
如下所示为一个典型的常用内部类:
1 2 3 4 5 6
| new View.OnClickListener(){ @Override public void onClick(View v) {
} };
|
匿名内部类适合那些只需要使用一次的类,比如在对按钮等进行事件监听的时候会用到,但是上面例子中的OnClickListener是一个接口,对接口进行new显然是错误的。
下面来还原一个完整的匿名内部类应该就很清楚了。
1 2 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这个类,如下所示:
1 2 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生成一个对象的引用。如下所示:
1 2 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怎么进行相应初始化呢?
使用构造代码块进行初始化,可以达到一个构造器的效果。
1 2 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的代码块主要分为四种、
- 普通代码块
普通代码块是不能够单独存在的,它必须要紧跟在方法名后面。同时也必须要使用方法名调用它。
1 2 3 4 5
| public class Test { public void test(){ System.out.println("普通代码块"); } }
|
- 静态代码块
想到静态我们就会想到static,静态代码块就是用static修饰的用{}括起来的代码段,它的主要目的就是对静态属性进行初始化。
1 2 3 4 5
| public class Test { static{ System.out.println("静态代码块"); } }
|
- 同步代码块
使用 synchronized 关键字修饰,并使用“{}”括起来的代码片段,它表示同一时间只能有一个线程进入到该方法块中,是一种多线程保护机制。
- 构造代码块
在类中直接定义没有任何修饰符、前缀、后缀的代码块即为构造代码块。我们明白一个类必须至少有一个构造函数,构造函数在生成对象时被调用。构造代码块和构造函数一样同样是在生成一个对象时被调用。
1 2 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("执行有参构造函数..."); } }
|
上面定义了一个非常简单的类,该类包含无参构造函数、有参构造函数以及构造代码块,同时在上面也提过代码块是没有独立运行的能力,他必须要有一个可以承载的载体,那么编译器会如何来处理构造代码块呢?编译器会将代码块按照他们的顺序(假如有多个代码块)插入到所有的构造函数的最前端,这样就能保证不管调用哪个构造函数都会执行所有的构造代码块。上面代码等同于如下形式:
1 2 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("执行有参构造函数..."); }
}
|
运行结果如下
1 2 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表达式的功能相当强大,用()->就可以代替整个匿名内部类!
这是我们上面刚说的匿名内部类
1 2 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(); }
|
下面我们看看怎么用表达式写出来
1 2 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#里提过。
1 2 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肯定也是支持的。
1 2 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实现的则是将所有值合并为一个。
1 2 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也是我们经常使用的一个操作。在操作集合的时候,经常需要从原始的集合中过滤掉一部分元素。
1 2 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接口非常适用于做过滤。
1 2 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表达式的魔力。