一、类、超类和子类
1.1 多态
已经存在的类称为超类、基类或者父类;新类称为子类、派生类或者孩子类。通常,字类拥有比父类更多的功能。
在字类中可以增加域、增加方法或者覆盖超类的方法,但是绝对不能删除继承的任何域和方法
可以使用 super 方法实现对超类构造器的调用,其中 super 调用构造器的语句必须是字类构造器的第一条语句
public class Employee {
private String name;
private int age;
private double salary;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
...
}
public class Manager extends Employee {
private double bonus;
public Manager(String name, int age, double salary){
//语句1
super(name, age, salary);
bonus = 10.00;
}
}
public class Employee {
private String name;
private int age;
private double salary;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
...
}
public class Manager extends Employee {
private double bonus;
public Manager(String name, int age, double salary){
//语句1
super(name, age, salary);
bonus = 10.00;
}
}
如果字类没有显示的调用超类的构造器,将自动调用超累默认构造器(没有参数)。如果超类没有定义不带参数的构造器,但是定义了带参数的构造器,此时如果在子类构造器中没有显示的调用父类的带参数的构造器,即没有写 1 这条语句,则会产生错误
一个变量可以指示多种实际类型的现象称为多态。在运行时候能够自动选择调用哪个方法的现象称为动态绑定
这里有一个问题,如果我们使用以下的语句
//语句1
Manager[] managers = new Manager[10];
//语句2
Employee[] staff = managers;
//语句3
staff[0] = new Employee("123",12,12,34,23)
//语句1
Manager[] managers = new Manager[10];
//语句2
Employee[] staff = managers;
//语句3
staff[0] = new Employee("123",12,12,34,23)
还是上面的继承关系,这里我们将子类(Manager 类)的引用转换成父类(Employee 类)的引用,前两句没有问题,且不用强制转换,想一下,如果一个人是经理,那么他也一定是雇员,雇员才是前提。此时相当于雇员(Employee) 里面存的都是经理(Manager) 的信息
但是第3句,本来规定存储经理(Manager) 信息的数组里面又添加了一条雇员(Employee) 信息,这显然是不对的。好比,这个岗位只是招研究生的,结果你一个本科生去投简历,那人家肯定不要你,一个道理,范围都定死了。
此时会报以下错误
如果第三句改成
staff[0] = new Manager("123",12,12,34,23);
staff[0] = new Manager("123",12,12,34,23);
那肯定不会出错了,因为本来就是规定存的经理(Manager),现在 new 一个经理,那正好符合规则
1.2 动态绑定过程
1.首先 new 一个字类的对象,创建一个字类的引用指向他。这个时候,如果调用下面的 getSalary(xxx) 方法,则编译器便会列举子类中名为 getSalary 的方法和其超类中访问属性为 public 且名为 getSalary 的方法
Manager manager = new Manager();
manager.getSalary(90);
Manager manager = new Manager();
manager.getSalary(90);
2.在字类和父类查找名为 getSalary 方法的过程中,只要参数匹配,就调用这个方法。期间,编译器会自动作参数的类型转换,即如果传入的参数是 int 类型,如果定义的相同名称的方法的参数类型比他大(double,long) 等等,也会自动调用该方法,因为会进行自动转换。而如果没能找到与参数类型匹配的方法,或者有多个方法与之对应,则会报错
public class Employee {
/*public double getSalary(int a){
System.out.println("a");
return a;
}*/
}
public class Manager extends Employee {
//语句1
public double getSalary(double a){
System.out.println("sal double");
return 1;
}
//语句2
public double getSalary(int a) {
System.out.println("sal int a");
return 1;
}
}
public class ManagerTest {
public static void main(String[] args) {
Manager manager = new Manager();
manager.getSalary(90);
}
}
public class Employee {
/*public double getSalary(int a){
System.out.println("a");
return a;
}*/
}
public class Manager extends Employee {
//语句1
public double getSalary(double a){
System.out.println("sal double");
return 1;
}
//语句2
public double getSalary(int a) {
System.out.println("sal int a");
return 1;
}
}
public class ManagerTest {
public static void main(String[] args) {
Manager manager = new Manager();
manager.getSalary(90);
}
}
结果:sal int a
如果把语句2去掉,同时父类不变,那么
输出:sal double
3.如果是 private、static、final 方法,那么编译器肯定知道该调用哪个方法,该调用方式称为静态绑定。与之对应的是,对象自己去找应该调用哪个方法,这种调用方式是动态绑定
4.由于每次都要方法去字类父类中寻找对应的方法,很麻烦,开销很大。因此,虚拟机为每个类创建了一个方法表,列出了所有方法,在真正需要调用方法的时候,虚拟机直接查找方法表就行了。
比如 Employee 的方法表
方法名 | 所在的类 |
getName | Employee |
getSalary() | Employee |
getHireDay | Employee |
这里只列举了一部分,另外还有默认调用 Object 的类,没有写出
Manager 的方法类(部分),其中第一个类是从 Employee 中继承可得,另一个是自己增加的
方法名 | 所在的类 |
getSalary() | Employee |
getSalary(int a) | Manager |
getSalary(double a) | Manager |
举例
比如 manager.getSalary(90) 这个方法。虚拟机直接在 Employee 和 Manager 类中搜索定义 getSalary 方法的类,由于 Manager 中就有两个相同签明的方法,于是就找参数为 int 的那个类就好了,此时虚拟机知道该调用哪个方法。
PS
覆盖一个方法的时候,字类方法不能低于父类方法的可见性。如果父类是 public 修饰的,由于字类不能比父类小,因此只能是 public。如果不写,则使 default,也会报错
如果父类是 protected,那么字类只能是 protected 或者 public,不写也会报错(不写则是 package-private)
class Employee {
protected void aa(){}
}
class Manager {
@Override
void aa() {} //报错
protected void aa(){} //正确
public void aa(){} //正确
}
class Employee {
protected void aa(){}
}
class Manager {
@Override
void aa() {} //报错
protected void aa(){} //正确
public void aa(){} //正确
}
1.3 fina 类及其方法
不允许进行扩展(继承)的类称为 final 类,当一个类被声明为 final 类型,那么其中的所有方法自动转为 final 方法,但是域不会转为 final
public final class A {
void printA(){}
}
public final class A {
void printA(){}
}
当使用以下语句时
被 final 修饰的方法不能被覆盖
public class A {
final void printA(){}
}
public class A {
final void printA(){}
}
1.4 强制类型转换
从上到下进行类型转换,即从大的范围转为小的范围
在程序设计中,尽量少的使用类型转换和 instanceof 运算符
1.5 抽象类
抽象类既可以声明一个没有实现的方法,也可以创建一个实现了的方法。字类继承了父类的抽象类之后,会对父类声明的方法加以实现
public abstract class Person {
private String name;
public Person(String name) {
this.name = name;
}
public abstract String getDescription();
public String getName() {
return name;
}
}
public class Employee extends Person{
public Employee(String name) {
super(name);
}
@Override
public String getDescription() {
return null;
}
}
public abstract class Person {
private String name;
public Person(String name) {
this.name = name;
}
public abstract String getDescription();
public String getName() {
return name;
}
}
public class Employee extends Person{
public Employee(String name) {
super(name);
}
@Override
public String getDescription() {
return null;
}
}
一个抽象类,即使不含抽象方法,也可以将类声明为抽象类
抽象类不能被实例化
如果将一个类声明为 abstract,就不能创建这个类的对象。如果定义了一个抽象类的对象,那么他是能通过一个非抽象字类来引用。如上面那个例子,最终可以通过以下方法来创建抽象类的实例,然后调用抽象类的方法
Person person = new Employee("aaa");
person.getName();
Person person = new Employee("aaa");
person.getName();
1.6 各个修饰符的范围
- private:仅对本类可见
- public:对所有类可见
- protected:对本包和所有字类可见
- default:对本包可见
二、装箱和拆箱
Integer a = 100; //装箱
int b = a; //拆箱
Integer a = 100; //装箱
int b = a; //拆箱
装箱调用 Integer.valueOf 方法,拆箱调用 intValue 方法
三、参考
《Java核心技术 卷1》