目录
Lambda表达式不等价匿名内部类
Lambda表达式不等价匿名内部类,但是为了理解方便,可以将Lambda表达式认为是匿名内部类的简写形式,语法糖。
匿名内部类实现
匿名内部类仍然是一个类,主类Main.class的字节码,可发现其创建了匿名内部类的对象。
匿名内部类仍然是一个类,只是不需要程序员显示指定类名,编译器会自动为该类取名。因此如果有如下形式的代码,编译之后将会产生两个class文件:
public class MainAnonymousClass {
public static void main(String[] args) {
new Thread(new Runnable(){
public void run(){
System.out.println("Anonymous Class Thread run()");
}
}).start();;
}
}
编译之后文件分布如下,两个class文件分别是主类和匿名内部类MainAnonymousClass$1产生的:
进一步分析主类MainAnonymousClass.class的字节码,可发现其创建了匿名内部类的对象:
// javap -c MainAnonymousClass.class
public class MainAnonymousClass {
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/Thread
3: dup
4: new #3 // class MainAnonymousClass$1 //创建内部类对象
7: dup
8: invokespecial #4 // Method MainAnonymousClass$1."<init>":()V
11: invokespecial #5 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
14: invokevirtual #6 // Method java/lang/Thread.start:()V
17: return
}
Lambda表达式实现
Lambda表达式被封装成了主类的一个私有方法lambda$main$0(),书写Lambda表达式不会产生新的类。
Lambda表达式通过invokedynamic指令实现,书写Lambda表达式不会产生新的类。如果有如下代码,编译之后只有一个class文件:
public class MainLambda {
public static void main(String[] args) {
new Thread(
() -> System.out.println("Lambda Thread run()") //lambda表达式
).start();
}
}
编译之后的结果:
通过javap反编译命名,我们更能看出Lambda表达式内部表示的不同:
// javap -c -p MainLambda.class
public class MainLambda {
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/Thread
3: dup
4: invokedynamic #3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable; /*使用invokedynamic指令调用*/
9: invokespecial #4 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
12: invokevirtual #5 // Method java/lang/Thread.start:()V
15: return
private static void lambda$main$0(); /*Lambda表达式被封装成主类的私有方法*/
Code:
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #7 // String Lambda Thread run()
5: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
反编译之后我们发现Lambda表达式被封装成了主类的一个私有方法lambda$main$0(),并通过invokedynamic指令进行调用。
Lambda表达式内部,this引用的意义
Lambda表达式被封装成类的一个私有方法lambda$main$0(),内部的this指向lambda表达式的当前宿主对象。
既然Lambda表达式不是内部类的简写,那么Lambda内部的this引用也就跟内部类对象没什么关系了。在Lambda表达式中this的意义跟在表达式外部完全一样,lambda表达式的内部的this,指向lambda表达式的当前宿主对象。因此下列代码将输出两遍 Hoolee,而不是两个引用地址。
public class Hello {
/**
*r1 lambda表达式的宿主对象是当前Hello类的实例
* lambda表达式内部的this指向当前Hello实例,所以运行打印的是Hoolee
*/
Runnable r1 = () -> { System.out.println(this); };
/**
*r2 lambda表达式的宿主对象是当前Hello类的实例
* lambda表达式被JVM编译成一个私有方法,所以可以访问类的toString()方法
*/
Runnable r2 = () -> { System.out.println(toString()); };
public static void main(String[] args) {
new Hello().r1.run();
new Hello().r2.run();
}
public String toString() { return "Hoolee"; }
}
Lambda表达式与设计模式
Lambda表达式,不过是个语法糖。jdk8引入了Lambda之后,并没有带来的GOF设计模式(23种)之外的新的设计模式,不过是利用lambda的语法糖的简易写法,可以引入到不少传统的设计模式中,使得我们的代码显得更加优雅而已。
策略模式
概念:
策略模式思想是针对一组算法,将每一种算法都封装到具有共同接口的独立的类中,从而是它们可以相互替换。策略模式的最大特点是使得算法可以在不影响客户端的情况下发生变化,从而改变不同的功能。
特点:
策略模式体现了面向对象程序设计中非常重要的两个原则:
- 封装变化的概念。
- 编程中使用接口,而不是使用的是具体的实现类(面向接口编程)。
图示:
举例:
interface ValidationStrategy { //函数式接口:通用的策略
boolean execute(String s);
}class IsAllLowerCase implements ValidationStrategy { //具体的策略实现方案1
public boolean execute(String s){
return s.matches("[a-z]+"); //这才是我们真正关心的业务核心逻辑
}
}class IsNumeric implements ValidationStrategy { //具体的策略实现方案2
public boolean execute(String s){
return s.matches("\\d+"); //这才是我们真正关心的业务核心逻辑
}
}public class Validator{ //业务类
public static boolean validate(ValidationStrategy strategy, String s){
return strategy.execute(s); //多态,面向接口编程
}
}Validator.validate( new IsAllLowerCase () , "aaa" ); //测试:入参:策略实现方案1
Validator.validate( new IsNumeric() , "bbb" ); //测试:入参:策略实现方案2
我们可以稍加改造成如下形式,通过引入lambda表达式,这样就不需要死模板代码,具体的策略实现方案不再需要抒写,就像我上方示例中的策略具体实现方案,就属于“死模板代码”
interface ValidationStrategy { //函数式接口:通用的策略
boolean execute(String s);
}public class Validator{ //业务类
public static boolean validate(ValidationStrategy strategy, String s){
return strategy.execute(s); //多态,面向接口编程
}
}Validator.validate( (String s) -> s.matches("[a-z]+"), "aaa" ); //测试:入参:lambda表达式
Validator.validate( (String s) -> s.matches("\\d+"), "bbb" ); //测试:入参:lambda表达式
模板模式
概念:
定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。
通俗点的理解就是 :完成一件事情,有固定的数个步骤,但是每个步骤根据对象的不同,而实现细节不同;就可以在父类中定义一个完成该事情的总方法,按照完成事件需要的步骤去调用其每个步骤的实现方法。每个步骤的具体实现,由子类完成。(细细体会,还是面向接口编程的思想)
特点:
模板模式体现了面向对象程序设计中非常重要的两个原则:
- 封装业务主体结构的骨架。
- 业务处理步骤中,使用抽象方法,而不是使用的是具体的实现类(面向接口编程)。
图示:
举例:
abstract class Bank{
//模板算法主体结构
public void process(int id) {
validLogin(id); //银行账户登录
Customer c = Database.getCustomerWithId(id); //获取账户信息
makeHappy(c); //客户满意度调查(不同分行是不同的满意问卷)
}//模板内的抽象方法
public abstract void makeHappy(Customer c);}
public class BankICBC extends Bank{ //具体子类:工行
public void makeHappy(Customer c){//模板算法结构内的抽象方法的具体实现
//工行满意问卷
}
}public class BankCCB extends Bank{ //具体子类:建行
public void makeHappy(Customer c){//模板算法结构内的抽象方法的具体实现
//建行满意问卷
}
}
我们可以稍加改造成如下形式,通过引入lambda表达式,这样就不需要死模板代码,具体的子类不再需要抒写,就像我上方示例中的工行/建行等具体实现方案,就属于“死模板代码”
class Bank{
//模板算法主体结构:引入lambda《行为值》,作为入参
public void process(int id, Consumer<Customer> makeHappy) {//lambda行为参数化
validLogin(id); //银行账户登录
Customer c = Database.getCustomerWithId(id); //获取账户信息
makeHappy.accept(c); //客户满意度调查(不同分行传入不同的lambda表达式)
}
}备注:Consumer<T>是jdk8引入的函数式接口,在java.util.function包中,lambda简易形式为:(T) -> void 消费入参T,无返回值的意思。
观察者模式
概念:
在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。
特点:
观察者模式体现了面向对象程序设计中非常重要的一个原则:
- 事件驱动开发:发生了某事件,通知所有的事件监听器。
图示:
举例:
import java.util.*;
/**
* jdk8中,引入
* Observable抽象类:被观察对象(事件源)
* Observer函数式接口:观察者(事件处理器)
*/
class ObservableProducer extends Observable
{
public ObservableProducer(String name){
this.name = name ;
}private String name;
public String getName() { return name ; }
public void setName(String name) {
this.name = name ;
setChanged(); //被观察对象状态变化,setChanged()方法内部通知所有观察者
}public String toString(){
return name;
}
}public class ObserverConsumerDemo
{
public static void main(String[] args)
{
ObservableProducer produce = new ObservableProducer("dindoa"); //被观察对象produce.addObserver(
(p,arg) -> System.out.println(p) ); //注册观察者,消费被观察对象的状态改变
produce.addObserver(
(p,arg) -> System.out.println(((ObservableProducer)p).getName()+"...") );//被观察对象状态改变
produce.setName("dindoaChanged");
//produce.setChanged();
//编译不通过,因为protected方法只能在 类 或者 子类 的类内部加以访问
//通知所有观察者
produce.notifyObservers();
}
}
以上观察者示例程序运行,输出:
dindoaChanged...
dindoaChanged
责任链模式
概念:
为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式,这个模式看起来像是在链接函数,函数内部调用另一个函数,依次持续下去。
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
特点:
责任链模式体现了面向对象程序设计中非常重要的一个原则:
- 解耦:对请求的发送者和接收者进行解耦。
图示:
web开发中的Filter过滤器
举例:
UnaryOperator<String> headerProcessing =
(String text) -> "From Raoul, Mario and Alan: " + text; //第1层过滤处理UnaryOperator<String> spellCheckerProcessing =
(String text) -> text.replaceAll("labda", "lambda"); //第2层过滤处理Function<String, String> pipeline =
headerProcessing.andThen(spellCheckerProcessing); //责任链/管道线(函数链接起来)String result = pipeline.apply("Aren't labdas really sexy?!!"); //测试
备注:jdk8引入的函数式接口,在java.util.function包中
- UnaryOperator<T>是函数式接口,lambda简易形式为:(T) -> T 消费入参T类类型,返回值T类类型
- Function<T,R>也是函数式接口,lambda简易形式为:(T) -> R 消费入参T类类型,返回值R类类型
工厂模式
概念:
无需向客户暴露实例化的逻辑就能完成对象的创建。常见于一些第三方框架。尤其是enum枚举类型的单例模式时,更是体现的淋漓尽致。
特点:
工厂模式体现了面向对象程序设计中非常重要的一个原则:
- 封装完整性:实例化对象的细节被封装并且是完整的。
图示:
举例:
//金融产品生产工厂:金融产品有:贷款、期权、股票、等等
public class ProductFactory {
public static Product createProduct(String name){
switch(name){
case "loan": return new Loan();
case "stock": return new Stock();
case "bond": return new Bond();
default: throw new RuntimeException("No such product " + name);
}
}
}Product p = ProductFactory.createProduct("loan"); //测试:生产loan产品
我们可以稍加改造成如下形式,通过引入lambda表达式的语法糖(俗称语法糖的语法糖)。其实,以下形式,并没有多么的精简代码,反而感觉代码更多了。
public class ProductFactory {
final static Map<String, Supplier<Product>> map = new HashMap<>();
static {
map.put("loan", Loan::new);
map.put("stock", Stock::new);
map.put("bond", Bond::new);
}public static Product createProduct(String name){
Supplier<Product> p = map.get(name);
if(p != null) return p.get();
throw new IllegalArgumentException("No such product " + name);
}
}Product p = ProductFactory.createProduct("loan"); //测试:生产loan产品
备注:jdk8引入的函数式接口,在java.util.function包中
- Supplier<T>是函数式接口,lambda简易形式为:() -> T 入参无,生产出一个T类类型的对象
Lambda表达式与面向接口编程与函数式接口
通过以上几种设计模式结合lambda表达式的编程手法,看问题本质,不难发现,其实它们都有这个特点:
体现面向接口编程,主体业务逻辑结构大致都可以归结为静态代理模式的体现,A方法主体业务逻辑结构内部,调用了x对象的抽象B方法,抽象B方法可以当成A方法的入参,利用 "lambda表达式《行为值》"作为入参这种手法,来组织A方法。在此,也可以说,A方法静态代理了B抽象方法。
/**
* 主体业务逻辑结构:A方法(形参:函数式接口)
*/
public void A(Function B){ //实参:传入的B是lambda表达式
//xxx逻辑
B.bbb(); // 函数式接口中的抽象方法//yyy逻辑
}
函数的副作用
概念:
无副作用的意思就是: 一个函数(java里是方法)的多次调用中,只要输入参数的值相同,输出结果的值也必然相同,并且在这个函数执行过程中不会改变程序的任何外部状态(比如全局变量,对象中的属性,都属于外部状态),也不依赖于程序的任何外部状态。
举例:
//无副作用的
public class NoSideEffect {
public static int add(int a, int b) { //调用add方法N次,a,b入参固定,最终结果恒定
return a+b;
}
public boolean isEmpty(String s) { //调用isEmpty方法N次,s入参固定,最终结果恒定
if (s == null || s.trim().length() == 0) {
return true;
}
return false;
}
}
//有副作用的
public class HasSideEffect {
public int v; //不同的实例对象,有可能v不同
public int getCount(int addtion) {//调用N次,v不同,最终结果不同
return v + addtion;
}
}
无副作用的要求可以很严格,在Fp(functional programing)的语言如lisp clojure中,无副作用的方法中连print都不能有,因为他们认为连屏幕也是外部状态,我个人觉得在写java的无副作用代码时,可以放宽一点,这个度可以自己把握,自己用着爽就ok。
“线程安全”是java中一个比较常见的概念,线程安全的类,是说不管多少个线程并发访问这个对象,都不会发生不可预期的错误,他们的表现跟单线程访问时一样,是安全可靠的。 无副作用和线程安全的概念有相似之处,无副作用的方法,一定是线程安全的,这两个概念都能够帮助写出并发友好的代码,无副作用的编程还有一个好处,代码会很清爽,因为这个方法里的代码只跟输入输出有关系, 习惯了写无副作用的代码,能够设计出更稳健的程序。 大家可以想象,如果一个系统的代码,到处是状态,到处有千丝万缕的联系,这个系统的可维护性会是怎么样的,写过几年代码的人,可能都会碰到过这种让人头疼的项目。
了解函数的副作用很重要,因为这是接下来我要讲解的流Stream编程时,为什么有这种说法:“一群互不影响各自独立的集合因素”通过流的编程手段来达到高效简洁。