以下为个人学习笔记和习题整理
课程:面向对象程序设计 ——Java 语言
- 浙江大学 - 翁恺 @ 中国大学 MOOC
https://www.icourse163.org/course/ZJU-1001542001

# 课堂笔记

面向对象程序设计语言有三大特性:封装、继承和多态性。

# 继承 / 派生 extends

用来做基础派生其它类的那个类称为父类、超类或者基类,派生出来的新类称为子类。
Java 用关键字 extends 表示这种继承 / 派生关系。

class ThisClass extends SuperClass { 
    //…
}

# 子类父类关系

继承表达了一种 is-a 关系,就是说,子类的对象可以被看作是父类的对象。
Java 的继承只允许单继承,即一个类只能有一个父类。

哪些东西被继承了,子类从父类那里得到了什么?
答案是所有的东西,所有的父类的成员,包括变量和方法,都成为了子类的成员,除了构造方法。
构造方法是父类所独有的,因为它们的名字就是类的名字,所以父类的构造方法在子类中不存在。除此之外,子类继承得到了父类所有的成员。

但是得到不等于可以随便使用。
每个成员有不同的访问属性,子类继承得到了父类所有的成员,但是不同的访问属性使得子类在使用这些成员时有所不同:
有些父类的成员直接成为子类的对外的界面,有些则被深深地隐藏起来,即使子类自己也不能直接访问。

下表列出了不同访问属性的父类成员在子类中的访问属性:

父类成员访问属性在父类中的含义在子类中的含义
public对所有人开放对所有人开放
protected只有包内其它类、自己和子类可以访问只有包内其它类、自己和子类可以访问
缺省只有包内其它类可以访问如果子类与父类在同一个包内:只有包内其它类可以访问。否则:相当于 private ,不能访问
private只有自己可以访问不能访问

public 的成员直接成为子类的 public 的成员, protected 的成员也直接成为子类的 protected 的成员。
Java 的 protected 的意思是包内和子类可访问,所以它比缺省的访问属性要宽一些。

对于父类的缺省的未定义访问属性的成员来说,他们是在父类所在的包内可见。
如果子类不属于父类的包,那么在子类里面,这些缺省属性的成员和 private 的成员是一样的:不可见。

父类的 private 的成员在子类里仍然是存在的,只是子类中不能直接访问。

我们不可以在子类中重新定义继承得到的成员的访问属性
如果我们试图重新定义一个在父类中已经存在的成员变量,那么我们是在定义一个与父类的成员变量完全无关的变量,在子类中我们可以访问这个定义在子类中的变量,在父类的方法中访问父类的那个。
尽管它们同名但是互不影响。

在构造一个子类的对象时,父类的构造方法也是会被调用的,而且父类的构造方法在子类的构造方法之前被调用。
在程序运行过程中,子类对象的一部分空间存放的是父类对象。
因为子类从父类得到继承,在子类对象初始化过程中可能会使用到父类的成员。所以父类的空间正是要先被初始化的,然后子类的空间才得到初始化。在这个过程中,如果父类的构造方法需要参数,如何传递参数就很重要了。

# 多态变量

子类型类似于类的层次,类型也构成了类型层次。
子类所定义的类型是其超类的类型的子类型。

当把一个对象赋值给一个变量时,对象的类型必须与变量的类型相匹配,如:

Car myCar = new Car();

是一个有效的赋值,因为 Car 类型的对象被赋值给声明为保存 Car 类型对象的变量。但是由于引入了继承,这里的类型规则就得叙述得更完整些:
一个变量可以保存其所声明的类型或该类型的任何子类型。

对象变量可以保存其声明的类型的对象,或该类型的任何子类型的对象。

Java 中保存对象类型的变量是多态变量。
“多态” 这个术语(字面意思是许多形态)是指,一个变量可以保存不同类型(即其声明的类型或任何子类型)的对象。

  • 类定义了类型
  • 子类定义了子类型
  • 子类的对象可以被当作父类的对象来使用
    • 赋值给父类的变量
    • 传递给需要父类对象的函数
    • 放进存放父类对象的容器里

Java 的对象变量是多态的,它们能保存不止一种类型的对象。 它们可以保存的是声明类型的对象,或声明类型的子类的对象。 当把子类的对象赋给父类的变量的时候,就发生了向上造型。

# 造型 cast

子类的对象可以赋值给父类的变量。

注意 java 中不存在对象对对象的赋值!

父类的对象不能赋值给子类的变量!

Vechicle V;
Car C= new Car(),
v=c; // 可以 
c=v; // 编译错误!

可以用造型:

c = (Car) v,
// 只有当 v 这个变量实际管理的是 Car 才行

用括号围起类型放在值的前面。
对象本身并没有发生任何变化,所以不是 “类型转换”
运行时有机制来检查这样的转化是否合理 C1assCastException

# 向上造型

  • 拿一个子类的对象,当作父类的对象来用
  • 向上造型是默认的,不需要运算符
  • 向上造型总是安全的

# 多态

所有的类都是继承自 Object

Object 类的函数,如

  1. toString()
  2. equals()

如果子类的方法覆盖 @Override 了父类的方法,我们也说父类的那个方法在子类有了新的版本或者新的实现。

覆盖的新版本具有与老版本相同的方法签名:相同的方法名称和参数表。

子类和父类中存在名称和参数表完全相同的函数,这一对函数构成覆盖关系。

通过父类的变量,调用存在覆盖关系的函数时, 会调用变量当时所管理的对象所属的类的函数。

对于外界来说,子类并没有增加新的方法,仍然是在父类中定义过的那个方法。
不同的是,这是一个新版本,所以通过子类的对象调用这个方法,执行的是子类自己的方法。

覆盖关系并不说明父类中的方法已经不存在了,而是当通过一个子类的对象调用这个方法时,子类中的方法取代了父类的方法,父类的这个方法被 “覆盖” 起来而看不见了。
而当通过父类的对象调用这个方法时,实际上执行的仍然是父类中的这个方法。
注意这里说的是对象而不是变量,因为一个类型为父类的变量有可能实际指向的是一个子类的对象。

当调用一个方法时,究竟应该调用哪个方法,这件事情叫做绑定。
绑定表明了调用一个方法的时候,我们使用的是哪个方法。
绑定有两种:
一种是早绑定,又称静态绑定,根据变量的声明类型来决定,这种绑定在编译的时候就确定了;
另一种是晚绑定,即动态绑定。动态绑定在运行的时候,根据变量当时实际所指的对象的类型,动态决定调用的方法。即,根据变量的动态类型来决定。
Java 缺省使用动态绑定。
在成员函数中调用其他成员函数也是通过 this 这个对象变量来调用的。

# 课内项目

# 媒体数据库

public class Item {
	private String title;
	private int playingTime;
	private boolean gotIt = false;
	private String comment;
	
	// 构造器
	public Item(String title, int playingTime, boolean gotIt, String comment) {
		super();
		this.title = title;		
		this.playingTime = playingTime;
		this.gotIt = gotIt;
		this.comment = comment;
	}
	// 无参数构造器,子类 super () 调用
	public Item() {
		
	}
	
	public void setTitle(String title) {
		this.title = title;
	}
	public void print() {
		System.out.println(title);		
	}
}
public class CD extends Item {	
	private String artist;
	private int numofTracks;
	
	public CD(String title, String artist, int numofTracks, int playingTime, String comment) {
		super(title, playingTime, false, comment); // 调用 Item 的构造器
//		this.title = title;
		this.artist = artist;
		this.numofTracks = numofTracks;
//		this.playingTime = playingTime;
//		this.comment = comment;
	}
	public void print() {
		System.out.print("CD:");
		super.print();
		System.out.print(artist);	
	}
	public static void main(String[] args) {
		CD cd = new CD("a","b", 2, 2, "...");
		cd.print();
		
		System.out.println(cd);
		System.out.println(cd.toString());
		
		String s = "aa" + cd;//cd 会自动 toString ()
		System.out.println(s);
		
		CD cd1 = new CD("a","b", 2, 2, "...");
		System.out.print(cd.equals(cd1));
		// 如果没有自己的 equals 方法,将显示 false
	}
	
	@Override
	public String toString() {
		return "CD [artist=" + artist + ", numofTracks=" + numofTracks + ", toString()=" + super.toString() + "]";
	}
	@Override
	public boolean equals(Object obj) {
		CD cc = (CD) obj;// 向下造型
		return artist.equals(cc.artist);
	}
}
public class DVD extends Item {	
	private String title;
	private String director;
	private int playingTime;
	private boolean gotIt = false;
	private String comment;
	public DVD(String title, String director, int playingTime, String comment) {
//		super();
		super(title, playingTime, false, comment);
//		this.title = title;
		setTitle("b");
		this.director = director;
//		this.playingTime = playingTime;
//		this.comment = comment;
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
	}
	public void print() {
//		System.out.println("DVD:" + title + ":" + director);	
		System.out.print("DVD:");
		super.print();
		System.out.print(director);
	}
}
Database.java
import java.util.ArrayList;
public class Database {
//	private ArrayList<CD> listCD = new ArrayList<CD>();
//	private ArrayList<DVD> listDVD = new ArrayList<DVD>();
	private ArrayList<Item> listItem = new ArrayList<Item>();
	
//	public void add(CD cd) {
//		listCD.add(cd);
//	}
//	// 函数重载
//	public void add(DVD dvd) {
//		listDVD.add(dvd);
//	}
	public void add(Item item) {
		listItem.add(item);
	}
	public void list() {
//		for (CD cd:listCD) {
//			cd.print();
//		}
//		
//		for (DVD dvd:listDVD) {
//			dvd.print();
//		}
		
		for (Item item:listItem) {
			item.print();
			System.out.println();
		}
	}
		
	public static void main(String[] args) {
		Item item = new Item("a", 0, true, "...");
		CD cd = new CD("a", "a", 0, 0, "...");
		item = cd;
		CD cc = (CD) item; // 强制转换
		
		Database db = new Database();
		db.add(new CD("abc", "abc", 4, 60, "..."));
		db.add(new CD("efg", "efg", 4, 60, "..."));
		db.add(new DVD("xxx", "aaa", 60, "..."));
		db.list();
	}
}

# 课堂讨论

  1. 关于 super() :能在一个构造函数里调用两次 super() 吗? super() 必须在构造函数的第一行吗?

不可以调用两次,且必须在第一行。
子类是从父类继承而来,继承了父类的属性和方法,如果在子类中先不完成父类的成员的初始化,则子类无法使用,因为在 java 中不允许调用没初始化的成员。在构造器中是顺序执行的,也就是说 super () 必须在第一行进行父类的初始化。

  1. 父类的私有的成员函数在子类中能使用吗?

子类不可直接调用父类的 private 方法,可以通过父类的 public (及其他子类可访问)方法和 this 间接使用父类 private 方法。

  1. 假设现有 4 个类: PersonTeacherStudentPhDStudentTeacherStudent 都是 Person 的子类, PhDStudentStudent 的子类。以下的赋值语句哪些是合法的,为什么?:
// 变量管理对象,是否合法
Person p1 = new Student(); // 合法
Person p2 = new PhDStudent(); // 合法
PhDStudent phd1 = new Student(); // 不合法
Teacher t1 = new Person(); // 不合法
Student s1 = new PhDStudent(); // 合法
// 变量赋值运算,类型是否安全
s1 = p1; // 不合法
s1 = (Student)p1;
s1 = p2;
s1 = (PhDStudent)p2;
s1 = (Student)p2;
p1 = s1; // 子类型给父类型,安全
t1 = s1; // 子类型给子类型,不可转换
s1 = phd1; // 子类型给父类型,安全
Phd1 = s1;
phd1 = (PhD Student)s1;

4. Itemprint 函数有几种可能的实现方式:
1. 和视频中一样, Itemprint 输出 Item 的私有成员变量,然后 CD 等子类 override 这个 print 函数,实现完整的输出,并在其中通过 super. 调用 Itemprint
2. Itemprint 会输出完整的内容,但是其中子类的部分,会调用一个 protectedprint_task 函数来输出。子类则不去 override print ,而是 override 这个 print_task 函数,实现子类自己的成员变量的输出;
3. Itemprint 函数什么也不做。另外给一个 format 函数,输出格式化的私有成员变量的内容表达。子类利用这个 format 函数得到内容后输出。

你觉得这些方案哪个更合适?为什么?如果你有更好的方案,也请提出来。

阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Ruri Shimotsuki 微信支付

微信支付

Ruri Shimotsuki 支付宝

支付宝

Ruri Shimotsuki 贝宝

贝宝