多态首先是建立在继承的基础上的,先有继承才能有多态。多态是指不同的子类在继承父类后分别都重写覆盖了父类的方法,即父类同一个方法,在继承的子类中表现出不同的形式。多态成立的另一个条件是在创建子类时候必须使用父类new子类的方式。
通俗的讲:多态是融合了重写和重载特点的结合体,多态有重载的形参列表不同的特点也有重写的继承类可访问的特点。
//食物类
public class Food {
private String name;
public Food(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
//鱼类
public class Fish extends Food{
public Fish(String name) {
super(name);
}
}
//骨头类
public class Bone extends Food{
public Bone(String name) {
super(name);
}
}
//动物类
public class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
//狗类
public class Dog extends Animal{
public Dog(String name) {
super(name);
}
}
//猫类
public class Cat extends Animal{
public Cat(String name) {
super(name);
}
}
//主人类
public class Master {
private String name;
public Master(String name) {
this.name = name;
}
public void feed(Cat cat,Fish fish){
System.out.println("主人"+name+"正在喂他的"+cat.getName()+"吃"+fish.getName());
}
public void feed(Dog dog,Bone bone){
System.out.println("主人"+name+"正在喂他的"+dog.getName()+"吃"+bone.getName());
}
}
//测试类
public class Test07 {
public static void main(String[] args) {
Master tom = new Master("汤姆");
Dog dog = new Dog("旺财");
Bone bone = new Bone("大骨头");
Cat cat = new Cat("小咪");
Fish fish = new Fish("小黄鱼");
tom.feed(dog,bone);
tom.feed(cat,fish);
}
}
这样的话就后面再想喂其他的动物吃东西,又得重载feed方法。复用性太高因此就需要多态,来减少代码的复用性。
在前面的学习中,重载和重写方法,就是让不同条件下调用不同的方法,这就是多态的一种体现方式.
例如
//A类
public class A {
public int sum(int a,int b){
return a+b;
}
public int sum(int a,int b,int c){
return a+b+c;
}
}
//测试类
public class Test07 {
public static void main(String[] args) {
A a1 = new A();
a1.sum(44,55);
a1.sum(55,66,44);
}
}
这里就是根据不同的形参列表找到不同的sum方法,也是一种多态的体现。同理重写方法也是一种多态的体现
编译类型就是编译成class文件时的类型,运行类型就是实际运行的类型
例如:现在有三个类 Animal类和Dog类和Cat类,其中Animal类是他们的父类。
//动物类
public class Animal {
public void cry(){
System.out.println("某种动物的叫声");
}
}
//狗类
public class Dog extends Animal{
public void cry(){
System.out.println("小狗叫...");
}
}
//猫类
public class Cat extends Animal{
public void cry(){
System.out.println("小猫叫...");
}
}
可以看到子类重写了父类的cry方法。
假设在测试类中实例化Animal类 Animal animal = new Animal(); 然后调用animal的方法 animal.cry();
输出的是:某种动物的叫声
那么上面说了对象的多态体现中有一点: 运行类型是可以变化的
那么我们根据这个特点,想输出狗类的cry方法,就不用再实例化一个Dog类的对象了,只需更改animal的运行时对象即可 也就是 父类的引用指向子类的对象
Animal animal = new Dog(); 然后再次调用cry方法 animal.cry();
输出的是
小狗叫…
这里是就是再继承的基础上,借用重写的特点,体现了对象的一个多态
内存图演示:
从上面了解到,多态的基本特点和思想。那么同理也可以利用多态的特点:父类的引用可以指向子类的对象
来解决一开始的主人 喂宠物 的问题,先开始的方法代码复用性比较高,如果要新增一个动物,就要在主人类中重载一个Feed方法,那么由于学习了多态-父类的引用可以指向子类的对象-这个特点,我们可以在父类的Feed方法中形参直接使用动物和食物的父类,然后实际传入的是他们的子类,这样使用多态的特点,减少代码的复用性.
//食物类
public class Food {
private String name;
public Food(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
//鱼类
public class Fish extends Food{
public Fish(String name) {
super(name);
}
}
//骨头类
public class Bone extends Food{
public Bone(String name) {
super(name);
}
}
//动物类
public class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
//狗类
public class Dog extends Animal{
public Dog(String name) {
super(name);
}
}
//猫类
public class Cat extends Animal{
public Cat(String name) {
super(name);
}
}
//主人类
public class Master {
private String name;
public Master(String name) {
this.name = name;
}
//这里形参直接使用父类,届时传入他们的子类对象。
public void feed(Animal animal,Food food){
System.out.println("主人"+name+"正在喂他的"+animal.getName()+"吃"+food.getName());
}
}
//测试类
public class Test07 {
public static void main(String[] args) {
Master tom = new Master("汤姆");
Dog dog = new Dog("旺财");
Bone bone = new Bone("大骨头");
Cat cat = new Cat("小咪");
Fish fish = new Fish("小黄鱼");
tom.feed(dog,bone);
tom.feed(cat,fish);
}
}
这样即实现了功能,也为后续的代码拓展性增加了。例如再增加一个猪类,吃米饭
//动物 猪类
public class Pig extends Animal{
public Pig(String name) {
super(name);
}
}
//食物类 米饭
public class Rice extends Food{
public Rice(String name){
super(name);
}
}
//测试类
public class Test07 {
public static void main(String[] args) {
Master tom = new Master("汤姆");
Dog dog = new Dog("旺财");
Bone bone = new Bone("大骨头");
Cat cat = new Cat("小咪");
Fish fish = new Fish("小黄鱼");
Pig pig = new Pig("小猪");
Rice rice = new Rice("米饭");
tom.feed(dog,bone);
tom.feed(cat,fish);
tom.feed(pig,rice);
}
}
向上转型的本质就是 父类的引用指向子类的对象
语法:父类类型 引用名 = new 子类类型();
规则-
规则解释:
其实后面三点的规则都是在解释第一点的规则
直接看第二点----可以调用父类的所有成员(属性,方法),但需遵守访问修饰符规则
现在有一个父类Animal类和一个子类Cat类
//父类
public class Animal {
public void run(){
System.out.println("跑");
}
public void sleep(){
System.out.println("睡");
}
private void show(){
System.out.println("你好");
}
}
//子类
public class Cat extends Animal{
//子类的特有方法 抓老鼠
public void catchMouse(){
System.out.println("抓老鼠");
}
}
//测试类
public class Test08 {
public static void main(String[] args) {
Animal animal = new Cat();
}
}
可以看到在测试类中是父类 Animal 的引用指向了子类Cat,根据规则可以调用父类所有的成员
可以看到跑和睡方法都可以调用,但是私有化的show方法不能直接调用,得父类提供一个公开的调用方法。
再看第三点----不能调用子类中的特有成员
因为在编译阶段,看的是左边编译类型,也就是父类Animal,而子类的特有方法父类是没有的,自然就无法调用了
第四点----最终运行内容看子类具体实现,即从子类中开始找成员,找到即返回,子类没有再去父类找。
最终运行就是编译完成后,运行的内容。这时看的就是右边运行类型也就是子类,那么对于成员和方法也就是和this的规则一样,先从子类开始找,子类没有再去父类。
例如 在Cat类中重写一下父类的run方法,然后在测试类中调用
public class Cat extends Animal{
//子类的特有方法 抓老鼠
public void catchMouse(){
System.out.println("抓老鼠");
}
//重写父类的run方法
public void run(){
System.out.println("子类的run方法");
}
}
//测试类
public class Test08 {
public static void main(String[] args) {
Animal animal = new Cat();
animal.run();
animal.sleep();
}
}
输出结果
从子类开始找run方法,发现子类有那么就执行子类的,然后在子类找sleep方法,发现没有,再去父类找,找到了执行父类的sleep方法
因为在向上转型中,编译阶段只能调用父类的成员,无法调用子类的特有成员,所以在向上转型的基础上发展了向下转型
语法:子类类型 引用名 = (子类类型) 父类引用名;
规则-
规则解释
第一点----只能强转父类的引用,不能强转父类的对象
当一个人生下来时,他就是一个人,他会改名字,但是他还是一个人类,不能变成狗 变成鸟。
同理向下转型不能改变在堆中的对象,只能改变在栈中的引用(也就是改变编译类型)
第二点----要求父类的引用必须指向的是当前向下转型目标类型的对象
还是使用上面那个案例
//父类
public class Animal {
public void run(){
System.out.println("跑");
}
public void sleep(){
System.out.println("睡");
}
private void show(){
System.out.println("你好");
}
}
//子类
public class Cat extends Animal{
//子类的特有方法 抓老鼠
public void catchMouse(){
System.out.println("抓老鼠");
}
}
//测试类
public class Test08 {
public static void main(String[] args) {
Animal animal = new Cat();
//向上转型之后再向下转型
Cat cat = (Cat)animal;
}
}
当向下转型后内存中:
因为向下转型是为了可以使用子类的方法,当父类的引用指向另一个对象时,就不符合逻辑了,也失去了原本的意义。其中父类的引用指向的是Cat对象,要让Dog类指向Cat类。这不就是说狗是猫?,原本是“动物”是“猫”,这是向上转型,然后“猫”是“动物”,向下转型,这都是符合逻辑的,但是猫是狗就不符合逻辑了。运行会报错
第三点----当向下转型后,可以调用子类类型中所有的成员
也就是向下转型的目的,当向上转型时,无法使用父类的引用,调用子类的特有成员,所以向下转型就是改变父类的引用变成子类的引用。
Animal animal = new Cat();
//向上转型之后再向下转型
Cat cat = (Cat)animal;
这样编译类型就是子类类型了
//测试类
public class Test08 {
public static void main(String[] args) {
Animal animal = new Cat();
//向上转型之后再向下转型
Cat cat = (Cat)animal;
cat.catchMouse();
}
}
属性没有重写一说,属性的值看编译类型
//A类父类
class A{
public int num = 10;
}
//B类子类
class B extends A{
public int num = 20;
}
//测试类
A a = new B();//向上转型
System.out.println(a.num);
属性不会和方法一样,从运行类型开始找起,而是直接看编译类型,所以会输出=10,当然如果编译类型没有找到,也会去编译类型的父类找。
instanceOf是一个比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的子类型
举例:
public class Test08 {
public static void main(String[] args) {
Object obj = new Object();
System.out.println(obj instanceof Object);//obj的运行类型就是Object--true
Cat cat = new Cat();
System.out.println(cat instanceof Animal);//cat的运行类型就是Animal的子类--true
}
}
说出下面语句哪些是对是错 为什么
double d =13.4;//对
long i = (long)d;//对
System.out.println(i);//对
int a = 5;//对
boolean b = (boolean) a;//错,int类型不能转为布尔类型
Object obj = "heelo";//对 向上转型
String str = (String)obj;//对 向下转型
System.out.println(str);//对 输出heelo
Object obj2 = new Integer(5);//对向上转型
String str2 = (String)obj2;//错 父类的引用必须指向的是当前向下转型目标类型的对象也就是必须指向Integer
Integer in1 = (Integer) obj2;//对向下转型
看看下面测试类中会输出什么
public 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);
}
}
class Test09{
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.count);//属性直接看编译类型,所以输出20
s.display();//方法从运行类型开始找,所以是20
Base b = s;//向上转型
System.out.println(b == s);//这里是比较运行类型,两个引用的运行类型都是Sub所以是true
System.out.println(b.count);//属性直接看编译类型,所以输出10
b.display();//方法从运行类型开始找,所以是20
}
}