技术源 WWW.JOCK168.COM
第八章 继承与抽象

发布于:2015-03-27 16:10亚博vip手机版网页:

 


    本章主要介绍Java中继承原因及实现以及继承的特点,使用抽象类的原因及实现和其特点,方法重写的特点,以及final和super关键词的使用。

    8.1 继承

    8.1.1 为什么要继承?

    在说明为什么要继承之前,先来看看下面两个范例。
    首先定义了学生类。
    范例1:Student.java

    class Student{
        private String name;//姓名
        private int age;    //年龄
        public void speak(){
            System.out.println(name + "在说话");
        }
        public void setName(String name){
            this.name = name;
        }
        public void setAge(int age){
            this.age = age;
        }
        public String getName(){
            return name;
        }
        public int getAge(){
            return age;
        }
    }

    然后再定义了老师类。
    范例2:Teacher.java

    class Teacher{
        private String name;//姓名
        private int age;    //年龄
        public void speak(){
            System.out.println(name + "在说话");
        }
        public void setName(String name){
            this.name = name;
        }
        public void setAge(int age){
            this.age = age;
        }
        public String getName(){
            return name;
        }
        public int getAge(){
            return age;
        }
    }

    从这两个类中可以看出,它们除了类名不同外,程序代码基本上都是相同的。既然有这么多相同代码,那么能不能把这些相同的代码抽取出来,形成一个单独的类,然后让这两个类和抽取出来的这个类产生一定的关系,让它们同时拥有这个单独的类的全部功能呢?
    为了解决上面的问题,Java提供了继承这样一个概念来处理。在实际开发中,经常都会遇到上面的这类问题——几个类中含有相同代码。使用继承后,可以提高代码的复用性。

    8.1.2 如何继承?

    既然Java提供了继承,那么在程序中如何使用继承呢?在Java中,通过使用关键词extends来实现继承。语法格式如下:

    class 子类 extends 父类{
    }

    假设从Student类和Teacher类中抽取出相同的代码组成父Person类,示例代码如下。

    范例3:InheritanceDemo01.java

    class Person{
        private String name;//姓名
        private int age;    //年龄
        public void speak(){
            System.out.println(name + "在说话");
        }
        public void setName(String name){
            this.name = name;
        }
        public void setAge(int age){
            this.age = age;
        }
        public String getName(){
            return name;
        }
        public int getAge(){
            return age;
        }
    }
    class Student extends Person{
    }
    class Teacher extends Person{
    }
    public class InheritanceDemo01{
        public static void main(String[] args){
            Student s = new Student();
            s.setName("小明");
            s.setAge(15);
            s.speak();
            Teacher t = new Teacher();
            t.setName("王老师");
            t.setAge(30);
            t.speak();
        }
    }

    程序输出结果为:
    小明在说话
    王老师在说话

    分析:首先从Student类和Teacher类中抽取它们相同部分代码,生成了一个新的Person类(父类),然后Student类通过关键词extends来继承Person类,Teacher类也继承Person类。通过extends继承后,会发现Student类和Teacher类代码变得非常的简单了。虽然代码变得简单了,但是程序的功能却并没有发生变化。当Student类extends继承Person类后,Student类就拥有了Person类中的属性和方法,而且这些方法在Student类中直接可以使用。

    8.1.3 使用继承后实例化顺序

    通过上面的示例已经知道,使用extends关键词可以让两个关产生继承关系,有了继承关系后,子类就可以使用父类中的属性和方法。那么使用继承后,在对象实例化时,是如何进行的呢?看下面两个示例就明白了。
    范例4:InheritanceDemo02.java

    class Father{
        public Father(){
            System.out.println("父类被实例化了!");
        }
    }
    class Son extends Father{
        public Son(){
            System.out.println("子类被实例化了!");
        }
    }
    public class InheritanceDemo02{
        public static void main(String[] args){
            Son son = new Son();
        }
    }

    程序的运行结果为:
    父类被实例化了!
    子类被实例化了!

    分析:从程序的运行结果上可以看出,使用继承后是先实例化父类,再实例化子类。换句话说,是先有父类,再有子类。这显然和现实世界相符合的,这也再一次证明Java是从现实世界抽取出来的。
    为什么使用了extends关键词来继承后,实例化子类的时候会先实例化父类呢?
    实际上当子类继承父类后,在子类的构造方法中隐含调用了父类的构造方法,而这个隐含调用是关键词super来实现的。看下面示例:
    范例5:InheritanceDemo03.java

    class Father{
        public Father(){
            System.out.println("父类被实例化了!");
        }
    }
    class Son extends Father{
        public Son(){
            super();
            System.out.println("子类被实例化了!");
        }
    }
    public class InheritanceDemo03{
        public static void main(String[] args){
            Son son = new Son();
        }
    }

    范例5和范例4的代码差别不大,输出结果是一样的。在本例Son类的构造方法中,添加了super();代码,这段代码就是调用父类的无参构造方法。super关键词和this关键词的作用一样。它可以代表的是父类对象,也可以调用父类的属性、方法和构造方法。如果是调用父类中的构造方法,super()方法需要放到子类构造方法的第一行;同样,super关键词也不能出现在static修饰的方法中。

    8.1.4 子类继承访问限制

    前面提到,子类继承父类后,就可以直接访问父类中的属性和方法。那么是不是所有的属性和方法都可以直接使用呢?看下面的示例:
    范例5:InheritanceDemo04.java

    class Role{
        private String name;
        int damage;
        private void attack(){
            System.out.println(name + "发起攻击");
        }
    }
    class SwordMan extends Role{
    }
    public class InheritanceDemo04{
        public static void main(String[] args){
            SwordMan man = new SwordMan();
            man.name = "大唐剑士";
            man.attack();
        }
    }

    程序编译时报如下错误信息:
    InheritanceDemo04.java:13: 错误: name可以在Role中访问private
                man.name = "大唐剑士";
                   ^
    InheritanceDemo04.java:14: 错误: 找不到符号
                man.attack();
                   ^
    符号:   方法 attack()
    位置: 类型为SwordMan的变量 man
    2 个错误


    该错误信息指出,Role类中的name属性和attack()方法是private(私有的),在InheritanceDemo04类中不能访问,只能在Role类中才能访问。换名话说,子类继承父类后,只能访问或继承父类中不是private(私有)的属性或方法。另外,子类也不能继承父类中的构造方法,原因就在于构造方法的特殊性。但子类可以调用父类的构造方法。
    注:在继承中,子类不能继承父类中私有属性或方法,也不能继承父类的构造方法。

    8.1.5 继承的缺性

    在Java中,一个子类只能有一个直接的父类,换句话说,Java只支持单继承。
    范例6:InheritanceDemo05.java

    class One{
    }
    class Two{
    }
    class Three extends One,Two{
    }
    public class InheritanceDemo05{
        public static void main(String[] args){
            Three three = new Three();
        }
    }

    程序编译时会输出如下错误信息:
    InheritanceDemo05.java:5: 错误: 需要'{'
        class Three extends One,Two{
                           ^
    1 个错误


    分析:为什么会出现这样的错误呢?其实Java是为了程序的安全性来考滤的。假如类One中有一个方法void show(){},而类Two中也有一个void show(){}方法,那么类Three同时继承了类One和类Two后,实例化Three类后,调用show()方法,这时Java虚拟机就不知道是应该执行类One中的show()方法还是执行类Two中的show()方法,而这种不确定性,会导致程序会出现错误。为了让程序不会出现这种不确定的错误发生,在Java中只支持单继承。
    为了弥补单继的缺性,在Java中可以使用多重继承,看下面示例:
    范例6:InheritanceDemo06.java

    class One{
        void oneMethod(){
        }
    }
    class Two extends One{
        void twoMethod(){
        }
    }
    class Three extends Two{
    }
    public class InheritanceDemo06{
        public static void main(String[] args){
            Three three = new Three();
        }
    }

    上面的示例中,Three类既拥有类One中的oneMethod()方法,也拥有类Two中的twoMethod()方法。

    8.1.6 方法重写

    假设在父类中有一个方法public void show(){},而子类中也有一个方法public void show(){},那么,实例化子类后调用show()方法是输出父类的呢还是输出子类的?看下面示例:
    范例6:InheritanceDemo07.java

    class Shape{
        public void getArea(){
            System.out.println("输出面积");
        }
    }
    class Circle extends Shape{
        public void getArea(){
            System.out.println("输出圆的面积");
        }
    }
    public class InheritanceDemo07{
        public static void main(String[] args){
            Circle circle = new Circle();
            circle.getArea();
        }
    }

    程序的输出结果为:
    输出圆的面积

    为什么会发生这种情况呢?前面提到子类继承父类后,是先实例化父类,那么为什么不是输出:“输出面积”呢?那是因为子类重写了父类中的getArea()方法。当子类重写了父类中的方法后,输出的结果就是子类的结果(重写后的结果)。

    8.1.6.1 为什么方法重写?

    很多时候,父类中定义的方法输出结果并不是程序最终执行想要的结果。比如我们没办法在父类中定义圆形的面积计算和方形的面积计算,而是在子类中去具体实现相应的面积计算方法,所以需要进行方法的重写。

    8.1.6.2 方法重写特点

    要想实现方法重写,必须满足如下要求:
    1. 子类和父类中方法名称必须相同;
    2. 子类和父类中方法的参数列表必须相同;
    3. 两个方法必须是在子父类中;
    4. 子类方法的返回值类型要么和父类相同,要么是父类返回类型的子类;
    5. 子类的访问修饰符必须大于或等于父类访问修饰符。比如父类方法是public修饰的那么子类方法必须是public修饰。

    8.2 抽象类

    8.2.1 为什么要抽象类?

    上一节介绍了方法重写,知道方法重写后输出的结果是重写后的输出结果。那么既然重写后输出的结果是子类的方法输出的结果,为什么还要在父类中定义被重写的方法呢?父类中定义的这个方法有什么意思呢?
    以前面求几何图形面积为例,根据不同的几何图形计算面积,要求计算面积的方法必须叫getArea()方法。
    范例7:AbstractDemo01.java

    class Shape{
        public float getArea(){
            return 0.0f;
        }
    }
    class Circle extends Shape{
        private float r;
        private float PI = 3.14f;
        public float getArea(){
            return r * r * PI;
        }
        public void setR(float r){
            this.r = r;
        }
        public float getR(){
            return r;
        }
    }
    public class AbstractDemo01{
        public static void main(String[] args){
            Circle c = new Circle();
            c.setR(2.0f);
            float area = c.getArea();
            System.out.println(area);
        }
    }

    从本例中可以看出,父类Shape中getArea()方法主要的作用是为了限制子类必须通过这个方法来计算面积,该方法本身在父类中的实现意义不大。也就是说,父类只需要有这个方法声明就行了,不必要实现这个方法。那么如何做到只声明方法而不去实现(方法后面的大括号)它呢?

    8.2.2 如何定义抽象类

    在Java中,使用抽象方法就可以实现上面的需求。定义抽象方法的语法格式如下:
    public abstract 返回值类型 方法名称();
    使用关键词abstract来修饰的方法就叫抽象方法。抽象方法是只能有方法的声明,不能有方法的实现。也就是说抽象方法后面不能有“{}”存在。当一个类中有抽象方法后,这个类必须是抽象类,它必须被abstract来修饰。定义抽象类的语法格式为:
    abstract class 类名{}
    因此,上面示例修改如下:
    范例8:AbstractDemo02.java

    abstract class Shape{
        public abstract float getArea();
    }
    class Circle extends Shape{
        private float r;
        private float PI = 3.14f;
        public float getArea(){
            return r * r * PI;
        }
        public void setR(float r){
            this.r = r;
        }
        public float getR(){
            return r;
        }
    }
    public class AbstractDemo02{
        public static void main(String[] args){
            Circle c = new Circle();
            c.setR(2.0f);
            float area = c.getArea();
            System.out.println(area);
        }
    }

    父类被定义为了抽象类,并且把getArea()方法定义为了抽象方法。经过这样定义后,程序更加合理一些。因为父类不需要也不会知道子类是如何实现面积计算的,只需要规定面积计算方法的声明即可。

    8.2.3 抽象类的特点


    前面已经介绍了抽象类和抽象方法,那么抽象类有那些特点呢?
    范例9:AbstractDemo03.java

    abstract class Animal{
        public abstract void eat();
        public void run(){
        }
    }
    class Cat extends Animal{
        public void sleep(){
        }
    }
    public class AbstractDemo03{
        public static void main(String[] args){
            Animal animal = new Animal();
        }
    }

    程序在编译时会出现如下错误信息:
    AbstractDemo03.java:6: 错误: Cat不是抽象的, 并且未覆盖Animal中的抽象方法eat()
        class Cat extends Animal{
        ^
    AbstractDemo03.java:12: 错误: Animal是抽象的; 无法实例化
            Animal animal = new Animal();
                            ^
    2 个错误


    从上面的错误信息中可以得出抽象类有如下特点:
    1. 必须是被abstract关键词修饰的类;
    2. 抽象类中可以有抽象方法,也可以有非抽象方法;
    3. 抽象类不能直接实例化,必须有子类;
    4. 如果子类继承抽象类后,没有重写父类中的抽象方法,那么子类也只能是抽象类。

    8.3 final关键词

    在Java中声明类、属性、方法时,可以使用final关键词来修饰。被final修饰的类不能有子类,也就是不能被继承;被final修饰的变量即为常量,必须赋初值,且赋值后其值不能改变;被final修饰的方法不能被重写。
    范例10:FinalDemo01.java

    public class FinalDemo01{
        public static void main(String[] args){
            final int num = 5;
            num *= 2;
        }
    }

    程序编译时会报如下错误:
    FinalDemo01.java:4: 错误: 无法为最终变量num分配值
            num *= 2;
            ^
    1 个错误

    这个错误信息说明被final修饰的变量其值不能再改变。
    范例11:FinalDemo02.java

    class FinalFather{
        public final void show(){
        }
    }
    public class FinalDemo02 extends FinalFather{
        public final void show(){
        }
    }

    程序编译时会报如下错误:
    FinalDemo02.java:6: 错误: FinalDemo02中的show()无法覆盖FinalFather中的show()
        public final void show(){
                          ^
      被覆盖的方法为final
    1 个错误

    这个错误说明被final修饰的方法不能被重写。
    范例12:FinalDemo03.java

    final class FinalClass{
    }
    public class FinalDemo03 extends FinalClass{
    }

    程序编译时会报如下错误:
    FinalDemo03.java:3: 错误: 无法从最终FinalClass进行继承
        public class FinalDemo03 extends FinalClass{
                                     ^
    1 个错误

    这个错误信息说明被final修饰的类不能被继承

 
  • 链接

  • 百度
  • 新浪
  •