JAVA基礎複習(六)

    本着重新學習(看到什麼複習什麼)的原則,這一篇講的是JAVA的特性。看了諸位大神的解釋後詳細的查了一些東西,記錄下來,也感謝各位在網絡上的分享!!!

    在正式開始JAVA的特性之前,再來回顧一下OOP,上篇學習了AOP面向切面編程,OOP則是面向對象編程。面向對象實際上就是把所有事物都看做是對象的這種思想。面向對象比之面向過程更容易維護,並且代碼可複用也更容易拓展,但是性能會比面向過程低。面向對象嘛,就先來看看什麼是對象。

    1.什麼是對象?

    在現實生活中的所有客觀存在的實體都可以稱之爲對象。常說的一句話就是“萬物皆對象”。

    2.什麼是類?

    類是一個抽象的概念,只是爲了描述對象信息,並且確定對象將會擁有的屬性(特性)和方法(行爲)。也就是一個模子,在類中定義的是各種屬性和方法,而這些屬性和方法是類所描述的這一類事物的共性。通過這些屬性和方法可以確定一個對象。

    所以類和對象的關係就是一個抽象出來的概念和一個實體的區別,類是對象的模板,對象則是類的實例。通過類可以創建實例,而每一個實例就是一個對象。而在瞭解了類和對象後就是JAVA的三大特性,封裝,繼承,多態。(也有人說有抽象,一起復習下。。。)

    3.什麼是封裝?

    這個問題可以用餐廳點餐的場景,每個菜品都有自己的製作方法,這些信息不能暴露給外人看,那麼我們可以通過什麼去了解該菜品的製作手法呢?我們可以通過餐廳給定的規矩,即給錢買菜品的方式去了解。封裝也是這樣,它不允許外部程序直接訪問某些過程方法或者信息,我們只能通過該類提供的方法來實現對隱藏信息的操作和訪問。封裝的核心思想也正是將屬性和方法結合爲一個整體,並儘可能隱藏內部實現細節,只需要關注類本身是如何使用的,而不必關注類內的具體實現。

    在封裝的實現過程中,由於需要隱藏屬性,那麼就要設置屬性的修飾符爲private,其次給定訪問方法,也就是創建對應屬性的getter/setter方法,用於對屬性進行取值和賦值,此外還可以在getter/setter方法中對屬性值是否合法進行判定,也就是說取值或者賦值也有可以擁有自己的邏輯處理,從而完成封裝,即類中的私有成員變量和方法只能在該類內部進行調用,而不能通過對象進行調用。

    我們有時候也將不同類中的相同方法或常用方法抽離出來使用的方式稱爲使用了封裝思想。

    4.什麼是內部類?

    內部類就是定義在某一個類中的類(Inner Class)。內部類的主要作用就在於可以提供更好的封裝效果,並且內部類實際上是可以直接訪問外部類的所有屬性和方法的,也包括被private修飾符修飾的屬性和方法,但是外部類不能直接訪問內部類的屬性和方法。當然,內部類的實現功能是可以被外部類實現的,只不過有時候會選擇內部類進行使用(如線程,驅動等)。另外,迫於JAVA的單繼承機制,可以使用內部類,內部類可以單獨繼承一個接口的實現而不用在外部類的位置考慮多繼承的問題(外部類不需要繼承該接口或實現,對內部類的繼承都沒有影響)。內部類的使用使得JAVA可以從一定程度上實現多繼承。

    實際上內部類是一個編譯時的概念,在編譯後實際上會生成兩個獨立的class文件。可以看到編譯後內部類也擁有了自己的class文件,被命名爲“外部類$內部類”的方式。

    內部類可以分爲四種,成員內部類,靜態內部類,方法內部類和匿名內部類。

    (1).成員內部類:成員內部類定義在外部類的內部,可以使用任何訪問控制符。成員內部類類內方法可以直接訪問外部類數據(無需考慮訪問控制符)。成員內部類必須用外部類對象來創建(內部類 內部類對象名 = 外部類對象.new 內部類();)。在成員內部類內無法聲明靜態成員變量(static:會提示你使用static final或者去掉static或者將該類本身定義爲static,但是若將內部類本身定義爲靜態,也再也無法創建內部類類型的對象),除static final外均不可使用。若成員內部類與外部類有相同成員變量或方法,成員內部類默認訪問自己的成員變量或方法,可以用外部類.this.類成員變量或方法(TryInnerClass.this.outString)的方式訪問外部類的同名成員變量或方法。

    (2).靜態內部類:靜態內部類同樣定義在外部類內部,聲明時需要使用static修飾符,靜態內部類不依賴外部類便能通過new創建對象,同時,靜態內部類也不能訪問外部類的成員變量或方法了,除非該成員變量或方法是靜態的。

    (3).方法內部類:方法內部類定義在外部類的方法中,方法內部類只在該方法內部可見。若方法內部類需要訪問外部類的成員變量或方法,則必須要求該成員變量或者方法必須擁有final修飾符(這裏要注意對於成員變量來說JDK1.8版本以上不用顯式修飾,但實際上也是有final修飾符的,只不過隱藏了)。

    (4).匿名內部類:可以被視爲沒有類名的方法內部類。匿名內部類必須繼承或實現一個接口,匿名類不能有顯式的extends或implements關鍵字。也不能聲明靜態成員變量。

package com.day_6.excercise_1;

import java.util.Collection;
import java.util.Iterator;

public class TryInnerClass {
	// 成員內部類
	private String outString = "外部成員變量";
    public class Inner {
        private String intString = "內部類成員變量";
		public void innerShow() {
			System.out.println("內部類調用外部類成員變量:"+TryInnerClass.this.outString);
			outShow();
		}
	}
    public void outShow() {
    	// 編譯錯誤
//		System.out.println("內部類成員變量:"+intString);
    	System.out.println("內部類成員變量:"+new Inner().intString);
    }
    
 // 靜態內部類
	private static String outStaticString = "外部靜態成員變量";
    public static class StaticInner {
        private static String intSaticString = "靜態內部類成員變量";
		public void innerStaticShow() {
			// 編譯錯誤
//			System.out.println("靜態內部類調用外部類靜態成員變量:"+ outString);
			System.out.println("靜態內部類調用外部類靜態成員變量:"+ outStaticString);
			// 編譯錯誤
//			outShow();
			outStaticShow();
		}
	}
    public static void outStaticShow() {
    	System.out.println("靜態內部類成員變量:"+ new StaticInner().intSaticString);
    }
    
    // 方法內部類
    private String outmethodString = "外部成員變量";
    public void tryoutMethodShow() {
    	class MethodInner {
    		public String methodString = "方法內部類成員變量";
    		public void innerMethodShow() {
    			System.out.println("方法內部類調用外部成員變量:"+outmethodString);
    			// 可用,打開輸出太亂了
//    			new TryInnerClass().outMethodShow();
    		}
    	}
    	// 供外部類調用方法內部類的內部方法
    	System.out.println("外部類調用方法內部類的內部方法");
    	new MethodInner().innerMethodShow();
    }
    
    
	public static void main(String[] args) {

        // 成員內部類
		TryInnerClass tryInnerClass = new TryInnerClass();
		Inner inner = tryInnerClass.new Inner();
		inner.innerShow();
		tryInnerClass.outShow();

		// 靜態內部類
		TryInnerClass tryInnerStaticClass = new TryInnerClass();
		// 編譯錯誤
//		StaticInner staticInner = tryInnerClass.new StaticInner();
		StaticInner staticInner = new StaticInner();
		// 編譯錯誤
//		tryInnerStaticClass.innerStaticShow();
		staticInner.innerStaticShow();

		// 方法內部類
		TryInnerClass tryInnerMethodClass = new TryInnerClass();
		tryInnerMethodClass.tryoutMethodShow();

		// 匿名內部類
		Collection<String> noNameCollection = new Collection<String>() {
			@Override
			public <T> T[] toArray(T[] a) {
				return null;
			}
			......
		};
	}
}

package com.day_6.excercise_1;

import java.io.PrintStream;

public class TryInnerClass$Inner
{
  public TryInnerClass$Inner(TryInnerClass paramTryInnerClass) {}
  private String intString = "內部類成員變量";
  public void innerShow() { System.out.println("內部類調用外部類成員變量:" + TryInnerClass.access$0(this.this$0));
    this.this$0.outShow();
  }
}

    然後我們來看一下內部類的繼承,一會再說繼承特性。通過上面的代碼可見內部類的編譯結果跟正常類的編譯結果相同,那麼就代表着內部類同樣可以被繼承。但是在內部類被繼承時有些不一樣,成員內部類在被繼承時需要加上外部類的類名,其次需要在成員內部類的子類的構造方法第一句顯式的添加(外部類對象引用.super(參數);)。這裏會對這個super()的用法有些疑問,有的解釋是這樣的:super本身是當前對象的直接父類的無參構造函數,當是某類的內部類時,實際上執行的就是Outer.inner.super(),因爲確實我需要在該成員內部類的子類的構造函數中傳入一個外部類的對象。我們都知道內部類有一個指向外部類的引用,當需要繼承一個內部類的時候,就得取得那個指向外部類的引用,也就是這句話。不過我還是在等待更好的解釋,還有一部分說是固定寫法,希望能告知的能評論下,萬分感謝!(mark一下。。。)

package com.day_6.excercise_1;

public class TryInnerExtends extends TryInnerClass.Inner {
	
	public TryInnerExtends(TryInnerClass innerClass) {
		innerClass.super();
	}
    public static void main(String[] args){
    	TryInnerClass otherClass = new TryInnerClass();
    	TryInnerExtends thirdExtends = new TryInnerExtends(otherClass);
    	thirdExtends.innerShow();
    }
}
//public class TryInnerExtends extends TryInnerClass.StaticInner {
//	
//	public TryInnerExtends() {
//		super();
//	}
//    public static void main(String[] args){
//    	TryInnerClass otherClass = new TryInnerClass();
//    	TryInnerExtends thirdExtends = new TryInnerExtends();
//    	thirdExtends.innerStaticShow();;
//    }
//}

    上下兩個類都可以實現繼承,但是編譯後的代碼不同,這裏貼出的代碼是對應的。

package com.day_6.excercise_1;

public class TryInnerExtends extends TryInnerClass.Inner
{
    public TryInnerExtends(final TryInnerClass innerClass) {
        innerClass.getClass();
        innerClass.super();
    }
    
    public static void main(final String[] args) {
        final TryInnerClass otherClass = new TryInnerClass();
        final TryInnerExtends thirdExtends = new TryInnerExtends(otherClass);
        thirdExtends.innerShow();
    }
}

package com.day_6.excercise_1;

public class TryInnerExtends extends TryInnerClass.StaticInner
{
    public static void main(final String[] args) {
        final TryInnerClass otherClass = new TryInnerClass();
        final TryInnerExtends thirdExtends = new TryInnerExtends();
        thirdExtends.innerStaticShow();
    }
}

    另外還要注意的是當一個類繼承了一個含有內部類的外部類並覆寫了內部類時,兩個內部類各自在自己的明明空間中,所以是完全獨立的兩個不同的實體。

    5.什麼是繼承?

    繼承的定義很好理解,父類可以派生出子類,子類可繼承父類中的屬性和方法,並可以擁有自己獨有的屬性和方法。也可以反過來說,從很多不同的類中抽取出相同的屬性或方法,並將這些屬性或方法進行封裝,成爲父類或者基類。這樣原有的哪些類只需要繼承這個父類,就可以擁有原來重複定義的屬性或方法了。在JAVA中,是單繼承的,也就是說一個類只能有一個直接父類。但是繼承的關係是傳遞的,這很好理解,類A繼承類B,類B繼承類C,那麼類A中就得有類B和類C中的屬性和方法。當然,繼承來的屬性或方法是可以被使用但是不一定可見的。繼承有很多需要注意的:

    (1).JAVA是單繼承,不允許多繼承(但可以使用其他方式實現多繼承的思想,如逐層繼承)。即使沒有聲明父類,也會有隱含父類Object存在。(寫到這裏我想到了一個問題,類A的父類是類Object,類B的父類是類A,那類B是不是同時繼承了兩個類?實際上不是這樣的,如果類B沒有繼承的父類,那麼Object類就是它的隱含父類,但是如果它有繼承的父類,那麼若只有類Object,類A,類B的話,類Object應該是類B的父類的父類,也就是說最大的基類一定是Object類,其他的類會在一直保持着單繼承的前提下,通過逐層繼承隱式的成爲Object類的派生類)

    (2).若子類有與父類同名的成員變量或方法,子類可以通過使用super關鍵字調用父類的成員變量或方法,這裏說的不是super()而是super單獨使用。但是所有私有屬性或構造方法或方法不可以被直接調用,但是可以通過如getter/setter方法進行調用。

    (3).如果子類構造方法中沒有顯式的調用父類的構造方法,則會在子類構造方法中隱式的調用父類的無參構造方法。如果顯式的調用構造,則必須在子類的構造方法第一行使用super()。如果子類構造方法中既沒有顯式調用父類的構造方法,而父類又沒有無參構造方法(當沒有構造方法被定義時,系統會自動幫助添加無參構造方法,這一點不變。這裏指的是該類存在有參構造方法,則系統不會幫助添加無參構造方法),則編譯報錯。

    (4).子類可以對繼承到的父類方法進行重寫,並且在調用方法時會優先調用子類方法(畢竟重寫,要求返回類型,方法名稱,參數類型及個數均與繼承父類的方法相同)。

    (5).繼承的初始化順序是先初始化父類再初始化子類。另外成員變量的初始化時先執行的,如果構造方法中也有對該成員變量的初始化,會因爲後執行構造方法的初始化的原因而改掉原來的成員變量的預設值。

    (6).在使用繼承時常常會用到final關鍵字,用final關鍵字修飾的類,方法,成員變量會不允許被繼承或重寫。如final修飾類,則該類不允許被繼承(與abstract不能同時使用,抽象類的作用就是被繼承類實現,與final作用正相反);final修飾方法,則該方法不允許被重寫(但是可以被重載);final修飾成員變量,則不能在其它位置修改值,包括構造方法中也包括在其它函數中(若不在外部對成員進行初始化也不在構造方法中進行初始化,則程序報錯);final修飾變量,則該變量的值只能被賦一次值,即爲常量。

    (7).在實例化子類對象前會先調用父類構造方法,再調用子類構造方法。

    (8).可以通過向上轉型或向下轉型完成父子類的類型轉換。向上轉型是將父類的對象引用指向new操作符創建的子類對象。雖然該對象時通過子類對象向上轉型獲得的,但是他的類型是父類類型,所以還是不能使用子類對象的方法。向下轉型可以通過強制轉型的方式完成,但是不一定成功。若是直接進行向下轉型,則會因爲在子類中可能定義了父類所沒有的特有方法而無法轉換成功;若是先向上轉型而後向下轉型,則會因爲原本父類對象的引用從指向子類對象中的父類擁有的方法等重新指向整個的子類對象,所以可以成功。可以使用instanceof進行類型的判定,安全的完成類型轉換。

package com.day_6.excercise_1;

public class Animal {
	public Integer age = 2;
	public Animal() {
		System.out.println("執行父類構造方法");
		age = 10;
	}
	public void eat() {
		System.out.println("Animal eat");
	}
	public void drink() {
		System.out.println("Animal drink");
	}
}
package com.day_6.excercise_1;

public class Cat extends Animal {
	public Cat() {
		System.out.println("執行子類構造方法");
	}
	public void eat() {
		System.out.println("Cat eat");
	}
	public void play() {
		System.out.println("Cat play");
	}
}
package com.day_6.excercise_1;

public class TryExtends {
	public static void main(String[] args) {
		
		Animal animalUp = new Cat();
		// 向上轉型成功
		animalUp.drink();
		// 向上轉型成功,但是因爲子類對象重寫了父類對象的方法,即父類引用指向子類對象,所以出現的是Cat eat
		animalUp.eat();
		// 向上轉型成功,不能使用子類特有的方法
//		animalUp.play();
		// 先向上轉型而後向下轉型成功
		((Cat) animalUp).play();
		// 向下轉型失敗
//		Animal animalDown = new Animal();
//		Cat cat = (Cat) animalDown;
//		cat.eat();
	}
}

    6.什麼是多態?

    多態指的是父類方法或屬性可以被子類重寫,這樣使得一個方法或屬性在父類及其不同的子類中可以擁有不同的含義。也就會導致同一個動作(方法)在不同對象使用時,會有不同的執行過程或執行結果。多態的使用,同樣是降低了代碼的耦合度,並且提高了代碼的可拓展性。多態可以從具體實現的角度分爲編譯時多態(方法重載)和運行時多態(動態綁定)。編譯時多態體現在可以通過參數類型,個數,次序來執行方法,也就是重載。運行時多態體現在會在執行一個方法時,順延着繼承鏈去尋找匹配的方法執行,基於實際的對象類型進行方法使用,而不是基於聲明的對象引用類型進行方法使用,也就是動態綁定(出現就對比一下,再說一遍靜態綁定,靜態綁定在編譯時就已經完成了綁定,雖然可以重寫,但是在調用時仍會使用原本的對象的靜態方法)。在上述的類中加入了一個靜態die方法,並且被子類Cat類重寫,而後由於向上轉型,本來應該輸出的是“Cat die”的子類重寫的方法,但是由於靜態綁定已經完成,所以只能輸出“Animal die”的父類方法。當然多態也是有前提條件的,首先必須有繼承關係,其次必須涉及子類到父類的類型轉換(還可以加上子類必須重寫了父類的方法)。

	// Animal類
	public static void die() {
		System.out.println("Animal die");
	}
	// Cat類
	public static void die() {
		System.out.println("Cat die");
	}
	// TryExtends類main函數
	public static void main(String[] args) {
		
		Animal animalUp = new Cat();
		// 向上轉型成功
		...
		// 靜態綁定
		animalUp.die();
		...
	}

    

    7.什麼是抽象類?

    抽象類是僅需要包含方法定義而不需要具體實現的類。也就是說我們可能會在規約子類必須實現某些方法時使用,或者根據衆多含有相同特徵的類抽象出一個抽象類,從而以這個抽象類爲模板進行其他子類的設計,實際上就是約束子類的方法實現必須擁有哪些,但是又不去具體約束該方法的實現內容。而抽象方法正是隻有聲明而不需要實現的方法。抽象類和抽象方法的關係是:包含抽象方法的類是抽象類,而抽象類裏不一定只包含抽象方法,可以包含普通方法,甚至可以沒有抽象方法。另外抽象類自己不能被實例化,需要創建一個指向該抽象類的子類的對象的引用來實例化。

package com.day_6.excercise_1;

public abstract class AbstractClass {
	public void getNormalFunc() {
		System.out.println("Normal Function");
	}
	public abstract void getAbstractFunc();
}
package com.day_6.excercise_1;

public class TryAbstract extends AbstractClass {
	@Override
	public void getAbstractFunc() {
		System.out.println("Abstract Function");
	}
	public static void main(String[] args) {
		TryAbstract tryAbstract = new TryAbstract();
		tryAbstract.getAbstractFunc();
		AbstractClass abstractClass = new TryAbstract();
		abstractClass.getNormalFunc();
	}
}

    下面探討幾個問題:

    abstract和static

    這裏要說明的是static修飾的是靜態方法(或其他),是編譯期間就已經完成的,但是abstract修飾的是要在確定對象引用的情況下,即通過類繼承等方式動態重寫方法,所以兩者衝突。

    abstract和final

    上面講過的,final修飾的類或方法等是不可再次進行更改的,而abstract修飾的類內方法在等待被重寫,所以兩者衝突。

    abstract和private

    private修飾的方法是類內可用的,繼承時不可訪問私有變量,而abstract修飾的目的就是要子類去繼承該抽象類並重寫對應的抽象類內未被實現的方法,所以兩者衝突。

    抽象類和接口

    抽象類中也可以用普通方法,但是接口中的所有方法都是必須是抽象的(接口中的方法的默認修飾符是public abstract,成員變量的默認修飾符是public static final)。所以相比較而言,抽象類可以有構造方法(雖然抽象類不能直接創建實例對象,但是實例化子類的時候就要先初始化父類,所以不管父類是不是抽象類都會調用其構造方法進行初始化),普通成員變量,普通方法,靜態方法,但是接口相對的,不能擁有構造方法,只能是常量,只能是抽象方法,不能包含靜態方法。並且抽象類還可以工通過子類間接實例化,但是接口不能實例化。所以兩者在構造上有相似(如都存在abstract關鍵字在類(接口)定義時,都可以包含抽象方法等)但是更多的是不同。

可否存在 構造方法 普通成員變量 普通方法 靜態方法
抽象類
接口 × × × ×

    所以接口的限制使其更多的用於在規範,接口並不關心類的內部實現,而只是規定了實現接口的類必須提供的某些方法,而抽象類更多使用於在代碼的抽取複用和個性化上。

    繼承和實現:

    之前在看到集合類的時候有一個存疑,我忘記了接口可以被繼承,今天來說一下這個問題。在接口中同樣定義着衆多需要傳遞的方法,並且這些方法都是抽象方法,那麼如果子類是非抽象類,則必須實現接口中的所有方法,但若子類同樣是抽象類或接口,則因爲也可以含有抽象方法而不必須實現該接口中的所有方法。另外類是單繼承,但是接口是允許多繼承的,並且可以多實現的,這也是JAVA的多繼承的一種實現方式。

    所以JAVA的三大特性在定義上最核心的就是希望實現代碼的複用的同時降低代碼間的耦合度,簡化外部調用的同時還便於調用者進行拓展。

    這一篇完成了之前的一個存疑點,之後的文章中會一一把之前的問題抽離出來進行解決。還是一樣,感謝各位在網絡上的分享,纔會讓我的複習的思路更加清晰,也能得到更好的答案去解決我的疑慮而後用自己理解的方式記錄下來,萬分感謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章