【编程语言】Java基础进阶——面向对象部分
斷斷續(xù)續(xù)終于把男神的Java都看完了,只是囫圇吞棗走馬觀花罷了,還有好多地方不是很理解,很多細節(jié)都沒能理清。之后再邊動手邊復習一遍,然后開始嘗試做點東西吧!
?
0. 一些小tips
·為避免出現(xiàn)錯誤提示:The local variable may not have been initialized.應該每個函數(shù)內(nèi)的局部變量都定義初始化。即保持良好的編程習慣,每個變量都定義初始化。
·String s = “”;
·boolean b = false;
·int a = 0;
?
·為避免構(gòu)造方法的歧義重載,良好的編程習慣是默認建立一個空的構(gòu)造方法:
<類名>()
{
}
·每一個類中都可以寫一個main方法,用來測試這個類的一些功能和屬性
·時刻謹記對象變量只是對象的管理者而非對象本身,聲明對象變量并沒有創(chuàng)建對象,就好比聲明一個指針,指針并不會指向任何有意義的內(nèi)存空間一樣。對象變量和指針變量,兩者都需被具體賦值,才能進行接下來的操作。
·需要對字符串進行很多復雜操作并會產(chǎn)生大量新的字符串時,為減少系統(tǒng)開銷,應使用StringBuffer,最后用toString()輸出結(jié)果字符串即可
1. 類與對象
·對象是實體,需要被創(chuàng)建,可以為我們做事情
·類是規(guī)范,根據(jù)類的定義來創(chuàng)建對象
類定義了對象,而對象是類的實體。
對象 = 屬性 + 服務
封裝:把數(shù)據(jù)和對數(shù)據(jù)的操作放在一起,由操作來保護數(shù)據(jù),數(shù)據(jù)不對外公開。
其中,
·數(shù)據(jù):屬性或狀態(tài) //? 類的成員變量
·操作:函數(shù)?????? // 類的成員函數(shù)
定義一個類,即是封裝的過程:
類中的變量都是類的成員變量(數(shù)據(jù)),類中的函數(shù)都是類的成員函數(shù)(對數(shù)據(jù)的操作)。
可以形象地將類描述成一個切開一半的咸蛋:蛋黃是數(shù)據(jù),而包裹在蛋黃外面的蛋白則是對數(shù)據(jù)的操作,由蛋白包裹覆蓋蛋黃以起到保護的作用,就是用對數(shù)據(jù)的操作來保護數(shù)據(jù)。
用類創(chuàng)建(new)一個對象,語法上表達為:
<類> <對象名> = new <類>();
定義出來的變量是對象變量,是對象的管理者而非所有者(即對象變量不是對象本身,類似指針與被指針指向的內(nèi)存區(qū)域的關(guān)系)。
一個類可以任意創(chuàng)建多個對象,每個對象都是獨立不同的。在Debug中可以看到每個對象變量的細節(jié):對象內(nèi)部的變量當前的值,對應的value項會顯示對象所屬的類名以及id等。
在創(chuàng)建對象變量之后,就可以用.運算符進行對象的操作了,即調(diào)用對象的某個函數(shù),寫法為:
<對象>.<對象的成員函數(shù)名>();
?
1)函數(shù)與成員變量
在函數(shù)中可以直接寫成員變量的名字來訪問成員變量,就好比C中的函數(shù)可以直接用全局變量的名字一樣。
函數(shù)是通過對象來調(diào)用的:
·v.insertMoney();
·這次調(diào)用臨時建立了insertMoney()和V之間的關(guān)系,讓insertMoney()內(nèi)部的成員變量指的是v的成員變量。
this是成員函數(shù)的一個特殊的固有本地變量,它表達了調(diào)用這個函數(shù)的那個對象(可以用指針的思想來理解:有一個固有的對象管理者this,函數(shù)調(diào)用時this臨時指向了.前面的那個對象,以表達當前調(diào)用函數(shù)的具體是哪個對象)
this可以寫在函數(shù)內(nèi)部,用來表示此次調(diào)用該函數(shù)的那個對象,因此,
·可以用this來區(qū)分同名的 對象的成員變量 與 函數(shù)內(nèi)部的本地變量:
class A {int a;void function( int a ) {this.a = a; // 若不用this則語法上是錯誤的,this.a表達的是對象的成員變量,而a則是函數(shù)function()內(nèi)的參數(shù),即一個本地變量,兩者是不同的 } }?
·也可以用來方便地(偷懶少打幾個字)來調(diào)用其他成員函數(shù):
1 class B { 2 int a; 3 … ; 4 5 void function1() 6 { 7 this.function2(); //等同于function2();在eclipse上輸入.之后會列出該類中的全部成員函數(shù),然后選擇所需即可自動填補 8 9 } 10 void function2() 11 { 12 … ; 13 } 14 }?
2)本地變量與成員變量
·定義在函數(shù)內(nèi)部的是本地變量,其生存期和作用域都是函數(shù)內(nèi)部
·成員變量是定義在類內(nèi)函數(shù)外的,生存期是對象的生存期,作用域是類內(nèi)部
3)成員變量初始化
在Java中,不允許存在未使用的本地變量,而成員變量的初始化則是自動的。
·成員變量在定義的地方就可以給初始值
·沒有初始值的成員變量會自動獲得0值 // 這里的0值是廣義的:0/false/null
·對象變量的0值表示它沒有管理任何對象,也可以主動給null值
·定義初始化時可以使用調(diào)用函數(shù)的返回值,甚至可以使用已經(jīng)定義過的成員變量的值
4)構(gòu)造函數(shù)與函數(shù)重載
如果有一個成員函數(shù)的名字與類名完全相同,則在創(chuàng)建這個類的每一個對象的時候會自動調(diào)用這個函數(shù),即構(gòu)造函數(shù),也稱構(gòu)造器。
構(gòu)造函數(shù):在構(gòu)造(創(chuàng)建)對象的時候會被自動調(diào)用的函數(shù),執(zhí)行時先執(zhí)行構(gòu)造函數(shù)外面的定義初始化,之后再進入構(gòu)造函數(shù),最后構(gòu)造函數(shù)結(jié)束返回new處,將構(gòu)造出來的對象交給對象變量管理。構(gòu)造函數(shù)不能有返回類型(包括void,void可以理解為這個函數(shù)有返回類型,類型是空(void),沒有返回值)。
在Java中,函數(shù)可以重載:
·一個類可以有多個構(gòu)造函數(shù),只要它們的參數(shù)表不同
·創(chuàng)建對象的時候給出不同的參數(shù)值,就會自動調(diào)用不同的構(gòu)造函數(shù)
·通過this()還可以調(diào)用其他構(gòu)造函數(shù)
·一個類里的同名但參數(shù)不同的函數(shù)構(gòu)成了重載關(guān)系
關(guān)于this():只能在構(gòu)造函數(shù)內(nèi)的第一句處使用一次。寫法:
1 class A { 2 int a; 3 … ; 4 A() // 構(gòu)造函數(shù),與類名完全一致且無返回類型 5 { 6 … ; 7 } 8 A( int a ) // 多個同名但參數(shù)表不同的函數(shù)實現(xiàn)函數(shù)重載 9 { 10 this(); //在A(int a)內(nèi)重載A(),只能在函數(shù)內(nèi)第一句處使用一次 11 … ; 12 } 13 }?
?
2. 對象交互
面向?qū)ο蟪绦蛟O(shè)計的第一步,就是在問題領(lǐng)域中識別出有效的對象,然后從識別出的對象中抽象出類來。
面向?qū)ο蟮暮诵脑谟诳吹皆趩栴}中有些什么樣的東西,每樣東西有什么樣的屬性,這些東西之間是如何交互的。
?
一個類中的成員變量可以使其他類的對象,最后得到的該類的對象是由多個其他類的對象組成的(就好比C中的結(jié)構(gòu)體嵌套結(jié)構(gòu)體一樣,Java中是對象包含對象,但Java中的對象能做的事情比C中的結(jié)構(gòu)體多太多了)。
?
面向?qū)ο笤O(shè)計思想:
對象和對象之間的聯(lián)系緊密程度叫做耦合。對象和對象的耦合程度越緊,表現(xiàn)在源代碼上,就是它們的代碼是互相依賴、互相牽制的。我們理想的模型,是對象和對象之間的耦合要盡可能的松,平行的對象要盡量減少直接聯(lián)系,讓更高層次的對象來提供通信服務。
即,同一個類的每一個對象都盡可能的相互獨立,彼此之間沒有直接聯(lián)系。這樣在編寫對象甲的時候就不用去考慮對象乙的情況,編寫對象乙的時候也不需要考慮對象甲。讓兩者之間的交互由更高級的對象丙(同時包含對象甲和對象乙)來完成。
?
封裝,就是把數(shù)據(jù)和對這些數(shù)據(jù)的操作放在一起,并且用這些操作把數(shù)據(jù)掩蓋起來,是面向?qū)ο蟮幕靖拍钪?#xff0c;也是最核心的概念。
一個非常直截了當?shù)氖侄蝸肀WC在類的設(shè)計的時候做到封裝:
·所有的成員變量必須是private的,這樣就避免別人任意使用你的內(nèi)部數(shù)據(jù);
·所有public的函數(shù),只是用來實現(xiàn)這個類的對象或類自己要提供的服務的,而不是用來直接訪問數(shù)據(jù)的。除非對數(shù)據(jù)的訪問就是這個類及對象的服務。簡單地說,給每個成員變量提供一對用于讀寫的get/set函數(shù)也是不合適的設(shè)計。
?
封閉的訪問屬性:private(私有的):
·只有這個類的內(nèi)部可以訪問(即如果在別的類中創(chuàng)造了該類的對象,也無法通過這個對象來訪問private的成員變量,因為不是在該類的內(nèi)部)
·類的內(nèi)部指的是成員函數(shù)和定義初始化
·這個限制是針對類的而不是針對對象的:即,對于私有的理解是從代碼的層面而非運行的層面上來看的。只要對private的成員變量的訪問是在該類的內(nèi)部發(fā)生的,這個操作就是合理的。也就是說,同一個類的不同對象之間可以互相訪問私有成員。
?
開放的訪問屬性:public(公共的):
·任何部分(其他類)都可以自由訪問
如果一個類是public的:
·表明任何類都可以利用該類定義變量
·該類所處源文件的文件名必須與該類名相同,否則會報錯:The public type <class name> must be defined in its own file.修改建議為:Rename compilation unit to ‘<class name>.java’
?
編譯單元:
·一個源代碼文件(.java文件)是一個編譯單元(compilation unit),編譯時一次僅對一個編譯單元進行編譯。
·一個編譯單元中可以有很多Java類,但只能有一個類是public的。如果類不是public的,則該類只能在所處的包中起作用。
?
若在成員前未加任何關(guān)鍵字來修飾限定它,那么表明該成員是friendly的,意為與該成員所屬類位于同一個包(package)的其他類可以訪問它。
?
包:
包是Java的類庫管理機制,它借助文件系統(tǒng)的目錄來管理類庫,一個包就是一個目錄,一個包內(nèi)的所有的類必須放在一個目錄下,那個目錄的名字必須是包的名字。
?
每一個Java源文件的第一個語句一定是package ?<包名>;這句話表明了這個public類屬于哪個包,也就是與public類同名的Java源文件位于那個與包同名的文件夾下。在Eclipse中,如果一個文件中沒有package語句,則表明該類屬于一個沒有名字的默認包(default package)。
?
如果在一個類中使用了另外的與其不在同一個包中的類,則有兩種方式:
1)在使用該類的時候?qū)懗鲱惖娜?#xff1a;<包名>.<類名>
2)使用import,寫法如下:
import <包名>.<類名>(即類的全名)
這樣寫的好處是:
清晰:在查找其他包的類時只需要看import處的那一列即可。
方便:在一個類中使用import的類只需要寫類名而不用寫類的全名。
同時import還可以寫成:
import <包名>.*(此處的*的含義是通配符)
這樣的寫法表示import該包里的全部類。一般來說不建議這種寫法,因為有可能會發(fā)生重名沖突。
注意,即使import過所需其他包的類,也無法訪問該類的friendly成員,因為friendly成員只能被同一個包的其他類訪問,不同包則不能。
?
包中包:
在建立新的包時如果起名為<已有包>.<包名>,則意味著這個新的包是一個包中包,在文件系統(tǒng)里體現(xiàn)為與該包同名的文件夾是已有包同名文件夾的子文件夾,在import類時則寫成:
import <包名>.<包名>.<類名>;
此時兩個包之間的.就等同于文件目錄路徑中的\
?
具體體現(xiàn)在文件系統(tǒng)中的層級依次為:
workspace
??? project
?????? src
?????????? package
????????????? *.java
?????? bin
?????????? package
????????????? *.class
例:
workspace
??? Project_1? //? 文件夾名與項目名一致,為Project_1
?????? src??? //? 文件夾名即src,按包分類存放.java文件(源文件)
?????????? clock? // 文件夾名與包名一致,為clock
????????????? Clock.java // 文件名與類名一致,為Clock
????????????? Display.java? //? 文件名與類名一致,為Display
????????????? time // 文件夾名為time,該包即是包中包,包名為clock.time
Time.java? //? 文件名與類名一致,為Time,該類全名為clock.time.Time
?????? bin??? //? 文件夾名即bin,按包分類存放.class文件(字節(jié)碼文件)
?????????? clock?
????????????? Clock.class??
????????????? Display.class
????????????? time??
????????????????? Time.class
??? Project_2
?????? src
?????????? a.java // 如果是默認包,則文件直接保存在src目錄下
?????? bin
?
在以上的情況下,如果在Clock.java文件中想要使用Time類,則應該在Clock類的前面寫import clock.time.Time; ? ?// 包的路徑是 ..\clock\time
?
static,類變量與類函數(shù):
?
類是描述,對象是實體。在類里所描述的成員變量,是位于這個類的每一個對象中的。
而如果某個成員有static關(guān)鍵字做修飾,它就不再屬于每一個對象,而是屬于整個類的了。
通過每個對象都可以訪問到這些類變量和類函數(shù),但是也可以通過類的名字來訪問它們。類函數(shù)由于不屬于任何對象,因此也沒有辦法建立與調(diào)用它們的對象的關(guān)系,就不能訪問任何非static的成員變量和成員函數(shù)了。
?
static與public和private一樣,是用來修飾變量或函數(shù)的關(guān)鍵字。
static意味著它所修飾的那個東西不屬于類中的任何對象,而屬于這個類。
?
在類體中,如果定義一個變量/函數(shù),在前面加上關(guān)鍵字static,則表明這個變量/函數(shù)是類變量/函數(shù)而非成員變量/函數(shù):
·成員變量/函數(shù)位于每個對象之中而非這個對象的類之中,即同一個類的每個對象擁有屬于自己的成員變量/函數(shù),對象與對象之間的成員變量/函數(shù)相互獨立沒有聯(lián)系;
·而static變量/函數(shù)則是類變量/函數(shù),類變量/函數(shù)屬于整個類,而不屬于這個類的任何一個對象,但每個對象都可以訪問到它。
有兩種方式可以訪問類變量/函數(shù):
<對象名>.<類變量/函數(shù)名>或<類名>.<類變量/函數(shù)名>
前者是通過對象來訪問類變量/函數(shù),因此只要屬于同一個類,任何對象都可以這樣寫。
后者是通過類來訪問類變量/函數(shù),更建議使用這種寫法,以便與對象的成員變量/函數(shù)清晰地區(qū)分開來。
?
關(guān)于static的規(guī)則:
·如果一個函數(shù)是static的,那么它只能訪問static變量/函數(shù)
·static的變量和函數(shù)既可以通過類名來訪問,也可以通過某個對象名訪問,但通過對象名訪問時并不能獲得該對象的具體信息。
·如果在定義static變量時定義初始化,那么這個初始化只會在加載這個類的時候(即創(chuàng)建類變量,為其分配內(nèi)存空間時)做一次,而與對象的創(chuàng)建無關(guān)。
?
3. 容器
程序設(shè)計的思想之一:人機交互與業(yè)務邏輯分離開來。在做業(yè)務邏輯的時候,只專注于數(shù)據(jù)本身,而不要去考慮人機交互方面的東西。因此,對于一個數(shù)據(jù)的讀取,不要在對象的方法里用單純的System.out.println()將它輸出,而是要把數(shù)據(jù)返回,由上層調(diào)用這個對象的函數(shù)來決定,得到這個返回值之后該做什么操作。這樣一方面使得函數(shù)的用法更加靈活(而不單單只能用來輸出),另一方面更符合面向?qū)ο蟪绦蛟O(shè)計的思想。人機交互與業(yè)務邏輯的統(tǒng)一標準由接口的設(shè)計來實現(xiàn),做項目的時候,先定義接口,再實現(xiàn)具體功能。
?
?
1)順序容器:ArrayList<Type>
·ArrayList<String> notes = new ArrayList<String>;
意為創(chuàng)建一個array list of string類型的對象變量notes
·ArrayList是系統(tǒng)類庫中的一個泛型類,java.util.ArrayList
·大小可以隨意改變
·有很多現(xiàn)成的庫函數(shù)可以用,需要閱讀官方文檔來了解更多信息
關(guān)于系統(tǒng)類庫的使用:
·是否充分理解系統(tǒng)類庫的類
·是否充分了解類庫中各種函數(shù)的功能和使用方法
?
容器類:
·用來存放任意數(shù)量的對象
·有兩個類型:
·容器的類型
·元素的類型
?
2)對象數(shù)組:
·對象數(shù)組中的每個元素都是對象的管理者而非對象本身
·對象數(shù)組的for-each情況與基礎(chǔ)數(shù)據(jù)類型的情況不同:
class Value {private int i = 0;public void set( int i ) { this.i = i; }public int get() { return i; } }public class TestArrayList {public static void main(String[] args) {Value[] a = new Value[10];for ( int i = 0 ; i < a.length; i++ ) {a[i] = new Value();a[i].set(i);}for ( Value v : a ) {System.out.println(v.get());}for ( Value v : a) {v.set(0);}for ( Value v : a ) {System.out.println(v.get());}}}?
?
則第一次輸出是0~9十個數(shù),而第二次輸出是十個0。由此可見,對象數(shù)組的for-each可以對數(shù)組內(nèi)容進行修改。
·對于容器類來說,for-each循環(huán)也可以用
?
3)集合容器:Set
·集合的概念與數(shù)學中的集合完全一致:
·集合中的每個元素都是不重復的,即每個元素具有唯一的值
·集合中的元素沒有順序
·集合也是一種泛型類:HashSet<String> s = new HashSet<String>();
?
HashSet和ArrayList的對象都可以用System.out.println()直接輸出,輸出結(jié)果為[]中顯示元素值,其中ArraryList的值是順序的,而Set中是無序的。
?
toString():
任何一個Java類,只要在其中實現(xiàn):
public String toString()
{
??? return <String類型字符串>;
}
就可以直接寫成System.out.println( <該類的對象名> );
這種寫法將會使System.out直接調(diào)用該類中的toString(),并輸出toString()返回的相應結(jié)果(String)。
如果類中沒有toString(),System.out.println( <該類的對象名> );則會輸出對象變量的值,即對象實體的引用,類似輸出指針會輸出指針指向的地址,格式為<類的全名>@<十六進制數(shù)>。
?
4)散列表
HashMap<Integer, String> a = new HashMap<Integer, String>();
數(shù)據(jù)結(jié)構(gòu)中的散列表里所有的東西都是以一對值的形式存放的:Key鍵 Value值
關(guān)于Integer:容器中的類型必須是對象而不能是基本類型的數(shù)據(jù),所以對于整型int,應使用其包裹類型Integer。
對于散列表,每個Key一定是唯一的,所以如果對于同一個鍵多次放入不同值,最后保存的是最后一次放入的值。
HashMap的一些方法:
a.put( <key>, <value> ); // key->value
a.get( <key> );????? // return value
a.containsKey( <key> );? // return true / false
a.keySet();?? // HashMap a中所有key的HashSet
a.keySet().size();?????? // HashSet()的size()方法
?
對HashMap的for-each:
for ( Integer k : a.keySet() ) {?????? // 枚舉所有key
??? String s = a.get(k);
??? System.out.println(s);????? //? 輸出key對應的value
}
?
4.繼承
繼承是面向?qū)ο笳Z言的重要特征之一,沒有繼承的語言只能被稱作“使用對象的語言”。繼承是非常簡單而強大的設(shè)計思想,它提供了我們代碼重用和程序組織的有力工具?;谝延械脑O(shè)計創(chuàng)造新的設(shè)計,就是面向?qū)ο蟪绦蛟O(shè)計中的繼承。在繼承中,新的類不是憑空產(chǎn)生的,而是基于一個已經(jīng)存在的類而定義出來的。通過繼承,新的類自動獲得了基礎(chǔ)類中所有的成員,包括成員變量和方法,包括各種訪問屬性的成員,無論是public還是private。當然,在這之后,程序員還可以加入自己的新的成員,包括變量和方法。顯然,通過繼承來定義新的類,遠比從頭開始寫一個新的類要簡單快捷和方便。繼承是支持代碼重用的重要手段之一。
?
對理解繼承來說,最重要的事情是,知道哪些東西被繼承了,或者說,子類從父類那里得到了什么。答案是:所有的東西,所有的父類的成員,包括變量和方法,都成為了子類的成員,除了構(gòu)造方法。構(gòu)造方法是父類所獨有的,因為它們的名字就是類的名字,所以父類的構(gòu)造方法在子類中不存在。除此之外,子類繼承得到了父類所有的成員。
但是得到不等于可以隨便使用。每個成員有不同的訪問屬性,子類繼承得到了父類所有的成員,但是不同的訪問屬性使得子類在使用這些成員時有所不同:有些父類的成員直接成為子類的對外的界面,有些則被深深地隱藏起來,即使子類自己也不能直接訪問。
?
public的成員直接成為子類的public的成員,protected的成員也直接成為子類的protected的成員。Java的protected的意思是包內(nèi)和子類可訪問,所以它比缺省的訪問屬性要寬一些。而對于父類的缺省的未定義訪問屬性的成員來說,他們是在父類所在的包內(nèi)可見,如果子類不屬于父類的包,那么在子類里面,這些缺省屬性的成員和private的成員是一樣的:不可見。父類的private的成員在子類里仍然是存在的,只是子類中不能直接訪問。我們不可以在子類中重新定義繼承得到的成員的訪問屬性。如果我們試圖重新定義一個在父類中已經(jīng)存在的成員變量,那么我們是在定義一個與父類的成員變量完全無關(guān)的變量,在子類中我們可以訪問這個定義在子類中的變量,在父類的方法中訪問父類的那個。盡管它們同名但是互不影響。
?
在構(gòu)造一個子類的對象時,父類的構(gòu)造方法也是會被調(diào)用的,而且父類的構(gòu)造方法在子類的構(gòu)造方法之前被調(diào)用。在程序運行過程中,子類對象的一部分空間存放的是父類對象。因為子類從父類得到繼承,在子類對象初始化過程中可能會使用到父類的成員。所以父類的空間正是要先被初始化的,然后子類的空間才得到初始化。在這個過程中,如果父類的構(gòu)造方法需要參數(shù),如何傳遞參數(shù)就很重要了。
?
子類比父類更加具體。例如鳥是父類,那么啄木鳥,貓頭鷹,麻雀,鴕鳥都是鳥的子類。
子類通過繼承得到了父類的所有東西,包括private的成員,盡管可能無法直接訪問和使用,但繼承自父類的東西確實存在,父類中所有public和protected的成員,在子類中都可以直接拿來使用。
?
關(guān)鍵字:extends
class thisClass extends superclass {?? // 表明thisClass是superClass 的子類
???
}
用關(guān)鍵字super表示父類,以區(qū)分調(diào)用的同名函數(shù)是子類中的還是父類中的,就像this用來表示當前對象,以區(qū)分同名變量是局部的還是成員的一樣。
?
在創(chuàng)建一個子類的對象時的代碼執(zhí)行順序總是:
1子類的構(gòu)造器中的super()(如果有super的話)
↓
2父類構(gòu)造器中的super()(如果有super的話)
↓
3父類成員變量定義初始化(如果有初始化的話)
????????????????? ↓
4父類構(gòu)造器內(nèi)部
↓
5子類成員變量定義初始化(如果有初始化的話)
↓
6子類構(gòu)造器內(nèi)部
?
也就是說,在構(gòu)造一個子類的對象時,父類的構(gòu)造方法在子類的構(gòu)造方法之前被調(diào)用。
構(gòu)造器是構(gòu)造對象時的入口,但并不是最優(yōu)先被執(zhí)行的,成員變量定義初始化最優(yōu)先,若無定義初始化則跳過。
在子類中,無論是否存在super(),在構(gòu)造子類的對象時都會自動調(diào)用父類的構(gòu)造器:若無super,則默認調(diào)用父類無參構(gòu)造器,此時若父類中存在有參構(gòu)造器,但沒有無參構(gòu)造器則會報錯;若有super(<參數(shù)表>),則會根據(jù)參數(shù)表重載相應的父類構(gòu)造器。
?
?
5.多態(tài)變量與造型
?
對象變量是多態(tài)變量,可以保存其聲明類型的對象,或該類型的任何子類型的對象。
?
子類和子類型
·類定義了類型
·子類定義了子類型
·子類的對象可以被當作父類的對象來使用
·賦值給父類的變量
·傳遞給需要父類對象的函數(shù)
·放進存放父類對象的容器
?
子類的對象可以賦值給父類的對象變量。
如Vehicle類是父類,其子類有Car,Bicycle等,那么Bicycle類和Car類的對象都可以賦值給Vehicle類的對象變量:
Vehicle v = new Vehicle();
Vehicle v = new Car();
Vehicle v = new Bicycle();
?
子類和參數(shù)傳遞:
子類的對象可以傳遞給需要父類對象的函數(shù)。
子類的對象可以放在存放父類對象的容器里。
?
多態(tài)變量
·Java的對象變量是多態(tài)的,它們能保存不止一種類型的對象
·它們可以保存的是聲明類型的變量,或聲明類型的子類的對象
·當把子類的對象賦值給父類的變量時,就發(fā)生了向上造型
?
靜態(tài)類型與動態(tài)類型
·一個對象變量的聲明類型就是其靜態(tài)類型
·一個對象變量當前指向的對象的類型是動態(tài)類型
因此Java中的對象變量總是有兩個類型,一個是聲明類型,一個是動態(tài)類型,只是有時這兩個類型可能是一致的。
?
造型cast
·一個類型的對象賦值給另一個類型的對象變量的過程
·子類的對象可以賦值給父類的變量
·注意!Java中不存在對象對對象的賦值
·父類的對象不能賦值給子類的變量
·當父類的變量當前指向的是子類的對象時,可以用造型來賦值,否則會出現(xiàn)異常:ClassCastException
?
如,Vehicle是父類Car是子類:
Vehicle v;
Car c = new Car();
v = c; //? 可以
c = v; //? 編譯錯誤
c = (Car)v;?? // 造型,此時v指向的是Car的對象,即v的動態(tài)類型是Car,可以用造型來賦值給Car類型的變量
再如,Item是父類,CD是子類:
Item item = new Item();
CD cd = new CD();
item = cd; // 可以
CD cc = (CD)item; ??? //? 造型,如果沒有(CD)則報錯
?
造型
·用括號圍起類型放在值的前面
·對象本身并沒有發(fā)生任何變化
·所以不是“類型轉(zhuǎn)換”:類型轉(zhuǎn)換是將原來的值變成轉(zhuǎn)換類型的值之后賦值,而造型是將當前類型的值(對象)看作是造型類型的值來看待,并沒有修改替換成為另外類型的值
·運行時有機制來檢查這樣的轉(zhuǎn)化是否合理
·ClassCastException
?
向上造型:把子類的對象賦值給父類的變量,(<父類型>)可以省略
·拿一個子類的對象,當作父類的對象來用
·向上造型是默認的,不需要運算符
·向上造型總是安全的
?
向下造型:把子類的對象通過父類變量賦值給子類變量,即父類變量當前指向子類對象,需要用到(<子類型>)
?
6.多態(tài)
?
如果子類的方法覆蓋了父類的方法,我們也說父類的那個方法在子類有了新的版本或者新的實現(xiàn)。覆蓋的新版本具有與老版本相同的方法簽名:相同的方法名稱和參數(shù)表。因此,對于外界來說,子類并沒有增加新的方法,仍然是在父類中定義過的那個方法。不同的是,這是一個新版本,所以通過子類的對象調(diào)用這個方法,執(zhí)行的是子類自己的方法。
?
覆蓋關(guān)系并不說明父類中的方法已經(jīng)不存在了,而是當通過一個子類的對象調(diào)用這個方法時,子類中的方法取代了父類的方法,父類的這個方法被“覆蓋”起來而看不見了。而當通過父類的對象調(diào)用這個方法時,實際上執(zhí)行的仍然是父類中的這個方法。注意我們這里說的是對象而不是變量,因為一個類型為父類的變量有可能實際指向的是一個子類的對象。
?
當調(diào)用一個方法時,究竟應該調(diào)用哪個方法,這件事情叫做綁定。綁定表明了調(diào)用一個方法的時候,我們使用的是哪個方法。綁定有兩種:一種是早綁定,又稱靜態(tài)綁定,這種綁定在編譯的時候就確定了;另一種是晚綁定,即動態(tài)綁定。動態(tài)綁定在運行的時候根據(jù)變量當時實際所指的對象的類型動態(tài)決定調(diào)用的方法。Java缺省使用動態(tài)綁定。
?
函數(shù)調(diào)用的綁定
·當通過對象變量調(diào)用函數(shù)的時候,調(diào)用哪個函數(shù)這件事情可以叫做綁定
·靜態(tài)綁定:根據(jù)變量的聲明類型來決定
·動態(tài)綁定:根據(jù)變量的動態(tài)類型來決定
·在成員函數(shù)中調(diào)用其它成員函數(shù)也是通過this這個對象變量來調(diào)用的
?
覆蓋/重寫override
·子類和父類中存在名稱和參數(shù)表完全相同的函數(shù),這一對函數(shù)構(gòu)成覆蓋關(guān)系
·通過父類的變量調(diào)用存在覆蓋關(guān)系的函數(shù)時,會調(diào)用變量當時所管理的對象所屬類的函數(shù)
?
所謂的多態(tài)就是,通過一個對象變量調(diào)用一個函數(shù)時,并不去判斷對象變量的動態(tài)類型可能是什么,當程序執(zhí)行的時候,讓其自動通過當前動態(tài)類型來執(zhí)行相應的函數(shù)。
?
在函數(shù)重寫時,函數(shù)的前一句話應該是@Override,子類中的重寫函數(shù)的聲明部分(即函數(shù)頭)應與父類的完全一致,包括限定詞,返回值類型,函數(shù)名,參數(shù)表
?
如:
@Override
public String toString()
{
??? return <String>;
}
?
@Override的作用是,告訴編譯器接下來的函數(shù)是對父類同名函數(shù)的重寫(即覆蓋),如果接下來的函數(shù)與父類同名函數(shù)有任何不一致的地方,則會報錯;如果沒有@Override,則不會對接下來的函數(shù)做任何限制,同樣地也就不再必須是對父類函數(shù)的重寫。
?
Java類型系統(tǒng)的根:Object類
Java是一種單根結(jié)構(gòu)的語言,Java中的所有類默認看作是Object類的子類,即,所有的類都是繼承自O(shè)bject的。
?
Object類的函數(shù)
·toString()
·equals()
·可通過eclipse補全功能查看其他函數(shù)
?
可擴展性:代碼不需要經(jīng)過修改就可以適應新的內(nèi)容或數(shù)據(jù)
可維護性:代碼經(jīng)過修改可以適應新的內(nèi)容或數(shù)據(jù)
?
7.設(shè)計原則
?
?
1)消除代碼復制:
程序中存在相似甚至相同的代碼塊,是非常低級的代碼質(zhì)量問題。
?
代碼復制存在的問題是,如果需要修改一個副本,那么就必須同時修改所有其他的副本,否則就存在不一致的問題。這增加了維護程序員的工作量,而且存在造成錯誤的潛在危險。很可能發(fā)生的一種情況是,維護程序員看到一個副本被修改好了,就以為所有要修改的地方都已經(jīng)改好了。因為沒有任何明顯跡象可以表明另外還有一份一樣的副本代碼存在,所以很可能會遺漏還沒被修改的地方。
?
消除代碼復制的兩個基本手段,就是函數(shù)和父類。
?
2)用封裝來降低耦合
·類和類之間的關(guān)系稱作耦合
·耦合越低越好,保持距離是形成良好代碼的關(guān)鍵
?
要評判某些設(shè)計比其他的設(shè)計優(yōu)秀,就得定義一些在類的設(shè)計中重要的術(shù)語,以用來討論設(shè)計的優(yōu)劣。對于類的設(shè)計來說,有兩個核心術(shù)語:耦合和聚合。耦合這個詞指的是類和類之間的聯(lián)系。之前的章節(jié)中提到過,程序設(shè)計的目標是一系列通過定義明確的接口通信來協(xié)同工作的類。耦合度反映了這些類聯(lián)系的緊密度。我們努力要獲得低的耦合度,或者叫作松耦合(loose coupling)。
?
耦合度決定修改應用程序的容易程度。在一個緊耦合的結(jié)構(gòu)中,對一個類的修改也會導致對其他一些類的修改。這是要努力避免的,否則,一點小小的改變就可能使整個應用程序發(fā)生改變。另外,要想找到所有需要修改的地方,并一一加以修改,卻是一件既困難又費時的事情。另一方面,在一個松耦合的系統(tǒng)中,常??梢孕薷囊粋€類,但同時不會修改其他類,而且整個程序還可以正常運作。
?
聚合與程序中一個單獨的單元所承擔的任務的數(shù)量和種類相對應有關(guān),它是針對類或方法這樣大小的程序單元而言的理想情況下,一個代碼單元應該負責一個聚合的任務(也就是說,一個任務可以被看作是一個邏輯單元)。一個方法應該實現(xiàn)一個邏輯操作,而一個類應該代表一定類型的實體。聚合理論背后的要點是重用:如果一個方法或類是只負責一件定義明確的事情,那么就很有可能在另外不同的上下文環(huán)境中使用。遵循這個理論的一個額外的好處是,當程序某部分的代碼需要改變時,在某個代碼單元中很可能會找到所有需要改變的相關(guān)代碼段。
?
3)增加可擴展性:
·可以運行的代碼 != 良好的代碼
·對代碼做維護的時候最能看出代碼的質(zhì)量
·可擴展性的意思就是代碼的某些部分不需要經(jīng)過修改就能適應將來可能的變化
?
·用接口來實現(xiàn)聚合
·在類的內(nèi)部實現(xiàn)新的方法,把具體細節(jié)徹底隱藏在類的內(nèi)部
·今后某一功能如何實現(xiàn)就和外部無關(guān)了
?
·用容器來實現(xiàn)靈活性
·減少“硬編碼”
·對于隨時變化數(shù)量的同類事物,用容器來實現(xiàn),比如HashMap
?
·框架+數(shù)據(jù)提高可擴展性
·從程序中識別出框架和數(shù)據(jù),以代碼實現(xiàn)框架,將部分功能以數(shù)據(jù)的方式加載,這樣能在很大程度上實現(xiàn)可擴展性。
?
8.抽象
?
我們用abstract關(guān)鍵字來定義抽象類。抽象類的作用僅僅是表達接口,而不是具體的實現(xiàn)細節(jié)。抽象類中可以存在抽象方法。抽象方法也是使用abstract關(guān)鍵字來修飾。抽象的方法是不完全的,它只是一個方法簽名而完全沒有方法體。
?
如果一個類有了一個抽象的方法,這個類就必須聲明為抽象類。如果父類是抽象類,那么子類必須覆蓋所有在父類中的抽象方法,否則子類也成為一個抽象類。一個抽象類可以沒有任何抽象方法,所有的方法都有方法體,但是整個類是抽象的。設(shè)計這樣的抽象類主要是為了防止制造它的對象出來。
?
抽象abstract
·抽象函數(shù):表達概念而無法實現(xiàn)具體代碼的函數(shù)
·帶有abstract修飾的函數(shù)
·public abstract void method();?? // 必須結(jié)尾帶分號,而不能是空的大括號{},就像C中的函數(shù)聲明
·抽象類:表達概念而無法構(gòu)造出實體的類
·有抽象函數(shù)的類一定是抽象類
·抽象類不能制造對象
·但是可以定義變量:任何繼承了該抽象類的非抽象子類的對象可以賦給這個抽象類的變量,即由抽象類變量管理其非抽象子類的對象。
?
實現(xiàn)抽象函數(shù)
·繼承自抽象類的子類必須覆蓋所有父類中的抽象函數(shù),這里的對父類的抽象函數(shù)的覆蓋稱作實現(xiàn)implement
·否則子類也是抽象類。因為有繼承自父類的抽象函數(shù)未重寫,只要一個類中有抽象函數(shù),這個類就必須是抽象類。
?
寫法:
public abstract class Shape {
??? public abstract void draw(Graphics g); // 只有函數(shù)頭加;沒有函數(shù)體
}
?
public class Line extends Shape { // Line是繼承自抽象類Shape的非抽象子類
??? … …
??? @Override
??? public void draw(Graphics g)??? //對抽象類Shape的抽象方法draw的具體實現(xiàn)
{
??? … …
}
}
?
兩種抽象
·與具體相對:表示一種概念而非實體
·與細節(jié)相對:表示在一定程度上忽略細節(jié)而著眼大局
?
數(shù)據(jù)與表現(xiàn)分離
·程序的業(yè)務邏輯與表現(xiàn)無關(guān)
·表現(xiàn)可以是圖形的也可以是文本的
·表現(xiàn)可以是當?shù)氐囊部梢允沁h程的
·負責表現(xiàn)的類只管根據(jù)數(shù)據(jù)來表現(xiàn)
·負責數(shù)據(jù)的類只管數(shù)據(jù)的存放
·一旦數(shù)據(jù)更新以后,表現(xiàn)仍然可以不變
·不用去精心設(shè)計哪個局部需要更新
·簡化了程序邏輯
·這是在計算機運算速度提高的基礎(chǔ)上實現(xiàn)的
責任驅(qū)動的設(shè)計
·將程序要實現(xiàn)的功能分攤到適合的類/對象中去是設(shè)計中非常重要的一環(huán)
?
網(wǎng)格化
·圖形界面本身有更高的解析度
·但是將畫面網(wǎng)格化以后,數(shù)據(jù)就更容易處理了
?
9.接口
?
接口interface
·類表達的是對某類東西的描述,而接口表達的是抽象的概念和規(guī)范
·接口是純抽象類
??? ·所有的成員函數(shù)都是抽象函數(shù)
·所有的成員變量都是public static final
·因此不需特意聲明abstract
·接口規(guī)定了長什么樣,但不管里面有什么東西
·在Java中,接口就是一種特殊的class
·它與class地位相同
·接口類型的變量可以被賦值任何實現(xiàn)了這個接口的類的對象
?
寫法:
public interface Cell {
int size;? // public static final
??? void draw( … );?? // abstract
}
?
實現(xiàn)接口
·類用extends表達子類繼承父類,用implements表達一個類實現(xiàn)了一個接口
·一個類可以實現(xiàn)很多接口
·接口可以繼承接口,但不能繼承類
·接口不能實現(xiàn)接口
?
寫法:
public class Fox extends Animal implements Cell {
??? … …
??? @Override
??? public void draw()
{
?????? … …
}
}
?
面向接口的編程方式
·設(shè)計程序時先定義接口,再實現(xiàn)類
·任何需要在函數(shù)間傳入傳出的一定是接口而不是具體的類
·是Java的成功關(guān)鍵之一,因為極適合多人同時寫一個大程序
·也是Java被批評的要點之一,因為代碼量會迅速膨脹,整體顯得臃腫
?
10.控制反轉(zhuǎn)與MVC設(shè)計模式
?
內(nèi)部類就是指一個類定義在另一個類的內(nèi)部,從而成為外部類的一個成員。因此一個類中可以有成員變量、方法,還可以有內(nèi)部類。實際上Java的內(nèi)部類可以被稱為成員類,內(nèi)部類實際上是它所在類的成員。所以內(nèi)部類也就具有和成員變量、成員方法相同的性質(zhì)。比如,成員方法可以訪問私有變量,那么成員類也可以訪問私有變量了。也就是說,成員類中的成員方法都可以訪問成員類所在類的私有變量。內(nèi)部類最重要的特點就是能夠訪問外部類的所有成員。
?
內(nèi)部類
·定義在別的類的內(nèi)部或函數(shù)內(nèi)部的類
·內(nèi)部類能直接訪問外部的全部資源
·包括任何私有成員,因為內(nèi)部類也是類的成員之一
·外部是函數(shù)時,只能訪問那個函數(shù)里的final的變量
?
匿名類
·在new對象的時候給出的類的定義形成了匿名類
·匿名類一定是內(nèi)部類,可以繼承某類,也可以實現(xiàn)某接口
·Swing的消息機制廣泛使用匿名類
?
寫法:
new 類名/接口名() {
??? … …
}
如:
JButton butStep = new JButton(“OneStep”);
butStep.addActionListener(new ActionListener() {
// addActionListener()是JButton類的方法,ActionListener是JButton的接口
??? @Override
??? public void actionPerformed()
??? {
?????? ……
}
});??? // 定義匿名類,實現(xiàn)ActionListener接口
?
注入反轉(zhuǎn)·控制反轉(zhuǎn)
·由按鈕(別人寫的類)公布一個守聽者接口和一對注冊/注銷方法
·根據(jù)需要編寫代碼實現(xiàn)那個接口,將守聽者對象注冊在按鈕上
·一旦按鈕被按下,就會反過來調(diào)用你的守聽者對象的某個方法
?
MVC
·數(shù)據(jù)、表現(xiàn)和控制三者分離,各負其責
·M = Model(模型)
·V = View(表現(xiàn))
·C = Control(控制)
·模型:保存和維護數(shù)據(jù),提供接口讓外部修改數(shù)據(jù),通知表現(xiàn)需要刷新
·表現(xiàn):從模型獲得數(shù)據(jù),根據(jù)數(shù)據(jù)畫出表現(xiàn)
·控制:從用戶得到輸入,根據(jù)輸入調(diào)整數(shù)據(jù)
·在MVC中,很重要的一件事是,Control和View之間沒有任何聯(lián)系。用戶在界面上所做的操作(控制)不直接修改界面上的顯示(表現(xiàn)),即,用來接收用戶操作/輸入的代碼并不直接修改在屏幕上顯示的內(nèi)容,而是用戶的輸入來調(diào)整內(nèi)部的數(shù)據(jù),再由內(nèi)部數(shù)據(jù)去觸發(fā)表現(xiàn)刷新。
·這樣做的好處是,每一部分都很單純,不需要去考慮其他細節(jié)的東西。
?
11.異常
?
捕捉異常
Try {
??? //? 可能產(chǎn)生異常的代碼
} catch(Type1 id1) {
??? //? 處理Type1異常的代碼
} catch(Type2 id2) {
??? //? 處理Type2異常的代碼
} catch(Type3 id3) {
??? //? 處理Type3異常的代碼
}
?
·得到異常對象之后
??? ·String getMessage();
??? ·String toString();
??? ·void printStackTrace();
·并不能回到發(fā)生異常的地方,具體的處理邏輯取決于業(yè)務邏輯需要
?
再度拋出
catch(Exception e) {
??? System.err.println(“An exception was thrown”);
??? throw e;
}
·如果在這個層面上需要處理,但并不能做最終的決定,就catch之后再throw
?
讀取文件使用異常機制的例子(偽碼描述):
try {
??? open the file;
??? determine its size;
??? allocate that much memory;
??? read the file into memory;
??? close the file;
} catch ( fileOpenFailed ) {
??? doSomething;
} catch ( sizeDeterminationFailed ) {
??? doSomething;
} catch ( memoryAllocationFailed ) {
??? doSomething;
} catch ( readFailed ) {
??? doSomething;
} catch ( fileClosedFailed ) {
??? doSomething;
}
?
異常
·有不尋常的事情發(fā)生了
·當這個事情發(fā)生的時候,原本打算要接著做的事情不能再繼續(xù)了,必須得要停下來,讓其他地方的某段代碼來處理
·異常機制最大的好處是清晰地分開了正常業(yè)務邏輯代碼和遇到情況時的處理代碼
?
異常聲明
·如果函數(shù)可能拋出異常,就必須在函數(shù)頭部加以聲明
??? void f() throws TooBig, TooSmall, DivZero { … }
·可以聲明并不會真的拋出的異常,以使函數(shù)更加靈活,增加可擴展性,方便以后的補充修改
?
可以拋出的
·任何繼承了Throwable類的對象
·Exception類繼承了Throwable
??? ·throw new Exception();
??? ·throw new Exception(“HELP”);
?
catch匹配異常
·Is-A的關(guān)系
·拋出的子類異常會被捕捉父類異常的catch捕捉到
?
捕捉任何異常
catch(Exception e) {
??? System.err.println(“Caught an exception”);
}
?
運行時刻異常
·像ArrayIndexOutOfBoundsException這樣的異常是不需要聲明的
·但是如果沒有適當?shù)臋C制來捕捉,就會最終導致程序終止
?
異常聲明遇到繼承關(guān)系
·當覆蓋一個函數(shù)的時候,子類不能聲明拋出比父類版本更多的異常
·在子類的構(gòu)造函數(shù)中,必須聲明父類可能拋出的全部異常,之后還可以增加子類自己的異常聲明
?
12.流
?
流的基礎(chǔ)類
·InputStream
·OutputStream
·都是對字節(jié)進行操作:字節(jié)流
?
InputStream
·read()
??? ·int read()
??? ·read(byte b[])
??? ·read(byte[], int off, int len)
??? ·skip(long n)
·int available()
·mark()
·reset()
·boolean markSupported()
·close()
?
?
OutputStream
·write()
·write(int b)
·write(byte b[])
·write(byte b[], int off, int len)
·flush()
·close()
?
文件流
·FileInputStream
·FileOutputStream
·對文件作讀寫操作,也是字節(jié)
·實際工程中已經(jīng)較少使用
·更常用的是以在內(nèi)存數(shù)據(jù)或通信數(shù)據(jù)上建立的流,如數(shù)據(jù)庫的二進制數(shù)據(jù)讀寫或網(wǎng)絡(luò)端口通信
·具體的文件讀寫往往有更專業(yè)的類,比如配置文件和日志文件
?
流過濾器
·以一個介質(zhì)流對象為基礎(chǔ)層層構(gòu)建過濾器流,最終形成的流對象能在數(shù)據(jù)的輸入輸出過程中,逐層使用過濾器流的方法來讀寫數(shù)據(jù)
?
Data
·DataInputStream
·DataOutputStream
·用以讀寫二進制方式表達的基本數(shù)據(jù)類型的數(shù)據(jù)
?
文本流
?
Reader/Writer
·二進制數(shù)據(jù)采用InputStream/OutputStream
·文本數(shù)據(jù)采用Reader/Writer
?
在流上建立文本處理
PrintWriter pw == new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(“abc.txt”))));
?
Reader
·常用的是BufferedReader
·readLine()
?
LineNumberReader
·可以得到行號
·getLineNumber()
?
FileReader
·InputStreamReader類的子類,所有方法都從父類中繼承而來
·FileReader(File file)
·在給定從中讀取數(shù)據(jù)的File的情況下創(chuàng)建一個新FileReader
·FileReader(String fileName)
??? ·在給定從中讀取數(shù)據(jù)的文件名的情況下創(chuàng)建一個新FileReader
·FileReader不能指定編碼轉(zhuǎn)換方式
?
漢字編碼
·InputStreamReader(InputStream in)
??? 創(chuàng)建一個使用默認字符集的InputStreamReader
·InputStreamReader(InputStream in, Charset cs)
??? 創(chuàng)建使用給定字符集的InputStreamReader
·InputStreamReader(InputStream in, CharsetDecoder dec)
??? 創(chuàng)建使用給定字符集解碼器的InputStreamReader
·InputStreamReader(InputStream in, String charsetName)
??? 創(chuàng)建使用指定字符集的InputStreamReader
?
格式化輸入輸出
·PrintWriter
??? ·format();
??? ·printf();用法與C一致
??? ·print();
??? ·println();
·Scanner
·在InputStream或Reader上建立一個Scanner對象可以從流中的文本中解析出以文本表達的各種基本類型
·next…();
?
Stream/Reader/Scanner
數(shù)據(jù)是二進制的
是->用InputStream
否->表達的是文本
??? 是->用Reader
??? 否->用Scanner
?
?
阻塞/非阻塞
·read()函數(shù)是阻塞的,在讀到所需的內(nèi)容之前會停下來等
??? ·使用read()的更“高級”的函數(shù),如newInt()、readLine()都是這樣的
??? ·所以常用單獨的線程來做socket讀的等待,或使用nio的channel選擇機制
·對于socket,可以設(shè)置SO時間
??? ·setSoTimeout(int timeOut)
?
?對象串行化
·ObjectInputStream類
??? ·readObject()
·ObjectOutputStream類
??? ·writeObjeact()
·Serializable接口
?
轉(zhuǎn)載于:https://www.cnblogs.com/lilinilil/p/8855288.html
總結(jié)
以上是生活随笔為你收集整理的【编程语言】Java基础进阶——面向对象部分的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【laravel5.4】laravel5
- 下一篇: ROS学习之参数