1. 程式人生 > >類與物件(二)

類與物件(二)

目錄

2.構造塊

覆寫:

多型性:

程式碼塊的定義與使用:

定義:使用“{ }”定義的一段程式碼。

根據程式碼塊定義的位置以及關鍵字,可以分為以下四種:

1.普通程式碼塊

2.構造塊

3.靜態程式碼塊

4.同步程式碼塊

1.普通程式碼塊

定義在方法中的程式碼塊。

2.構造塊

定義在類中的程式碼塊(不加修飾符)。

舉個栗子:

class A{
    public A(){
        System.out.println("這是構造方法");
    }
    {
        System.out.println("這是構造塊");
    }
}

public class Test{
    public static void main(String[] args){
        A a1 = new A();
        A a2 = new A();
    }
}

執行結果:

通過上述程式碼我們發現:在物件產生時,構造塊優先於構造方法執行,有幾個物件產生,就呼叫幾次構造塊。構造塊可以完成一些屬性的初始化操作(在呼叫構造方法前)。

3.靜態程式碼塊

使用static定義的程式碼塊。

根據靜態塊所在的類不同,可以分為以下兩種型別:

1.在非主類中

2.在主類中

3.1在非主類中的靜態程式碼塊

舉個栗子:

class A{
    public A(){
        System.out.println("這是構造方法");
    }
    {
        System.out.println("這是構造塊");
    }
    static{
        System.out.println("這是靜態塊");
    }
}

public class Test{
    public static void main(String[] args){
        System.out.println("-----start-----");
        A a1 = new A();
        A a2 = new A();
        System.out.println("-----end-----");
    }
}

執行結果:

通過上述程式碼我們發現:靜態程式碼塊在類載入時執行,優先於構造塊執行,無論有多少物件產生,只會呼叫一次。

靜態塊的主要作用是為static屬性進行初始化。

3.2在主類中的靜態程式碼塊

舉個栗子:

public class Test{
    {
        System.out.println("主類的構造塊");
    }
    static{
        System.out.println("主類的靜態塊");
    }
    public Test(){
        System.out.println("主類的構造方法");
    }
    public static void main(String[] args){
        System.out.println("-----start-----");
        A a1 = new A();
        A a2 = new A();
        System.out.println("-----end-----");
    }
}

執行結果:

通過上述程式碼我們可以發現:在主類中定義的程式碼塊,優先於主方法執行。

4.同步程式碼塊

與執行緒同步問題有關,在此不多加闡述。

程式碼塊的基本知識都已經明白了,接下來我們看一道習題,結合了上述所有知識點。可以自己想想答案是什麼。



class HelloA{
public HelloA(){
System.out.println("這是父類的構造方法");
}
{
System.out.println("這是父類的非靜態程式碼塊");
}
static{
System.out.println("這是父類的靜態程式碼塊");
}
}

class HelloB extends HelloA{
public HelloB(){
System.out.println("這是子類的構造方法");
}
{
System.out.println("這是子類的非靜態程式碼塊");
}
static{
System.out.println("這是子類的靜態程式碼塊");
}
}

public class Test{
public static void main(String[] args){
System.out.println("-----start-----");
new HelloB();
new HelloB();
System.out.println("-----end-----");
}
}

繼承的定義與使用:

1.繼承的實現

在Java中,繼承使用extends關鍵字來實現。定義的語法如下:

class 子類 extends 父類

注:子類又時也被稱為派生類,父類也被稱為超類或基類。

在發生了類的繼承關係後,子類可以直接繼承父類的操作,實現程式碼的重用。

子類最低也維持和父類相同的功能。

子類可以進行功能上的擴充。

2.繼承的限制

子類物件例項化前,首先呼叫父類構造方法產生父類物件後,再呼叫子類構造方法例項化子類物件。

舉個栗子:

class Person{
    public Person(){
        System.out.println("父類物件產生");
    }
}

class Student extends Person{
    public Student(){
        super();//此語句在無參時寫與不寫都一樣
        System.out.println("子類物件產生");
    }
}

public class Test{
    public static void main(String[] args){
       new Student();
    }
}

執行結果:

注:實際上在子類的構造方法中,相當於隱含了一條語句,super( );如果父類中沒有提供無參構造,就必須使用super( )明確指明你要呼叫的構造方法。

Java只允許單繼承,不允許多繼承。(Java的單繼承侷限)

要想在Java中實現類似的“多繼承”,要麼多層繼承,要麼使用內部類。

//多層繼承
class A(){}
class B extends A(){}
class C extends B(){}

多層繼承層數不建議太多,最好不要超過3層。

在繼承時,子類會繼承父類的所有結構。(包含私有域與其他屬性,方法)

顯式繼承:所有非私有操作(非private操作)屬於顯式繼承(可以直接呼叫)

隱式繼承:所有私有操作(private操作)屬於隱式繼承(不可以直接呼叫,需要通過其他形式呼叫,例如getter,setter)

覆寫:

定義:子類定義了與父類方法名稱,引數列表,返回值完全相同的方法。被覆寫的方法不能擁有比父類更為嚴格的訪問控制權限。

private < default < protected < public

舉個栗子:

class Person{
    public void print(){
        System.out.println("父類的print方法");
    }
}

class Student extends Person{
   public void print(){
       System.out.println("子類的print方法");
   }
}

public class Test{
    public static void main(String[] args){
     new Student().print();
    }
}

執行結果:

以後在進行覆寫操作的時候,要注意以下兩點:

a.看new在哪(當前使用的物件是通過哪個類new的)

b.呼叫的方法有沒有被子類覆寫,如果被覆寫,呼叫的一定是被覆寫後的方法。

覆寫和過載的區別:

區別 過載(overload) 覆寫(override)
概念 方法名稱相同,引數的型別及個數不同 方法名稱,返回值型別,引數的型別及個數完全相同
範圍 一個類 繼承關係
限制 沒有許可權要求 被覆寫的方法不能擁有比父類更為嚴格的訪問控制權限

super關鍵字:

1.super用於方法

a.用於構造方法

表示呼叫父類構造方法。

語法:super (引數列表)

I. 當子類呼叫父類無參構造時,super( )可寫可不寫,表示呼叫父類無參構造。

II. 當子類呼叫父類有參構造時,super(引數列表)必須要寫,要告訴編譯器當前呼叫的是哪個有參構造。

注意:

(1)子類構造方法中呼叫父類構造必須是第一行語句

(2)this與super不能同時呼叫

b.用於普通方法

語法:super.方法名(引數)

用於在子類中明確呼叫父類被覆寫的方法。

舉個栗子:

class Person{
    private String name;
    public Person(String name){
        this.name = name;
        System.out.println("父類的有參構造");
    }
    public void print(){
        System.out.println("父類的print方法");
    }
}

class Student extends Person{
    public Student(){
        super("li");//super用於構造方法
        System.out.println("子類的無參構造");
    }
   public void print(){
       super.print();//super用於普通方法
       System.out.println("子類的print方法");
   }
}

public class Test{
    public static void main(String[] args){
     new Student().print();
    }
}

執行結果:

2.super用於屬性

super.屬性名 

表示呼叫父類中被覆寫的屬性

final關鍵字:

1.final修飾類(String類以及8大基本資料型別的包裝類,Integer)

a.當一個類被final關鍵字修飾,表示該類不能擁有子類(該類不允許被繼承)。

b.一旦一個類被final修飾,該類的所有方法都會預設加上final。

2.final修飾方法

當一個方法被final修飾,明確表示該方法不能被覆寫。

3.final修飾變數

I.final修飾普通資料型別的成員變數

被final修飾的成員變數必須在宣告時初始化(構造塊或構造方法中初始化),並且初始化後值無法被修改。

II.final修飾引用資料型別的變數

其引用不可變,即不能再指向其他的物件。

多型性:

1.方法的多型性

a.方法的過載:同一個方法名稱可以根據引數的型別或個數不同,呼叫不同的方法體。

b.方法的覆寫:同一個父類的方法,可能根據例項化子類的不同也有不同的實現。

2.物件的多型性

a.物件的向上轉型:用於引數統一化

語法:父類 父類引用 = new 子類();

不管是否發生了向上轉型,其核心本質還在於:你使用的是哪一個子類(new在哪裡),而且呼叫的方法是否被子類覆寫了。

b.向下轉型:當父類引用需要呼叫子類擴充方法時,才需要向下轉型

語法:子類 子類引用 = (子類)父類引用;

注:要發生向下轉型,必須先發生向上轉型

舉個栗子:

class Person{
    public void print(){
        System.out.println("我是父類方法");
    }
}

class Student extends Person{
    public void print(){
        System.out.println("我是子類方法");
    }
    public void fun(){
        System.out.println("只有子類有");
    }
}

public class Test{
    public static void main(String[] args){
        Person per = new Student();//向上轉型
        per.print();
        System.out.println("-----------------------");
        //這個父類能夠呼叫的方法只能是本類定義好的方法
        //所以並沒有子類中的dun()方法,那麼只能向下轉型
        Student stu = (Student) per;//向下轉型
        stu.fun();
    }
}

執行結果:

內部類的定義與使用:

內部類:在類內部進行其他類結構巢狀操作。

1.內部類的優缺點:

內部類的優點:

a.內部類與外部類可以方便的訪問彼此的私有域(包括私有方法,私有屬性)

b.內部類是另外一種封裝(保護性),對外部的其他類隱藏

c.內部類可以實現Java單繼承的侷限

缺點:

結構複雜

2.內部類與外部類的關係:

a.對於非靜態內部類,內部類的建立需要依賴外部類物件,在沒有外部類例項之前無法建立非靜態內部類。

b.內部類是一個相對獨立的個體,與外部類沒有is--a關係。

c.內部類可以直接訪問外部類的元素(包含私有域),但是外部類不可以直接訪問內部類元素,需要通過內部類的引用間接訪問

3.建立內部類語法

a.在外部類外部建立非靜態內部類

外部類.內部類 內部類引用 = new 外部類().new 內部類();

Outter.Inner in = new Outter( ).new Inner( );

b.在外部類外部建立靜態內部類

外部類.內部類 內部類引用 = new 外部類.內部類( );

Outter.Inner in = new Outter.Inner( );

4.內部類的分類

I.成員內部類(類比成員方法)

a.成員內部類內部不能存在任何static變數或方法,可以訪問外部類的靜態域

b.成員內部類是依附外部類的,所有隻有先建立了外部類,才能建立內部類。

II.靜態內部類(類比靜態方法)

a.靜態內部類的建立不需要依賴外部類,可以直接建立

b.靜態內部類不可以使用任何外部類的非static域(包含屬性與方法),但是可以存在自己的成員變數

舉個栗子:

class Outter{
    private static String msg = "Hello World";
    //定義一個內部類
    static class Inner{
        public void print(){
            //只能使用外部類的靜態屬性
            System.out.println(msg);
        }
    }
    //在外部類定義一個方法,負責產生內部類物件並且呼叫print方法
    public void fun(){
        Inner in = new Inner();//內部類物件
        in.print();//內部類提供的print方法
    }
}

public class Test{
    public static void main(String[] args){
        Outter.Inner in = new Outter.Inner();
        in.print();
    }
}

執行結果:

III.方法內部類(區域性內部類)

  a.方法內部類不允許使用訪問許可權修飾符 public、private、protected

  b.方法內部類對外部完全隱藏,除了建立這個類的方法可使用以外,其他地方均不能訪問

  c.方法內部類如果要想使用方法形參,該形參必須使用final宣告(JDK8將形參變為隱式final宣告)

IV.匿名內部類(lamdba表示式前身)

匿名內部類就是一個沒有名字的方法內部類。因此特點與方法內部類完全一樣,除此之外還有兩個自己的特點:

    a.匿名內部類必須繼承一個抽象類或者實現一個介面

    b.匿名內部類沒有構造方法,因為它沒有類名

舉個栗子:

interface MyInterface{
    void test();
}

class Outter{
    private int num;
    public void display(int para){
        //匿名內部類,實現了MyInterface介面
        new MyInterface(){
        
            @Override
            public void test() {
                System.out.println("匿名內部類"+para);
            }
        }.test();
    }
}

public class Test{
    public static void main(String[] args){
        Outter outter = new Outter();
        outter.display(20);
    }
}

執行結果:

5.內部類的特點

a.破壞了程式的結構

b.方便進行私有屬性的訪問。(外部類也可以訪問內部類的私有結構)

c.如果發現類名稱出現了“.”,應當 立即想到內部類的概念