抽象类,多态的应用,引用时关于成员变量与成员方法引用的原则(超详细)

                                                 抽象类

 

抽象方法 : 没有方法体的方法。

抽象类被abstract所修饰的类。

//抽象类的语法格式:
【权限修饰符】 abstract class 类名{
    
}
【权限修饰符】 abstract class 类名 extends 父类{
    
}
​
//抽象方法的语法格式
【其他修饰符】 abstract 返回值类型  方法名(【形参列表】);

注意:抽象方法没有方法体

 

关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

    理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  2. 抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。

    理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法。

  3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

    理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

  4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。

    理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

 

                                                     多态

例如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。那么此时就会出现各种子类的类型。但是Java是强类型静态语言,既每一个变量在使用之前必须声明它确切的类型,然后之后的赋值和运算时都是严格按照这个数据类型来处理的。例如:

int num = 10;
String str = "hello";
Student stu = new Student();

但是,有的时候,我们在设计一个数组、或一个方法的形参、返回值类型时,无法确定它具体的类型,只能确定它是某个系列的类型。这个时候,Java就引入了多态。

1、格式

父类类型 变量名 = 子类对象;
​
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

2、编译时类型与运行时类型不一致问题

  • 编译时,看“父类”,只能调用父类声明的方法,不能调用子类扩展的方法(即调用的方法父类中必须有);

  • 运行时,看“子类”,一定是执行子类重写的方法体(即,如果子类重写了父类方法,则调用子类的方法);

 

多态的应用:

 

1、多态参数

父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性与便利。

2、多态数组

例如:家里养了两只猫,两条狗,想要统一管理他们的对象,可以使用多态动物数组

//多态数组
public class TestAnimal {
	public static void main(String[] args) {
		Animal[] all = new Animal[4];//可以存储各种Animal子类的对象
		all[0] = new Cat();
		all[1] = new Cat();
		all[2] = new Dog();
		all[3] = new Dog();
		
		for (int i = 0; i < all.length; i++) {
			all[i].eat();//all[i]编译时是Animal类型,运行时看存储的是什么对象
		}
	}
}

父子类之间的类型转换

1、向上转型

  • 当父类引用指向一个子类对象时,便是向上转型。这个过程是自动完成的

使用格式:

父类类型  变量名 = new 子类类型();
如:Animal a = new Cat();

注意:此时通过父类的变量就只能调用父类中有的方法,不能调用子类特有的方法了

 

多态引用时关于成员变量与成员方法引用的原则:

 

1、成员变量:只看编译时类型

如果直接访问成员变量,那么只看编译时类型

public class TestField {
	public static void main(String[] args) {
		Father f = new Son();
		System.out.println(f.x);//只看编译时类型,结果为1
	}
}
class Father{
	int x = 1;
}
class Son extends Father{
	int x = 2;
}
//结果为调用父类的实例变量x,因此输出1.

 

2、非虚方法:只看编译时类型

如果方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的,这样的方法称为非虚方法。其他方法称为虚方法。

  • 静态方法、私有方法、final方法、实例构造器、通过super调用的父类方法都是非虚方法。

    • 静态方法与类型直接关联

    • 私有方法在外部不可访问

    • final不可被继承

public class TestField {
	public static void main(String[] args) {
		Father f = new Son();
		f.test();//只看编译时类型,结果为“father”,因为调用的是静态方法,静态方法是非虚方法,在编译期就确定了具体的调用版本,只看编译时类型,如果该方法不是静态的,那么结果为“son”
	}
}
class Father{
	public static void test(){
		System.out.println("father");
	}
}
class Son extends Father{
	public static void test(){
		System.out.println("son");
	}
}
//因为该方法为静态方法属于非虚方法,在编译器就确定了,因为引用是Father对象,因此调用的是father中的成员方法,因此输出"father"

小贴士:静态方法不能被重写调用静态方法最好使用“类名.”

 

3、虚方法:静态分派与动态绑定

(1)示例一:重写

abstract class Animal {  
    public abstract void eat();  
}  
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
}  

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
}

public class Test{
    public static void main(String[] args){
        Animal a = new Cat();
        a.eat();
    }
}
//结果为:“吃鱼”

如上代码在编译期间先进行静态分派:即确定是调用Animal类中的public void eat()方法,如果Animal类或它的父类中没有这个方法,将会报错。

而在运行期间动态的在进行动态分派:即确定执行的是Cat类中的public void eat()方法,因为子类重写了eat()方法,如果没有重写,那么还是执行Animal类在的eat()方法

(2)示例二:重载

class MyClass{
	public void method(Father f) {
		System.out.println("father");
	}
	public void method(Son s) {
		System.out.println("son");
	}
	public void method(Daughter f) {
		System.out.println("daughter");
	}
}
class Father{
	
}
class Son extends Father{
	
}
class Daughter extends Father{
	
}
public class TestOverload {
	public static void main(String[] args) {
		MyClass my = new MyClass();
		Father f = new Father();
		Father s = new Son();
		Father d = new Daughter();
        
		my.method(f);//father
		my.method(s);//father
		my.method(d);//father
	}
}
/**结果为:
    father
    father
    father
*/

如上代码在编译期间先进行静态分派:即确定是调用MyClass类中的method(Father f)方法,如果Animal类或它的父类中没有这个方法,将会报错。

而在运行期间动态的在进行动态分派:即确定执行的是MyClass类中的method(Father f)方法,因为my对象的运行时类型还是MyClass类型。

有些同学会疑问,不是应该分别执行method(Father f)、method(Son s)、method(Daughter d)吗?

因为此时f,s,d编译时类型都是Father类型,因此method(Father f)是最合适的。

(3)示例三:重载

class MyClass{
	public void method(Father f) {
		System.out.println("father");
	}
	public void method(Son s) {
		System.out.println("son");
	}
}
class Father{
	
}
class Son extends Father{
	
}
class Daughter extends Father{
	
}
public class TestOverload {
	public static void main(String[] args) {
		MyClass my = new MyClass();
		Father f = new Father();
		Son s = new Son();
		Daughter d = new Daughter();
		my.method(f);//father
		my.method(s);//son
		my.method(d);//father
	}
}

如上代码在编译期间先进行静态分派:即确定是分别调用MyClass类中的method(Father f),method(Son s),method(Father f)方法。

而在运行期间动态的在进行动态分派:即确定执行的是MyClass类中的method(Father f),method(Son s),method(Father f)方法,因为my对象的运行时类型还是MyClass类型。

有些同学会疑问,这次为什么分别执行method(Father f)、method(Son s)?

因为此时f,s,d编译时类型分别是Father、Son、Daughter,而Daughter只能与Father参数类型匹配

(4)示例四:重载与重写

class MyClass{
	public void method(Father f) {
		System.out.println("father");
	}
	public void method(Son s) {
		System.out.println("son");
	}
}
class MySub extends MyClass{
	public void method(Daughter d) {
		System.out.println("daughter");
	}
}
class Father{
	
}
class Son extends Father{
	
}
class Daughter extends Father{
	
}
public class TestOverload {
	public static void main(String[] args) {
		MyClass my = new MySub();
		Father f = new Father();
		Son s = new Son();
		Daughter d = new Daughter();
		my.method(f);//father
		my.method(s);//son
		my.method(d);//father
	}
}

如上代码在编译期间先进行静态分派:即确定是分别调用MyClass类中的method(Father f),method(Son s),method(Father f)方法。

而在运行期间动态的在进行动态分派:即确定执行的是MyClass类中的method(Father f),method(Son s),method(Father f)方法。

有些同学会疑问,my对象不是MySub类型吗,而MySub类型中有method(Daughter d)方法,那么my.method(d)语句应该执行MySub类型中的method(Daughter d)方法?

  • my变量在编译时类型是MyClass类型,那么在MyClass类中,只有method(Father f),method(Son s)方法,

  • f,s,d变量编译时类型分别是Father、Son、Daughter,而Daughter只能与Father参数类型匹配

  • 而在MySub类中并没有重写method(Father f)方法,所以仍然执行MyClass类中的method(Father f)方法

 

(5)结论

方法的分派有两个需要考虑的因素:

  • 方法的参数:在编译期间确定,根据编译器时类型,找最匹配的

  • 方法的所有者:

    • 如果没有重写,就按照编译时类型处理

    • 如果有重写,就按照运行时类型处理

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值