您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關Java中對象的本質是什么,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
在學習編程語言中,除非是最底層的 0101001 這些東西,一定離不開類型(type)。什么是類型?類型就是“抽象的是什么”。
“抽象的是什么”這句話,其實很容易理解,我們學習基礎數據類型時必會提到每種數據類型都占據多少多少個字節,而且他們都是有范圍限制的,比如 byte 數據類型是8位、有符號的,以二進制補碼表示的整數——這其實就是通過給二進制數字人為的賦予定義來獲得一個固定的表示內容來達到的,這個過程就是抽象:本來只是毫無意義的 0101001 這種東西,通過抽象,它能夠變成任意我們希望的東西。
Java 中八大基本數據類型就是建立在多層抽象上的,而面向對象,大家都說“萬物皆對象”,這句話的意思應該是“萬物皆可被抽象后描述,而不僅僅限于一些基本的數據類型”,所以在 Java 中定義一個 class 時,我們給 class 賦予類型屬性和行為方法,通過這兩者抽象后形成的類(class)就是類型(type)的意思,他們幾乎可以等同看待。type 所抽象的東西還比較基本,所以說是基本數據類型。而 class 則全部都是對于外界事物的描述,對象則是某個類中的個體,每個個體都擁有相同的特征,所以他們屬于同一類(class)。所以類只是個概念,而對象才是一個個活蹦亂跳的可以使用操作的對象。
創建一個類,就是創建新的數據類型。
舉個例子暢想一下。int 是 Java 本身自帶的基本數據類型,它的描述是:32位、有符號的以二進制補碼表示的整數,我們可以獲得一堆的整數,它的類型是確定的,行為(運算)也被限定,帶有正負等。Java 中也定義了一個 Integer 的 class,這個 Integer 跟 int 有什么關聯嗎?當然,它被設計來對應于基本數據類型 int,并且定義了很多操作方法。所以,如果忽略 int 類型的存在的話,Integer 類完全就是一種數據類型,而且還是一種“升級版”的。
封裝性很容易理解,就是把一堆東西用大括號包起來。封裝性作為面向對象的三大特性之一,深層意義絕不僅限于這個淺顯的概念。
上面說到類與類型,類型就是“抽象的是什么”。類呢?就是“對萬物的抽象后封裝其描述”。所以我在上面總結說:創建一個類,就是創建新的數據類型。
封裝了什么描述呢?每個類中的成員變量都是對這個類抽象后的描述。
比方說,我要創建一個類:人,那么我覺得人應該要有名字、性別。如果實際情況不需要知道或用到其他的屬性,我就不會創建其他的成員變量來描述“人”這個類,而只有兩個成員變量。抽象就是:我只描述我想要的。
抽象了一個類(型)之后,在 Java 里就是用花括號包起來,就封裝成了一個真正的類(型概念)。
封裝性是面向對象的最基本的特性。
同上面一樣,繼承的概念我也不想多說,只是說一些我認為起到點睛效果的點。
在 Java 里的繼承不是真正意義上的或者純概念上的繼承,它是通過讓派生類獲得基類的一個子對象來獲得看起來是繼承的樣子(效果)的。這個子對象等同于我們手動 new 一個對象,這就是為什么我們在寫每一個派生類的任意構造函數時都需要確保能夠調用到基類的任一個構造函數且只能調用一個構造函數的原因。
——這句話有點拗口,但是不難理解,這也是考察Java基礎時常考到的知識點——給出幾個類你,他們之間有繼承關系,問你編譯運行后的輸出結果是什么,通常結果都是發生編譯時異常,因為代碼中往往會通過很隱秘的方法讓派生類最終并不能調用到基類的構造函數,這樣的結果就是派生類沒辦法生成并獲取基類的子對象,那么繼承所必需的代碼就不完整,自然就在編譯的時候就發生異常了。
理解這一點之后發現,所謂的繼承其實可以通過手動的方式完成幾乎相同(并不完全相同)的效果,你肯定猜到了,那就是直接 new 另一個類的對象,讓它成為自己的類的成員變量——你一定經常這樣做。這樣的結果就是,你獲得那個類的全部訪問權限允許的屬性和方法。
常常有人出這樣的題目來嚇唬人,就是有繼承關系的兩個類,基類有個私有變量a(String型),派生類能不能使用基類的a變量?答案肯定是不能的,系統提示沒有訪問權限。如果是真的概念上的繼承的話,派生類應該獲得基類的所有元素才對啊,為什么說沒有權限呢?原因也是上述的:這僅僅是通過new一個類的對象來訪問而已,自然是不能直接操作對象的聲明為私有的任何東西的。
真正的繼承,當然就是說所有東西都是屬于派生類才對的。
假設這時候子類也有一個私有變量a(String型)。能不能訪問到呢?這兩個變量是什么關系呢?這時候變成可以訪問了,因為他們都是分別屬于兩個類的成員變量,互相獨立,他們之間沒有任何的關系,這時候其實就是訪問自己的私有變量,當然沒有問題了。
所以說手動的、直接 new 一個對象,也是一種“繼承”,這種方式反而更加靈活方便,它有個專門的名詞叫做“組合”。組合與繼承之間糾纏著的愛恨情仇大抵如上。同時,在寫代碼的時候,通常是優先使用組合而不是繼承。
那么什么時候使用繼承呢?繼承雖然相比組合來說比較笨重不靈活(比如不能多繼承可以多組合等),但是繼承的魅力還是不小的,比如多態等。所以當你覺得派生類有向上(基類)轉型的必要時,使用繼承。
多態依賴于繼承,組合是沒辦法完成多態的,這就是優缺點——有得必有失,看你的取舍。
多態的概念也不多說,因為這文章并不是知識普及用的,是思想提升用的,如果連相關知識點都沒有掌握的話,估計你也不會看到這里了。
多態有個類型轉換的問題:向上轉型和向下轉型。向上轉型是永遠不會出錯的,同時意味著派生類丟失基類并沒有的部分信息。而向下轉型原則上是不允許的或者是不建議的,隨意的、直接的向下轉型,在編譯器就會報錯。如果確實有需要,則需要強制轉型:所以在 Java 里所有的向下轉型都必須顯式聲明,當顯式聲明時,就意味著你已經了解這種風險并愿意承擔其帶來的問題等。
這里有個問題:為什么向上轉型是安全的,向下轉型則是危險的?如果你把它當作一個知識點去學習并記住了,那么你為什么不會好奇其背后的緣由呢?這里就想回答這個背后的原因。
上面說繼承的時候說過,Java 中的繼承并不是我們概念上所理解的真正的繼承,如果子類繼承了父類,那么當你 new 了一個子類對象時,其實在 Java 的底層會同時幫你 new 一個父類的對象作為子類的子對象,這時候在Java里想要向上轉型時,通過這個父類的子對象很容易就知道了這個繼承關系,轉型自然是安全的。
而如果 new 了的是父對象,再向下轉型成子對象時,這樣在編譯期就會發生異常——Java 里是不允許這樣的隨意向下轉型的行為的。所以你需要在父對象的前面顯式聲明說:我要強制轉型。編譯器才能通過編譯。
但是,在上面這種情況,即使你在轉換的時候已經在前面的小括號里聲明了類型來強制轉型,讓自己的代碼在編譯時能夠通過了,運行時還是會出現異常的:ClassCastException,因為這時候JVM發現這個父對象根本沒有任何強轉類型(子類)的信息。為了避免這種運行時異常通常需要去確認一下是不是同一個類型:instanceof 判斷通過后再進行轉換,就能確保代碼不會出現異常。
有一種向下轉型不會出現運行時異常 ClassCastException 的情況,那就是一開始 new 的是子類的對象,然后賦值給一個父類的引用,然后再將這個父類引用的對象強制轉型為子類對象,這時候的強轉是成功的——這背后的原因也很容易理解:new 創建的所有對象都是存放在堆內存區的,而引用則存放在棧內存區,它保存的只是堆內存中那個對象的開始地址而已。因為一開始 new 的就是子類的對象,所以這個對象是不僅擁有父類的子對象,而且擁有自身的對象的。這時候它不管是向上轉型還是向下強制轉型,都是不會有問題的。
下面是一個相似的例子:
第25行代碼會在運行時拋出ClassCastException,java.lang.Integer cannot be cast to java.lang.Double。這兩個類都繼承自Number抽象類,所以他們的向上轉型都不會有任何問題并且不需要強轉,但是向上轉型后再向下轉型時,拋出的異常依然能識別到創建對象時的類,強轉是失敗的。
這就是為什么非要做類型判斷后才能進行強轉,所以注釋掉的28到33行代碼才是正確的做法。
同時,在Java里有個 Class 類,JVM 每加載一個類,Class 類都會新建一個對象,用于記錄它的類型信息,Java 的反射機制也是基于它。
以上就是Java中對象的本質是什么,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。