当前位置: 首页 > 后端技术 > Java

关于为什么Java是single-dispatch而CommonLisp又伟大了

时间:2023-04-01 17:43:54 Java

众所周知,Java语言支持基于子类型的多态性。比如某百科给出了一个Example(代码被我稍微调整了一下)}}classDogextendsAnimal{Stringtalk(){return"Woof!";}}publicclassExample{staticvoidletSHear(finalAnimala){System.out.println(a.talk());}publicstaticvoidmain(String[]args){letSHear(newCat());letSear(新狗());}}基于子类型的多态性需要在程序运行时根据参数的类型选择不同的具体方法——例如,在上面的例子中,当参数a在方法letSHear方法talk时,该实例的实例对应的talk方法是在运行时根据变量a的类型来选择的(第一次是Cat,第二次是Dog),不是根据编译时的Animal类型。但是,在不同的语言中,在运行时搜索方法时选择的参数个数是不同的。对于Java,它只接受方法的第一个参数(即receiver),这种策略称为singledispatch。Java的singledispatch为了演示Java为什么是singledispatch,示例代码中的方法必须接收两个参数(除了方法的receiver之外还要多一个参数)//演示Java是singledispatch。抽象类Shape{}classCircleextendsShape{}classRectangleextendsShape{}classTriangleextendsShape{}abstractclassAbstractResizer{publicabstractvoidresize(Circlec);publicabstractvoidresize(Rectangler);publicabstractvoidresize(Shapes);publicabstractvoidresize(Trianglet);}classResizerextendsAbstractResizer{publicvoidresize(Circlec){System.out.println("ScaleCircle");}publicvoidresize(Rectangler){System.out.println("缩放矩形");}publicvoidresize(Shapes){System.out.println("缩放任何形状");}publicvoidresize(Trianglet){System.out.println("ScaleTriangle");}}publicclassTrial1{publicstaticvoidmain(String[]args){AbstractResizerresizer=newResizer();Shape[]shapes={newCircle(),newRectangle(),newTriangle()};对于(形状形状:形状){resizer.resize(形状);}}}很明显,Resizer类的实例方法resize接收两个参数——第一个是Resizer类的实例对象,第二个可能是yesShape及其三个子类之一的实例对象如果Java的多态策略是multipledispatch,那么应该分别调用三个不同版本的resize方法,但实际上并不能通过JDK中提供的程序javap来查看是哪个版本在main方法中调用resize方法时使用了Resizer类,运行命令javap-c-l-s-vTrial1,可以看到resize方法对应的JVM字节码叫做invokevirtual。invokevirtual指令的解释可以在JVM规范文档中找到。显然,在JVM的字节码中,已经解析了invokevirtual调用的方法的参数类型——LShape代表了一个叫Shape的类,所以在方法接收者,即Resizer类中查找时,只有resize(形状s)版本将被击中。变量s的运行时类型在查找方法时根本没有用,所以Java的多态是singledispatch。根据参数的运行时类型打印不同的内容并不难。简单粗暴的方法可以是");}elseif(sinstanceofRectangle){System.out.println("缩放矩形");}elseif(sinstanceofTriangle){System.out.println("Zoomtriangle");}else{System.out.println("缩放任意图形");}}}或使用访客模式。什么是多重派遣?第一次知道multipledispatch这个词,其实是在无意中搜索CLOS的相关资料时看到的。在CommonLisp中,定义类和方法的语法不同于通常的语言风格。例如,下面的代码定义了四个类Java(defclassshape()())(defclasscircle(shape)())(defclassrectangle(shape)())(defclasstriangle(shape)())(defclassabstract-resizer()())(defclassresizer(abstract-resizer)())(defgenericresize(resizershape))(defmethodresize((resizerresizer)(shapecircle))(formatt"scalecircle~%"))(defmethodresize((resizerresizer)(shaperectangle))(formatt"scalerectangle~%"))(defmethodresize((resizerresizer)(shapeshape))(formatt"scalearbitraryshape~%"))(defmethodresize((resizerresizer)(shapetriangle))(formatt"scaletriangle~%"))(let((resizer(make-instance'resizer))(shapes(list(make-instance'circle)(make-instance'rectangle)(make-instance'triangle))))(dolist(shapeshapes)(resizeresizershape)))执行以上代码会调用不同版本的resize方法来打印内容,因为defmethod支持声明每个参数对应的classapproach非常直观,以至于我没有意识到它有一个叫做multipledispatch的特殊名称,而且大多数语言都不支持它。后记聪明的你应该已经发现,在上面的CommonLisp代码中,Java中抽象类AbstractResizer对应的类abstract-resizer是完全不需要的。defgeneric本身就是一种定义抽象接口的手段。另外,在第三版的resize方法中,可以看到标识符shape作为参数的名称和参数所属类的名称——是的,在CommonLisp中,一个符号不仅可以表示一个A变量和一个函数,它也可以兼作类型,而不仅仅是一般的Lisp-2语言。阅读原文