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

cs61bweek4--扩展、转换、高阶函数

时间:2023-04-02 10:02:29 Java

1.extends前面我们学习的继承是继承自interface,使用implements关键字定义与接口的层次关系,现在我们要继承类,使用extends关键字定义类的层次结构。比如现在有一个类RotatingSLList,它继承了类SLList。我们可以在类声明中设置这种继承关系,使用extends关键字如下:RotatingSLList还有一个附加功能,就是将元素向右旋转(元素顺序不变):例如[5101520],以20为轴心,将左边的所有元素右旋变为[2051015]实现方法:publicvoidrotateRight(){Itemx=removeLast();addFirst(x);}通过使用extends关键字,子类继承了父类的所有成员。“成员”包括:所有实例和静态变量、所有方法、所有嵌套类。注意构造函数不是继承的,子类不能直接访问父类的私有成员。此外,子类还可以自定义一些其他的方法、变量等,也可以重写属于父类的methodsuper关键字。通过super关键字,子类可以使用父类中的方法,使用super。访问,例如,子类使用并覆盖SLList中的removeLast()方法:@OverridepublicItemremoveLast(){Itemx=super.removeLast();deletedItems.addLast(x);返回x;}构造函数不像我们前面说的那样继承,子类继承自父类,包括实例和静态变量、方法、嵌套类的所有成员,但不包括构造函数。我们在为子类编写构造函数初始化时,需要先考虑父类的构造函数。一个生动的例子是,假设我们有两个类:publicclassHuman{...}publicclassTAextendsHuman{...}如果我们运行以下构造函数:必须创造一个人。那么人类就可以被赋予TA的品质。如果不首先创造人类,就没有必要建立TA。因此,子类需要在构造函数初始化之前调用父类的构造函数。使用super():TA(){super();somebody=newTA();}假设你不初始化子类的构造函数不加super(),Java会先隐式自动调用父类的构造函数,但只会调用无参版本,例如:publicVengefulSLList(Itemx){deletedItems=newSLList();}虽然子类的构造函数是带参数的版本,但是由于没有调用super(x),Java会自动调用父类的无参构造函数.要正确调用带参数的父类的构造函数:publicVengefulSLList(Itemx){super(x);deletedItems=newSLList();}请注意:Java不允许使用super.super,请参阅此链接Java中的每个类都是Object类或扩展Object类的后代。也就是说,每个类都继承了Object类:Object类是类层次结构的根。每个类都有Object作为超类。所有对象,包括数组,都实现了这个类的方法。publicclassAextendsObjectextendsB(){...}因此,Object类中的一些方法可以在任何类中调用:.equals(Objectobj)、.hashCode()和toString()看得更详细但是接口不扩展Objectclassseemoredetail2。封装(这段是微调的谷歌翻译)封装是面向对象编程的基本原则之一,也是程序员用来抵御最大敌人——复杂性的方法之一。管理复杂性是我们在编写大型程序时必须面对的主要挑战之一。一些对抗复杂性的工具主要包括抽象级别(抽象障碍!)和一个称为“设计变更”的概念。围绕这个想法,程序应该构建成模块化的、可交互的部分,可以在不破坏系统的情况下与外界交换。此外,隐藏其他人不需要的信息是管理大型系统的另一种基本方法。封装源于“对外部隐藏信息”的概念。用细胞来解释封装,一个细胞的内部结构可能非常复杂,由染色体、线粒体、核糖体等组成,但它被完全封装在一个模块中——抽象掉了内部的复杂性。在计算机科学术语中,模块可以定义为作为一个整体协同工作以执行一项任务或一组相关任务的方法的集合。这可能类似于表示列表的类。现在,如果一个模块的实现细节隐藏在内部,并且与它交互的唯一方式是通过接口文档,则称该模块是封装的。3.ImplementationInheritance如何打破封装假设我们有一个封装好的Dog接口,包括:)功能:现在调用VerboseDog的barkMany(),步骤为:VerboseDogd=newVerboseDog();编译器类型(静态类型)和运行时类型(动态类型)都是VerboseDog;d.BarkMany(3)ConsiderDynamicselection(override都考虑DynamicSelection),由于此时的runtimetype是VerboseDog,调用子类的BarkMany()Override-->callbark(),此时的runtimetype是VerboseDog,准备调用子类的Overridebark(),因为子类中没有bark()方法,使用继承自父类的bark()方法打印三次bark。假设管理者改变了Dog类内部的函数实现,但是从外部看,函数还是和以前一样:在这种情况下,仍然按照前面的步骤调用子类的barkMany():VerboseDogd=新的VerboseDog();编译器类型(静态类型)和运行时类型(动态类型)都是VerboseDog;d.BarkMany(3)考虑动态选择,由于此时运行类型为VerboseDog,调用子类的BarkMany()Override-->callbark(),此时运行类型为VerboseDog,准备调用子类的Overridebark(),因为子类中没有bark()方法,使用从父类继承的bark()方法是问题的关键。此时父类的bark()是publicvoidbark(){barkMany(1);},就是调用父类的bark()-->CallbarkMany(1),因为此时的runtimetype为VerboseDog,barkMany(1)实际上调用了子类的OverridebarkMany(),1作为参数传递给子类的barkMany()@OverridepublicvoidbarkMany(intN){System.out.println("Asadog,Isay:");对于(inti=0;ivsl=newVengefulSLList(9);SLListsl=vsl;这两行没有问题,因为VengefulSLList是SLList的子类,和SLList是“is-a”关系,即VengefulSLList一定属于SLList,所以sl的SLList类型的内存盒可以容纳VengefulSLList输入sl.addLast(50);sl.removeLast();这两行没有问题。sl调用addLast(50)时,runtime类型是VengefulSLList,但是VengefulSLList没有OverrideaddLast(),所以调用SLList自己的addLast()来调用sl.removeLast(),由于已经有SubclassOverride(上面提到),所以VengefulSLList的removeLast()sl.printLostItems()被调用;这行代码会编译出错,请注意,编译器判断语句是否在运行前(即运行前)有效性是根据静态类型(编译时类型等于声明的类型)来判断的。由于sl的静态类型是SLList,而SLList中没有printLostItems()方法,所以会直接报错,不会进入动态选择VengefulSLListvsl2=sl;同时,这行代码也会编译出错。一般来说,编译器在检查方法调用和赋值操作时,会参考对象的静态类型(编译器只允许基于编译时类型的方法调用和赋值),由于vsl2的静态类型是VengefulSLList,而静态类型sl的是SLList,根据“is-a”关系,VengefulSLList它是SLList,但SLList不一定是VengefulSLList。因此,编译器不允许VengefulSLList的内存盒存储SLList类型的对象。cast方法调用的编译器时间类型(静态类型)与声明的类型相同。假设我们有这个方法publicstaticDogmaxDog(Dogd1,Dogd2){...}由于maxDog的返回类型是Dog,所以对maxDog()的任何调用都具有静态类型DogPoodlefrank=newPoodle("Frank",5);PoodlefrankJr=newPoodle("FrankJr.",15);DoglargerDog=maxDog(frank,frankJr);PoodlelargerPoodle=maxDog(frank,frankJr);//不编译!RHS有编译时类型Dog上面代码的思路是返回两个poodle中较大的一个,但是在第四行,Poodle和Dog的关系不是“is-a”,即不是所有狗是贵宾犬,所以它会编译错误。其实上面我们知道不管是frank还是frankJr谁大,最后的结果肯定是Poodle,那么有什么办法可以解决呢?答案是使用强制类型转换。强制类型转换是一个强大而危险的工具。本质上,强制类型转换绕过了编译器自身的类型检查,让编译器相信我们的判断是正确的,相信我。危险是:Poodlefrank=newPoodle("Frank",5);MalamutefrankSr=newMalamute("FrankSr.",100);PoodlelargerPoodle=(Poodle)maxDog(frank,frankSr);//运行时异常!假设我们比较poodlefrank和雪橇犬frankSr的大小,将最终结果强制转换为Poodle(贵宾犬),但实际上Malamute>Poodle,当maxDog在运行时返回Malamute而我们尝试转换的时候Malamute转换为Poodle,我们会遇到ClassCastException异常5.HigherOrderFunction:将其他函数视为参数和数据的函数。例如,在Python中:deftenX(x):return10*xdefdo_twice(f,x):returnf(f(x))print(do_twice(tenX,2))do_twice(f,x)以tenX为参数,执行tenX(tenX(2)),返回200实现更高-Java中的顺序函数?在旧版本(Java7或更早版本)中,内存盒不能保存指向函数的指针,这意味着函数本身不能作为参数传入,然后被高阶函数中的内存盒接收。解决方案是考虑使用接口继承:定义一个IntUnaryFunction接口,声明函数apply()publicinterfaceIntUnaryFunction{intapply(intx);}定义一个子类classTenX,继承这个接口:intapply(intx){返回10*x;}}上面两个java文件实现了python:deftenX(x):return10*x我们知道Java中一个.java文件是可以使用其他.java文件的,那些文件相当于包装了这里面用到的每一个对象.java文件有点像C语言的typedefstruct结构。封装后的类相当于一种新的“数据类型”。我们可以在这个.java文件中使用其他类的非私有成员、方法等。因此,如果要实现函数的功能,就需要封装包含apply()实现的TenX类,然后使用它作为一个参数可以容纳在内存盒中(对象当然可以容纳在内存盒中)在函数之间传递继续新建一个.java文件,HoFDemoclass:publicclassHoFDemo{publicstaticintdo_twice(IntUnaryFunctionf,intx){returnf.apply(f.apply(x));}publicstaticvoidmain(String[]args){System.out.println(do_twice(newTenX(),2));}}将IntUnaryFunction实例化为f,可以在函数(方法)中作为参数传递,解决了Java7中函数不能作为参数传递的问题,然后就可以使用f.apply(f.apply(x))完成pythonJava8中高阶函数的功能Java8之后,一个新的类型可以容纳对方法的引用publicclassJava8HoFDemo{publicstaticinttenX(intx){return10*x;}publicstaticintdoTwice(Functionf,intx){returnf.apply(f.apply(x));}publicstaticvoidmain(String[]args){intresult=doTwice(Java8HoFDemo::tenX,2);System.out.println(结果);}}