首頁  >  文章  >  Java  >  Java:類別與繼承

Java:類別與繼承

高洛峰
高洛峰原創
2017-02-08 11:05:191505瀏覽

對於物件導向的程式設計語言來說,類別毫無疑問是其最重要的基礎。抽象、封裝、繼承、多態這四大特性都離不開類,只有存在類,才能體現物件導向程式設計的特點,今天我們就來了解一些類別與繼承的相關知識。首先,我們來講述一下與類別的初始化相關的東西,然後再從幾個方面闡述繼承這一大特性。以下是本文的目錄大綱:

  一.你了解類嗎?

  二.你了解繼承嗎?

  三.常見的面試筆試題

一.你了解類嗎?

  在Java中,類文件是以.java為後綴的代碼文件,在每個類文件中最多只允許出現一個public類,當有public類的時候,類文件的名稱必須和public類的名稱相同,若不存在public,則類別檔案的名稱可以為任意的名稱(當然以數字開頭的名稱是不允許的)。

  在類別內部,對於成員變量,如果在定義的時候沒有進行顯示的賦值初始化,則Java會保證類別的每個成員變數都得到恰當的初始化:

  1)對於  char、short、byte、intint 、long、float、double等基本資料型別的變數會預設初始化為0(boolean變數預設會被初始化為false);

  2)對於引用型別的變量,會預設為null。

  如果沒有顯示地定義構造器,則編譯器會自動創建一個無參構造器,但是要記住一點,如果顯示地定義了構造器,編譯器就不會自動添加構造器。請注意,所有的構造器都預設為static的。

  下面我們著重講解一下初始化順序:

  當程式執行時,需要產生某個類別的對象,Java執行引擎會先檢查是否載入了這個類,如果沒有加載,則先執行類別的載入再生成對象,如果已經加載,則直接產生物件。

  在類別的載入過程中,類別的static成員變數會被初始化,另外,如果類別中有static語句區塊,則會執行static語句區塊。 static成員變數和static語句區塊的執行順序同程式碼中的順序一致。記住,在Java中,類別是按需加載,只有當需要用到這個類別的時候,才會加載這個類,並且只會加載一次。看下面這個範例就明白了:

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
         
        Bread bread1 = new Bread();
        Bread bread2 = new Bread();
    }
}
 
 
class Bread {
    static{
        System.out.println("Bread is loaded");
    }
    public Bread() {
        System.out.println("bread");
    }
}

  運行這段程式碼就會發現"Bread is loaded"只會被列印一次。

  在生成物件的過程中,會先初始化物件的成員變量,然後再執行建構器。也就是說類別中的變數會在任何方法(包括構造器)呼叫之前初始化,即使變數散步於方法定義之間。

public class Test {
    public static void main(String[] args)  {
        new Meal();
    }
}
 
 
class Meal {
     
    public Meal() {
        System.out.println("meal");
    }
     
    Bread bread = new Bread();
}
 
class Bread {
     
    public Bread() {
        System.out.println("bread");
    }
}

  輸出結果為:

bread
meal

二.你了解繼承嗎?

  繼承是所有OOP語言不可缺少的部分,在java中使用extends關鍵字來表示繼承關係。當建立一個類別時,總是在繼承,如果沒有明確指出要繼承的類,就總是隱式地從根類Object進行繼承。例如下面這段程式碼:

class Person {
    public Person() {
         
    }
}
 
class Man extends Person {
    public Man() {
         
    }
}

  類別Man繼承於Person類,這樣一來的話,Person類別稱為父類別(基底類別),Man類別稱為子類別(導出類別)。如果兩個類別存在繼承關係,子類別會自動繼承父類別的方法和變量,在子類別中可以呼叫父類別的方法和變數。在java中,只允許單繼承,也就是說 一個類別最多只能顯示地繼承於一個父類別。但是一個類別卻可以被多個類別繼承,也就是說一個類別可以擁有多個子類別。

  1.子類別繼承父類別的成員變數

  當子類別繼承了某個類別之後,便可以使用父類別中的成員變量,但是並不是完全繼承父類別的所有成員變數。具體的原則如下:

  1)能夠繼承父類別的public和protected成員變數;不能夠繼承父類別的private成員變數;

  2)對於父類別的套件存取權限成員變數,如果子類別和父類別在同一個套件下,則子類別能夠繼承;否則,子類別不能夠繼承;

  3)對於子類別可以繼承的父類別成員變量,如果在子類別中出現了同名稱的成員變量,則會發生隱藏現象,即子類別的成員變數會屏蔽掉父類別的同名成員變數。如果要在子類別中存取父類別中同名成員變量,則需要使用super關鍵字來進行引用。

  2.子類別繼承父類別的方法

  同樣地,子類別也並不是完全繼承父類別的所有方法。

  1)能夠繼承父類別的public和protected成員方法;不能夠繼承父類別的private成員方法;

  2)對於父類別的套件存取權成員方法,如果子類別套件和父類別在同一個套件下,則子類別能夠繼承;否則,子類別不能夠繼承;

  3)对于子类可以继承的父类成员方法,如果在子类中出现了同名称的成员方法,则称为覆盖,即子类的成员方法会覆盖掉父类的同名成员方法。如果要在子类中访问父类中同名成员方法,需要使用super关键字来进行引用。

  注意:隐藏和覆盖是不同的。隐藏是针对成员变量和静态方法的,而覆盖是针对普通方法的。(后面会讲到)

  3.构造器

  子类是不能够继承父类的构造器,但是要注意的是,如果父类的构造器都是带有参数的,则必须在子类的构造器中显示地通过super关键字调用父类的构造器并配以适当的参数列表。如果父类有无参构造器,则在子类的构造器中用super关键字调用父类构造器不是必须的,如果没有使用super关键字,系统会自动调用父类的无参构造器。看下面这个例子就清楚了:

class Shape {
     
    protected String name;
     
    public Shape(){
        name = "shape";
    }
     
    public Shape(String name) {
        this.name = name;
    }
}
 
class Circle extends Shape {
     
    private double radius;
     
    public Circle() {
        radius = 0;
    }
     
    public Circle(double radius) {
        this.radius = radius;
    }
     
    public Circle(double radius,String name) {
        this.radius = radius;
        this.name = name;
    }
}

  这样的代码是没有问题的,如果把父类的无参构造器去掉,则下面的代码必然会出错:

Java:類別與繼承

  改成下面这样就行了:

Java:類別與繼承

  4.super

  super主要有两种用法:

  1)super.成员变量/super.成员方法;

  2)super(parameter1,parameter2....)

  第一种用法主要用来在子类中调用父类的同名成员变量或者方法;第二种主要用在子类的构造器中显示地调用父类的构造器,要注意的是,如果是用在子类构造器中,则必须是子类构造器的第一个语句。

三.常见的面试笔试题

1.下面这段代码的输出结果是什么?

public class Test {
    public static void main(String[] args)  {
        new Circle();
    }
}
 
class Draw {
     
    public Draw(String type) {
        System.out.println(type+" draw constructor");
    }
}
 
class Shape {
    private Draw draw = new Draw("shape");
     
    public Shape(){
        System.out.println("shape constructor");
    }
}
 
class Circle extends Shape {
    private Draw draw = new Draw("circle");
    public Circle() {
        System.out.println("circle constructor");
    }
}

shape draw constructor
shape constructor
circle draw constructor
circle constructor

  这道题目主要考察的是类继承时构造器的调用顺序和初始化顺序。要记住一点:父类的构造器调用以及初始化过程一定在子类的前面。由于Circle类的父类是Shape类,所以Shape类先进行初始化,然后再执行Shape类的构造器。接着才是对子类Circle进行初始化,最后执行Circle的构造器。

2.下面这段代码的输出结果是什么?

public class Test {
    public static void main(String[] args)  {
        Shape shape = new Circle();
        System.out.println(shape.name);
        shape.printType();
        shape.printName();
    }
}
 
class Shape {
    public String name = "shape";
     
    public Shape(){
        System.out.println("shape constructor");
    }
     
    public void printType() {
        System.out.println("this is shape");
    }
     
    public static void printName() {
        System.out.println("shape");
    }
}
 
class Circle extends Shape {
    public String name = "circle";
     
    public Circle() {
        System.out.println("circle constructor");
    }
     
    public void printType() {
        System.out.println("this is circle");
    }
     
    public static void printName() {
        System.out.println("circle");
    }
}

shape constructor
circle constructor
shapethis is circle
shape

  这道题主要考察了隐藏和覆盖的区别(当然也和多态相关,在后续博文中会继续讲到)。

  覆盖只针对非静态方法(终态方法不能被继承,所以就存在覆盖一说了),而隐藏是针对成员变量和静态方法的。这2者之间的区别是:覆盖受RTTI(Runtime type  identification)约束的,而隐藏却不受该约束。也就是说只有覆盖方法才会进行动态绑定,而隐藏是不会发生动态绑定的。在Java中,除了static方法和final方法,其他所有的方法都是动态绑定。因此,就会出现上面的输出结果。

更多Java:類別與繼承相关文章请关注PHP中文网!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn