wordpress发消息,宁波市网站排名优化,wordpress 倡萌 相册,物流网站后台文章目录 一、多态的形式和体现#xff08;1#xff09;为什么需要多态性(polymorphism)#xff1f;#xff08;2#xff09; 对象的多态性 二、 多态的理解#xff08;1#xff09;如何理解多态性#xff08;2#xff09;Java中多态性的体现#xff08;3#xff09… 文章目录 一、多态的形式和体现1为什么需要多态性(polymorphism)2 对象的多态性 二、 多态的理解1如何理解多态性2Java中多态性的体现3多态性的应用4虚方法调用5多态的使用注意事项6举例7多态的好处和弊端1、好处举例1举例2 2、弊端 8成员变量没有多态性 三、向上转型与向下转型1为什么要类型转换2 如何向上或向下转型3 instanceof关键字 四、 练习1练习12练习23练习34练习4 五、面试题1题目12题目2 一千个读者眼中有一千个哈姆雷特。–多种形态 一、多态的形式和体现
1为什么需要多态性(polymorphism)
开发中有时我们在设计一个数组、或一个成员变量、或一个方法的形参、返回值类型时无法确定它具体的类型只能确定它是某个系列的类型。
案例
1声明一个Dog类包含public void eat()方法输出“狗啃骨头”
2声明一个Cat类包含public void eat()方法输出“猫吃鱼仔”
3声明一个Person类功能如下
包含宠物属性包含领养宠物方法 public void adopt(宠物类型Pet)包含喂宠物吃东西的方法 public void feed()实现为调用宠物对象.eat()方法
public class Dog {public void eat(){System.out.println(狗啃骨头);}
}public class Cat {public void eat(){System.out.println(猫吃鱼仔);}
}public class Person {private Dog dog;//adopt领养public void adopt(Dog dog){this.dog dog;}//feed喂食public void feed(){if(dog ! null){dog.eat();}}/*问题1、从养狗切换到养猫怎么办 修改代码把Dog修改为养猫2、或者有的人养狗有的人养猫怎么办 3、要是还有更多其他宠物类型怎么办如果Java不支持多态那么上面的问题将会非常麻烦代码维护起来很难扩展性很差。*/
}2 对象的多态性
多态性是面向对象中最重要的概念在Java中的体现
对象的多态性父类的引用指向子类的对象
格式父类类型指子类继承的父类类型或者实现的接口类型
父类类型 变量名 子类对象举例
Person p new Student();Object o new Person();//Object类型的变量o指向Person类型的对象o new Student(); //Object类型的变量o指向Student类型的对象对象的多态在Java中子类的对象可以替代父类的对象使用。所以一个引用类型变量可能指向(引用)多种不同类型的对象
二、 多态的理解
1如何理解多态性
理解理解为一个事物的多种形态。
生活举例 女朋友我想养一个宠物。若将宠物看作变量类型真正赋的值是一个具体的宠物它有多种 孩子我想要一个玩具。最终的玩具有很多种不确定 老板张秘书安排一个技术科的同事跟我一起下周出差。最终的技术科同事有很多不确定 2Java中多态性的体现
Java代码层面多态性比如需要一个人现在声明就是一个Person但是实际上给这个Person赋值的时候new的不是Person而是Person子类的一个对象。
举例
父类【Person.java】
package yuyi06;public class Person {//属性String name;int age;//方法public void eat(){System.out.println(人要吃饭);}public void walk(){System.out.println(人要走路);}
}子类【Man.java】
package yuyi06;public class Man extends Person {//属性boolean isSmoking;//方法//重写public void eat(){System.out.println(男人吃饭不要挑食);}public void walk(){System.out.println(男人走路要端正);}public void earnMoney(){System.out.println(男人要挣钱养家糊口);}
}子类【Woman.java】
package yuyi06;public class Woman extends Person{//属性boolean isBeauty;//方法//重写public void eat(){System.out.println(女人吃饭要根据自己喜好);}public void walk(){System.out.println(女人走路很有特色);}public void goShopping(){System.out.println(女人喜欢逛街);}
}测试类【PersonTest.java】
package yuyi06;public class PersonTest {public static void main(String[] args) {//多态性之前Person p1new Person(); //声明的是Personnew的也是PersonMan m1new Man();//多态性子类对象的多态性Person p2new Man(); //声明的是Personnew的是Man}
}子类对象的多态性父类的引用指向子类的对象。或子类的对象赋给父类的引用
比如:Person p2 new Man();声明是一个父类的在new具体对象的时候拿的是具体子类的对象
3多态性的应用
Java引用变量有两个类型编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定运行时类型由实际赋给该变量的对象决定。简称编译时看左边运行时看右边。
若编译时类型和运行时类型不一致就出现了对象的多态性(Polymorphism)多态情况下“看左边”看的是父类的引用父类中不具备子类特有的方法 “看右边”看的是子类的对象实际运行的是子类重写父类的方法 举例
测试类【PersonTest.java】
package yuyi06;public class PersonTest {public static void main(String[] args) {//多态性之前Person p1new Person(); //声明的是Personnew的也是PersonMan m1new Man();//多态性子类对象的多态性Person p2new Man(); //声明的是Personnew的是Man//多态性的应用p2.eat();p2.walk();}
}上面的代码输出结果
很明显p2它new的对象是Man所以输出就是Man里面重写的方法。如下 但是此时按住Ctrl键然后点击p2后面的eat()方法跳转的却是Person类里面的方法大家可以自己去试试 所以编译器认为这个eat()方法是Person类的编译的时候但是真正执行的时候是右边new的类的方法。 多态性的应用虚拟方法调用
在多态场景下调用方法时
编译时认为方法是左边声明的父类的类型的方法即被重写的方法
执行时实际执行的是子类重写父类的方法 简称为编译看左边运行看右边。 编译时看左边什么类型决定后面编译的是谁运行时看右边子类对象是谁就调用它重写之后的方法。 看一下字节码文件 invokevirtual调用虚的–虚方法 4虚方法调用
在Java中虚方法是指在编译阶段不能确定方法的调用入口地址在运行阶段才能确定的方法即可能被重写的方法。
Person e new Student();
e.getInfo(); //调用Student类的getInfo()方法子类中定义了与父类同名同参数的方法在多态情况下将此时父类的方法称为虚方法父类根据赋给它的不同子类对象动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
举例 前提Person类中定义了welcome()方法各个子类重写了welcome()。 执行多态的情况下调用对象的welcome()方法实际执行的是子类重写的方法。 拓展 静态链接或早起绑定当一个字节码文件被装载进JVM内部时如果被调用的目标方法在编译期可知且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。那么调用这样的方法就称为非虚方法调用。比如调用静态方法、私有方法、final方法、父类构造器、本类重载构造器等。 动态链接或晚期绑定如果被调用的方法在编译期无法被确定下来也就是说只能够在程序运行期将调用方法的符号引用转换为直接引用由于这种引用转换过程具备动态性因此也就被称之为动态链接。调用这样的方法就称为虚方法调用。比如调用重写的方法针对父类、实现的方法针对接口。 5多态的使用注意事项
1、多态性的使用前提① 要有类的继承关系 ② 要有方法的重写
2、多态的适用性适用于方法不适用于属性。
【测试属性是否满足多态】
【Person.java】
package yuyi06;public class Person {//属性int id1001;
}【Man.java】
package yuyi06;public class Man extends Person {//属性int id1002;
}【PersonTest.java】
package yuyi06;public class PersonTest {public static void main(String[] args) {//多态性子类对象的多态性Person p2new Man(); //声明的是Personnew的是Man//测试属性是否满足多态性System.out.println(p2.id);}
}输出结果 可以看到输出结果是父类里面声明的值。
Person p2new Man();
因为声明的就是Person编译的时候看的是左边运行时看的也是左边。 综上属性不满足多态性 多态性的应用只看方法不管属性本来属性也不建议大家在子父类中去定义重名的。 6举例
再举例
package com.atguigu.polymorphism.grammar;public class Pet {private String nickname; //昵称public String getNickname() {return nickname;}public void setNickname(String nickname) {this.nickname nickname;}public void eat(){System.out.println(nickname 吃东西);}
}package com.atguigu.polymorphism.grammar;public class Cat extends Pet {//子类重写父类的方法Overridepublic void eat() {System.out.println(猫咪 getNickname() 吃鱼仔);}//子类扩展的方法public void catchMouse() {System.out.println(抓老鼠);}
}package com.atguigu.polymorphism.grammar;public class Dog extends Pet {//子类重写父类的方法Overridepublic void eat() {System.out.println(狗子 getNickname() 啃骨头);}//子类扩展的方法public void watchHouse() {System.out.println(看家);}
}1、方法内局部变量的赋值体现多态
package com.atguigu.polymorphism.grammar;public class TestPet {public static void main(String[] args) {//多态引用Pet pet new Dog();pet.setNickname(小白);//多态的表现形式/*编译时看父类只能调用父类声明的方法不能调用子类扩展的方法运行时看“子类”如果子类重写了方法一定是执行子类重写的方法体*/pet.eat();//运行时执行子类Dog重写的方法
// pet.watchHouse();//不能调用Dog子类扩展的方法pet new Cat();pet.setNickname(雪球);pet.eat();//运行时执行子类Cat重写的方法}
}2、方法的形参声明体现多态
package com.atguigu.polymorphism.grammar;public class Person{private Pet pet;public void adopt(Pet pet) {//形参是父类类型实参是子类对象this.pet pet;}public void feed(){pet.eat();//pet实际引用的对象类型不同执行的eat方法也不同}
}package com.atguigu.polymorphism.grammar;public class TestPerson {public static void main(String[] args) {Person person new Person();Dog dog new Dog();dog.setNickname(小白);person.adopt(dog);//实参是dog子类对象形参是父类Pet类型person.feed();Cat cat new Cat();cat.setNickname(雪球);person.adopt(cat);//实参是cat子类对象形参是父类Pet类型person.feed();}
}3、方法返回值类型体现多态
package com.atguigu.polymorphism.grammar;public class PetShop {//返回值类型是父类类型实际返回的是子类对象public Pet sale(String type){switch (type){case Dog:return new Dog();case Cat:return new Cat();}return null;}
}package com.atguigu.polymorphism.grammar;public class TestPetShop {public static void main(String[] args) {PetShop shop new PetShop();Pet dog shop.sale(Dog);dog.setNickname(小白);dog.eat();Pet cat shop.sale(Cat);cat.setNickname(雪球);cat.eat();}
}7多态的好处和弊端
1、好处
好处变量引用的子类对象不同执行的方法就不同实现动态绑定。代码编写更灵活、功能更强大可维护性和扩展性更好了。 极大的减少了代码的冗余不需要定义多个重载的方法。 举个例子看一下多态使用的场景。
举例1
package yuyi07;/*** ClassName: AnimalTest* Package: yuyi07* Description:** Author 雨翼轻尘* Create 2023/11/7 0007 20:52*/
public class AnimalTest {public static void main(String[] args) {AnimalTest testnew AnimalTest(); //通过test来调用adopt方法}public void adopt(Animal animal){//只能调用Animal里面声明的动物animal.eat();animal.jump();}
}class Animal{public void eat(){System.out.println(动物吃饭饭);}public void jump(){System.out.println(动物跳高高);}
}
在AnimalTest里此时在某一个场景下想要调用adopt方法。就需要造一个对象test然后通过test来调用adopt方法。 ①没有多态性
若是没有多态性因为在adopt()方法里面参数声明的是Animal类型所以在用test调用adopt()方法的时候参数也需要是Animal类型的。 这个时候都不知道养的是啥如果没有多态性此时想领养一只狗就需要再声明一个参数是狗的方法。如下
public class AnimalTest {public static void main(String[] args) {AnimalTest testnew AnimalTest();test.adopt(new Animal());}public void adopt(Animal animal){//只能调用Animal里面声明的动物2animal.eat();animal.jump();}//方法动物狗public void adopt(Dog dog){dog.eat();dog.jump();}//方法动物猫public void adopt(Cat cat){cat.eat();cat.jump();}//...
}如果还有更多的需要领养的动物那么就需要不停地AnimalTest类里面写adopt()方法的重载。 ②有多态性
若此时有多态性就可以在AnimalTest方法外面写关于很多动物的类将它继承于Animal那么在用test调用adopt()方法的时候就可以在参数里面new任何一个动物了不再需要继续在AnimalTest类里面声明adopt()方法的重载了。
如下
package yuyi07;public class AnimalTest {public static void main(String[] args) {AnimalTest testnew AnimalTest();test.adopt(new Dog());}public void adopt(Animal animal){//只能调用Animal里面声明的动物animal.eat();animal.jump();}
}class Animal{public void eat(){System.out.println(动物吃饭饭);}public void jump(){System.out.println(动物跳高高);}
}//类关于狗
class Dog extends Animal {public void eat(){System.out.println(小狗进食);}public void jump(){System.out.println(狗急跳墙);}public void watchDoor(){System.out.println(小狗看家);}
}//类关于猫
class Cat extends Animal {public void eat(){System.out.println(小猫吃小鱼);}public void jump(){System.out.println(小猫跳舞);}public void catMouse(){System.out.println(猫抓老鼠);}
}执行结果 ⚡注意 test.adopt(new Dog());
形式上来看匿名对象new Dog()赋值给了animal。
此时就构成多态的“形”了父类Animal的引用指向子类Dog的对象Animal animalnew Dog(); 编译的时候创建AnimalTest类的时候调用animal的eat()方法和jump()方法只能调用Animal类里面有的方法所以编译的时候eat()方法和jump()方法都是Animal类里面的。
但是真正执行的时候test.adopt(new Dog());传递的是Dog对象所以最终执行结果表示的是Dog类里面重写的方法如下 编译时看左边运行时看右边 若此时想养猫可以直接new一个Cat对象test调用的adopt()还是同一个方法以多态的方式去呈现。编译左边运行时右边。
package yuyi07;/*** ClassName: AnimalTest* Package: yuyi07* Description:** Author 雨翼轻尘* Create 2023/11/7 0007 20:52*/
public class AnimalTest {public static void main(String[] args) {AnimalTest testnew AnimalTest();test.adopt(new Dog());test.adopt(new Cat());}public void adopt(Animal animal){ //Animal animalnew Dog(); 声明的是Animal实际上new了一个Dog//只能调用Animal里面声明的动物animal.eat();animal.jump();}/*public void adopt(Dog dog){dog.eat();dog.jump();}public void adopt(Cat cat){cat.eat();cat.jump();}*/
}class Animal{public void eat(){System.out.println(动物吃饭饭);}public void jump(){System.out.println(动物跳高高);}
}class Dog extends Animal {public void eat(){System.out.println(小狗进食);}public void jump(){System.out.println(狗急跳墙);}public void watchDoor(){System.out.println(小狗看家);}
}class Cat extends Animal {public void eat(){System.out.println(小猫吃小鱼);}public void jump(){System.out.println(小猫跳舞);}public void catMouse(){System.out.println(猫抓老鼠);}
}运行结果 多态性不光是少定义了几个重载的方法在实际开发当中Animal 只能写这一个父类因为不知道具体有多少个子类。 举例2
再举个例子巩固一下多态的使用。 class Account{public void withdraw(){} //取钱
}class CheckAccount extends Account{ //信用卡//存在方法的重写public void withdraw(){} //取钱
}
class SavingAccount extends Account{ //储蓄卡//存在方法的重写
}class Customer{Account account;public void setAccount(Account account){this.account account;}public Account getAccount(){return accout;}}class CustomerTest{main(){Customer cust new Customer();cust.setAccount(new CheckAccount());cust.getAccount().withdraw(); //取钱}
}⚡注意
1.多态场景 2.编译是Account真正调用运行的是CheckAccount里面重写的方法 3.这里只需要传递一个账户就好具体什么账户其实都是Account子类的对象去银行办卡都是具体的卡比如信用卡、储蓄卡等不会是抽象的Account。 2、弊端
弊端一个引用类型变量如果声明为父类的类型但实际引用的是子类对象那么该变量就不能再访问子类中添加的属性和方法。 在多态的场景下我们创建了子类的对象也加载了子类特有的属性和方法。但是由于声明为父类的引用 导致我们没有办法直接调用子类特有的属性和方法。 举例
Student m new Student();
m.school pku; //合法,Student类有school成员变量
Person e new Student();
e.school pku; //非法,Person类没有school成员变量// 属性是在编译时确定的编译时e为Person类型没有school成员变量因而编译错误。开发中
使用父类做方法的形参是多态使用最多的场合。即使增加了新的子类方法也无需改变提高了扩展性符合开闭原则。
【开闭原则OCP】
对扩展开放对修改关闭通俗解释软件系统中的各种组件如模块Modules、类Classes以及功能Functions等应该在不修改现有代码的基础上引入新功能 接下来还是拿之前的例子来看 ①问题1Person p2 new Man();
针对于创建的对象在内存中是否加载了Man类中声明的特有的属性和方法
加载了这当然是有的new的就是Man自己类里面和父类里面的功能都有加载进内存的。
②问题2能不能直接调用Man中加载的特有的属性和方法呢
不能。
比如 在方法里面来看下面两种写法明显第二种更好
//1.多态
Person p2new Man(); //只能调用父类Person中的方法Man中的方法只是加载到内存中了但是调用不了//2.非多态
Man m2new Man(); //Man里面的方法和父类中的方法都可以调用//第二种写法很明显比第一种好。多态主要使用的场景就是在形参的场景下。比如 既然Person p2 new Man();内存中有Man特有的属性和方法如果在特殊场景下想调用可以吗
上面咱们只说了不能直接调用那处理之后呢
怎么处理这里涉及到一个操作–向下转型。请看第三个大标题
8成员变量没有多态性
若子类重写了父类方法就意味着子类里定义的方法彻底覆盖了父类里的同名方法系统将不可能把父类里的方法转移到子类中。对于实例变量则不存在这样的现象即使子类里定义了与父类完全相同的实例变量这个实例变量依然不可能覆盖父类中定义的实例变量
package com.atguigu.polymorphism.grammar;public class TestVariable {public static void main(String[] args) {Base b new Sub();System.out.println(b.a);System.out.println(((Sub)b).a);Sub s new Sub();System.out.println(s.a);System.out.println(((Base)s).a);}
}
class Base{int a 1;
}
class Sub extends Base{int a 2;
}三、向上转型与向下转型
首先一个对象在new的时候创建是哪个类型的对象它从头至尾都不会变。即这个对象的运行时类型本质的类型用于不会变。
但是把这个对象赋值给不同类型的变量时这些变量的编译时类型却不同。
1为什么要类型转换
因为多态就一定会有把子类对象赋值给父类变量的时候这个时候在编译期间就会出现类型转换的现象。
但是使用父类变量接收了子类对象之后我们就不能调用子类拥有而父类没有的方法了。这也是多态给我们带来的一点小麻烦。所以想要调用子类特有的方法必须做类型转换使得编译通过。 基本数据类型之间的自动类型提升与强制类型转换。 比如int类型的变量i将它赋值给double类型的变量j就是自动类型提升double jint i; 若再将double j转换为int i就需要强制类型转换int i(int)double j; 比如
向上转型当左边的变量的类型父类 右边对象/变量的类型子类我们就称为向上转型 此时编译时按照左边变量的类型处理就只能调用父类中有的变量和方法不能调用子类特有的变量和方法了但是运行时仍然是对象本身的类型所以执行的方法是子类重写的方法体。此时一定是安全的而且也是自动完成的 向下转型当左边的变量的类型子类 右边对象/变量的编译时类型父类我们就称为向下转型 此时编译时按照左边变量的类型处理就可以调用子类特有的变量和方法了但是运行时仍然是对象本身的类型不是所有通过编译的向下转型都是正确的可能会发生ClassCastException为了安全可以通过isInstanceof关键字进行判断
2 如何向上或向下转型
向上转型自动完成
向下转型子类类型父类变量
【举例1】
package com.atguigu.polymorphism.grammar;public class ClassCastTest {public static void main(String[] args) {//没有类型转换Dog dog new Dog();//dog的编译时类型和运行时类型都是Dog//向上转型Pet pet new Dog();//pet的编译时类型是Pet运行时类型是Dogpet.setNickname(小白);pet.eat();//可以调用父类Pet有声明的方法eat但执行的是子类重写的eat方法体
// pet.watchHouse();//不能调用父类没有的方法watchHouseDog d (Dog) pet;System.out.println(d.nickname d.getNickname());d.eat();//可以调用eat方法d.watchHouse();//可以调用子类扩展的方法watchHouseCat c (Cat) pet;//编译通过因为从语法检查来说pet的编译时类型是PetCat是Pet的子类所以向下转型语法正确//这句代码运行报错ClassCastException因为pet变量的运行时类型是DogDog和Cat之间是没有继承关系的}
}【举例2】
【Man.java】
package yuyi06;/*** ClassName: Man* Package: yuyi06* Description:** Author 雨翼轻尘* Create 2023/11/6 0006 8:54*/
public class Man extends Person {//属性boolean isSmoking;int id1002;//方法//重写public void eat(){System.out.println(男人吃饭不要挑食);}public void walk(){System.out.println(男人走路要端正);}public void earnMoney(){System.out.println(男人要挣钱养家糊口);}
}【PersonTest1.java】
package yuyi06;/*** ClassName: PersonTest1* Package: yuyi08* Description:** Author 雨翼轻尘* Create 2023/11/8 0008 23:50*/
public class PersonTest1 {public static void main(String[] args) {Person p1new Man();//不能调用子类特有的结构/* p1.earnMoney(); //通过p1去调用Man里面特有的方法是不可以的p1.isSomking; //通过p1去调用Man里面特有的属性也是不可以的*///向下转型Man m1(Man) p1; //想声明为一个Man类型括号里面写的就是Man//此时可以调用Man类里面特有的属性和方法了m1.earnMoney();System.out.println(m1.isSmoking);}
}执行结果 ⚡注意
1.不能调用子类特有的结构 2.向下转型使用强转符 3.此时p1和m1指向的是同一个堆空间中的对象
Person p1new Man();
Man m1(Man) p1;可以测试一下 输出结果 测试结果为true说明p1与m1指向堆空间的同一个对象。 那为啥不能用p1直接调用Man()里面的特有方法呢是因为声明的类型不一样只需要将类型强转一下就好了。 4.向下转型可能会出现类型转换异常
之前说基本数据类型的强制类型转换的时候说到了精度损失的问题比如double类型的数据转换为int类型的数据就会有精度损失。
这里向下转型的时候也会出现这种类似的情况。
比如
//举例2
Person p2new Woman();
Man m2(Man)p2; //将Woman强转为Man
m2.earnMoney(); //这里虽然可以通过m2调用Man里面的方法但是内存中并没有加载Man里面特有方法所以执行的时候不可以了运行报错ClassCastException 根本不是这个类型的非要强转就会出错但是对于编译器来说并不会报错。编译时正确运行时报错 3 instanceof关键字
为了避免ClassCastException的发生Java提供了 instanceof 关键字给引用变量做类型的校验。如下代码格式
//检验对象a是否是数据类型A的对象返回值为boolean型
对象a instanceof 数据类型A举例1
package com.atguigu.polymorphism.grammar;public class TestInstanceof {public static void main(String[] args) {Pet[] pets new Pet[2];pets[0] new Dog();//多态引用pets[0].setNickname(小白);pets[1] new Cat();//多态引用pets[1].setNickname(雪球);for (int i 0; i pets.length; i) {pets[i].eat();if(pets[i] instanceof Dog){Dog dog (Dog) pets[i];dog.watchHouse();}else if(pets[i] instanceof Cat){Cat cat (Cat) pets[i];cat.catchMouse();}}}
}说明 只要用instanceof判断返回true的那么强转为该类型就一定是安全的不会报ClassCastException异常。如果对象a属于类A的子类Ba instanceof A值也为true。要求对象a所属的类与类A必须是子类和父类的关系否则编译错误。 举例2
还是刚才报错的例子如下
//举例2
Person p2new Woman();
// Man m2(Man)p2; //将Woman强转为Man
// m2.earnMoney(); //这里虽然可以通过m2调用Man里面的方法但是内存中并没有加载Man里面特有方法所以执行的时候不可以了//建议在向下转型之前使用instanceof进行判断避免出现类型转换异常
if(p2 instanceof Man){ //判断一下p2是不是Man若是Man则返回true则可以转换Man m2(Man)p2; //可以强转并且调用Man里面的方法m2.earnMoney();
}再次输出看一下可以发现没有异常了因为压根没有到if里面去就没有转换成功
⚡注意 建议在向下转型之前使用instanceof进行判断避免出现类型转换异常 格式 a instanceOf A : 判断对象a是否是类A的实例。结果是布尔类型。 左边a是一个对象右边A是一个类型若对象a是A类型的一个对象那么就是true否则是false。
若此时比较的是p2是不是Woman类型p2 instanceof Woman那就会是true可以进入既然能进入那么强转是没有问题的如下
//举例2
Person p2new Woman();
// Man m2(Man)p2; //将Woman强转为Man
// m2.earnMoney(); //这里虽然可以通过m2调用Man里面的方法但是内存中并没有加载Man里面特有方法所以执行的时候不可以了//建议在向下转型之前使用instanceof进行判断避免出现类型转换异常
if(p2 instanceof Man){ //判断一下p2是不是Man若是Man则返回true则可以转换Man m2(Man)p2; //可以强转并且调用Man里面的方法m2.earnMoney();
}if(p2 instanceof Woman){System.out.println(Woman); //能够打印输出
}输出 如果a instanceOf A 返回true则 a instanceOf superA 返回也是true。其中A 是superA的子类。 比如 问a是不是一个Woman若是true则a是一个Person。其中Woman是Person的子类。 只要将Woman换成是它的父类就都可以进入if语句如下
//举例2
Person p2new Woman();
// Man m2(Man)p2; //将Woman强转为Man
// m2.earnMoney(); //这里虽然可以通过m2调用Man里面的方法但是内存中并没有加载Man里面特有方法所以执行的时候不可以了//建议在向下转型之前使用instanceof进行判断避免出现类型转换异常
if(p2 instanceof Man){ //判断一下p2是不是Man若是Man则返回true则可以转换Man m2(Man)p2; //可以强转并且调用Man里面的方法m2.earnMoney();
}if(p2 instanceof Woman){System.out.println(Woman); //能够打印输出
}if(p2 instanceof Person){System.out.println(Person);
}if(p2 instanceof Object){System.out.println(Object);
}输出结果 结论
如果在开发中需要想要向下转型想要调用具体的对象实体的特有属性和方法在转换之前需要用instanceof去判断一下是否可以转换以保证程序的健壮性。
向上转型多态前提继承性、方法的重写
向下转型强转
四、 练习
1练习1
题目描述
①定义三个类父类GeometricObject代表几何形状子类Circle代表圆形MyRectangle代表矩形。
②定义一个测试类GeometricTest
编写equalsArea方法测试两个对象的面积是否相等注意方法的参数类型使用父类体现多态性
编写displayGeometricObject方法显示对象的面积注意方法的参数类型。
类关系图 代码
①
【GeometricObject.java】
package yuyi08;/*** ClassName: GeometricObject* Package: yuyi08* Description:** Author 雨翼轻尘* Create 2023/11/9 0009 9:33*/
public class GeometricObject {//属性protected String color;protected double weight;//构造器protected GeometricObject(String color,double weight){this.colorcolor;this.weightweight;}//方法public String getColor(){return color;}public void setColor(String color){this.colorcolor;}public double getWeight() {return weight;}public void setWeight(double weight) {this.weight weight;}//求面积public double findArea(){return 0.0;}
}【Circle.java】
package yuyi08;/*** ClassName: Circle* Package: yuyi08* Description:** Author 雨翼轻尘* Create 2023/11/9 0009 9:58*/
public class Circle extends GeometricObject {//在父类构造器中只写了一个带参的构造器子类由于没有声明构造器默认是空参构造器首行默认super()又因为父类没有super()所以会报错//属性private double radius; //半径//构造器显示调用父类中带参的构造器public Circle(String color, double weight, double radius) {super(color, weight); //用自动生成(InsertAlt)也会自动调用父类构造器this.radius radius;}//方法public double getRadius() {return radius;}public void setRadius(double radius) {this.radius radius;}//计算圆的面积(对父类中的方法重写)Overridepublic double findArea() { //直接敲findArea会有弹窗显示点击回车之后自动生成这个重写方法//return super.findArea(); //默认和父类方法一致直接return父类的方法了return Math.PI*radius*radius;}
}【MyRectangle.java】
package yuyi08;/*** ClassName: MyRectangle* Package: yuyi08* Description:** Author 雨翼轻尘* Create 2023/11/9 0009 10:11*/
public class MyRectangle extends GeometricObject{//属性private double width; //宽private double height; //高//构造器public MyRectangle(String color, double weight, double width, double height) {super(color, weight);this.width width;this.height height;}//方法public double getWidth() {return width;}public void setWidth(double width) {this.width width;}public double getHeight() {return height;}public void setHeight(double height) {this.height height;}//计算矩形的面积Overridepublic double findArea() {return width*height;}
}②
【GeometricTest.java】
package yuyi08;/*** ClassName: GeometricTest* Package: yuyi08* Description:* 编写equalsArea方法测试两个对象的面积是否相等注意方法的参数类型使用父类体现多态性* 编写displayGeometricObject方法显示对象的面积注意方法的参数类型。** Author 雨翼轻尘* Create 2023/11/9 0009 10:18*/
public class GeometricTest {/*** 比较两个几何图形的面积是否相等* param g1* param g2* return true:面积相等 false面积不相等*/public boolean equalsArea(GeometricObject g1,GeometricObject g2){ //调用equalsArea方法的是GeometricTest类它与几何图形没有关系所以两个几何图形就需要通过形参来体现了return g1.findArea() g2.findArea(); //基本数据类型用不了equals}/*** 显示几何图形的面积 displayGeometricObject //display:显示 Geometric:几何 Object:图形* param g*/public void displayGeometricObject(GeometricObject g){ //具体要显示哪个几何图形呢肯定不是调用者(GeometricTest)所以需要有参数System.out.println(几何图形的面积为 g.findArea());}public static void main(String[] args) {//在当前类中调用上面的方法GeometricTest testnew GeometricTest();//测试1Circle c1new Circle(red,1.0,2.3);Circle c2new Circle(blue,1.0,3.3);test.displayGeometricObject(c1); //显示c1几何图形的面积 GeometricObject gnew Circle(red,1.0,2.3);test.displayGeometricObject(c2); //显示c2几何图形的面积boolean isEqualtest.equalsArea(c1,c2);if(isEqual){System.out.println(面积相等);}else{System.out.println(面积不相等);}//测试2//用匿名对象来写(这个对象后面也不再使用就用匿名来写即可)test.displayGeometricObject(new MyRectangle(blue,1.0,2.3,4.5));}
}运行结果 ⚡注意
多态性的体现
声明的时候是父类的引用赋值的时候是一个子类的实例。子类对象的多态性 GeometricObject gnew Circle(red,1.0,2.3);
多态性多种形态。比如左边需要一个几何图形右边给了一个具体的图形。
当我们按住Ctrl键点击findArea()方法会跳转到父类中此时认为是父类中声明的编译时 在真正运行的时候执行的是子类重写父类的方法。
所以最终的运行结果不是父类里面写的0.0而是具体的传过去的几何图形的面积 这就是虚方法调用虚拟方法调用又叫动态绑定编译的时候是方法A运行的时候是方法B编译与运行调用的不是同一个方法。动态方法可以重写。
还有一个是静态绑定编译的时候是一个方法运行的时候调用的还是这个方法编译与运行调用的是同一个方法。静态方法没有重写一说。
2练习2
题目描述
修改AnimalTest类的方法在判断Animal是Dog或Cat时向下转型并调用各自特有的方法。
【AnimalTest.java】
package yuyi09;/*** ClassName: AnimalTest* Package: yuyi07* Description:** Author 雨翼轻尘* Create 2023/11/7 0007 20:52*/
public class AnimalTest {public static void main(String[] args) {AnimalTest testnew AnimalTest();test.adopt(new Dog());test.adopt(new Cat());}public void adopt(Animal animal){ //Animal animalnew Dog(); 声明的是Animal实际上new了一个Dog//只能调用Animal里面声明的动物animal.eat();animal.jump();}/*public void adopt(Dog dog){dog.eat();dog.jump();}public void adopt(Cat cat){cat.eat();cat.jump();}*/
}class Animal{public void eat(){System.out.println(动物吃饭饭);}public void jump(){System.out.println(动物跳高高);}
}class Dog extends Animal {public void eat(){System.out.println(小狗进食);}public void jump(){System.out.println(狗急跳墙);}public void watchDoor(){System.out.println(小狗看家);}
}class Cat extends Animal {public void eat(){System.out.println(小猫吃小鱼);}public void jump(){System.out.println(小猫跳舞);}public void catMouse(){System.out.println(猫抓老鼠);}
}代码
【AnimalTest.java】
package yuyi09;/*** ClassName: AnimalTest* Package: yuyi07* Description:** Author 雨翼轻尘* Create 2023/11/7 0007 20:52*/
public class AnimalTest {public static void main(String[] args) {AnimalTest testnew AnimalTest();test.adopt(new Dog());test.adopt(new Cat());}public void adopt(Animal animal){ //Animal animalnew Dog(); 声明的是Animal实际上new了一个Dog//只能调用Animal里面声明的动物animal.eat();animal.jump();//animal.watchDoor(); //不可以/*//不够健壮Dog animal1(Dog)animal;animal1.watchDoor();*///完美if(animal instanceof Dog){Dog animal1(Dog)animal;animal1.watchDoor();} else if (animal instanceof Cat) {Cat animal2(Cat)animal;animal2.catMouse();}}/*public void adopt(Dog dog){dog.eat();dog.jump();}public void adopt(Cat cat){cat.eat();cat.jump();}*/
}class Animal{public void eat(){System.out.println(动物吃饭饭);}public void jump(){System.out.println(动物跳高高);}
}class Dog extends Animal {public void eat(){System.out.println(小狗进食);}public void jump(){System.out.println(狗急跳墙);}public void watchDoor(){System.out.println(小狗看家);}
}class Cat extends Animal {public void eat(){System.out.println(小猫吃小鱼);}public void jump(){System.out.println(小猫跳舞);}public void catMouse(){System.out.println(猫抓老鼠);}public Cat() {}
}️分析
在测试里面写了一个adopt方法参数是animal。
在编写的时候只知道是animal所以在调用的时候只能调用父类中声明的方法编译看左边。如下、
public void adopt(Animal animal){ //Animal animalnew Dog(); 声明的是Animal实际上new了一个Dog//只能调用Animal里面声明的动物animal.eat();animal.jump();
}若用animal去调用Dog类里面的方法是不可以的因为Animal类里面并没有这个方法。如下
animal.watchDoor(); //不可以此时会报错 若是想调用Dog类里面的方法必须要做强转将animal转成Dog如下
Dog animal1(Dog)animal;
animal1.watchDoor();此时就可以用animal1去调用Dog特有的方法编译器不会报错 但问题在于此时不一定是狗啊。
所以此时new Dog() 没有问题如下 但执行new Cat()的时候就会报错了 这时候adopt()方法就不够健壮。
在test.adopt(new Cat());的时候调用了adopt()方法然后Dog animal1(Dog)animal;在adopt()方法里面又调用了Dog所以就会挂掉。
要想不出异常在刚才的强转之前需要加一个判断判断animal是不是Dog若是再调用。Cat也一样。如下
public void adopt(Animal animal){ //Animal animalnew Dog(); 声明的是Animal实际上new了一个Dog//只能调用Animal里面声明的动物animal.eat();animal.jump();//animal.watchDoor(); //不可以/*//不够健壮Dog animal1(Dog)animal;animal1.watchDoor();*///完美if(animal instanceof Dog){Dog animal1(Dog)animal;animal1.watchDoor();} else if (animal instanceof Cat) {Cat animal2(Cat)animal;animal2.catMouse();}
}这样运行才是正确的 ⚡注意
看一下test.adopt(new Cat());的调用过程 3练习3
题目描述
已知代码如下
class Person {protected String nameperson;protected int age50;public String getInfo() {return Name: name \n age: age;}
}
class Student extends Person {protected String schoolpku;public String getInfo() {return Name: name \nage: age \nschool: school;}
}
class Graduate extends Student{public String majorIT;public String getInfo(){return Name: name \nage: age \nschool: school\nmajor:major;}
}建立InstanceTest 类在类中定义方法method(Person e);
在method中:
(1)根据e的类型调用相应类的getInfo()方法。
(2)根据e的类型执行
如果e为Person类的对象输出
“a person”;
如果e为Student类的对象输出
“a student”
“a person ”
如果e为Graduate类的对象输出
“a graduated student”
“a student”
“a person”
代码
【InstanceTest.java】
package yuyi10;/*** ClassName: InstanceTest* Package: yuyi10* Description:* 建立InstanceTest 类在类中定义方法method(Person e);* 在method中:* (1)根据e的类型调用相应类的getInfo()方法。* (2)根据e的类型执行* 如果e为Person类的对象输出* “a person”;* 如果e为Student类的对象输出* “a student”* “a person ”* 如果e为Graduate类的对象输出* “a graduated student”* “a student”* “a person”* Author 雨翼轻尘* Create 2023/11/10 0010 15:20*/
public class InstanceTest {public void method(Person e){//(1)根据e的类型调用相应类的getInfo()方法。需要判断一下吗不需要//是哪个类的就可以直接调用它的getInfo()方法System.out.println(e.getInfo()); //虚方法调用不用指明是哪个类型//(2)根据e的类型执行//方式1/*if(e instanceof Graduate){System.out.println(a graduated student);System.out.println(a student);System.out.println(a person);} else if (e instanceof Student) {System.out.println(a student);System.out.println(a person);}else{System.out.println(a person);}*///方式2if(e instanceof Graduate){System.out.println(a graduated student);}if(e instanceof Student){System.out.println(a student);}if(e instanceof Person){System.out.println(a person);}}public static void main(String[] args) {InstanceTest testnew InstanceTest();test.method(new Student());}
}运行结果 ⚡注意
根据e的类型调用相应类的getInfo()方法。需要判断一下吗不需要是哪个类的就可以直接调用它的getInfo()方法即可。如下
package yuyi10;/*** ClassName: InstanceTest* Package: yuyi10* Description:* 建立InstanceTest 类在类中定义方法method(Person e);* 在method中:* (1)根据e的类型调用相应类的getInfo()方法。* Author 雨翼轻尘* Create 2023/11/10 0010 15:20*/
public class InstanceTest {public void method(Person e){//根据e的类型调用相应类的getInfo()方法。需要判断一下吗不需要/*if(e instanceof Graduate){} else if (e instanceof Student) {} else if (e instanceof Person) {}*///是哪个类的就可以直接调用它的getInfo()方法e.getInfo();}
}4练习4
题目描述
1、在包中声明人Person、男人Man、女人Woman类
1在Person类中包含
①public void eat()打印吃饭
②public void toilet()打印上洗手间
2在Man类中包含
①重写上面的方法
②增加 public void smoke()打印抽烟
3在Woman类中包含
①重写上面的方法
②增加 public void makeup()打印化妆
2、在包中声明测试类Exer4
1public static void meeting(Person… ps)
在该方法中每一个人先吃饭然后上洗手间然后如果是男人随后抽根烟如果是女人随后化个妆
2public static void main(String[] args)
在主方法中创建多个男人和女人对象并调用meeting()方法进行测试
代码
【Person.java】
package yuyi11;/*** ClassName: Person* Package: yuyi11* Description:* 1在Person类中包含* ①public void eat()打印吃饭* ②public void toilet()打印上洗手间* Author 雨翼轻尘* Create 2023/11/10 0010 16:36*/
public class Person {//打印吃饭public void eat(){System.out.println(人吃饭);}//打印上洗手间public void toilet(){System.out.println(人去洗手间);}
}【Man.java】
package yuyi11;/*** ClassName: Man* Package: yuyi11* Description:* 2在Man类中包含* ①重写上面的方法* ②增加 public void smoke()打印抽烟* Author 雨翼轻尘* Create 2023/11/10 0010 16:37*/
public class Man extends Person{//打印吃饭public void eat(){System.out.println(男人大口地吃饭);}//打印上洗手间public void toilet(){System.out.println(男人去男洗手间);}//打印抽烟public void smoke(){System.out.println(男人不要抽太多烟);}
}【Woman.java】
package yuyi11;/*** ClassName: Woman* Package: yuyi11* Description:* 3在Woman类中包含* ①重写上面的方法* ②增加 public void makeup()打印化妆* Author 雨翼轻尘* Create 2023/11/10 0010 16:38*/
public class Woman extends Person {//打印吃饭public void eat(){System.out.println(女人优雅地吃饭);}//打印上洗手间public void toilet(){System.out.println(女人去女洗手间);}//打印化妆public void makeup(){System.out.println(女人画美美的妆);}}【Exer4.java】
package yuyi11;/*** ClassName: Exer4* Package: yuyi11* Description:* 1public static void meeting(Person... ps)* 在该方法中每一个人先吃饭然后上洗手间然后如果是男人随后抽根烟如果是女人随后化个妆** 2public static void main(String[] args)* 在主方法中创建多个男人和女人对象并调用meeting()方法进行测试* Author 雨翼轻尘* Create 2023/11/10 0010 16:45*/
public class Exer4 {public void meeting(Person... ps){ //可变形参相当于一个数组ps相当于数组名for (int i 0; i ps.length; i) {ps[i].eat();ps[i].toilet();if(ps[i] instanceof Woman){Woman woman(Woman) ps[i];woman.makeup();}else if(ps[i] instanceof Man){Man man(Man) ps[i];man.smoke();}//换行System.out.println();}}public static void main(String[] args) {Exer4 exernew Exer4(); //调用非静态地方法需要通过对象去调用exer.meeting(new Man(),new Woman(),new Man());}
}运行结果 ⚡补充 if(ps[i] instanceof Woman w){//Woman woman(Woman) ps[i];//woman.makeup();w.makeup();
}JDK7新特性
如图 instanceof后面不仅可以写子类也可以写父类比如Object如下 但是如果是随意一个类比如String编译都会报错如下 这里的变量ps有类型这里是Person我们通常用instanceof是要判断另一个是不是这个类型的那么这里基本是拿着基于Person的父类或子类来比较的编译都可以通过运行就要看比较结果是true还是false了。
若是这个类与Person没有任何关系比如String那么就不可以光是判断也不可能是true编译都不会通过。 五、面试题
1题目1
下面代码输出结果是多少
package com.atguigu06.polymorphism.interview;class Base {int count 10;public void display() {System.out.println(this.count);}
}class Sub extends Base {int count 20;public void display() {System.out.println(this.count);}
}public class FieldMethodTest {public static void main(String[] args){Sub s new Sub();System.out.println(s.count);//20s.display();//20Base b s;System.out.println(b s); //trueSystem.out.println(b.count);//10b.display();//20Base b1 new Base();System.out.println(b1.count); //10b1.display();//10}
}输出结果 分析 属性不存在多态性编译和执行都看左边方法有多态性编译看左边执行看右边。 2题目2
多态是编译时行为还是运行时行为
package com.atguigu06.polymorphism.interview;import java.util.Random;/*** 面试题多态是编译时行为还是运行时行为*/
class Animal {protected void eat() {System.out.println(animal eat food);}
}class Cat extends Animal {protected void eat() {System.out.println(cat eat fish);}
}class Dog extends Animal {public void eat() {System.out.println(Dog eat bone);}
}class Sheep extends Animal {public void eat() {System.out.println(Sheep eat grass);}
}public class InterviewTest {public static Animal getInstance(int key) {switch (key) {case 0:return new Cat ();case 1:return new Dog ();default:return new Sheep ();}}public static void main(String[] args) {int key new Random().nextInt(3); //0,1,2System.out.println(key);Animal animal getInstance(key);animal.eat();}
}执行结果 分析
多态表现得特性泛泛来说就是多种形态在Java里面目前来说就是子类对象的多态性。
比如这个例子中看到的是一个一个的Person类但是实际上传的值是一个一个的子类对象多态性。 现在的问题是这种多态性是在编译的时候确定的还是在运行时才确定下来
其实是运行的时候因为编译的时候认为的是声明的类型父类类型只有运行的时候才知道真正加载进来的是哪一个子类。 此题中有Animal一个父类有Cat、Dog、Sheep三个子类并且重写了方法。
看图