多态-概述
对象本身也存在着多态性。
Class 动物 {} Class 猫 extends 动物 {} Class 狗 extends 动物 {} 猫 x=new 猫(); 动物 x=new 猫();//猫是动物就可以这样写 (一个对象,两种形态,类似王者荣耀的花木兰)
猫这类事物即具备猫的形态,又具备着动物的形态。父类创建的对象指向了子类对象。一个对象具备2种形态。多态在代码中的体现,也即是父类或者接口的引用指向子类的对象。
多态的好处
Abstract class Animal { Abstract void eat(); } Class Dog extends Animal { Void eat() { Sop(“啃骨头”); } Void lookhome() { Sop(“看家”); } } Class Cat extends Animal { Void eat() { Sop(“吃鱼”); } Void catchMouse() { Sop(“抓老鼠”); } } Class PolymorphismDemo { Public static void main(String[] args) { Cat c=new Cat(); Dog d=new Dog(); Method(c);//多态后可以这样做 Method(d);//多态后可以这样做 Method(new pig());//现在这样都没问题,因为有多态了,现在想传什么可以传什么。 } Public static void method(Cat c) { c.eat(); } Public static void method(Dog d) { d.eat(); } }
但是又来了个兔子,又得重新写,这样很麻烦。猫、狗、鸡直接不要那些猫、狗直接可以下面这样:
Public static void method(Animal a)//这个a可以不断指向animal的不同的子类对象 { a.eat(); }
多态的出现,提高了代码的可拓展性。前期定义的代码可以使用后期的内容。
多态的弊端: 前期定义的内容不能调用后期子类的特有内容,(比如动物不能调用抓老鼠,可以调用吃饭)。多态的前提:
1. 必须有关系,要么就是继承或者是实现。(其实实现就是特殊的继承)
2. 要有覆盖
面向对象(多态—多态转型)
复习一下:byte b=3;int x=b;第二条语句把byte类型提升为int类型。
Animal a=new Cat();//自动类型提升,猫对象提升为动物类型。
现在操作a,只能当作动物来对待,甚至都不具备抓老鼠的功能了。不能使用猫的特有方法了。作用:就是限制特有功能的访问。(专业讲:向上转型)
如果还想用具体猫的特有功能,现在可以将该对象进行向下转型(为了使用子类的特有方法)。
Cat c=(Cat)a;//此时就可以了,a就变成猫类型了。
Animal a=new Animal();
Cat c=(Cat) a;//这样就不行,本来就是动物类型,这样向下转型没法转
当然Animal a=new Dog();
Cat c=(Cat) a;//这样也会出错
注;对于转型,自始至终都是子类对象在做着类型的变化。
转型的目的:向上转型是为了限定功能使用,提高拓展性;向下转型是为了使用子类的特有功能。
Class 毕姥爷 { Void 讲课() { Sop(“管理”); } Void 钓鱼() { Sop(“看电影”); } } Class 毕老师 extends 毕姥爷 { Void 讲课() { Sop(“JAVA”); } Void 看电影() { Sop(“看电影”); } } Class DuoTaiDemo { Public static void main(String[] args) { 毕姥爷 x=new 毕老师();//向上转型了转化为他爹 X.讲课(); //运行结果为java X.钓鱼(); //你压根不会钓鱼啊,哈哈,编译失败,我弄错了。 X.看电影(); //因为找他爹的人压根不知道他有看电影的功能。如果想要看电影,这个时候就用到子类的特有功能。这个时候如下: 毕老师 y=(毕老师)x; 毕老师.看电影();//现在这样就可以了。 } }
转型时要小心:ClassCastException类型转化异常。
对于之前如果传的是猫,向上转型为动物,然后再强转为猫,没毛病,但如果是狗的话,就不行。因此需要在此做一个判断(因为向上转型为父类后可以接受的对象种类太多了):
If(a instanceof Cat)//用于判断对象的具体类型。只能用于引用类型数据判断,这样的话程序更加健壮。(通常向下转型前需要判断)
多态-成员变量
多态的前提就是继承。
多态的成员的特点:
1. 成员变量
编译时:参考引用型变量所属的类中是否有调用的成员变量,有则编译通过,否则编译失败。
运行时:参考引用型变量所属的类中是否有调用的成员变量,并运行该所属类中的成员变量。(了解即可,开发很少见,不过面试多见)
也即是编译和运行都参考等号的左边。
2. 成员函数(弄错了,编译时自己以为父类没有show方法可以编译通过,实际上不行) 这里就不再举例了
编译时:参考引用型变量所属的类中是否有调用的成员变量,有则编译通过,否则编译失败。
运行时:参考的是对象所属的类中是否有调用的函数。
也即是编译看左边运行看右边
3. 静态函数
类名直接调用即可,跟对象没啥关系。也不涉及什么子类对象的问题。相当于已经绑定在类上面了。(不涉及对象的多态性,直接运行了)
简单说就是:编译和运行都看左边即可。其实对于静态方法,是不需要对象的。直接用类名调用即可。
下面这个示例(成员变量的示例)又做错了:
Class Fu { Int num=3; } Class Zi extends Fu { Int num=4; } Class DuoTaiDemo3 { Public static void main(String[] args) { Fu f=new Zi(); Sop(f.num);//打印结果为3,感觉跟之前毕姥爷那个例子不太一样啊?何解 } //这里的解释是f指向父类了,直接从父类那里找,如果把父类的那个 //num注释掉的话,那么就会不报错,也不会打印4. //注意要区分前面那个毕姥爷的例子,那个例子是因为把父类的方法给覆盖了。(这个示例要掌握,覆盖只发生在函数上,变量是没有关系的。) }
一出现多态就要知道向上转型。就不能使用子类的特有方法。
内部类(嵌套类)
存在的原因:方便访问外部类中的成员。
内部类编译后生成的名称:外部类名&内部类名.class
访问特点:
1. 内部类可以访问外部类中的成员(包括私有变量)。
2. 外部类要访问内部类,必须要建立内部类的对象。
Class Outer { Private int num=3; Class Inner { Void show() { Sop(“show run”+num); } } Public void method() { Inner in=new Inner(); In.show(); } }
一般用于类的设计,分析事物描述中还有事物,而且这个事物还在访问被描述事物的内容。这时就是还有的食物定义成内部类来描述。(人体,心脏在人体内,心脏需要访问人体中的很多其他部位)
内部类-修饰符
Class Outer { Private int num=3; Class Inner { Void show() { Sop(“show run”+num); } } Public void method() { Inner in=new Inner(); In.show(); } } Class InnerClassDemo { Public static void main() { Outer.Inner in=new Outer().new Inner(); In.show();//直接访问外部类中的内部类中的成员。但是如果private的话,就不能访问了。如果内部类的修饰符是static的话,相当于是一个外部类,以上2句代码可以直接Outer.Inner in=new Outer.Inner();直接外部类加载进内存了。如果想要直接直接调用内部类中的静态成员(类名调用),该内部类也必须是静态的。 } }
内部类细节
Class Outer { Int num=3; Class Inner { Int num=4; Void show() { Int num=5; Sop(this.num);//去除this会打印5,现在打印4. Sop(Inner.this.num);//居然会打印4,所以可以借鉴下 Sop(Outer.this.num);//这样才可以打印3.( 因为内部类持有外部类的引用,所以可以这样做。 ) (实际开发不会这样搞,但是还是要懂) } } } Class InnerClassDemo2 { Public static void main(String[] args) { New Outer().method(); } }
内部类-局部内部类
/*
内部类可以存储在局部位置上。
*/
Class Outer { Int num=3; Void method() { Class Inner { Void show() { Sop(“show...”+num); } } Inner in=new Inner(); In.show(); } } Class InnerClassDemo3 { Public static void main(String[] args) { New Outer().method(); } }
上面这个示例可以正常运行。接着看下面:
Class Outer { Int num=3; Void method() { Int x=9;//需要加final关键字才可以编译通过(固定规则,但是老师阐明了一些原因,但是还没有太听懂) 原理就是:内部类在局部位置上只能访问局部中被final修饰的局部变量。如果没有听懂就记住。 Class Inner { Void show() { Sop(“show...”+x); } } Inner in=new Inner(); In.show(); } } Class InnerClassDemo3 { Public static void main(String[] args) { New Outer().method(); } }
匿名内部类-概述(之前在匿名对象中遇到过)
就是内部类的简写格式。
必须有前提:内部类必须继承或者实现一个外部类或者接口。所谓匿名也就是:内部类没有名字。
匿名内部类:其实就是一个匿名子类对象。
格式:new父类or接口(){子类内容}
Abstract class Demo { Abstract void show(); } Class Outer { Int num=4; } Public void method() { New Demo() //其实在这里就是new了一个子类对象。 { Void show() { Sop(“show...”+num); } Void haha() { Sop(“haha”); } }.show(); //调用了自己子类的方法,当然以上还可以加哈哈方法也行。 }
匿名内部类-应用(未做笔记)
2种等价的形式:
匿名内部类-细节
静态中没有this的。主函数不能直接访问非静态成员。
匿名内部类这个子类对象被向上转型为了object类型,就不能再使用子类的特有方法。(参考之前的编译看哪边,运行看哪边。)
对象的初始化过程
图中下部分为打印结果。顺序也已经在图中标注了(红色阿拉伯数字)。
显示初始化动作和构造代码块初始化以及super(无非父类对应的是Object,暂时不用深究)这些操作,父类和子类均有的。父类的那一套东西对应于子类的构造函数中的第一句super();