You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

13 KiB

1. Inheritance继承 and Polymorphism多态

Suppose you will define classes to model circles, rectangles, and triangles. These classes have many common features. What is the best way to design these classes so to avoid redundancy? The answer is to use inheritance.

1.1. 如何降低描述荣誉

如何降低代码的冗余例如在C语言中通过什么方式降低代码的冗余。

在C语言中降低代码冗余增加代码复用性一般使用函数来实现但是函数是一个高纬度的抽象和我们日常生活联系并不紧密。那么我们考虑日常生活中的例子。

通常我们描述一猫,只用说明猫是:界:动物界;门:脊椎动亚门;纲:哺乳纲;目:食肉目;科:猫科;属:猫属;种:猫种。 Alt text

上述的图表示物种分类的一部分。这样描述一个物种的分类有很多好处:

  1. 清楚表明了祖先与后代的关系(继承树);
  2. 后代具备祖先的所有特性;
  3. 明确不同种之间的关系。

如果大家都清楚上述的规则当我们讨论猫种的时候就默认猫种具备祖先的所有特性而不必再说明猫种是脊椎动物胎生等这些它祖先所具备的特性。这种特性在Java种的类种也存在这就是继承。

1.2. Java中的继承

例如要在平面上描述两个类圆和矩形那么这两个类好像有一些基本的特性是一致的。例如填充颜色color是否填充 filled对象创建时间 dateCreated等。另外需要对上述属性设置或者读取的方法getColor()setColor()isFilled()setFilled()等。

如果每个类都需要描述所有的这些特性好像有点浪费如何做到降低代码冗余我们使用到继承如下图表述GeometrocObject是父类保护所有子类共有的特性数据和方法Circle和Rectangle继承于GeometrocObject。这样在编写Circle和Rectangle的时候就只需要对其特性进行描述就可以了这两个类的共性在其父类中以及体现出来了子类完全继承父类的所有特性数据和方法

image-20230311122706219

代码实现如下:

1.2.1. 父类

SimpleGeometricObject

public class SimpleGeometricObject {
	private String color = "white";
	private boolean filled;
	private java.util.Date dateCreated;

	/** Construct a default geometric object */
	public SimpleGeometricObject() {
		dateCreated = new java.util.Date();
	}

	/**
	 * Construct a geometric object with the specified color and filled value
	 */
	public SimpleGeometricObject(String color, boolean filled) {

		dateCreated = new java.util.Date();
		this.color = color;
		this.filled = filled;
	}

	/** Return color */
	public String getColor() {
		return color;
	}

	/** Set a new color */
	public void setColor(String color) {
		this.color = color;
	}

	/**
	 * Return filled. Since filled is boolean, its get method is named isFilled
	 */
	public boolean isFilled() {
		return filled;
	}

	/** Set a new filled */
	public void setFilled(boolean filled) {
		this.filled = filled;
	}

	/** Get dateCreated */
	public java.util.Date getDateCreated() {
		return dateCreated;
	}

	/** Return a string representation of this object */
	public String toString() {
		return "created on " + dateCreated + "\ncolor: " + color + " and filled: " + filled;
	}
}

可以看到父类对共有的变量和方法进行了定义,虽然父类可以实例化,但是好像实例化父类并没有什么实际的意义。

注意:这个类最好不能被实例化,因为没有意义。下一章将借用这个离职,并使其不能被实例化。

1.2.2. 子类Circle

CircleFromSimpleGeometricObject

public class CircleFromSimpleGeometricObject extends SimpleGeometricObject {

	private double radius;

	public CircleFromSimpleGeometricObject() {

	}

	public CircleFromSimpleGeometricObject(double radius) {
		this.radius = radius;
	}

	public CircleFromSimpleGeometricObject(double radius, String color, boolean filled) {
		this.radius = radius;
		setColor(color);
		setFilled(filled);
	}

	/** Return radius */
	public double getRadius() {
		return radius;
	}

	/** Set a new radius */
	public void setRadius(double radius) {
		this.radius = radius;
	}

	/** Return area */
	public double getArea() {
		return radius * radius * Math.PI;
	}

	/** Return diameter */
	public double getDiameter() {
		return 2 * radius;
	}

	/** Return perimeter */
	public double getPerimeter() {
		return 2 * radius * Math.PI;
	}

	/* Print the circle info */
	public void printCircle() {
		System.out.println("The circle is created " + getDateCreated() + " and the radius is " + radius);
	}

}
  1. 子类Circle使用extend SimpleGeometricObject表明继承于SimpleGeometricObject那么子类拥有父类的所有特性(数据和方法);
  2. 但是请注意,虽然子类拥有父类的所有特性,不意味子类就可以无限制的访问这些特性,例如colorfilleddateCreated 这些是父类的私有变量在子类中并不能访问而要通过对应的getter和setter函数访问这些函数是公开的
  3. CircleFromSimpleGeometricObject增加了自己的特性radius并设置成私有以及对应的getter和setter函数
  4. 子类中增加了自己的行为:getArea()getPerimeterprintCircle()
  5. 当然,子类型的构造函数也不一样了,这是另外一个话题,后面会讲解。

1.2.3. 子类

RectangleFromSimpleGeometricObject

public class RectangleFromSimpleGeometricObject extends SimpleGeometricObject {
	private double width;
	private double height;

	public RectangleFromSimpleGeometricObject() {
	}

	public RectangleFromSimpleGeometricObject(double width, double height) {
		this.width = width;
		this.height = height;
	}

	public RectangleFromSimpleGeometricObject(double width, double height, String color, boolean filled) {
		this.width = width;
		this.height = height;
		setColor(color);
		setFilled(filled);
	}

	/** Return width */
	public double getWidth() {
		return width;
	}

	/** Set a new width */
	public void setWidth(double width) {
		this.width = width;
	}

	/** Return height */
	public double getHeight() {
		return height;
	}

	/** Set a new height */
	public void setHeight(double height) {
		this.height = height;
	}

	/** Return area */
	public double getArea() {
		return width * height;
	}

	/** Return perimeter */
	public double getPerimeter() {
		return 2 * (width + height);
	}
}

RectangleFromSimpleGeometricObject也使用同样的方式继承于SimpleGeometricObject只是新增的变量和方法不太一样。

1.2.4. 测试主类

TestCircleRectangle

public class TestCircleRectangle {
	public static void main(String[] args) {
		CircleFromSimpleGeometricObject circle = new CircleFromSimpleGeometricObject(1);
		System.out.println("A circle " + circle.toString());
		System.out.println("The color is " + circle.getColor());
		System.out.println("The radius is " + circle.getRadius());
		System.out.println("The area is " + circle.getArea());
		System.out.println("The diameter is " + circle.getDiameter());

		RectangleFromSimpleGeometricObject rectangle = new RectangleFromSimpleGeometricObject(2, 4);
		System.out.println("\nA rectangle " + rectangle.toString());
		System.out.println("The area is " + rectangle.getArea());
		System.out.println("The perimeter is " + rectangle.getPerimeter());
	}
}

通过测试程序,我们发现子类的对象引用变量上可以调用toString()getColor() 等方法(这些方法是在父类中定义的),说明子类的确继承了父类的所有特性。

思考一个问题:getArea()函数和getPerimeter()函数既然在两个子类中存在,就说明是这两个子类的共性;那为什么不把这两个函数写在父类当中?对了其实是因为他们的算法不同,虽然函数签名是一样的。下一章将要学习如何把这个方法放到父类中去。

另外一个是toString()这个函数非常特别,后面会讲到。

2. 子类的构造函数

2.1. 构造函数也被继承了吗?

No. They are not inherited.They are invoked explicitly or implicitly. Explicitly using the super keyword.

A constructor is used to construct an instance of a class. Unlike properties and methods, a superclass's constructors are not inherited in the subclass. They can only be invoked from the subclasses' constructors, using the keyword super. If the keyword super is not explicitly used, the superclass's no-arg constructor is automatically invoked.

构造函数并不会被子类型所继承子类型通过显式或者隐式的方式调用父类的构造函数显式调用使用super关键字。如果没有显式调用那么子类型将隐式调用父类型不带参数的构造函数。

为了便于子类型调用父类的构造函数,建议在定义类型的时候,要么不写构造函数,否则建议写一个不带参数的构造函数。

A constructor may invoke an overloaded constructor or its superclasss constructor. If none of them is invoked explicitly, the compiler puts super() as the first statement in the . For example

image-20230311222741376

image-20230311222819514

父类如果只定义了带参数的构造函数那么Java不会分配不带参数的构造函数没有不带参数的构造函数这时如果子类没有显式调用父类带参数的构造函数将导致构造函数找不到的问题。

例如:

class Person {
	int age;

	public Person(int a) {
		age = a;
	}
}

class Man extends Person {
}

上诉例子中定义的 Man 类将出现编译错误,因为 Person 缺少不带参数的构造函数Person 又没有显式的调用Person带参数的构造函数。修改 Man 类如下就可以了:

class Man extends Person {
	public Man() {
		super(10);
	}
}

2.2. super关键字

The keyword super refers to the superclass of the class in which super appears. This keyword can be used in three ways:

  1. To call a superclass constructor
  2. To call a superclass method
  3. To access superclass fields

super关键字调用父类构造方法还可以理解为什么还需要super关键字去调用父类的函数和字段CircleFromSimpleGeometricObject 不是可以直接调用父类SimpleGeometricObject的方法吗?例如:getColor()等方法?

这是因为子类可以覆盖override父类的方法后面的覆盖父类方法的小节会讲解。

2.3. 构造函数链

当使用new关键字构建一个对象的时候如果这个对象所属的类有父类通常情况下都有那么是否和一步一步构建的我们看看下面的例子。

public class Faculty extends Employee {

	public static void main(String[] args) {
		new Faculty();
	}

	public Faculty() {
		super();
		System.out.println("(4) Faculty's no-arg constructor is invoked");
	}
}

class Employee extends PPP {
	public Employee() {
		this("(2) Invoke Employees overloaded constructor");
		System.out.println("(3) Employee's no-arg constructor is invoked");
	}

	public Employee(String s) {
		super();
		System.out.println(s);
	}
}

class PPP {
	public PPP() {
		System.out.println("(1) Person's no-arg constructor is invoked");
	}
}

main函数开起来比较奇怪为什么在Faculty内中又使用new 方法构造自己所在的类其实大家不用太关心main函数在那里。首先因为java虚拟机要访问到这个静态函数作为程序的入口其次对于 main 函数只要被构建的类对于main函数来说是可以访问就行了。上述所有类型都定义在一个文件间当中这样如同是一个包因此main函数可以访问该文件中的所有类进而也可以访问 Faculty 类,构建 Faculty 类的对象当然也是可以的。其实把main函数放在这个文件的任何类中都是可以的只不过我们习惯放在 Faculty 这个类中(这个文档的文件名也是和这个类一致的)。

我们接下来仔细分析一下发生了什么,运行的结果是:

(1) Person's no-arg constructor is invoked
(2) Invoke Employees overloaded constructor
(3) Employee's no-arg constructor is invoked
(4) Faculty's no-arg constructor is invoked

也就是说PPP的构造函数被最先执行完成注意不是最先被调用然后是 Employee 的构造函数被执行完成,最后是 Faculty 的构造函数被执行完成。

  sequenceDiagram
    activate Faculty()
    Faculty()->>Employee(): 显式调用super()
    deactivate Faculty()
	 activate Employee()
    Employee()->>PPP(): 隐式调用 PPP()
    deactivate Employee()
    activate PPP()
    Note over PPP():1  
    PPP()->>Employee(): PPP()调用返回
    activate Employee()
    deactivate PPP()      
    Note over Employee():2、3  
    deactivate Employee()  
    Employee()->>Faculty():Employee()调用返回
    activate Faculty()
     Note over Faculty():4
    deactivate Faculty()


  

3. Overriding 覆盖父类的方法

4. Overriding vs. Overloading

5. Object类

5.1. toString方法

6. Polymorphism多态

6.1. 动态函数绑定

7. 引用类型的类型转换

7.1. 子类型转换成父类型

7.2. 父类型转换成子类型

8. equals 方法

9. ArrayList类

10. protected 访问修饰

11. final 修饰