Java继承与多态

Posted 从零开始的智障生活

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java继承与多态相关的知识,希望对你有一定的参考价值。

目录

一、继承

1.1 Java继承一父对多子,一子对一父,且纵深到顶

1.2 重写(overwrite)与重构(over)

 1.3 构造函数的继承

1.4 this and super

1.4.1 this的意义

1.4.2 this的特殊用法——this与构造函数

1.4.3 super的意义

1.4.4 super的特殊用法——super与构造函数

1.5 内部类的继承

1.6 向上转型与向下转型

1.6.1 向上转型

1.6.2 向下转型

二、多态


一、继承

继承的意义:继承使得整个程序框架具有一定的弹性,在程序中复用一些已经定义完整的类可以减少开发周期,也可以提高软件的可维护性和扩展性。

接下来介绍Java继承的特性

1.1 Java继承一父对多子,一子对一父,且纵深到顶

一个子类最多只有一个父类(不同于C和C++),而且没有类可以成为自己的超类,即不能继承自己。但一个父类可以有多个子类。

子类会一直向上,继承父类中、爷爷类,无限祖先类中非private修饰符的成员,包括变量、方法、内部类,直到不再有继承的顶部。

实例:在不同的包testA,testB,testC下分别新建类文件A.java、B.java、C.java,然后用B继承A,C继承B。

package testA;

public class A 
	
	private String strA = "这是一个A的private变量";
	
	public String getStrA() return strA;
	public void setStrA(String strA) this.strA = strA;
package testB;

import testA.A;

public class B extends A
	
	private String strB = "这是一个A的private变量";
		
	public String getStrB() return strB;
	public void setStrB(String strB) this.strB = strB;
package testC;

import testB.B;

public class C extends B
	
	private String strC = "这是一个C的private变量";
	
	public String getStrC() return strC;
	public void setStrC(String strC) this.strC = strC;

	public static void main(String[] args) 		C c = new C();
		c.setStrA("父类A方法被子类C给调用了");
		c.setStrB("父类B方法被子类C给调用了");
		c.setStrC("子类C调用了自己的方法");
	

1.2 重写(overwrite)与重构(over)

继承并不只是扩展祖先类的功能,还可以【重写】(还可以称为【覆盖】)祖先类的方法。

【重写】:重写一般是在项目测试前,发现父类的方法不适用于子类,要重新创建这样一个方法,可能原本一个形参,变成三个形参,原本是private,现在是public。

就是在子类中将【父类的成员方法名】保留,重写内容可以包括:

  • 成员方法的参数类型与个数
  • 成员方法的方法体内容
  • 成员方法的权限修饰符
  • 成员方法的返回值
package testA;
public class A 
	public void func() System.out.println("这是A类public方法");
package testB;
import testA.A;
public class B extends A
	protected int func(int b) System.out.println("这是B类的public方法"+b);return b;
package testC;
import testB.B;
public class C extends B
	private String c func(String c) System.out.println("这是C类的public方法"+c);return c;

【重构】:在不改变代码外在行为(原本接受的参数不变,返回值不变)的前提下,对代码做出修改,以改进程序的内部结构。重构是一种经千锤百炼形成的有条不紊的程序整理方法,可以最大限度地减小整理过程中引入错误的概率。本质上说,重构就是在代码写好之后改进它的设计。

重构保留【父类的成员方法名、成员方法的参数类型与个数、成员方法的权限修饰符、成员方法的返回值】,只修改成员【方法的方法体内容】。

package testA;
public class A 
	public void func1() System.out.println("这是A类public方法");
package testB;
import testA.A;
public class B extends A
	public void func1() System.out.println("这是B类的public方法");
package testC;
import testB.B;
public class C extends B
	public void func1() System.out.println("这是C类的public方法");

 1.3 构造函数的继承

如果父类中,没有明确定义的无参构造函数,即只有默认构造函数则子类可以任意建立构造函数

新建子类对象过程:在调用子类的构造函数时,会自动调用父类的默认构造函数。

package testA;
public class A 
package testB;
import testA.A;
public class B extends A
	protected B(String b) System.out.println("这是B类的有参构造函数");
	protected B(char c) System.out.println("这是B类的另一个有参构造函数");

如果父类A中,有明确定义的无参构造函数,则子类可以任意建立构造函数

新建子类对象过程:在调用子类的构造函数时,会自动调用父类的明确定义的无参构造函数

package testA;
public class A 
    public A()System.out.println("这是A类的无参构造函数");
	public A(int a)System.out.println("这是A类的有参int构造函数");
package testB;
import testA.A;
public class B extends A
	protected B(String b) System.out.println("这是B类的有参构造函数");
	protected B(char c) System.out.println("这是B类的另一个有参构造函数");

如果父类A中,没有无参构造函数,只有有参构造函数则子类必须且只要在每个构造函数的第一行,用super(参数列表),明确调用父类有参构造函数

package testA;
public class A 
	public A(int a)System.out.println("这是A类的有参int构造函数");
package testB;
import testA.A;
public class B extends A
	protected B(int b) 
        super(9999);    // 必须在第一行
        System.out.println("这是B类的有参构造函数");
    
	protected B(char c) 
        super(999);    // 必须放在第一行
        System.out.println("这是B类的另一个有参构造函数");
    

1.4 this and super

1.4.1 this的意义

this主要是为了服务于封装,增加安全性。在Java中,新建一个字段,直接调用它,会降低安全性,比如一个密码字段pwd,可以进行封装。

对于对象:this代表一个类的一个对象,对于非静态成员这样的针对对象创建的成员,为了能够在定义的时候方便,于是使用this表示一个未定对象,而当创建了一个对象之后,this就表示这个对象。而其也可以调用这个对象可以调用的所有变量和方法。

对于方法:this可以调用的方法与一般对象相同,遵从就近原则。比如D继承C,C继承B,B继承A。而对于类D,可以调用的方法包括从ABC继承而来以及自身的方法,但是对于已经被子类重写或重构的方法,则不能被调用,比如类A的方法a1,a2被类B重写,但方法名依然是a1,a2,而那么D调用的就只能是被类B重写的方法。

对于编程:但是特别的是,由于main方法是静态方法,而this本身是不为静态所接受的,所以this无法在静态方法中使用,也就没法用在main方法中。

public class Testthis 
	private String pwd;
	public String getPwd() return pwd;
	// 用this代表未定对象
	public void setPwd(String pwd) this.pwd = pwd;	
	
	public  void testThis(String str) 
		this.setPwd(str);
	

1.4.2 this的特殊用法——this与构造函数

调用构造函数一般只有在创建对象时,才会进行,这就好像是构造函数与对象挂上了对等关系。但其实不是,构造函数的主要目的是创建对象,但其本身也是函数可以被调用。

但是不是可以在构造函数中,使用new 类名();来执行构造函数?

不是,这操作是无意义的,要想将构造函数绑定到特定对象上,可以用this实现。

this()表示无参构造函数,this(参数列表)表示有参构造函数。

但是这种方式只能出现在明确定义的构造函数的第一行。

public class Testthis 
	public Testthis() 
		System.out.println("这是无参构造函数");
	
	public Testthis(int num) 
		this();
		System.out.println("这是先用this()调用无参构造函数的int有参构造函数");
	
	public Testthis(String str) 
		this(999);
		System.out.println("这是先用先用this(int)的String有参构造函数");
	
	public static void main(String[] args) 
		new Testthis("aaa");
	

运行结果:

这是无参构造函数
这是先用this()调用无参构造函数的int有参构造函数
这是先用先用this(int)的String有参构造函数

1.4.3 super的意义

不同于this是代替当前类的不定对象,而super是代表当前类的直接父类的不定对象,可以调用超类的非私有变量和方法。

super从父类的继承上同样遵循就近原则。在继承过程中,我们常常会出现当前类重写或重构父类的方法的情况,这样当前类调用的就只有被重写或重构后的方法,然而被重写或重构前的父类的方法,会被当前类跳过。而super却可以调用父类可以调用的这些已经被当前类重写或重构的方法。

public class A 
	public int myCal(int m,int n) return m+n;
package testB;
import testA.A;
public class B extends A
	public int myCal(int m,int n) return m-n;
	public void testsuper() 
		super.myCal(m, n);	// 调用到了父类的被重写的方法m+n
		this.myCal(m, n);	// 当前类调用了重写父类的方法m-n
	
	
package testC;
import testA.A;
import testB.B;

public class C extends B
   	public int myCal(int m,int n) return m*n;
	public void testsuper() 
		super.myCal(m, n);	// 调用到了父类B的被重写的方法m-n,却不能调用B重写A的方法
		this.myCal(m, n);	// 当前类调用重写了父类B的方法m*n
	

提示:下面1.6节的向上转型与向下转型同样可以实现操作祖先类被重写或重构的方法的功能。

其次,super也是非静态意义的对象存在,故super同this一样不能出现在静态方法中,也就不可以出现在main方法中。

最后,super也与this同样对于构造函数具有特殊意义,也同样只能出现在构造函数的第一行

  • super():代表父类的无参构造函数。前提是父类没有明确定义的构造函数,或者有明确定义的无参构造函数。
  • super(参数列表):代表父类的有参构造函数。前提是父类中有明确定义的有参构造函数。

1.4.4 super的特殊用法——super与构造函数

package testA;
public class A 
	public A() 
		System.out.println("这是A类的无参构造函数");
	
	// 在A内只有一个有参构造函数
	public A(String a)
        System.out.println("这是A类的有参String构造函数:"+a);
    	
package testB;
import testA.A;
public class B extends A
    public B()
        super("aaa"); // 手动调用了父类A的有参构造函数A(String)。
        System.out.println("这是类B的无参构造函数");
    
	

 正如1.3节讲的,如果父类中只有有参构造函数,则子类中所有的构造函数的第一行,必须用super(参数列表);手动调用父类构造函数。

1.5 内部类的继承

特点1:内部类比较类似于方法,可以被继承,继承的规则也是遵循public、protected、private。

实例:新建三个包testAtestBtestC,分别新建三个类ABC,在三个类中分别新建一个public内部类AinBinCin。

而内部类分为非静态内部类和静态内部类(用static修饰的内部类)。

  • 在非静态方法内,可以直接新建继承而来的内部类(包括静态和非静态)。
  • 在静态方法内:
    • 对于非静态内部类,必须要新建当前类的局部变量对象,然后创建内部类对象;
    • 而对于静态内部类,则可以直接创建内部类对象。
package testA;
public class A 
	protected class Ain
		public Ain() 
			System.out.println("这是类A的内部类");
		
	
	public A() 
		System.out.println("这是A类的无参构造函数");
	
package testB;
import testA.A;
public class B extends A
	public B() 
		System.out.println("这是B类的无参构造函数");
	
	static public class Bin
		public Bin() 
			System.out.println("这是类B的内部类");
		
	
package testC;
import testA.A;
import testB.B;
public class C extends B
	public C() 
		System.out.println("这是C类的无参构造函数");
	
	class Cin
		public Cin() 
			System.out.println("这是类C的内部类");
		
	
	public void testInnerClass() 
		// 在非静态方法中,可以直接新建内部类对象
		Cin cin2 = new Cin();
		Bin bin = new Bin(); 
		Ain ain = new Ain();
	
	public static void main(String[] args) 
		// 在静态方法中,新建内部类对象,内部类对象必须要由外部类对象创建
		// 因为对于静态方法而言,非静态内部类的构造方法是,非静态成员不能在静态方法内调用。
		// 而新建对象c则是静态方法的局部变量,
		C c = new C();
		Cin cin = c.new Cin(); 
		Ain ain = c.new Ain();
		
		// 静态内部类可以直接在静态方法内部创建
		Bin bin = new Bin();
	

1.6 向上转型与向下转型

Java的子类对象转化成父类对象称为向上转型,就好像说正方形是四边形。
Java的父类对象转化成子类对象称为向下转型,就好像说有着正方形条件的四边形是正方形。

1.6.1 向上转型

相对于向下转型,向上转型比较好理解,向上转型可以使用强制类型转换,将子类转换成父类。就好像说正方形是四边形。

实例:在ABC三个类中都新建方法myCal(int,int),用于计算两个数值的值。而C继承B,B继承A。然后再每个类中分别新建方法myCala/b/c(int,int)

package testA;
public class A 
	// 计算数字a与b
	public int myCal(int m,int n) 
		return m+n;
	
	public int myCala(int m,int n) 
		return m+n+1;
	
package testB;
import testA.A;

public class B extends A
	public int myCal(int m,int n) 
		return m-n;
	
	public int myCalb(int m,int n) 
		return m-n+1;
	
package testC;
import testA.A;
import testB.B;
public class C extends B
    public int myCal(int m,int n) 
		return m*n;
	
	public int myCalc(int m,int n) 
		return m*n+1;
	
	public static void main(String[] args) 
		C c = new C();
		B b = new B();
		B c2b = (B)c;    // 向上转型,子类C对象转父类B对象
        // 向上转型之后,只能调用父类B可以调用的方法。
		System.out.println("调用父类B的myCal:"+c2b.myCal(1, 2));
		System.out.println("调用父类B的myCalb:"+c2b.myCalb(1, 2));
		System.out.println("调用父类B继承来的myCala:"+c2b.myCala(1, 2));
	

运行结果:

调用父类B的myCal:2
调用父类B的myCalb:0
调用父类B继承来的myCala:4

1.6.2 向下转型

向下转型,不同于向上转型,我们能说向上转型可以理解成:正方形是四边形,但不能说向下转型可以理解成四边形是正方形。要说四边形是正方形,必须要给四边形加上条件。这时候就需要明确告知编译器这个条件。而这种明确告知的方式就叫作显示类型转换

同等理解意义上的,有正方形条件的四边形,不能说成是菱形(这里正方形与菱形同级)。

首先要理解:祖先类对象是子类对象的一个实例的意义。依靠向上转型定义出来的祖先类对象,就是这样的实例,这就说明了这个父类对象四边形,具有了成为正方形的条件。

显示类型转换过程:

  1. 给定一个由子类B对象b向上转型而来的父类C对象bToC,这个btoc就是有了B类条件的C类对象
  2. C类对象btoC向下转型成B类对象btoCtoB,虽然如此,一般会需要用instanceof这个计算符号去判断是否btoc是B类的一个实例(btoc当然是C类的一个实例,不过这里是要进行向下转型,故要判断其是否具有向下转型的资格)。
  3. 还是直接用强制类型转换即可,它来了,它来了,它又回来了。它又变成了类C的对象。
package testA;
public class A 
	// 计算数字a与b
	public int myCal(int m,int n) 
		return m+n;
	
	public int myCala(int m,int n) 
		return m+n+1;
	
package testB;
import testA.A;

public class B extends A
	public int myCal(int m,int n) 
		return m-n;
	
	public int myCalb(int m,int n) 
		return m-n+1;
	
package testC;
import testA.A;
import testB.B;
public class C extends B
    public int myCal(int m,int n) 
		return m*n;
	
	public int myCalc(int m,int n) 
		return m*n+1;
	
	public static void main(String[] args) 
		C c = new C();
		A a = new A();
		A c2a = (A)c;     // 由子类C对象向上转型成祖先类A的对象   
        if(c2a instanceof C)    // 判断a2c是不是C类的实例
            C c2a2c = (C)c2a;    // 向下转型
            System.out.println("调用子类C的myCal,1*2=:"+c2a2c.myCal(1, 2));
            System.out.println("调用子类C的myCalc,1*2+1=:"+c2a2c.myCalc(1, 2));
        
		System.out.println("调用祖先类A的myCal,1+2=:"+c2a.myCal(1, 2));
		System.out.println("调用祖先类A的myCala,1+2+1=:"+c2a.myCala(1, 2));
		
	

 运行结果:

调用子类C的myCal,1*2=:2
调用子类C的myCalc,1*2+1=:3
调用祖先类A的myCal,1+2=:2
调用祖先类A的myCala,1+2+1=:4

二、多态

如果说继承是为了在子类中扩展父类功能属性,甚至是升级,而多态则是为了减少子类因利用父类属性处理问题所带来的负担

比如说四边形有一个属性point字段point[],保存四个顶点的位置。子类正方形和平行四边形都可以利用这个顶点字段绘制这个图形。

而我们知道四边形就可以处理这个问题,虽然子类正方形和平行四边形都可以独立完成,但分别定义像这种逻辑上明显可以直接由四边形实现的方法,很容易出现代码冗余,如果再增加长方形,菱形,那就更多了。

这些对象都是四边形的子类,如果遇到了绘图问题,那么在父类四边形中定义一个有参构造函数A(float[] p),那么只要对子类进行向上转型,就可以由父类四边形来实现了。

package testUptoAndDownto;
public class A 
	public A(float[] points)         // 有参构造函数用于接收子类向上转型的参数
		System.out.println("这是四边形");
		this.points = points;
	
	
	private float[] points = new float[4];
	private int nextIndex = 0;
	public void draw(A a)             // 四边形绘制方法
		while(nextIndex<points.length) 
			System.out.println(points[nextIndex]);
			nextIndex++;
		
	
package testUptoAndDownto;
public class B extends A
	public B(float[] points)     // 有参构造函数
		super(points);
		System.out.println("这是正方形");
	
	public static void main(String[] args) 
		float[] points=1,2,3,4;
		A a = (A)new B(points);	// 向上转型
		a.draw(a);
	

运行结果:

这是四边形
这是正方形
1.0
2.0
3.0
4.0

package testUptoAndDownto;
public class C extends A
	public C(float[] points) 
		super(points);
		System.out.println("这是平行四边方形");
	
	public static void main(String[] args) 
		float[] points=1,2,1,2;
		A a = (A)new C(points);	// 向上转型
		a.draw(a);
	

运行结果:

这是四边形
这是平行四边方形
1.0
2.0
1.0
2.0

子类正方形和平行四边形就不用都定义draw()方法了。但是二者还是为此分别新建了一个构造函数用来传递给父类points字段,这也就难免了。

以上是关于Java继承与多态的主要内容,如果未能解决你的问题,请参考以下文章

Java语言基础28-29--接口与多态

Java基础09—接口继承与多态

Java基础09—接口继承与多态

Java编程 实现类的继承与多态 写一个动物类。成员变量为动物的种类,成员方法是动物叫声。

java 动态绑定 多态

Java高阶部分知识点汇总-继承与多态