Java之继承

8.1 继承的基础知识

Java支持单继承,但不支持多继承。

尽管子类包含超类的所有成员,但是子类不能访问超类中被声明为私有的所有成员。

8.2 使用super关键字

    class Box
    {
        doubel width;
        doubel height;
        doubel depth;

        Box(doubel w,doubel h,doubel d)
        {
            width = w;
            height = h;
            depth = d;
        }
    }

    class BoxWeight extents Box
    {
        doubel weight;

        BoxWeight(doubel w,doubel h,doubel d,doubel m)
        {
            width = w;  //当超类中width是私有成员时,就不能这样赋值,只能使用super()进行赋值。
            height = h;
            depth = d;
            weight = m;
        }
    }
但是,有时候希望创建只有自己才知道实现细节的超类(也就是说将超类的数据成员保存为私有)。对于这种情况,子类就不能直接访问或初始化这些变量。
因为封装是OOP的主要特性,所以Java为这一个问题提供了一个解决的方案是很正常的。
无论何时,当子类需要引用它的直接超类时,都可以使用关键字super.

super有两种一般用法:第一种用于调用超类的构造函数;第二种用于访问超类中被子类的某个成员隐藏的成员。

(1)使用super调用超类的构造函数。

格式:super(arg-list);
其中,arg-list是超类中构造函数中需要的全部参数,并且super()总是子类的构造函数中执行的第一条语句。
例如:
    class BoxWeight
    {
        BoxWeight(doubel w,doubel h,doubel d,doubel m)
        {
            super(w,h,d);
            weight = m;
        }
    }
其中,BoxWeight()使用参数w、h和d调用super(),这会调用Box类的构造函数,使用这些值初始化width、height和depth变量。
BoxWeight自身不在初始化这些值,而是只需要初始化自身特有的值:weight.这样一来,Box类就可以将这些变量声明为私有的。

总结:当子类调用super()时,会调用直接超类的构造函数。因此,super()总是引用调用类的直接超类。即使在多层次继承中也是如此。
      此外,super()总是子类构造函数中执行的第一句。

(2)使用super访问超类中被子类的某个成员隐藏的成员。

super的这种一种用法和this有些类似,只不过super总是引用超类。
格式:super.member
其中,member既可以是方法,也可以是实例变量。

最常用的形式情况是,子类的成员名称隐藏了超类中的同名成员。
例如:
    class A {
        int i;
    }

    class B extents A {
        int i;

        B(int a,int b)
        {
            super.i = a;
            i = b;
        }

        void show()
        {
            System.out.println("i in superclass:"+super.i);
            System.out.println("i in subclass:"+i);
        }
    }

    class SueSuper {
        public static void main(String[] args)
        {
            B subOb = new B(1,2);
            subOb.show();
        }
    }
输出:
i in superclass: 1
i in subclass: 2

8.4 构造函数的调用时机

答案是:在类层次中,从超类到子类按照继承的顺序调用构造函数。

8.5 方法重写

在类层次中,如果子类的一个方法和超类的一个方法具有相同的名称和类型签名,那么称子类中的这个方法重写了超类中相应的那个方法。
当子类中调用被重写的方法时,总是调用有子类定义的版本,由超类定义的版本会被隐藏。
例如:
    class A {
        int i,j;

        A(int a,int b)
        {
            i = a;
            j = b;
        }

        void show()
        {
            System.out.println("i and j:"+i+" "+j);
        }
    }

    class B extents A {
        int k;

        B(int a,int b,int c)
        {
            super(a,b);
            k = c;
        }

        void show()
        {
            System.out.println("k:"+k);
        }
    }

    class Override {
        public static void main(String[] args)
        {
            B subOb = new B(1,2,3);

            subOb.show();
        }
    }
输出:
k:3
当在类型B的对象上调用show()方法时,使用的是B中定义的版本。也就是说,类B中的show()版本覆盖了在类A中声明的版本。

如果希望访问到超类中被重写的方法,可以通过super完成该操作。例如将上面的类B改成如下形式:
    class B extents A 
    {
        int k;

        B(int a,int b,int c)
        {
            super(a,b);
            k = c;
        }

        void show()
        {
            super.show(); //this calls A's show()
            System.out.println("k:"+k);
        }
    }
输出为:
i and j: 1,2
k:3

只有当两个方法的签名(方法名称和参数列表的顺序和类型)相同时,才发生重写。如果不相同,那么这两个方法只是简单的重载关系。

8.6 动态方法调度

方法重写形成了动态方法调度(dynamic method dispatch)的基础,动态方法调度是Java中最强大的功能之一。动态方法调度是一种机制,
通过这种机制可以在运行时,而不是在编译时解析对重写方法的调用。动态方法调度很重要,因为这是Java实现运行时多态的机理所在。

首先在此声明一个重要的原则:超类引用变量可以指向子类对象。Java利用这一事实,在运行时解析对重写方法的调用。
下面是实现原理:当通过超类引用调用重写的方法时,Java根据在调用时所引用对象的类型来判断调用哪个版本的方法。
因此,这个决定是在运行时做出的。如果引用不同的对象,就会调用不同版本的重写方法。换句话说,是当前正在引用的对象
的类型(而不是引用变量的类型)决定了要执行哪个版本的重写方法。
例如:
    class A {
        void callme()
        {
            System.out.println("Inside A's callme method")
        }
    }

    class B extents A {
        void callme()
        {
            System.out.println("Inside B's callme method");
        }
    }

    class C extents A {
        void callme()
        {
            System.out.println("Inside C's callme method");
        }
    }

    class DisPatch {
        public static void main(String[] args)
        {
            A a = new A();
            B b = new B();
            C c = new C();
            A r;

            r=a;
            r.callme():

            r=b;
            r.callme();

            r=c;
            r.callme();
        }
    }
输出:
Inside A's callme method
Inside B's callme method
Inside C's callme method

8.7 使用抽象类

下面的程序创建了一个超类Figure,该类存储二维对象的尺寸。该类还定义了一个area()方法,该方法计算对象的面积。
这个程序从Figure类派生了两个子类。第一个子类是Rectangle(矩形),第二个子类是Triangle(三角形),每个子类都重写了area()方法,
从而可以相应的返回矩形和三角形的面积。
    class Figure
    {
        double dim1;
        double dim2;

        Figure(double a, double b)
        {
            dim1 = a;
            dim2 = b;
        }

        doubel area()
        {
            System.out.println("Area for Figure is undefined.")
            return 0;
        }
    }

    class Rectangle extents Figure
    {
        Rectangle(double a, double b)
        {
            super(a,b);
        }

        double area()
        {
            System.out.println("Inside Area for Rectangle.");
            return dim1*dim2;
        }
    }

    class Triangle extents Figure
    {
        Triangle(double a,double b)
        {
            super(a,b);
        }

        double area()
        {
            System.out.println("Inside Area for Triangle.");
            return dim1*dim2/2;
        }
    }

    class FindAreas
    {
        public static void main(String[] args)
        {
            Figure f = new Figure(10,10);
            Rectangle r = new Rectangle(9,5);
            Triangle t = new Triangle(10,8);
            Figure figure;

            figure = f;
            System.out.println("Area is"+figure.area());

            figure = r;
            System.out.println("Area is"+figure.area());

            figure = t;
            System.out.println("Area is"+figure.area());
        }
    }
输出:
Area for Figure is undefined.
Area is 0
Inside Area for Rectangle.
Area is 45
Inside Area for Triangle.
Area is 40

有时候会希望创建这样一种超类——只定义被所有子类共享的一般形式,而让每个子类填充细节。这种类决定了子类必须实现的方法的本质。
例如,分析Triangle类,如果没有定义area()方法,它将没有意义。对于这种情况,你会希望有某些方式能够确保子类确实、真正的重写了所有必需的方法。
Java对这个问题提供的解决方法是抽象方法(abstract method).

可以通过abstract类型修饰符,要求特定的方法必须被子类重写。这些方法有时被称做子类责任(subclass responsibility).
因为在超类中没有提供实现。因此,子类必须重写他们————不能简单的使用在超类中定义的版本。

为了声明抽象方法,需要使用下面的一般形式:
abstract type name(parameter-list);

任何包含一个或多个抽象方法的类都必须被声明为抽象。为了声明抽象类,只需要简单的在类声明的开头、在class关键字前面使用关键字abstract。
对于抽象类不存在对象。也就是说,不能使用new运算符直接实例化抽象类。这种类的对象是无用的,因为抽象类的定义是不完整的。
此外,不能声明抽象的构造方法,也不能声明抽象的静态方法。抽象类的所有子类,要么实现超类中的所有抽象方法,要么自己也声明为抽象类。
抽象类中可以包含非抽象方法。

将上面的例子,写成抽象的形式:
    abstract class Figure
    {
        double dim1;
        double dim2;

        Figure(double a,double b)
        {
            dim1 = a;
            dim2 = b;
        }

        abstract double area();
    }

    class Rectangle extents Figure
    {
        Rectangle(double a,double b)
        {
            super(a,b);
        }

        double area()
        {
            System.out.println("Inside Area for Rectangle.");
            return dim1*dim2;
        }
    }

    class Triangle extents Figure
    {
        Triangle(double a,double b)
        {
            super(a,b);
        }

        double area()
        {
            System.out.println("Inside Area For Triangle.");
            return dim1*dim2/2;
        }
    }

    class AbstractAreas
    {
        public static void main(String[] args)
        {
            Figure f;
            Rectangle r = new Rectangle(10,10);
            Triangle t = new Triangle(10,8);

            f = r;
            System.out.println("Area is "+f.area());

            f = t;
            System.out.println("Area is "+f.area());
        }
    }
尽管不能创建Figure类型的对象,但是可以创建Figure类型的引用变量。因为,Java运行时多态是通过使用超类引用实现的。
因此,必须能够创建指向抽象类的引用,从而可以用于指向子类对象。

8.8 在继承中使用final关键字

关键字final有三个用途。
首先,可以用于创建自己命名常量的等价物。
final关键字的另外两个用途是用于继承。

(1)使用final关键字阻止重写

虽然方法重写是Java中最强大的特性之一,但是有时候希望阻止这种情况的发生。为了禁止重写方法,可以在方法中声明的开头使用final作为修饰符。
使用final声明的方法不能被重写。

经方法声明为final,有时可以提高性能:编译器可以自由的内联对这类方法的调用,因为编译器知道这些方法不能被子类重写。当调用小的fanal方法时,
Java编译器通常可以复制子例程的字节码,直接和调用方法的编译代码内联到一起,从而可以消除方法调用所需要的开销。

内联是final方法才有的一个选项。通常Java在运行时动态分析对方法的调用,这称为后期绑定。
但是,因为final方法不能被重写,所以对final方法的调用可以在编译时解析,这称为早期绑定。

(2)使用final关键字阻止继承

有时候希望阻止类的继承。为此,可以在类声明的前面使用final关键字。将类声明为final,就隐式的将类的所有方法声明为final.
因此,将类同时声明为abstract和final是非法的,因为抽象类是不完整的,它依赖子类来提供完整的实现。

8.9 Object类

有一个特殊的类,即Object,该类由Java定义的。所有其他类都是Object的子类。也就是说,Object是所有其他类的超类。这意味着Object
类型的引用变量可以引用其他类型的对象。此外,因为数组也是作为类实现的,所以Object类型的变量也可以引用任何数组。

Object类定义了如下方法,这意味着所有对象都可以使用这些方法:

Object clone():     创建一个和将要复制对象完全相同的新对象。
boolean equals(Object object):      判定一个对象是否和另一个对象相等。
void finalize():        在回收不在使用的对象之前调用。
Class<?> getClass():        在运行时获取对象所使用的类
int hashCode():         返回与调用对象相关联的散列值。
void notify():      恢复执行在调用对象上等待的某个线程。
void notifyAll():       恢复执行在调用对象上等待的所有线程。
String toString():      返回一个描述对象的字符串。

void wait():
void wait(long milliseconds):
void wait(long milliseconds, int nanoseconds):      等待另一个线程的执行。

方法getClass()、notify()、notifyAll()以及wait()被声明为final.你可以重写其他方法。


出自:《Java 8编程参考官方教程(第9版)》

你可能感兴趣的:(java,继承,Class)