coreJava 繼承-抽象多態-反射

繼承(inheritance)

有一個用來判斷是否應該設計爲繼承關係的簡單規則,這就是“is-a”規則,它表明子類的每個對象也是超類的對象。“is-a“規則的另一種表述方式是置換法則。它表明程序中出現超類對象的任何地方都可以用子類對象置換。例如,可以將一個子類的對象賦給超類變量。

之類不能夠直接地訪問超類的私有域。(關鍵字super調用get方法獲取)

super不是一個對象的引用,不能將super賦給另一個對象變量,它只是一個指示編譯器調用超類方法的特殊關鍵字
關鍵字this有兩個用途:一是引用隱式參數,二是調用該類其他的構造器,同樣,super關鍵字也有兩個用途:一是調用超類的方法,二是調用超類的構造器。在調用構造器的時候,這兩個關鍵字的使用方式很相似。調用構造器的語句只能作爲另一個構造器的第一條語句出現。構造參數既可以傳遞給本類(this)的其他構造器,也可以傳遞給超類(super)的構造器

一個對象變量(例如,變量e)可以指示多種實際類型的現象被稱爲多態(參考文章最後部分)(polymorphism)。在運行時能夠自動地選擇調用哪個方法的現象稱爲動態綁定(dynamicbinding)
1:如果方法是 private方法,static方法,final方法,或者構造器 編譯器就可以準確知道應該調用那個方法,我們將這種調用方式成爲靜態綁定(staticbinding)  =》因爲statci和final方法不能被繼承,private聲明的方法和成員變量不能被子類繼承,所有的private方法都被隱式的指定爲final的(由此我們也可以知道:將方法聲明爲final類型的一是爲了防止方法被覆蓋,二是爲了有效的關閉java中的動態綁定)
2:如果方法不是上面的這4種情況,編譯器會進行動態綁定 public.當程序運行時,並且是採用動態綁定調用方法時,JVM會調用與X所引用對象的實際類型最合適的那個類方法,假設X類型是D類型,它是C的子類如果D類中定義了f(string)方法,則會直接調用,不會去C中查找  =》爲了避免每次調用方法都進行查找,虛擬機會預先爲每個類生成對應的方法表,方法表中記錄了所有方法的簽名和實際調用的方法

public class Person{
    int age=11;
    public void method(){
        System.out.println("父類方法,對象類型:"+this.getClass());//父類方法,對象類型:Man
    }
} 

public class Man extends Person{
    int age=22;
    public static void main(String[]args){
        Person sample=new Man();  //向上轉型
        sample.method();
        System.out.println(sample.age);//11,還是Person父類的屬性,屬相沒有虛擬調用
    }
}//聲明的是父類的引用(句柄),但準確的調用了子類的對象,調用method,在子類中沒有該方法,所以去父類中尋找到並調用之。

在處理Java類中的成員變量時,並不是採用運行時綁定,而是一般意義上的靜態綁定。所以在向上轉型的情況下,對象的方法可以“找到”子類,而對象的屬性還是父類的屬性(age)。注意class與age

動態綁定有一個非常重要的特性:無需對現存的代碼進行修改,就可以對程序進行擴展。

方法的名字和參數列表稱爲方法的簽名.例如,f(int)和f(String)是兩個具有相同名字,不同簽名的方法。(返回類型不是簽名的一部分從JavaSE5.0開始,允許子類將覆蓋方法的返回類型定義爲原返回類型的子類型)

如果一個方法沒有被覆蓋並且很短,編譯器就能夠對它進行優化處理,這個過程爲稱爲內聯(inlining)。內聯調用e.getName()將被替換爲訪問e.name域。這是一項很有意義的改進,這是由於CPU在處理調用方法的指令時,使用的分支轉移會擾亂預取指令的策略,所以,這被視爲不受歡迎的。然而,如果getName在另外一個類中被覆蓋,那麼編譯器就無法知道覆蓋的代碼將會做什麼操作,因此也就不能對它進行內聯處理了。

將一個子類的引用賦給一個超類變量,編譯器是允許的。但將一個超類的引用賦給一個子類變量,必須進行類型轉換if(obj instanceof Class ) 當 obj 爲 Class 的對象,或者是其直接或間接子類,或者是其接口的實現類,結果result 都返回 true,否則返回false(obj 是引用類型)
•只能在繼承層次內進行類型轉換。
•在將超類轉換成子類之前,應該使用instanceof進行檢查。
如果x爲null,進行下列測試 x instanceof C,不會產生異常,只是返回false。之所以這樣處理是因爲null沒有引用任何對象,當然也不會引用C類型的對象

抽象

包含一個或多個抽象方法的類本身必須被聲明爲抽象的,抽象類還可以包含具體數據和具體方法.類即使不含抽象方法,也可以將類聲明爲抽象類.抽象類不能被實例化.需要注意,可以定義一個抽象類的對象變量,但是它只能引用非抽象子類的對象。例如:Personp=newStudent("VineeVu","Economics")

1)僅對本類可見private。
2)對所有類可見public:
3)對本包和所有子類可見protected。
4)對本包可見—默認(很遺憾,)不需要修飾符。

Object

Object類是Java中所有類的始祖,在Java中,只有基本類型(primitivetypes)不是對象,例如,數值、字符和布爾類型的值都不是對象.

Object類中的equals方法用於檢測一個對象是否等於另外一個對象。在Object類中,這個方法將判斷兩個對象是否具有相同的引用.使用Objects.equals方法。如果兩個參數都爲null,Objects.equals(a,b)調用將返回true;如果其中一個參數爲null,則返回false;否則,如果兩個參數都不爲null,則調用a.equals(b).

在子類中定義equals方法時,首先調用超類的equals。如果檢測失敗,對象就不可能相等。如果超類中的域都相等,就需要比較子類中的實例域

Java語言規範要求equals方法具有下面的特性:
1)自反性:對於任何非空引用x,x.equals(x)應該返回true,
2)對稱性:對於任何引用x和y,當且僅當y.equals(x)返回true,x.equals(y)也應該返回true。
3)傳遞性:對於任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也應該返回true。
4)一致性:如果x和y引用的對象沒有發生變化,反覆調用x.equals(y)應該返回同樣的結果。
5)對於任意非空引用x,x.equals(null)應該返回false,爲空時是true

== 運算符用於比較基本類型的值是否相同,或者比較兩個對象的引用是否相等
在 Object 類中,== 運算符和 equals 方法是等價的,都是比較兩個對象的引用是否相等(多數情況下還是需要重寫equals方法)

1.如果子類能夠擁有自己的相等概念,則對稱性需求將父類強制採用 getClass 進行檢測
2.如果由超類決定相等的概念,那麼就可以使用 imtanceof進行檢測, 這樣可以在不同子類的對象之間進行相等的比較。原因如下:https://cloud.tencent.com/developer/article/1079384 

Person p = new Person("Tom",22);
Man m = new Man("Tom",22,"男");
System.out.println(p.equals(m));//true
System.out.println(m.equals(p));//false

person.equals(man)得到的結果是 true,而man.equals(person)得到的結果卻是false,這顯然是不正確的。問題出現在 instanceof 關鍵字上Man 是 Person 的子類,person instanceof Man 結果當然是false。這違反了我們上面說的對稱性。實際上用 instanceof 關鍵字是做不到對稱性的要求的。這裏推薦做法是用 getClass()方法取代 instanceof 運算符。getClass() 關鍵字也是 Object 類中的一個方法,作用是返回一個對象的運行時類.

打印結果 person.equals(man)得到的結果是 false,man.equals(person)得到的結果也是false,滿足對稱性

如果使用 getClass 實現equals方法的重寫,那麼就不能在兩個不同子類的對象進行相等的比較,所以什麼時候使用 instanceof 運算符,什麼時候使用 getClass() 有如下建議:
  ①、如果子類能夠擁有自己的相等概念,則對稱性需求將強制採用 getClass 進行檢測。
  ②、如果有超類決定相等的概念,那麼就可以使用 instanceof 進行檢測,這樣可以在不同的子類的對象之間進行相等的比較。

下面給出一個完美的equals方法的建議:
  1、顯示參數命名爲 otherObject,稍後會將它轉換成另一個叫做 other 的變量。
  2、判斷比較的兩個對象引用是否相等,如果引用相等那麼表示是同一個對象,那麼當然相等
  3、如果 otherObject 爲 null,直接返回false,表示不相等
  4、比較 this 和 otherObject 是否是同一個類:如果 equals 的語義在每個子類中有所改變,就使用 getClass 檢測;如果所有的子類都有統一的定義,那麼使用 instanceof 檢測
  5、將 otherObject 轉換成對應的類類型變量:ClassName other = (ClassName) otherObject
  6、最後對對象的屬性(實例域)進行比較。使用 == 比較基本類型,使用 equals 比較對象。如果都相等則返回true,否則返回false。
注意如果是在子類中定義equals,則要包含 super.equals(other)

hashCode方法

散列碼(hashcode)是由對象導出的一個整型值。hashCode方法定義在Object類中,因此每個對象都有一個默認的散列碼,其值爲對象的存儲地址。兩個相等的對象要求返回相等的散列碼。

字符串s與t擁有相同的散列碼,這是因爲字符串的散列碼是由內容導出的。而字符串緩衝sb與tb卻有着不同的散列碼,這是因爲在StringBuffer類中沒有定義hashCode方法,它的散列碼是由Object類的默認hashCode方法導出的對象存儲地址.
public int hashCoded
{
return Objects.hash(name, salary, hireDay);
}
Equals 與 hashCode 的定義必須一致:如果 x.equals(y) 返回 true, 那麼 x.hashCode( ) 就必須與 y.hashCode( ) 具有相同的值
hashCode方法應該返回一個整型數值(也可以是負數,)併合理地組合實例域的散列碼,以便能夠讓各個不同的對象產生的散列碼更加均勻.請注意,無論何時重寫equals方法,通常都必須重寫hashCode方法,以維護hashCode方法的一般約定,該方法聲明相等對象必須具有相同的哈希代碼。以便用戶可以將對象插人到散列表中

兩個對象相等,其 hashCode 一定相同;
兩個對象不相等,其 hashCode 有可能相同;
hashCode 相同的兩個對象,不一定相等;
hashCode 不相同的兩個對象,一定不相等;

toString方法
它用於返回表示對象值的字符串。
絕大多數(但不是全部)的toString方法都遵循這樣的格式:類的名字,隨後是一對方括號括起來的域值.Object類定義了toString方法,用來打印輸出對象所屬的類名和散列碼。
ps:一個nativeMethod就是一個Java調用非Java代碼的接口

ArrayList<Employee>staff=newArrayList<Eniployee>0;兩邊都使用類型參數Employee,這有些繁瑣。JavaSE7中,可以省去右邊的類型參數:ArrayList<Employee>staff=newArrayListo();

staff.ensureCapacity(lOO);.一旦能夠確認數組列表的大小不再發生變化,就可以調用trimToSize方法(將數組列表的存儲容量削減到當前尺寸)。這個方法將存儲區域的大小調整爲當前元素數量所需要的存儲空間數目。垃圾回收器將回收多餘的存儲空間

數組列表自動擴展容量的便利增加了訪問元素語法的複雜程度

使用toArray方法將數組元素拷貝到一個數組中。
X[] a=new X[list.size()]; list.toArray(a);

對數組實施插人和刪除元素的操作效率比較低。

對象包裝器與自動裝箱

Integer、Long、Float、Double、Short、Byte、Character、Void和Boolean(前6個類派生於公共的超類Number)。對象包裝器類是不可變的,即一旦構造了包裝器,就不允許更改包裝在其中的值。同時,對象包裝器類還是final,因此不能定義它們的子類

枚舉類
enum和class、interface的地位一樣,使用enum定義的枚舉類默認繼承了java.lang.Enum,而不是繼承Object類。枚舉類可以實現一個或多個接口。枚舉類的所有實例都必須放在第一行展示,不需使用new關鍵字,不需顯式調用構造器。自動添加publicstaticfinal修飾。
使用enum定義、非抽象的枚舉類默認使用final修飾,不可以被繼承。枚舉類的構造器只能是私有的

Throwable是Exception類的超類

反射

能夠分析類能力的程序稱爲反射(reflective)。
•在運行時分析類的能力。
•在運行時查看對象,例如,編寫一個toString方法供所有類使用。
•實現通用的數組操作代碼。
•利用Method對象,這個對象很C++像中的函數指針。

Strings="java.util.Random";//調用靜態方法forName獲得類名對應的Class對象。
Objectm=Class.forName(s).newlnstance();

一個Class對象實際上表示的是一個類型,而這個類型未必一定是一種類。例如,int不是類,但int.class是一個Class類型的對象。

虛擬機爲每個類型管理一個Class對象。因此,可以利用==運算符實現兩個類對象比較的操作

在java.lang.reflect包中有三個類Field、Method和Constructor分別用於描述類的域、方法和構造器。
Class類中的getFields、getMethods和getConstructors方法將分別返回類提供的public域、方法和構造器數組,其中包括超類的公有成員。
Class類的getDeclareFields、getDeclareMethods和getDeclaredConstructors方法將分別返回類中聲明的全部域、方法和構造器,其中包括私有和受保護成員,但不包括超類的成員

在java.lang.reflect包中有三個類Field、Method和Constructor分別用於描述類的域、方法和構造器。
這三個類都有一個叫做getName的方法,用來返回項目的名稱。Field類有一個getType方法,用來返回描述域所屬類型的Class對象。
Method和Constructor類有能夠報告參數類型的方法,Method類還有一個可以報告返回類型的方法。
這3個類還有一個叫做getModifiers的方法,它將返回一個整型數值,用不同的位開關描述public和static這樣的修飾符使用狀況。另外,還可以利用java.lang.refleCt包中的Modifier類的靜態方法分析getModifiers返回的整型數值。例如,可以使用Modifier類中的isPublic、isPrivate或isFinal判斷方法或構造器是否是public、private或final。我們需要做的全部工作就是調用Modifier類的相應方法,並對返回的整型數值進行分析,另外,還可以利用Modifier.toString方法將修飾符打印出來。Classcl=Class.forName(name);Stringmodifiers=Modifier.toString(cl.getModifiers());//public**1

查看對象域的關鍵方法是Field類中的get方法。如果f是一個Field類型的對象(例如,通過getDeclaredFields得到的對象)obj是某個包含f域的類的對象,f.get(obj)將返回一個對象,其值爲obj域的當前值。
Employeeharry=newEmployee("HarryHacker",35000,10,1,1989);
Classcl=harry.getClass0;
//theclassobjectrepresentingEmployee
Fieldf=cl.getDeclaredFieldC'name"):
//thenamefieldoftheEmployeeclass
Objectv=f.get(harry);
//thevalueofthenamefieldoftheharryobject,i.e.,theStringobject"HarryHacker"
實際上,這段代碼存在一個問題。由於name是一個私有域,所以get方法將會拋出一個IllegalAccessException。只有利用get方法才能得到可訪問域的值。除非擁有訪問權限,否則Java安全機制只允許査看任意對象有哪些域,而不允許讀取它們的值。
反射機制的默認行爲受限於Java的訪問控制。然而,如果一個Java程序沒有受到安全管理器的控制,就可以覆蓋訪問控制。爲了達到這個目的,需要調用Field、Method或Constructor對象的setAccessiblef.setAtcessible(true);//nowOKtocallf.get(harry);setAccessible方法是AccessibleObject類中的一個方法,它是Field、Method和Constructor類的公共超類。這個特性是爲調試、持久存儲和相似機制提供的。

調用f.set(obj,value)可以將obj對象的f域設置成新值

Java數組會記住每個元素的類型,即創建數組時new表達式中使用的元素類型。將一個Employee[]臨時地轉換成Object[]數組,然後再把它轉換回來是可以的,但一從開始就是Objectt的數組卻永遠不能轉換成Employe數組。

在Method類中有一個invoke方法,它允許調用包裝在當前Method對象中的方法
Object invoke(Object obj,Object...args)第一個參數是隱式參數,其餘的對象提供了顯式參數(在JavaSE5.0以前的版本中,必須傳遞一個對象數組,如果沒有顯式參數就傳遞一個null)。對於靜態方法,第一個參數可以被忽略,即可以將它設置爲null.假設用ml代表Employee類的getName方法,下面這條語句顯示瞭如何調用這個方法:String n=(String)ml.invoke(harry);

invoke的參數和返回值必須是Object類型的public Object invoke(Object implicitParameter,Object[] explicitParamenters)調用這個對象所描述的方法,傳遞給定參數,並返回方法的返回值。對於靜態方法,把null作爲隱式參數傳遞。在使用包裝器傳遞基本類型的值時,基本類型的返回值必須是未包裝的

多態

強烈建議文章:https://www.cnblogs.com/chenssy/p/3372798.html
父類類型的引用可以調用父類中定義的所有屬性和方法,對於只存在與子類中的方法和屬性它就望塵莫及了(因爲向上轉型後會丟失該方法)

多態總結如下:
指向子類的父類引用由於向上轉型了,它只能訪問父類中擁有的方法和屬性,而對於子類中存在而父類中不存在的方法,該引用是不能使用的,儘管是重載該方法。若子類重寫了父類中的某些方法,在調用該些方法的時候,必定是使用子類中定義的這些方法(動態連接、動態調用)

在繼承鏈中對象方法的調用存在一個優先級:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)

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