【java基础(二十六)】类、超类、子类(四)

抽象类

如果自上而下在类的继承层级结构中上移,位于上册的类更具有通用性,甚至可能更加抽象。从某种角度看,祖先类更加通用,人们只将它作为派生类的基类,而不作为想使用的特定的实例类。例如,考虑一下对Employee类层次的扩展。一名雇员是一个人,一名学生也是一个人。下面将类Persion和类Student添加到类的层次结构中。
在这里插入图片描述
为什么要花费精力进行这样高层次的抽象呢?每个人都有一些诸如姓名这样的属性。学生与雇员都有姓名属性,因此可以将getName方法放置在位于继承关系较高层次的通用超类中。
现在,再增加一个getDescription方法,它可以返回对一个人的简单描述。如:
对于Employee: an employee with a salary of $50,000,00
对于Student: a student majoring in computer science
在Employee类和Student类中实现这个方法很容易。但是在Person类中应该提供什么内容呢?除了姓名之外,Person类一无所知。当然,可以让Person.getDescription()返回一个空字符串。然而,还有一个更好的方法,就是使用abstract关键字,这样就完全不需要实现这个方法了。

public abstract String getDescription();
// 不需要方法的实现

为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。

public abstract class Persion {
	public abstract String getDescription();
}

除了抽象方法之外,抽象类还可以包含具体数据和具体方法。例如,Persion类还保存着姓名和一个返回姓名的具体方法。

public abstract class Person() {
	private String name;
	
	public Person(String name) {
		this.name = name;
	}
	
	public abstract String getDescription();
	
	public String getName() {
		return name;
	}
}

抽象方法充当着占位的角色,它们的具体实现在子类中。扩展抽象类可以有两种选择。一种是在抽象类中定义部分抽象类方法或不定义抽象类方法,这样就必须将子类也标记为抽象类;另一种是定义全部的抽象方法,这样一来,子类就不是抽象的了。
例如,通过扩展抽象Person类,并实现getDescription方法来定义Student类。由于在Student类中不再含有抽象方法,所以不必将这个类声明为抽象的。
类即使不含抽象方法,也可以将类声明为抽象类。
抽象类不能被实例化。也就是说,如果将一个类声明为abstract,就不能创建这个类的对象。如:

new Person("Vince Vu");

是错误的,但可以创建一个具体子类的对象。
需要注意,可以定义一个抽象类的对象变量,但是它之鞥呢引用非抽象子类的对象。如:

Person p = new Student("Vince Vu", "Economics");

这里的p是一个抽象类Peron的变量,Person引用了一个非抽象子类Student的实例。
下面定义一个扩展抽象类Person的具体子类Student:

public class Student extends Person {
	private String major;
	
	public Student(String name, String major) {
		super(name);
		this.major = major;
	}
	
	public String getDescription() {
		return "a student majoring in " + major;
	}
}

在Student类中定义了getDescription方法。因此,在Student类中的全部方法都是非抽象的,这个类不再是抽象类。
定义Person数组,将雇员和学生填充到Person引用数组:

Person[] people = new Person[2];
people[0] = new Employee(...);
people[1] = new Student(...);

然后,输出这些对象的姓名和信息描述:

for (Person p : people)
	System.out.println(p.getName + ", " + p.getDescription());

有些人可能对下面这个调用感到困惑:
p.getDescription();
这不是调用了一个没有定义的方法吗?请牢记,由于不能构造抽象类Person的对象,所以变量p永远不会引用Person对象,而是引用诸如Employee或Student这样的具体子类对象,而这些对象都定义了getDescription方法。

实例

Person.java

package cn.freedompc.abstractclasses;

public abstract class Person {

	public abstract String getDescription();
	
	private String name;
	
	public Person(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
	
}

Employee.java

package cn.freedompc.abstractclasses;

import java.time.LocalDate;

public class Employee extends Person {

	private double salary;
	private LocalDate hireDay;
	
	public Employee(String name, double salary, int year, int month, int day) {
		super(name);
		this.salary = salary;
		hireDay = LocalDate.of(year, month, day);
	}
	
	public double getSalary() {
		return salary;
	}
	
	public LocalDate getHireDay() {
		return hireDay;
	}	
	
	public String getDescription() {
		return String.format("an employee with a salary of $%.2f", salary);
	}
	
	public void raiseSalary(double byPercent) {
		double raise = salary * byPercent / 100;
		salary += raise;
	}
}

Student.java

package cn.freedompc.abstractclasses;

public class Student extends Person {

	private String major;
	
	public Student(String name, String major) {
		super(name);
		this.major = major;
	}
	
	public String getDescription() {
		return "a student majoring in " + major;
	}
	
}

PersonTest.java

package cn.freedompc.abstractclasses;

public class PersonTest {

	public static void main(String[] args) {
		Person[] people = new Person[2];
		
		people[0] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
		people[1] = new Student("Maria Morris", "computer science");
		
		for (Person p : people)
			System.out.println(p.getName() + ", " + p.getDescription());
	}
	
}

运行结果

在这里插入图片描述
是否可以省略Person超类中的抽象方法,而仅在Employee和Student子类中定义getDescription方法呢?如果这样的话,就不能通过变量p调用getDescription方法了。编译器只允许调用在类中声明的方法。
在Java程序设计语言中,抽象方法是一个重要的概念。在接口(interface)中会看到更多的抽象方法。

受保护访问

大家都知道,最好将类中的域标记为private,而方法标记为public。任何声明为private的内容对其他类都是不可见的。前面已经看到,这对于子类来说也完全适用,即子类也不能访问超类的私有域。
然而,在有些时候,人们希望超类中的某些方法允许被子类调用,或允许子类的方法访问超类的某个域。为此,需要将这些方法域声明为protected。例如,如果将超类Employee中的hireDay声明为protected,而不是私有的,Manager中的方法就可以直接地访问它。
不过,Manager类中的方法只能够访问Manager对象中的hireDay域,而不能访问其他Employee对象中的这个域。这种限制有助于避免滥用受保护机制,使得子类只能获得访问受保护域的权利。
在实际应用中,要谨慎使用protected属性。假设需要将设计的类提供给其他程序猿使用,而这个类中设置了一些受保护域,由于其他程序猿可以由这个类再派生出新类,并访问其中的受保护域。在这种情况下,如果需要这个类的实现进行修改,就必须通知所有使用这个类的程序猿。这违背了OOP提倡的数据封装原则。
受保护的方法更具有实际意义。如果需要限制某个方法的使用,就可以将它声明为protected。这表明子类得到信任,可以正确地使用这个方法,而其他类则不行。
下面归纳一下Java用于控制可见性的4个访问修饰符:

  1. 仅对本类可见 – private
  2. 对所有类可见 – public
  3. 对本包和所有子类可见 – protected
  4. 对本包可见 – 默认,不需要修饰符。

捐赠

若你感觉读到这篇文章对你有启发,能引起你的思考。请不要吝啬你的钱包,你的任何打赏或者捐赠都是对我莫大的鼓励。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章