您好,登錄后才能下訂單哦!
這篇文章主要介紹javaSE知識點有哪些,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
javaSE知識點匯總
Java基礎知識精華部分
寫代碼:
1,明確需求。我要做什么?
2,分析思路。我要怎么做?1,2,3。
3,確定步驟。每一個思路部分用到哪些語句,方法,和對象。
4,代碼實現。用具體的java語言代碼把思路體現出來。
學習新技術的四點:
1,該技術是什么?
2,該技術有什么特點(使用注意):
3,該技術怎么使用。demo
4,該技術什么時候用?test。
-----------------------------------------------------------------------------------------------
一:java概述:
1991 年Sun公司的James Gosling等人開始開發名稱為 Oak 的語言,希望用于控制嵌入在有線電視交換盒、PDA等的微處理器;
1994年將Oak語言更名為Java;
Java的三種技術架構:
JAVASE:Java Platform Standard Edition,完成桌面應用程序的開發,是其它兩者的基礎;
JAVAEE:Java Platform Enterprise Edition,開發企業環境下的應用程序,主要針對web程序開發;
JAVAME:Java Platform Micro Edition,開發電子消費產品和嵌入式設備,如手機中的程序;
1,JDK:Java Development Kit,java的開發和運行環境,是java的開發工具,包括 jre。
2,JRE:Java Runtime Environment,java程序的運行環境,java運行的所需的類庫+JVM(java虛擬機)。
3,配置環境變量:讓java jdk\bin目錄下的工具,可以在任意目錄下運行,原因是,將該工具所在目錄告訴了系統,當使用該工具時,由系統幫我們去找指定的目錄。
環境變量的配置:
1):永久配置方式:JAVA_HOME=%安裝路徑%\Java\jdk
path=%JAVA_HOME%\bin
2):臨時配置方式:set path=%path%;C:\Program Files\Java\jdk\bin
特點:系統默認先去當前路徑下找要執行的程序,如果沒有,再去path中設置的路徑下找。
classpath的配置:
1):永久配置方式:classpath=.;c:\;e:\
2):臨時配置方式:set classpath=.;c:\;e:\
注意:在定義classpath環境變量時,需要注意的情況
如果沒有定義環境變量classpath,java啟動jvm后,會在當前目錄下查找要運行的類文件;
如果指定了 classpath,那么會在指定的目錄下查找要 運行的類文件。
還會在當前目錄找嗎?兩種情況:
1):如果classpath的值結尾處有分號,在具體路徑中沒有找到運行的類,會默認在當前目錄再找一次。
2):如果classpath的值結果出沒有分號,在具體的路徑中沒有找到運行的類,不會再當前目錄找。
一般不指定分號,如果沒有在指定目錄下找到要運行的類文件,就報錯,這樣可以調試程序。
4,javac命令和java命令做什么事情呢?
java是分兩部分的:一個是編譯,一個是運行。
javac:負責的是編譯的部分,當執行javac時,會啟動java的編譯器程序。對指定擴展名的.java文件進行編譯。 生成了jvm可以識別的字節碼文件。也就是class文件,也就是java的運行程序。
java:負責運行的部分.會啟動jvm.加載運行時所需的類庫,并對class文件進行執行.
一個文件要被執行,必須要有一個執行的起始點,這個起始點就是main函數.
----------------------------------------------------------------------------------------------
二:java語法基礎:
1,關鍵字:其實就是某種語言賦予了特殊含義的單詞。
保留字:其實就是還沒有賦予特殊含義,但是準備日后要使用過的單詞。
2,標示符:其實就是在程序中自定義的名詞。比如類名,變量名,函數名。包含 0-9、a-z、$、_ ;
注意:
1),數字不可以開頭。
2),不可以使用關鍵字。
3,常量:是在程序中的不會變化的數據。
4,變量:是內存中的一個存儲空間,用于存儲 常量數據。
作用:方便于運算。因為有些數據不確定。所以確定該數據的名詞和存儲空間。
特點:變量空間可以重復使用。
什么時候定義變量?只要是數據不確定的時候,就定義變量。
變量空間的開辟需要什么要素呢?
1,這個空間要存儲什么數據?數據類型。
2,這個空間叫什么名字啊?變量名稱。
3,這個空間的第一次的數據是什么? 變量的初始化值。
變量的作用域和生存期:
變量的作用域:
作用域從變量定義的位置開始,到該變量所在的那對大括號結束;
生命周期:
變量從定義的位置開始就在內存中活了;
變量到達它所在的作用域的時候就在內存中消失了;
數據類型:
1):基本數據類型:整數型byte、short、int、long、浮點型float、double、字符型char、布爾型boolean
2):引用數據類型: 數組、類、接口。
級別從低到高為:byte,char,short(這三個平級)-->int-->float-->long-->double
自動類型轉換:從低級別到高級別,系統自動轉的;
強制類型轉換:什么情況下使用?把一個高級別的數賦給一個別該數的級別低的變量;
運算符號:
1)、算術運算符。
+ - * / % %:任何整數模2不是0就是1,所以只要改變被模數就可以實現開關運算。
+:連接符。
++,--
2)、賦值運算符。
= += -= *= /= %=
3)、比較運算符。
特點:該運算符的特點是:運算完的結果,要么是true,要么是false。
4)、邏輯運算符。
& | ^ ! && ||
邏輯運算符除了 ! 外都是用于連接兩個boolean類型表達式。
&: 只有兩邊都為true結果是true。否則就是false。
|:只要兩邊都為false結果是false,否則就是true
^:異或:和或有點不一樣。
兩邊結果一樣,就為false。
兩邊結果不一樣,就為true.
& 和 &&區別: & :無論左邊結果是什么,右邊都參與運算。
&&:短路與,如果左邊為false,那么右邊不參數與運算。
| 和|| 區別:|:兩邊都運算。
||:短路或,如果左邊為true,那么右邊不參與運算。
5)、位運算符:用于操作二進制位的運算符。
& | ^
<< >> >>>(無符號右移)
練習:對兩個變量的數據進行互換。不需要第三方變量。
int a = 3,b = 5;-->b = 3,a = 5;
a = a + b; a = 8;
b = a - b; b = 3;c
a = a - b; a = 5;
a = a ^ b;//
b = a ^ b;//b = a ^ b ^ b = a
a = a ^ b;//a = a ^ b ^ a = b;
練習:高效的算出 2*8 = 2<<3;
5,語句。
If switch do while while for
這些語句什么時候用?
1】、當判斷固定個數的值的時候,可以使用if,也可以使用switch。
但是建議使用switch,效率相對較高。
switch(變量){
case 值:要執行的語句;break;
…
default:要執行的語句;
}
工作原理:用小括號中的變量的值依次和case后面的值進行對比,
和哪個case后面的值相同了,就執行哪個case后面的語句,如果沒有相同的則執行default后面的語句;
細節:
1):break是可以省略的,如果省略了就一直執行到遇到break為止;
2):switch 后面的小括號中的變量應該是byte,char,short,int四種類型中的一種;
3):default可以寫在switch結構中的任意位置:
如果將default語句放在了第一行,則不管expression與case中的value是否匹配,程序會從default開始執行直到第一個break出現。
2】、當判斷數據范圍,獲取判斷運算結果boolean類型時,需要使用if。
3】、當某些語句需要執行很多次時,就用循環結構。
while和for可以進行互換:
區別在于:如果需要定義變量控制循環次數,建議使用for。因為for循環完畢,變量在內存中釋放。
break:作用于switch ,和循環語句,用于跳出,或者稱為結束。
break語句單獨存在時,下面不要定義其他語句,因為執行不到,編譯會失敗。當循環嵌套時,break只跳出當前所在循環。要跳出嵌套中的外部循環,只要給循環起名字即可,這個名字稱之為標號。
continue:只作用于循環結構,繼續循環用的。
作用:結束本次循環,繼續下次循環。該語句單獨存在時,下面不可以定義語句,執行不到。
6,函 數:為了提高代碼的復用性,可以將其定義成一個單獨的功能,該功能的體現就是java中的函數。函數就是體現之一。
java中的函數的定義格式:
修飾符 返回值類型 函數名(參數類型 形式參數1,參數類型 形式參數1,…){
執行語句;
return 返回值;
}
當函數沒有具體的返回值時,返回的返回值類型用void關鍵字表示。
如果函數的返回值類型是void時,return語句可以省略不寫的,系統會幫你自動加上。
return的作用:結束函數。結束功能。
如何定義一個函數?
函數其實就是一個功能,定義函數就是實現功能,通過兩個明確來完成:
1)、明確該功能的運算完的結果,其實是在明確這個函數的返回值類型。
2)、在實現該功能的過程中是否有未知內容參與了運算,其實就是在明確這個函數的參數列表(參數類型&參數個數)。
函數的作用:
1)、用于定義功能。
2)、用于封裝代碼提高代碼的復用性。
注意:函數中只能調用函數,不能定義函數。
主函數:
1)、保證該類的獨立運行。
2)、因為它是程序的入口。
3)、因為它在被jvm調用。
函數定義名稱是為什么呢?
答:1)、為了對該功能進行標示,方便于調用。
2)、為了通過名稱就可以明確函數的功能,為了增加代碼的閱讀性。
重載的定義是:在一個類中,如果出現了兩個或者兩個以上的同名函數,只要它們的參數的個數,或者參數的類型不同,即可稱之為該函數重載了。
如何區分重載:當函數同名時,只看 參數列表。和返回值類型沒關系。
7,數 組:用于存儲同一類型數據的一個容器。好處:可以對該容器中的數據進行編號,從0開始。數組用于封裝數據,就是一個具體的實體。
如何在java中表現一個數組呢?三種表現形式。
1)元素類型[ ] 變量名 = new 元素類型[元素的個數];
2)元素類型[ ] 變量名 = {元素1,元素2...};
3)元素類型[ ] 變量名 = new 元素類型[ ]{元素1,元素2...};
數組定義格式詳解:
元素類型 即:數組存儲的數據類型: 創建的數組容器可以存儲什么數據類型。
[ ] : 表示數組。
變量名:即 數組名字:為定義的數組起個變量名,滿足標識符規范,可以使用名字操作數組。
new:關鍵字,創建數組使用的關鍵字。
元素類型,即:數組存儲的數據類型: 創建的數組容器可以存儲什么數據類型。[長度]:數組的長度,表示數組容器中可以存儲多少個元素。
注意:數組有定長特性,長度一旦指定,不可更改
數組的訪問:
索引: 每一個存儲到數組的元素,都會自動的擁有一個編號,從0開始,這個自動編號稱為數組索引(index),可以通過數組的索引訪問到數組中的元素。
格式:數組名[索引]
數組的長度屬性: 獲取到數組的長度: 數組名.length 其中 屬性length的執行結果是數組的長度,int類型結果。
數組的最大索引值為 :數組名.length-1
索引訪問數組中的元素:
1)數組名[索引]=數值,為數組中的元素賦值,如:arr[0] = 6;
2)變量=數組名[索引],獲取出數組中的元素,如:int i = arr[0]
數組作為方法參數和返回值
數組作為方法參數傳遞,傳遞的參數是數組內存的地址
數組作為方法的返回值,返回的是數組的內存地址
注意:方法的參數為基本類型時,傳遞的是數據值. 方法的參數為引用類型時,傳遞的是地址值
Arrays類:java.util.Arrays 此類包含用來操作數組的各種方法,比如排序和搜索等。其所有方法均為靜態方法
操作數組的方法
1】public static String toString(int[] a) :返回指定數組內容的字符串表示形式
2】public static void sort(int[] a) :對指定的 int 型數組按數字升序進行排序
1】例子:
public static void main(String[] args) {
// 定義int 數組
int[] arr = {2,34,35,4,657,8,69,9};
// 打印數組,輸出地址值
System.out.println(arr); // [I@2ac1fdc4
// 數組內容轉為字符串
String s = Arrays.toString(arr);
// 打印字符串,輸出內容
System.out.println(s); // [2, 34, 35, 4, 657, 8, 69, 9]
}
2】例子:
public static void main(String[] args) {
// 定義int 數組
int[] arr = {24, 7, 5, 48, 4, 46, 35, 11, 6, 2};
System.out.println("排序前:"+ Arrays.toString(arr)); // 排序前:[24, 7, 5, 48, 4, 46, 35, 11, 6,2]
// 升序排序
Arrays.sort(arr);
System.out.println("排序后:"+ Arrays.toString(arr));// 排序后:[2, 4, 5, 6, 7, 11, 24, 35, 46,48]
}
---------------------------------------------------------
//二分查找法。必須有前提:數組中的元素要有序。
public static int halfSeach_2(int[] arr,int key){
int min,max,mid;//定義最小,最大,中間數
min = 0;//最小為0
max = arr.length-1;// 最大為數組的長度-1
mid = (max+min)>>1; //(max+min)/2;//中間數為最大加最小除以2
while(arr[mid]!=key){//如果數組中間值不等于key
if(key>arr[mid]){//如果key>中間值
min = mid + 1;
}
else if(key<arr[mid])
max = mid - 1;
if(max<min)
return -1;
mid = (max+min)>>1;
}
return mid;
}
---------------------------------------------------------
Java虛擬機(jvm)的內存劃分:
1:寄存器。2:本地方法區。3:方法區。4:棧。5:堆。
區域名稱 作用
1.寄存器 :給CPU使用,和我們開發無關。
2.本地方法棧: JVM在使用操作系統功能的時候使用,和我們開發無關。
3.方法區 :存儲可以運行的class文件。
4.方法棧 :方法運行時使用的內存,比如main方法運行,進入方法棧中執行。
5.堆內存 :存儲對象或者數組,new來創建的,都存儲在堆內存。
棧:存儲的都是局部變量 ( 函數中定義的變量,函數上的參數,語句中的變量 );
只要數據運算完成所在的區域結束,該數據就會被釋放。
堆:用于存儲數組和對象,也就是實體。啥是實體啊?就是用于封裝多個數據的。
1:每一個實體都有內存首地址值。
2:堆內存中的變量都有默認初始化值。因為數據類型不同,值也不一樣。
3:垃圾回收機制。
---------------------------------------------------------
Math類:java.lang.Math 類包含用于執行基本數學運算的方法,如初等指數、對數、平方根和三角函數。
1】public static double abs(double a) :返回 double 值的絕對值 如:double d1 = Math.abs(‐5); //d1的值為5
2】public static double ceil(double a) :返回大于等于參數的最小的整數。如:double d2 = Math.ceil(‐3.3); //d2的值為 ‐3
3】public static double floor(double a) :返回小于等于參數最大的整數。如:double d2 = Math.floor(‐3.3); //d2的值為‐4.0
4】public static long round(double a) :返回最接近參數的 long。(相當于四舍五入方法)
如:long d1 = Math.round(5.5); //d1的值為6.0
long d2 = Math.round(5.4); //d2的值為5.0
三:面向對象:★★★★★
面向對象:強調的是通過調用對象的行為來實現功能,而不是自己一步一步的去操作實現
面向對象特點: 1:將復雜的事情簡單化。
2:面向對象將以前的過程中的執行者,變成了指揮者。
3:面向對象這種思想是符合現在人們思考習慣的一種思想。
面向過程和面向對象的區別:
舉例
洗衣服:
面向過程:把衣服脫下來-->找一個盆-->放點洗衣粉-->加點水-->浸泡10分鐘-->揉一揉-->清洗衣服-->擰干-->晾起來
面向對象:把衣服脫下來-->打開全自動洗衣機-->扔衣服-->按鈕-->晾起來
區別:
面向過程:強調步驟。
面向對象:強調對象,這里的對象就是洗衣機。面向對象的語言中,包含了三大基本特征,即封裝、繼承和多態
過程和對象 在我們的程序中是如何體現的呢?過程其實就是函數;對象是將函數等一些內容進行了封裝。
匿名對象使用場景:
1:當對方法只進行一次調用的時候,可以使用匿名對象。
2:當對象對成員進行多次調用時,不能使用匿名對象。必須給對象起名字。
類和對象
類:是一組相關屬性和行為的集合。可以看成是一類事物的模板,使用事物的屬性特征和行為特征來描述該類事物
屬性:就是該事物的狀態信息。行為:就是該事物能夠做什么
對象:泛指現實中一切事物,是一類事物的具體體現。對象是類的一個實例(對象并不是找個女朋友),必然具備該類事物的屬性和行為。
舉例:一只小貓。屬性:tom、5kg、2 years、yellow。 行為:溜墻根走、蹦跶的跑、喵喵叫。
類與對象的關系:
類是對一類事物的描述,是抽象的。
對象是一類事物的實例,是具體的。
類是對象的模板,對象是類的實體
類的定義:
定義類:就是定義類的成員,包括成員變量和成員方法。在定義前,必須先要對事物進行屬性和行為的分析,才可以用代碼來體現。
1:成員變量:其實對應的就是事物的屬性。在類中,方法外
2:成員函數:其實對應的就是事物的行為。只不過要把static去掉
對象的使用
對象的使用格式:創建對象:類名 對象名 = new 類名();
使用對象訪問類中的成員:
1)對象名.成員變量;
2)對象名.成員方法();
成員變量的默認值
數據類型 默認值
基本類型: 整數(byte,short,int,long) 0
浮點數(float,double) 0.0
字符(char) '\u0000'
布爾(boolean) false
引用類型 : 數組,類,接口 null
成員變量和局部變量的區別:
1:成員變量直接定義在類中,方法外
局部變量定義在方法中,參數上,語句中。
2:成員變量在這個類中有效。
局部變量只在自己所屬的大括號內有效,大括號結束,局部變量失去作用域。
3:成員變量存在于堆內存中,隨著對象的產生而存在,消失而消失。
局部變量存在于棧內存中,隨著所屬區域的運行而存在,結束而釋放。
java面向對象編程的三大特性:封裝、繼承和多態。
面向對象的特征主要有 封裝 繼承 多態 以及 抽象。
相關概念:
封裝:隱藏內部實現,提供給外界實現方法, 好處:不影響類內部構造,保護數據。是指對象把自己的數據和對數據的操作封裝在了一起;
封裝具有良好的模塊性,目標就是實現編程思想中的 高內聚,低耦合的思想(防止互相依賴帶來的變動影響)。
在面向對象中,對象是封裝的基本單位,所以,在面向對象的語言中,封裝的使用使代碼更為清晰。
在面向對象中,封裝就是將一個對象的行為屬性封裝到一個模塊中,也就是類中,屬性用變量定義,行為用方法定義,例如 實體
封裝的回答方式從 思想 到 對象 到 如何封裝一個對象。
繼承: 兩個類具有包含關系(is-a),重用父類代碼,為多態做鋪墊。
繼承的回答方式:子類對父類的繼承,子類把父類的數據和對數據的操作繼承了過來,同時又增添了 自己的數據 和 對數據的操作;
多態:主要有兩種類型的多態。一種是操作的名稱的多態,即多個操作的名稱一樣,但接受的消息類型必須不同;另一種是與繼承相關的多態,來自不同類型的對象對同一操作的調用產生不同的效果。
多態發生在代碼的運行期。(泛型)
多態增強了軟件的靈活性和擴展性(實現了 行為共享(方法共享))
為系統組件或模塊之間解耦提供了解決方案
多態的回答方式 多態的產生時期,和好處
實現多態,有二種方式,覆蓋,重載。
覆蓋,是指子類重新定義父類的虛函數的做法。
重載,是指允許存在多個同名函數,而這些函數的參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。
函數重載:是指允許存在多個同名函數,這些函數的參數列表不同,或許是參數個數不同,或許是參數類型不同,或許是兩者都不同。
重要一點:函數重載是發生在同一個類中。調用時,根據參數類型的不同進行調用,同時編譯器在編譯期間就確定了要調用的函數。(函數的重載與多態無關)。
函數覆蓋:函數覆蓋也被稱為函數重寫(override),是子類重新定義基類虛函數的方法。
構成函數覆蓋的條件:
(1)基類的函數必須是虛函數(virtual進行聲明)
(2)發生覆蓋的兩個函數必須分別位于派生類和基類中
(3)函數名稱和參數列表必須完全相同
由于c++,c#多態性是通過虛函數來實現的,所以函數覆蓋總是和多態聯系在一起,并且是程序在運行時才確定要調用的函數,因此也成為動態綁定或者后期綁定。
函數隱藏:指子類中具有和基類同名的函數,但是并不考慮參數列表是否相同,從而在子類中隱藏了基類的同名函數。有以下兩種情況:
(1)子類函數和基類函數 完全相同,只是基類的函數 沒有使用virtual關鍵字,此時 基類的函數將被隱藏。
(2)子類函數和基類函數 名字相同,但是參數列表不同,在這種情況下,無論基類的函數是否聲明為virtual,基類的函數都將被隱藏。
抽象:找出某些對象共有的一些特點,然后歸到一個類里面,這個類的創建只考慮這些對象的相似之處
封裝
封 裝(面向對象特征之一):是指隱藏對象的屬性和實現細節,僅對外提供公共訪問方式。
好處:將變化隔離;便于使用;提高重用性;安全性。
1.封裝原則:將屬性隱藏起來,若需要訪問某個屬性,提供公共方法對其訪問
2.封裝的步驟
1】. 使用 private 關鍵字來修飾成員變量。
2】. 對需要訪問的成員變量,提供對應的一對 getXxx 方法 、 setXxx 方法
3.封裝的操作——private關鍵字
private的含義
1. private是一個權限修飾符,代表最小權限。
2. 可以修飾成員變量和成員方法。
3. 被private修飾后的成員變量和成員方法,只在本類中才能訪問
private的使用格式:private 數據類型 變量名
如:private int age;//私有的訪問權限最低,只有在本類中的訪問有效。
注意:私有僅僅是封裝的一種體現形式而已。
私有的成員:其他類不能直接創建對象訪問,所以只有通過本類對外提供具體的訪問方式來完成對私有的訪問,可以通過對外提供函數的形式對其進行訪問。
好處:可以在函數中加入邏輯判斷等操作,對數據進行判斷等操作。
總結:開發時,記住,屬性是用于存儲數據的,直接被訪問,容易出現安全隱患,所以,類中的屬性通常被私有化,并對外提供公共的訪問方法。
這個方法一般有兩個,規范寫法:對于屬性 xxx,可以使用setXXX(),getXXX()對其進行操作
4.封裝優化
封裝優化51——this關鍵字
this的含義:this代表所在類的當前對象的引用(地址值),即對象自己的引用。this:代表對象。就是所在函數所屬對象的引用。
this到底代表什么呢?哪個對象調用了this所在的函數,this就代表哪個對象,就是哪個對象的引用。即誰在調用,this就代表誰。
開發時,什么時候使用this呢?
在定義功能時,如果該功能內部使用到了調用該功能的對象,這時就用this來表示這個對象。
this 還可以用于構造函數間的調用。
調用格式:this(實際參數);
1】this對象后面跟上. 調用的是成員屬性和成員方法(一般方法);
this使用格式: this.成員變量 注意:方法中只有一個變量名時,默認也是使用 this 修飾,可以省略不寫
2】this對象后面跟上 () 調用的是本類中的對應參數的構造函數。
注意:用this調用構造函數,必須定義在構造函數的第一行。因為構造函數是用于初始化的,所以初始化動作一定要執行。否則編譯失敗。
封裝優化2——構造方法
當一個對象被創建時候,構造方法用來初始化該對象,給對象的成員變量賦初始值。
注意:無論你是否自定義構造方法,所有的類都有構造方法,因為Java自動提供了一個無參數構造方法,一旦自己定義了構造方法,Java自動提供的默認無參數構造方法就會失效。
構造方法的定義格式
修飾符 構造方法名(參數列表){
// 方法體
}
構造方法的寫法上,方法名與它所在的類名相同。它沒有返回值,所以不需要返回值類型,甚至不需要void。如:
public class Student {
private String name;private int age;
// 無參數構造方法
public Student() {}
// 有參數構造方法
public Student(String name,int age) {
this.name = name;
this.age = age;
}
}
注意事項
1. 如果你不提供構造方法,系統會給出無參數構造方法。
2. 如果你提供了構造方法,系統將不再提供無參數構造方法。
3. 構造方法是可以重載的,既可以定義參數,也可以不定義參數
5.標準代碼——JavaBean
JavaBean 是 Java語言編寫類的一種標準規范。符合 JavaBean 的類,要求類必須是具體的和公共的,并且具有無參數的構造方法,提供用來操作成員變量的 set 和 get 方法
格式:
public class ClassName{
//成員變量
//構造方法
//無參構造方法【必須】
//有參構造方法【建議】
//成員方法
//getXxx()
//setXxx()
}
繼 承(面向對象特征之一)
好處:
1:提高了代碼的復用性。
2:讓類與類之間產生了關系,提供了另一個特征多態的前提。
父類的由來:其實是由多個類不斷向上抽取共性內容而來的。
java中對于繼承,java只支持單繼承。java雖然不直接支持多繼承,但是保留了這種多繼承機制,進行改良。
單繼承:一個類只能有一個父類。
多繼承:一個類可以有多個父類。
為什么不支持多繼承呢?
因為當一個類同時繼承兩個父類時,兩個父類中有相同的功能,那么子類對象調用該功能時,運行哪一個呢?因為父類中的方法中存在方法體。
但是java支持多重繼承。A繼承B B繼承C C繼承D。
多重繼承的出現,就有了繼承體系。體系中的頂層父類是通過不斷向上抽取而來的。它里面定義的該體系最基本最共性內容的功能。
所以,一個體系要想被使用,直接查閱該系統中的父類的功能即可知道該體系的基本用法。那么想要使用一個體系時,需要建立對象。建議建立最子類對象,因為最子類不僅可以使用父類中的功能。還可以使用子類特有的一些功能。
簡單說:對于一個繼承體系的使用,查閱頂層父類中的內容,創建最底層子類的對象。
子父類出現后,類中的成員都有了哪些特點:
1:成員變量。
當子父類中出現一樣的屬性時,子類類型的對象,調用該屬性,值是子類的屬性值。
如果想要調用父類中的屬性值,需要使用一個關鍵字:super
This:代表是本類類型的對象引用。
Super:代表是子類所屬的父類中的內存空間引用。
注意:子父類中通常是不會出現同名成員變量的,因為父類中只要定義了,子類就不用在定義了,直接繼承過來用就可以了。
2:成員函數。
當子父類中出現了一模一樣的方法時,建立子類對象會運行子類中的方法。好像父類中的方法被覆蓋掉一樣。所以這種情況,是函數的另一個特性:覆蓋(復寫,重寫)
什么時候使用覆蓋呢?當一個類的功能內容需要修改時,可以通過覆蓋來實現。
3:構造函數。
發現子類構造函數運行時,先運行了父類的構造函數。為什么呢?
原因:子類的所有構造函數中的第一行,其實都有一條隱身的語句super();
super(): 表示父類的構造函數,并會調用于參數相對應的父類中的構造函數。而super():是在調用父類中空參數的構造函數。
為什么子類對象初始化時,都需要調用父類中的函數?(為什么要在子類構造函數的第一行加入這個super()?)
因為子類繼承父類,會繼承到父類中的數據,所以必須要看父類是如何對自己的數據進行初始化的。所以子類在進行對象初始化時,先調用父類的構造函數,這就是子類的實例化過程。
注意:子類中所有的構造函數都會默認訪問父類中的空參數的構造函數,因為每一個子類構造內第一行都有默認的語句super();
如果父類中沒有空參數的構造函數,那么子類的構造函數內,必須通過super語句指定要訪問的父類中的構造函數。
如果子類構造函數中用this來指定調用子類自己的構造函數,那么被調用的構造函數也一樣會訪問父類中的構造函數。
問題:super()和this()是否可以同時出現的構造函數中。
兩個語句只能有一個定義在第一行,所以只能出現其中一個。
super()或者this():為什么一定要定義在第一行?
因為super()或者this()都是調用構造函數,構造函數用于初始化,所以初始化的動作要先完成。
繼承的細節:
什么時候使用繼承呢?
當類與類之間存在著所屬關系時,才具備了繼承的前提。a是b中的一種。a繼承b。狼是犬科中的一種。
英文書中,所屬關系:" is a "
注意:不要僅僅為了獲取其他類中的已有成員進行繼承。
所以判斷所屬關系,可以簡單看,如果繼承后,被繼承的類中的功能,都可以被該子類所具備,那么繼承成立。如果不是,不可以繼承。
細節二:
在方法覆蓋時,注意兩點:
1:子類覆蓋父類時,必須要保證,子類方法的權限必須大于等于父類方法權限可以實現繼承。否則,編譯失敗。
2:覆蓋時,要么都靜態,要么都不靜態。 (靜態只能覆蓋靜態,或者被靜態覆蓋)
繼承的一個弊端:打破了封裝性。對于一些類,或者類中功能,是需要被繼承,或者復寫的。
這時如何解決問題呢?介紹一個關鍵字,final:最終。
final特點:
1:這個關鍵字是一個修飾符,可以修飾類,方法,變量。
2:被final修飾的類是一個最終類,不可以被繼承。
3:被final修飾的方法是一個最終方法,不可以被覆蓋。
4:被final修飾的變量是一個常量,只能賦值一次。
其實這樣的原因的就是給一些固定的數據起個閱讀性較強的名稱。
不加final修飾不是也可以使用嗎?那么這個值是一個變量,是可以更改的。加了final,程序更為嚴謹。常量名稱定義時,有規范,所有字母都大寫,如果由多個單詞組成,中間用 _ 連接。
抽象類: abstract
抽象:不具體,看不明白。抽象類表象體現。
在不斷抽取過程中,將共性內容中的方法聲明抽取,但是方法不一樣,沒有抽取,這時抽取到的方法,并不具體,需要被指定關鍵字abstract所標示,聲明為抽象方法。
抽象方法所在類一定要標示為抽象類,也就是說該類需要被abstract關鍵字所修飾。
抽象類的特點:
1:抽象方法只能定義在抽象類中,抽象類和抽象方法必須由abstract關鍵字修飾(可以描述類和方法,不可以描述變量)。
2:抽象方法只定義方法聲明,并不定義方法實現。
3:抽象類不可以被創建對象(實例化)。
4:只有通過子類繼承抽象類并覆蓋了抽象類中的所有抽象方法后,該子類才可以實例化。否則,該子類還是一個抽象類。
抽象類的細節:
1:抽象類中是否有構造函數?有,用于給子類對象進行初始化。
2:抽象類中是否可以定義非抽象方法?
可以。其實,抽象類和一般類沒有太大的區別,都是在描述事物,只不過抽象類在描述事物時,有些功能不具體。所以抽象類和一般類在定義上,都是需要定義屬性和行為的。只不過,比一般類多了一個抽象函數。而且比一般類少了一個創建對象的部分。
3:抽象關鍵字abstract和哪些不可以共存?final ,private , static
4:抽象類中可不可以不定義抽象方法?可以。抽象方法目的僅僅為了不讓該類創建對象。
主函數和構造函數以及一般函數
1.主函數
類中怎么沒有定義主函數呢?
注意:主函數的存在,僅為該類是否需要獨立運行,如果不需要,主函數是不用定義的。
主函數的解釋:保證所在類的獨立運行,是程序的入口,被jvm調用。
2.構造函數:用于給對象進行初始化,是給與之對應的對象進行初始化,它具有針對性,函數中的一種。
構造函數特點:
1:該函數的名稱和所在類的名稱相同。
2:不需要定義返回值類型。
3:該函數沒有具體的返回值。
記住:所有對象創建時,都需要初始化才可以使用。
注意事項:一個類在定義時,如果沒有定義過構造函數,那么該類中會自動生成一個空參數的構造函數,為了方便該類創建對象,完成初始化。如果在類中自定義了構造函數,那么默認的構造函數就沒有了。
一個類中,可以有多個構造函數,因為它們的函數名稱都相同,所以只能通過參數列表來區分。所以,一個類中如果出現多個構造函數。它們的存在是以重載體現的。
構造函數和一般函數有什么區別呢?
1:兩個函數定義格式不同。
2:構造函數是在對象創建時,就被調用,用于初始化,而且初始化動作只執行一次。
一般函數,是對象創建后,需要調用才執行,可以被調用多次。
什么時候使用構造函數呢?
分析事物時,發現具體事物一出現,就具備了一些特征,那就將這些特征定義到構造函數內。
構造代碼塊和構造函數有什么區別?
構造代碼塊:是給所有的對象進行初始化,也就是說,所有的對象都會調用一個代碼塊。只要對象一建立。就會調用這個代碼塊。
構造函數:是給與之對應的對象進行初始化。它具有針對性。
Person p = new Person();
創建一個對象都在內存中做了什么事情?
1:先將硬盤上指定位置的Person.class文件加載進內存。
2:執行main方法時,在棧內存中開辟了main方法的空間(壓棧-進棧),然后在main方法的棧區分配了一個變量p。
3:在堆內存中開辟一個實體空間,分配了一個內存首地址值。new
4:在該實體空間中進行屬性的空間分配,并進行了默認初始化。
5:對空間中的屬性進行顯示初始化。
6:進行實體的構造代碼塊初始化。
7:調用該實體對應的構造函數,進行構造函數初始化。()
8:將首地址賦值給p ,p變量就引用了該實體。(指向了該對象)
--------------------------------------------------------------------------------------------
static:★★★ 關鍵字
static 關鍵字,是一個修飾符,可以修飾變量、方法和代碼塊。在使用的過程中,其主要目的還是想在不創建對象的情況下,去調用方法。
靜態原理
static 修飾的內容:
是隨著類的加載而加載的,且只加載一次。
存儲于一塊固定的內存區域(靜態區),所以,可以直接被類名調用。
它優先于對象存在,所以,可以被所有對象共享。
1.修飾變量:
類變量:用 static關鍵字修飾的成員變量。該類的每個對象都共享同一個類變量的值。任何對象都可更改該類變量的值,但也可在不創建該類的對象時操作類變量
當 用于修飾成員(成員變量和成員函數)時,被修飾的成員是屬于類的,而不是單單是屬于某個對象的。也就是說,既然屬于類,就可以不靠創建對象來調用了。
類變量 定義格式:static 數據類型 變量名
如:static int numberID;
2.修飾方法:
靜態方法
類方法:使用 static關鍵字修飾的成員方法。靜態方法在聲明中有 static ,建議使用類名來調用,而不需要
創建類的對象。調用方式非常簡單
定義格式:
修飾符 static 返回值類型 方法名 (參數列表){
// 執行語句
}
靜態方法調用的注意事項:
1】靜態方法可以直接訪問類變量和靜態方法。
2】靜態方法只能訪問靜態成員,不能直接訪問普通成員變量或成員方法。反之,成員方法可以直接訪問類變量或靜態方法。
3】靜態方法中,不能使用this關鍵字。
調用格式
被static修飾的成員可以并且建議通過類名直接訪問。雖然也可以通過對象名訪問靜態成員,原因即多個對象均屬
于一個類,共享使用同一個靜態成員,但是不建議,會出現警告信息。
格式:
// 訪問類變量
類名.類變量名;
// 調用靜態方法
類名.靜態方法名(參數);
3.修飾代碼塊
靜態代碼塊
靜態代碼塊:定義在成員位置,使用static修飾的代碼塊{ }。
位置:類中方法外。
執行:隨著類的加載而執行且執行一次,優先于main方法和構造方法的執行。
作用:給類變量進行初始化賦值。
格式:
public class ClassName{
static {
// 執行語句
}
}
static特點:
1,想要實現對象中的共性數據的對象共享。可以將這個數據進行靜態修飾。
2,被靜態修飾的成員,可以直接被類名所調用。也就是說,靜態的成員多了一種調用方式。類名.靜態方式。
3,靜態隨著類的加載而加載。而且優先于對象存在。
static弊端:
1,有些數據是對象特有的數據,是不可以被靜態修飾的。因為那樣的話,特有數據會變成對象的共享數據。這樣對事物的描述就出了問題。所以,在定義靜態時,必須要明確,這個數據是否是被對象所共享的。
2,靜態方法只能訪問靜態成員,不可以訪問非靜態成員。
因為靜態方法加載時,優先于對象存在,所以沒有辦法訪問對象中的成員。
3,靜態方法中不能使用this,super關鍵字。
因為this代表對象,而靜態在時,有可能沒有對象,所以this無法使用。
4,主函數是靜態的。
什么時候定義靜態成員呢?或者說:定義成員時,到底需不需要被靜態修飾呢?
成員分兩種:
1,成員變量。(數據共享時靜態化)
該成員變量的數據是否是所有對象都一樣:
如果是,那么該變量需要被靜態修飾,因為是共享的數據。
如果不是,那么就說這是對象的特有數據,要存儲到對象中。
2,成員函數。(方法中沒有調用特有數據時就定義成靜態)
如果判斷成員函數是否需要被靜態修飾呢?
只要參考,該函數內是否訪問了對象中的特有數據:
如果有訪問特有數據,那方法不能被靜態修飾。
如果沒有訪問過特有數據,那么這個方法需要被靜態修飾。
成員變量和靜態變量的區別:
1,成員變量所屬于對象。所以也稱為實例變量。
靜態變量所屬于類。所以也稱為類變量。
2,成員變量存在于堆內存中。
靜態變量存在于方法區中。
3,成員變量隨著對象創建而存在。隨著對象被回收而消失。
靜態變量隨著類的加載而存在。隨著類的消失而消失。
4,成員變量只能被對象所調用 。
靜態變量可以被對象調用,也可以被類名調用。
所以,成員變量可以稱為對象的特有數據,靜態變量稱為對象的共享數據。
靜態的注意:靜態的生命周期很長。
靜態代碼塊:就是一個有靜態關鍵字標示的一個代碼塊區域。定義在類中。
作用:可以完成類的初始化。靜態代碼塊隨著類的加載而執行,而且只執行一次(new 多個對象就只執行一次)。如果和主函數在同一類中,優先于主函數執行。
Public:訪問權限最大。
static:不需要對象,直接類名即可。
void:主函數沒有返回值。
Main:主函數特定的名稱。
(String[] args):主函數的參數,是一個字符串數組類型的參數,jvm調用main方法時,傳遞的實際參數是 new String[0]。
jvm默認傳遞的是長度為0的字符串數組,我們在運行該類時,也可以指定具體的參數進行傳遞。可以在控制臺,運行該類時,在后面加入參數。參數之間通過空格隔開。jvm會自動將這些字符串參數作為args數組中的元素,進行存儲。
靜態代碼塊、構造代碼塊、構造函數同時存在時的執行順序:靜態代碼塊 à 構造代碼塊 à 構造函數;
生成Java幫助文檔:命令格式:javadoc –d 文件夾名 –auther –version *.java
/** //格式
*類描述
*@author 作者名
*@version 版本號
*/
/**
*方法描述
*@param 參數描述
*@return 返回值描述
*/
---------------------------------------------------------------------------------------------
設計模式:
設計模式:解決問題最行之有效的思想。是一套被反復使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。
java中有23種設計模式:
單例設計模式:★★★★★
解決的問題:保證一個類在內存中的對象唯一性。
比如:多程序讀取一個配置文件時,建議配置文件封裝成對象。會方便操作其中數據,又要保證多個程序讀到的是同一個配置文件對象,就需要該配置文件對象在內存中是唯一的。
Runtime()方法就是單例設計模式進行設計的。
如何保證對象唯一性呢?
思想:
1,不讓其他程序創建該類對象。
2,在本類中創建一個本類對象。
3,對外提供方法,讓其他程序獲取這個對象。
步驟:
1,因為創建對象都需要構造函數初始化,只要將本類中的構造函數私有化,其他程序就無法再創建該類對象;
2,就在類中創建一個本類的對象;
3,定義一個方法,返回該對象,讓其他程序可以通過方法就得到本類對象。(作用:可控)
代碼體現:
1,私有化構造函數;
2,創建私有并靜態的本類對象;
3,定義公有并靜態的方法,返回該對象。
---------------------------------------------
//餓漢式
class Single{
private Single(){} //私有化構造函數。
private static Single s = new Single(); //創建私有并靜態的本類對象。
public static Single getInstance(){ //定義公有并靜態的方法,返回該對象。
return s;
}
}
---------------------------------------------
//懶漢式:延遲加載方式。
class Single2{
private Single2(){}
private static Single2 s = null;
public static Single2 getInstance(){
if(s==null)
s = new Single2();
return s;
}
}
-------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------
模板方法設計模式:
解決的問題:當功能內部一部分實現時確定,一部分實現是不確定的。這時可以把不確定的部分暴露出去,讓子類去實現。
abstract class GetTime{
public final void getTime(){ //此功能如果不需要復寫,可加final限定
long start = System.currentTimeMillis();
code(); //不確定的功能部分,提取出來,通過抽象方法實現
long end = System.currentTimeMillis();
System.out.println("毫秒是:"+(end-start));
}
public abstract void code(); //抽象不確定的功能,讓子類復寫實現
}
class SubDemo extends GetTime{
public void code(){ //子類復寫功能方法
for(int y=0; y<1000; y++){
System.out.println("y");
}
}
}
---------------------------------------------------------------------------------------------
接 口:★★★★★
1:是用關鍵字interface定義的。
2:接口中包含的成員,最常見的有全局常量、抽象方法。
注意:接口中的成員都有固定的修飾符。
成員變量:public static final
成員方法:public abstract
interface Inter{
public static final int x = 3;
public abstract void show();
}
3:接口中有抽象方法,說明接口不可以實例化。接口的子類必須實現了接口中所有的抽象方法后,該子類才可以實例化。否則,該子類還是一個抽象類。
4:類與類之間存在著繼承關系,類與接口中間存在的是實現關系。
繼承用extends ;實現用implements ;
5:接口和類不一樣的地方,就是,接口可以被多實現,這就是多繼承改良后的結果。java將多繼承機制通過多現實來體現。
6:一個類在繼承另一個類的同時,還可以實現多個接口。所以接口的出現避免了單繼承的局限性。還可以將類進行功能的擴展。
7:其實java中是有多繼承的。接口與接口之間存在著繼承關系,接口可以多繼承接口。
接口都用于設計上,設計上的特點:(可以理解主板上提供的接口)
1:接口是對外提供的規則。
2:接口是功能的擴展。
3:接口的出現降低了耦合性。
抽象類與接口:
抽象類:一般用于描述一個體系單元,將一組共性內容進行抽取,特點:可以在類中定義抽象內容讓子類實現,可以定義非抽象內容讓子類直接使用。它里面定義的都是一些體系中的基本內容。
接口:一般用于定義對象的擴展功能,是在繼承之外還需這個對象具備的一些功能。
抽象類和接口的共性:都是不斷向上抽取的結果。
抽象類和接口的區別:
1:抽象類只能被繼承,而且只能單繼承。
接口需要被實現,而且可以多實現。
2:抽象類中可以定義非抽象方法,子類可以直接繼承使用。
接口中都有抽象方法,需要子類去實現。
3:抽象類使用的是 is a 關系。
接口使用的 like a 關系。
4:抽象類的成員修飾符可以自定義。
接口中的成員修飾符是固定的。全都是public的。
在開發之前,先定義規則,A和B分別開發,A負責實現這個規則,B負責使用這個規則。至于A是如何對規則具體實現的,B是不需要知道的。這樣這個接口的出現就降低了A和B直接耦合性。
------------------------------------------------------------------------------------------------
多 態★★★★★(面向對象特征之一):函數本身就具備多態性,某一種事物有不同的具體的體現。
體現:父類引用或者接口的引用指向了自己的子類對象。//Animal a = new Cat();
多態的好處:提高了程序的擴展性。
多態的弊端:當父類引用指向子類對象時,雖然提高了擴展性,但是只能訪問父類中具備的方法,不可以訪問子類中特有的方法。(前期不能使用后期產生的功能,即訪問的局限性)
多態的前提:
1:必須要有關系,比如繼承、或者實現。
2:通常會有覆蓋操作。
多態的出現思想上也做著變化:以前是創建對象并指揮對象做事情。有了多態以后,我們可以找到對象的共性類型,直接操作共性類型做事情即可,這樣可以指揮一批對象做事情,即通過操作父類或接口實現。
--------------------------------------------------------------
class 畢姥爺{
void 講課(){
System.out.println("企業管理");
}
void 釣魚(){
System.out.println("釣魚");
}
}
class 畢老師 extends 畢姥爺{
void 講課(){
System.out.println("JAVA");
}
void 看電影(){
System.out.println("看電影");
}
}
class {
public static void main(String[] args) {
畢姥爺 x = new 畢老師(); //畢老師對象被提升為了畢姥爺類型。
// x.講課();
// x.看電影(); //錯誤.
畢老師 y = (畢老師)x; //將畢姥爺類型強制轉換成畢老師類型。
y.看電影();//在多態中,自始自終都是子類對象在做著類型的變化。
}
}
---------------------------------------------------------------
如果想用子類對象的特有方法,如何判斷對象是哪個具體的子類類型呢?
可以可以通過一個關鍵字 instanceof ;//判斷對象是否實現了指定的接口或繼承了指定的類
格式:<對象 instanceof 類型> ,判斷一個對象是否所屬于指定的類型。
Student instanceof Person = true;//student繼承了person類
多態在子父類中的成員上的體現的特點:
1,成員變量:在多態中,子父類成員變量同名。
在編譯時期:參考的是引用型變量所屬的類中是否有調用的成員。(編譯時不產生對象,只檢查語法錯誤)
運行時期:也是參考引用型變量所屬的類中是否有調用的成員。
簡單一句話:無論編譯和運行,成員變量參考的都是引用變量所屬的類中的成員變量。
再說的更容易記憶一些:成員變量 --- 編譯運行都看 = 左邊。
2,成員函數。
編譯時期:參考引用型變量所屬的類中是否有調用的方法。
運行事情:參考的是對象所屬的類中是否有調用的方法。
為什么是這樣的呢?因為在子父類中,對于一模一樣的成員函數,有一個特性:覆蓋。
簡單一句:成員函數,編譯看引用型變量所屬的類,運行看對象所屬的類。
更簡單:成員函數 --- 編譯看 = 左邊,運行看 = 右邊。
3,靜態函數。
編譯時期:參考的是引用型變量所屬的類中是否有調用的成員。
運行時期:也是參考引用型變量所屬的類中是否有調用的成員。
為什么是這樣的呢?因為靜態方法,其實不所屬于對象,而是所屬于該方法所在的類。
調用靜態的方法引用是哪個類的引用調用的就是哪個類中的靜態方法。
簡單說:靜態函數 --- 編譯運行都看 = 左邊。
-----------------------------------------------------------------------------------------------
------java.lang.Object
Object:所有類的直接或者間接父類,Java認為所有的對象都具備一些基本的共性內容,這些內容可以不斷的向上抽取,最終就抽取到了一個最頂層的類中的,該類中定義的就是所有對象都具備的功能。
具體方法:
1,boolean equals(Object obj):用于比較兩個對象是否相等,其實內部比較的就是兩個對象地址。
而根據對象的屬性不同,判斷對象是否相同的具體內容也不一樣。所以在定義類時,一般都會復寫equals方法,建立本類特有的判斷對象是否相同的依據。ooo
public boolean equals(Object obj){
if(!(obj instanceof Person))
return false;
Person p = (Person)obj;
return this.age == p.age;
}
2,String toString():將對象變成字符串;默認返回的格式:類名@哈希值 = getClass().getName() + '@' + Integer.toHexString(hashCode())
為了對象對應的字符串內容有意義,可以通過復寫,建立該類對象自己特有的字符串表現形式。
public String toString(){
return "person : "+age;
}
3,Class getClass():獲取任意對象運行時的所屬字節碼文件對象。
4,int hashCode():返回該對象的哈希碼值。支持此方法是為了提高哈希表的性能。
通常equals,toString,hashCode,在應用中都會被復寫,建立具體對象的特有的內容。
------------------------------------------------------------------------------------------------
內部類:如果A類需要直接訪問B類中的成員,而B類又需要建立A類的對象。這時,為了方便設計和訪問,直接將A類定義在B類中。就可以了。A類就稱為內部類。內部類可以直接訪問外部類中的成員。而外部類想要訪問內部類,必須要建立內部類的對象。
-----------------------------------------------------
class Outer{
int num = 4;
class Inner {
void show(){
System.out.println("inner show run "+num);
}
}
public void method(){
Inner in = new Inner();//創建內部類的對象。
in.show();//調用內部類的方法。
}
}
-------------------------------------------------------
當內部類定義在外部類中的成員位置上,可以使用一些成員修飾符修飾 private、static。
1:默認修飾符。
直接訪問內部類格式:外部類名.內部類名 變量名 = 外部類對象.內部類對象;
Outer.Inner in = new Outer.new Inner();//這種形式很少用。
但是這種應用不多見,因為內部類之所以定義在內部就是為了封裝。想要獲取內部類對象通常都通過外部類的方法來獲取。這樣可以對內部類對象進行控制。
2:私有修飾符。
通常內部類被封裝,都會被私有化,因為封裝性不讓其他程序直接訪問。
3:靜態修飾符。
如果內部類被靜態修飾,相當于外部類,會出現訪問局限性,只能訪問外部類中的靜態成員。
注意;如果內部類中定義了靜態成員,那么該內部類必須是靜態的。
內部類編譯后的文件名為:“外部類名$內部類名.java”;
為什么內部類可以直接訪問外部類中的成員呢?
那是因為內部中都持有一個外部類的引用。這個是引用是 外部類名.this
內部類可以定義在外部類中的成員位置上,也可以定義在外部類中的局部位置上。
當內部類被定義在局部位置上,只能訪問局部中被final修飾的局部變量。
匿名內部類:沒有名字的內部類。就是內部類的簡化形式。一般只用一次就可以用這種形式。匿名內部類其實就是一個匿名子類對象。想要定義匿名內部類:需要前提,內部類必須繼承一個類或者實現接口。
匿名內部類的格式:new 父類名&接口名(){ 定義子類成員或者覆蓋父類方法 }.方法。
匿名內部類的使用場景:
當函數的參數是接口類型引用時,如果接口中的方法不超過3個。可以通過匿名內部類來完成參數的傳遞。
其實就是在創建匿名內部類時,該類中的封裝的方法不要過多,最好兩個或者兩個以內。
--------------------------------------------------------
//面試
//1
new Object(){
void show(){
System.out.println("show run");
}
}.show();
//2
Object obj = new Object(){
void show(){
System.out.println("show run");
}
};
obj.show();
1和2的寫法正確嗎?有區別嗎?說出原因。
寫法是正確,1和2都是在通過匿名內部類建立一個Object類的子類對象。
區別:
第一個可是編譯通過,并運行。
第二個編譯失敗,因為匿名內部類是一個子類對象,當用Object的obj引用指向時,就被提升為了
Object類型,而編譯時檢查Object類中是否有show方法,所以編譯失敗。
-------------------------------------------------------
class InnerClassDemo6 {
+(static)class Inner{
void show(){}
}
public void method(){
this.new Inner().show();//可以
}
public static void main(String[] args) {//static不允許this
This.new Inner().show();//錯誤,Inner類需要定義成static
}
}
------------------------------------------------------
interface Inter{
void show();
}
class Outer{//通過匿名內部類補足Outer類中的代碼。
public static Inter method(){
return new Inter(){
public void show(){}
};
}
}
class InnerClassDemo7 {
public static void main(String[] args) {
Outer.method().show();
/*
Outer.method():意思是:Outer中有一個名稱為method的方法,而且這個方法是靜態的。
Outer.method().show():當Outer類調用靜態的method方法運算結束后的結果又調用了show方法,意味著:method()方法運算完一個是對象,而且這個對象是Inter類型的。
*/
function (new Inter(){
public void show(){}
}); //匿名內部類作為方法的參數進行傳遞。
}
public static void function(Inter in){
in.show();
}
}
------------------------------------------------------------------------------------------------
異 常:★★★★
異常:就是不正常。程序在運行時出現的不正常情況。其實就是程序中出現的問題。這個問題按照面向對象思想進行描述,并封裝成了對象。因為問題的產生有產生的原因、有問題的名稱、有問題的描述等多個屬性信息存在。當出現多屬性信息最方便的方式就是將這些信息進行封裝。異常就是java按照面向對象的思想將問題進行對象封裝。這樣就方便于操作問題以及處理問題。
出現的問題有很多種,比如角標越界,空指針等都是。就對這些問題進行分類。而且這些問題都有共性內容比如:每一個問題都有名稱,同時還有問題描述的信息,問題出現的位置,所以可以不斷的向上抽取。形成了異常體系。
--------java.lang.Throwable:
Throwable:可拋出的。
|--Error:錯誤,一般情況下,不編寫針對性的代碼進行處理,通常是jvm發生的,需要對程序進行修正。
|--Exception:異常,可以有針對性的處理方式
無論是錯誤還是異常,它們都有具體的子類體現每一個問題,它們的子類都有一個共性,就是都以父類名才作為子類的后綴名。
這個體系中的所有類和對象都具備一個獨有的特點;就是可拋性。
可拋性的體現:就是這個體系中的類和對象都可以被throws和throw兩個關鍵字所操作。
------------------------------------------------------
class ExceptionDemo{
public static void main(String[] args) {
// byte[] buf = new byte[1024*1024*700];//java.lang.OutOfMemoryError內存溢出錯誤
}
}
------------------------------------------------------
在開發時,如果定義功能時,發現該功能會出現一些問題,應該將問題在定義功能時標示出來,這樣調用者就可以在使用這個功能的時候,預先給出處理方式。
如何標示呢?通過throws關鍵字完成,格式:throws 異常類名,異常類名...
這樣標示后,調用者,在使用該功能時,就必須要處理,否則編譯失敗。
處理方式有兩種:1、捕捉;2、拋出。
對于捕捉:java有針對性的語句塊進行處理。
try {
需要被檢測的代碼;
}
catch(異常類 變量名){
異常處理代碼;
}
fianlly{
一定會執行的代碼;
}
--------------------------------------------------------
catch (Exception e) { //e用于接收try檢測到的異常對象。
System.out.println("message:"+e.getMessage());//獲取的是異常的信息。
System.out.println("toString:"+e.toString());//獲取的是異常的名字+異常的信息。
e.printStackTrace();//打印異常在堆棧中信息;異常名稱+異常信息+異常的位置。
}
---------------------------------------------------------
異常處理原則:功能拋出幾個異常,功能調用如果進行try處理,需要與之對應的catch處理代碼塊,這樣的處理有針對性,拋幾個就處理幾個。
特殊情況:try對應多個catch時,如果有父類的catch語句塊,一定要放在下面。
throw 和throws關鍵字的區別:
throw用于拋出異常對象,后面跟的是異常對象;throw用在函數內。
throws用于拋出異常類,后面跟的異常類名,可以跟多個,用逗號隔開。throws用在函數上。
通常情況:函數內容如果有throw,拋出異常對象,并沒有進行處理,那么函數上一定要聲明,否則編譯失敗。但是也有特殊情況。
異常分兩種:
1:編譯時被檢查的異常,只要是Exception及其子類都是編譯時被檢測的異常。
2:運行時異常,其中Exception有一個特殊的子類RuntimeException,以及RuntimeException的子類是運行異常,也就說這個異常是編譯時不被檢查的異常。
編譯時被檢查的異常和運行時異常的區別:
編譯被檢查的異常在函數內被拋出,函數必須要聲明,否編譯失敗。
聲明的原因:是需要調用者對該異常進行處理。
運行時異常如果在函數內被拋出,在函數上不需要聲明。
不聲明的原因:不需要調用者處理,運行時異常發生,已經無法再讓程序繼續運行,所以,不讓調用處理的,直接讓程序停止,由調用者對代碼進行修正。
定義異常處理時,什么時候定義try,什么時候定義throws呢?
功能內部如果出現異常,如果內部可以處理,就用try;
如果功能內部處理不了,就必須聲明出來,讓調用者處理。
自定義異常:當開發時,項目中出現了java中沒有定義過的問題時,這時就需要我們按照java異常建立思想,將項目的中的特有問題也進行對象的封裝。這個異常,稱為自定義異常。
對于除法運算,0作為除數是不可以的。java中對這種問題用ArithmeticException類進行描述。對于這個功能,在我們項目中,除數除了不可以為0外,還不可以為負數。可是負數的部分java并沒有針對描述。所以我們就需要自定義這個異常。
自定義異常的步驟:
1:定義一個子類繼承Exception或RuntimeException,讓該類具備可拋性。
2:通過throw 或者throws進行操作。
異常的轉換思想:當出現的異常是調用者處理不了的,就需要將此異常轉換為一個調用者可以處理的異常拋出。
try catch finally的幾種結合方式:
1,
try
catch
finally
這種情況,如果出現異常,并不處理,但是資源一定關閉,所以try finally集合只為關閉資源。
記住:finally很有用,主要用戶關閉資源。無論是否發生異常,資源都必須進行關閉。
System.exit(0); //退出jvm,只有這種情況finally不執行。
當異常出現后,在子父類進行覆蓋時,有了一些新的特點:
1:當子類覆蓋父類的方法時,如果父類的方法拋出了異常,那么子類的方法要么不拋出異常要么拋出父類異常或者該異常的子類,不能拋出其他異常。
2:如果父類拋出了多個異常,那么子類在覆蓋時只能拋出父類的異常的子集。
注意:
如果父類或者接口中的方法沒有拋出過異常,那么子類是不可以拋出異常的,如果子類的覆蓋的方法中出現了異常,只能try不能throws。
如果這個異常子類無法處理,已經影響了子類方法的具體運算,這時可以在子類方法中,通過throw拋出RuntimeException異常或者其子類,這樣,子類的方法上是不需要throws聲明的。
常見異常:
1、腳標越界異常(IndexOutOfBoundsException)包括數組、字符串;
空指針異常(NullPointerException)
2、類型轉換異常:ClassCastException
3、沒有這個元素異常:NullPointerException
4、不支持操作異常;
異常要盡量避免,如果避免不了,需要預先給出處理方式。比如家庭備藥,比如滅火器。
-----------------------------------------------------------------------------------------------
包:定義包用package關鍵字。
1:對類文件進行分類管理。
2:給類文件提供多層名稱空間。
如果生成的包不在當前目錄下,需要最好執行classpath,將包所在父目錄定義到classpath變量中即可。
一般在定義包名時,因為包的出現是為了區分重名的類。所以包名要盡量唯一。怎么保證唯一性呢?可以使用url域名來進行包名稱的定義。
package pack;//定義了一個包,名稱為pack。 注意:包名的寫法規范:所有字母都小寫。
//package cn.itcast.pack.demo;
類的全名稱是 包名.類名
編譯命令:javac –d 位置(.當前路徑) java源文件 (就可以自動生成包)
包是一種封裝形式,用于封裝類,想要被包以外的程序訪問,該類必須public;
類中的成員,如果被包以外訪問,也必須public;
包與包之間訪問可以使用的權限有兩種:
1:public
2:protected:只能是不同包中的子類可以使用的權限。
總結java中的四種權限:
范圍 publicprotected(保護) default(默認)private
同一個類中 ok ok ok ok
同一包中 ok ok ok
子類 ok
不同包中 ok ok
-----------------------------------------------------------------------------------------------
Import - 導入:類名稱變長,寫起來很麻煩。為了簡化,使用了一個關鍵字:import,可以使用這個關鍵字導入指定包中的類。記住:實際開發時,到的哪個類就導入哪個類,不建議使用*.
import packa.*;//這個僅僅是導入了packa當前目錄下的所有的類。不包含子包。
import packa.abc.*;//導入了packa包中的子包abc下的當前的所有類。
如果導入的兩個包中存在著相同名稱的類。這時如果用到該類,必須在代碼中指定包名。
常見的軟件包:
java.lang : language java的核心包,Object System String Throwable jdk1.2版本后,該包中的類自動被導入。
java.awt : 定義的都是用于java圖形界面開發的對象。
javax.swing: 提供所有的windows桌面應用程序包括的控件,比如:Frame , Dialog, Table, List 等等,就是java的圖形界面庫。
java.net : 用于java網絡編程方面的對象都在該包中。
java.io : input output 用于操作設備上數據的對象都在該包中。比如:讀取硬盤數據,往硬盤寫入數據。
java.util : java的工具包,時間對象,集合框架。
java.applet: application+let 客戶端java小程序。server+let --> servlet 服務端java小程序。
jar :java的壓縮包,主要用于存儲類文件,或者配置文件等。
命令格式:jar –cf 包名.jar 包目錄
解壓縮:jar –xvf 包名.jar
將jar包目錄列表重定向到一個文件中:jar –tf 包名.jar >c:\1.txt
-----------------------------------------------------------------------------------------------
多線程:★★★★
進程:正在進行中的程序。其實進程就是一個應用程序運行時的內存分配空間。
線程:其實就是進程中一個程序執行控制單元,一條執行路徑。進程負責的是應用程序的空間的標示。線程負責的是應用程序的執行順序。
一個進程至少有一個線程在運行,當一個進程中出現多個線程時,就稱這個應用程序是多線程應用程序,每個線程在棧區中都有自己的執行空間,自己的方法區、自己的變量。
jvm在啟動的時,首先有一個主線程,負責程序的執行,調用的是main函數。主線程執行的代碼都在main方法中。
當產生垃圾時,收垃圾的動作,是不需要主線程來完成,因為這樣,會出現主線程中的代碼執行會停止,會去運行垃圾回收器代碼,效率較低,所以由單獨一個線程來負責垃圾回收。
隨機性的原理:因為cpu的快速切換造成,哪個線程獲取到了cpu的執行權,哪個線程就執行。
返回當前線程的名稱:Thread.currentThread().getName()
線程的名稱是由:Thread-編號定義的。編號從0開始。
線程要運行的代碼都統一存放在了run方法中。
線程要運行必須要通過類中指定的方法開啟。start方法。(啟動后,就多了一條執行路徑)
start方法:1)、啟動了線程;2)、讓jvm調用了run方法。
創建線程的第一種方式:繼承Thread ,由子類復寫run方法。
步驟:
1,定義類繼承Thread類;
2,目的是復寫run方法,將要讓線程運行的代碼都存儲到run方法中;
3,通過創建Thread類的子類對象,創建線程對象;
4,調用線程的start方法,開啟線程,并執行run方法。
線程狀態:
被創建:start()
運行:具備執行資格,同時具備執行權;
凍結:sleep(time),wait()—notify()喚醒;線程釋放了執行權,同時釋放執行資格;
臨時阻塞狀態:線程具備cpu的執行資格,沒有cpu的執行權;
消亡:stop()
創建線程的第二種方式:實現一個接口Runnable。
步驟:
1,定義類實現Runnable接口。
2,覆蓋接口中的run方法(用于封裝線程要運行的代碼)。
3,通過Thread類創建線程對象;
4,將實現了Runnable接口的子類對象作為實際參數傳遞給Thread類中的構造函數。
為什么要傳遞呢?因為要讓線程對象明確要運行的run方法所屬的對象。
5,調用Thread對象的start方法。開啟線程,并運行Runnable接口子類中的run方法。
Ticket t = new Ticket();
/*
直接創建Ticket對象,并不是創建線程對象。
因為創建對象只能通過new Thread類,或者new Thread類的子類才可以。
所以最終想要創建線程。既然沒有了Thread類的子類,就只能用Thread類。
*/
Thread t1 = new Thread(t); //創建線程。
/*
只要將t作為Thread類的構造函數的實際參數傳入即可完成線程對象和t之間的關聯
為什么要將t傳給Thread類的構造函數呢?其實就是為了明確線程要運行的代碼run方法。
*/
t1.start();
為什么要有Runnable接口的出現?
1:通過繼承Thread類的方式,可以完成多線程的建立。但是這種方式有一個局限性,如果一個類已經有了自己的父類,就不可以繼承Thread類,因為java單繼承的局限性。
可是該類中的還有部分代碼需要被多個線程同時執行。這時怎么辦呢?
只有對該類進行額外的功能擴展,java就提供了一個接口Runnable。這個接口中定義了run方法,其實run方法的定義就是為了存儲多線程要運行的代碼。
所以,通常創建線程都用第二種方式。
因為實現Runnable接口可以避免單繼承的局限性。
2:其實是將不同類中需要被多線程執行的代碼進行抽取。將多線程要運行的代碼的位置單獨定義到接口中。為其他類進行功能擴展提供了前提。
所以Thread類在描述線程時,內部定義的run方法,也來自于Runnable接口。
實現Runnable接口可以避免單繼承的局限性。而且,繼承Thread,是可以對Thread類中的方法,進行子類復寫的。但是不需要做這個復寫動作的話,只為定義線程代碼存放位置,實現Runnable接口更方便一些。所以Runnable接口將線程要執行的任務封裝成了對象。
-------------------------------------------------------
//面試
new Thread(new Runnable(){ //匿名
public void run(){
System.out.println("runnable run");
}
}
{
public void run(){
System.out.println("subthread run");
}
}.start(); //結果:subthread run
---------------------------------------------------------
Try {
Thread.sleep(10);
}catch(InterruptedException e){}// 當刻意讓線程稍微停一下,模擬cpu 切換情況。
多線程安全問題的原因:
通過圖解:發現一個線程在執行多條語句時,并運算同一個數據時,在執行過程中,其他線程參與進來,并操作了這個數據。導致到了錯誤數據的產生。
涉及到兩個因素:
1,多個線程在操作共享數據。
2,有多條語句對共享數據進行運算。
原因:這多條語句,在某一個時刻被一個線程執行時,還沒有執行完,就被其他線程執行了。
解決安全問題的原理:
只要將操作共享數據的語句在某一時段讓一個線程執行完,在執行過程中,其他線程不能進來執行就可以解決這個問題。
如何進行多句操作共享數據代碼的封裝呢?
java中提供了一個解決方式:就是同步代碼塊。
格式:
synchronized(對象) { // 任意對象都可以。這個對象就是鎖。
需要被同步的代碼;
}
---------------------------------------------------------------
同步:★★★★★
好處:解決了線程安全問題。
弊端:相對降低性能,因為判斷鎖需要消耗資源,產生了死鎖。
定義同步是有前提的:
1,必須要有兩個或者兩個以上的線程,才需要同步。
2,多個線程必須保證使用的是同一個鎖。
同步的第二種表現形式:
同步函數:其實就是將同步關鍵字定義在函數上,讓函數具備了同步性。
同步函數是用的哪個鎖呢?
通過驗證,函數都有自己所屬的對象this,所以同步函數所使用的鎖就是this鎖。
當同步函數被static修飾時,這時的同步用的是哪個鎖呢?
靜態函數在加載時所屬于類,這時有可能還沒有該類產生的對象,但是該類的字節碼文件加載進內存就已經被封裝成了對象,這個對象就是該類的字節碼文件對象。
所以靜態加載時,只有一個對象存在,那么靜態同步函數就使用的這個對象。
這個對象就是 類名.class
同步代碼塊和同步函數的區別?
同步代碼塊使用的鎖可以是任意對象。
同步函數使用的鎖是this,靜態同步函數的鎖是該類的字節碼文件對象。
在一個類中只有一個同步,可以使用同步函數。如果有多同步,必須使用同步代碼塊,來確定不同的鎖。所以同步代碼塊相對靈活一些。
-------------------------------------------------------
★考點問題:請寫一個延遲加載的單例模式?寫懶漢式;當出現多線程訪問時怎么解決?加同步,解決安全問題;效率高嗎?不高;怎樣解決?通過雙重判斷的形式解決。
//懶漢式:延遲加載方式。
當多線程訪問懶漢式時,因為懶漢式的方法內對共性數據進行多條語句的操作。所以容易出現線程安全問題。為了解決,加入同步機制,解決安全問題。但是卻帶來了效率降低。
為了效率問題,通過雙重判斷的形式解決。
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){ //鎖是誰?字節碼文件對象;
if(s == null){
synchronized(Single.class){
if(s == null)
s = new Single();
}
}
return s;
}
}
---------------------------------------------------------
同步死鎖:通常只要將同步進行嵌套,就可以看到現象。同步函數中有同步代碼塊,同步代碼塊中還有同步函數。
線程間通信:思路:多個線程在操作同一個資源,但是操作的動作卻不一樣。
1:將資源封裝成對象。
2:將線程執行的任務(任務其實就是run方法。)也封裝成對象。
等待喚醒機制:涉及的方法:
wait:將同步中的線程處于凍結狀態。釋放了執行權,釋放了資格。同時將線程對象存儲到線程池中。
notify:喚醒線程池中某一個等待線程。
notifyAll:喚醒的是線程池中的所有線程。
注意:
1:這些方法都需要定義在同步中。
2:因為這些方法必須要標示所屬的鎖。
你要知道 A鎖上的線程被wait了,那這個線程就相當于處于A鎖的線程池中,只能A鎖的notify喚醒。
3:這三個方法都定義在Object類中。為什么操作線程的方法定義在Object類中?
因為這三個方法都需要定義同步內,并標示所屬的同步鎖,既然被鎖調用,而鎖又可以是任意對象,那么能被任意對象調用的方法一定定義在Object類中。
wait和sleep區別: 分析這兩個方法:從執行權和鎖上來分析:
wait:可以指定時間也可以不指定時間。不指定時間,只能由對應的notify或者notifyAll來喚醒。
sleep:必須指定時間,時間到自動從凍結狀態轉成運行狀態(臨時阻塞狀態)。
wait:線程會釋放執行權,而且線程會釋放鎖。
Sleep:線程會釋放執行權,但不是不釋放鎖。
線程的停止:通過stop方法就可以停止線程。但是這個方式過時了。
停止線程:原理就是:讓線程運行的代碼結束,也就是結束run方法。
怎么結束run方法?一般run方法里肯定定義循環。所以只要結束循環即可。
第一種方式:定義循環的結束標記。
第二種方式:如果線程處于了凍結狀態,是不可能讀到標記的,這時就需要通過Thread類中的interrupt方法,將其凍結狀態強制清除。讓線程恢復具備執行資格的狀態,讓線程可以讀到標記,并結束。
---------< java.lang.Thread >----------
interrupt():中斷線程。
setPriority(int newPriority):更改線程的優先級。
getPriority():返回線程的優先級。
toString():返回該線程的字符串表示形式,包括線程名稱、優先級和線程組。
Thread.yield():暫停當前正在執行的線程對象,并執行其他線程。
setDaemon(true):將該線程標記為守護線程或用戶線程。將該線程標記為守護線程或用戶線程。當正在運行的線程都是守護線程時,Java 虛擬機退出。該方法必須在啟動線程前調用。
join:臨時加入一個線程的時候可以使用join方法。
當A線程執行到了B線程的join方式。A線程處于凍結狀態,釋放了執行權,B開始執行。A什么時候執行呢?只有當B線程運行結束后,A才從凍結狀態恢復運行狀態執行。
-----------------------------------------------------------
Lock接口:多線程在JDK1.5版本升級時,推出一個接口Lock接口。
解決線程安全問題使用同步的形式,(同步代碼塊,要么同步函數)其實最終使用的都是鎖機制。
到了后期版本,直接將鎖封裝成了對象。線程進入同步就是具備了鎖,執行完,離開同步,就是釋放了鎖。
在后期對鎖的分析過程中,發現,獲取鎖,或者釋放鎖的動作應該是鎖這個事物更清楚。所以將這些動作定義在了鎖當中,并把鎖定義成對象。
所以同步是隱示的鎖操作,而Lock對象是顯示的鎖操作,它的出現就替代了同步。
在之前的版本中使用Object類中wait、notify、notifyAll的方式來完成的。那是因為同步中的鎖是任意對象,所以操作鎖的等待喚醒的方法都定義在Object類中。
而現在鎖是指定對象Lock。所以查找等待喚醒機制方式需要通過Lock接口來完成。而Lock接口中并沒有直接操作等待喚醒的方法,而是將這些方式又單獨封裝到了一個對象中。這個對象就是Condition,將Object中的三個方法進行單獨的封裝。并提供了功能一致的方法 await()、signal()、signalAll()體現新版本對象的好處。
< java.util.concurrent.locks > Condition接口:await()、signal()、signalAll();
--------------------------------------------------------
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
}
finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
}
finally {
lock.unlock();
}
}
}
------------------------------------------------------------------------------------------------
API:(Application Programming Interface,應用程序編程接口)是一些預先定義的函數,目的是提供應用程序與開發人員基于某軟件或硬件的以訪問一組例程的能力,而又無需訪問源碼,或理解內部工作機制的細節。
--< java.lang >-- String字符串:★★★☆
java中用String類進行描述。對字符串進行了對象的封裝。這樣的好處是可以對字符串這種常見數據進行方便的操作。對象封裝后,可以定義N多屬性和行為。
如何定義字符串對象呢?String s = "abc";只要是雙引號引起的數據都是字符串對象。
特點:字符串一旦被初始化,就不可以被改變,存放在方法區中的常量池中。
------------------------------------------------------
String s1 = "abc"; // s1指向的內存中只有一個對象abc。
String s2 = new String("abc"); // s2指向的內容中有兩個對象abc、new 。
System.out.println(s1==s2);//false ==比較的是地址值
System.out.println(s1.equals(s2));//true ,字符串中equals比較的是字符串內容是否相同。
而在Object中,equals比較的是兩個對象的地址值,是否為相同。
查API。
-------------------------------------------------------
字符串的方法:
1:構造方法:將字節數組或者字符數組轉成字符串。
String s1 = new String();//創建了一個空內容的字符串。
String s2 = null;//s2沒有任何對象指向,是一個null常量值。
String s3 = "";//s3指向一個具體的字符串對象,只不過這個字符串中沒有內容。
//一般在定義字符串時,不用new。
String s4 = new String("abc");
String s5 = "abc"; 一般用此寫法
new String(char[]);//將字符數組轉成字符串。
new String(char[],offset,count);//將字符數組中的一部分轉成字符串。
2:一般方法:
按照面向對象的思想:
2.1 獲取:
2.1.1:獲取字符串的長度。length();
2.1.2:指定位置的字符。char charAt(int index);
2.1.3:獲取指定字符的位置。如果不存在返回-1,所以可以通過返回值-1來判斷某一個字符不存在的情況。
int indexOf(int ch);//返回第一次找到的字符角標
int indexOf(int ch,int fromIndex); //返回從指定位置開始第一次找到的角標
int indexOf(String str); //返回第一次找到的字符串角標
int indexOf(String str,int fromIndex);
int lastIndexOf(int ch);
int lastIndexOf(int ch,int fromIndex);
int lastIndexOf(String str);
int lastIndexOf(String str,int fromIndex);
2.1.4:獲取子串。
String substring(int start);//從start位開始,到length()-1為止.
String substring(int start,int end);//從start開始到end為止。//包含start位,不包含end位。
substring(0,str.length());//獲取整串
2.2 判斷:
2.2.1:字符串中包含指定的字符串嗎?
boolean contains(String substring);
2.2.2:字符串是否以指定字符串開頭啊?
boolean startsWith(string);
2.2.3:字符串是否以指定字符串結尾啊?
boolean endsWith(string);
2.2.4:判斷字符串是否相同
boolean equals(string);//覆蓋了Object中的方法,判斷字符串內容是否相同。
2.2.5:判斷字符串內容是否相同,忽略大小寫。
boolean equalsIgnoreCase(string) ;
2.3 轉換:
2.3.1:通過構造函數可以將字符數組或者字節數組轉成字符串。
2.3.2:可以通過字符串中的靜態方法,將字符數組轉成字符串。
static String copyValueOf(char[] );
static String copyValueOf(char[],int offset,int count);
static String valueOf(char[]);
static String valueOf(char[],int offset,int count);
2.3.3:將基本數據類型或者對象轉成字符串。
static String valueOf(char);
static String valueOf(boolean);
static String valueOf(double);
static String valueOf(float);
static String valueOf(int);
static String valueOf(long);
static String valueOf(Object);
2.3.4:將字符串轉成大小寫。
String toLowerCase();
String toUpperCase();
2.3.5:將字符串轉成數組。
char[] toCharArray();//轉成字符數組。
byte[] getBytes();//可以加入編碼表。轉成字節數組。
2.3.6:將字符串轉成字符串數組。切割方法。
String[] split(分割的規則-字符串);
2.3.7:將字符串進行內容替換。注意:修改后變成新字符串,并不是將原字符串直接修改。
String replace(oldChar,newChar);
String replace(oldstring,newstring);
2.3.8: String concat(string); //對字符串進行追加。
String trim();//去除字符串兩端的空格
int compareTo();//如果參數字符串等于此字符串,則返回值 0;如果此字符串按字典順序小于字符串參數,則返回一個小于 0 的值;如果此字符串按字典順序大于字符串參數,則返回一個大于 0 的值。
------------------------------------------------------------------------------------------------
--< java.lang >-- StringBuffer字符串緩沖區:★★★☆
構造一個其中不帶字符的字符串緩沖區,初始容量為 16 個字符。
特點:
1:可以對字符串內容進行修改。
2:是一個容器。
3:是可變長度的。
4:緩沖區中可以存儲任意類型的數據。
5:最終需要變成字符串。
容器通常具備一些固定的方法:
1,添加。
StringBuffer append(data):在緩沖區中追加數據。追加到尾部。
StringBuffer insert(index,data):在指定位置插入數據。
2,刪除。
StringBuffer delete(start,end);刪除從start至end-1范圍的元素
StringBuffer deleteCharAt(index);刪除指定位置的元素
//sb.delete(0,sb.length());//清空緩沖區。
3,修改。
StringBuffer replace(start,end,string);將start至end-1替換成string
void setCharAt(index,char);替換指定位置的字符
void setLength(len);將原字符串置為指定長度的字符串
4,查找。(查不到返回-1)
int indexOf(string); 返回指定子字符串在此字符串中第一次出現處的索引。
int indexOf(string,int fromIndex);從指定位置開始查找字符串
int lastIndexOf(string); 返回指定子字符串在此字符串中最右邊出現處的索引。
int lastIndexOf(string,int fromIndex); 從指定的索引開始反向搜索
5,獲取子串。
string substring(start); 返回start到結尾的子串
string substring(start,end); 返回start至end-1的子串
6,反轉。
StringBuffer reverse();字符串反轉
------------------------------------------------------------------------------------------------
--< java.lang >-- StringBuilder字符串緩沖區:★★★☆
JDK1.5出現StringBuiler;構造一個其中不帶字符的字符串生成器,初始容量為 16 個字符。該類被設計用作 StringBuffer 的一個簡易替換,用在字符串緩沖區被單個線程使用的時候(這種情況很普遍)。
方法和StringBuffer一樣;
StringBuffer 和 StringBuilder 的區別:
StringBuffer線程安全。
StringBuilder線程不安全。
單線程操作,使用StringBuilder 效率高。
多線程操作,使用StringBuffer 安全。
---------------------------------------------------------
StringBuilder sb = new StringBuilder("abcdefg");
sb.append("ak"); //abcdefgak
sb.insert(1,"et");//aetbcdefg
sb.deleteCharAt(2);//abdefg
sb.delete(2,4);//abefg
sb.setLength(4);//abcd
sb.setCharAt(0,'k');//kbcdefg
sb.replace(0,2,"hhhh");//hhhhcdefg
//想要使用緩沖區,先要建立對象。
StringBuffer sb = new StringBuffer();
sb.append(12).append("haha");//方法調用鏈。
String s = "abc"+4+'q';
s = new StringBuffer().append("abc").append(4).append('q').toString();
---------------------------------------------------------
class Test{
public static void main(String[] args) {
String s1 = "java";
String s2 = "hello";
method_1(s1,s2);
System.out.println(s1+"...."+s2); //java....hello
StringBuilder s11 = new StringBuilder("java");
StringBuilder s22 = new StringBuilder("hello");
method_2(s11,s22);
System.out.println(s11+"-----"+s22); //javahello-----hello
}
public static void method_1(String s1,String s2){
s1.replace('a','k');
s1 = s2;
}
public static void method_2(StringBuilder s1,StringBuilder s2){
s1.append(s2);
s1 = s2;
}
}
---------------------------------------------------------
基本數據類型對象包裝類:是按照面向對象思想將基本數據類型封裝成了對象。
好處:
1:可以通過對象中的屬性和行為操作基本數據。
2:可以實現基本數據類型和字符串之間的轉換。
關鍵字 對應的類名
byte Byte
short Short paserShort(numstring);
int Integer 靜態方法:parseInt(numstring)
long Long
float Float
double Double
char Character
Boolean Boolean
基本數據類型對象包裝類:都有 XXX parseXXX 方法
只有一個類型沒有parse方法:Character ;
--------------------------------------------------------
Integer對象: ★★★☆
數字格式的字符串轉成基本數據類型的方法:
1:將該字符串封裝成了Integer對象,并調用對象的方法intValue();
2:使用Integer.parseInt(numstring):不用建立對象,直接類名調用;
將基本類型轉成字符串:
1:Integer中的靜態方法 String toString(int);
2:int+"";
將一個十進制整數轉成其他進制:
轉成二進制:toBinaryString
轉成八進制:toOctalString
轉成十六進制:toHexString
toString(int num,int radix);
將其他進制轉換十進制:
parseInt(string,radix); //將給定的數轉成指定的基數進制;
在jdk1.5版本后,對基本數據類型對象包裝類進行升級。在升級中,使用基本數據類型對象包裝類可以像使用基本數據類型一樣,進行運算。
Integer i = new Integer(4); //1.5版本之前的寫法;
Integer i = 4; //自動裝箱,1.5版本后的寫法;
i = i + 5;
//i對象是不能直接和5相加的,其實底層先將i轉成int類型,在和5相加。而轉成int類型的操作是隱式的。自動拆箱:拆箱的原理就是i.intValue();i+5運算完是一個int整數。如何賦值給引用類型i呢?其實有對結果進行裝箱。
Integer c = 127;
Integer d = 127;
System.out.println(c = = d); //true
//在裝箱時,如果數值在byte范圍之內,那么數值相同,不會產生新的對象,也就是說多個數值相同的引用指向的是同一個對象。
------------------------------------------------------------------------------------------------
集合框架:★★★★★,用于存儲數據的容器。
特點:
1:對象封裝數據,對象多了也需要存儲。集合用于存儲對象。
2:對象的個數確定可以使用數組,但是不確定怎么辦?可以用集合。因為集合是可變長度的。
集合和數組的區別:
1:數組是固定長度的;集合可變長度的。
2:數組可以存儲基本數據類型,也可以存儲引用數據類型;集合只能存儲引用數據類型。
3:數組存儲的元素必須是同一個數據類型;集合存儲的對象可以是不同數據類型。
數據結構:就是容器中存儲數據的方式。
對于集合容器,有很多種。因為每一個容器的自身特點不同,其實原理在于每個容器的內部數據結構不同。
集合容器在不斷向上抽取過程中。出現了集合體系。
在使用一個體系時,原則:參閱頂層內容。建立底層對象。
------------------------------------------------------------
--< java.util >-- Collection接口:
Collection:
|--List:有序(元素存入集合的順序和取出的順序一致),元素都有索引。元素可以重復。
|--Set:無序(存入和取出順序有可能不一致),不可以存儲重復元素。必須保證元素唯一性。
1,添加:
add(object):添加一個元素
addAll(Collection) :添加一個集合中的所有元素。
2,刪除:
clear():將集合中的元素全刪除,清空集合。
remove(obj) :刪除集合中指定的對象。注意:刪除成功,集合的長度會改變。
removeAll(collection) :刪除部分元素。部分元素和傳入Collection一致。
3,判斷:
boolean contains(obj) :集合中是否包含指定元素 。
boolean containsAll(Collection) :集合中是否包含指定的多個元素。
boolean isEmpty():集合中是否有元素。
4,獲取:
int size():集合中有幾個元素。
5,取交集:
boolean retainAll(Collection) :對當前集合中保留和指定集合中的相同的元素。如果兩個集合元素相同,返回flase;如果retainAll修改了當前集合,返回true。
6,獲取集合中所有元素:
Iterator iterator():迭代器
7,將集合變成數組:
toArray();
------------------------------------------------------------
--< java.util >-- Iterator接口:
迭代器:是一個接口。作用:用于取集合中的元素。
boolean | hasNext() 如果仍有元素可以迭代,則返回 true。 |
E | next() 返回迭代的下一個元素。 |
void | remove() 從迭代器指向的 collection 中移除迭代器返回的最后一個元素(可選操作)。 |
每一個集合都有自己的數據結構,都有特定的取出自己內部元素的方式。為了便于操作所有的容器,取出元素。將容器內部的取出方式按照一個統一的規則向外提供,這個規則就是Iterator接口。
也就說,只要通過該接口就可以取出Collection集合中的元素,至于每一個具體的容器依據自己的數據結構,如何實現的具體取出細節,這個不用關心,這樣就降低了取出元素和具體集合的耦合性。
Iterator it = coll.iterator();//獲取容器中的迭代器對象,至于這個對象是是什么不重要。這對象肯定符合一個規則Iterator接口。
-----------------------------------------------------------------------------
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("abc0");
coll.add("abc1");
coll.add("abc2");
//--------------方式1----------------------
Iterator it = coll.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//---------------方式2用此種----------------------
for(Iterator it = coll.iterator();it.hasNext(); ){
System.out.println(it.next());
}
}
-----------------------------------------------------------------------------
--< java.util >-- List接口:
List本身是Collection接口的子接口,具備了Collection的所有方法。現在學習List體系特有的共性方法,查閱方法發現List的特有方法都有索引,這是該集合最大的特點。
List:有序(元素存入集合的順序和取出的順序一致),元素都有索引。元素可以重復。
|--ArrayList:底層的數據結構是數組,線程不同步,ArrayList替代了Vector,查詢元素的速度非常快。
|--LinkedList:底層的數據結構是鏈表,線程不同步,增刪元素的速度非常快。
|--Vector:底層的數據結構就是數組,線程同步的,Vector無論查詢和增刪都巨慢。
1,添加:
add(index,element) :在指定的索引位插入元素。
addAll(index,collection) :在指定的索引位插入一堆元素。
2,刪除:
remove(index) :刪除指定索引位的元素。 返回被刪的元素。
3,獲取:
Object get(index) :通過索引獲取指定元素。
int indexOf(obj) :獲取指定元素第一次出現的索引位,如果該元素不存在返回-1;
所以,通過-1,可以判斷一個元素是否存在。
int lastIndexOf(Object o) :反向索引指定元素的位置。
List subList(start,end) :獲取子列表。
4,修改:
Object set(index,element) :對指定索引位進行元素的修改。
5,獲取所有元素:
ListIterator listIterator():list集合特有的迭代器。
List集合支持對元素的增、刪、改、查。
List集合因為角標有了自己的獲取元素的方式: 遍歷。
for(int x=0; x<list.size(); x++){
sop("get:"+list.get(x));
}
在進行list列表元素迭代的時候,如果想要在迭代過程中,想要對元素進行操作的時候,比如滿足條件添加新元素。會發生.ConcurrentModificationException并發修改異常。
導致的原因是:
集合引用和迭代器引用在同時操作元素,通過集合獲取到對應的迭代器后,在迭代中,進行集合引用的元素添加,迭代器并不知道,所以會出現異常情況。
如何解決呢?
既然是在迭代中對元素進行操作,找迭代器的方法最為合適.可是Iterator中只有hasNext,next,remove方法.通過查閱的它的子接口,ListIterator,發現該列表迭代器接口具備了對元素的增、刪、改、查的動作。
ListIterator是List集合特有的迭代器。
ListIterator it = list.listIterator;//取代Iterator it = list.iterator;
方法摘要 | |
void | add(E e) 將指定的元素插入列表(可選操作)。 |
boolean | hasNext() 以正向遍歷列表時,如果列表迭代器有多個元素,則返回 true(換句話說,如果 next 返回一個元素而不是拋出異常,則返回 true)。 |
boolean | hasPrevious() 如果以逆向遍歷列表,列表迭代器有多個元素,則返回 true。 |
E | next() 返回列表中的下一個元素。 |
int | nextIndex() 返回對 next 的后續調用所返回元素的索引。 |
E | previous() 返回列表中的前一個元素。 |
int | previousIndex() 返回對 previous 的后續調用所返回元素的索引。 |
void | remove() 從列表中移除由 next 或 previous 返回的最后一個元素(可選操作)。 |
void | set(E e) 用指定元素替換 next 或 previous 返回的最后一個元素(可選操作)。 |
可變長度數組的原理:
當元素超出數組長度,會產生一個新數組,將原數組的數據復制到新數組中,再將新的元素添加到新數組中。
ArrayList:是按照原數組的50%延長。構造一個初始容量為 10 的空列表。
Vector:是按照原數組的100%延長。
注意:對于list集合,底層判斷元素是否相同,其實用的是元素自身的equals方法完成的。所以建議元素都要復寫equals方法,建立元素對象自己的比較相同的條件依據。
LinkedList:的特有方法。
addFirst();
addLast();
在jdk1.6以后。
offerFirst();
offerLast();
getFirst():獲取鏈表中的第一個元素。如果鏈表為空,拋出NoSuchElementException;
getLast();
在jdk1.6以后。
peekFirst();獲取鏈表中的第一個元素。如果鏈表為空,返回null。
peekLast();
removeFirst():獲取鏈表中的第一個元素,但是會刪除鏈表中的第一個元素。如果鏈表為空,拋出NoSuchElementException
removeLast();
在jdk1.6以后。
pollFirst();獲取鏈表中的第一個元素,但是會刪除鏈表中的第一個元素。如果鏈表為空,返回null。
pollLast();
------------------------------------------------------------
--< java.util >-- Set接口:
Set接口中的方法和Collection中方法一致的。Set接口取出方式只有一種,迭代器。
|--HashSet:底層數據結構是哈希表,線程是不同步的。無序,高效;
HashSet集合保證元素唯一性:通過元素的hashCode方法,和equals方法完成的。
當元素的hashCode值相同時,才繼續判斷元素的equals是否為true。
如果為true,那么視為相同元素,不存。如果為false,那么存儲。
如果hashCode值不同,那么不判斷equals,從而提高對象比較的速度。
|--LinkedHashSet:有序,hashset的子類。
|--TreeSet:對Set集合中的元素的進行指定順序的排序。不同步。TreeSet底層的數據結構就是二叉樹。
哈希表的原理:
1,對對象元素中的關鍵字(對象中的特有數據),進行哈希算法的運算,并得出一個具體的算法值,這個值 稱為哈希值。
2,哈希值就是這個元素的位置。
3,如果哈希值出現沖突,再次判斷這個關鍵字對應的對象是否相同。如果對象相同,就不存儲,因為元素重復。如果對象不同,就存儲,在原來對象的哈希值基礎 +1順延。
4,存儲哈希值的結構,我們稱為哈希表。
5,既然哈希表是根據哈希值存儲的,為了提高效率,最好保證對象的關鍵字是唯一的。
這樣可以盡量少的判斷關鍵字對應的對象是否相同,提高了哈希表的操作效率。
對于ArrayList集合,判斷元素是否存在,或者刪元素底層依據都是equals方法。
對于HashSet集合,判斷元素是否存在,或者刪除元素,底層依據的是hashCode方法和equals方法。
TreeSet:
用于對Set集合進行元素的指定順序排序,排序需要依據元素自身具備的比較性。
如果元素不具備比較性,在運行時會發生ClassCastException異常。
所以需要元素實現Comparable接口,強制讓元素具備比較性,復寫compareTo方法。
依據compareTo方法的返回值,確定元素在TreeSet數據結構中的位置。
TreeSet方法保證元素唯一性的方式:就是參考比較方法的結果是否為0,如果return 0,視為兩個對象重復,不存。
注意:在進行比較時,如果判斷元素不唯一,比如,同姓名,同年齡,才視為同一個人。
在判斷時,需要分主要條件和次要條件,當主要條件相同時,再判斷次要條件,按照次要條件排序。
TreeSet集合排序有兩種方式,Comparable和Comparator區別:
1:讓元素自身具備比較性,需要元素對象實現Comparable接口,覆蓋compareTo方法。
2:讓集合自身具備比較性,需要定義一個實現了Comparator接口的比較器,并覆蓋compare方法,并將該類對象作為實際參數傳遞給TreeSet集合的構造函數。
第二種方式較為靈活。
------------------------------------------------------------
Map集合:
|--Hashtable:底層是哈希表數據結構,是線程同步的。不可以存儲null鍵,null值。
|--HashMap:底層是哈希表數據結構,是線程不同步的。可以存儲null鍵,null值。替代了Hashtable.
|--TreeMap:底層是二叉樹結構,可以對map集合中的鍵進行指定順序的排序。
Map集合存儲和Collection有著很大不同:
Collection一次存一個元素;Map一次存一對元素。
Collection是單列集合;Map是雙列集合。
Map中的存儲的一對元素:一個是鍵,一個是值,鍵與值之間有對應(映射)關系。
特點:要保證map集合中鍵的唯一性。
1,添加。
put(key,value):當存儲的鍵相同時,新的值會替換老的值,并將老值返回。如果鍵沒有重復,返回null。
void putAll(Map);
2,刪除。
void clear():清空
value remove(key) :刪除指定鍵。
3,判斷。
boolean isEmpty():
boolean containsKey(key):是否包含key
boolean containsValue(value) :是否包含value
4,取出。
int size():返回長度
value get(key) :通過指定鍵獲取對應的值。如果返回null,可以判斷該鍵不存在。當然有特殊情況,就是在hashmap集合中,是可以存儲null鍵null值的。
Collection values():獲取map集合中的所有的值。
5,想要獲取map中的所有元素:
原理:map中是沒有迭代器的,collection具備迭代器,只要將map集合轉成Set集合,可以使用迭代器了。之所以轉成set,是因為map集合具備著鍵的唯一性,其實set集合就來自于map,set集合底層其實用的就是map的方法。
★ 把map集合轉成set的方法:
Set keySet();
Set entrySet();//取的是鍵和值的映射關系。
Entry就是Map接口中的內部接口;
為什么要定義在map內部呢?entry是訪問鍵值關系的入口,是map的入口,訪問的是map中的鍵值對。
---------------------------------------------------------
取出map集合中所有元素的方式一:keySet()方法。
可以將map集合中的鍵都取出存放到set集合中。對set集合進行迭代。迭代完成,再通過get方法對獲取到的鍵進行值的獲取。
Set keySet = map.keySet();
Iterator it = keySet.iterator();
while(it.hasNext()) {
Object key = it.next();
Object value = map.get(key);
System.out.println(key+":"+value);
}
--------------------------------------------------------
取出map集合中所有元素的方式二:entrySet()方法。
Set entrySet = map.entrySet();
Iterator it = entrySet.iterator();
while(it.hasNext()) {
Map.Entry me = (Map.Entry)it.next();
System.out.println(me.getKey()+"::::"+me.getValue());
}
--------------------------------------------------------
使用集合的技巧:
看到Array就是數組結構,有角標,查詢速度很快。
看到link就是鏈表結構:增刪速度快,而且有特有方法。addFirst; addLast; removeFirst(); removeLast(); getFirst();getLast();
看到hash就是哈希表,就要想要哈希值,就要想到唯一性,就要想到存入到該結構的中的元素必須覆蓋hashCode,equals方法。
看到tree就是二叉樹,就要想到排序,就想要用到比較。
比較的兩種方式:
一個是Comparable:覆蓋compareTo方法;
一個是Comparator:覆蓋compare方法。
LinkedHashSet,LinkedHashMap:這兩個集合可以保證哈希表有存入順序和取出順序一致,保證哈希表有序。
集合什么時候用?
當存儲的是一個元素時,就用Collection。當存儲對象之間存在著映射關系時,就使用Map集合。
保證唯一,就用Set。不保證唯一,就用List。
------------------------------------------------------------------------------------------------
Collections:它的出現給集合操作提供了更多的功能。這個類不需要創建對象,內部提供的都是靜態方法。
靜態方法:
Collections.sort(list);//list集合進行元素的自然順序排序。
Collections.sort(list,new ComparatorByLen());//按指定的比較器方法排序。
class ComparatorByLen implements Comparator<String>{
public int compare(String s1,String s2){
int temp = s1.length()-s2.length();
return temp==0?s1.compareTo(s2):temp;
}
}
Collections.max(list); //返回list中字典順序最大的元素。
int index = Collections.binarySearch(list,"zz");//二分查找,返回角標。
Collections.reverseOrder();//逆向反轉排序。
Collections.shuffle(list);//隨機對list中的元素進行位置的置換。
將非同步集合轉成同步集合的方法:Collections中的 XXX synchronizedXXX(XXX);
List synchronizedList(list);
Map synchronizedMap(map);
原理:定義一個類,將集合所有的方法加同一把鎖后返回。
Collection 和 Collections的區別:
Collections是個java.util下的類,是針對集合類的一個工具類,提供一系列靜態方法,實現對集合的查找、排序、替換、線程安全化(將非同步的集合轉換成同步的)等操作。
Collection是個java.util下的接口,它是各種集合結構的父接口,繼承于它的接口主要有Set和List,提供了關于集合的一些操作,如插入、刪除、判斷一個元素是否其成員、遍歷等。
-------------------------------------------------------
Arrays:
用于操作數組對象的工具類,里面都是靜態方法。
asList方法:將數組轉換成list集合。
String[] arr = {"abc","kk","qq"};
List<String> list = Arrays.asList(arr);//將arr數組轉成list集合。
將數組轉換成集合,有什么好處呢?用aslist方法,將數組變成集合;
可以通過list集合中的方法來操作數組中的元素:isEmpty()、contains、indexOf、set;
注意(局限性):數組是固定長度,不可以使用集合對象增加或者刪除等,會改變數組長度的功能方法。比如add、remove、clear。(會報不支持操作異常UnsupportedOperationException);
如果數組中存儲的引用數據類型,直接作為集合的元素可以直接用集合方法操作。
如果數組中存儲的是基本數據類型,asList會將數組實體作為集合元素存在。
集合變數組:用的是Collection接口中的方法:toArray();
如果給toArray傳遞的指定類型的數據長度小于了集合的size,那么toArray方法,會自定再創建一個該類型的數據,長度為集合的size。
如果傳遞的指定的類型的數組的長度大于了集合的size,那么toArray方法,就不會創建新數組,直接使用該數組即可,并將集合中的元素存儲到數組中,其他為存儲元素的位置默認值null。
所以,在傳遞指定類型數組時,最好的方式就是指定的長度和size相等的數組。
將集合變成數組后有什么好處?限定了對集合中的元素進行增刪操作,只要獲取這些元素即可。
------------------------------------------------------------------------------------------------
Jdk5.0新特性:
Collection在jdk1.5以后,有了一個父接口Iterable,這個接口的出現的將iterator方法進行抽取,提高了擴展性。
--------------------------------------------------
增強for循環:foreach語句,foreach簡化了迭代器。
格式:// 增強for循環括號里寫兩個參數,第一個是聲明一個變量,第二個就是需要迭代的容器
for( 元素類型 變量名 : Collection集合 & 數組 ) {
…
}
高級for循環和傳統for循環的區別:
高級for循環在使用時,必須要明確被遍歷的目標。這個目標,可以是Collection集合或者數組,如果遍歷Collection集合,在遍歷過程中還需要對元素進行操作,比如刪除,需要使用迭代器。
如果遍歷數組,還需要對數組元素進行操作,建議用傳統for循環因為可以定義角標通過角標操作元素。如果只為遍歷獲取,可以簡化成高級for循環,它的出現為了簡化書寫。
高級for循環可以遍歷map集合嗎?不可以。但是可以將map轉成set后再使用foreach語句。
1)、作用:對存儲對象的容器進行迭代: 數組 collection map
2)、增強for循環迭代數組:
String [] arr = {"a", "b", "c"};//數組的靜態定義方式,只試用于數組首次定義的時候
for(String s : arr) {
System.out.println(s);
}
3)、單列集合 Collection:
List list = new ArrayList();
list.add("aaa");
// 增強for循環, 沒有使用泛型的集合能不能使用增強for循環迭代?能
for(Object obj : list) {
String s = (String) obj;
System.out.println(s);
}
4)、雙列集合 Map:
Map map = new HashMap();
map.put("a", "aaa");
// 傳統方式:必須掌握這種方式
Set entrys = map.entrySet(); // 1.獲得所有的鍵值對Entry對象
iter = entrys.iterator(); // 2.迭代出所有的entry
while(iter.hasNext()) {
Map.Entry entry = (Entry) iter.next();
String key = (String) entry.getKey(); // 分別獲得key和value
String value = (String) entry.getValue();
System.out.println(key + "=" + value);
}
// 增強for循環迭代:原則上map集合是無法使用增強for循環來迭代的,因為增強for循環只能針對實現了Iterable接口的集合進行迭代;Iterable是jdk5中新定義的接口,就一個方法iterator方法,只有實現了Iterable接口的類,才能保證一定有iterator方法,java有這樣的限定是因為增強for循環內部還是用迭代器實現的,而實際上,我們可以通過某種方式來使用增強for循環。
for(Object obj : map.entrySet()) {
Map.Entry entry = (Entry) obj; // obj 依次表示Entry
System.out.println(entry.getKey() + "=" + entry.getValue());
}
5)、集合迭代注意問題:在迭代集合的過程中,不能對集合進行增刪操作(會報并發訪問異常);可以用迭代器的方法進行操作(子類listIterator:有增刪的方法)。
6)、增強for循環注意問題:在使用增強for循環時,不能對元素進行賦值;
int[] arr = {1,2,3};
for(int num : arr) {
num = 0; //不能改變數組的值
}
System.out.println(arr[1]); //2
--------------------------------------------------
可變參數(...):用到函數的參數上,當要操作的同一個類型元素個數不確定的時候,可是用這個方式,這個參數可以接受任意個數的同一類型的數據。
和以前接收數組不一樣的是:
以前定義數組類型,需要先創建一個數組對象,再將這個數組對象作為參數傳遞給函數。現在,直接將數組中的元素作為參數傳遞即可。底層其實是將這些元素進行數組的封裝,而這個封裝動作,是在底層完成的,被隱藏了。所以簡化了用戶的書寫,少了調用者定義數組的動作。
如果在參數列表中使用了可變參數,可變參數必須定義在參數列表結尾(也就是必須是最后一個參數,否則編譯會失敗。)。
如果要獲取多個int數的和呢?可以使用將多個int數封裝到數組中,直接對數組求和即可。
---------------------------------------------------
靜態導入:導入了類中的所有靜態成員,簡化靜態成員的書寫。
import static java.util.Collections.*; //導入了Collections類中的所有靜態成員
---------------------------------------------------
枚舉:關鍵字 enum
問題:對象的某個屬性的值不能是任意的,必須為固定的一組取值其中的某一個;
解決辦法:
1)、在setGrade方法中做判斷,不符合格式要求就拋出異常;
2)、直接限定用戶的選擇,通過自定義類模擬枚舉的方式來限定用戶的輸入,寫一個Grade類,私有構造函數,對外提供5個靜態的常量表示類的實例;
3)、jdk5中新定義了枚舉類型,專門用于解決此類問題;
4)、枚舉就是一個特殊的java類,可以定義屬性、方法、構造函數、實現接口、繼承類;
------------------------------------------------------------------------------
自動拆裝箱:java中數據類型分為兩種 : 基本數據類型 引用數據類型(對象)
在 java程序中所有的數據都需要當做對象來處理,針對8種基本數據類型提供了包裝類,如下:
int --> Integer
byte --> Byte
short --> Short
long --> Long
char --> Character
double --> Double
float --> Float
boolean --> Boolean
jdk5以前基本數據類型和包裝類之間需要互轉:
基本---引用 Integer x = new Integer(x);
引用---基本 int num = x.intValue();
1)、Integer x = 1; x = x + 1; 經歷了什么過程?裝箱 à 拆箱 à 裝箱;
2)、為了優化,虛擬機為包裝類提供了緩沖池,Integer池的大小 -128~127 一個字節的大小;
3)、String池:Java為了優化字符串操作 提供了一個緩沖池;
----------------------------------------------------------
泛型:jdk1.5版本以后出現的一個安全機制。表現格式:< >
好處:
1:將運行時期的問題ClassCastException問題轉換成了編譯失敗,體現在編譯時期,程序員就可以解決問題。
2:避免了強制轉換的麻煩。
只要帶有<>的類或者接口,都屬于帶有類型參數的類或者接口,在使用這些類或者接口時,必須給<>中傳遞一個具體的引用數據類型。
泛型技術:其實應用在編譯時期,是給編譯器使用的技術,到了運行時期,泛型就不存在了。
為什么? 因為泛型的擦除:也就是說,編輯器檢查了泛型的類型正確后,在生成的類文件中是沒有泛型的。
在運行時,如何知道獲取的元素類型而不用強轉呢?
泛型的補償:因為存儲的時候,類型已經確定了是同一個類型的元素,所以在運行時,只要獲取到該元素的類型,在內部進行一次轉換即可,所以使用者不用再做轉換動作了。
什么時候用泛型類呢?
當類中的操作的引用數據類型不確定的時候,以前用的Object來進行擴展的,現在可以用泛型來表示。這樣可以避免強轉的麻煩,而且將運行問題轉移到的編譯時期。
----------------------------------------------------------
泛型在程序定義上的體現:
//泛型類:將泛型定義在類上。
class Tool<Q> {
private Q obj;
public void setObject(Q obj) {
this.obj = obj;
}
public Q getObject() {
return obj;
}
}
//當方法操作的引用數據類型不確定的時候,可以將泛型定義在方法上。
public <W> void method(W w) {
System.out.println("method:"+w);
}
//靜態方法上的泛型:靜態方法無法訪問類上定義的泛型。如果靜態方法操作的引用數據類型不確定的時候,必須要將泛型定義在方法上。
public static <Q> void function(Q t) {
System.out.println("function:"+t);
}
//泛型接口.
interface Inter<T> {
void show(T t);
}
class InterImpl<R> implements Inter<R> {
public void show(R r) {
System.out.println("show:"+r);
}
}
------------------------------------------------------------
泛型中的通配符:可以解決當具體類型不確定的時候,這個通配符就是 ? ;當操作類型時,不需要使用類型的具體功能時,只使用Object類中的功能。那么可以用 ? 通配符來表未知類型。
泛型限定:
上限:?extends E:可以接收E類型或者E的子類型對象。
下限:?super E:可以接收E類型或者E的父類型對象。
上限什么時候用:往集合中添加元素時,既可以添加E類型對象,又可以添加E的子類型對象。為什么?因為取的時候,E類型既可以接收E類對象,又可以接收E的子類型對象。
下限什么時候用:當從集合中獲取元素進行操作的時候,可以用當前元素的類型接收,也可以用當前元素的父類型接收。
泛型的細節:
1)、泛型到底代表什么類型取決于調用者傳入的類型,如果沒傳,默認是Object類型;
2)、使用帶泛型的類創建對象時,等式兩邊指定的泛型必須一致;
原因:編譯器檢查對象調用方法時只看變量,然而程序運行期間調用方法時就要考慮對象具體類型了;
3)、等式兩邊可以在任意一邊使用泛型,在另一邊不使用(考慮向后兼容);
ArrayList<String> al = new ArrayList<Object>(); //錯
//要保證左右兩邊的泛型具體類型一致就可以了,這樣不容易出錯。
ArrayList<? extends Object> al = new ArrayList<String>();
al.add("aa"); //錯
//因為集合具體對象中既可存儲String,也可以存儲Object的其他子類,所以添加具體的類型對象不合適,類型檢查會出現安全問題。 ?extends Object 代表Object的子類型不確定,怎么能添加具體類型的對象呢?
public static void method(ArrayList<? extends Object> al) {
al.add("abc"); //錯
//只能對al集合中的元素調用Object類中的方法,具體子類型的方法都不能用,因為子類型不確定。
}
------------------------------------------------------------------------------------------------------------------------------------------------
API--- java.lang.System: 屬性和行為都是靜態的。
long currentTimeMillis(); // 返回當前時間毫秒值
exit(); // 退出虛擬機
Properties getProperties() ; // 獲取當前系統的屬性信息
Properties prop = System.getProperties(); //獲取系統的屬性信息,并將這些信息存儲到Properties集合中。
System.setProperty("myname","畢老師"); //給系統屬性信息集添加具體的屬性信息
//臨時設置方式:運行jvm時,可以通過jvm的參數進行系統屬性的臨時設置,可以在java命令的后面加入 –D<name>=<value> 用法:java –Dmyname=小明 類名。
String name = System.getProperty("os.name");//獲取指定屬性的信息
//想要知道該系統是否是該軟件所支持的系統中的一個。
Set<String> hs = new HashSet<String>();
hs.add("Windows XP");
hs.add("Windows 7");
if(hs.contains(name))
System.out.println("可以支持");
else
System.out.println("不支持");
--------------------------------------------------------------------------------------------------------------------
API--- java.lang.Runtime: 類中沒有構造方法,不能創建對象。
但是有非靜態方法。說明該類中應該定義好了對象,并可以通過一個static方法獲取這個對象。用這個對象來調用非靜態方法。這個方法就是 static Runtime getRuntime();
這個Runtime其實使用單例設計模式進行設計。
class RuntimeDemo {
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
Process p = r.exec("notepad.exe SystemDemo.java"); //運行指定的程序
Thread.sleep(4000);
p.destroy(); //殺掉進程
}
}
--------------------------------------------------------------------------------------------------------------------
API--- java.util.Math: 用于數學運算的工具類,屬性和行為都是靜態的。該類是final不允許繼承。
static double ceil(double a) ; //返回大于指定數值的最小整數
static double floor(double a) ; //返回小于指定數值的最大整數
static long round(double a) ; //四舍五入成整數
static double pow(double a, double b) ; //a的b次冪
static double random(); //返回0~1的偽隨機數
public static void main(String[] args) {
Random r = new Random();
for(int x=0; x<10; x++) {
//double d = Math.floor(Math.random()*10+1);
//int d = (int)(Math.random()*10+1);
int d = r.nextInt(10)+1;
System.out.println(d);
}
}
--------------------------------------------------------------------------------------------------------------------
API--- java.util.Date:日期類,月份從0-11;
/*
日期對象和毫秒值之間的轉換。
1,日期對象轉成毫秒值。Date類中的getTime方法。
2,如何將獲取到的毫秒值轉成具體的日期呢?
Date類中的setTime方法。也可以通過構造函數。
*/
//日期對象轉成毫秒值
Date d = new Date();
long time1 = d.getTime();
long time2 = System.currentTimeMillis(); / /毫秒值。
//毫秒值轉成具體的日期
long time = 1322709921312l;
Date d = new Date();
d.setTime(time);
/*
將日期字符串轉換成日期對象:使用的就是DateFormat方法中的 Date parse(String source) ;
*/
public static void method() throws Exception {
String str_time = "2011/10/25";
DateFormat df = new SimpleDateFormat("yyyy/MM/dd"); //SimpleDateFormat作為可以指定用戶自定義的格式來完成格式化。
Date d = df.parse(str_time);
}
/*
如果不需要使用特定的格式化風格,完全可以使用DateFormat類中的靜態工廠方法獲取具體的已經封裝好風格的對象。getDateInstance();getDateTimeInstance();
*/
Date d = new Date();
DateFormat df = DateFormat.getDateInstance(DateFormat.LONG);
df = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG);
String str_time = df.format(d);
//將日期對象轉換成字符串的方式:DateFormat類中的format方法。
//創建日期格式對象。
DateFormat df = new SimpleDateFormat(); //該對象的建立內部會封裝一個默認的日期格式。11-12-1 下午1:48
//如果想要自定義日期格式的話。可使用SimpleDateFormat的構造函數。將具體的格式作為參數傳入到構造函數中。如何表示日期中年的部分呢?可以必須要參與格式對象文檔。
df = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
//調用DateFormat中的format方法。對已有的日期對象進行格式化。
String str_time = df.format(d);
--------------------------------------------------------------------------------------------------------------------
API--- java.util. Calendar:日歷類
public static void method(){
Calendar c = Calendar.getInstance();
System.out.println(c.get(Calendar.YEAR)+"年"+(c.get(Calendar.MONTH)+1)+"月"
+getNum(c.get(Calendar.DAY_OF_MONTH))+"日"
+"星期"+getWeek(c.get(Calendar.DAY_OF_WEEK)));
}
public static String getNum(int num){
return num>9 ? num+"" : "0"+num;
}
public static String getWeek(int index){
/*
查表法:建立數據的對應關系.
最好:數據個數是確定的,而且有對應關系。如果對應關系的一方,是數字,而且可以作為角標,那么可以通過數組來作為表。
*/
String[] weeks = {"","日","一","二","三","四","五","六"};
return weeks[index];
}
------------------------------------------------------------------------------------------------------------------------------------------------
IO流:★★★★★,用于處理設備上數據。
流:可以理解數據的流動,就是一個數據流。IO流最終要以對象來體現,對象都存在IO包中。
流也進行分類:
1:輸入流(讀)和輸出流(寫)。
2:因為處理的數據不同,分為字節流和字符流。
字節流:處理字節數據的流對象。設備上的數據無論是圖片或者dvd,文字,它們都以二進制存儲的。二進制的最終都是以一個8位為數據單元進行體現,所以計算機中的最小數據單元就是字節。意味著,字節流可以處理設備上的所有數據,所以字節流一樣可以處理字符數據。
那么為什么要有字符流呢?因為字符每個國家都不一樣,所以涉及到了字符編碼問題,那么GBK編碼的中文用unicode編碼解析是有問題的,所以需要獲取中文字節數據的同時+ 指定的編碼表才可以解析正確數據。為了方便于文字的解析,所以將字節流和編碼表封裝成對象,這個對象就是字符流。只要操作字符數據,優先考慮使用字符流體系。
注意:流的操作只有兩種:讀和寫。
流的體系因為功能不同,但是有共性內容,不斷抽取,形成繼承體系。該體系一共有四個基類,而且都是抽象類。
字節流:InputStream OutputStream
字符流:Reader Writer
在這四個系統中,它們的子類,都有一個共性特點:子類名后綴都是父類名,前綴名都是這個子類的功能名稱。
--------------------------------------------------------------------------------------------------------------------
public static void main(String[] args) throws IOException { //讀、寫都會發生IO異常
/*
1:創建一個字符輸出流對象,用于操作文件。該對象一建立,就必須明確數據存儲位置,是一個文件。
2:對象產生后,會在堆內存中有一個實體,同時也調用了系統底層資源,在指定的位置創建了一個存儲數據的文件。
3:如果指定位置,出現了同名文件,文件會被覆蓋。
*/
FileWriter fw = new FileWriter("demo.txt"); // FileNotFoundException
/*
調用Writer類中的write方法寫入字符串。字符串并未直接寫入到目的地中,而是寫入到了流中,(其實是寫入到內存緩沖區中)。怎么把數據弄到文件中?
*/
fw.write("abcde");
fw.flush(); // 刷新緩沖區,將緩沖區中的數據刷到目的地文件中。
fw.close(); // 關閉流,其實關閉的就是java調用的系統底層資源。在關閉前,會先刷新該流。
}
close()和flush()的區別:
flush():將緩沖區的數據刷到目的地中后,流可以使用。
close():將緩沖區的數據刷到目的地中后,流就關閉了,該方法主要用于結束調用的底層資源。這個動作一定做。
--------------------------------------------------------------------------------------------------------------------
io異常的處理方式:io一定要寫finally;
FileWriter寫入數據的細節:
1:window中的換行符:\r\n兩個符號組成。 linux:\n。
2:續寫數據,只要在構造函數中傳入新的參數true。
3:目錄分割符:window \\ /
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("demo.txt",true);
fw.write("abcde");
}
catch (IOException e ){
System.out.println(e.toString()+"....");
}
finally{
if(fw!=null)
try{
fw.close();
}
catch (IOException e){
System.out.println("close:"+e.toString());
}
}
}
--------------------------------------------------------------------------------------------------------------------
FileReader:使用Reader體系,讀取一個文本文件中的數據。返回 -1 ,標志讀到結尾。
import java.io.*;
class FileReaderDemo {
public static void main(String[] args) throws IOException {
/*
創建可以讀取文本文件的流對象,FileReader讓創建好的流對象和指定的文件相關聯。
*/
FileReader fr = new FileReader("demo.txt");
int ch = 0;
while((ch = fr.read())!= -1) { //條件是沒有讀到結尾
System.out.println((char)ch); //調用讀取流的read方法,讀取一個字符。
}
fr.close();
}
}
--------------------------------------------------------------------------------------------------------------------
讀取數據的第二種方式:第二種方式較為高效,自定義緩沖區。
import java.io.*;
class FileReaderDemo2 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("demo.txt"); //創建讀取流對象和指定文件關聯。
//因為要使用read(char[])方法,將讀取到字符存入數組。所以要創建一個字符數組,一般數組的長度都是1024的整數倍。
char[] buf = new char[1024];
int len = 0;
while(( len=fr.read(buf)) != -1) {
System.out.println(new String(buf,0,len));
}
fr.close();
}
}
--------------------------------------------------------------------------------------------------------------------
IO中的使用到了一個設計模式:裝飾設計模式。
裝飾設計模式解決:對一組類進行功能的增強。
包裝:寫一個類(包裝類)對被包裝對象進行包裝;
* 1、包裝類和被包裝對象要實現同樣的接口;
* 2、包裝類要持有一個被包裝對象;
* 3、包裝類在實現接口時,大部分方法是靠調用被包裝對象來實現的,對于需要修改的方法我們自己實現;
--------------------------------------------------------------------------------------------------------------------
字符流:
Reader:用于讀取字符流的抽象類。子類必須實現的方法只有 read(char[], int, int) 和 close()。
|---BufferedReader:從字符輸入流中讀取文本,緩沖各個字符,從而實現字符、數組和行的高效讀取。 可以指定緩沖區的大小,或者可使用默認的大小。大多數情況下,默認值就足夠大了。
|---LineNumberReader:跟蹤行號的緩沖字符輸入流。此類定義了方法 setLineNumber(int) 和 getLineNumber(),它們可分別用于設置和獲取當前行號。
|---InputStreamReader:是字節流通向字符流的橋梁:它使用指定的 charset 讀取字節并將其解碼為字符。它使用的字符集可以由名稱指定或顯式給定,或者可以接受平臺默認的字符集。
|---FileReader:用來讀取字符文件的便捷類。此類的構造方法假定默認字符編碼和默認字節緩沖區大小都是適當的。要自己指定這些值,可以先在 FileInputStream 上構造一個 InputStreamReader。
|---CharArrayReader:
|---StringReader:
-------------------------------------------------
Writer:寫入字符流的抽象類。子類必須實現的方法僅有 write(char[], int, int)、flush() 和 close()。
|---BufferedWriter:將文本寫入字符輸出流,緩沖各個字符,從而提供單個字符、數組和字符串的高效寫入。
|---OutputStreamWriter:是字符流通向字節流的橋梁:可使用指定的 charset 將要寫入流中的字符編碼成字節。它使用的字符集可以由名稱指定或顯式給定,否則將接受平臺默認的字符集。
|---FileWriter:用來寫入字符文件的便捷類。此類的構造方法假定默認字符編碼和默認字節緩沖區大小都是可接受的。要自己指定這些值,可以先在 FileOutputStream 上構造一個 OutputStreamWriter。
|---PrintWriter:
|---CharArrayWriter:
|---StringWriter:
---------------------------------
字節流:
InputStream:是表示字節輸入流的所有類的超類。
|--- FileInputStream:從文件系統中的某個文件中獲得輸入字節。哪些文件可用取決于主機環境。FileInputStream 用于讀取諸如圖像數據之類的原始字節流。要讀取字符流,請考慮使用 FileReader。
|--- FilterInputStream:包含其他一些輸入流,它將這些流用作其基本數據源,它可以直接傳輸數據或提供一些額外的功能。
|--- BufferedInputStream:該類實現緩沖的輸入流。
|--- Stream:
|--- ObjectInputStream:
|--- PipedInputStream:
-----------------------------------------------
OutputStream:此抽象類是表示輸出字節流的所有類的超類。
|--- FileOutputStream:文件輸出流是用于將數據寫入 File 或 FileDescriptor 的輸出流。
|--- FilterOutputStream:此類是過濾輸出流的所有類的超類。
|--- BufferedOutputStream:該類實現緩沖的輸出流。
|--- PrintStream:
|--- DataOutputStream:
|--- ObjectOutputStream:
|--- PipedOutputStream:
--------------------------------
緩沖區是提高效率用的,給誰提高呢?
BufferedWriter:是給字符輸出流提高效率用的,那就意味著,緩沖區對象建立時,必須要先有流對象。明確要提高具體的流對象的效率。
FileWriter fw = new FileWriter("bufdemo.txt");
BufferedWriter bufw = new BufferedWriter(fw);//讓緩沖區和指定流相關聯。
for(int x=0; x<4; x++){
bufw.write(x+"abc");
bufw.newLine(); //寫入一個換行符,這個換行符可以依據平臺的不同寫入不同的換行符。
bufw.flush();//對緩沖區進行刷新,可以讓數據到目的地中。
}
bufw.close();//關閉緩沖區,其實就是在關閉具體的流。
-----------------------------
BufferedReader:
FileReader fr = new FileReader("bufdemo.txt");
BufferedReader bufr = new BufferedReader(fr);
String line = null;
while((line=bufr.readLine())!=null){ //readLine方法返回的時候是不帶換行符的。
System.out.println(line);
}
bufr.close();
-----------------------------
//記住,只要一讀取鍵盤錄入,就用這句話。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//輸出到控制臺
String line = null;
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
bufw.write(line.toUpperCase());//將輸入的字符轉成大寫字符輸出
bufw.newLine();
bufw.flush();
}
bufw.close();
bufr.close();
------------------------------
流對象:其實很簡單,就是讀取和寫入。但是因為功能的不同,流的體系中提供N多的對象。那么開始時,到底該用哪個對象更為合適呢?這就需要明確流的操作規律。
流的操作規律:
1,明確源和目的。
數據源:就是需要讀取,可以使用兩個體系:InputStream、Reader;
數據匯:就是需要寫入,可以使用兩個體系:OutputStream、Writer;
2,操作的數據是否是純文本數據?
如果是:數據源:Reader
數據匯:Writer
如果不是:數據源:InputStream
數據匯:OutputStream
3,雖然確定了一個體系,但是該體系中有太多的對象,到底用哪個呢?
明確操作的數據設備。
數據源對應的設備:硬盤(File),內存(數組),鍵盤(System.in)
數據匯對應的設備:硬盤(File),內存(數組),控制臺(System.out)。
4,需要在基本操作上附加其他功能嗎?比如緩沖。
如果需要就進行裝飾。
轉換流特有功能:轉換流可以將字節轉成字符,原因在于,將獲取到的字節通過查編碼表獲取到指定對應字符。
轉換流的最強功能就是基于 字節流 + 編碼表 。沒有轉換,沒有字符流。
發現轉換流有一個子類就是操作文件的字符流對象:
InputStreamReader
|--FileReader
OutputStreamWriter
|--FileWrier
想要操作文本文件,必須要進行編碼轉換,而編碼轉換動作轉換流都完成了。所以操作文件的流對象只要繼承自轉換流就可以讀取一個字符了。
但是子類有一個局限性,就是子類中使用的編碼是固定的,是本機默認的編碼表,對于簡體中文版的系統默認碼表是GBK。
FileReader fr = new FileReader("a.txt");
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");
以上兩句代碼功能一致,
如果僅僅使用平臺默認碼表,就使用FileReader fr = new FileReader("a.txt"); //因為簡化。
如果需要制定碼表,必須用轉換流。
轉換流 = 字節流+編碼表。
轉換流的子類File = 字節流 + 默認編碼表。
凡是操作設備上的文本數據,涉及編碼轉換,必須使用轉換流。
-----------------------------------------------------------------------------------------------
File類:將文件系統中的文件和文件夾封裝成了對象。提供了更多的屬性和行為可以對這些文件和文件夾進行操作。這些是流對象辦不到的,因為流只操作數據。
File類常見方法:
1:創建。
boolean createNewFile():在指定目錄下創建文件,如果該文件已存在,則不創建。而對操作文件的輸出流而言,輸出流對象已建立,就會創建文件,如果文件已存在,會覆蓋。除非續寫。
boolean mkdir():創建此抽象路徑名指定的目錄。
boolean mkdirs():創建多級目錄。
2:刪除。
boolean delete():刪除此抽象路徑名表示的文件或目錄。
void deleteOnExit():在虛擬機退出時刪除。
注意:在刪除文件夾時,必須保證這個文件夾中沒有任何內容,才可以將該文件夾用delete刪除。
window的刪除動作,是從里往外刪。注意:java刪除文件不走回收站。要慎用。
3:獲取.
long length():獲取文件大小。
String getName():返回由此抽象路徑名表示的文件或目錄的名稱。
String getPath():將此抽象路徑名轉換為一個路徑名字符串。
String getAbsolutePath():返回此抽象路徑名的絕對路徑名字符串。
String getParent():返回此抽象路徑名父目錄的抽象路徑名,如果此路徑名沒有指定父目錄,則返回 null。
long lastModified():返回此抽象路徑名表示的文件最后一次被修改的時間。
File.pathSeparator:返回當前系統默認的路徑分隔符,windows默認為 “;”。
File.Separator:返回當前系統默認的目錄分隔符,windows默認為 “\”。
4:判斷:
boolean exists():判斷文件或者文件夾是否存在。
boolean isDirectory():測試此抽象路徑名表示的文件是否是一個目錄。
boolean isFile():測試此抽象路徑名表示的文件是否是一個標準文件。
boolean isHidden():測試此抽象路徑名指定的文件是否是一個隱藏文件。
boolean isAbsolute():測試此抽象路徑名是否為絕對路徑名。
5:重命名。
boolean renameTo(File dest):可以實現移動的效果。剪切+重命名。
String[] list():列出指定目錄下的當前的文件和文件夾的名稱。包含隱藏文件。
如果調用list方法的File 對象中封裝的是一個文件,那么list方法返回數組為null。如果封裝的對象不存在也會返回null。只有封裝的對象存在并且是文件夾時,這個方法才有效。
------------------------------------------------------------------------------------------------
遞歸:就是函數自身調用自身。
什么時候用遞歸呢?
當一個功能被重復使用,而每一次使用該功能時的參數不確定,都由上次的功能元素結果來確定。
簡單說:功能內部又用到該功能,但是傳遞的參數值不確定。(每次功能參與運算的未知內容不確定)。
遞歸的注意事項:
1:一定要定義遞歸的條件。
2:遞歸的次數不要過多。容易出現 StackOverflowError 棧內存溢出錯誤。
其實遞歸就是在棧內存中不斷的加載同一個函數。
------------------------------------------------------------------------------------------------
Java.util.Properties:一個可以將鍵值進行持久化存儲的對象。Map--Hashtable的子類。
Map
|--Hashtable
|--Properties:用于屬性配置文件,鍵和值都是字符串類型。
特點:1:可以持久化存儲數據。2:鍵值都是字符串。3:一般用于配置文件。
|-- load():將流中的數據加載進集合。
原理:其實就是將讀取流和指定文件相關聯,并讀取一行數據,因為數據是規則的key=value,所以獲取一行后,通過 = 對該行數據進行切割,左邊就是鍵,右邊就是值,將鍵、值存儲到properties集合中。
|-- store():寫入各個項后,刷新輸出流。
|-- list():將集合的鍵值數據列出到指定的目的地。
-------------------------------------------------------------------------------------------------
以下介紹IO包中擴展功能的流對象:基本都是裝飾設計模式。
Java.io.outputstream.PrintStream:打印流
1:提供了更多的功能,比如打印方法。可以直接打印任意類型的數據。
2:它有一個自動刷新機制,創建該對象,指定參數,對于指定方法可以自動刷新。
3:它使用的本機默認的字符編碼.
4:該流的print方法不拋出IOException。
該對象的構造函數。
PrintStream(File file) :創建具有指定文件且不帶自動行刷新的新打印流。
PrintStream(File file, String csn) :創建具有指定文件名稱和字符集且不帶自動行刷新的新打印流。
PrintStream(OutputStream out) :創建新的打印流。
PrintStream(OutputStream out, boolean autoFlush) :創建新的打印流。
PrintStream(OutputStream out, boolean autoFlush, String encoding) :創建新的打印流。
PrintStream(String fileName) :創建具有指定文件名稱且不帶自動行刷新的新打印流。
PrintStream(String fileName, String csn)
PrintStream可以操作目的:1:File對象。2:字符串路徑。3:字節輸出流。
前兩個都JDK1.5版本才出現。而且在操作文本文件時,可指定字符編碼了。
當目的是一個字節輸出流時,如果使用的println方法,可以在printStream對象上加入一個true參數。這樣對于println方法可以進行自動的刷新,而不是等待緩沖區滿了再刷新。最終print方法都將具體的數據轉成字符串,而且都對IO異常進行了內部處理。
既然操作的數據都轉成了字符串,那么使用PrintWriter更好一些。因為PrintWrite是字符流的子類,可以直接操作字符數據,同時也可以指定具體的編碼。
--------------------------------------------------------
PrintWriter:具備了PrintStream的特點同時,還有自身特點:
該對象的目的地有四個:1:File對象。2:字符串路徑。3:字節輸出流。4:字符輸出流。
開發時盡量使用PrintWriter。
方法中直接操作文件的第二參數是編碼表。
直接操作輸出流的,第二參數是自動刷新。
//讀取鍵盤錄入將數據轉成大寫顯示在控制臺.
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//源:鍵盤輸入
//目的:把數據寫到文件中,還想自動刷新。
PrintWriter out = new PrintWriter(new FileWriter("out.txt"),true);//設置true后自動刷新
String line = null;
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
out.println(line.toUpperCase());//轉大寫輸出
}
//注意:System.in,System.out這兩個標準的輸入輸出流,在jvm啟動時已經存在了。隨時可以使用。當jvm結束了,這兩個流就結束了。但是,當使用了顯示的close方法關閉時,這兩個流在提前結束了。
out.close();
bufr.close();
------------------------------------------------------------------------------------------------
SequenceInputStream:序列流,作用就是將多個讀取流合并成一個讀取流。實現數據合并。
表示其他輸入流的邏輯串聯。它從輸入流的有序集合開始,并從第一個輸入流開始讀取,直到到達文件末尾,接著從第二個輸入流讀取,依次類推,直到到達包含的最后一個輸入流的文件末尾為止。
這樣做,可以更方便的操作多個讀取流,其實這個序列流內部會有一個有序的集合容器,用于存儲多個讀取流對象。
該對象的構造函數參數是枚舉,想要獲取枚舉,需要有Vector集合,但不高效。需用ArrayList,但ArrayList中沒有枚舉,只有自己去創建枚舉對象。
但是方法怎么實現呢?因為枚舉操作的是具體集合中的元素,所以無法具體實現,但是枚舉和迭代器是功能一樣的,所以,可以用迭代替代枚舉。
合并原理:多個讀取流對應一個輸出流。
切割原理:一個讀取流對應多個輸出流。
import java.io.*;
import java.util.*;
class SplitFileDemo{
private static final String CFG = ".properties";
private static final String SP = ".part";
public static void main(String[] args) throws IOException{
File file = new File("c:\\0.bmp");
File dir = new File("c:\\partfiles");
meger(dir);
}
//數據的合并。
public static void meger(File dir)throws IOException{
if(!(dir.exists() && dir.isDirectory()))
throw new RuntimeException("指定的目錄不存在,或者不是正確的目錄");
File[] files = dir.listFiles(new SuffixFilter(CFG));
if(files.length==0)
throw new RuntimeException("擴展名.proerpties的文件不存在");
//獲取到配置文件
File config = files[0];
//獲取配置文件的信息。
Properties prop = new Properties();
FileInputStream fis = new FileInputStream(config);
prop.load(fis);
String fileName = prop.getProperty("filename");
int partcount = Integer.parseInt(prop.getProperty("partcount"));
//--------------------------
File[] partFiles = dir.listFiles(new SuffixFilter(SP));
if(partFiles.length!=partcount)
throw new RuntimeException("缺少碎片文件");
//---------------------
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
for(int x=0; x<partcount; x++){
al.add(new FileInputStream(new File(dir,x+SP)));
}
Enumeration<FileInputStream> en = Collections.enumeration(al);
SequenceInputStream sis = new SequenceInputStream(en);
File file = new File(dir,fileName);
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len = 0;
while((len=sis.read(buf))!=-1){
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
//帶有配置信息的數據切割。
public static void splitFile(File file)throws IOException{
//用一個讀取流和文件關聯。
FileInputStream fis = new FileInputStream(file);
//創建目的地。因為有多個。所以先創建引用。
FileOutputStream fos = null;
//指定碎片的位置。
File dir = new File("c:\\partfiles");
if(!dir.exists())
dir.mkdir();
//碎片文件大小引用。
File f = null;
byte[] buf = new byte[1024*1024];
//因為切割完的文件通常都有規律的。為了簡單標記規律使用計數器。
int count = 0;
int len = 0;
while((len=fis.read(buf))!=-1){
f = new File(dir,(count++)+".part");
fos = new FileOutputStream(f);
fos.write(buf,0,len);
fos.close();
}
//碎片文件生成后,還需要定義配置文件記錄生成的碎片文件個數。以及被切割文件的名稱。
//定義簡單的鍵值信息,可是用Properties。
String filename = file.getName();
Properties prop = new Properties();
prop.setProperty("filename",filename);
prop.setProperty("partcount",count+"");
File config = new File(dir,count+".properties");
fos = new FileOutputStream(config);
prop.store(fos,"");
fos.close();
fis.close();
}
}
class SuffixFilter implements FileFilter{
private String suffix;
SuffixFilter(String suffix){
this.suffix = suffix;
}
public boolean accept(File file){
return file.getName().endsWith(suffix);
}
}
-----------------------------------------------------------------------------------------------
RandomAccessFile:
特點:
1:該對象即可讀取,又可寫入。
2:該對象中的定義了一個大型的byte數組,通過定義指針來操作這個數組。
3:可以通過該對象的getFilePointer()獲取指針的位置,通過seek()方法設置指針的位置。
4:該對象操作的源和目的必須是文件。
5:其實該對象內部封裝了字節讀取流和字節寫入流。
注意:實現隨機訪問,最好是數據有規律。
class RandomAccessFileDemo{
public static void main(String[] args) throws IOException{
write();
read();
randomWrite();
}
//隨機寫入數據,可以實現已有數據的修改。
public static void randomWrite()throws IOException{
RandomAccessFile raf = new RandomAccessFile("random.txt","rw");
raf.seek(8*4);
System.out.println("pos :"+raf.getFilePointer());
raf.write("王武".getBytes());
raf.writeInt(102);
raf.close();
}
public static void read()throws IOException{
RandomAccessFile raf = new RandomAccessFile("random.txt","r");//只讀模式。
//指定指針的位置。
raf.seek(8*1);//實現隨機讀取文件中的數據。注意:數據最好有規律。
System.out.println("pos1 :"+raf.getFilePointer());
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();
System.out.println(name+"::"+age);
System.out.println("pos2 :"+raf.getFilePointer());
raf.close();
}
public static void write()throws IOException{
//rw:當這個文件不存在,會創建該文件。當文件已存在,不會創建。所以不會像輸出流一樣覆蓋。
RandomAccessFile raf = new RandomAccessFile("random.txt","rw");//rw讀寫模式
//往文件中寫入人的基本信息,姓名,年齡。
raf.write("張三".getBytes());
raf.writeInt(97);
raf.close();
}
}
------------------------------------------------------------------------------------------------
管道流:管道讀取流和管道寫入流可以像管道一樣對接上,管道讀取流就可以讀取管道寫入流寫入的數據。
注意:需要加入多線程技術,因為單線程,先執行read,會發生死鎖,因為read方法是阻塞式的,沒有數據的read方法會讓線程等待。
public static void main(String[] args) throws IOException{
PipedInputStream pipin = new PipedInputStream();
PipedOutputStream pipout = new PipedOutputStream();
pipin.connect(pipout);
new Thread(new Input(pipin)).start();
new Thread(new Output(pipout)).start();
}
------------------------------------------------------------------------------------------------
對象的序列化:目的:將一個具體的對象進行持久化,寫入到硬盤上。
注意:靜態數據不能被序列化,因為靜態數據不在堆內存中,是存儲在靜態方法區中。
如何將非靜態的數據不進行序列化?用transient 關鍵字修飾此變量即可。
Serializable:用于啟動對象的序列化功能,可以強制讓指定類具備序列化功能,該接口中沒有成員,這是一個標記接口。這個標記接口用于給序列化類提供UID。這個uid是依據類中的成員的數字簽名進行運行獲取的。如果不需要自動獲取一個uid,可以在類中,手動指定一個名稱為serialVersionUID id號。依據編譯器的不同,或者對信息的高度敏感性。最好每一個序列化的類都進行手動顯示的UID的指定。
import java.io.*;
class ObjectStreamDemo {
public static void main(String[] args) throws Exception{
writeObj();
readObj();
}
public static void readObj()throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
Object obj = ois.readObject();//讀取一個對象。
System.out.println(obj.toString());
}
public static void writeObj()throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("lisi",25)); //寫入一個對象。
oos.close();
}
}
class Person implements Serializable{
private static final long serialVersionUID = 42L;
private transient String name;//用transient修飾后name將不會進行序列化
public int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
public String toString(){
return name+"::"+age;
}
}
-----------------------------------------------------------------------------------------------
DataOutputStream、DataInputStream:專門用于操作基本數據類型數據的對象。
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
dos.writeInt(256);
dos.close();
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
int num = dis.readInt();
System.out.println(num);
dis.close();
-----------------------------------------------------------------------------------------------
ByteArrayInputStream:源:內存
ByteArrayOutputStream:目的:內存。
這兩個流對象不涉及底層資源調用,操作的都是內存中數組,所以不需要關閉。
直接操作字節數組就可以了,為什么還要把數組封裝到流對象中呢?因為數組本身沒有方法,只有一個length屬性。為了便于數組的操作,將數組進行封裝,對外提供方法操作數組中的元素。
對于數組元素操作無非兩種操作:設置(寫)和獲取(讀),而這兩操作正好對應流的讀寫操作。這兩個對象就是使用了流的讀寫思想來操作數組。
//創建源:
ByteArrayInputStream bis = new ByteArrayInputStream("abcdef".getBytes());
//創建目的:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int ch = 0;
while((ch=bis.read())!=-1){
bos.write(ch);
}
System.out.println(bos.toString());
-----------------------------------------------------------------------------------------------
網絡編程:
端口:
物理端口:
邏輯端口:用于標識進程的邏輯地址,不同進程的標識;有效端口:0~65535,其中0~1024系統使用或保留端口。
java 中ip對象:InetAddress.
import java.net.*;
class IPDemo{
public static void main(String[] args) throws UnknownHostException{
//通過名稱(ip字符串or主機名)來獲取一個ip對象。
InetAddress ip = InetAddress.getByName("www.baidu.com");//java.net.UnknownHostException
System.out.println("addr:"+ip.getHostAddress());
System.out.println("name:"+ip.getHostName());
}
}
Socket:★★★★,套接字,通信的端點。
就是為網絡服務提供的一種機制,通信的兩端都有Socket,網絡通信其實就是Socket間的通信,數據在兩個Socket間通過IO傳輸。
UDP傳輸:
1,只要是網絡傳輸,必須有socket 。
2,數據一定要封裝到數據包中,數據包中包括目的地址、端口、數據等信息。
直接操作udp不可能,對于java語言應該將udp封裝成對象,易于我們的使用,這個對象就是DatagramSocket. 封裝了udp傳輸協議的socket對象。
因為數據包中包含的信息較多,為了操作這些信息方便,也一樣會將其封裝成對象。這個數據包對象就是:DatagramPacket.通過這個對象中的方法,就可以獲取到數據包中的各種信息。
DatagramSocket具備發送和接受功能,在進行udp傳輸時,需要明確一個是發送端,一個是接收端。
udp的發送端:
1,建立udp的socket服務,創建對象時如果沒有明確端口,系統會自動分配一個未被使用的端口。
2,明確要發送的具體數據。
3,將數據封裝成了數據包。
4,用socket服務的send方法將數據包發送出去。
5,關閉資源。
--------------------------------------------------------------
import java.net.*;
class UdpSend{
public static void main(String[] args)throws Exception {
// 1,建立udp的socket服務。
DatagramSocket ds = new DatagramSocket(8888);//指定發送端口,不指定系統會隨機分配。
// 2,明確要發送的具體數據。
String text = "udp傳輸演示 哥們來了";
byte[] buf = text.getBytes();
// 3,將數據封裝成了數據包。
DatagramPacket dp = new DatagramPacket(buf,
buf.length,InetAddress.getByName("10.1.31.127"),10000);
// 4,用socket服務的send方法將數據包發送出去。
ds.send(dp);
// 5,關閉資源。
ds.close();
}
}
-------------------------------------------------------------
udp的接收端:
1,創建udp的socket服務,必須要明確一個端口,作用在于,只有發送到這個端口的數據才是這個接收端可以處理的數據。
2,定義數據包,用于存儲接收到數據。
3,通過socket服務的接收方法將收到的數據存儲到數據包中。
4,通過數據包的方法獲取數據包中的具體數據內容,比如ip、端口、數據等等。
5,關閉資源。
-------------------------------------------------------------
class UdpRece {
public static void main(String[] args) throws Exception{
// 1,創建udp的socket服務。
DatagramSocket ds = new DatagramSocket(10000);
// 2,定義數據包,用于存儲接收到數據。先定義字節數組,數據包會把數據存儲到字節數組中。
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
// 3,通過socket服務的接收方法將收到的數據存儲到數據包中。
ds.receive(dp);//該方法是阻塞式方法。
// 4,通過數據包的方法獲取數據包中的具體數據內容,比如ip,端口,數據等等。
String ip = dp.getAddress().getHostAddress();
int port = dp.getPort();
String text = new String(dp.getData(),0,dp.getLength());//將字節數組中的有效部分轉成字符串。
System.out.println(ip+":"+port+"--"+text);
// 5,關閉資源。
ds.close();
}
}
-------------------------------------------------------------
TCP傳輸:兩個端點的建立連接后會有一個傳輸數據的通道,這通道稱為流,而且是建立在網絡基礎上的流,稱之為socket流。該流中既有讀取,也有寫入。
tcp的兩個端點:一個是客戶端,一個是服務端。
客戶端:對應的對象,Socket
服務端:對應的對象,ServerSocket
TCP客戶端:
1,建立tcp的socket服務,最好明確具體的地址和端口。這個對象在創建時,就已經可以對指定ip和端口進行連接(三次握手)。
2,如果連接成功,就意味著通道建立了,socket流就已經產生了。只要獲取到socket流中的讀取流和寫入流即可,只要通過getInputStream和getOutputStream就可以獲取兩個流對象。
3,關閉資源。
--------------------------------------------------------------
import java.net.*;
import java.io.*;
//需求:客戶端給服務器端發送一個數據。
class TcpClient{
public static void main(String[] args) throws Exception{
Socket s = new Socket("10.1.31.69",10002);
OutputStream out = s.getOutputStream();//獲取了socket流中的輸出流對象。
out.write("tcp演示,哥們又來了!".getBytes());
s.close();
}
}
--------------------------------------------------------------
TCP服務端:
1,創建服務端socket服務,并監聽一個端口。
2,服務端為了給客戶端提供服務,獲取客戶端的內容,可以通過accept方法獲取連接過來的客戶端對象。
3,可以通過獲取到的socket對象中的socket流和具體的客戶端進行通訊。
4,如果通訊結束,關閉資源。注意:要先關客戶端,再關服務端。
--------------------------------------------------------------
class TcpServer{
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(10002);//建立服務端的socket服務
Socket s = ss.accept();//獲取客戶端對象
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+".....connected");
// 可以通過獲取到的socket對象中的socket流和具體的客戶端進行通訊。
InputStream in = s.getInputStream();//讀取客戶端的數據,使用客戶端對象的socket讀取流
byte[] buf = new byte[1024];
int len = in.read(buf);
String text = new String(buf,0,len);
System.out.println(text);
// 如果通訊結束,關閉資源。注意:要先關客戶端,在關服務端。
s.close();
ss.close();
}
}
-----------------------------------------------------------------------------------------------
反射技術:其實就是動態加載一個指定的類,并獲取該類中的所有的內容。而且將字節碼文件封裝成對象,并將字節碼文件中的內容都封裝成對象,這樣便于操作這些成員。簡單說:反射技術可以對一個類進行解剖。
反射的好處:大大的增強了程序的擴展性。
反射的基本步驟:
1、獲得Class對象,就是獲取到指定的名稱的字節碼文件對象。
2、實例化對象,獲得類的屬性、方法或構造函數。
3、訪問屬性、調用方法、調用構造函數創建對象。
獲取這個Class對象,有三種方式:
1:通過每個對象都具備的方法getClass來獲取。弊端:必須要創建該類對象,才可以調用getClass方法。
2:每一個數據類型(基本數據類型和引用數據類型)都有一個靜態的屬性class。弊端:必須要先明確該類。
前兩種方式不利于程序的擴展,因為都需要在程序使用具體的類來完成。
3:使用的Class類中的方法,靜態的forName方法。
指定什么類名,就獲取什么類字節碼文件對象,這種方式的擴展性最強,只要將類名的字符串傳入即可。
// 1. 根據給定的類名來獲得 用于類加載
String classname = "cn.itcast.reflect.Person";// 來自配置文件
Class clazz = Class.forName(classname);// 此對象代表Person.class
// 2. 如果拿到了對象,不知道是什么類型 用于獲得對象的類型
Object obj = new Person();
Class clazz1 = obj.getClass();// 獲得對象具體的類型
// 3. 如果是明確地獲得某個類的Class對象 主要用于傳參
Class clazz2 = Person.class;
反射的用法:
1)、需要獲得java類的各個組成部分,首先需要獲得類的Class對象,獲得Class對象的三種方式:
Class.forName(classname) 用于做類加載
obj.getClass() 用于獲得對象的類型
類名.class 用于獲得指定的類型,傳參用
2)、反射類的成員方法:
Class clazz = Person.class;
Method method = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2});
method.invoke();
3)、反射類的構造函數:
Constructor con = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...})
con.newInstance(params...)
4)、反射類的屬性:
Field field = clazz.getField(fieldName);
field.setAccessible(true);
field.setObject(value);
獲取了字節碼文件對象后,最終都需要創建指定類的對象:
創建對象的兩種方式(其實就是對象在進行實例化時的初始化方式):
1,調用空參數的構造函數:使用了Class類中的newInstance()方法。
2,調用帶參數的構造函數:先要獲取指定參數列表的構造函數對象,然后通過該構造函數的對象的newInstance(實際參數) 進行對象的初始化。
綜上所述,第二種方式,必須要先明確具體的構造函數的參數類型,不便于擴展。所以一般情況下,被反射的類,內部通常都會提供一個公有的空參數的構造函數。
------------------------------------------------------
// 如何生成獲取到字節碼文件對象的實例對象。
Class clazz = Class.forName("cn.itcast.bean.Person");//類加載
// 直接獲得指定的類型
clazz = Person.class;
// 根據對象獲得類型
Object obj = new Person("zhangsan", 19);
clazz = obj.getClass();
Object obj = clazz.newInstance();//該實例化對象的方法調用就是指定類中的空參數構造函數,給創建對象進行初始化。當指定類中沒有空參數構造函數時,該如何創建該類對象呢?請看method_2();
public static void method_2() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//既然類中沒有空參數的構造函數,那么只有獲取指定參數的構造函數,用該函數來進行實例化。
//獲取一個帶參數的構造器。
Constructor constructor = clazz.getConstructor(String.class,int.class);
//想要對對象進行初始化,使用構造器的方法newInstance();
Object obj = constructor.newInstance("zhagnsan",30);
//獲取所有構造器。
Constructor[] constructors = clazz.getConstructors();//只包含公共的
constructors = clazz.getDeclaredConstructors();//包含私有的
for(Constructor con : constructors) {
System.out.println(con);
}
}
------------------------------------------------------
反射指定類中的方法:
//獲取類中所有的方法。
public static void method_1() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
Method[] methods = clazz.getMethods();//獲取的是該類中的公有方法和父類中的公有方法。
methods = clazz.getDeclaredMethods();//獲取本類中的方法,包含私有方法。
for(Method method : methods) {
System.out.println(method);
}
}
//獲取指定方法;
public static void method_2() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//獲取指定名稱的方法。
Method method = clazz.getMethod("show", int.class,String.class);
//想要運行指定方法,當然是方法對象最清楚,為了讓方法運行,調用方法對象的invoke方法即可,但是方法運行必須要明確所屬的對象和具體的實際參數。
Object obj = clazz.newInstance();
method.invoke(obj, 39,"hehehe");//執行一個方法
}
//想要運行私有方法。
public static void method_3() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//想要獲取私有方法。必須用getDeclearMethod();
Method method = clazz.getDeclaredMethod("method", null);
// 私有方法不能直接訪問,因為權限不夠。非要訪問,可以通過暴力的方式。
method.setAccessible(true);//一般很少用,因為私有就是隱藏起來,所以盡量不要訪問。
}
//反射靜態方法。
public static void method_4() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
Method method = clazz.getMethod("function",null);
method.invoke(null,null);
}
------------------------------------------------------------------------------------------------
正則表達式:★★★☆,其實是用來操作字符串的一些規則。
好處:正則的出現,對字符串的復雜操作變得更為簡單。
特點:將對字符串操作的代碼用一些符號來表示。只要使用了指定符號,就可以調用底層的代碼對字符串進行操作。符號的出現,簡化了代碼的書寫。
弊端:符號的出現雖然簡化了書寫,但是卻降低了閱讀性。
其實更多是用正則解決字符串操作的問題。
組:用小括號標示,每定義一個小括號,就是一個組,而且有自動編號,從1開始。
只要使用組,對應的數字就是使用該組的內容。別忘了,數組要加\\。
(aaa(wwww(ccc))(eee))技巧,從左括號開始數即可。有幾個左括號就是幾組。
常見操作:
1,匹配:其實用的就是String類中的matches方法。
String reg = "[1-9][0-9]{4,14}";
boolean b = qq.matches(reg);//將正則和字符串關聯對字符串進行匹配。
2,切割:其實用的就是String類中的split方法。
3,替換:其實用的就是String類中的replaceAll();
4,獲取:
1),先要將正則表達式編譯成正則對象。使用的是Pattern中靜態方法 compile(regex);
2),通過Pattern對象獲取Matcher對象。
Pattern用于描述正則表達式,可以對正則表達式進行解析。
而將規則操作字符串,需要從新封裝到匹配器對象Matcher中。
然后使用Matcher對象的方法來操作字符串。
如何獲取匹配器對象呢?
通過Pattern對象中的matcher方法。該方法可以正則規則和字符串想關聯。并返回匹配器對象。
3),使用Matcher對象中的方法即可對字符串進行各種正則操作。
以上是“javaSE知識點有哪些”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。