类的继承
1、什么是类的继承
类的继承是指子类通过继承父类,自动获得父类的可访问成员。用extends关键字实现。一般而言,子类可以自动获得父类的public和protected成员,当子类和父类在同一个包的情况下,还可以访问“包访问权限”成员,而父类的private成员是无法访问的
2、为什么要用类的继承
继承是Java的三大特性之一,是实现软件复用的重要手段,通过继承父类的成员,大大提高了开发效率。不过与此同时缺点也很明显,父类和子类的耦合度过高,有些违背Java的另一大特性:封装。但是它也有着特定的作用,为“多态”服务,具体的讨论会在后续章节展开。
3、类的继承要点分析
类的继承意味着子类需要用到一些父类的既定成员,而作为父类的特殊情况,子类又需要在某些地方做出调整,完成自身需求的实现。由此,它面临着两大问题,对父类成员的访问和对父类成员的重写,以下将会从这两个方面进行探究。
示例代码:
父类:
package test.superclasspackage;
public class SuperClass {
/** 父类实例变量 */
public String FieldA = "父类实例变量A";
/** 父类静态变量 */
public static String FieldB = "父类静态变量B";
/**
* 父类普通方法
*/
public Object commonMethod(){
System.out.println("父类普通方法");
return "父类普通方法";
}
/**
* 父类静态方法
*/
public static void staticMethod(){
System.out.println("父类静态方法");
}
}
子类:
package test.subclasspackage;
import test.superclasspackage.SuperClass;
public class SubClass extends SuperClass{
/** 子类重写父类实例变量 */
public String FieldA = "子类实例变量A";
/**
* 访问父类的成员变量
*/
public void readSuperClassFields(){
//访问子类的实例变量
System.out.println("访问子类实例变量:"+FieldA);
//访问父类的实例变量
System.out.println("访问父类实例变量:"+super.FieldA);
//访问父类静态变量方法1
System.out.println("访问父类静态变量方法1:"+super.FieldB);
//访问父类静态变量方法2
System.out.println("访问父类静态变量方法2:"+FieldB);
//访问父类静态变量方法3
System.out.println("访问父类静态变量方法3:"+SuperClass.FieldB);
}
/**
* 访问父类方法
*/
public void readSuperClassMethods(){
super.commonMethod();
super.staticMethod();
}
/**
* 重写父类的实例方法
*/
@Override
public String commonMethod(){
System.out.println("子类普通方法");
return "子类普通方法";
}
/**
* "重写"父类的静态方法
*/
// @Override
public static void staticMethod(){
System.out.println("子类静态方法");
}
public static void main(String[] args) {
//访问父类的实例变量A,此处会报错,静态方法里不能用super
// System.out.println(super.SuperClassFieldA);
SubClass subClass = new SubClass();
System.out.println("访问父类成员变量--------------------");
subClass.readSuperClassFields();
System.out.println();
System.out.println("访问父类方法-----------------------");
subClass.readSuperClassMethods();
System.out.println();
System.out.println("运用多态访问子类静态方法-----------------------");
SuperClass superClass1 = new SubClass();
superClass1.staticMethod();
staticMethod();
System.out.println();
System.out.println("运用多态访问子类实例方法-----------------------");
SuperClass superClass2 = new SubClass();
superClass2.commonMethod();
}
}
运行结果:
问题1:super关键字的使用
super是Java提供的关键字,它是对父类对象的引用。通过super.变量名和super.方法名可以实现对父类成员变量和成员方法的访问。当父类的成员变量和成员方法没有被重写时,super关键字可以不加,不影响使用,可为了程序的可读性考虑,建议从父类继承过来的属性和方法都加上super限定。
注意:
1. super是针对对象的,所以super不能出现在静态方法中,this关键字也是
2. 既然super是针对对象的,所以一般用super来获得父类的实例成员,然而用super去获得父类的静态成员语法上也不会报错,不过还是推荐用静态的方式去访问静态成员
问题2:方法的重写
(1)方法重写基于一个基本原则:两同两小一大
两同:子类方法和父类方法的方法名字,形参列表相同
两小:子类方法的返回值类型及抛出异常比父类小(这个小的意思是返回值类型与父类返回值类型相等或是其子类)
一大:子类方法的访问权限比父类的大
解析:为什么会有这样一个原则?两同很好理解,那两小一大呢?我的理解,子类重写父类的方法是为了全部替换父类方法出现的地方,如果子类的访问权限小于父类,则必定有一个范围是子类涵盖不了的。两小的话从多态方面考虑,SuperClass c = new SubClass(),编译类型是SuperClass,我们程序可能会用到SuperClass中一个方法的返回值,而此方法被SubClass重写了。而且运行时类型实际上是SubClass,如果此时子类实际的返回值类型比父类大的话,可能在运行时出现编译期无法预知的问题,从而使程序崩溃,异常的话同理。
(2)静态方法的"重写"
仔细观察代码中分别对父类的实例方法和静态方法进行重写后的效果,很明显,父类的实例方法被重写成功了,它执行的是被重写后的方法。而静态方法分别用了多态和直接调用两种方式去尝试对重写后静态方法的调用,结果是多态方式调用失败,直接调用成功。为什么呢?
实际上父类的静态方法是伪“”重写“”,实际是覆盖,不存在多态。仔细观察代码,我在子类重写父类静态方法上面有个@Override被注释掉了,如果放开的话会报错,所以是不允许被重写的。为什么说是覆盖,因为在代码中直接调用,调用的是重写后的方法,说明父类的静态方法被覆盖了。至于为什么多态方式调用失败也很好理解,因为多态是动态绑定,根据对象实际运行时候的类型去决定调用哪个方法,而静态方法则是在编译期就已经确定下来的了,在编译期对象的类型是SuperClass,所以运行时候去调用的方法也应该是SuperClass中的静态方法。
对了最后记录一下,实例方法中是直接可以调用静态方法的,而静态方法不能调实例方法。以前一直以为实例调实例,静态调静态,汗!!!
为什么会这样,我想也是和类的初始化有关,静态方法在实例方法之前,静态方法初始化的时候,实例方法并没有初始化,如果调用被崩掉。所以实例方法和静态方法的调用是单向的。