Java多态

182 阅读13分钟

多态:意味着允许不同类的对象对同一消息做出不同的响应。

分为以下两种:

编译时多态:设计时多态,使用方法重载实现

运行时多态:程序运行时动态决定调用哪个方法

Java中的多态一般指运行时多态

必要条件:

-满足继承关系

-父类引用指向子类对象

package com.imooc.animal;

public class Animal {
	//属性:昵称、年龄
	private String name;
	private int month;
	public Animal() {
		
	}
	public Animal(String name,int month) {
		this.name=name;
		this.month=month;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getMonth() {
		return month;
	}
	public void setMonth(int month) {
		this.month = month;
	}
	//方法:吃东西
	public void eat() {
		System.out.println("动物都有吃东西的能力");
	}
	
}


package com.imooc.animal;

public class Cat extends Animal{
	//属性:体重
	private double weight;
	public Cat() {
		
	}
	public Cat(String name,int month,double weight) {
		super(name,month);
		this.weight=weight;
	}
	public double getWeight() {
		return weight;
	}
	public void setWeight(double weight) {
		this.weight = weight;
	}
	//方法:跑动
	public void run() {
		System.out.println("小猫快乐地奔跑");
	}
	//方法:吃东西(重写父类方法)
	@Override
	public void eat() {
		System.out.println("猫吃鱼");
	}
}


package com.imooc.animal;

public class Dog extends Animal {
	//属性:性别
	private String sex;
	public Dog() {
		
	}
	public Dog(String name,int month,String sex) {
		this.setMonth(month);
		this.setName(name);
		this.setSex(sex);
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	//方法:睡觉
	public void sleep() {
		System.out.println("小狗有午睡的习惯");
	}
	//方法:吃东西(重写父类方法)
	@Override
	public void eat() {
		System.out.println("狗吃肉");
	}
        //父类中用static修饰的方法不允许子类进行重写,只能继承使用
        //子类中的同名同修饰方法public static void say()则算作是子类独有的方法
        //向上转型后,只能调用到父类原有的静态方法
        public static void say(){
                System.out.println("动物间打招呼");
        }
}


package com.imooc.test;

import com.imooc.animal.Animal;
import com.imooc.animal.Cat;
import com.imooc.animal.Dog;

public class Test {

	public static void main(String[] args) {
		Animal one=new Animal();//1
		/*
		 * 向上转型(隐式转型)(自动转型)
		 * 父类引用指向子类实例,可以调用子类重写
		 * 父类的方法以及父类派生的方法,无法调用子类独有方法
		 * 小类转型为大类
		 */
		Animal two=new Cat();//2
                //等效于以下两句
                //Cat cat=new Cat();
                //two=cat;
		Animal three=new Dog();//3
		one.eat();
		two.eat();
		two.eat();
		two.setMonth(2);
		two.getMonth();
		//two.run(); 报错
		three.eat();
	}
}

向上转型和动态绑定机制

多态的实现可以借由向上转型和动态绑定机制来完成,其中,向上转型实现了将子类对象向上转型为父类类型;而动态绑定机制能识别出对象转型前的类型,从而自动调用该类的方法,两者相辅相成。

向上转型

语法:父类类型 父类引用=new 子类类型(参数);

即父类引用指向子类实例,也表示子类的对象可以赋值给父类的对象。

绑定机制

绑定就是将一个方法调用同一个方法所在的类连接到一起。

Java中存在静态绑定和动态绑定两种。

静态绑定是指在程序运行之前进行绑定(由编译器和链接程序完成),也叫前期绑定。

动态绑定是指在程序运行期间由JVM根据对象的类型自动的判断应该调用哪个方法,也叫后期绑定。

向下转型一般是为了重新获得因为向上转型而丢失的子类特性而存在,因此,通常在向下转型前需要先进行向上转型,向下转型通常也会结合instanceof一起应用

1.向下转型需在满足向上转型前提下进行

2.注意类型转换时的括号应用

                /*
		 * 向下转型(强制类型转换)
		 * 子类引用指向父类对象,此处必须进行强转,可以调用子类特有的方法
		 * 必须满足转型条件才能进行强转
		 */
                (Cat)two.run();//报错
                //由于运算符优先级,会优先执行two.run();;该方法运行后才会进行Cat类型转换                ((Cat)two).run();//正确写法
		Cat temp=(Cat)two;
		temp.eat();
		temp.run();
		temp.getMonth();
	
		Dog temp2=(Dog)two;//运行时报错
                //向下转型时,不允许转为非原始类型,即“哪来回哪去”
		temp2.eat();//运行时报错
		temp2.sleep();//运行时报错
		temp2.getSex();//运行时报错

instanceof:返回true/false

判断左边的对象是否是右边的实例(右边类型对象的特征)

	        if(two instanceof Cat) {
			Cat temp=(Cat)two;
			temp.eat();
			temp.run();
			temp.getMonth();
			System.out.println("two可以转换为Cat类型");
		}
		if(two instanceof Dog) {
			Dog temp2=(Dog)two;
			temp2.eat();
			temp2.sleep();
			temp2.getSex();
			System.out.println("two可以转换为Dog类型");
		}
		if(two instanceof Animal) {
			System.out.println("Animal");
		}
		if(two instanceof Object) {
			System.out.println("Object");
		}

package com.imooc.animal;

public class Master {
	/*
	 * 喂宠物
	 * 喂猫咪:吃完东西后,主人会带着去玩线球
	 * 喂狗狗:吃完东西后,主人会带着狗狗去睡觉
	 */
	//方案一:编写方法,传入不同类型的动物,调用各自的方法
//	public void feed(Cat cat) {
//		cat.eat();
//		cat.playBall();
//	}
//	public void feed(Dog dog) {
//		dog.eat();
//		dog.sleep();
//	}
	//方案二:编写方法传入动物的父类,方法中通过类型转换,调用指定子类的方法
	public void feed(Animal obj) {
		if(obj instanceof Cat) {
			Cat temp=(Cat)obj;
			temp.eat();
			temp.playBall();
		}else if(obj instanceof Dog) {
			Dog temp=(Dog)obj;
			temp.eat();
			temp.sleep();
		}
	}
	/*
	 * 饲养何种宠物
	 * 空闲时间多:养狗狗
	 * 空闲时间不多:养猫咪
	 */
	//方案一
//	public Dog hasManyTime() {
//		System.out.println("主人休闲时间比较充足,适合养狗狗");
//		return new Dog();
//	}
//	public Cat hasLittleTime() {
//		System.out.println("主人平时比较忙碌,适合养猫咪");
//		return new Cat();
//	}
	//方案二
	public Animal raise(boolean isManyTime) {
		if(isManyTime) {
			System.out.println("主人休闲时间比较充足,适合养狗狗");
			return new Dog();
		}else {
			System.out.println("主人平时比较忙碌,适合养猫咪");
			return new Cat();
		}
	}
}


package com.imooc.test;

import com.imooc.animal.Animal;
import com.imooc.animal.Cat;
import com.imooc.animal.Dog;
import com.imooc.animal.Master;

public class MasterTest {

	public static void main(String[] args) {
		Master master=new Master();
		Cat one =new Cat();
		Dog two=new Dog();
		master.feed(one);
		master.feed(two);
		boolean isManyTime=false;
		//向上转型
//		Animal temp;
//		if(isManyTime) {
//			temp=master.hasManyTime();
//		}else {
//			temp=master.hasLittleTime();
//		}
		Animal temp=master.raise(isManyTime);
		System.out.println(temp);
	}

}

抽象类

应用场景:某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现这些方法。

目的:1.为其子类提供一个公共的类型(父类引用指向子类对象)

2.封装子类中的重复内容(成员变量和方法)

3.将父类设计成抽象类后,藉由父子类的继承关系,限制子类的设计随意性,避免了无意义父类的实例化。

//抽象类:不允许实例化,只能被继承,可以通过向上转型,指向子类实例
public abstract class Animal {

抽象方法

abstract定义抽象方法,不需要具体实现

包含抽象方法的类 是抽象类

抽象类中可以没有抽象方法

        //抽象方法:加上abstract后要求父类方法不能有方法体
	//子类必须重写该抽象方法,否则子类也是抽象类才能不报错
	//static final private不能与abstract并存
	public abstract void eat();

接口

接口定义了某一批类所需要遵守的规范

接口不关心这些类的内部数据,也不关系这些类里方法的实现细节,它只规定这些类里必须提供某些方法

package com.imooc.tel;
/*
 * 原始手机
 */
public class Telephone {
	private String brand;
	private int price;
	public Telephone() {
		
	}
	
	public String getBrand() {
		return brand;
	}

	public void setBrand(String brand) {
		this.brand = brand;
	}

	public int getPrice() {
		return price;
	}

	public void setPrice(int price) {
		this.price = price;
	}
	//打电话
	public void call() {
		System.out.println("手机可以打电话");
	}
}

package com.imooc.tel;

public class SecondTelephone extends Telephone{
	public void message() {
		System.out.println("手机可以发短信");
	}
}


package com.imooc.tel;

public class ThirdPhone extends SecondTelephone{
	public void video() {
		System.out.println("手机可以看视频");
	}
	public void music() {
		System.out.println("手机可以听音乐");
	}
}

package com.imooc.tel;

public class FourthPhone extends ThirdPhone implements IPhoto {
//	public void photo() {
//		System.out.println("手机可以拍照");
//	}
	public void network() {
		System.out.println("手机可以上网");
	}
	public void game() {
		System.out.println("手机可以玩游戏");
	}
	@Override
	public void photo() {
		System.out.println("手机可以拍照");
		
	}
}

package com.imooc.tel;

public class Camera implements IPhoto{

	@Override
	public void photo() {
		System.out.println("相机可以拍照");
	}
//	public void photo() {
//		System.out.println("相机可以拍照");
//	}
}


package com.imooc.tel;

public class SmartWatch implements INet{
	public static final int TEMP=30;
	@Override
	public void network() {
		System.out.println("智能手表可以上网");
		
	}

	@Override
	public void connection() {
		System.out.println("智能手表可以连接");
		
	}

}

package com.imooc.tel;

public abstract class Computer implements INet{
	@Override
	public void network() {
		System.out.println("电脑可以上网");
	}
	
}


package com.imooc.tel;
/*
 * 具有照相能力的接口
 */
public interface IPhoto {
	//具有拍照的能力
	public void photo();
}


package com.imooc.tel;
//接口访问修饰符:public 或者 默认
public interface INet {
	//接口中抽象方法可以不写abstract关键字
	//方法访问修饰符默认public
	//当一个类实现接口时,需要去实现接口中的所有抽象方法,否则需要将该类设置为抽象类
	public void network();
	public void connection();
	//接口中可以包含常量,默认public static final
	int TEMP=20;
}


package com.imooc.test;

import com.imooc.tel.Camera;
import com.imooc.tel.FourthPhone;
import com.imooc.tel.INet;
import com.imooc.tel.IPhoto;
import com.imooc.tel.SmartWatch;

public class PhoneTest {

	public static void main(String[] args) {
		FourthPhone phone=new FourthPhone();
		phone.call();
		phone.message();
		phone.video();
		phone.music();
		phone.photo();
		phone.network();
                //作为接口,不能直接实例化对象,只能设置接口的引用指向具体的接口实现类
                //当使用接口引用指向实现类时,不能直接调用实现类独有的方法,需要强制类型转换才能调用
		//接口引用指向实现类
		IPhoto ip=new FourthPhone();
		ip.photo();
		ip=new Camera();
		ip.photo();
		System.out.println(INet.TEMP);
		INet net=new SmartWatch();
		System.out.println(net.TEMP);//20?30?
		SmartWatch sw=new SmartWatch();
		System.out.println(sw.TEMP);
	}

}

默认方法和静态方法

都在接口中提供了带方法体的实现,这些方法在接口的实现类中可以直接应用

默认方法:

1.在实现类中需要通过接口引用进行访问

2.可以在实现类中重写与重载,重写时需去掉default关键字,重载的方法无法通过接口引用访问

3.重写方法中,可以通过:接口.super.默认方法的方式调用接口中原有的默认方法

静态方法:

1.在实现类中需要通过接口名进行访问

2.实现类无法继承(即直接应用)接口中的静态方法

3.实现类无法重写静态方法,可以存在相同格式静态方法,但两者独立存在

        //default:默认方法 可以带方法体 jdk1.8后新增 
	//可在实现类中重写,并可以通过接口的引用调用
	default void connection() {
		System.out.println("我是接口中的默认链接");
	}
	//static:静态方法 可以带方法体 jdk1.8后新增
	//不可以在实现类中重写,可以通过接口名调用
	static void stop() {
		System.out.println("我是接口中的静态方法");
	}

        @Override
	public void connection() {
		// TODO Auto-generated method stub
		INet.super.connection();//调用接口中默认的方法
	}

        net.connection();
	INet.stop();

在一个Java文件中可以存在多个类,多个接口,但是只能存在一个public修饰的类或接口,且此时文件名需要与public修饰的类或者接口同名。

如果希望更多的接口扩展方法是支持在无实例对象产生的情况下调用,那么静态方法更佳;如果希望实现类能更多的直接应用接口中的方法,则默认方法更适合。

//接口的实现要放在继承的后面
public class FourthPhone extends ThirdPhone implements IPhoto,INet {

//在Java中,一个类可以实现多个接口
public class SmartWatch implements INet,IPhoto{

多接口中重名默认方法如何处理?

        default void connection() {
		System.out.println("我是IPhoto接口中的默认链接");
	}

        default void connection() {
		System.out.println("我是INet接口中的默认链接");
	}

同时实现以上接口会导致实现类报错,解决办法:实现类中重写默认方法

	public void connection() {
		System.out.println("SmartWatch中的connection");
	}

        //若父类中同样存在同名方法,则优先调用父类方法
	public void connection() {
		System.out.println("我是ThirdPhone的connection");
	}

        //或者可以在当前实现类中重写该方法,则优先调用该方法
	public void connection() {
		System.out.println("我是FourthPhone的connection");
	}

关于多接口中重名常量该如何处理?

package com.imooc.test;
interface One{
	static int x=11;
}
interface Two{
	final int x=22;
}
class Three{
	public int x=33;
}
public class Testone implements One,Two{
	public void test() {
		//System.out.println(x);//error:The field x is ambiguous
		//正确写法
		System.out.println(One.x);
		System.out.println(Two.x);
	}
	public static void main(String[] args) {
		new Testone().test();
	}
}


package com.imooc.test;
interface One{
	static int x=11;
}
interface Two{
	final int x=22;
}
class Three{
	public int x=33;
}
public class Testone extends Three implements One,Two{
	public int x=44;//加上后报错消失
	public void test() {
		//System.out.println(x);//当父类成员与接口常量重名时,子类无法判断使用具体哪一个常量
	}
	public static void main(String[] args) {
		new Testone().test();
	}
}

当接口与父类中存在相同成员时,无法判断将调用哪个常量;会优先继承父类的成员方法及静态方法

接口的继承

package com.imooc.test;

public interface IFather {
	void say();
	default void connection() {
		System.out.println("IFather中的connection");
	}
}

package com.imooc.test;

public interface IFather2 {
	void fly();
	default void connection() {
		System.out.println("IFather2中的connection");
	}
        static void sleep(){

        }
}


package com.imooc.test;
//接口也可以实现继承,并且可以继承多个父接口
public interface ISon extends IFather ,IFather2{
	void run();
	//方案一、无法判断使用哪个父类的connection方法,必须自己定义同名方法
	default void connection() {
		System.out.println("ISon中的connection");
	}
        //方案二
        @Override
        default void run(){
            //当多父接口中存在相同方法签名的默认方法时,
            //无法判断用哪个,需要子接口中重写
            IFather.super.run();
        }
}

package com.imooc.test;

public class Demo implements ISon {

	@Override
	public void say() {
		// TODO Auto-generated method stub

	}

	@Override
	public void run() {
		// TODO Auto-generated method stub

	}

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		
	}
        public static void main(String[] args){
                //不可继承静态方法
                //error:The method sleep() is undefined for the type ISon
                ISon.sleep();
        }
}

如果是单继承,可以继承父接口中定义的常量、抽象方法、默认方法,无法继承静态方法

如果是多继承,只可以继承父接口中定义的抽象方法,常量和默认方法则无法分辨应用哪个。

接口和抽象类的比较

当描述多种毫无关系的类型之间的共同行为能力时,更推荐使用接口;而当多类间可以形成可追溯的产生轨迹时,更推荐通过抽象类继承

UML中的类关系表示

结合UML(统一建模语言)图例来表示类与类之间的各种关系

类的层次结构代表类与类之间的关系,类的关系有很多种,包括泛化、实现、依赖、关联、聚合和组合,这些关系在大的类别还可分为两种:横向关系和纵向关系,其中,纵向关系就是泛化、实现,而横向关系就是剩下的四种,它们的强弱关系:依赖<关联<聚合<组合

内部类

在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类

与之对应,包含内部类的类被称为外部类,内部类隐藏在外部类之内,更好的实现了信息隐藏

分为四种,成员内部类、静态内部类、方法内部类和匿名内部类

1.成员内部类

package com.imooc.people;
//外部类
public class Person {
	public int age;
	
	public Heart getHeart() {
		new Heart().temp=12;
		return new Heart();
	}
	public void eat() {
		System.out.println("人会吃东西");
	}
	//成员内部类
	/*
	 * 1.内部类在外部使用时,无法直接实例化,需要借由外部类信息才能完成实例化
	 * 2.内部类的访问修饰符,可以任意,但是访问范围会受到影响
	 * 3.内部类可以直接访问外部类的成员;如果出现同名属性,优先访问内部类中定义的
	 * 4.可以使用外部类.this.成员的方式,访问外部类中同名的信息
	 * 5.外部类访问内部类信息,需要通过内部类实例,无法直接访问
	 * 6.内部类编译后,class文件命名:外部类 $内部类.class
	 * 7.内部类中是否可以包含与外部类相同方法签名的方法?
	 */
	public class Heart{
		int age=13;
		int temp=22;
		public String beat() {
			eat();
			return Person.this.age+"岁心脏在跳动";
		}
		
	}
}

package com.imooc.people;

public class PeopleTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Person lili=new Person();
		lili.age=12;
		//获取内部类对象实例,方式1:new 外部类.new 内部类
		Person.Heart myHeart=new Person().new Heart();
		System.out.println(myHeart.beat());
		//获取内部类对象实例,方式2:外部类对象.new 内部类
		myHeart=lili.new Heart();
		System.out.println(myHeart.beat());
		//获取内部类对象实例,方式3:外部类对象.获取方法
		myHeart=lili.getHeart();
		System.out.println(myHeart.beat());
	}

}

2.静态内部类

静态内部类对象可以不依赖于外部类对象,直接创建