天天看點

Java基礎回顧-011 基礎文法2 容器3 異常

1 基礎文法

Java不僅是一種語言,Java是一個完整的平台,有一個龐大的庫,其中包含了很多可重用的代碼和一個提供諸如安全性、跨作業系統的可移植性以及自動垃圾收集等服務的執行環境。

 面向對象:是一種程式設計技術。它将重點放在資料(即對象)和對象的接口上。

1.1 封裝

在面向對象那個設計方法中,封裝(Encapsulation)是一種将抽象函數式接口的實作細節包裝、隐藏起來的方法。

封裝就是隐藏實作細節,提供簡化接口。使用者隻需要關注怎麼用,而不需要關注内部是怎麼實作的。實作細節可以随時修改,而不影響使用者。函數是封裝,類也是封裝。通過封裝,才能在更高的層次上考慮和解決問題。

可以說,封裝是程式設計的第一原則,沒有封裝,代碼之間會存在着實作細節的依賴,則建構和維護複雜的程式是難以想象的。

  • 可以被認為是一種保護屏障,防止該類的代碼和資料被外部類定義的代碼随機通路。
  • 要通路該列的代碼和資料,必須通過嚴格的接口控制。
  • 封裝最主要的功能在于我們修改自己的實作代碼,而不用修改那些調用我們代碼的程式片段。
  • 适當的封裝可以讓程式更容易了解與維護,也加強了程式的安全性。

典型示例:

私有化成員變量,公有化通路方法。

public class Encaptest {
    private String name;
    private String idNum;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getIdNum() {
        return idNum;
    }

    public void setIdNum(String idNum) {
        this.idNum = idNum;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
           

對類進行測試:

public class RunEncap {

    public static void main(String[] args) {
        Encaptest encap = new Encaptest();
        encap.setName("wonjie");
        encap.setIdNum("008008008");
        encap.setAge(18);

        System.out.println("Name : " + encap.getName() + "\n" +"Age : " + encap.getAge());
    }
}
           

輸出結果:

Name : wonjie

Age : 18

1.2 繼承

就是子類繼承父類的特征和行為,使得子類對象(執行個體) 具有父類的執行個體域和方法,或者子類從父類繼承方法,使得子類具有父類相同的行為。

與封裝的關系:由于子類與父類之間可能存在着實作細節的依賴,繼承可能會破壞封裝。

class Parent {
    public void run() {
        System.out.println("I just run.");
    }
}

public class Son extends Parent {
    public static void main(String[] args) {
        Son son = new Son();
        son.run();
    }
}
           

輸出:

I just run.

可以看出,Son類并沒有定義方法和屬性,但是通過繼承Parent類,就可以具有run()方法。

注:Java支援支援單繼承,不支援多繼承,但支援多重複繼承。

Java基礎回顧-011 基礎文法2 容器3 異常

 特性:

  • 子類可以擁有父類的非private的屬性、方法
  • 子類可以擁有自己的屬性和方法,即子類可以對父類進行擴充
  • 子類可以用自己的方法實作父類的方法

super 和 this 的差別:

  • this引用一個對象,是實實在在的,可以作為函數參數,可以作為傳回值
  • super是一個關鍵字,不能作為參數和傳回值,它隻是用于告訴編譯器通路父類的相關變量和方法

使用繼承的一個好處是可以統一處理不同子類型的對象。

關于繼承需要注意的點:

  • 如果父類沒有預設的構造方法,它的任何子類都必須在構造方法中通過super調用Base的帶參數構造方法。否則Java會提示編譯錯誤。
class Base {
    private String member;
    public Base(String member) {
        this.member = member;
    }
}
public class Child extends Base {
    private int a = 123;
    public Child(String member) {
        super(member);

    }
}
           
  • 如果在父類構造方法中調用了可被重寫的方法,則可能出現意想不到的結果。
class Base {
    public Base() {
        test();
    }
    public void test() {

    }
}
public class Child extends Base {
    private int a = 123;
    public Child() {

    }
    public void test() {
        System.out.println(a);
    }
}
           

 測試:

public class Test {
    public static void main(String[] args) {
        Child c = new Child();
        c.test();
    }
}
           

輸出:

123

 第一次輸出是在new過程中輸出的,在new過程中,首先初始化父類,父類構造函數調用test()方法,test()方法被子類重寫了,就會調用子類的test()方法,子類方法通路子類執行個體變量a,而這個時候子類的執行個體變量的指派語句和構造方法還有沒執行,是以輸出的是其預設值0。

1.2.1 重名和靜态綁定

基類代碼:

public class Base {
    public static String s = "static_base";
    public String m = "base";
    public static void staticTest() {
        System.out.println("base static:" + s);
    }
}
           

子類代碼:

public class Child extends Base {
    public static String s = "child_base";
    public String m = "child";
    public static void staticTest() {
        System.out.println("child static:" + s);
    }
}
           

子類定義了和父類重名的變量和方法。對于一個子類對象,它就有了兩份變量和方法,在子類内部通路的時候,通路的是子類的,或者說,子類變量和方法隐藏了父類對應的變量和方法。

測試:

 建立了一個子類對象,然後将對象分别指派給子類引用變量c和父類引用變量b,然後通過b和c分别引用變量和方法。

public class Test {
    public static void main(String[] args) {
        Child c = new Child();
        Base b = c;
        System.out.println(b.s);
        System.out.println(b.m);
        b.staticTest();
        System.out.println(c.s);
        System.out.println(c.m);
        c.staticTest();
    }
}
           

static_base

base

base static:static_base

child_base

child

child static:child_base

 通過b通路的是Base的變量和方法,當通過c通路時,通路的是Child的變量和方法,這稱之為靜态綁定,即通路綁定到變量的靜态類型。靜态綁定是程式編譯階段即可決定,而動态綁定則到等到程式運作時。

執行個體變量、靜态變量、靜态方法、private方法,都是靜态綁定的。

1.2.2 重載和重寫

重載:方法名稱相同但參數簽名不同(參數個數、類型或順序不同)

重寫:子類重寫與父類相同參數簽名的方法

基類代碼:

public class Base {
    public int sum(int a, int b) {
        System.out.println("base_int_int");
        return a+b;
    }
}
           

子類代碼:

public class Child extends Base {
    public long sum(long a, long b) {
        System.out.println("child_long_long");
        return a+b;
    }
}
           

測試代碼:

public class Test {
    public static void main(String[] args) {
        Child c = new Child();
        int a = 2;
        int b = 3;
        c.sum(a,b);
    }
}
           

 Child和Base都定義了sum方法,子類的sum方法雖然不完全比對但是是相容的,父類的sum方法參數類型是完全比對的。輸出: 

 base_int_int

如果将父類改為:

public class Base {
    public long sum(int a, long b) {
        System.out.println("base_int_long");
        return a+b;
    }
}
           

輸出:

base_int_long

 調用依然是父類的方法,父類和子類的兩個方法的類型都不完全比對,由于父類的更比對一點,是以仍然調用父類方法。再修改子類代碼:

public class Child extends Base {
    public long sum(int a, long b) {
        System.out.println("child_int_long");
        return a+b;
    }
}
           

目前子類與父類方法相同,同樣不比對,此時,輸出:

child_int_long

 是以可以看出:當多個重名函數的時候,在決定要調用那個函數的過程中,首先是按照參數類型進行比對,換句話說,尋找在所有重載版本中最比對的,然後才看變量的動态類型,進行動态綁定。

PS:當子類和父類方法都是完全比對時,調用子類方法。

1.2.3 父子類型轉換

向上轉型:子類型的對象可以指派給父類型的引用變量

Base b = new Child();
           

向下轉型:父類型的對象指派給子類型的引用變量(不一定轉換成功)

Child c = (Child)b;
           

 Child c = (Child)b;就是将變量b的類型強制轉換為Child并指派為c,這是沒有問題的,因為b的動态類型就是Child,但是下面的轉化方式就不可以。

Base b = new Base();
Child c = (Child)b;
           

 文法上不會報錯,但運作時會抛出錯誤,錯誤為類型轉換異常。

Exception in thread "main" java.lang.ClassCastException: beforeJob.Base cannot be cast to beforeJob.Child

一個父類變量能不能轉換為一個子類的變量,取決于這個父類變量的動态類型(即引用的對象類型) 是不是這個子類或這個子類的子類。

1.2.4 繼承通路權限protected

public:外部可以通路

private:隻能内部通路

protected:可以被子類通路,還可以被同一包中的其他類通路,不管其他類是不是該類的子類。

基類代碼:

public class Base {
    protected int currentStep;
    protected void step1() {
        
    }
    protected void step2() {
        
    }
    public void action() {
        this.currentStep = 1;
        step1();
        this.currentStep = 2;
        step2();
    }
}
           

action表示對外提供的行為, 内部有兩個步驟step1()和step2(),使用currentStep變量表示目前進行到了那個步驟,step1()、step2()和currentStep是protected的,子類一般不重寫action,而隻是重寫step1和step2,同時,子類可以直接通路currentStep檢視進行到哪一步。子類代碼:

public class Child extends Base {
    public void step1() {
        System.out.println("child step " + this.currentStep);
    }
    protected void step2() {
        System.out.println("child step " + this.currentStep);
    }
}
           

測試:

public class Test {
    public static void main(String[] args) {
        Child c = new Child();
        c.action();
    }
}
           

 輸出:

child step 1

child step 2

基類定義了表示對外行為的方法action,并定義了可以被子類重寫的兩個步驟step1()和step2() ,以及被子類檢視的變量currentStep,子類通過重寫protected方法step1()和step2()來修改對外的行為。

這種思路和方法是一種設計模式,稱之為模闆方法。action方法就是一個模闆方法,它定義了實作的模闆,而具體實作則由子類提供。模闆方法在很多架構中有廣泛的應用,這是使用protected的一種常見場景。

1.2.5 其他

可見性重寫:重寫時,子類方法不能降低父類方法的可見性。

final修飾類-防止繼承;修飾方法-防止重寫。

1.2.6 繼承實作的基本原理

基類:

public class Base {
    public static int s;
    private int a;
    static {
        System.out.println("基類靜态代碼塊,s:" + s);
        s = 1;
    }
    {
        System.out.println("基類執行個體代碼塊,a:" + a);
        a = 1;
    }
    public Base() {
        System.out.println("基類構造方法,a:" + a);
        a = 2;
    }
    protected void step() {
        System.out.println("base s : " + s + ", a : " + a);
    }
    public void action() {
        System.out.println("start");
        step();
        System.out.println("end");
    }
}
           

子類:

public class Child extends Base {
    public static int s;
    private int a;
    static {
        System.out.println("子類靜态代碼塊,s:" + s);
        s = 10;
    }
    {
        System.out.println("子類執行個體代碼塊,a:" + a);
        a = 10;
    }
    public Child() {
        System.out.println("子類構造方法,a:" + a);
        a = 20;
    }
    protected void step() {
        System.out.println("child s : " + s + ", a : " + a);
    }
}
           

 測試:

public class Test {
    public static void main(String[] args) {
        System.out.println("---new Child()");
        Child c = new Child();
        System.out.println("\n---c.action()");
        c.action();
        Base b = c;
        System.out.println("\n---b.action()");
        b.action();
        System.out.println("\n---b.s:" + b.s);
        System.out.println("\n---c.s:" + c.s);
    }
}
           

輸出:

---new Child()

基類靜态代碼塊,s:0

子類靜态代碼塊,s:0

基類執行個體代碼塊,a:0

基類構造方法,a:1

子類執行個體代碼塊,a:0

子類構造方法,a:10

---c.action()

start

child s : 10, a : 20

end

---b.action()

start

child s : 10, a : 20

end

---b.s:1

---c.s:10

1.3 多态

一種類型的變量,可以引用多種實際類型對象。

例子:

public class ShapeManager {
    private static final int MAX_NUM = 100;
    private Shape[] shapes = new Shape[MAX_NUM];
    private int shapeNum = 0;
    public void addShape(Shape shape) {
        if (shapeNum < MAX_NUM) {
            shapes[shapeNum++] = shape;
        }
    }
    public void draw() {
        for (int i = 0; i < shapeNum; i++) {
            shapes[i].draw();
        }
    }
}
           
public class ManageTest {
    public static void main(String[] args) {
        ShapeManager manager = new ShapeManager();
        manager.addShape(new Circle(new Point(4,4),3));
        manager.addShape(new Line(new Point(2,3),new Point(3,4),"green"));
        manager.addShape(new ArrowLine(new Point(1,2),new Point(5,5),"black",false,true));
        manager.draw();
    }
}
           

 對于變量shape,有兩種類型:

  1. 類型Shape,稱為shape的靜态類型
  2. 類型Circle/Line/ArrorLine,稱之為shape的動态類型。在ShapeManager的draw方法中,shape[i].draw()調用的是其對應動态類型的draw方法,這種稱之為方法的動态綁定。

為什麼要有多态和動态綁定?

建立對象的代碼和操作對象的代碼,經常不在一起,操作對象的代碼往往隻知道對象是某種父類型,也往往隻需要知道它是某種父類型就可以了。

多态和動态綁定是計算機程式中的一種重要思維方式,使得操作對象的程式不需要關注對象的實際類型,進而可以統一處理不同對象,但又能實作每個對象的特有行為。

子類對象可以指派給父類引用變量,這叫多态;實際執行調用的是子類實作,這叫動态綁定。

2 容器

Java基礎回顧-011 基礎文法2 容器3 異常
Java基礎回顧-011 基礎文法2 容器3 異常

3 異常

3.1 異常分類

Throwable是所有異常的基類,它有兩個子類:Error和Exception。

Java基礎回顧-011 基礎文法2 容器3 異常

 Error表示系統錯誤或資源耗盡,由Java系統自己使用,應用程式不抛出和處理。如上圖虛拟機錯誤(VirtualMachineError)及其子類記憶體溢出錯誤和棧溢出錯誤。

Exception表示應用程式錯誤,它有很多子類,應用程式也可以通過繼承Exception或其子類建立自定義異常。

如此多不同的異常類其實并沒有比Throwable這個基類多多少屬性和方法,大部分類在繼承父類之後隻是定義了幾個構造方法,這些構造方法也隻是調用了父類的構造方法,并沒有額外的操作。

定義這麼多不同的類,是因為名字不同。異常類名字本身就代表異常的關鍵資訊,無論是抛出還是捕獲異常,使用合适的名字都有助于代碼的可讀性和可維護性。

自定義異常:

public class AppException extends Exception {
    public AppException() {
        super();
    }
    public AppException(String message, Throwable cause) {
        super(message, cause);
    }
    public AppException(String message) {
        super(message);
    }
    public AppException(Throwable cause) {
        super(cause);
    }
}
           

3.2 異常處理

3.2.1 catch比對

try {
            // 可能觸發異常的代碼
        } catch (NumberFormatException e) {
            System.out.println("not valid number");
        } catch (RuntimeException e) {
            System.out.println("runtime exception " + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
           

3.2.2 重新抛出異常

對catch塊内處理完後,可以重新抛出異常。這個異常可以是原來的,也可以是建立的。

try {
            // 可能觸發異常的代碼
        } catch (NumberFormatException e) {
            System.out.println("not valid number");
            throw new AppException("輸入格式不正确", e);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
           

3.2.3 finally

catch後面可以跟finally語句,文法如下:

try {
            // 可能抛出異常
        } catch () {
            // 捕獲異常
        } finally {
            // 不管有無異常都執行
        }
           

3.2.4 try-with-resources

對于一些使用資源的場景,比如檔案和資料庫連接配接,典型的使用流程是首先打開資源,最後在finally語句中調用資源的關閉方法。針對這種場景,Java7開始支援一種新的文法,稱為try-with-resources,這種文法針對實作了java.lang.AutoCloseable接口的對象,該對象定義為:

public interface AutoCloseable {
    void close() throws Exception;
}
           

沒有try-with-resources時,使用形式如下:

public static void useResource() throws Exception {
    AutoCloseable r = new FileInputStream("hello"); //建立資源
    try {
        // 使用資源
    } finally {
        r.close();
    }
}
           

使用try-with-resources文法,形式如下:

public static void useResource() throws Exception {
    try(AutoCloseable r = new FileInputStream("hello")) { //建立資源
        // 使用資源
     }
}
           

3.2.5 throws

異常機制中,還要一個和throw很像的關鍵字throws,用于聲明一個方法可能抛出的異常,文法如下所示:

public void test() throws AppException, SQLException, NumberFormatException {
    // 主體代碼
}