Java 8 Lambda 表达式模拟 Mixin 实现类的多重继承Word格式.docx
- 文档编号:22155015
- 上传时间:2023-02-02
- 格式:DOCX
- 页数:7
- 大小:18.49KB
Java 8 Lambda 表达式模拟 Mixin 实现类的多重继承Word格式.docx
《Java 8 Lambda 表达式模拟 Mixin 实现类的多重继承Word格式.docx》由会员分享,可在线阅读,更多相关《Java 8 Lambda 表达式模拟 Mixin 实现类的多重继承Word格式.docx(7页珍藏版)》请在冰豆网上搜索。
例如在Python的socketserver模块中就有使用mixin的方法,在这里,mixin帮助4个基于不同Socket的服务,包括支持多进程的UDP和TCP服务以及支持多线程的UDP和TCP服务。
1.classForkingUDPServer(ForkingMixIn,UDPServer):
pass
2.classForkingTCPServer(ForkingMixIn,TCPServer):
3.classThreadingUDPServer(ThreadingMixIn,UDPServer):
4.classThreadingTCPServer(ThreadingMixIn,TCPServer):
什么是虚拟扩展方法?
Java8将引入虚拟扩展方法的概念,也叫publicdefendermethod.让我们姑且把这个概念简化为VEM。
VEM旨在为Java接口提供默认的方法定义,你可以用它在已有的接口中添加新的方法定义,例如Java里的集合API。
这样类似Hibernate这样的第三方库无需重复实现这些集合API的所有方法,因为已经提供了一些默认方法。
下面是如何在接口中定义方法的示例:
1.publicinterfaceCollection<
T>
extendsIterable<
{
2.<
R>
Collection<
filter(Predicate<
p)
3.default{returnCollections.<
filter(this,p);
}
4.
5.}
Java8对混入的模拟
现在我们来通过VEM实现一个混入效果,不过事先警告的是:
请不要在工作中使用!
下面的实现不是线程安全的,而且还可能存在内存泄露问题,这取决于你在类中定义的hashCode和equals方法,这也是另外一个缺点,我将在后面讨论这个问题。
首先我们定义一个接口(模拟状态Bean)并提供方法的默认定义:
1.publicinterfaceSwitchableMixin{
2.booleanisActivated()default{returnSwitchables.isActivated(this);
3.voidsetActivated(booleanactivated)default{Switchables.setActivated(this,activated);
4.}
然后我们定义一个工具类,包含一个Map实例来保存实例和状态的关联,状态通过工具类中的私有的嵌套类代表:
1.publicfinalclassSwitchables{
2.
3.privatestaticfinalMap<
SwitchableMixin,SwitchableDeviceState>
SWITCH_STATES=newHashMap<
>
();
5.publicstaticbooleanisActivated(SwitchableMixindevice){
6.SwitchableDeviceStatestate=SWITCH_STATES.get(device);
7.returnstate!
=null&
&
state.activated;
8.}
9.
10.publicstaticvoidsetActivated(SwitchableMixindevice,booleanactivated){
11.SwitchableDeviceStatestate=SWITCH_STATES.get(device);
12.if(state==null){
13.state=newSwitchableDeviceState();
14.SWITCH_STATES.put(device,state);
15.}
16.state.activated=activated;
17.}
18.
19.privatestaticclassSwitchableDeviceState{
20.privatebooleanactivated;
21.}
22.
23.}
这里是一个使用用例,突出了状态的继承:
1.privatestaticclassDevice{}
2.privatestaticclassDeviceAextendsDeviceimplementsSwitchableMixin{}
3.privatestaticclassDeviceBextendsDeviceimplementsSwitchableMixin{}
1.DeviceAa=newDeviceA();
2.DeviceBb=newDeviceB();
3.a.setActivated(true);
4.assertThat(a.isActivated()).isTrue();
5.assertThat(b.isActivated()).isFalse();
“完全不同的东西”
上面的实现跑起来似乎挺正常的,但Oracle的Java语言架构师BrianGoetz向我提出一个疑问说当前实现是无法工作的(假设线程安全和内存泄露问题已解决)
1.interfaceFakeBrokenMixin{
2.staticMap<
FakeBrokenMixin,String>
backingMap
3.=Collections.synchronizedMap(newWeakHashMap<
());
5.StringgetName()default{returnbackingMap.get(this);
6.voidsetName(Stringname)default{backingMap.put(this,name);
7.}
8.
9.interfaceXextendsRunnable,FakeBrokenMixin{}
10.
11.XmakeX(){return()->
{System.out.println("
X"
);
};
12.
13.Xx1=makeX();
14.Xx2=makeX();
15.x1.setName("
x1"
16.x2.setName("
x2"
17.
18.System.out.println(x1.getName());
19.System.out.println(x2.getName());
你猜这段代码执行后会显示什么结果呢?
疑问的解决
第一眼看去,这个实现的代码没有问题。
X是一个只包含一个方法的接口,因为getName和setName已经有了默认的定义,但Runable接口的run方法没有定义,因此我们可通过lambda表达式来生成X的实例,然后提供run方法的实现,就像makeX那样。
因此,你希望这个程序执行后显示的结果是:
1.x1
2.x2
如果你删掉getName方法的调用,那么执行结果变成:
1.MyTest$1@30ae8764
2.MyTest$1@123acf34
这两行显示出makeX方法的执行来自两个不同的实例,而这时当前OpenJDK8生成的(这里我使用的是OpenJDK824.0-b07).
不管怎样,当前的OpenJDK8并不能反映最终的Java8的行为,为了解决这个问题,你需要使用特殊参数-XDlambdaToMethod来运行javac命令,在使用了这个参数后,运行结果变成:
1.x2
如果不调用getName方法,则显示:
1.MyTest$$Lambda$1@5506d4ea
2.MyTest$$Lambda$1@5506d4ea
每个调用makeX方法似乎都是来自相同匿名内部类的一个单例实例,如果观察包含编译后的javaclass文件的目录,会发现并没有一个名为MyTestClass$$Lambda$1.class的文件。
因为在编译时,lambda表达式并没有经过完整的翻译,事实上这个翻译过程是在编译和运行时完成的,javac编译器将lambda表达式变成JVM新增的指令invokedynamic(JSR292)。
这个指令包含所有必须的关于在运行时执行lambda表达式的元信息。
包括要调用的方法名、输入输出类型以及一个名为bootstrap的方法。
bootstrap方法用于定义接收此方法调用的实例,一旦JVM执行了invokedynamic指令,JVM就会在特定的bootstrap上调用lambda元工厂方法(lambdametafactorymethod)。
再回到刚才那个疑问中,lambda表达式转成了一个私有的静态方法,()->
}被转到了MyTest:
1.privatestaticvoidlambda$0(){
2.System.out.println("
3.}
如果你用javap反编译器并使用-private参数就可以看到这个方法,你也可以使用-c参数来查看更加完整的转换。
当你运行程序时,JVM会调用lambdametafactorymethod来尝试阐释invokedynamic指令。
在我们的例子中,首次调用makeX时,lambdametafactorymethod生成一个X的实例并动态链接run方法到lambda$0方法.X的实例接下来被存储在内存中,当第二次调用makeX时就直接从内存中读取这个实例,因此你第二次调用的实例跟第一次是一样的。
修复了吗?
有解决办法吗?
目前尚无这个问题直接的修复或者是解决办法。
尽管Oracle的Java8计划默认激活-XDlambdaToMethod参数,因为这个参数并不是JVM规范的一部分,因此不同供应商和JVM的实现是不同的。
对一个lambda表达式而言,你唯一能期望的就是在类中实现你的接口方法。
其他的方法
到此为止,尽管我们对mixin的模仿并不能兼容Java8,但还是可能通过多继承和委派为已有的类添加多个服务。
这个方法就是virtualfieldpattern(虚拟字段模式).
所以来看看我们的Switchable.
1.interfaceSwitchable{booleanisActive();
2.voidsetActive(booleanactive);
我们需要一个基于Switchable的接口,并提供一个附加的抽象方法返回Switchable的实现。
集成的方法包含默认的定义,它们使用getter来转换到Switchable实现的调用:
1.publicinterfaceSwitchableViewextendsSwitchable{
2.SwitchablegetSwitchable();
3.booleanisActive()default{returngetSwitchable().isActive();
4.voidsetActive(booleanactive)default{getSwitchable().setActive(active);
接下来,我们创建一个完整的Switchable实现:
1.publicclassSwitchableImplimplementsSwitchable{
2.privatebooleanactive;
3.@Override
4.publicbooleanisActive(){
5.returnactive;
6.}
7.@Override
8.publicvoidsetActive(booleanactive){
9.this.active=active;
10.}
11.}
这里是我们使用虚拟字段模式的例子:
1.publicclassDevice{}
2.publicclassDeviceAextendsDeviceimplementsSwitchableView{
3.privateSwitchableswitchable=newSwitchableImpl();
4.@Override
5.publicSwitchablegetSwitchable(){
6.returnswitchable;
9.publicclassDeviceBextendsDeviceimplementsSwitchableView{
10.privateSwitchableswitchable=newSwitchableImpl();
11.@Override
12.publicSwitchablegetSwitchable(){
13.returnswitchable;
14.}
DeviceBb=newDeviceB();
2.a.setActive(true);
3.assertThat(a.isActive()).isTrue();
4.assertThat(b.isActive()).isFalse();
结论
在这篇文章中,我们使用了两种方法通过Java8的虚拟扩展方法为类增加多个服务。
第一个方法使用一个Map来存储实例状态,这个方法很危险,因为不是线程安全而且存在内存泄露问题,这完全依赖于不同的JVM对Java语言的实现。
另外一个方法是使用虚拟字段模式,通过一个抽象的getter来返回最终的实现实例。
第二种方法更加独立而且更加安全。
虚拟扩展方法是Java的新特性,本文主要介绍的是多重继承的实现,详细你会有更深入的研究以及应用于其他方面,别忘了跟大家分享。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java Lambda 表达式模拟 Mixin 实现类的多重继承 表达式 模拟 实现 多重 继承
![提示](https://static.bdocx.com/images/bang_tan.gif)