一:java概述(快速瀏覽): 1991 年Sun公司的James Gosling等人開始開發名稱為 Oak 的語言,希望用於控制嵌入在有線電視交換盒、PDA等的微處理器;
1994年將Oak語言更名為Java;
Java的三種技術架構:
JAVAEE:Java Platform Enterprise Edition,開發企業環境下的應用程式,主要針對web程式開發;
JAVASE:Java Platform Standard Edition,完成桌面應用程式的開發,是其它兩者的基礎;
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;
a = a - b; a = 5;
a = a ^ b;//
b = a ^ b;//b = a ^ b ^ b = a
a = a ^ b;//a = a ^ b ^ a = b;
複製程式碼
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...};
元素型別[] 變數名 = new 元素型別[]{元素1,元素2...};
//二分查詢法。必須有前提:陣列中的元素要有序。
public class DichotomySearch {
public static void main(String[] args) {
int[] arr = new int[] { 12, 23, 34, 45, 53, 67, 77, 89, 90 };
System.out.println(search(arr, 34));
/*System.out.println(search(arr, 45));
System.out.println(search(arr, 67));
System.out.println(search(arr, 89));
System.out.println(search(arr, 99));*/
}
/**
*
* @param arr
* @param key
* @return middle 為陣列下標值(0,1,2...)
*/
public static int search(int[] arr, int key) {
int start = 0;
int end = arr.length - 1;
while (start <= end) {
int middle = (start + end) / 2;
if (key < arr[middle]) {
end = middle - 1;
} else if (key > arr[middle]) {
start = middle + 1;
} else {
return middle;
}
}
return -1;
}
複製程式碼
}
java分了5片記憶體。
1:暫存器。2:本地方法區。3:方法區。4:棧。5:堆。
棧:儲存的都是區域性變數 ( 函式中定義的變數,函式上的引數,語句中的變數 );
只要資料運算完成所在的區域結束,該資料就會被釋放。
堆:用於儲存陣列和物件,也就是實體。啥是實體啊?就是用於封裝多個資料的。
1:每一個實體都有記憶體首地址值。
2:堆記憶體中的變數都有預設初始化值。因為資料型別不同,值也不一樣。
3:垃圾回收機制。
三:物件導向(理解)
以把大象裝進冰箱為例子談談程式導向和麵向物件的區別:
程式導向(代表語言 c語言):
1、開啟冰箱。
2、裝進大象。
3、關上冰箱。
複製程式碼
****開發時重點在於定義函式(過程;開啟,裝進,關上)。
物件導向(代表語言 java語言):
1、冰箱開啟。
2、冰箱儲存。
3、冰箱關閉。
複製程式碼
**開發時重點在於定義物件(物件;冰箱)。
特點:
1:將複雜的事情簡單化。
2:物件導向將以前的過程中的執行者,變成了指揮者。
3:物件導向這種思想是符合現在人們思考習慣的一種思想。
複製程式碼
過程和物件在我們的程式中是如何體現的呢?過程其實就是函式;物件是將函式等一些內容進行了封裝。
匿名物件使用場景:
1:當對方法只進行一次呼叫的時候,可以使用匿名物件。
2:當物件對成員進行多次呼叫時,不能使用匿名物件。必須給物件起名字。
複製程式碼
在類中定義其實都稱之為成員。成員有兩種:
1:成員變數:其實對應的就是事物的屬性。
2:成員函式:其實對應的就是事物的行為。
複製程式碼
所以,其實定義類,就是在定義成員變數和成員函式。但是在定義前,必須先要對事物進行屬性和行為的分析,才可以用程式碼來體現。
private int age; //私有的訪問許可權最低,只有在本類中的訪問有效。
注意:私有僅僅是封裝的一種體現形式而已。
私有的成員:其他類不能直接建立物件訪問,所以只有通過本類對外提供具體的訪問方式來完成對私有的訪問,可以通過對外提供函式的形式對其進行訪問。
好處:可以在函式中加入邏輯判斷等操作,對資料進行判斷等操作。
總結:開發時,記住,屬性是用於儲存資料的,直接被訪問,容易出現安全隱患,所以,類中的屬性通常被私有化,並對外提供公共的訪問方法。
這個方法一般有兩個,規範寫法:對於屬性 xxx,可以使用setXXX(),getXXX()對其進行操作。
類中怎麼沒有定義主函式呢?
注意:主函式的存在,僅為該類是否需要獨立執行,如果不需要,主函式是不用定義的。
主函式的解釋:保證所在類的獨立執行,是程式的入口,被jvm呼叫。
成員變數和區域性變數的區別:
1:成員變數直接定義在類中。
區域性變數定義在方法中,引數上,語句中。
2:成員變數在這個類中有效。
區域性變數只在自己所屬的大括號內有效,大括號結束,區域性變數失去作用域。
3:成員變數存在於堆記憶體中,隨著物件的產生而存在,消失而消失。
區域性變數存在於棧記憶體中,隨著所屬區域的執行而存在,結束而釋放。
複製程式碼
建構函式:用於給物件進行初始化,是給與之對應的物件進行初始化,它具有針對性,函式中的一種。
特點:
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變數就引用了該實體。(指向了該物件)
複製程式碼
封 裝(物件導向特徵之一): 是指隱藏物件的屬性和實現細節,僅對外提供公共訪問方式。
好處:將變化隔離;便於使用;提高重用性;安全性。
封裝原則:將不需要對外提供的內容都隱藏起來,把屬性都隱藏,提供公共方法對其訪問。
this:代表物件。就是所在函式所屬物件的引用。
this到底代表什麼呢?哪個物件呼叫了this所在的函式,this就代表哪個物件,就是哪個物件的引用。
開發時,什麼時候使用this呢?
在定義功能時,如果該功能內部使用到了呼叫該功能的物件,這時就用this來表示這個物件。
複製程式碼
this 還可以用於建構函式間的呼叫。
呼叫格式:this(實際引數);
this物件後面跟上 . 呼叫的是成員屬性和成員方法(一般方法);
this物件後面跟上 () 呼叫的是本類中的對應引數的建構函式。
複製程式碼
注意:用this呼叫建構函式,必須定義在建構函式的第一行。因為建構函式是用於初始化的,所以初始化動作一定要執行。否則編譯失敗。
static:★★★ 關鍵字,是一個修飾符,用於修飾成員(成員變數和成員函式)。
特點:
1,想要實現物件中的共性資料的物件共享。可以將這個資料進行靜態修飾。
2,被靜態修飾的成員,可以直接被類名所呼叫。也就是說,靜態的成員多了一種呼叫方式。類名.靜態方式。
3,靜態隨著類的載入而載入。而且優先於物件存在。
複製程式碼
弊端:
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;
}
複製程式碼
}
繼 承(物件導向特徵之一) 好處:
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:抽象類中可不可以不定義抽象方法?可以。抽象方法目的僅僅為了不讓該類建立物件。
複製程式碼
模板方法設計模式: 解決的問題:當功能內部一部分實現時確定,一部分實現是不確定的。這時可以把不確定的部分暴露出去,讓子類去實現。
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方法,建立本類特有的判斷物件是否相同的依據。
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進行操作。
異常的轉換思想:當出現的異常是呼叫者處理不了的,就需要將此異常轉換為一個呼叫者可以處理的異常丟擲。
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中的四種許可權:
範圍 public protected default private
同一個類中 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比較的是字串內容是否相同。
字串的方法:
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介面: 迭代器:是一個介面。作用:用於取集合中的元素。
booleanhasNext() 如果仍有元素可以迭代,則返回 true。
Enext() 返回迭代的下一個元素。
voidremove() 從迭代器指向的 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
sop("get:"+list.get(x));
}
在進行list列表元素迭代的時候,如果想要在迭代過程中,想要對元素進行操作的時候,比如滿足條件新增新元素。會發生.ConcurrentModificationException併發修改異常。
導致的原因是:
集合引用和迭代器引用在同時操作元素,通過集合獲取到對應的迭代器後,在迭代中,進行集合引用的元素新增,迭代器並不知道,所以會出現異常情況。
如何解決呢?
既然是在迭代中對元素進行操作,找迭代器的方法最為合適.可是Iterator中只有hasNext,next,remove方法.通過查閱的它的子介面,ListIterator,發現該列表迭代器介面具備了對元素的增、刪、改、查的動作。
ListIterator是List集合特有的迭代器。
ListIterator it = list.listIterator;//取代Iterator it = list.iterator;
方法摘要
voidadd(E e) 將指定的元素插入列表(可選操作)。
booleanhasNext() 以正向遍歷列表時,如果列表迭代器有多個元素,則返回 true(換句話說,如果 next 返回一個元素而不是丟擲異常,則返回 true)。
booleanhasPrevious() 如果以逆向遍歷列表,列表迭代器有多個元素,則返回 true。
Enext() 返回列表中的下一個元素。
intnextIndex() 返回對 next 的後續呼叫所返回元素的索引。
Eprevious() 返回列表中的前一個元素。
intpreviousIndex() 返回對 previous 的後續呼叫所返回元素的索引。
voidremove() 從列表中移除由 next 或 previous 返回的最後一個元素(可選操作)。
voidset(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{
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 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 {
private Q obj;
public void setObject(Q obj) {
this.obj = obj;
}
public Q getObject() {
return obj;
}
}
//當方法操作的引用資料型別不確定的時候,可以將泛型定義在方法上。
public void method(W w) {
System.out.println("method:"+w);
}
//靜態方法上的泛型:靜態方法無法訪問類上定義的泛型。如果靜態方法操作的引用資料型別不確定的時候,必須要將泛型定義在方法上。
public static void function(Q t) {
System.out.println("function:"+t);
}
//泛型介面.
interface Inter {
void show(T t);
}
class InterImpl implements Inter {
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 al = new ArrayList
複製程式碼
//要保證左右兩邊的泛型具體型別一致就可以了,這樣不容易出錯。
ArrayList al = new ArrayList();
al.add("aa"); //錯
複製程式碼
//因為集合具體物件中既可儲存String,也可以儲存Object的其他子類,所以新增具體的型別物件不合適,型別檢查會出現安全問題。 ?extends Object 代表Object的子型別不確定,怎麼能新增具體型別的物件呢?
public static void method(ArrayList 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= 用法:java–Dmyname=小明 類名。
String name = System.getProperty("os.name");//獲取指定屬性的資訊
//想要知道該系統是否是該軟體所支援的系統中的一個。
Set hs = new HashSet();
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) ; //返回小於指定數值的最大整數
tatic 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 al = new ArrayList();
for(int x=0; x
al.add(new FileInputStream(new File(dir,x+SP)));
}
Enumeration 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物件中的方法即可對字串進行各種正則操作。
注:第一次寫,寫得不好各位大佬多多關照了