java基本思想


面向對象

眾所周知,面向對象編程思想是java的基本思想。java的整個體系和技術實現都是以這個思想為基礎。(這個通過類和接口能看出來,后面提到)

對這個事情的認知度甚至變成了很多公司的面試標准。比如有的公司會問你什么是面向對象、面向對象和面向過程的區別、面向對象有哪些特性。

不過細心的人會發現,沒有公司會問你,你是怎么將面向對象思想應用到實際工作中的。這是因為:
其一,大多數人了解面向對象的思想是為了應付面試;
其二,確實很少有人會在實際工作中使用面向對象的思想。
第一點是因為教育機構(大學、培訓機構)的老師大都是從c也就是面向過程的語言走過來的。他們的思想已經習慣了使用面向過程編程。然后老師們就使用這個思想去教導學生,這就形成了新一代年輕的程序員也會習慣面向過程的思想去思考問題。師徒制也存在這個問題。如此往復循環,就形成了第二點。而如此積累,當工作量逐漸加大的時候就會出現程序架構設計不合理、數據庫設計不方便、不想寫注釋、不知道些什么注釋等情況。然后就變成了“卧槽這代碼是我寫的么???”。這個時候再遇上個需求變更,心態估計就崩了。

在這里感慨一下,真正將面向對象的思想落地,才是高級編程語言程序員的出路。(有興趣的可以了解一下領域模型驅動,就是常說的DDD)

對於面向對象思想的個人理解:

面向對象思想,是一種將現實世界抽象成代碼的思想。
即通過將現實世界中獨立個體(事物)的屬性和行為封裝在一個類中,通過抽象和繼承實現個體間的相互影響、協作。形成了對象多態和行為多態的特性。
話說得稍微有點抽象了。簡單來說就是把實際轉換成代碼,代碼之間相互作用形成了代碼間的相互協調,表現了實際中一個事物的多種形態。這種思想叫做面向對象的思想。

與面向過程的區別

面向過程就是分析出解決問題所需要的步驟,然后用函數把這些步驟一步一步實現,使用的時候一個一個依次調用就可以了;面向對象是把構成問題事物分解成各個對象,建立對象的目的不是為了完成一個步驟,而是為了描敘某個事物在整個解決問題的步驟中的行為。
所以,面向對象相較於面向過程來說更具有擁抱變化的能力。
說到這里,忍不住跟看這篇文章的人推薦一部電影:《模仿游戲》。主演是鼎鼎大名的卷福。故事情節大致是主角為了破譯德軍的密碼建造了一個機器。聚焦於機器的建造過程來看,機器使用了窮舉法來實驗密碼的正確性,設計這個機器就是為了破解這個密碼,這樣來看是一個面向過程的設計思想。聚焦於整部電影來看,主角建造這個機器有一部分原因是為了紀念兒時的玩伴,甚至還給機器取了玩伴的名字。當需要破譯密碼的時候就把工作交給這個機器,機器計算完了之后會返回結果。主角在內心里把這個機器當做了一個人,在思想上將這個人抽象成了機器的實現邏輯。這樣來看就是面向對象的思想了。這個機器也是人工智能的起源——圖靈機。

咳咳,有點扯遠了。不過我確實推薦朋友們找一找自己專業相關的電影看看,能極大程度提高對專業的興趣和積極性。

分割線1,其實這里也沒有特別長,但是下面主要討論技術和思想了,給上面的一些閑扯做個分隔。
封裝

正如上面所說,java體現面向對象思想的一個基本點就是封裝。這個動作的設計初衷就是將現實世界中的事物描述成代碼。所以設計了一個容器叫做類,類中可以放描述事物的屬性(成員變量)和描述事物的行為(成員方法)。
封裝完類之后將一些類分門別類的放在對應的包中,然后整合成了一個項目。如此一來,通過封裝這個動作就把一系列功能封裝成了對象。

接口(抽象)

單純從封裝這個動作來看,將實際事物封裝為類的思想過程就是抽象。而通過抽象,可以把一些類中的共同點匯集到一個類中復用。那么將不同實現相同名字的變量和方法抽象到一個不用寫邏輯實現的類中,這個類就叫做接口。這個接口就是最高層次的抽象了。

那么從實際工作來看,可以從兩個格局理解接口。
一個是系統級的戰略格局,一個是代碼級的戰術格局。

從戰略格局來看,系統之間的相互交互需要制定規則和標准;制定了統一的規則和標准之后,大家按照這個標准去開發。這樣可以做到多個協調系統之間的並行開發,以及系統之間的耦合性降低。
戰略上,接口定義了系統之間相互協調通訊的方式和入口。(從單個系統看其他的系統,接口也描述了其他系統的功能)

從戰術格局來看,類可以描述實際事物的具體屬性和行為。也就是說類可以代表一個具體事物,但是沒有辦法表示事物可以實現的功能。
舉個例子說明:dog類可以描述狗。那么二哈和拉布拉多屬於狗的品種,是狗的一種屬性,所應該作為dog類中的一個屬性(成員變量)。而狗可以啃骨頭,可以定義成dog類中的一個成員方法。
但是拉布拉多可以做導盲犬,二哈可以做雪橇犬。當然你可以把這個當做狗的職業封裝為一個屬性,但是顯然是不合適的。因為,導盲犬和雪橇犬可以完成的動作沒有幾個一樣的。動作需要定義成方法。所以這個時候需要一個新的類來定義這兩個職業,而不能封裝為一個屬性。
可是,通過繼承的方式實現由有了單根性的限制(一個類只能繼承一個類)。對於代碼描述事物的功能就很麻煩了。於是java就使用接口解決了這個問題。
所以從戰術格局來看,接口描述的是對象的功能(類描述的事對象的屬性,這個后面說)。

抽象類(抽象和細節)

抽象類其實是類到接口中間的一個過度產物。

從代碼上來看,抽象類既可以定義復用的方法邏輯還可以定義需要子類實現的抽象方法。
所以說,抽象類是在類設計之后才抽象出來的。
對比接口,可以說接口是設計出來的結果而抽象類是重構出來的結果。兩者方向不一樣。是不同過程的產物。

實現類(細節)

類是描述實際事物的基本單位,而這里要說的實現類,則是特指實現了接口的類。
這種類雖然在本質上跟普通的類是一樣的,但是意義不同。
實現類是用來實現接口的邏輯的,他有接口的規則限定。而普通的類則沒有這么明確的限定。
所以實現類的命名一般是接口名+Impl組成;而普通類根據實際要代表的事物命名即可。

這里小總結一下,接口是抽象的規則,實現類是具體的細節。兩者缺其一,那么這個系統從設計上來講是不完整的。接口是編寫代碼之前設計出來的;其目的是制定規則,以便可以進行並行開發和擁抱變化。而抽象類,是抽象和細節的混合物,是在開發階段重構出來的結果,目的是復用代碼。

繼承

繼承是面向對象中思想中的一個概念,其實基類拓展超類實現在一個類中可以直接調用另一個類的方法是通過拓展指針實現的(extends)。不過從實現效果上來看,跟領域模型中的父子非常相像。所以就把基類稱為子類,超類稱為父類了。

extends

繼承的技術實現就是利用extends關鍵字,其實他就是個指針而已。(這里不叫引用,因為它確實和java中的引用不太一樣,所以加以區別、便於理解)
先來看一段代碼:

public class A{}
public class B extends A{}

大家都知道B類繼承了A類所以B中可以使用A中的構造方法和成員(變量和方法)。
這是因為當編譯器檢測到B類后面的extends關鍵字時,會把其后的類名(也就是A)的地址(類的完全限定名)編譯在B類的class源碼中。

之后運行的時候,類加載器會在加載B類的時候將A類的信息同時加載到方法區中;
而java默認調用子類構造方法的時候會先調用父類的構造方法。
那么這個時候通過B類實例化出來的對象自然就能調用A類的方法了。

所以,這樣來看。extends關鍵字其實就是一個指針變量,其值為class定義時關鍵字后面的類名,也就是拓展類的地址。

extends翻譯過來就是拓展的意思。所以B extends A。B就能調用A中的成員。反過來A並沒有拓展B ,所以A就不能調用B中的成員。這就形成了子類可以使用父類的成員,而父類不能使用子類的。
而且為了保證程序的設計順序性和可讀性,java限制了兩個類互相拓展。就是說當B extends A時,A就不能再拓展B了。再加上拓展的單根行,效果就變成了一個父類可以有多個子類,而一個子類只能有一個父類。這樣如果把成員看做財產的話;效果就跟領域模型(現實社會)中的父子非常相像了。所以基類和超類也被稱為子類和父類,而拓展也被稱作繼承。

implments

接口的設計一是為了打破java繼承的單根性,二是為了跟接口代表的含義更加貼切。
上面說到繼承是單根性的。這樣就造成了父類只能是通過代碼抽象出來的復用代碼類,很難從設計階段定義;而且確實有了多繼承的需求。(接口代表了功能,一個對象可以同時有多個功能)
所以java設計了接口來代表設計模型中的功能和系統間的規則。

那么接口中的方法都是沒有具體實現的,所以需要一個類來實現這個接口中的規則。那么當一個類跟一個接口關聯起來的時候,自然也就稱作這個類實現了這個接口。

接口之間是可以相互繼承的就像大規則包含小規則;接口和類之間是實現的,因為接口定義的規則最終要通過類來定義運行邏輯。

this

this關鍵字跟extends其實也是一個指針變量,但是this的用法跟c中的宏定義非常相像;所以要跟extends關鍵字加以區分。

this() 調用當前對象的構造方法
this.成員(變量或方法) 調用當前對象的成員(變量或方法)

發生上面兩種情況的調用時,其實可以直接想象成new一個本類的對象賦值給引用a。然后把this換成a,就行了。所說的this代表當前調用的對象,就是這個意思。

super

由於類的構造方法執行順序是: 父類構造方法–>本類成員變量初始化–>本類構造方法。

這樣就還需要一個指向父類對象的指針。所以就設計了super關鍵字。用法和this關鍵字一樣,只是super指向了父類。

super() 調用父類對象的構造方法
super.成員(變量或方法) 調用父類對象的成員(變量或方法)

這里要說一下extends 、this、super的區別:
三者均可以看成是指針變量,但是extends存放的是類的地址;
this和super存放的是對象的地址。
換句話說extends是在編譯期工作的;而this和extends是在程序運行時工作的。
這樣記憶就不容易混淆了。

多態

多態是從面向對象思想和設計的角度來說的。
當我們設計了一個類時,在另一個類中調用這個類的對象,但是表現的好像是這個類的父類;這種情況就是向上造型。
有些時候我們需要一個類的子類,但是持有的卻是其父類的引用,這個是我們需要做一個強制類型轉換來得到我們需要的子類。這個操作就是所謂的向下造型。
上下造型在面向對象編程時非常實用。當這種方式在一個系統中頻繁使用的時候,表現出來的效果經常是一個對象扮演多種角色,還可以相互轉換。這種現象被人們稱為多態。

對象多態(類型轉換、上下造型)

對象多態是多態中的一種主要表現形式。發生在對象之間,也就是說對象多態的基本單位是對象,不可向下細分。

這里先說一下實現原理,還是比較巧妙的。

 


我們來看,main方法第一行第二行不用解釋,就是創建了兩個對象 (ac表示 aClass bc同理)。
然后會發生之前說extends關鍵字時說過的現象:bc可以調用A類和本類的成員,也就是變量a和變量b。但是ac卻只能調用A類的成員。原因在extends那里說過了這里就不在詳細說明。

第三行就是我們常說的向上造型。原理大概是這樣的:
編譯器從左到右檢測代碼,首先檢測到A ab。此時編譯器會讀取A類的信息,並標記有一個局部變量ab數據類型是A。

然后檢測到操作符 = 編譯器會去檢查右側的表達式。如果是字面量表達式(比如3+5或者簡單字符串拼接)那么會直接在編譯期計算,之后檢查結果的數據類型是否合規;如果是復雜表達式(除了字面量表達式之外的表達式)則只檢查數據類型是否合規。
我們上面的代碼屬於復雜表達式,編譯器只進行數據類型的檢查。
java語法規定new 后面只能是類的構造函數,也就是說這里必須實例化一個對象出來。這里編譯器只需要將new關鍵字后邊的字符拿出來,然后把()和;拿掉,就得到了實例化對象的數據類型了。
這也就是java要求構造方法必須與類同名的原因。

然后編譯器拿到了右邊表達式結果的數據類型B跟左邊的數據類型作比較。這個時候extends關鍵字就開始起作用了。由於B extends A,編譯器會在讀取B類信息的時候同時將A類的地址添到信息中,算作B類信息的一部分。這樣,B就能匹配兩種數據類型,一種是自己,一種是擴展類,也就是A。

這樣在比較左右兩邊數據類型的時候就變成了 A = B|A 。表達式成立,編譯通過。

同理mian方法第四句,由於A並沒有拓展B。所以數據類型比較不成立就會編譯報錯。

到這里就是向上造型的大概實現原理了。同樣的原理也可以推出繼承的幾種限制,比如成員訪問權限和拋出異常類型的限制。這里不做詳細介紹了。

可能有人會問上面的第四句,比較左右數據類型的時候不應該是B|A = A嘛?這樣應該也成立呀。
這個其實是編譯器的識別規則,上面提到了構造方法的執行順序,第一步就是執行父類構造方法。也就是說真正運行起來的時候,只有執行構造方法,才能有父類的對象,才能調用父類的成員。所以這里的表達式應該是B = A,是不成立的。

說完了向上造型,向下造型就很好理解了。上面的例子中,A類型的變量ab 實際的值確實B類的對象。
而做.運算的時候,編譯器只根據變量的數據類型決定能.出來的東西。所以父類型的引用,雖然是向上造型,但是仍然不能調用子類的成員。如果某一個時刻我們確實需要使用子類的成員,可以使用強制類型轉換。也就是:

B b = (b)ab;
1
這里編譯器不會報錯,是因為檢測到了強制類型轉換。但是轉換成功的前提是ab的值確實是B類型的。如果不是,在程序運行的時候會拋出類型轉換異常。

到這里,上下造型就說完了。利用上下造型極大的提高了程序的靈活性,使得java編程跟面向對象思想更加貼切。在上下造型被大范圍應用的時候,同一個對象可以賦值給不同類型的變量,在程序中某一個運行時段代表的意義也就不一樣,這就形成了對象多態的效果。

行為多態(重寫和重載)

行為多態的基本單位是方法,不可向下細分。行為多態是發生在對象內部的。
表現為一個對象在代表的意義不同時行為也不同。

這里提一下重寫和重載的區別,好多面試里也問道。其實只要搞清楚這兩個動作是怎么回事就很容易回答這個問題了。

先說重載。一般發生在同一個類中,編譯期就會綁定,方法名相同參數列表不同。
這個設計是為了模仿銀行窗口這樣的對象。假設客戶要取錢,但是事先並不知道客戶要那什么憑證來取錢。所以設立一個窗口,無論什么憑證,只要是取錢的業務都可以到窗口辦理。
有了重載就可以很方便的實現了。

再說重寫。發生在父子類中,運行期才會確定,方法名相同參數列表也相同。
注意如果在父子類中方法名相同參數列表不同的情況是重載不是重寫。雖然說父子類不是同一個類,但是有拓展關系。就是說在編譯期就能看到兩個類中的成員,所以在父子類中也可以發生重載。
這里來舉個例子:

 


還是上面的AB類,這次在B類中定義了一個跟A類中print方法完全相同的print();

當在編譯時,第一句向上造型上面說過了。ab.print(1);編譯器會去查看ab變量的數據類型,也就是A類,A類中含有print方法,參數為int。符合語法,編譯通過。同理第三句也是一樣。

當在運行時,程序會取ab的值,這個時候ab就是一個B對象了,那么去內存中B對象的領域里取print方法運行,實際上執行的邏輯是B類中定義的print方法的邏輯。所以就打印了balabala。這個現象好像A的方法被重新寫了一遍一樣,所以就把這種現象叫做重寫了。

然后執行第四句,同樣的邏輯,找到ac的數據類型,比較正確編譯通過;
執行的時候取ac的值,執行A對象的print方法,自然就打印1了。

到這里重寫和重載就說了個大概了。這兩種語法規定的出現,讓java對象實現了一個對象多種行為姿態的效果。被稱為行為多態。

行為多態和對象多態放在一起組成了java語言的第三大特性,統稱為多態。而java語言又是面向對象的代表語言,自然也就說面向對象的第三大特性是多態了。

其實這里是推崇了一下java,事實應該是先有思想的特性才有語言的實現。上面所說只是為了調一下胃口,並不代表真實歷史。 不過單憑java的開源,個人覺得就值得推崇一下。
---------------------
作者:AgentRich
來源:CSDN
原文:https://blog.csdn.net/Agent_Rich/article/details/81986910
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM