JAVA物件導向基礎

Monste發表於2021-07-16

JAVA物件導向基礎

一、類與例項

物件:萬物皆物件,所有的東西都是物件,物件是一個自包含的實體,用一組可識別的特性和行為來標識。

:是具有相同屬性和功能的物件的抽象集合

例項:就是一個真實的物件

例項化:建立物件的過程,使用new關鍵字來建立

public void Test()
{
	Cat cat = new Cat();//將Cat類例項化
}

'Cat cat = new Cat();'做了兩件事

Cat cat;//宣告一個Cat的物件,物件名為cat
cat = new Cat();//將此cat物件例項化

二、構造方法

構造方法又叫建構函式,就是對類進行初始化。與類同名,無返回值,不需要void,在new的時候呼叫。

Cat cat = new Cat()

其中,Cat()就是構造方法。

所有類都有構造方法,不定義構造方法系統會預設生產空的構造方法。定義構造方法,則預設的構造就方法會消失。

public class Cat
{
    //宣告Cat類的私有字串變數name
    private String name = "";
    //定義Cat類的構造方法,引數是輸入一個字串。
    public Cat(String name)
    {
        this.name = name;//將引數賦值給私有變數name
    }
}

三、方法過載

方法過載提供建立同名的多個方法的能力,但是這些方法需使用不同的引數型別,不只構造方法可以過載,普通方法也可以過載。

方法名相同,引數型別或個數必須要有所不同。

方法過載可在不改變原方法的基礎上,新增功能。

public class Cat
{
    private String name = "";
    public Cat(String name)
    {
        this.name = name;
    }
    //將構造方法過載
    public Cat()
    {
        this.name = "無名";
    }
}

四、屬性與修飾符

屬性

屬性:是一個方法或一對方法,在呼叫它的程式碼看來,它是一個欄位,即屬性適合於以欄位的方式使用方法呼叫的場合。

欄位:是儲存類要滿足其設計所需要的資料,欄位都是與類相關的變數。

//宣告一個內部欄位,private,預設為3
private int shoutNum = 3;
//ShoutNum屬性,注意是public
public int ShoutNum
{
    //get表示外界呼叫時可以得到shoutNum的值
    public int get(){
        return shoutNum;
    }
    //set表示外界可以給內部的shoutNum賦值
    public void set(int value){
        shoutNum = value;
    }
}

屬性有兩個方法,get和set。

get:訪問器返回與宣告的屬性相同的資料型別,呼叫時可以得到內部的欄位的值或引用。

set:呼叫屬性時可以給內部的欄位或引用賦值。

修飾符

public:表示它所修飾的類成員可以允許其它任何類來訪問,俗稱公有的。

private:表示只允許同一個類中的成員訪問,其它類包括它的子類無法訪問,俗稱私有的。

通常欄位都是private,即私有變數(一般是首字母小寫或前加‘_’),而屬性都是public,即公有變數(首字母大寫)。

final

  1. 修飾變數:修飾變數時必須賦初值且不呢改變,修飾引用變數不能再指向其他物件。
  2. 修飾方法:方法前面加上final關鍵字,代表這個方法不可被子類重寫。
  3. 修飾類:表示這個類不能被繼承,類中的成員可以根據需要設為final,final類中的所有成員方法都會被隱飾地指定為final方法。

注:類的private方法會被隱飾地指定為final

protected:繼承的子類可以對基類(父類)有完全訪問權。對子類公開,不對其它類公開。

五、封裝

每個物件都包含它能進行操作所需的所有資訊,因此物件不必依賴其它物件來完成自己的操作,能夠避免物件屬性賦值的隨意性。

封裝的好處:

  1. 良好的封裝能減少耦合(相互作用相互影響的關係)。
  2. 類內部的實現可以自由地修改。
  3. 具有清晰的對外介面。(對外的屬性和方法)set和get方法。

this關鍵字是對當前內的簡化。

提供專門對外的set和get方法對屬性進行設定,而不能用類.屬性的方法修改值。

public class Student{
    private int age;//age屬性私有化
    
    public Student(int age){
        this.age = age;
    }
    public int getAge(){
        return age;
    }
    public void setAge(int age){
        this.age = age;
    }
}

六、繼承

java只支援單繼承,不允許多繼承。

子類繼承父類:

  1. 子類擁有父類所有的屬性和方法(包括私有屬性和私有方法),但是父類中的私有屬性和方法子類無法訪問,只是擁有
  2. 子類擁有自己的屬性和方法,即子類可以在父類的基礎上做擴充套件。
  3. 子類可以用自己的方式重寫父類的方法。
package Test;

class Students {
    int age;//id屬性私有化
    String name;//name屬性私有化
    Students(int age,String name){
    	this.age = age;
    	this.name = name;
    }
    public void printInfo() {
		System.out.println("父類方法 年齡:"+age+" 姓名:"+name);
	}
}

class Student extends Students{
	Student(int age, String name) {
		super(age, name);
	}
	//重寫父類同名方法
    @Override
	public void printInfo() {
		System.out.println("子類方法 年齡:"+age+" 姓名:"+name);
	}
	//使用super呼叫父類的方法
	public void FatherprintInfo() {
		super.printInfo();
	}
}
public class test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Student s2 = new Student(19,"張三");
		s2.printInfo();
		s2.FatherprintInfo();
	}
}

//子類方法 年齡:19 姓名:張三
//父類方法 年齡:19 姓名:張三

super關鍵字

  1. super用於在子類呼叫父類的重名方法(不重名也能引用)。
  2. super在子類中引用重名的變數(不重名也能引用)。
  3. super()可以用於直接呼叫父類的構造方法。

繼承的優點

繼承使得所有子類公共的的部分都放在了父類,使得程式碼得到了共享,就可以避免重複,繼承使得修改和擴充套件繼承而來的實現都較為容易。

繼承的缺點

繼承也是有缺點的,那就是父類做出修改,子類就不得不進行改變。另外,繼承也會破壞包裝,父類實現的細節暴露給子類。

總結:只有合理的應用繼承才能發揮好的作用。例如貓繼承動物,而動物不能繼承貓。

七、多型的淺理解

多型表示不同的物件執行相同的操作,但是要通過自己的實現程式碼來執行。

注意點:

  1. 子類以父類的身份出現。
  2. 子類在工作時以自己的方式來實現。
  3. 子類以父類身份出現時,子類特有的屬性和方法不可以使用,只能用父類存在的方法。

多型有兩種情形

編譯時多型:

  1. 過載(overload)多個同名的不同方法。

執行時多型:

  1. 覆蓋(override),子類對父類方法進行覆蓋(重寫)。
  2. 動態繫結--也稱為虛方法呼叫,正真的方法在執行時才確定。
  3. 在呼叫方法時,程式會正確地呼叫子類物件的方法。

多型中成員的特點

1.多型成員變數:編譯執行看左邊。

People p = new Student();
System.out.println(p.name)//p是People中的值,只能取到父類中的值。

2.多型成員方法:編譯看左邊,執行看右邊。

People p1 = new Student();
System.out.println(p1.printInfo())//p1的表面上型別是People,但實際上是Student,所以呼叫的是重寫後的方法。

instancof關鍵字

用於判斷某個物件是否屬性某種資料型別。

返回型別為布林型別。

People p1 = new Student();
System.out.println(p1 instancof Student);
//true

多型的轉型

向上轉型

將子類物件賦值給父類變數,稱之為向上轉型。

//People[] people = {new Student(19,"張三"), new Teacher(30,"老師A")};
People people[];//宣告一個人類陣列
people = new People[1];//例項化最多兩個人類物件
//向上轉型
people[0] = new Student(19,"張三");
people[1] = new Teacher(30,"老師A");
//foreach遍歷物件
for(People item : people)
{
    item.printInfo();
}
//年齡:19 姓名:張三
//年齡:30 姓名:老師A

向下轉型

將父類變數轉換為子類變數,稱之為向下轉型。向下轉型格式如下:

子類型別 變數名 = (子類型別) 父類型別的變數
for(int i=0;i<people.length;i++){
    if(people[i] instanceof Student){
    	//向下轉型
        Student student = (Student) people[i];
	}else if(people[i] instanceof Teacher){
        Teacher teacher = (Teacher) people[i];
    }else{
        System.out.println("程式錯誤。");
    }
}

八、抽象類

仔細觀察,你會發現,People類其實根本不能例項化,一個學生長什麼樣子,可以想象。new People;即例項化一個人,一個人長什麼樣?

People是一個抽象的名詞,沒有其具體物件與之對應。

Java執行把類和方法宣告為abstract,即抽象類和抽象方法。

abstract class People{//加abstract關鍵字,表面抽象類。
	......
     //在方法返回值前加abstract表明此方法是抽象方法,抽象方法沒有方法體,直接在括號後面加上";"
	protected abstract void printInfo();
}

抽象類注意點:

  1. 抽象類不能例項化。
  2. 抽象方法必須被子類重寫。
  3. 如果類中有抽象方法,那麼類就必須定義為抽象類,不論是否還包含其它的一般方法。

讓抽象類擁有儘可能多的共同程式碼,擁有儘可能少的資料。

抽象類通常代表一個抽象的概念,它提供一個繼承的出發點,當設計一個新的抽象類時,一定是用來繼承的,所有在一個繼承關係形成的等級結構裡,樹葉節點應當是具體類。而樹葉節點均應當是抽象類。

九、介面(interface)

介面就是把隱式公共方法和屬性組合起來,以封裝特定功能的一個集合,一旦類實現了介面,類就可以職稱介面所指定的所有屬性和成員,宣告介面的語法和抽象類完全相同,但不允許提供介面中任何成員的執行方式。所以介面不能例項化,不能有構造方法和欄位;不能有修飾符(如public,private等);不能宣告虛擬的或靜態的等。還有實現介面的類就必須要屬性介面中的所有方法和屬性。

一個類可以支援多個介面,多個類也可以支援相同的介面。介面的命名,前面要加一個大寫的字母'I',這是規範。

介面用interface宣告,而不是class,介面名稱前要加‘I’,介面中的方法或屬性前面不能有修飾符、方法沒有方法體。

//宣告一個IPeople介面,此介面有一個printInfo方法
interface IPeople{
    void printInfo();
}

使用介面實現學生類

class Student implements IPeople{
    public void printInfo(){
        System.out.println("執行正確。");
    }
}

區分抽象類和介面

抽象類可以給出一些成員的實現,介面卻不包含成員的實現,抽象類的抽象成員可被子類部分實現,介面的成員需要實現類完全實現,一個類只能繼承一個抽象類,但可實現多個介面等。

區分:

  1. 類是對物件的抽象;抽象類是對類的抽象;介面是對行為的抽象。
  2. 如果行為跨越不同類的物件,可使用介面;對於一些相似的類物件,用繼承抽象類。(實現介面和繼承抽象類並不衝突)。
  3. 從設計者角度來講,抽象類是從子類物件中發現了公共的東西,泛化出父類,然後子類繼承父類。而介面是根本不知子類的存在,方法如何實現還不確認,提前定義。

十、泛型

泛型是具有佔位符(型別引數)的類、結構、介面和方法,這些佔位符是類、結構、介面和方法所儲存或使用的一個或多個型別的佔位符。泛型集合類可以將型別引數用作它所儲存的對像的型別的佔位符;型別引數作為其欄位和其方法的引數型別出現。

IList arrayPeople;//宣告一個集合變數,可以用介面IList,也可以直接宣告“ArrayList arrayPeople;”

用法就是在IList和List後面加'',這個'T'就是你需要指定的集合的資料或物件型別。

//宣告一泛型集合變數,用介面IList,注意:IList表示此集合變數只能接受People型別,其它型別不接受,也可以直接宣告“List ”
IList<People> arrayPeople;
//例項化List物件,需要指定List<T>的'T'是People
arrayPeople = new List<People>();

泛型的使用

泛型有三種使用的方式,分別為:泛型類、泛型介面、泛型方法。

public class People<T>{
	//T stands for "Type"
    private T t;
    public void set(T t){this.t = t;}
    public T get(return t);
}

十一、註解

Java註解是附加在程式碼中的一些元資訊,用於一些工具在編譯、執行時進行解析和使用,起到說明,配置的功能。註解不會也不呢影響程式碼的實際邏輯,僅僅起到輔助性的作用。

註解 Annotation 實現原理與自定義註解例子(opens new window)

十二、反射

什麼是反射

可以獲取任意一個類的所有屬性和方法,還可以呼叫這些屬性和方法。

Java反射主要提供以下功能:

  • 在執行時判斷任意一個物件所屬的類;
  • 在執行時構造任意一個類的物件;
  • 在執行時判斷任意一個類所具有的成員變數和方法(通過反射甚至可以呼叫private方法);
  • 在執行時呼叫任意一個物件的方法;

重點:是執行時而不是編譯時。

反射機制的優缺點

  • 優點:可以讓程式碼更加靈活,為各種框架提供開箱即用的功能提供了便利。
  • 缺點:讓我們在執行時有了分析操作類的能力,增加了安全問題。比如可以無視泛型引數的安全檢查(泛型引數的安全檢查發生在編譯時)。另外,反射的效能也會稍差點。不過,對框架來說影響不大。Java Reflection: Why is it so slow?

反射的應用場景

我們平時大部分時候都是在寫業務程式碼,很少會接觸到直接使用反射機制的場景。

但是,這並不代表反射沒有用。相反,正是因為反射,你才能這麼輕鬆地使用各種框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射機制。

這些框架中也大量使用了動態代理,而動態代理的實現也依賴反射。

十三、異常

在Java中,所有的異常都有一個共同的祖先java.lang包中的Throwable類,Throwable類有兩個重要的子類Error(錯誤)和Exception(異常)。其中Error用來表示JVM(Java虛擬機器)無法處理的錯誤,Exception又分為兩種:

  • 受檢查異常:需要用try-catch語句捕獲並進行處理,並且可以從異常中恢復。
  • 不受檢查異常:java程式碼在編譯過程中,即使不處理不受檢查異常也可以正常通過編譯。是執行時的錯誤,例如除0會引發ArithmeticException(算術錯誤異常),此時程式崩潰並且無法恢復。RuntimeException及其子類都統稱為非受檢查異常。

Throwable類常用方法

public string getMessage()//返回異常發生時的簡要描述。
public string toString()//返回異常發生時的詳細資訊。
public string getLocalizedMessage()//返回異常物件的本地化資訊。使用Throwable的子類覆蓋這個方法,可以生成本地化資訊。如果子類沒有覆蓋該方法,則該方法返回的資訊與getMessage()返回結果相同。
public void printStackTrace()//在控制檯上列印Throwable物件封裝的異常資訊。

try-catch-finally

try塊:用於捕獲異常,後面可以接零個或多個catch塊,如果沒有catch塊,則必須跟一個finally塊。

catch塊:用於處理try捕獲到的異常。

finally塊:無論是否捕獲或處理異常,finally塊裡的語句都會被執行。當在try塊或catch塊中遇到return語句時,finally語句塊將在方法返回之前被執行。

在下面三種特殊情況下,finally塊不會被執行:

  • 在try或finally塊中用了System.exit(int)退出程式。如果System.exit(int)在異常語句之後,finally還是會被執行。
  • 程式所在的執行緒死亡
  • 關閉CPU

注意:當try語句和finally語句中都有return語句時,在方法返回之前,finally語句的內容將被執行,並且finally與的返回值會覆蓋原始的返回值,如下:

public class Test{
	public static int f(int value){
		try{
            return value*value;
        }finally{
            if(value == 2)
                return 0;
        }
	}
}

如果呼叫f(2),返回值將是0,因為try語句的返回值被finally語句的返回值覆蓋。

使用try-with-resources來代替try-catch-finally

  1. 適用範圍(資源的定義):任何實現java.lang.AutoCloseable或者java.io.Closeable的物件。
  2. 關閉資源和finally塊的執行順序:在try-with-resources語句中,任何catch或finally塊在宣告的資源關閉後執行。

《Effecitve Java》中明確指出:

面對必須要關閉的資源,我們總是應該優先使用try-with-resources而不是try-finally。隨之產生的程式碼更簡短,更清晰,產生的異常對我們也更有用。try-with-resources語句讓我們更簡短清晰,產生的異常對我們更有用。try-with-resources語句讓我們更容易編寫必須要關閉的資源的程式碼,若採用try-finally則幾乎做不到這點。

Java中類似於InputStreamOutputStreamScannerPrintWriter等的資源都需要我們呼叫close()方法來手動的關閉。

使用try-catch-finally語句實現:

//讀取文字檔案的內容
Scanner scanner = null;
try{
	scanner = new Scanner(new File("D://read.txt"));
    while(scanner.hasNext())
    	System.out.println(scanner.nextLine());
}catch (FileNotFoundException e){
    e.printStackTrace();
}finally{
    if(scanner != null)
        scanner.close();
}

使用Java 7之後的try-with-resources語句改造上面的程式碼:

try (Scanner scanner = new Scanner(new File("D:\\read.txt"))){
	while(scanner.hasNext())
    	System.out.println(scanner.nextLine());
}catch (FileNotFoundException e){
    e.printStackTrace();
}

通過使用分號分隔符,可以在try-with-resources塊中宣告多個資源。

try(BufferredInputStream bin = new BufferredInputStream(new FileInputStream(new File("test.txt"))); BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))){
	int b;
    while((b = bin.read()) != -1)
        bout.write(b);
}catch (IOException e)
{
    e.printStackTrace();
}

十四、I/O流

相關文章