- Java基礎(1)

文章目錄

>> 基礎

~ 內存的劃分

1、寄存器:CPU來處理;

2、本地方法區:與所在系統相關;分版本;

3、方法區(共享數據區、數據區、共享區):裏面放方法;也是代碼存放區;
(1)方法區裏面包括靜態方法區、非靜態方法區;成員方法存放在非靜態方法區,靜態方法存放在靜態區;靜態區、非靜態區方法都是共享的,只是成員方法和靜態方法的調用方式和所屬不一樣 (非靜態區裏面的成員都有一個this所屬,因爲非靜態區的成員只能被對象調用,靜態區的成員都所屬與類名,可以被對象調用也可以通過類名調用)
(2)運行類的時候,類就加載到內存,內存劃分區域,方法存放在方法區;

4、棧內存:代碼運行區;存儲的都是局部變量;凡是定義在方法中的變量都是局部變量;而且變量所處作用域一旦結束,該變量就自動釋放;

5、堆內存:存儲的是數組和對象(數組就是對象);凡是new建立的都在堆中;
特點
(1)每一個實體都有一個首地址值;
(2)堆內存中的每一個變量都有默認初始化值,根據類型的不同而不同;整數是0、小數是0.0或者0.0f、boolean型是false、char型是‘\u0000’;
(3)垃圾回收機制;

~ 交互方式

1、GUI(Graphical User Interface)圖形化界面;
2、CLI(Command Line Interface)命令行方式;

~ JAVA靠語言的三種技術架構

在這裏插入圖片描述

~ Java語言特點 - 跨平臺性 - JVM

1、因爲有JVM,所以具有良好的可移植性;

2、一次編譯隨處運行;

3、JVM不跨平臺,分版本的,具體的平臺安裝指定的JVM版本:Win版的JVM適用於Windows操作系統,Linux版的JVM適用於Linux操作系統,Mac版的JVM適用於Mac操作系統;

4、JVM用來解析Java程序;

5、JRE包括JVM、核心類庫;安裝JRE就可以運行一個開發好的Java程序;

6、JDK包括JRE、Java開發工具(編譯工具javac.exe,打包工具jar.exe),使用JDK開發的程序交給JRE運行;

~ JAVA環境配置—系統變量

1、 JAVA_HOME: 裏面放JDK安裝目錄,bin前的路徑;
D:\Java\JDK

2、CLASSPATH: 裏面加上JDK裏面的bin的路徑;
%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;

3、PATH: path裏面存放可執行文件的路徑;
%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;

4、在當前路徑下運行非當前路徑下的class文件,需要設置classpath環境變量;path裏面存放可執行文件.exe的路徑,所以設置一個新的環境變量,方便虛擬機找到要運行的文件,類文件路徑classpath;

5、classpath環境變量的作用: 是將Java的運行文件的所在的路徑告訴系統,虛擬機在執行時會按照指定的classpath路徑進行類文件的查找並執行;規定JVM該到什麼地方去執行Java程序;

6、設置classpath的目的: 是告訴虛擬機該到哪裏去找要運行的類文件;
若沒有配置classpath路徑,則虛擬機在當前路徑下查找類文件,若沒有則報錯;若配置了,虛擬機在指定位置查找class文件;
若當前路徑下和classpath路徑下都有相同名字的class文件,則虛擬機在classpath路徑下查找class文件,若classpath路徑下沒有需要的class文件,則報錯;

7、classpath=f:\myclass; 若結尾處沒有分號,則報錯;若加了分號則在指定路徑沒找到,就會再到當前路徑下查找所需class文件;(最好不加分號,指定路徑沒找到就要報錯)

8、classpath可以在dos命令行下下臨時設置:set classpath=class文件路徑

~ cmd運行.java文件

1、使用記事本,編輯文件,另存爲.java格式:

// 加上public之後,保存文件時文件名與類名Demo不一樣,編譯就會失敗;
public class Demo 
{  public static void main(String[] args)
	{   System.out.println("Hello World");}
}

2、cmd進入到.java文件所在文件夾下,輸入命令javac Demo.java,將java文件編譯爲.class文件;
3、輸入命令java Demo,運行.class文件;

~ Java編碼

Java用底層的Unicode國際標準碼錶,包括ASCII表、GBK2312-GBK-GBK18030;

~ Java 基礎

1、Java程序都定義在類中,Java程序都是以類的形式存在的,類的形式其實就是一個字節碼文件最終體現;
用class關鍵字來完成類的定義,並起一個閱讀性強的類名;

2、定義一個主函數,爲了保證程序的獨立運行,public static void main(String[] args) 這是主函數的固定格式,JVM認識;

~ Java語言基礎組成

關鍵字、標識符、註釋、變量與常量、運算符、語句、函數、數組;

1、關鍵字:被賦予特殊Java含義的單詞;
特點:關鍵字中所有字母都是小寫;類名首字母最好大寫,多個單詞組成首字母大寫;一般函數名首字母小寫,構造函數名首字母大寫,與類名一樣;
(1)用於定義數據類型的關鍵字: class、interface、byte、short、int、long、float、double、char、boolean、void;
(2)用於定義數據類型值的關鍵字: true、false、null
(3)用於定義流程控制的關鍵字: if~else、switch~case、while~do、default、for、break、continue、return
(4)用於定義訪問權限修飾符的關鍵字: private、protect、public
(5)用於定義類、函數、變量 修飾符的關鍵字: abstract、final、static、synchronized
(6)用定義類與類之間關係的關鍵字: extends、implement
(7)用於定義建立實例、引用案例、判斷實例的關鍵字: new、this、super、instanceof
(8)用於異常處理的關鍵字: try、catch、finally、throw、throws
(9)用於包的關鍵字: package、import
(10)其他修飾符關鍵字: native、strictfp、transient、volatile、assert

2、標識符:在程序中自定義的一些名稱(用於標識某些東西的符號);
要求:由26個英文字母大小寫,數字,下劃線_,美元符號$;不可以數字開頭;不可以使用關鍵字;

3、註釋:用於註解、說明、解釋程序中的文字,提高了代碼的閱讀性;可用於調試程序;
(1)註釋的格式
單行註釋: // 註釋文字
多行註釋: /*註釋文字*/
文檔註釋: /**註釋文字*/

(2)文檔註釋 是Java特有的,可以對寫的源代碼進行說明性文字的體現,它跟多行註釋最大的不同在於,文檔中所寫的文字註釋可以通過Java中的javadoc.exe工具進行提取,生成一個說明書,把文檔註釋和源代碼都放到一個網頁文件中,這個網頁文檔記錄了說明性文字和程序代碼,就是程序的說明書;

(3)注意:單行註釋中可以嵌套單行、多行註釋;多行註釋中不能嵌套多行註釋,報錯;註釋不編譯到類文件中;

(4)使用 javadoc.exe 提取文檔註釋,文檔註釋提取的是public、protect,private不提取;所以private限定的方法 使用多行註釋即可;

4、變量與常量
常量:不能改變的數值;
在 Java 中, 利用關鍵字 final 指示常量;關鍵字 final 表示這個變量只能被賦值一次,一旦被賦值之後, 就不能夠再更改了,習慣上, 常量名使用全大寫;
在 Java 中, 經常希望某個常量可以在一個類中的多個方法中使用, 通常將這些常量稱爲類常量; 可以使用關鍵字 static final設置一個類常量;
(1)整數常量
(2)小數常量
(3)布爾型常量,只有兩個數值:true,false
(4)字符常量:將一個數字字母或符號用單引號(’’)標識: ‘a’
(5)字符串常量:將一個或多個字符用雙引號("")標識:"a", "", " "
(6)null常量,只有一個數值:null;

變量: 內存中的一個存儲區域,該存儲區域有自己的名稱(變量名)和類型(數據類型),該區域的數據可以在同一類型範圍內不斷變化;
(1)變量用來不斷地存放同一類型的常量,並可以重複使用;
(2)變量的作用範圍:一對{}之間;
(3)初始化值:數據類型 變量名=初始化值

5、運算符
(1)算數運算符: + - * / %(取餘) +(連接符)
注意:字符串與數據相加,+是連接符;任何數據與字符串用+相加都是相連接,拼成一個更大的字符串;
整數被 0 除將會產生一個異常, 而浮點數被 0 除將會得到無窮大或 NaN 結果;

(2)自增運算符++ --

(3)賦值運算符= += -= *= /= %=

(4)比較運算符> < == >= <= !=
注意:運算符運算完肯定有結果!比較運算符結果爲布爾型:true、false

(5)邏輯運算符&(與) |(或) !(非) ^(異或--同0異1) &&/||(與/或 短路計算)
邏輯就是指的一種關係,邏輯運算符用於連接兩個boolean類型的表達式;

&與&&、 |與|| 區別: 運算結果一樣,運算過程不一樣;雙 與/或 對數據進行短路計算,左邊結果影響右邊是否參與運算;單“與”“或”還能做位運算;

(6)位運算符<<左移 >>右移 >>>無符號右移 & 按位與 |按位或 ^按位異或 ~非(反碼)
位運算符:直接對二進制位進行運算;

注意:一個數異或同一個數兩次結果還是這個數,633=6;異或一次相當於加密,再異或一次相當於解密;ukey裏面有程序自動讀取這個數,不用知道;

<<左移右邊補0;左移幾位相當於該數據*2的幾次方;所以左移可完成2的次冪運算;
>>右移最高位是0左邊補0,是1左邊補1;右移幾位相當於該數據除以2的幾次冪;
>>>無符號右移,實際進行右移時 高位出現的空位,補0;

(7)三元運算符(條件表達式)?表達式1:表達式2;
三元運算符就是if—else的簡化格式
簡寫格式什麼時候用:當if-else運算後 有一個具體結果時,可以簡化寫成三元運算符;

(8)運算符級別
&& 的優先級比 || 的優先級高:a && b || c 等價於 (a && b) || c
+=是右結合運算符:a += b += c 等價於 a += (b += c)
在這裏插入圖片描述
在這裏插入圖片描述

6、語句
7、函數
8、數組

~ 自動類型提升與強制類型轉換

自動類型提升:只有數值型變量之間 不同類型能運算,佔用內存較小的類型做一次自動類型提升;

強制類型轉換:最好不用,因爲把前三位丟棄,容易丟失精度;
在這裏插入圖片描述

~ 程序的流程控制

(1)順序結構
(2)判斷結構 if—else、 if~else
(3)選擇結構 switch-case
(4)循環結構 for while do-while
(5)其他流程控制語句 break、 continue

switch(表達式)                 // 四種類型的值:byte,short,int,char;
{
     case 取值1:執行語句;break// case無順序;
     case 取值2:執行語句;breakdefault:執行語句;break}
for(初始化表達式;循環條件表達式;循環後的操作表達式)
{    執行語句;(循環體)  }

(6)if和switch比較:(常用if)
if
(1)對具體的值進行判斷;
(2)對區間進行判斷;
(3)對運算結果是布爾類型的表達式進行判斷;
switch
(1)對具體的值進行判斷;
(2)值的個數通常是固定的;
對於幾個固定的值判斷,建議使用switch語句,因爲switch語句會將具體的答案都加載進內存,效率相對高一點;不過if相對簡單,常用;

(7)for和while特點:(常用for)
1、for和while可以互換;
2、格式不同,在在使用上有點小區別:若需要通過變量來對循環進行控制,該變量只作爲循環增量存在時,區別就體現出來了;

int x=1;
while(x<5){ System.out.println(“y”);x++}
forint  y=1;y<5;y++(System.out.println(“y”);)
循環結束後還可以對x進行操作,但不能對y進行操作,因爲y在for循環體定義的,
循環體循環結束後,y就消失;x在主函數體內定義的,循環結束後,while用完後,
x還駐留在內存中,所以浪費內存;

(7)break:跳出當前循環體;,若出現了循環嵌套,默認跳出內循環,break想要跳出指定的循環,可以通過標號(for循環的名字)來完成;
作用範圍:要麼是switch語句,要麼是循環語句;
注意:當break單獨存在時,下面不要定義其他語句,應爲執行不到;

(8)continue:結束本次循環,繼續下次循環;
作用範圍:循環結構
注意:如果continue單獨存在時,下面不要有任何語句,因爲執行不到;


>> 函數

~ 定義

1、定義:就是定義在類中的具有特定功能的一段獨立的小程序,也稱爲方法;

2、格式

修飾符 返回值類型 函數名(參數類型 形參1,參數類型 形參2...{
     執行語句;
      return 返回值;
}
  • 修飾符:static,public;
  • 返回值類型:函數運行後的結果的數據類型;
  • 函數名:小駝峯格式;
  • 參數類型:是形式參數的數據類型;
  • 形式參數:是一個變量,用於存儲調用函數時傳遞給函數的實際參數;
  • 實際參數:傳遞給實際參數的具體數值;
  • return:用於結束函數;
  • 返回值:該函數運算後的結果,該結果會返回給調用者;
  • 特殊情況:void 返回值類型爲空;
    功能沒有具體非返回值;這時return後面直接用分號;結束;因爲沒有具體值所以不能寫具體數據類型,在Java中只能用一個關鍵字來表示:void;
    注意:若返回值類型爲void,則函數中的return語句可以不寫;

3、函數特點
(1)定義函數可以將功能代碼進行封裝;
(2)便於對該功能進行復用;
(3)函數只有被調用纔會被執行;
(4)函數的出現提高了代碼的複用性;
(5)對於函數沒有具體的返回值的情況,返回值類型用關鍵字void表示,函數中return語句如果在最後一行,可以省略不寫;

注意:函數中只能調用函數,不能在函數內部定義函數; 定義函數時,函數的結果應該返回給調用者,交由調用者處理;

4、函數兩個特性:
①重載overload,同一個類中;
②覆蓋override,子父類中;覆蓋也稱爲重寫,覆寫;

~ 主函數

public static void main(String[] args){ ...... }

1、主函數特殊之處:
(1)格式是固定的:public static void main(int[] x){ }不是主函數,是與主函數重名的重載函數;
(2)被jvm所識別和調用;

2、public:因爲權限必須是最大的;
static:虛擬機在調用函數的時候不需要對象的,直接用主函數所屬類名調用即可;
void:主函數沒有具體的返回值;
main:函數名,不是關鍵字,只是一個jvm識別的固定的名字;
String[] args:主函數的參數列表,是一個數組類型的參數,而且元素都是字符串類型; new String[0] 虛擬機創建了一個數組實體,傳給主函數;虛擬機在調用主函數時傳值給主函數;

~ 主函數內存圖解

在這裏插入圖片描述

1、執行test類的時候,這個類就進內存了;

2、先在方法區劃分空間,存放類的方法:成員方法存放在非靜態區,靜態方法存放在靜態區;
所有的類的默認構造函數加載到非靜態方法區,非靜態區所有成員都有一個this所屬,只能被對象調用;主函數作爲靜態方法加載到靜態方法區;

3、類加載完成之後,jvm通過類名調用test類的main方法,mian方法進棧;

4、執行main方法的第一句話:Person.method();,這時Person類才進行加載;JVM會去classpath路徑下查找是否有Person.class文件,若沒有設置classpath,默認在當前路徑下查找;找到以後,就會將class文件加載進內存:在方法區中分配空間;

5、Person類加載完成之後,執行Person.method();語句;通過類名調用,只能去靜態方法區去找;在靜態區查找是否有Person類,找到之後查找Person類的區域中是否有method靜態方法,有的話method方法就進棧;

6、method方法進棧,方法裏面有局部變量就劃分空間存放變量,沒有局部變量就直接執行代碼,執行完代碼之後,方法彈棧;

7、執行主函數的下一句代碼;Person p = new Person("java",20);

8、首先Person p在棧區的main方法中定義定義變量p,然後new Person("java",20)在堆內存中開闢空間創建Person對象,內存空間有一個首地址,Person中的成員變量在這片空間中進行默認初始化賦值 (不包括靜態變量);

9、類的成員變量默認初始化之後,就要進行構造函數初始化,這時相應的構造函數進棧,這個構造函數要調用對象中的數據,要給對象中的數據進行初始化,所以該構造函數就持有一個this所屬,指向堆內存中調用它的那個對象;

10、初始化操作完成之後,構造函數彈棧,堆中對象的內存地址 賦給棧中的p變量;

11、p.show();show方法進棧,有一個this所屬,this指向調用它的對象在堆中的內存地址;

12、show方法執行完之後彈棧,主函數語句執行完畢,彈棧,主函數彈棧之後JVM執行完畢;

~ 底層運算原理

/>javac FunctionDemo.java --> 沒報錯時生成FunctionDemo.class字節碼文件;
/>java FunctionDemo

1、javac啓動Java的編譯器程序,對給定的.Java文件進行編譯(檢查),若都通過,生成Java指定格式的運行程序;

2、Java命令啓動虛擬機,目的是讓它運行Java應用程序FunctionDemo;啓動虛擬機執行一個類的時候自動先找類裏面有沒有一個名稱爲main的函數,找到從這兒執行,未找到運行時報錯;

3、虛擬機啓動以後,在執行一個應用程序的時候,任何應用程序在啓動後都要在內存中劃分空間;先讓主函數進棧,然後執行主函數的第一句代碼,主函數調用其他函數,函數運算完就從棧內存中釋放,再次調用再次加載進棧內存;主函數執行完也出內存,程序結束;

棧的特點:先進後出;

~ 函數的重載overload

1、定義:在同一個類中,允許存在一個以上的同名函數,只要它們的參數個數或者參數類型不同即可;

2、特點:與返回值類型無關,只看參數列表;
java是嚴謹性語言,若函數出現的調用的不確定性,會編譯失敗;

3、好處:便於閱讀,優化了程序設計;

int add(int x,int y) {return x+y;}
int add(int x,int y,int z) {return x+y+z;}
double add(double x,double y){return x+y;}

4、重載代碼通常會重複,因爲功能一樣;參數個數不一樣,一般都可以複用,參數類型不同一般不能複用;

public static int add(int a,int b)        // 加法運算:兩個整數的和;
{    return a+b;    }
public static int add(int a,int b,int c)  //加法運算:三個整數的和; 
{    return add(a,b)+c;    }

>> 數組

~ 定義

1、定義:同一種類型數據的集合;其實數組就是一個容器;

2、好處:可以自動給數組中的元素從0開始編號,方便操作這些元素;

3、初始化
(1)int[] arr = new int[5]; :需要一個容器但是不明確容器中的具體數據;
(2)int[] arr = new int[]{3,5,7,1}; :常規初始化數組的的方式;
(3)int arr = {3,5,7,1}; :靜態初始化方式;需要一個容器存儲已知的具體數據;
注意:數組一旦定義,就要確定長度!不初始化,默認值是0;

~ 數組在內存中的分佈

public static void main(String[] args){
    int[] arr = new int[3];
}

1、程序開始執行,主函數進棧,然後順序執行主函數中代碼;
2、int[] arr:arr在主函數中,是局部變量,存儲在棧內存中;所以在佔內存中開闢空間,創建arr變量;
3、new int[3]:是數組對象,存儲在堆中;所以在堆內存中開闢空間,創建對象;
堆中存儲的都是實體,數組叫實體,對象也叫實體;
實體:實實在在存在的個體;
實體的作用:封裝多個數據;
4、實體或對象把在堆內存中的地址賦值給棧內存中創建的變量arr,arr通過地址指向數組;
5、引用數據類型:arr引用了堆內存中的一個實體;
6、arr = null; 此時堆中的arr實體不消失,而是視爲垃圾,不定時回收;
在這裏插入圖片描述

~ 數組中常見問題

1、ArrayIndexOutOfBoundsException:數組角標超出範圍異常,當訪問到數組中不存在的角標時,就會發生該異常;
2、NullPointerException:空指針異常,當引用型變量沒有任何實體指向時,還在用其操作實體,就會發生該異常;
3、[I@c17164 : [數組型 , i整形;

~ 數組的常見操作

對數組最基本的操作就是存、取;
核心思想:就是對角標的操作;角標即索引;

1、遍歷:Arrays.toString(array);Arrays.deepToString(matrix)

public static void printfArray(int[] arr)
{    System.out.print("[");
     for(int i=0;i<arr.length;i++)
     {    if(i!=arr.length-1)   System.out.print(arr[i]+", ");
	      else   System.out.println(arr[i]+"]");
     }
}

2、獲取最大、小值:
思路
(1)需要進行比較,並定義變量記錄住每次比較後較大的值;
(2)對數組中的元素進行遍歷取出,和變量中記錄的元素進行比較;如果遍歷到的元素大於變量中記錄到元素,就用變量記錄住該大的值;
(3)遍歷結果,改變量記錄的就是最大值;

public static int getMax(int[] arr)
{   int max = 0;
	for(int i=1;i<arr.length;i++)
	{    if(arr[i]>arr[max])   max=i;  }
	return arr[max];
}

3、排序:選擇排序、冒泡排序: Java定義好了排序方法,可以直接拿來用:import java.util.*;Array.sort(arr);

(1)選擇排序:
第一次循環,a[0]與a[1~n]進行比較,若a[0]>a[x],a[0]=a[x];循環結束後,a[0]值最小,固定,不參與下次循環;
第二次循環,a[1]與a[2~n]進行比較,固定a[1]值第二小,不參與下次循環;
第三次循環…

public static void selectSort(int[] arr) {
	for (int x = 0; x < arr.length - 1; x++) {  // 層循環從0開始,到長度-1結束;
		for (int y = x + 1; y < arr.length; y++) { 
			if (arr[x] > arr[y]) {
				int temp = arr[x];
				arr[x] = arr[y];
				arr[y] = temp;
			}
		}
	}
}

(2)冒泡排序: 每次固定最大值;

public static void bubbleSort(int[] arr) {
	for (int x = 0; x < arr.length - 1; x++) {
		for (int y = 0; y < arr.length - 1 - x; y++) {
			if (arr[y] > arr[y + 1]) {
				int temp = arr[y];
				arr[y] = arr[y + 1];
				arr[y + 1] = temp;
			}
		}
	}
}
public static void buddleSort(int[] arr) {
	for (int x = arr.length - 1; x > 0; x--) {
		for (int y = 0; y < x; y++) {
			if(){}  ......
		}
	}
}

4、查找: 查找的是這個元素第一次出現的位置的索引;

// 普通查找法,適用於無序數組;
public static int getIndex(int[] arr, int key) {
	for (int i = 0; i < arr.length; i++) {
		if (arr[i] == key) return i;
	}
	return -1;
}

5、折半查找(二分查找): 查找有序數組;
import java.util.*;int index = Array.binarySearch(arr,50); 如果存在,返回的是具體的角標位置;如果不存在,返回的是(-插入點-1)

public static int binarySearch(int[] arr, int key) {
	int min = 0;
	int max = arr.length - 1;
	int mid = (min + max) / 2;
	while (arr[mid] != key) {
		if (key > arr[mid])       min = mid + 1;
		else if (key < arr[mid])  max = mid - 1;
		if (max < min)  return -1;
		mid = (min + max) / 2;
	}
	return mid;
}
public static int binarySearch_2(int[] arr, int key) {
	int min, mid, max;
	min = 0;
	max = arr.length - 1;
	while (max > min) {
		mid = (max + min) >> 1;
		if (key > arr[mid])      min = mid + 1;
		else if (key < arr[mid]) max = mid - 1;
		else  return mid;
	}
	return -1;
}

~ 數組在實際開發中的應用-查表法

什麼時候使用數組?
如果數據出現了對應關係,而且對應關係的一方是有序的數字編號,並可作爲角標使用;這時就必須要想到數組的使用;就可以將這些數據存儲到數組中;根據運算的結果作爲角標直接去查數組中對應的元素即可;這種方式成爲查表法;

0,1,2,3,4,5,6,7,8,9, A, B, C, D, E, F
0,1,2,3,4,5,6,7,8,9,10, 11, 12, 13, 14, 15

1、獲取一個整數的16進製表現形式,原始方法:

public static void toHex(int num) {
	for (int i = 0; i < 8; i++) {
		int temp = num & 15;  // 取二進制的後四位,是16進制的一位;
		if (temp > 9)
			System.out.print((char) (temp - 10 + 'A'));
		else
			System.out.print(temp);
		num = num >>> 4;   // 無符號右移二進制的4位;
	}
}

2、查表法:

public static void toHex_1(int num) {
	// 定義一個對應關係表
	char[] chs = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
	for (int x = 0; x < 8; x++) {
		int temp = num & 15;   // 取二進制的4位,對應16進制的一位
		System.out.print(chs[temp]);
		num = num >>> 4;
	}
}
public static void toHex_2(int num) {
	if (num == 0) {
		System.out.println("0");
		return;  // 若不寫return,則繼續執行下面程序
	}
	// 定義一個對應關係表
	char[] chs = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
	/* 一會查表會查到比較多的數據;數據一多就先存儲起來,再進行操作;所以定義一個數組:臨時容器 */
	char[] arr = new char[8];  
	int pos = arr.length;
	while (num != 0) {
		int temp = num & 15;  // 二進制&1,八進制&7
		arr[--pos] = chs[temp];
		num = num >>> 4; // 二進制右移4位 
	}
	for (int x = pos; x < arr.length; x++) {
		System.out.print(arr[x]);
	}
}

3、Java中提供的方法:import java.util.*;int I = Interger.toBinaryString(8); //轉換爲二進制;

~ 查表法應用

public class test {
	public static void main(String[] args) {
		String week = getWeek(7);
		System.out.println(week);
	}
	public static String getWeek(int num) {
		if (num > 7 || num <= 0)
			return "無效的星期!";
		String[] week = new String[] { "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日" };
		return week[num - 1];
	}
}

~ 二維數組

格式1:int[][] arr = new int[3][2];
格式2:int[][] arr = new int[3][];
定義二維數組,指定二維數組的長度就可,一維數組長度不用管,後期存哪個一維數組,長度就是這個一維數組的長度;一維數組長度可不一樣;

public static void main(String[] args) {
	int[][] arr = new int[3][2];  // 創建一個二維數組,該數組中有三個一維數組,每個一維數組中有兩個元素;
	System.out.println(arr);      // [[I@4e4d1abd 直接打印二維數組在堆內存中的地址;
	System.out.println(arr[0]);   // [I@28169674  直接打印二維數組中的角標爲0 的一維數組內存地址;
	System.out.println(arr[0][0]);// 0   直接打印二維數組中的角標爲0 的一維數組中角標爲0的元素值;
	
	int[][] arr1 = new int[3][];   // 創建一個二維數組,該數組中有三個一維數組,每個一維數組中元素個數不確定;
	System.out.println(arr1);      // [[I@7676438d 直接打印二維數組在堆內存中的地址;
	System.out.println(arr1[0]);   // null  直接打印二維數組中的角標爲0 的一維數組;
	System.out.println(arr1[0][0]);// java.lang.NullPointerException
	
	System.out.println(arr.length);   //打印二維數組的長度,其實就是一一維數組的個數;
	System.out.println(arr[1].length);//打印二維數組中的角標爲1 的一維數組的長度;
}

>> 面向對象

~ 面向過程與面向對象

1、面向過程思想:強調的是過程(動作), C語言-函數(對函數調用執行);打開冰箱—存儲大象—關上冰箱

2、面向對象思想:強調的是對象(實體), C++、Java、C#; 冰箱打開—冰箱存儲—冰箱關上

3、面向對象特點
(1)面相對象是一種常見的思想,符合人們的思想習慣;
(2)面向對象的出現,將複雜的問題簡單化;
(3)面向對象的出現,讓在過程中的執行者,變成了對象中的指揮者;

4、面向對象三個特徵:封裝、繼承、多態;

~ 類與對象的關係

1、:是用Java這種語言對現實生活中的事物進行描述;用類的形式來體現的;

2、怎麼描述呢:對於事物的描述通常只關注兩個方面:屬性、行爲;只要明確該事物的屬性和行爲,並定義在類中即可;
定義類其實就是在定義類中的成員(成員變量<–>屬性,成員函數<–>行爲)

3、對象:其實就是該類事物實實在在存在的個體;(在Java中萬物皆對象)

4、類與對象之間的關係:類是事物的描述;對象是該類事物的實例,在Java中通過new來創建;

~ 匿名對象

匿名對象:沒有名字的對象,定義對象的簡寫格式: new Car();
new Car().run() 等價於 Car c = new Car(); c.run();

1,當對象對方法僅進行一次調用的時候,就可以簡化成匿名對象;
2,匿名對象可以作爲實際參數進行傳遞:show(new Car()); 等價於 Car c1 = new Car(); show(c1);

~ 面向對象特徵1 - 封裝

1、封裝:隱藏對象的屬性和實現細節,僅對外提供公共訪問方式:隱藏屬性age,提供setAge() getAge() 兩個方法;

2、好處:提高重用性和安全性、將變化隔離(裏面變化與外界無關)、便於使用;

3、封裝原則:將不需要對外提供的內容都隱藏起來:把屬性都隱藏,提供公共方法對其訪問;

4、最小的封裝體函數—>類—>框架

~ 構造函數

1、構造函數:構建創造對象時調用的函數;創建對象都必須要通過構造函數初始化;

2、作用:對對象進行初始化;

3、格式定義

public class test {
	int i;
	 // 函數名稱與類名相同;不用定義返回值類型;沒有具體的返回值;
	public test(int i) {  this.i = i;  } 
}

4、特點
(1)函數名稱與類名相同;
(2)不用定義返回值類型;
(3)沒有具體的返回值;
(4)多個構造函數在類中是以重載的形式來體現的;
(5)一個類中若沒有定義過構造函數,那麼該類中會有一個默認的空參數構造函數;如果在類中定義了指定的構造函數,那麼類中的默認構造函數就沒有了;
(6)構造函數可以有多個,用於對不同的對象進行鍼對性的初始化;

5、一般函數和構造函數區別
構造函數:對象創建時,就會調用與之對應的構造函數,對對象進行初始化,調用只調用一次;
一般函數:對象創建後,需要函數功能時才調用,可以被調用多次;

6、什麼時候定義構造函數:在描述事物時,該事物一存在就具備的一些內容,這些內容都定義在構造函數中;

7、細節
(1)構造函數如果完成了set功能,set方法是否需要? 需要
(2)一般函數不能直接調用構造函數;
(3)構造函數如果前面加了void就變成了一般函數;
(4)構造函數中是有return語句的,少見寫return但是有;

~ this關鍵字

1、this : 代表當前對象;this就是所在函數所屬對象的引用;
簡單說:哪個對象調用了this所在的函數,this就代表哪個對象;

2、特點
(1)當成員變量和局部變量重名,可以用關鍵字this來區分:Person(String name){ this.name = name; },把局部變量的值賦給成員變量;
(2)this也可以用於在構造函數中調用其他構造函數,以提高代碼重用性:
注意:只能定義在構造函數的第一行;因爲初始化動作要先執行;

Person(String name){ this.name = name;  }
Person(String name, int age){
    this(name);     // 調用帶有一個參數name的構造函數;
    this.age = age;
}

(3)在本類中調用本類的對象都用this;

3、this內存原理圖解
在這裏插入圖片描述
(1)程序執行開始,主函數進棧;
(2)Person p:main裏面創建一個變量p,堆裏面創建一個新的對象,對應一個內存地址;對象中有兩個屬性:name默認值null,age默認值0;
(3)new Person("旺財"):創建一個Person對象,並往裏面傳值,所以要調用帶參構造函數,所以Person(String name){}構造函數進棧;
(4)構造函數中默認有一個this指針,指向調用該構造函數的對象在內存中的地址:哪個對象調用了this所在的函數,this就代表哪個對象;
還有一個變量:該構造函數形參傳入的局部變量,該局部變量的值在構造函數執行時,賦給構造函數所在類的成員變量;
(5)完成對象創建並初始化操作之後,構造函數彈棧;
(6)堆內存中創建的對象 地址賦給棧內存中的main中的p變量;
(7)執行代碼下一句:p.speak();,speak()方法進棧;
(8)speak方法是對某個具體對象進行操作的方法,裏面有一個this指針,哪個對象調用它,它就指向哪個對象;
(9)speak方法裏面沒有定義變量,裏面的name/age變量都是對象的成員變量,所以方法裏面的變量值,就是對象的變量的值;
(10)執行完speak方法之後,speak方法彈棧,繼續執行下一句代碼;

4、構造函數間調用
在這裏插入圖片描述

~ static關鍵字

1、static的特點:
(1)static是一個修飾符,用於修飾成員(成員變量/成員函數);
(2)static修飾的成員被所有的對象所共享;
(3)static優先於對象存在,因爲static的成員隨着類的加載就已經存在了;
(4)static修飾的成員多了一種調用方式:可以直接被類名所調用:類名.靜態成員(對象可以用,類也可以用)
(5)static修飾的數據是共享數據,對象中的存儲的是特有數據;

2、靜態使用的注意事項:
(1)靜態方法只能訪問靜態成員(方法+變量);非靜態既可以訪問靜態,又可以訪問非靜態;(不new對象的時候,靜態變量存在而非靜態變量不存在,非靜態變量隨着對象的創建而創建)
(2)靜態方法中不可以使用this或者super關鍵字;(沒有對象,this沒有代表誰)
(3)主函數是靜態的;
(4)靜態變量前面省略類名,成員變量前面省略this;Syso(Person.country+":"+this.name);

3、靜態變量什麼時候使用:
(1)當分析對象中所具備的成員變量的值都是相同的,這時這個成員就可以被靜態修飾;(有一個值不同就不能是靜態的)
(2)只要數據在對象中都是不同的,就是對象的特有數據,必須存儲在對象中,是非靜態的;
(3)如果是相同的數據,對象不需要做修改,只需要使用即可,不需要存儲在對象中,定義成靜態的;

4、靜態方法什麼時候使用:(方法/函數就是功能)
函數是否用靜態修飾,就參考一點:該函數功能是否有訪問到對象中的特有數據 (非靜態數據):若訪問特有數據就用非靜態;

簡單點說,從源代碼看,該功能是否需要訪問非靜態的成員變量,如果需要,該功能就是非靜態的;如果不需要,就可以將該功能定義成靜態的;當然,也可以定義成非靜態,但是非靜態需要被對象調用,而僅創建對象調用非靜態的沒有訪問特有數據的方法,該對象的創建是沒有意義,只是佔用堆內存;所以最好定義成靜態的;

5、靜態代碼塊:static{ System.out.println("我是靜態代碼塊"); }
在java中使用static關鍵字聲明的一段獨立的代碼區間,在JVM加載類時,隨着類的加載而加載並執行,所以靜態代碼塊先於主方法執行,而且只執行一次(因爲只加載一次);

類裏面全是靜態成員時,不用創建對象,這時候需要靜態代碼塊;

作用:用於給類的屬性進行初始化;有些類不用創建對象,這時候不用調用構造函數,所以就沒法對類進行初始化,這時候就用靜態代碼塊;構造函數給對象進行初始化;

注意:(1)靜態代碼塊不能存在於任何方法體內;(2) 靜態代碼塊不能直接訪問靜態實例變量和實例方法,需要通過類的實例對象來訪問;

class StaticCode {
	static int num;
	static {   // 靜態代碼塊
		num = 10;
		num *= 3;
		System.out.println("hahahah");
	}
	StaticCode(){}  // 無參構造函數
	static void show() { System.out.println(num); }
}
class Person {
	private String name;
	{          // 構造對象的代碼塊,可以給所有對象進行初始化的;
		System.out.println("constructor code ");
	}
	static {   // 靜態代碼塊
		System.out.println("static code");
	}
	Person() // 構造函數:給對應的對象進行鍼對性的初始化;
	{
		name = "baby";
		cry();
	}
	Person(String name) {
		this.name = name;
		cry();
	}
	public void cry(){ System.out.println("哇哇"); }
	public void speak(){System.out.println("name:"+name);}
	static void show(){ System.out.println("show run"); }
}
class test {
	// 靜態代碼塊,隨類的加載而加載並執行,只執行一次;
	static { System.out.println("a"); } 
	public static void main(String[] args) {
		// Person p = null;
		// p.speak();  // 沒有創建對象,不能調用類的非靜態方法,空指針異常;
		Person p1 = new Person();
		p1.speak();
		Person p2 = new Person("旺財");
		p2.speak();
		new Person();
		new StaticCode().show();
		new StaticCode().show();
		StaticCode.show();
		System.out.println("b");
	}
}
// ---------------------------------------------------------------------------------------------------------
a  // 執行開始,test類加載進內存,靜態代碼塊隨類的加載而加載進內存並執行;
static code  // 執行主函數第一句代碼:Person p1,Person類加載進內存,
                同時加載並執行類中靜態代碼塊,給類進行初始化;

constructor code  // p1:執行主函數第一句代碼:new Person(),調用對應的
                         構造函數之前先執行構造代碼塊,給對象進行初始化;
哇哇    // 構造代碼塊執行完之後,繼續執行構造函數的代碼;
name:baby  // p1.speak()

constructor code     // p2
哇哇
name:旺財

constructor code     // new person(); 調用無參構造函數,開始執行第一句代碼之前,
                        先執行構造代碼塊,然後繼續執行構造函數的代碼;
哇哇

hahahah      // new StaticCode().show(); 第一次使用該類,先加載進內存,
                同時加載並執行執行構靜態代碼塊;
30     // 創建對象,調用構造函數,然後調用show方法;
30     // 創建對象,調用構造函數,然後調用show方法;
30     // 直接類名調用靜態show方法;
b

~ 靜態代碼塊、構造代碼塊、局部代碼塊區別

1、靜態代碼塊:
(1)java類中使用static關鍵字聲明的代碼塊:static{ ...... },不能在方法體內定義;
(2)在JVM加載類時,隨着類的加載而加載並執行,所以靜態代碼塊先於主方法執行,而且只執行一次(因爲只加載一次);
(3)用於給類的屬性進行初始化;

2、構造代碼塊:
(1)java類中定義的,沒有使用static關鍵字進行聲明的代碼塊;
(2)構造代碼塊會在構造函數被調用時執行,且優先於this()語句執行;(java編譯器在編譯時會先將構造代碼塊中的代碼移到構造函數中執行,構造函數中原有的代碼最後執行)
(3)用於給對象進行初始化;

3、局部代碼塊:
(1)在方法中定義的代碼塊;(不能使用static關鍵字進行聲明)
(2)作用:在方法中,如果要縮短變量的壽命,可以使用;
方法中,某段代碼之後,都不再使用某個變量(這個變量有可能是一個很大的Map集合,很佔內存),則可以將其定義到局部代碼塊中,及時結束其生命週期,釋放空間;

~ 成員變量和局部變量的區別

1、成員變量定義在類中,整個類中都可以訪問;
局部變量定義在方法、語句、局部代碼塊中,只在所屬的區域有效;

2、成員變量存在於堆內存的對象中;
局部變量存在於棧內存的方法中;

3、成員變量隨着對象的創建而存在,隨着對象的消失而消失;
局部變量隨着所屬區域的執行而存在,隨着所屬區域的結束而釋放;

4、成員變量都有默認初始化值;
局部變量沒有默認初始化值;

~ 成員變量(實例變量)和靜態變量(類變量)的區別

1、兩個變量的生命週期不同:
成員變量隨着對象的創建而存在,隨着對象的被回收而釋放;
靜態變量隨着類的加載而存在,隨着類的消失而消失;(虛擬機結束,類就結束了;類本身就是一個對象)
2、調用方式不同:
成員變量只能被對象調用;
靜態變量可以被對象調用,還可以被類名調用;
3、別名不同:
成員變量也稱爲實例變量;
靜態變量稱爲類變量;
4、數據存儲位置不同:
成員變量數據存儲在堆內存的對象中,所以也叫對象的特有數據;
靜態變量數據存儲在方法區(共享數據區)的靜態區,所以也叫對象的共享數據;

~ 單例設計模式

設計模式:23種;是對問題行之有效的解決方式;其實它是一種解決問題的思想;

1、解決的問題: 單例,即單個實例, 可以保證一個類在內存中的對象的唯一性:一個類在內存中只能有一個實例對象;

2、什麼時候需要單例: 對於多個程序,必須使用同一個配置信息對象時,就需要保證該對象的唯一性;

3、如何保證對象唯一性:
(1)不允許其他程序用new創建該類對象;
(2)在該類中創建一個本類實例;
(3)對外提供一個公共方法讓其他程序可以獲取該對象;

4、實現步驟:
(1)私有化該類構造函數;
(2)通過new在本類中創建一個本類對象;
(3)定義一個公有的方法,將創建的對象返回;

5、單例設計模式的兩種表現方式:
(1)餓漢式: 類一加載,對象就已經存在了;(開發時用的較多)

class Single   
{   // s是引用類型的靜態成員變量,定義成private ,可控
	private static Single s = new Single();
	private Single() {}
	public static Single getInstance() {
		return s;
	}
}

(2)懶漢式: 類加載進來,沒有對象,只有調用了getInstance方法時,纔會創建對象;是單例設計模式的延遲加載形式;
存在安全隱患:多線程調用時,不能保證對象唯一;

class Single2 {
	private static Single2 s = null;
	private Single2() {}
	public static Single2 getInstance() {
		if (s == null)  s = new Single2();
		return s;
	}
}

~ 面向對象特徵2 - 繼承

子類(基類)繼承父類(超類)

1、繼承的好處、弊端:
(1)提高了代碼的複用性;
(2)讓類與類之間產生了關係,給第三個特徵多態提供了前提:沒繼承就沒有多態;
(3)繼承弊端:打破了封裝性;

2、java中支持單繼承,不直接支持多繼承,但對C++中的多繼承機制進行改良;
單繼承:一個子類只能有一個直接父類;
多繼承:一個子類可以有多個直接父類 (java中不允許,進行了改良)
不直接支持多繼承,因爲多個父類中有相同成員,會產生調用的不確定性;在java中是通過“多實現”的方式來體現;

3、java支持多層(多重)繼承: C繼承B,B繼承A;就會出現繼承體系;

4、當要使用一個繼承體系時:
(1)查看該體系中的頂層類A,瞭解該體系的基本功能;
(2)創建體系中的最子類C對象,完成功能的使用;

5、什麼時候定義繼承:
當類與類之間存在着所屬關係的時候,就定義繼承;xx是yy中的一種 xx extends yy

6、子、父類中成員變量的特點:super
當本類的成員和局部變量同名用this區分;
當子、父類中的成員變量同名用super區分父類;

注意
①this和super的用法很相似:
this代表一個本類對象的引用,super代表一個父類空間,不代表父類對象,因爲沒創建父類對象就可使用父類成員;
②子類不能直接訪問父類中private私有成員,需通過public方法間接訪問;

7、子、父類中成員函數的特點:
當子、父類中出現成員函數一模一樣的情況,會運行子類的函數;這種現象,稱爲 覆蓋 操作;這是函數在子父類中的特性;

覆蓋注意事項:
(1)子類方法覆蓋父類方法時,子類權限必須要大於等於父類的權限:public>空;
父類是private,子類是public是,不能覆蓋,因爲private不能訪問;
(2)靜態只能覆蓋靜態,或被靜態覆蓋:父類方法是static,子類必須是static;
(3)什麼時候使用覆蓋操作:當對一個類進行子類的擴展時,子類需要保留父類的功能聲明,但要定義子類中該功能的特有內容時,就使用覆蓋操作完成;

8、子、父類中構造函數的特點:
(1)構造函數不能覆蓋,因爲沒繼承過來;
(2)在子類構造對象時,發現,訪問子類構造函數時,父類也運行了;
原因是:在子類的構造函數中第一行有一個默認的隱式語句:super();
(3)子類的實例化過程:子類中所有的構造函數默認都會訪問父類中空參數的構造函數;

9、爲什麼子類實例化的時候要訪問父類中的構造函數:
(1)那是因爲子類繼承了父類,獲取到了父類中內容(屬性),所以在使用父類內容之前,要先看父類是如何對自己的內容進行初始化的;所以子類在構造對象時,必須訪問父類中的構造函數;
(2)爲了完成這個必須的動作,就在子類的構造函數中加入了super()語句;
(3)如果父類中沒有定義空參數構造函數,那麼子類的構造函數必須用super明確要調用父類中哪個構造函數;同時子類構造函數中如果使用this調用了本類構造函數時,那麼super就沒有了,因爲super和this都只能定義在第一行所以只能有一個;
但是可以保證的是,子類中肯定會有其他的構造函數訪問父類的構造函數;

注意:supre語句必須要定義在子類構造函數第一行;因爲父類的初始化動作要先完成;

~ 一個對象實例化過程

Person p = new Person();
(1)JVM會讀取指定的路徑下的Person.class文件,並加載進內存,並會先加載Person的父類(如果有直接的父類的情況下);
(2)在堆內存中的開闢空間,分配地址;
(3)並在對象空間中,對對象中的屬性進行默認初始化;
(4)調用對應的構造函數進行初始化;
(5)在構造函數中,第一行會先到調用父類中構造函數進行初始化;
(6)父類初始化完畢後,再對子類的屬性進行顯示初始化(執行成員變量的賦值操作);
(7)再進行子類構造函數的特定初始化;
(8)初始化完畢後,將地址值賦值給引用變量;

~ final(最終)關鍵字

1、final關鍵字特點:
(1)final是一個修飾符,可以修飾類,方法,變量;一般與static結合使用;
(2)final修飾的類不可以被繼承(最終類);
(3)final修飾的方法不可以被覆蓋(最終方法);
(4)final修飾的變量是一個常量(最終值),只能賦值一次,必須初始化;

2、爲什麼要用final修飾變量:
其實在程序如果一個數據是固定的,那麼直接使用這個數據就可以了,但是這樣閱讀性差,所以它該數據起個名稱,而且這個變量名稱的值不能變化,所以加上final固定;

3、寫法規範: 常量所有字母都大寫,多個單詞,中間用_連接;

~ 繼承 - 抽象類

1、特點:
(1)方法只有聲明沒有實現(方法體)時,該方法就是抽象方法,需要被abstract修飾;
(2)抽象方法必須定義在抽象類中,該類必須也被abstract修飾;
(3)抽象類不可以被實例化:因爲調用抽象方法沒意義;
(4)抽象類必須有其子類覆蓋了所有的抽象方法後,該子類纔可以實例化,否則,這個子類還是抽象類;

2、注意:
(1)抽象類中有構造函數嗎?
有,用於給子類對象進行初始化;
(2)抽象類可以不定義抽象方法嗎?
可以的,但是很少見,目的就是不讓該類創建對象; 通常這個類中的方法有方法體,但是卻沒有內容;
(3)抽象方法只有方法聲明,沒有方法體;

abstract class Demo
{    void show1()//抽象方法
	 void show2(){}  //方法體
}

3、抽象關鍵字不可以和那些關鍵字共存:
(1)private不行,因爲抽象方法需被子類覆蓋,private類型方法子類看不到,無法覆蓋;
(2)static不行,靜態成員不需要對象;
(3)final不行,final方法不能被覆蓋,abstract必須被覆蓋;

4、抽象類和一般類的異同點:
相同點:抽象類和一般類都是用來描述事物的,都在內部定了成員;
不同:
(1)一般類有足夠的信息描述事物;抽象類描述事物的信息有可能不足;
(2)一般類中不能定義抽象方法,只能定非抽象方法;抽象類中可定義抽象方法,同時也可以定義非抽象方法;
(3)一般類可以被實例化; 抽象類不可以被實例化;

5、抽象類一定是個父類: 因爲需要子類覆蓋其方法後纔可以對子類實例化;

~ 接口

1、接口: 當一個抽象類中的方法都是抽象的時候,這時可以將該抽象類用另一種形式定義和表示:就是接口 interface;接口裏的方法都是抽象的;

2、定義接口使用的關鍵字不是class,是interface;

abstract class AbsDemo      // 抽象類:該類中的方法都是抽象的,可以定義成接口;
{   abstract void show1();
    abstract void show2();   
}
interface Demo    // 接口:使用interface關鍵字定義;
{     public static final int NUM = 4;
      public abstract void show1();
      public abstract void show2();
}
class DemoImpl  implements Demo  // 接口實現類:實現接口中所有的方法;
{    public void show1(){ 實現 }
     public void show2(){ 實現 }
}

3、接口中常見的成員 定義格式:都有固定的修飾符
(1)全局常量: public static final 常量類型 常量名;(不寫public static final系統默認加上)
(2)抽象方法public abstract 返回值類型 方法名(參數列表);

4、接口注意事項:
(1)接口中的成員都是公共的權限;
(2)類與類之間是繼承關係,類與接口之間是實現關係,接口之間是繼承關係,而且可以多繼承;
(3)接口不可以實例化,只能由實現了接口的子類並覆蓋了接口中所有的抽象方法後,該子類纔可以實例化,否則,這個子類就是一個抽象類;
(4)在java中不直接支持多繼承,因爲會出現調用的不確定性;所以java將多繼承機制進行改良,在java中變成了多實現:一個類可以實現多個接口;

5、接口的特點:
(1)接口是對外暴露的規則;
(2)接口是程序的功能擴展;
(3)接口的出現降低耦合性;
(4)接口可以用來多實現;
(5)類與接口之間是實現關係,而且類可以繼承一個類的同時實現多個接口;
(6)接口與接口之間可以有繼承關係;
(7)接口的出現避免了單繼承的侷限性,因爲一個類只能有一個父類;接口與接口之間是繼承關係,而且接口可以多繼承;

~ 抽象類和接口的異同點

相同點:都是不斷向上抽取而來的;
不同點
(1)抽象類需要被繼承,而且只能單繼承;接口需要被實現,而且可以多實現;
(2)抽象類中可以定義抽象方法和非抽象方法,子類繼承後,可以直接使用非抽象方法;接口中只能定義抽象方法,必須由子類去實現;
(3)抽象類的繼承,是is a關係,在定義該體系的基本共性內容;接口的實現是 like a 關係,在定義體系額外功能;

~ 面向對象特徵3 - 多態

多態:某一類事物的多種存在形態;簡單說:就是一個對象對應着不同類型;

1、多態在代碼中的體現: 父類或者接口的引用指向其子類的對象;動物 x = new 貓();

2、多態的好處: 提高了代碼的擴展性,前期定義的代碼可以使用後期的內容;

3、多態的弊端: 前期定義的內容不能使用(調用)後期子類的特有內容;

4、多態的前提: ①必須有關係:繼承或實現; ②要有覆蓋;

5、Animal a = new Cat();:自動類型提升(向上轉型), 將子類型隱藏;就不用使用子類的特有方法;作用:限制對特有功能的訪問;
貓對象提升了動物類型,但是貓的特有功能無法訪問;

如果還想用具體動物貓的特有功能, 可以將該對象進行向下轉型:Cat c = (Cat)a;向下轉型目的 是爲了使用子類中的特有方法;

注意:對於轉型,自始自終都是子類對象在做着類型的變化;

6、instanceof: 用於判斷對象的具體類型;只能用於 引用數據類型 判斷,通常在向下轉型前用於健壯性的判斷;

abstract class Animal{ abstract void eat(); }
class Dog extends Animal {
	void eat(){ System.out.println("啃骨頭"); }
	void lookHome(){ System.out.println("看家"); }
}
class Cat extends Animal {
	void eat(){ System.out.println("喫魚"); }
	void catchMouse(){ System.out.println("抓老鼠"); }
}
class test {
	public static void main(String[] args) {
		Animal a = new Cat(); // 父類的引用指向子類對象;
		a.eat();
		// a.catchMouse();  // 不能使用子類的特有方法;
		Cat c1 = (Cat)a; 
		c1.eat();
		c1.catchMouse();
		// Animal a1 = new Animal(); // 錯,接口不能創建實例對象;
		// Animal a2 = new Dog();    // 錯,a2指向的是狗類對象,不能向下轉型爲貓類對象;
		// Cat c1 = (Cat)a2;         // ClassCastException類型轉換異常
	}
	public static void method(Animal a) // Animal a = new Dog();
	{   a.eat();
		if (a instanceof Cat) {
			Cat c = (Cat) a;
			c.catchMouse();
		} else if (a instanceof Dog) {
			Dog d = (Dog) a;
			d.lookHome();
		}
		// else{ ;;;;;}
	}
}

7、多態時,成員變量的特點:
(1)編譯時:參考引用型變量所屬的類中的是否有調用的成員變量,有,編譯通過,沒有,編譯失敗;
(2)運行時:參考引用型變量所屬的類中的是否有調用的成員變量,並運行該所屬類中的成員變量;
(3)簡單說:編譯和運行都參考等號的左邊;

8、多態時,成員函數的特點:
成員函數(非靜態函數):動態綁定
(1)編譯時:參考引用型變量所屬的類中的是否有調用的函數;有,編譯通過,沒有,編譯失敗;
(2)運行時:參考的是對象所屬的類中是否有調用的函數;
(3)簡單說:編譯看左邊,運行看右邊,因爲成員函數存在覆蓋特性;

9、多態時,靜態函數的特點:
靜態函數:靜態綁定
(1)編譯時:參考引用型變量所屬的類中的是否有調用的靜態方法;
(2)運行時:參考引用型變量所屬的類中的是否有調用的靜態方法;
(3)簡單說,編譯和運行都看左邊;
其實對於靜態方法,是不需要對象的,所以不涉及多態性,直接用類名調用即可;

class Fu {
	int num = 3;
	void show(){ System.out.println("fu show"); }
	static void method() {
		System.out.println("fu static method");
	}
}
class Zi extends Fu {
	int num = 4;
	void show(){ System.out.println("zi show"); }
	static void method(){ System.out.println("zi static method"); }
}
class DuoTaiDemo3 {
	public static void main(String[] args) {
		Fu f = new Zi(); // 3   輸出父類的成員變量:成員變量編譯、運行都看等號左邊;
		Zi z = new Zi(); // 4
		System.out.println(z.num); 
		Fu f1 = new Zi();
		f.show();        // 輸出子類方法:成員方法運行看右邊
		Fu f2 = new Zi();
		f.method();      // 輸出父類方法:靜態方法運行看左邊
		Fu.method();
		Zi.method();     // 靜態方法不涉及多態,因爲不需要創建對象,直接通過類名調用
	}
}

~ 內部類

1、內部類: 將一個類定義在另一個類的裏面,裏面那個類稱爲內部類(內置類,嵌套類);

2、內部類訪問特點:
(1)內部類可以直接訪問外部類中的成員;
(2)外部類要訪問內部類,必須建立內部類的對象;

3、內部類的用處:
一般用於類的設計;分析事物時,發現該事物描述中還有事物,而且這個事物還在訪問被描述事物的內容;這時就把還有的事物定義成內部類來描述;

class Outer{
	private static int num = 31;
	static class Inner{
		void show(){ System.out.println("show run..."+num); }
		// 如果內部類中定義了靜態成員,該內部類也必須是靜態的;
		static void function(){ System.out.println("function run ...."+num); }
	}
	public void method(){
		Inner in = new Inner();
		in.show();   // 外部類要訪問內部類,必須建立內部類的對象
	}
}
class DuoTaiDemo3 {
	public static void main(String[] args) {
		Outer out = new Outer();
		out.method();
		// 直接訪問外部類中的內部類中的成員;
		Outer.Inner in1 = new Outer().new Inner();
		in1.show();
		// 如果內部類是靜態的, 相當於一個外部類;
		Outer.Inner in2 = new Outer.Inner();
		in2.show();
		// 如果內部類是靜態的,成員是靜態的;
		Outer.Inner.function();

	}
}

4、爲什麼內部類能直接訪問外部類中成員: 因爲內部類持有了外部類的引用:外部類名.this

class Outer{
	int num = 3;
	class Inner{
		int num = 4;
		void show(){
			int num = 5;
			System.out.println(num);            // 5
			System.out.println(this.num);       // 4
			System.out.println(Inner.this.num); // 4
			System.out.println(Outer.this.num); // 3
		}
	}
	void method(){ new Inner().show(); }
}
class test {
	public static void main(String[] args) {
		new Outer().method();
	}
}

5、內部類可以存放在局部位置上: 內部類在局部位置上只能訪問局部中被final修飾的局部變量;

class Outer{
    int num = 3;
	Object method(final int y){
		final int x = 9;
		class Inner{
			public String toString(){ return "show..." + x + y; }
		}
		Object in = new Inner();
		return in;
	}
}

6、匿名內部類: 就是內部類的簡寫格式,其實就是一個匿名子類對象;
格式new 父類or接口(){子類內容}
必須有前提:內部類必須繼承或者實現一個外部類或者接口;

abstract class Demo{ abstract void show(); }  // 抽象類
class Outer{
	int num = 3;
	public void method(){
		new Demo(){     // 匿名內部類
			void show(){ Syso("show.." + num); }
		}.show();
	}
}
main: new Outer().method();

7、匿名內部類 通常的使用場景之一: 當函數參數是接口類型時,而且接口中的方法不超過三個(不包括3),可以用匿名內部類作爲實際參數進行傳遞;

interface A // 接口
{   void show1();
	void show2();
}
class Outer {
	public void method()   
	{   A in = new A()  // 創建一個匿名內部類對象,賦值給接口Inter類型的引用
		{   public void show1(){}
			public void show2(){}
		};
		in.show1();  // 通過A類型的引用名調用匿名內部類的方法
		in.show2();
	}
}
class test {
	class Inner {}
	public static void main(String[] args) {
		show(new A(){ public void show1(){}
					  public void show2(){}
					});
		// new Inner();// 編譯失敗,因爲主函數是static型,Inner不是
	}
	public void method(){new Inner();}   // 編譯成功
	public static void show(A in) // 接收接口型的參數,賦值給in
	{   in.show1();
		in.show2();
	}
}

8、匿名內部類注意問題: 匿名內部類的子類對象被向上轉型成其他類 類型,這樣就不能再使用子類特有的方法了;

~ 異常

1、異常: 是在運行時期發生的不正常情況;
異常類:描述不正常的情況的類,需要繼承異常體系;

(1)在java中用類的形式對不正常情況進行了描述和封裝對象;
(2)其實異常就是java通過面向對象的思想將問題封裝成了對象,用異常類對其進行描述;
(3)不同的問題用不同的類進行具體的描述, 比如角標越界、空指針等;問題很多,意味着描述的類也很多;將其共性進行向上抽取,形成了異常體系;
(4)異常體系的特點:Throwable及其所有的子類都具有可拋性;子類的後綴名都是用其父類名作爲後綴,閱讀性很強;
(5)可拋性:是通過兩個關鍵字來體現的:throwsthrow;凡是可以被這兩個關鍵字所操作的類和對象都具備可拋性;

2、異常體系: 以前正常流程代碼和問題處理代碼相結合,現在將正常流程代碼和問題處理代碼分離,提高閱讀性;所以最終問題(不正常情況)就分成了兩大類:ErrorException ,都繼承Throwable

(1)一般不可處理的:Error
特點:是由jvm拋出的嚴重性的問題;這種問題發生一般不針對性處理,因爲處理不了,需要直接修改程序;
(2)可以處理的:Exception

3、Throwable: 無論是error,還是異常,都是問題,問題發生就應該可以拋出讓調用者知道並處理;

**4、自定義異常:**自定義的問題描述;
對於角標是整數不存在,可以用角標越界表示;對於負數爲角標的情況,準備用負數角標異常來表示;負數角標這種異常在java中並沒有定義過,那就按照java異常的創建思想:面向對象,將負數角標進行自定義描述,並封裝成對象;這種自定義的問題描述成爲自定義異常;

注意:如果讓一個類稱爲異常類,必須要繼承異常體系,因爲只有稱爲異常體系的子類纔有資格具備可拋性,纔可以被兩個關鍵字所操作:throws、throw;

自定義異常時,要麼繼承Exception:在函數聲明處throws拋出;要麼繼承RuntimeException;

5、編譯時檢測異常和運行時異常的區別:
(1)編譯時被檢測異常: Exception和其子類都是,除了特殊子類RuntimeException體系;

這種問題一旦出現,希望在編譯時就進行檢測,讓這種問題有對應的處理方式;這樣的問題都可以針對性的處理;

編譯型檢測異常需要在函數聲明處聲明throws,運行時異常不需要throws;

(2)運行時異常 (編譯時不檢測): 就是Exception中的RuntimeException和其子類;

這種問題的發生,無法讓功能繼續,運算無法進行,更多是因爲調用者的原因
導致的,或者引發了內部狀態的改變導致的;
那麼這種問題一般不處理,直接編譯通過,在運行時,讓調用者調用時的程序
強制停止,讓調用者對代碼進行修正;

6、throw和throws的區別:
(1)throws使用在函數上,throw使用在函數內;
(2)throws拋出的是異常類,可以拋出多個,用逗號隔開;throw拋出的是異常對象;

7、異常 - 原理 & 異常對象的拋出 throw:

class FuShuIndexException extends Exception{  // 自定義異常類
	FuShuIndexException(){}  // 無參構造函數;
	FuShuIndexException(String msg){super(msg);} // 有參構造函數,直接調用父類的方法;
}
class Demo {
	public int method(int[] arr, int index) throws FuShuIndexException {
		                      // 自定義異常繼承Exception時需要在函數聲明處throws拋出;
		if (arr == null)  // 這個對象中會包含着問題的名稱,信息,位置等信息,將其反饋給調用者;
			throw new NullPointerException("數組的引用不能爲空!");
		if (index >= arr.length)  
			throw new ArrayIndexOutOfBoundsException("數組的角標越界:" + index);
		if (index < 0)  
			throw new FuShuIndexException("數組的角標不能爲負:" + index);
		return arr[index];
	}
}
class test {
	public static void main(String[] args) throws FuShuIndexException {
		int[] arr = new int[3];
		Demo d = new Demo();
		int num = d.method(null, -30);
		System.out.println("num=" + num);
		System.out.println("over");
	}
}

在這裏插入圖片描述

8、異常捕捉 try - catch - finally
(1)異常處理的捕捉形式: 這是可以對異常進行鍼對性處理的方式;
(2)具體格式是:

try { 需要被檢測異常的代碼 }
catch(異常類 變量){ 處理異常的代碼}  //該變量用於接收發生的異常對象
finally{  一定會被執行的代碼,通常用於關閉(釋放)資源  }

(3)異常處理的原則:
① 函數內部如果拋出需要檢測的異常,那麼函數上必須要聲明,否則必須在函數內用try-catch捕捉,否則編譯失敗;
② 如果調用到了聲明異常的函數,要麼try-catch,要麼throws,否則編譯失敗;
③ 什麼時候catch,什麼時候throws 呢:功能內部可以解決,用try-catch,解決不了,用throws告訴調用者,由調用者解決;
④ 一個功能若拋出多個異常,則調用時,必須有對應多個catch進行鍼對性的處理; 內部有幾個需要檢測的異常,就拋幾個異常,拋出幾個,就catch幾個;有多catch時父類的catch放在最下面;

(4)try - catch - finally 代碼塊組合特點:
try - catch - finally
try - catch(多個catch):當沒有必要資源需要釋放時,可以不用定義finally;
try - finally :異常無法直接catch處理,但是資源需要關閉;

9、異常的應用:異常的轉換、封裝 ;
異常的轉換: 接收的異常 與拋出的異常 不一樣;
異常的封裝: 內部異常進行處理的外部異常轉換;不該暴露出去的問題沒有必要暴露出去,因爲暴露出去對方也處理不了;

class MaoYanException extends Exception{ // 自定義異常類:藍屏異常;
	MaoYanException(String msg){ super(msg); } //構造函數,調用父類的方法,輸出信息;
}
class LanPingException extends Exception{ // 自定義異常類:冒煙異常
	LanPingException(String msg){ super(msg); }
}
class NoPlanException extends Exception{ // 自定義異常類:計劃無法完成
	NoPlanException(String msg){ super(msg); }
}
class Computer{
	private int state = 2;    
	public void run() throws LanPingException, MaoYanException{ //拋出自定義異常類
		if(state==1) throw new LanPingException("電腦藍屏!");
		if(state==2) throw new MaoYanException("電腦冒煙!");
		System.out.println("電腦正常運行");
	}
	public void reset(){  // 方法:電腦重啓
		state = 0;
		System.out.println("電腦重啓!");
	}
}
class Person{
	private String name;
	private Computer comp; // 創建電腦類引用
	Person(String name){   // 構造函數
		this.name = name;
		comp = new Computer(); // 創建電腦對象
	}
	public void test(){ System.out.println("大家自習!"); }
	public void teach() throws NoPlanException{
		try{
			comp.run();
			System.out.println(name + "講課!");
		}catch (LanPingException e) {
			System.out.println(e.toString());
			comp.reset();
			teach();
		}catch (MaoYanException e) {
			System.out.println(e.toString());
			test();
			//throw e;  // 可以直接拋出catch的異常, 可以對電腦進行維修;
			// 異常轉換:接收的異常與拋出的異常不一樣
			throw new NoPlanException("課時進度無法完成," + e.toString());
		}
	}
}
class test {
	public static void main(String[] args) {
		Person p = new Person("畢老師");
		try {
			p.teach();
		} catch (NoPlanException e) {
			System.out.println(e.toString() + "......");
			System.out.println("換人");
		}
	}
}
> MaoYanException: 電腦冒煙!
> 大家自習!
> NoPlanException: 課時進度無法完成,MaoYanException: 電腦冒煙!......
> 換人

10、異常的注意事項:
(1)子類在覆蓋父類方法時,父類的方法如果拋出了異常,那麼子類的方法只能拋出父類的異常或者該異常的子類;
(2)如果父類拋出多個異常,那麼子類只能拋出父類異常的子集;

簡單說:子類覆蓋父類,只能拋出父類的異常,或者父類異常的子類,或者子集; 子類問題在父類問題範圍內,不能拋出父類以外的異常;

注意:如果父類的方法沒有拋出異常,那麼子類覆蓋時絕對不能拋,只能捕捉try;

class A extends Exception{}
class B extends A{}
class C extends Exception{}
class Fu{
	public void show() throws A {}
}
class Zi extends Fu {
	public void show() throws C {} // 不拋,或拋出A或拋出B, 拋出C不行;
}
class T {
	void method(Fu f)  // Fu f = new Zi();
	{   try { f.show(); }  
	    catch (A a) { }
	}
}
class test {
	public static void main(String[] args) {
		T t = new T();
	    t.method(new Zi());
	}
}

~ Object類

1、Object: Java中所有類的根類,默認繼承;Object是不斷抽取而來,具備着所有對象都具備的共性內容;

2、常用的共性功能:
public boolean equals(Object obj) :比較對象的地址,用時重寫此方法;
public int hashCode():重寫equal時,同時重寫hashCode;
getClass:返回object的運行時類,當前對象所屬的字節碼文件.class
toString()

class Person{
	private int age;
	Person(int age){ this.age = age; }
	// public boolean compare(Person p){ return this.age==p.age;}
	// 一般不用重新定義此方法,直接重寫父類Object的equal方法,根據對象的特有內容,建立判斷對象是否相同的依據;
	@Override
	public boolean equals(Object obj) { // 傳入的參數類型是Person,向上轉型爲Object型;
		if(!(obj instanceof Person)){ // 如果傳入的對象 不是Person類向上轉型的,
			// return false;             // 返回false, 或拋出異常;
			throw new ClassCastException("類型錯誤!");  
		} // 注意:此處不能拋出編譯時異常,只能拋出運行時異常,因爲重寫的是父類的方法,父類沒有拋異常;
		Person p = (Person)obj;   //向下轉型,獲取子類的特有成員;
		return this.age == p.age;
	}
	public int hashCode(){ return age;}
	public String toString(){ return "Person: " + age; }
}
class test {
	public static void main(String[] args) {
		Person p1 = new Person(10);
		Person p2 = new Person(20);
		System.out.println(p1.equals(p2)); // false
		System.out.println(p1 == p2); // false
		Person p3 = p1;      // 輸出true,因爲地址值一樣
		System.out.println(Integer.toHexString(p1.hashCode())); // a
		Class c1 = p1.getClass();
		Class c2 = p2.getClass();
		System.out.println(c1 == c2);  // true
		System.out.println(c1.getName()); // Person
		System.out.println(p1.toString()); // Person: 10
		System.out.println(p1.getClass().getName() + "$" + Integer.toHexString(p1.hashCode()));
	}
}

~ 包 package

1、包的聲明: package mypack; //包名都是小寫字母;

2、包導入import: 爲了簡化類名的書寫;
import packa.DemoA; //導入了packa包中的DemoA類
import packa.DemoAA;
import packa.*; //導入了packa包中所有的類
import packa.abc.*; //import只能導入類,包裏面的子包不能導入

3、導包的原則: 用到哪個類,就導入哪個類;需要20個類,就導入20個;

4、不同包中類與類之間的訪問:
(1)protected修飾的方法,只有不同包中的子類能調用;只有繼承父類,才能訪問父類的protected方法;
(2)包與包之間的類進行訪問,被訪問的包中的類必須是public的,被訪問的包中的類的方法也必須是public的;

package packb;
public class DemoB {
	protected void method(){ System.out.println("DemoB method"); }
}
package packa;  // packa包繼承packb包
public class DemoA extends packb.DemoB {
	public void show() // packa繼承了packb,所以能訪問packb中的protected方法method()
	{   method();
		System.out.println("DemoA show ");
	}
}
package mypack;
public class PackageDemo {
	public static void main(String[] args) {
		packa.DemoA a = new packa.DemoA(); // 創建packa包裏面DemoA的對象
		a.show();   // 不同包中的方法,可以直接調用其他包中public方法;
		packb.DemoB b = new packb.DemoB();
		// packb包裏面的method方法定義成了protect型,此包沒繼承packb包,所以不能直接訪問method
		// b.method();
	}
}

~ 4種權限

private:私有,是一個權限修飾符;用於修飾成員;私有的內容只在本類中有效;
注意:私有僅僅是封裝的一種體現而已;
在這裏插入圖片描述


>> 多線程

~ 多線程概述

1、進程: 正在進行中的程序(直譯); 在內存中開闢空間; eg:QQ、微信;

2、線程: 就是進程中一個負責程序執行的控制單元(執行路徑);

3、多線程: 一個進程中可以多執行路徑,稱之爲多線程;其實應用程序的執行都是cpu在做着快速的切換完成的,這個切換是隨機的;

4、多線程特點: 併發、隨機;

5、注意:
(1)一個進程中至少要有一個線程;
(2)開啓多個線程是爲了同時運行多部分代碼;
(3)每一個線程都有自己運行的內容,這個內容可以稱爲線程要執行的任務;

6、多線程好處: 解決了多部分 同時(併發)運行的問題;

7、多線程的弊端: 線程太多回到效率的降低;

8、JVM啓動時就啓動了多個線程,至少有兩個線程可以分析的出來:
①執行main函數的線程 - 主線程,該線程的任務代碼都定義在main函數中;
②負責垃圾回收的線程:回收垃圾;

~ 線程的四種狀態

在這裏插入圖片描述

~ 多線程 - wait和sleep的區別

(1)wait可以指定時間也可以不指定,sleep必須指定時間;
(2)在同步中時,對cpu的執行權和鎖的處理不同:
wait:釋放執行權,釋放鎖;
sleep:釋放執行權,不釋放鎖;
一個同步裏面可以有多個活線程,但只有一個能運行:拿到鎖的;

~ 多線程的創建方法一:繼承Thread類

1、步驟:
(1)定義一個類繼承Thread類;
(2)覆蓋Thread類中的run方法;
(3)直接創建Thread的子類 創建線程對象;
(4)調用 start 方法開啓線程並調用線程的任務run方法執行;

2、注意: 可以通過Thread的getName獲取線程的名稱、Thread-編號(從0開始),主線程的名字就是main;

3、創建線程的目的: 是爲了開啓一條執行路徑,去運行指定的代碼和其他代碼實現同時運行;而運行的指定代碼就是這個執行路徑的任務;

4、jvm創建的主線程的任務都定義在了主函數中;而自定義的線程它的任務定義在run方法中;

(1)Thread類用於描述線程,線程是需要任務的,所以Thread類也是對任務的描述;這個任務就通過Thread類中的run方法來體現;也就是說,run方法就是封裝自定義線程運行任務的函數;

(2)run方法中定義的就是線程要運行的任務代碼;

(3)開啓線程是爲了運行指定代碼,所以只有繼承Thread類,並複寫run方法,將運行的代碼定義在run方法中即可;

5、代碼:

class Demo extends Thread {  //定義一個類,繼承Thread類
	private String name;
	Demo(String name) { // 構造函數
		super(name);    // 調用父類的方法,傳值name
		this.name = name;
	}
	@Override
	public void run() {  // 重寫父類run方法
		for (int i = 0; i < 10; i++) {                   // 獲取當前線程名稱
			System.out.println(name+"...i="+i+"...name="+Thread.currentThread().getName());
		}
	}
}
class test {
	public static void main(String[] args) {
		Thread t1 = new Thread();
		Demo d1 = new Demo("wangcai");   // 創建一個線程
		Demo d2 = new Demo("xiaoqiang");
		d1.start();  // 開啓線程,調用run方法
		d2.start();  // 調用run和調用start有什麼區別?調用run和普通沒有創建線程一樣;
		System.out.println("Over..." + Thread.currentThread().getName());
	}
}

6、多線程運行圖:
在這裏插入圖片描述

~ 多線程的創建方法二:實現Runnable接口 - 常用

1、步驟:
(1)定義類實現Runnable接口;
(2)覆蓋接口中的run方法,將線程的任務代碼封裝到run方法中;
(3)通過Thread類創建線程對象,並將Runnable接口的子類對象作爲Thread類的構造函數的參數進行傳遞;
爲什麼?因爲線程的任務都封裝在Runnable接口 子類對象的run方法中,所以要在線程對象創建時就必須明確要運行的任務;
(4)調用線程對象的start方法開啓線程;

ThreadImpl i = new ThreadImpl();  //Runnable接口的子類對象i
Thread t = new Thread(i);         // 通過Thread類創建線程對象
t.start();                        //調用線程對象的start方法開啓線程

2、實現Runnable接口的好處: (Thread繼承Runnable)
(1)將線程的任務從線程的子類中分離出來,進行了單獨的封裝;
按照面向對象的思想將任務的封裝成對象;
(2)避免了java單繼承的侷限性;(有父類就不能再繼承Thread類了)
(3)Runnable的出現僅僅是將線程的任務進行了對象的封裝;

3、Thread中有run,傳入的d 中也有run,爲什麼調用的是d.run?
因爲Thread類中有一個Runnable接口類型的成員變量r,public void run(){ if(r!=null) r.run(); }

4、代碼:

// 準備擴展Demo類的功能,讓其中的內容可以作爲線程的任務執行,通過實現接口的形式完成;
class Demo implements Runnable{
	@Override  // 實現Runnable接口的run方法;
	public void run(){ show(); }
	public void show(){
		for(int i=0; i<10; i++) 
			System.out.println(Thread.currentThread().getName() + "....." + i);
	}
}
class test {
	public static void main(String[] args) {
		Demo d = new Demo();       // 創建接口的子類Demo 的對象d
		Thread t1 = new Thread(d); // 是線程對象,傳入的參數是實現Runnable接口的子類 對象;
		Thread t2 = new Thread(d);
		t1.start();
		t2.start();
		// Demo d1 = new Demo();  //不是線程對象
		// Demo d2 = new Demo();
		// d1.start();            //開啓不了,編譯失敗
		// d2.start();
	}
}

~ 線程安全問題 - synchronized

1、線程安全問題產生的原因:
(1)多個線程在操作共享的數據;
(2)操作共享數據的線程代碼有多條;
當一個線程在執行操作共享數據的多條代碼過程中,其他線程參與了運算,
就會導致線程安全問題的產生;
if(num>0){System.out.println(num);} 是兩條代碼:if判斷+println輸出;

2、解決思路: 就是將多條操作共享數據的線程代碼封裝起來,當有線程在執行這些代碼的時候,其他線程時不可以參與運算的;必須要當前線程把這些代碼都執行完畢後,其他線程纔可以參與運算;

3、同步代碼塊 / 同步函數: 在java中,用同步代碼塊就可以解決這個問題;

4、同步的好處: 解決了線程的安全問題;

5、同步的弊端: 相對降低了效率,因爲同步外的線程的都會判斷同步鎖;

6、同步的前提: 同步中必須有多個線程並使用同一個同步鎖;

7、通過繼承Thread類實現的多線程,創建的是每個線程類的實例,每個線程操作一個線程實例;通過實現Runnable接口的方式實現的多線程,創建一個實例對象和多個線程對象,將此實例對象作爲參數傳遞給Thread類的構造函數,每個線程都操作同一個實例對象;

~ 同步代碼塊、同步函數、靜態同步函數

1、格式:
(1)同步代碼塊: synchronized(對象) { 需要被同步的代碼 ;}
(2)同步函數: public synchronized void add(int num){ 代碼 }
(3)靜態同步函數: public static synchronized void show()

2、不使用同步代碼塊,會出線程安全問題:
在這裏插入圖片描述

3、同步代碼塊 - 代碼:

class Ticket implements Runnable{
	private int num = 100;
	Object obj = new Object();  // 創建對象作爲Synchronized的參數 - 同步鎖
	@Override
	public void run() {
		while(true){
			synchronized (obj) {
				if(num>0) System.out.println(Thread.currentThread().getName()+"...sale..."+num--);
			}
		}
	}
}
class test {
	public static void main(String[] args) {
		Ticket t = new Ticket();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		t2.start();
	}
}

3、同步函數 - 代碼塊:

// 需求: 儲戶,兩個,每個都到銀行存錢每次存100,共存三次;
class Bank{
	private int sum;
	Object obj = new Object();
	public synchronized void add(int num){ // 同步函數
		sum = sum + num;
		System.out.println("sum = " + sum);
	}
}
class Cus implements Runnable{
	private Bank b = new Bank();
	public void run() {
		for(int i=0; i<3; i++){ b.add(100); }
	}
}
class test {  public static void main(String[] args) {}   }

4、靜態 / 同步函數和同步代碼塊的區別:
(1)同步函數的鎖是固定的this;
(2)同步代碼塊的鎖是任意的對象;(可以使用靜態同步函數的鎖)
(3)靜態的同步函數使用的鎖是:該函數所屬字節碼文件對象;
可以用 getClass方法獲取,也可以用當前類名.class 表示:Ticket.classthis.getClass()
(4)同步函數可以看成是同步代碼塊的簡寫形式;
當同步代碼塊的鎖是this時,可以簡寫成同步函數;
(5)建議使用同步代碼塊
如果一個類中只需要一個鎖,建議使用同步函數,使用this作爲同步鎖,寫法簡單;
如果一個類中有多個鎖,或者多個類中使用同一個鎖,那麼只能使用同步代碼塊;

~ 多線程下的單例模式

// 餓漢式:操作共享數據的代碼只有一條,沒有線程安全問題;
class Single{ 
	private static final Single s = new Single(); // 2、在本類中創建本類對象
	private Single(){}    // 1、私有化構造函數,只能在本類中創建對象並初始化;
	public static Single getInstance(){ return s; }// 3、共有方法返回實例對象;
}
// 懶漢式:getInstance方法中,操作的是共享數據,且有多條代碼;
class Single{
	private static Single s = null;
	private Single(){}
	// 使用同步函數 的方式:每次調用該方法,都要先判斷同步鎖,效率低;
//	public static synchronized Single getInstance(){
//		if(s == null) s = new Single();
//		return s;
//	}
	public static Single getInstance(){
		if(s == null){   // 加入同步爲了解決多線程安全問題,加入雙重判斷是爲了解決效率問題;
			synchronized (Single.class) { // 靜態同步函數,沒有this,只能通過類名獲取class文件對象;
				if(s==null) s=new Single();
			}
		}
		return s;
	}
}

~ 死鎖代碼

死鎖常見情景之一 - 同步的嵌套: 同步函數裏面嵌套同步代碼塊:同步代碼塊的鎖是obj,同步函數的鎖是this;

public synchronized void show(){
		synchronized (obj) {
			 if(num>0)Syso(Thread.currentThread().getName()+".....sale...."+num--);
		}
	}

創建兩個線程任務類的實例對象,再創建兩個線程對象,分別執行兩個線程任務,線程1執行if流程,線程2執行else流程;
當線程1拿到locka鎖之後,等待lockb鎖,若此時線程2拿到了lockb鎖,等待locka鎖,就會發生死鎖:互相等待對方持有的鎖;

class Deadlock implements Runnable{
	private boolean flag;
	public Deadlock(boolean flag){ this.flag=flag;}
	public void run() {
		if(flag){
			synchronized (MyLock.locka) {
				System.out.println(Thread.currentThread().getName()+"...if..locka");
				synchronized (MyLock.lockb) {
					System.out.println(Thread.currentThread().getName()+"...if..lockb");
				}
			}
		}else {
			synchronized (MyLock.lockb) {
				System.out.println(Thread.currentThread().getName()+"...else..lockb");
				synchronized (MyLock.locka) {
					System.out.println(Thread.currentThread().getName()+"...else..locka");
				}
			}
		}
	}
}
class MyLock{ // 直接使用創建好的對象作爲同步鎖;
	public static final Object locka= new Object();
	public static final Object lockb= new Object();
}
class test {
	public static void main(String[] args) {
		Deadlock d1 = new Deadlock(true);
		Deadlock d2 = new Deadlock(false);
		Thread t1 = new Thread(d1);
		Thread t2 = new Thread(d2);
		t1.start();
		t2.start();
	}
}

~ 線程間通信 - 等待喚醒機制

線程間通訊: 多個線程在處理同一資源,但是任務卻不同;要在同一個鎖上的線程之間才能進行通信,不同的鎖上的線程之間操作互補干擾;

1、涉及的方法:
(1)wait():讓線程處於凍結狀態,被wait的線程會被存儲到線程池中;
(2)notify():喚醒線程池中一個線程(任意).
(3)notifyAll():喚醒線程池中的所有線程;

2、注意: 這些方法都必須定義在同步中,因爲這些方法是用於操作線程狀態的方法,是監視線程狀態的,線程狀態發生改變時,需要明確改變的是哪一個鎖上的線程;鎖的特點是對多個線程進行同步;所以使用wait/notify方法時需要明確操作的是哪個鎖上的線程;

一個類中能寫的同步有很多,也能有多個鎖;a鎖的notify方法不能喚醒b鎖的wait後的線程;

3、爲什麼操作線程的方法wait、notify、notifyAll定義在了Object類中?
因爲這些方法是監視器的方法;監視器其實就是鎖,鎖是對象;
鎖可以是任意的對象,任意的對象調用的方式一定定義在Object類中;

4、代碼: 單生產者、單消費者問題;
在這裏插入圖片描述

class Resource{  // 資源類
	String name;
	String sex;
	boolean flag = false; // 加入判斷,資源內有東西時,不input
}
class Input implements Runnable{
	Resource r;  // 創建resource對象,在初始化時接收參數用;
	// Object obj = new Object();  // 輸入輸出都創建對象作爲同步鎖,鎖不一樣,同步失敗;
	// 構造函數,創建對象時初始化,形參是主函數創建的Resource對象,這個對象只有一個,傳入 Input、Output函數;
	// 這時輸入輸出函數這兩個線程操作的是同一個資源;
	// 以構造函數接收的r作爲鎖,輸入輸出兩個線程的鎖是同一個,同步成功;
	Input(Resource r){ this.r = r;}  
	public void run() {
		int x = 0;
		while(true){
			synchronized (r) {
				if(r.flag)  // true表示r對象有內容,不進行輸入操作,直接進入wait狀態;
					try { r.wait(); } // 只能捕捉,不能拋出;因爲run方法沒聲明異常;
				 					  // 如果父類的方法沒有拋出異常,那麼子類覆蓋時絕對不能拋,只能捕捉try
				    catch (InterruptedException e) { e.printStackTrace(); }
				if(x==0){ r.name="mike"; r.sex="男"; }
				else { r.name="麗麗"; r.sex="女"; }
				r.flag = true; // 操作完成之後,將狀態改爲true,下次input再獲得執行權時直接進入等待狀態;
				r.notify();    // 喚醒線程池中的線程;(喚醒對方)
			}
			x=(x+1)%2;
		}
	}
}
class Output implements Runnable{
	Resource r;
	public Output(Resource r){ this.r=r; }
	public void run() {
		while(true){
			synchronized (r) {   // r或者Resource.class作爲參數傳入;
				if(!r.flag) 
					try { r.wait(); } 
			    	catch (InterruptedException e) { e.printStackTrace(); }
				System.out.println(r.name+"....."+r.sex);
				r.flag = false;
				r.notify();
			}
		}
	}
}
class test {
	public static void main(String[] args) {
		Resource r = new Resource();  // 創建資源
		Input in = new Input(r); // 創建任務,將資源傳入任務,使得兩個任務操作的是同一個資源
		Output out = new Output(r);
		Thread t1 = new Thread(in);  // 創建線程,執行路徑
		Thread t2 = new Thread(out); // 兩個線程操作的是同一個鎖; 
		t1.start();  // 開啓線程
		t2.start();
	}
}

5、代碼優化:

class Resource{  // 資源類
	String name;
	String sex;
	boolean flag = false; // 加入判斷,資源內有東西時,不input
	public synchronized void set(String name, String sex){
		if(flag) try{ this.wait(); }catch (InterruptedException e){e.printStackTrace();}
		this.name = name;
		this.sex = sex;
		flag = true;
		this.notify();
	}
	public synchronized void get(){
		if(!flag) try{ this.wait(); }catch (InterruptedException e){e.printStackTrace();}
		System.out.println(name+"....."+sex);
		flag = false;
		this.notify();
	}
}
class Input implements Runnable{
	Resource r;  
	Input(Resource r){ this.r = r;}  
	public void run() {
		int x = 0;
		while(true){
			if(x==0) r.set("mike", "男");
			else r.set("麗麗", "女");
			x=(x+1)%2;
		}
	}
}
class Output implements Runnable{
	Resource r;
	public Output(Resource r){ this.r=r; }
	public void run() {
		while(true){ r.get(); }
	}
}

~ 線程間通信 - 多生產者、多消費者問題

1、程序問題: 上面的代碼屬於 “單生產者單消費者” 問題,用於多生產者多消費者情況會出現錯誤:生產者生產產品1後,可能繼續生產產品2,然後消費者消費產品2,產品1就會沒有被消費;
在這裏插入圖片描述

2、程序初始狀態:
在這裏插入圖片描述

3、問題產生原因:
(1)程序開始運行,初始情況,線程池爲空;
name=''; count=0; flag=false; 等待執行權:t0、t1、t2、t3; 線程池:空

(2)t0獲得CPU執行權,開始執行操作:首先判斷flag標記爲false,不執行wait,繼續給r賦值:給name賦值“烤鴨1”,count=1,修改flag=true,執行this.notify(); t0執行完此次循環開始下次循環:判斷flag=true,執行this.wait();交出CPU執行權,進入線程池成等待狀態;
name='烤鴨1'; count=1; flag=true; 等待執行權:t1、t2、t3; 線程池:t0

(3)假設t1獲取執行權,首先判斷flag=true,執行this.wait();交出CPU執行權,進入線程池成等待狀態;
name='烤鴨1'; count=1; flag=true; 等待執行權:t2、t3; 線程池:t0、t1

(4)假設t2獲取執行權,首先判斷flag=true,不執行this.wait();繼續執行輸出語句消費烤鴨1,修改flag=false; 喚醒線程池其中一個(假設t0)線程;
t2結束本次循環,繼續下次循環;首先判斷flag=false,執行this.wait();交出CPU執行權,進入線程池成等待狀態;
name=''; count=1; flag=false; 等待執行權:t0、t3; 線程池:t1、t2

(5)t3獲得執行權,首先判斷flag=false,執行this.wait();交出CPU執行權,進入線程池成等待狀態;
name=''; count=1; flag=false; 等待執行權:t0; 線程池:t1、t2、t3

(6)t0重新獲得執行權,由於t0凍結之前已經執行過if判斷語句了,所以在此獲得執行權後,不再進行flag判斷,而是直接就行運行下面的賦值語句給r賦值:name“烤鴨2”,count=2,修改flag=true,執行this.notify();假設喚醒t1;
t0執行完此次循環開始下次循環:判斷flag=true,執行this.wait();交出CPU執行權,進入線程池成等待狀態;
name='烤鴨2'; count=2; flag=true; 等待執行權:t1; 線程池:t0、t2、t3

(7)t1重新獲得執行權,由於t1凍結之前也已經執行過if判斷語句了,所以在此獲得執行權後,不再進行flag判斷,而是直接就行運行下面的賦值語句給r賦值:name“烤鴨3”,count=3,修改flag=true,執行this.notify();

注意:此時“烤鴨2”未被消費!程序出現錯誤!
改進:修改if判斷爲while循環,使得線程每次被喚醒後都重新判斷flag標記;

改成while循環重新執行1-5步之後狀態:
name=''; count=1; flag=false; 等待執行權:t0; 線程池:t1、t2、t3

(6)t0重新獲得執行權,重新判斷flag=false,繼續執行賦值語句:name=“烤鴨2”,count=2,flag=true;假設喚醒t1;
執行下次循環後判斷flag=true,進入wait狀態;
name='烤鴨2'; count=2; flag=true; 等待執行權:t1; 線程池:t0、t2、t3

(7)t1重新獲得執行權,判斷flag=true,進入wait狀態;
name='烤鴨2'; count=2; flag=true; 等待執行權: ; 線程池:t1、t0、t2、t3

注意:此時線程池中凍結狀態線程:t0、t1、t2、t3;所有線程都進入wait狀態,程序發生死鎖;
解決辦法:每次喚醒線程池中所有線程;

4、總結:
(1)if判斷標記,只有一次,會導致不該運行的線程運行了,出現了數據錯誤的情況;
(2)while判斷標記,解決了線程獲取執行權後,是否要運行!(重新判斷)
(3)notify:只能喚醒一個線程,如果本方喚醒了本方,沒有意義;
而且while判斷標記+notify會導致死鎖;
(4)notifyAll解決了本方線程一定會喚醒對方線程的問題;

5、修改後代碼:多生產者、多消費者問題;

class Resource{
	private String name;
	private int count;
	private boolean flag = false;
	public synchronized void set(String name){
		while(flag) try{ this.wait();} catch (InterruptedException e){ e.printStackTrace(); }
		this.name = name + count;
		count++;
		System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
		flag = true;
		this.notifyAll();
	}
	public synchronized void out(){
		if(!flag) try{ this.wait();} catch (InterruptedException e){ e.printStackTrace(); }
		System.out.println(Thread.currentThread().getName()+"..消費者......"+this.name);
		flag = false;
		this.notifyAll();
	}
}
class Producer implements Runnable{
	private Resource r;
	Producer(Resource r){ this.r = r; }
	public void run(){ while(true){ r.set("烤鴨"); } }
}
class Consumer  implements Runnable{
	private Resource r;
	Consumer (Resource r){ this.r = r; }
	public void run(){ while(true){ r.out(); } }
}
class test {
	public static void main(String[] args) {
		Resource r = new Resource();
		Producer p = new Producer(r); Consumer c = new Consumer(r);
		Thread t0 = new Thread(p); Thread t1 = new Thread(p);
		Thread t2 = new Thread(c); Thread t3 = new Thread(c);
		t0.start(); t1.start(); t2.start(); t3.start();
	}
}

~ 多生產者、多消費者 - JDK5.0新特性 - Lock、Condition

1、同步函數只有一個鎖,一個鎖只能對應一組監聽器的方法:wait、notify、notifyAll;

2、JDK5將鎖封裝成對象Lock接口,將監聽器的方法封裝成對象Condition接口;封裝成對象後,一個lock鎖上可以掛多個監聽器對象Condition,相當於一個鎖上可以有多組監聽器方法;
在這裏插入圖片描述

3、Lock接口: 它的出現替代了 同步 (同步函數/同步代碼塊);
同步對於鎖的操作是隱式的:獲取鎖/釋放鎖的操作看不見;
jdk5以後,將同步和 鎖obj 封裝成了對象Lock,並將操作鎖的隱式方式定義到了對象中,將同步的隱式鎖操作變成了顯式鎖操作:lock.lock();獲取鎖 , lock.unlock();釋放鎖,通常需要定義finally代碼塊中;
同時更爲靈活:可以一個鎖上加上多組監視器;

4、Condition接口: 它的出現替代了Object中的wait、notify、notifyAll方法;
將這些監視器方法單獨進行了封裝,變成Condition監視器對象;
可以與任意鎖進行組合:Condition c1/c2 = lock.newCondition();
con.await();
con.signal();
con.signalAll();

5、使用Lock+Condition以後,每次喚醒線程就不用再喚醒線程池中所有線程了,可以通過對方的監聽器對象con的signal方法,只喚醒對方的線程;

class Resource{
	private String name;
	private int count;
	private boolean flag = false;
	Lock lock = new ReentrantLock(); // 創建一個Lock對象,代替同步+鎖;
	Condition producer_con = lock.newCondition(); // 通過已有的鎖獲取該鎖上的兩組監視器對象;
	Condition consumer_con = lock.newCondition(); // 一組監視生產者,一組 監視消費者;
	public void set(String name){
		lock.lock(); // 顯示獲取所對象;
		try {             // 使用生產者的await方法,凍結生產者的線程;
			while(flag) try{ producer_con.await();} catch (InterruptedException e){ e.printStackTrace(); }
			this.name = name + count;
			count++;
			System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
			flag = true;
			consumer_con.signal(); // 使用消費者的signal方法喚醒一個消費者的線程;
		} finally {
			lock.unlock();
		}
	}
	public synchronized void out(){
		lock.lock();
		try {               // 使用消費者的await方法,凍結消費者的線程;
			while(!flag) try{ consumer_con.await();} catch (InterruptedException e){ e.printStackTrace(); } 
			System.out.println(Thread.currentThread().getName()+"..消費者......"+this.name);
			flag = false;
			producer_con.signal(); // 使用生產者的signal方法喚醒一個生產者的線程;
		} finally {
			lock.unlock();
		}
	}
}

~ 停止線程方式 - 定義標記 - Interrupt

1、停止線程:
(1)stop方法:已過時,不再用;
(2)run方法結束(結束run方法);

2、怎麼控制線程的任務結束:
任務中都會有循環結構,只要控制住循環就可以結束任務;控制循環通常就用定義標記來完成:flag=true

3、如果線程處於了凍結狀態,無法讀取標記;如何結束:
可以使用interrupt()方法將線程從凍結狀態強制恢復到運行狀態中來,讓線程具備cpu的執行資格;

4、但是強制動作會發生了InterruptedException,記得要處理:try catch;

5、守護線程 - setDaemon:
setDaemon將線程設置爲守護線程(後臺線程),它依附於前臺線程而存在;

6、前後臺線程兩者區別: 前臺線程需手動結束,所有前臺線程都結束後,後臺線程無論處於什麼狀態,都結束;

class StopThread implements Runnable{
	private boolean flag = true;
	public synchronized void run() {
		while(flag){
			// 通過在主線程的st.setFlag()修改標記時,兩個線程剛開始while循環,就wait凍結,
			// 等不到主進程循環50次時修改flag,程序就停止不動了,無法結束;
			try { wait(); }   
			catch (InterruptedException e) {  //interrupt強行中斷wait,有異常需try
				System.out.println(Thread.currentThread().getName()+"....."+e);
				flag = false;  //修改標記,線程interrupt活後,再次運行時判斷;
			} 
			System.out.println(Thread.currentThread().getName()+"......++++");
		}
	}
	public void setFlag(){ flag = false; }
}
class test {
	public static void main(String[] args) {
		StopThread st = new StopThread();  // 創建線程任務
		Thread t1 = new Thread(st);  // 創建線程對象,將任務作爲參數傳入
		Thread t2 = new Thread(st);
		t1.start();
		// 將t2設置成爲後臺線程,其他的是前臺線程;
		// 兩者區別:前臺線程需手動結束,所有前臺線程都結束後,後臺線程無論處於什麼狀態,都結束;
 		t2.setDaemon(true);
		t2.start();
		
		int num = 1;
		for(;;){  // 無限循環,num=50 時設置標記+結束主函數循環;
			// num=50時,修改flag標記的值;但是剛開始運行,num=1,線程還沒來得及判斷標記的值時,
			// 就wait凍結,這時用interrupt強制中斷wait凍結,同時捕捉異常,catch裏面修改flag值,
			// 等線程interrupt活了之後,判斷flag值爲FALSE,while值爲假,不再執行循環體;
			if(++num==50){
				// st.setFlag(); 
				t1.interrupt();
				t2.interrupt();
				break; }
			System.out.println("main..."+num);
		}
		System.out.println("主線程結束!");
	}
}

~ 其他方法 - join、setPriority(優先級)

1、設置線程優先級: 優先級越高,獲取CPU概率越大;1min--5none--10max

2、join: 臨時加入一個線程運算時使用;

class Demo implements Runnable {
	public void run() {
		for (int x = 0; x < 50; x++) {
			System.out.println(Thread.currentThread().toString() + "....;;;." + x);
			Thread.yield();   // 暫停(此方法瞭解)
		}
	}
}
class test {
	public static void main(String[] args) {
		Demo d = new Demo();
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		t1.start();
		t2.start();
		t2.setPriority(Thread.MAX_PRIORITY);
		try { t1.join(); // t1線程要申請加入進來,運行,主線程等待t1線程運行終止; 
		}catch (InterruptedException e){ e.printStackTrace(); }
		for (int x = 0; x < 50; x++) {
			System.out.println(Thread.currentThread() + ";....." + x);
		}
	}
}

>> 常用對象API

~ String 類

0、字符串拼接:使用連接符+,或者使用靜態join方法,將多個字符串拼接在一起:String all = String.join(" / ", "S", "M", "L", "XL"); // "S / M / L / XL"
1、String s = "abc"; :定義一個類類型的變量;

2、String s = "abc";:創建一個字符串對象在常量池中,字符串對象一旦被初始化就不會再改變;
String s1 = new String("abc");:創建兩個對象,一個new一個字符串對象在堆內存中;

3、String s = "abc";:"abc"存儲在字符串常量池中,再創建一個新的字符串類常量,在常量池中查找該字符串是否已經存在,若存在,將地址值賦值給變量引用,若沒有,重新創建,放到常量池中;
s = "nba";:重新創建另一個字符串s,跟上面一個不一樣;

4、String 類構造函數:
(1)String s = new String();:等效於String s = "";,是對象;不等效String s = null;;常量值爲空,不是對象;

(2)將字節數組或者字符數組轉成字符串可以通過String類的構造函數完成;
String s = new String(arr,1,3);
String s1 = new String(arr);
String s = ""

5、String 類常見功能 - 獲取、轉換、判斷、比較:
(1)獲取:

  • ① 獲取字符串中字符的個數(長度):int length();
  • ② 根據位置獲取字符:char charAt(int index);
  • ③ 根據字符獲取在字符串中的第一次出現的位置:
    int indexOf(int ch)
    int indexOf(int ch,int fromIndex):從指定位置進行查找ch的第一次出現位置;
    int indexOf(String str);
    int indexOf(String str,int fromIndex);
    int lastIndexOf(int ch):根據字符串獲取在字符串中的最後一次出現的位置;
    int lastIndexOf(int ch,int fromIndex)從指定位置進行ch的查找最後一次出現位置;
    int lastIndexOf(String str);
    int lastIndexOf(String str,int fromIndex);
  • ④ 獲取字符串中一部分字符串;也叫子串:
    String substring(int beginIndex, int endIndex):包含begin 不包含end ;
    String substring(int beginIndex);

(2)轉換:

  • ① 將字符串變成字符串數組(字符串的切割):String[] split(String regex):涉及到正則表達式;
  • ② 將字符串變成字符數組:char[] toCharArray();
  • ③ 將字符串變成字節數組:byte[] getBytes();
  • ④ 將字符串中的字母轉成大小寫:String toUpperCase()String toLowerCase()
  • ⑤ 將字符串中的內容進行替換:String replace(char oldch,char newch);String replace(String s1,String s2);
  • ⑥ 將字符串兩端的空格去除:String trim();
  • ⑦ 將字符串進行連接 :String concat(string);

(3)判斷:

  • ① 兩個字符串內容是否相同:boolean equals(Object obj);boolean equalsIgnoreCase(string str);:忽略大寫比較字符串內容;
  • ② 字符串中是否包含指定字符串:boolean contains(string str);
  • ③ 字符串是否以指定字符串開頭,是否以指定字符串結尾:boolean startsWith(string);boolean endsWith(string);

(4)比較: int compareTo(String anotherString)

6、練習:
(1)字符串數組排序: 給定一個字符串數組,按照字典順序進行從小到大的排序:{"nba","abc","cba","zz","qq","haha"}
思路: 選擇/冒泡排序 + for嵌套 + 比較compareTo() + 換位;

class test {
	public static void main(String[] args) {
		String[] arr = { "nba", "abc", "cba", "zz", "qq", "haha" };
		printArray(arr);
		sortString(arr);
		printArray(arr);
	}
	public static void sortString(String[] arr){
		for(int i=0; i<arr.length-1; i++){
			for(int j=i+1; j<arr.length; j++){ //字符串比較用compareTo方法
				if(arr[i].compareTo(arr[j])>0) swap(arr, i, j);
			}
		}
	}
	public static void swap(String[] arr, int i, int j){
		String temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
	}
	public static void printArray(String[] arr){
		System.out.print("[");
		for(int i=0; i<arr.length; i++){
			if(i==arr.length-1) System.out.println(arr[i] + "]");
			else System.out.print(arr[i] + ",");
		}
	}
}

(2)子串的次數: 一個子串在整串中出現的次數:"nbaernbatynbauinbaopnba"
思路:
(1)要找的子串是否存在,如果存在獲取其出現的位置,這個可以使用indexOf完成;
(2)如果找到了,那麼就記錄出現的位置並在剩餘的字符串中繼續查找該子串,而剩餘字符串的起始位是出現位置+子串的長度;
(3)以此類推,通過循環完成查找,如果找不到就是-1,並對 每次找到用計數器記錄;

class test {
	public static void main(String[] args) {
		String str = "nbaernbatnbaynbauinbaopnba";
		String key = "nba";		
		int count = getKeyStringCount(str,key);
		System.out.println("count="+count);
	}
	private static int getKeyStringCount(String str, String key) {
		int count = 0; // 定義計數器;
		int index = 0; // 定義變量記錄key出現的位置;
		// while((index = str.indexOf(key))!=-1){
		//  	str = str.substring(index+key.length());
		//  	count++;
		// }  // 在字符串變量池中生成多個字符串變量
		while((index = str.indexOf(key, index))!=-1){
			index = index + key.length();
			count++;  // 這個方法不在字符串變量池中新生成字符串
		}
		return count;
	}
}

(3)最大相同子串: 輸出兩個字符串中最大相同的子串: "qwerabcdtyuiop""xcabcdvbn"
思路:
(1)既然取得是最大子串,先看短的那個字符串是否在長的那個字符串中,如果存在,短的那個字符串就是最大子串;
(2)如果不是,那麼就將短的那個子串進行長度遞減的方式去子串,去長串中判斷是否存在, 如果存在就已找到,就不用在找了;

class test {
	public static void main(String[] args) {
		String s1 = "qwerabcdtyuiop";
		String s2 = "xcabcdvbn";
		String s = getMaxSubstring(s2, s1);
		System.out.println("s=" + s);
	}
	private static String getMaxSubstring(String s2, String s1) {
		String max = (s1.length()>s2.length())?s1:s2;
		String min = max.equals(s1)?s2:s1;
		System.out.println(max+">"+min);
		for(int i=0; i<min.length(); i++){
			for(int a=0,b=min.length()-i; b!=min.length()+1; a++,b++){
				String sub = min.substring(a, b);
				if(max.contains(sub)) return sub;
			}
		}
		return null;
	}
}

(4)去除兩端空白: 模擬一個trim功能一致的方法;去除字符串兩端的空白;
思路: 定義兩個變量:一個變量作爲從頭開始判斷字符串空格的角標,不斷++;一個變量作爲從尾開始判斷字符串空格的角標,不斷–;判斷到不是空格爲止,取頭尾之間的字符串即可;

class test {
	public static void main(String[] args) {
		String s = "    ab   c     ";
		s = myTrim(s);
		System.out.println("-" + s + "-");
	}
	private static String myTrim(String s) {
		int start = 0;
		int end = s.length()-1;
		while(start<=end && s.charAt(start)==' ') start++;
		while(start<=end && s.charAt(end)==' ') end--;
		return s.substring(start, end+1); // 不包含end
	}
}

~ StringBuffer 類

1、StringBuffer:就是字符串緩衝區,用於存儲數據的容器;

2、特點:
(1)長度的可變的;
(2)可以存儲不同類型數據;
(3)最終要轉成字符串進行使用;
(4)可以對字符串進行修改;

3、功能:
(1)添加
StringBuffer append(data);
StringBuffer insert(index,data);
(2)刪除
StringBuffer delete(start,end); :包含頭,不包含尾;
StringBuffer deleteCharAt(int index);:刪除指定位置的元素;
(3)查找
char charAt(index);int indexOf(string);int lastIndexOf(string);
(4)修改
StringBuffer replace(start,end,string);
void setCharAt(index,char);
(5)顛倒reversesb.reverse();

4、代碼:

class test {
	public static void main(String[] args) {
		StringBuffer sb = new StringBuffer();  // 創建緩衝區對象;
		sb.append("a").append("b"); // 方法調用鏈:在尾部添加元素;
		sb.insert(1, "c");       // 在指定索引位置插入元素;
		sb.append("d");
		sb.delete(1, 3);  // 刪除指定索引範圍內的字符:[1,3),索引下標從0開始;
		sb.delete(0, sb.length()); // 清空緩衝區;
		sb = new StringBuffer("abcde"); // 盆都扔了,買新的;
		sb.replace(1, 3, "nba"); // 替換字符串; [1,3),相當於將索引爲12的兩個元素替換成nba
		sb.setCharAt(1, 'q');  // 替換單個字符
		sb.setLength(10); // 設置容器長度,字符串<長度時自動補全空格
		sb.reverse();  // 倒着輸出內容 
	}
}

~ StringBuilder 類

1、jdk1.5以後出現了功能和StringBuffer一模一樣的對象:StringBuilder
不同的是: StringBuffer是線程同步的,通常用於多線程;StringBuilder是線程不同步的,通常用於單線程, 它的出現提高了效率;

2、jdk升級: ①簡化書寫;②提高效率;③增加安全性;

3、練習: 將一個int數組變成字符串;

class test {
	public static void main(String[] args) {
		int[] arr = {3,1,5,3,8};
		String s = arrayToString2(arr);		
		System.out.println(s);
	}
	private static String arrayToString2(int[] arr) {
		StringBuilder sb = new StringBuilder();
		sb.append("[");
		for(int i=0; i<arr.length; i++){
			if(i != arr.length-1) sb.append(arr[i]+",");
			else sb.append(arr[i]+"]");
		}
		return sb.toString();
	}
	private static String arrayToString1(int[] arr) {
		String s = "[";
		for(int i=0; i<arr.length; i++){
			if(i != arr.length-1) s=s+arr[i]+",";
			else s=s+arr[i]+"]";
		}
		return s;   // 每鏈接一次就新創建一個字符串在字符串常量池中
	}
}

~ System 類

1、System:類中的方法和屬性都是靜態的;

2、常見方法:
(1)long currentTimeMillis();:獲取當前時間的毫秒值;
(2)setProperty(key,value);:給系統設置一些屬性信息;這些信息是全局,其他程序都可以使用;
(3)getProperties();:獲取系統的屬性信息;

3、獲取系統的屬性信息,並存儲到了Properties集合中;
properties集合中存儲都是String類型的鍵和值,最好使用它自己的存儲和取出的方法來完成元素的操作;

class test {
	private static final String LINE_SEPARATOR = System.getProperty("line.separator");
	public static void main(String[] args) {
		long l1 = 1335664696656l;  //System.currentTimeMillis();
		System.out.println(l1/1000/60/60/24);  //毫秒值1335664696656、天數
		// code...   //獲取一段程序的運行時間
		long l2 = System.currentTimeMillis();
		System.out.println(l2-l1);
		System.out.println("hello-"+LINE_SEPARATOR+" world");
		//給系統設置一些屬性信息;這些信息是全局,其他程序都可以使用; 
		System.setProperty("myclasspath", "c:\\myclass");	
		
		Properties prop = System.getProperties();
		// Properties隸屬於HashTable,有自己的操作元素的方法
		Set<String> nameSet = prop.stringPropertyNames();
		for (String name : nameSet) {
			String value = prop.getProperty(name);
			System.out.println(name + "::" + value);
		}
	}
}

~ Runtime 類

Runtime:沒有構造方法,不可以創建對象;但是有非靜態的方法,說明該類應該提供靜態的 返回該類對象的 方法,而且只有一個,說明Runtime類使用了單例設計模式;

public static void main(String[] args) {
	// getRuntime()是靜態方法,在裏面調用構造函數創建對象,然後返回
	Runtime r = Runtime.getRuntime(); // 返回類型是Process類型的
	Process p = r.exec("notepad.exe");
	Thread.sleep(5000);
	p.destroy(); // 殺掉進程
}

~ Math 類

Math:提供了操作數學運算的方法,都是靜態的;
常用的方法
ceil():返回大於參數的最小整數;
floor():返回小於參數的最大整數;
round():返回四捨五入的整數;
pow(a,b):a的b次方;
random():僞隨機數 [0,1);

public static void main(String[] args) {
	double d1 = Math.ceil(12.56);   Sop("d1="+d1); //13.0
	double d2 = Math.floor(12.56);  Sop("d2="+d2); //12.0
	double d3 = Math.round(12.46);  Sop("d3="+d3); //12.0		
	double d = Math.pow(10, 2);     Sop("d=" + d); // 100.0
	Random r = new Random(); // 直接將隨機數封裝成對象,對象方法多
	for (int i = 0; i < 10; i++) {
		double d = Math.ceil(Math.random() * 10); // [1,10)
		double d = (int) (Math.random() * 6 + 1); // [1,6)
		double d = (int) (r.nextDouble() * 6 + 1);
		int d = r.nextInt(6) + 1;
		System.out.println(d);
	}
}

~ Date 類

1、日期對象和毫秒值之間的轉換:
(1)毫秒值 --> 日期對象: 可通過Date對象的方法對該日期中各個字段(年月等)進行操作;
①通過Date對象的構造方法 new Date(timeMillis);
②還可以通過setTime設置;

(2)日期對象 --> 毫秒值: 可以通過具體的數值進行運算 getTime方法;

public static void main(String[] args) {
	long time = System.currentTimeMillis();  // 獲取當前時間的毫秒值
	System.out.println(time); // 1546851227126
	
	Date date = new Date();   // 將當前日期和時間封裝成Date對象
	System.out.println(date); // Mon Jan 07 16:56:35 CST 2019
	date.setTime(1546851227126l); // 通過setTime設置  
	System.out.println(date);
	
	Date date2 = new Date(1546851227126l); // 將指定毫秒值封裝成Date對象
	System.out.println(date2); // Mon Jan 07 16:53:47 CST 2019
	
	Date date3 = new Date();   //日期對象-->毫秒值: getTime方法     
	long time3 = date.getTime();
	System.out.println(time);
}

2、將日期對象 --> 日期格式的字符串: 使用的是DateFormat類中的format方法;

public static void main(String[] args) {
	//獲取日期格式對象,具備着默認的風格,FULL LONG等可以指定風格;
	Date date = new Date();
	// 創建日期格式的對象
	DateFormat dateFormat1 = DateFormat.getDateInstance(); // 2019-1-7
	DateFormat dateFormat2 = DateFormat.getDateInstance(DateFormat.LONG); // 2019年1月7日
	DateFormat dateFormat3 = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG);
	// 將日期對象 --> 日期格式的字符串
	String str_date = dateFormat3.format(date);
	System.out.println(str_date); // 2019年1月7日 下午05時09分27秒
	// 自定義風格:創建子類SimpleDateFormat對象
	dateFormat1 = new SimpleDateFormat("yyyy---MM---dd");
	String str_date2 = dateFormat1.format(date);
	System.out.println(str_date2); // 2019---01---07
}

3、將日期格式的字符串 --> 日期對象: 使用的是DateFormat類中的parse()方法;

public static void main(String[] args) throws ParseException {
	// 默認格式
	String str_date1 = "2017-2-1";
	DateFormat dateFormat1 = DateFormat.getDateInstance();
	// 指定風格
	String str_date2 = "2012年4月19日";
	DateFormat dateFormat2 = DateFormat.getDateInstance(DateFormat.LONG);
	// 自定義風格
	String str_date3 = "2017---2---14";
	DateFormat dateFormat3 = DateFormat.getDateInstance();
	dateFormat3 = new SimpleDateFormat("yyyy---MM---ddd");
	
	Date date = dateFormat1.parse(str_date1);
	System.out.println(date);
}

4、練習: 計算 “2012-3-17"到"2012-4-6” 中間有多少天;

思路:兩個日期相減;
(1)咋減呢?必須要有兩個可以進行減法運算的數;能減可以是毫秒值;
(2)如何獲取毫秒值?通過date對象;
(3)如何獲取date對象呢?可以將字符串轉成date對象;

步驟
(1)將日期格式的字符串轉成Date對象;
(2)將Date對象轉成毫秒值;
(3)相減,在變成天數;

class test {
	public static void main(String[] args) throws ParseException {
		String str_date1 = "2012-3-17";
		String str_date2 = "2012-4-18";
		// 1、將日期格式的字符串轉成Date對象;
		DateFormat dateFormat = DateFormat.getDateInstance();
		dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		Date date1 = dateFormat.parse(str_date1);
		Date date2 = dateFormat.parse(str_date2);
		Long time1 = date1.getTime();
		Long time2 = date2.getTime();
		Long time = Math.abs(time1-time2);
		int day = getDay(time);
		System.out.println(day);
	}
	public static int getDay(Long time){
		return (int) (time/1000/24/60/60);
	}
}

~ Calendar 類

class test {
	public static void main(String[] args) {
		// Calendar c = Calendar.getInstance();
		int year = 2012;   // 獲取某一年2月有多少天
		showDays(year);
	}
	public static void showDays(int year) {
		Calendar c = Calendar.getInstance();
		c.set(year, 2, 1);
		c.add(Calendar.DAY_OF_MONTH, -1);
		showDate(c);
	}
	public static void showDate(Calendar c) {
		int year = c.get(Calendar.YEAR);
		int month = c.get(Calendar.MONTH) + 1;
		int day = c.get(Calendar.DAY_OF_MONTH);
		int week = c.get(Calendar.DAY_OF_WEEK);
		System.out.println(year + "年" + month + "月" + day + "日" + getWeek(week));
	}
	public static String getWeek(int i) {
		String[] weeks = { "", "星期日", "星期一", ... , "星期五", "星期六" };
		return weeks[i];
	}
}

>> 集合框架

~ 基礎 概述

1、數值有很多,用數組存;數組有很多,用二維數組存;
數據有很多,用對象存;對象有很多,用集合存;

2、集合本身是對象:能存儲對象的對象;

3、集合是一個容器,用以存儲對象;

4、集合類的由來: 對象用於封裝特有數據,對象多了需要存儲,如果對象的個數不確定,就使用集合容器進行存儲;

5、集合特點:
(1)用於存儲對象的容器;
(2)集合的長度是可變的;
(3)集合中不可以存儲基本數據類型值;

6、COllection接口: 集合容器因爲內部的數據結構不同,有多種具體容器,不斷的向上抽取,就形成了集合框架;框架的頂層Collection接口;

Collection是接口,不能直接new對象,所以使用其子類ArrayList創建對象,將對象再賦值給Collection類型的引用;

7、Collection 的基本方法:
(1)添加:
boolean add(Object obj);
boolean addAll(Collection coll);
(2)刪除:
boolean remove(object obj);
boolean removeAll(Collection coll);
void clear();
(3)判斷:
boolean contains(object obj);
boolean containsAll(Colllection coll);
boolean isEmpty();判斷集合中是否有元素;
(4)獲取:
int size();
Iterator iterator(); 取出元素的方式:迭代器;
(5)其他:
boolean retainAll(Collection coll); 取交集;
Object[] toArray(); 將集合轉成數組;

8、迭代器Iterator接口、iterator()方法:
迭代器對象必須依賴於具體容器,因爲每一個容器的數據結構都不同,所以該迭代器對象是在容器中進行內部實現的;對於使用容器者而言,具體的實現不重要,只要通過容器獲取到該實現的迭代器的對象即可,也就是iterator方法;

Iterator接口 就是對所有的Collection容器進行元素取出的公共接口;

Iterator it = coll.iterator();		
while(it.hasNext()){  System.out.println(it.next());  }

9、Collection 基本方法練習:

public static void main(String[] args) {
	Collection<String> c1 = new ArrayList<String>(); // 創建一個集合類對象
	Collection<String> c2 = new ArrayList<String>();  
	// 添加元素 add
	c1.add("abc1");
	c1.add("abc2");  // c1:[abc1, abc2, abc3]
	c1.add("abc3");  // c1.size() = 3
	c2.add("abc4");  
	c2.add("abc5");  // c2:[abc4, abc5]
	// 刪除元素 remove
	c1.remove("abc3");   // c1:[abc1, abc2] 會改變集合的長度:size=2
	// 清空集合 clear
	// c1.clear();  // c1: [],size=0
	c1.contains("abc3");  // false
	// 將集合2中的元素添加到集合1中
	c1.addAll(c2);  // c1:[abc1, abc2, abc4, abc5]
	// 將兩個集合中的相同元素從調用removeAll的集合中刪除
	boolean b1 = c1.removeAll(c2);  // true,刪除成功; c1:[abc1, abc2]
	boolean b2 = c1.containsAll(c2);  // 若c1中包含c2中所有元素,則返回true;
	// 取交集,保留 和指定的集合 相同的元素,而刪除不同的元素;和removeAll功能相反
	c1.addAll(c2);
	boolean b = c1.retainAll(c2);  // true,c1:[abc4, abc5]
}

10、集合框架體系結構圖:
在這裏插入圖片描述

11、Collection接口的兩個子藉口:List / Set 接口;
List:列表,有序(存入和取出的順序一致),元素都有索引(角標),元素可以重複;
Set:集合,無序,元素不能重複;

~ List 列表集合

1、List:列表,有序,可重複;

2、List:特有的常見方法: 有一個共性特點就是都可以操作角標;
(1)添加void add(index,element);void add(index,collection);
(2)刪除Object remove(index); // 刪除指定索引位置的元素;
(3)修改Object set(index,element);
(4)獲取Object get(index); // 返回指定索引位置的元素;list特有的取出元素的方式之一;
int indexOf(object); // 返回指定元素在List中的第一次出現的索引值;
int lastIndexOf(object);
List subList(from,to); //獲取子列表;

注意:list集合是可以完成對元素的增刪改查;

public static void main(String[] args) {
	List list = new ArrayList();
	// 添加元素
	list.add("abc0");
	list.add("abc1");    // list: [abc0, abc1]
	// 插入元素
	list.add(1, "abc2"); // list: [abc0, abc2, abc1]
	// 刪除元素
	Syso("remove: "+list.remove(1)); // remove: abc1,list: [abc0, abc1]
	// 修改元素
	Syso("set: "+list.set(1, "abc9")); // set: abc1,list: [abc0, abc9]
	// 獲取元素
	Syso("get: "+list.get(0)); // get: abc0,list: [abc0, abc9]
	// 獲取字列表
	Syso("subList: "+list.subList(0, 2)); // subList: [abc0, abc9],list: [abc0, abc9]
}

3、List:有三個子類:
(1)Vector:內部是數組數據結構,是同步的;增刪,查詢都很慢!
(2)ArrayList:內部是數組數據結構,是不同步的;替代了Vector;查詢的速度快;
(3)LinkedList:內部是鏈表數據結構,是不同步的;增刪元素的速度很快;

4、List集合特有的功能:
(1)list.get(index):根據指定索引值取出列表集合元素;
(2)ListIterator it = list.listIterator();:只有list集合具備listIterator迭代功能,它可以實現在迭代過程中完成對元素的增刪改查;

public static void main(String[] args) {
	List<String> list = new ArrayList<String>();
	list.add("abc1");
	list.add("abc2");
	ListIterator<String> it = list.listIterator(); // 獲取列表迭代器對象
	while(it.hasNext()){
		Object obj = it.next();  // 將取出來的迭代器對象放到obj對象中存儲
		if(obj.equals("abc2")) it.set("abc9");
	}
	System.out.println("hasNext:"+it.hasNext()); // 正向輸出
	System.out.println("hasPrevious:"+it.hasPrevious()); // 逆向輸出	
//		Iterator<String> it = list.iterator(); // 獲取list的迭代器對象;
//		while(it.hasNext()){
//			Object obj = it.next(); // java.util.ConcurrentModificationException
//			if(obj.equals("abc2")) list.add("abc9");
//			else System.out.println("obj: "+obj);
//		}   System.out.println("list:"+list);
}

ConcurrentModificationException 異常
(1)產生異常的原因:
list集合中每次add元素,長度都會變化;在長度爲3時調用集合的迭代器,這時的迭代器對象的值是3(迭代器只知道這時候集合中有3個元素),迭代器就按照3個元素的方式開始遍歷集合取出元素;當執行list.add("abc9");時,迭代器並不知道集合添加了元素,這時候是迭代器在操作元素,而在迭代器操作元素的過程中,又用集合在操作元素,這樣就會引發java.util.ConcurrentModificationException異常:當方法檢測到對象的併發修改(集合和迭代器同時對元素進行修改,就會導致迭代出現問題),但不允許這種修改時,拋出此異常;

(2)注意事項:
在迭代器過程中,不要使用集合操作元素,容易出現異常:java.util.ConcurrentModificationException

(3)解決方法:
Iterator接口有一個子接口ListIterator,List的ListIterator方法可以返回元素的列表迭代器,這個迭代器只有列表List有;
通過ListIterator對象的方法,可以實現在迭代器操作集合過程中,對集合進行增刪改查操作;

~ LinkedList 集合

1、常用方法:

public static void main(String[] args) {
	LinkedList<String> link = new LinkedList<>();
	link.addFirst("abc1");  // link.addLast("abc0");
	link.addFirst("abc2");  // 在鏈表頭部添加元素;
	link.addFirst("abc3");
	System.out.println(link);  // [abc3, abc2, abc1]
	Iterator<String> it = link.iterator();  
	while(it.hasNext()){
		System.out.println(it.next());  // abc3 abc2 abc1
	}
	Syso(link.getFirst());  // 獲取但不刪除,如果鏈表爲空,拋出NoSuchElementException 
	Syso(link.getLast());
	Syso(link.peekFirst()); // 獲取但不移除,如果鏈表爲空,返回null
	Syso(link.peekLast());
	Syso(link.removeFirst()); // 獲取並移除,如果鏈表爲空,拋出NoSuchElementException
	Syso(link.removeFirst());
	Syso(link.pollFirst());   // 獲取並移除,如果鏈表爲空,返回null
	Syso(link.pollLast());
	while(!link.isEmpty()){
		Syso(link.removeLast()); // 取一個刪除一個,取完容器空了;
	}                                    	 
}

2、面試題:使用 LinkedList 來模擬一個堆棧 或 隊列數據結構;
堆棧:先進後出,First In Last Out - FILO
隊列:先進先出,First In First Out - FIFO

我們應該描述這樣一個容器:給使用者提供一個容器對象 完成這兩種結構中的一種;

class Duilie{
	private LinkedList<Object> link; // 使用java提供的LinkedList定義一個私有成員變量;
	public Duilie(){ link = new LinkedList<Object>(); } // 構造函數,直接調用已有容器創建對象,賦值給自定義成員引用
	// 隊列先進先出,每次在隊列尾部添加元素;
	public void myAdd(Object obj){ link.addLast(obj); }
	// 每次在隊列頭部取元素;remove:獲取並刪除,列表爲空拋異常;
	public Object myGet(){ return link.removeFirst(); }
	public boolean isNull(){ return link.isEmpty(); }
}
class test {
	public static void main(String[] args) {
		Duilie d = new Duilie(); // 用自定義容器創建一個對象
		d.myAdd("abc1"); // 調用自定義容器裏面的myAdd方法增加新元素
		d.myAdd("abc2");
		d.myAdd("abc3"); // 打印輸出,調用自定義容器裏面的判斷爲空的方法
		while(!d.isNull()){ System.out.println(d.myGet()); }
	}
}

~ ArrayList 集合 - 存儲自定對象

1、存自定義數據的時候,取出的時候注意考慮強轉動作;
2、自動裝箱:基本數據類型賦值給引用數據類型的時候進行裝箱;
al.add(5); //al.add(new Integer(5));
3、自動拆箱:引用數據類型和基本數據類型做運算時候進行;

class Person{
	private String name;
	private int age;
	public Person(String name, int age){ this.name = name; this.age = age; }
	// set、 get方法......	
}
class test {
	public static void main(String[] args) {
		Person person = new Person("list1",21);
		ArrayList<Object> al = new ArrayList<>();
		al.add(person);  // 添加數據的時候,先創建對象再傳參;
		al.add(new Person("list2",22)); // 直接在傳參的時候創建對象;
		Iterator<Object> it = al.iterator(); // 創建迭代器對象
		while(it.hasNext()){
			Person p = (Person)it.next(); // 將獲取的數據賦值給新創建的對象引用
			System.out.println("name: "+p.getName()+", age: "+p.getAge());
		}
	}
}

4、練習 - 定義功能去除ArrayList中重複元素:
(1)ArrayList判斷元素是否相同依據的是equals方法,HasSet判斷元素是否相同依據的是hashCode+equals方法;
(2)判斷集合中是否包含某數據,通過使用contains方法判斷,而contains方法原理是調用equals方法實現的,所以Person類需要重寫object的equals方法,不需要重寫hashCode,因爲沒有用到;remove底層判斷相同用的也是equals方法;

class Person{
	private String name;
	private int age;
	public Person(String name, int age){ this.name = name; this.age = age; }
	// set、 get方法......
	public boolean equals(Object obj) {
		if(this	== obj) return true;
		if(!(obj instanceof Person)) throw new ClassCastException("類型錯誤!");
		Person p = (Person)obj;
		return this.name.equals(p.name) && this.age==p.age;
	}
	public String toString(){ return name+":"+age; }
}
public class test {
	public static void main(String[] args) {
		ArrayList<Person> al = new ArrayList<Person>();
		al.add(new Person("list1",21)); // 添加自定義對象;
		al.add(new Person("list1",22));
		al.add(new Person("list1",22));
		al.add(new Person("list1",23)); // [list1:21, list1:22, list1:22, list1:23]
		al = getSingleElement(al); // [list1:21, list1:22, list1:23]
	}
	public static ArrayList getSingleElement(ArrayList al) {
		ArrayList temp = new ArrayList(); // 定義一個臨時容器;
		Iterator it = al.iterator(); // 迭代傳入的al集合;
		while(it.hasNext()) {
			Object obj = it.next(); // 判斷被迭代到的元素是否在臨時容器中存在;
			if(!temp.contains(obj)) temp.add(obj); // 若不存在,則添加;
		}
		return temp;
	}
}

~ Set 集合 - HashSet、TreeSet

1、Set集合:無序,元素不可重複;

2、Set接口中的方法和Collection接口中的方法一致

3、有HashSet、TreeSet兩個子類實現:HashSet、TreeSet;

~ HashSet 集合 - 存儲自定對象

1、哈希表:
在這裏插入圖片描述

2、HashSet: 內部數據結構是哈希表,是不同步的;
是通過對象的hashCode+equals方法來完成對象的唯一性的判斷的:
(1)首先判斷對象的hashCode值,若對象的hashCode只值不同,不用再判斷對象的equals值,直接將對象存儲到哈希表中;
(2)若hashCode值相同,就要繼續判斷對象的equals方法返回值是否爲true,若爲true,則視爲相同元素,不存入哈希表,若爲false,則視爲不同元素,存儲到哈希表中;

注意: 若要將對象存儲到HashSet集合中,定義對象類時,必須要重寫Object的hashCode+equals方法;

3、HashSet 存儲自定義對象:
(1)要求:往hashSet集合中存儲Person對象,如果姓名和年齡相同,視爲同一個人,視爲相同元素;
(2)判斷方法:HashSet集合數據結構是哈希表,所以存儲元素的時候,首先使用的元素的hashCode方法來確定位置,如果位置相同,再通過元素的equals來確定值是否相同;
(3)創建Person類:Person繼承Object,繼承Object的hasCode方法、equals方法;Object的hasCode方法,判斷的是底層系統的哈希地址,equals判斷的不是內容,而是地址,每個對象都有自己的地址;所以,需要在Person類中,重寫(覆蓋)父類Object的兩個方法;

class Person{
	private String name;
	private int age;
	public Person(String name, int age){ this.name = name; this.age = age; }
	// set、 get方法......	
	@Override
	public int hashCode() {
		System.out.println(this+".......hashCode");	
		return name.hashCode()+age*27; // 返回名字的哈希值+年齡*一個數;
	}
	@Override
	public boolean equals(Object obj) {
		if(this==obj) return true; // 若傳入的是同一個對象,直接返回true
		if(!(obj instanceof Person)) throw new ClassCastException("類型錯誤!");
		System.out.println(this+"...equals..."+obj);
		Person p = (Person)obj;
		return this.name.equals(p.name) && this.age==p.age; // 判斷名字年齡是否相同;
	}
	public String toString(){return name+":"+age;}	
}
class test {
	public static void main(String[] args) {
		HashSet<Person> hs = new HashSet<Person>();
		hs.add(new Person("hashSet1",21));
		hs.add(new Person("hashSet1",22)); // 再增加時,會調用hasCode、equals方法
		hs.add(new Person("hashSet1",23));
		Iterator<Person> it = hs.iterator();
		while(it.hasNext()){
			Person p = (Person)it.next();
			System.out.println(p);
		}
	}
}

~ LinkedHashSet 集合

1、LinkedHashSet是HashSet的子類: HashSet hs = new LinkedHashSet();

2、若既要有序,又要唯一,使用LinkedHashSet;
若要有序,使用List;
若要唯一,使用HashSet;

~ TreeSet 集合

1、TreeSet: 可以對Set集合中的元素進行指定順序的排序;是不同步的;

2、判斷元素唯一性的方式:根據比較方法compareTo的返回結果,判斷是否爲0,是0,就視爲是相同元素,不進行存儲;

3、TreeSet集合只看compareTo方法,不看hashCode+equals兩個方法;

4、 TreeSet 對元素進行排序的方式:

(1)讓元素自身具備比較的功能: 定義一個Person類,實現Comparable接口,覆蓋compareTo方法;

如果不要按照對象中具備的自然順序進行排序,或者對象中不具備自然順序,怎麼辦?可以使用第二種方式;

(2)讓集合自身具備比較功能: 定義一個比較器的類,實現Comparator接口,重寫compare方法,然後創建該類的實例對象,作爲參數傳遞給TreeSet的構造函數;

5、注意:Person具備自然排序,TreeSet具備比較器,兩個都存在,以比較器爲主;
字符串對象本身具備的自然排序不是我們需要的,就只能使用比較器來完成;
字符串的自然排序是java寫好的;

6、練習: 以Person對象的年齡進行從小到大的排序;

// 讓元素自身具備比較功能:實現Comparable接口,覆蓋compareTo方法;
class Person implements Comparable{
	private String name;
	private int age;
	public Person(String name, int age){ this.name = name; this.age = age; }
	// set、 get、toString方法......
	@Override  // 實現Comparable接口的compareTo方法
	public int compareTo(Object o) { // 按照年齡進行從小到大的排序;
		Person p = (Person)o;
		int temp = this.age - p.age;
		return temp==0?this.name.compareTo(p.name):temp;
	}
}
// 創建了一個根據Person類的name進行排序的比較器:
// 讓集合自身具備比較功能,定義一個類實現Comparator接口,覆蓋compare方法;
class ComparatorBuName implements Comparator{
	@Override  // 覆蓋Comparator接口的compare方法
	public int compare(Object o1, Object o2) {
		Person p1 = (Person)o1;
		Person p2 = (Person)o2; // compareTo是String自帶的比較方法
		int temp = p1.getName().compareTo(p2.getName());
		return temp==0?p1.getAge()-p2.getAge():temp; //若temp=0時,按年齡進行二次比較,返回
	}
}
class test {
	public static void main(String[] args) {
		// 將創建的ComparatorByName()對象傳給TreeSet構造器;
		// 字符串對象本身具備的自然排序不是我們需要的時候,就只能使用比較器來完成;;
		// 對象具備自然排序,集合具備比較器,兩個都存在時,以比較器爲主;
		TreeSet ts = new TreeSet(new ComparatorBuName()); 
		ts.add(new Person("zhangsan",28));
		ts.add(new Person("lisi",29));
		ts.add(new Person("zhouqi",29));
		ts.add(new Person("zhaoliu",25));
		ts.add(new Person("wangu",24));
		Iterator it = ts.iterator();	
		while(it.hasNext()){
			Person p = (Person)it.next();	
			System.out.println(p.getName()+":"+p.getAge());
		}	
	}
}

7、練習: 字符串長度排序;

class ComparatorByLength implements Comparator{
	public int compare(Object o1, Object o2) {
		String s1 = (String) o1;
		String s2 = (String) o2;
		int temp = s1.length() - s2.length();
		return temp==0?s1.compareTo(s2):temp;
	}   // 若temp=0,使用String自己的compareTo方法進行二次比較
}
class test {
	public static void main(String[] args) {
		TreeSet ts = new TreeSet<>();
		ts.add("aaaaa");  ts.add("zz"); ts.add("nbaq");
		ts.add("cba");  ts.add("abc");	
		Iterator it = ts.iterator();
		while(it.hasNext()){ System.out.println(it.next()); }
	}
}

~ List列表與Set集合選擇

1、List列表與Set集合選擇:

2、如何記錄每一個容器的結構和所屬體系:
看名字:
-> 後綴名就是該集合所屬的體系;
-> 前綴名就是該集合的數據結構;
①看到array:就要想到數組,就要想到查詢快,有角標;
②看到link:就要想到鏈表,就要想到增刪快,就要想要 add get remove+frist last方法;
③看到hash:就要想到哈希表,就要想到唯一性、元素需覆蓋hashcode和equals方法;
④看到tree:就要想到二叉樹,就要想要排序、兩個接口ComparableComparator ;

3、通常這些常用的集合容器都是不同步的;

~ Map 集合

1、Map: 一次添加一對元素,也稱爲雙列集合;
其實map集合中存儲的就是鍵值對, map集合中必須保證鍵的唯一性;
Collection: 一次添加一個元素,也稱爲單列集合;

2、常用方法:
(1)添加
value put(key,value):返回前一個和key關聯的值,如果沒有 返回null;若存相同鍵,值會覆蓋;
(2)刪除
void clear():清空map集合;
value remove(key):根據指定的key刪除這個鍵值對,返回值;
(3)判斷
boolean containsKey(key)
boolean containsValue(value)
boolean isEmpty()
(4)獲取
value get(key):通過鍵獲取值,如果沒有該鍵返回null;當然可以通過返回null,來判斷是否包含指定鍵;
int size():獲取鍵值對的個數;

3、Map常用的子類:
(1)Hashtable :內部結構是哈希表,是同步的;不允許null作爲鍵,null作爲值;
(2)Properties:用來存儲鍵值對型的配置文件的信息,可以和IO技術相結合;
(3)HashMap : 內部結構是哈希表,不是同步的;允許null作爲鍵,null作爲值;
(4)TreeMap : 內部結構是二叉樹,不是同步的;可以對Map集合中的鍵進行排序;

4、取出map集合中的元素:
(1)取出map中的所有元素: 通過keySet方法獲取map中所有的鍵所在的Set集合,再通過Set的迭代器獲取到每一個鍵,再對每一個鍵通過map集合的get方法獲取其對應的值即可;

(2)通過Map轉成set就可以迭代: entrySet; 該方法將鍵和值的映射關係作爲對象存儲到了Set集合中,而這個映射關係的類型就是Map.Entry類型;

class test {
	public static void main(String[] args) {
		Map<Integer,String> map = new HashMap<Integer,String>();
		map.put(1, "map1");
		map.put(2, "map2");
		map.put(3, "map3");
		Collection<String> values = map.values(); // 取出map中所有的值;
		Iterator<String> it = values.iterator();
		while(it.hasNext()){ System.out.println(it.next()); }
		// 方法1:通過keySet方法獲取map中所有的鍵所在的Set集合,
		// 再通過Set的迭代器獲取到每一個鍵,再對每一個鍵通過map集合的get方法獲取其對應的值即可;
		Set<Integer> keySet = map.keySet();
		Iterator<Integer> itKeySet = keySet.iterator();
		while(itKeySet.hasNext()){
			Integer key = itKeySet.next();
			String value = map.get(key);
			System.out.println(key+":"+value);
		}
		// 方法2:通過Map轉成set就可以迭代:entrySet方法
		// 該方法將鍵和值的映射關係作爲對象存儲到了Set集合中,而這個映射關係的類型就是Map.Entry類型(結婚證)
		Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
		Iterator<Entry<Integer, String>> itEntrySet = entrySet.iterator();
		while(itEntrySet.hasNext()){
			Map.Entry<Integer, String> mapEntry = itEntrySet.next();
			Integer key = mapEntry.getKey();
			String value = mapEntry.getValue();
			System.out.println(key+":"+value);
		}
	}
}
interface MyMap{  public static interface MyEntry{ void get();  }  //內部接口 }
class MyDemo implements MyMap.MyEntry{  public void get(){}  }
class Outer{     // Outer.Inner.show();
	    static class Inner{  static void show(){}  
 }

~ HashMap - 存儲自定義對象

將學生對象和學生的歸屬地通過鍵與值存儲到map集合中:
HashMap要保證鍵唯一,鍵相同的時候,值覆蓋;
鍵存儲的是對象類型時,對象需要重寫hashcode+equals方法;

class Student {
	private String name;
	private int age;
	public Student(String name, int age){ this.name = name;this.age = age; }
	@Override 
	public int hashCode() { return name.hashCode()+age*27;  }
	@Override
	public boolean equals(Object obj) {
		if(this==obj) return true;
		if((obj instanceof Student)) throw new ClassCastException("類型錯誤!");
		Student student = (Student)obj;
		return this.name.equals(student.name) && this.age==student.age;
	}
}
class test{
	public static void main(String[] args) {
		HashMap<Student,String> hm = new HashMap<Student,String>();
		hm.put(new Student("hm11",11), "beijing");
		hm.put(new Student("hm2",22), "tianjin");
		hm.put(new Student("hm3",33), "shanghai");
		Set<Student> keySet = hm.keySet();
		Iterator<Student> it = keySet.iterator();
		while(it.hasNext()) {
			Student key = it.next();
			String value = hm.get(key);
			System.out.println(key.getName()+":"+key.getAge()+":"+value);
		}
	}
}

~ TreeMap - 存儲自定義對象

class Student implements Comparable<Student>{
	private String name;
	private int age;
	public Student(String name, int age){ this.name = name;this.age = age; }
	@Override
	public int compareTo(Student o) {
		int temp = this.getName().compareTo(o.getName());
		return temp==0?this.getAge()-o.getAge():temp;
	}
}
class test{
	public static void main(String[] args) {
		TreeMap<Student,String> tm = new TreeMap<Student,String>();
		tm.put(new Student("tm1",11), "北京");
		tm.put(new Student("tm3",33), "上海");
		tm.put(new Student("tm2",22), "天津");
		Iterator<Map.Entry<Student, String>> it = tm.entrySet().iterator();
		while(it.hasNext()) {
			Map.Entry<Student, String> me = it.next();
			Student key = me.getKey();
			String value = me.getValue();
			System.out.println(key.getName()+":"+key.getAge()+":"+value);
		}
	}
}

~ LinkedHashMap

有序 指的是存入和取出數據的順序一致;按照從大到小或從小到大的順序指的是排序

TreeSet有序的意思指的是按照特定方式進行的排序;
HashSet是無序的,指的是存和取的順序不一致;
LinkedHashSet是有序的:存入取出順序一致;

public static void main(String[] args) {
	HashMap<Integer,String> hm = new LinkedHashMap<Integer,String>();
	hm.put(7, "zhangsan");
	hm.put(3, "lisi");
	hm.put(1, "wangwu");
	hm.put(5, "zhaoliu");   
	Iterator<Map.Entry<Integer, String>> it = hm.entrySet().iterator();
	while(it.hasNext()){
		Map.Entry<Integer, String> me = it.next();
		Integer key = me.getKey();
		String value = me.getValue();
		System.out.println(key+" : "+value);    // 7 3 1 5
	}
}
LinkedHashMap輸出:7 3 1 5 
HashMap輸出:1 3 5 7

~ Map 集合練習

1、記錄字母次數: "fdgavcbsacdfs" 獲取該字符串中,每一個字母出現的次數,要求打印結果是:a(2)b(1)...

思路:對於結果的分析發現,字母和次數之間存在着映射的關係,而且這種關係很多,很多就需要存儲,能存儲映射關係的容器有數組和Map集合;
關係–方式沒有序編號嗎,那就是使用Map集合; 又發現可以保證唯一性的一方具備着順序如 a b c …,所以可使用TreeMap集合,這個集合最終應該存儲的是字母和次數的對應關係;

過程
①因爲操作的是字符串中的字母,所以先將字符串變成字符數組;
②遍歷字符數組,用每一個字母作爲鍵去查Map集合這個表;
如果該字母鍵不存在,就將該字母作爲鍵, 1作爲值存儲到map集合中;
如果該字母鍵存在,就將該字母鍵對應值取出並+1,在將該字母和+1後的值
存儲到map集合中,鍵相同值會覆蓋;這樣就記錄住了該字母的次數;
③遍歷結束,map集合就記錄所有字母的出現的次數;

class test {
	 public static void main(String[] args) {
		String str = "fdg+avAdc  bs5dDa9c-dfs";	
		String s = getCharCount(str);
		System.out.println(s);
		
	}
	 public static String getCharCount(String s){
		 // 1、將字符串轉換成字符數組;
		 char[] chr = s.toCharArray();
		 // 2、定義Map集合;
		 Map<Character,Integer> map = new HashMap<Character,Integer>();
		 for(int i=0; i<chr.length; i++){
			 // 過濾非字母字符;
			 if(!(chr[i]>='a'&&chr[i]<='z')||chr[i]>='A'&&chr[i]<='Z') continue;
			 int count = 1;
			 // 將數組中的字母作爲鍵去查找map集合;
			 Integer value = map.get(chr[i]);
			 if(value!=null) count=value+1;
			 map.put(chr[i], count);
		 }
		 return mapToString(map);
	 }
	 public static String mapToString(Map<Character,Integer> map){
		 StringBuilder sb = new StringBuilder();
		 Iterator<Character> it = map.keySet().iterator();
		 while(it.hasNext()){
			 Character key = it.next();		
			 Integer value = map.get(key);
			 sb.append(key+"("+value+")");
		 }
		 return sb.toString();
	 }
}

2、Map查表法: Map在有映射關係時,可以優先考慮,在查表法中的應用較爲多見;

public class MapTest2 {
	public static void main(String[] args) {
		String week = getWeek(1);
		System.out.println(week);		
		System.out.println(getWeekByMap(week));
	}
	public static String getWeekByMap(String week){		
		Map<String,String> map = new HashMap<String,String>();	
		map.put("星期一","Mon");
		map.put("星期二","Tus");
		map.put("星期三","Wes");
		map.put("星期日","Sun");
		map.put("星期天","Sun");		
		return map.get(week);
	}	
	public static String getWeek(int week){		
		if(week<1 || week>7)
			throw new RuntimeException("沒有對應的星期,請您重新輸入");		
		String[] weeks = {"","星期一","星期二"};		
		return weeks[week];
	}
}

~ 工具類 Collections

Collections類:是集合框架的工具類,裏面的方法都是靜態的,可以不創建對象,直接使用;

1、排序: Collections.sort();

class test {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("abcde");
		list.add("nba");
		list.add("aa");
		System.out.println(list);
		// 對list進行指定集合的排序;
 		Collections.sort(list); // 已有方法,直接使用;
 		Collections.sort(list, new ComparatorByLength()); // 已有方法,帶比較器;
 		mySort(list); // 自定義方法;
		mySort(list, new ComparatorByLength()); // 自定義方法帶比較器;
		System.out.println(list);
	}
	public static <T extends Comparable<? super T>> void mySort(List<T> list) {
		for(int i=0; i<list.size()-1; i++){
			for(int j=i+1; j<list.size(); j++){
				if (list.get(i).compareTo(list.get(j)) > 0) {
					// T temp = list.get(i);
					// list.set(i, list.get(j));
					// list.set(j, temp);
					Collections.swap(list, i, j);
				}
			}
		}
		
	}
	private static <T> void mySort(List<T> list, Comparator<? super T> comp) {
		for(int i=0; i<list.size()-1; i++){
			for(int j=i+1; j<list.size(); j++){
				 if(comp.compare(list.get(i), list.get(j))>0){
					 Collections.swap(list, i, j);
				 }
			}
		}
	}
}
class ComparatorByLength implements Comparator<String>{
	@Override
	public int compare(String o1, String o2) {
		int temp = o1.length() - o2.length();
		return temp==0?o1.compareTo(o2):temp;
	}
}

2、折半、最值:

class test {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("abcde");
		list.add("nba");
		list.add("aa");
		System.out.println(list);
		Collections.sort(list);  // 先排序,之後才能二分查找;
		int index = Collections.binarySearch(list, "nba");  // 二分查找
		System.out.println("index: "+index);
		String max = Collections.max(list, new ComparatorByLength());  // 最大值
		System.out.println("max: "+max);
	}
}
class ComparatorByLength implements Comparator<String>{
	@Override
	public int compare(String o1, String o2) {
		int temp = o1.length() - o2.length();
		return temp==0?o1.compareTo(o2):temp;
	}
}

3、逆序、替換:

public static void main(String[] args) {
	TreeSet<String> ts = new TreeSet<String>(new Comparator<String>() {
		@Override
		public int compare(String o1, String o2) {
			return o2.compareTo(o1);
		}
	});
//		TreeSet<String> ts = new TreeSet<String>(Collections.reverseOrder(new ComparatorByLength()));
	ts.add("abc");   	
	ts.add("hahaha");
	ts.add("zzz");	
	System.out.println(ts);		
}

4、其他方法、將非同步集合轉成同步集合的方法: 給非同步的集合加鎖,返回一個同步的集合

public static void main(String[] args) {
	List<String> list = new ArrayList<String>();	//新建對象	
	list.add("abcde");
	Collections.replaceAll(list, "cba", "nba"); 
	// set(indexOf("cba"),"nba");用的是set方法,將集合中元素替換
	Collections.shuffle(list);    //隨機排列指定的列表,使用默認的隨機性源
	Collections.fill(list, "cc");//用指定的元素替換指定列表中的所有元素
	System.out.println(list);
}
List list = new ArrayList();    //定義一個List對象,非同步的;
list = MyCollections.synList(list);  //返回一個同步的list.
class MyCollections{                 //自定義一個類
	public static  List synList(List list){   //靜態方法		
		return new MyList(list);             //返回一個私有方法創建的對象
	} 
	private class MyList implements List{ //定義一個私有方法,實現List接口
	    private List list;	
	    private static final Object lock = new Object();
	    MyList(List list){	    this.list = list;	    }	
	    public boolean add(Object obj){
	    	 synchronized(lock){    return list.add(obj);    }
	    }	
	    public boolean remove(Object obj){
		    synchronized(lock){   return list.remove(obj);    }
	    }
}
}

~ 工具類 Arrays - 數組轉成集合

1、Arrays: 集合框架的工具類,裏面的方法都是靜態的;
List<String> list = Arrays.asList(arr);:asList將數組轉換成集合,然後可以使用集合的方法操作數組中的元素;

注意:數組的長度是固定的,所以對於集合的增刪方法是不可以使用的
否則會發生異常UnsupportedOperationException

2、demo_2() 如果數組中的元素是對象,那麼轉成集合時,直接將數組中的元素作爲集合中的元素進行集合存儲;
如果數組中的元素是基本類型數值,那麼會將該數組作爲集合中的元素進行存儲;

class test {
	public static void main(String[] args) {
		int[] arr = { 3, 1, 5, 6, 3, 6 };
		System.out.println(arr); // 不用toString輸出的是arr哈希碼
		System.out.println(Arrays.toString(arr));
	}
	public static void demo_1() {
		String[] arr = {"abc", "haha", "xixi"};
		boolean b = myContains(arr, "xixi");    // 自定義數組轉集合的方法
		System.out.println("contains:" + b);
		List<String> list = Arrays.asList(arr); // 已有數組轉集合的方法
		boolean b1 = list.contains("xixi");     // 直接使用集合的方法;
		System.out.println("list contaisn:=" + b1);
		// list.add("hiahia");  //UnsupportedOperationException
		System.out.println(list);
	}
	public static boolean myContains(String[] arr, String key) {
		for (int i = 0; i < arr.length; i++) {
			if (arr[i].equals(key)) return true;
		}
		return false;
	}
	public static void demo_2() {
		int[] arr = { 31, 11, 51, 61 };
		List<int[]> list = Arrays.asList(arr);
		System.out.println(list);
	}
}

~ 工具類 Collection的toArray方法 - 集合轉成數組

1、集合轉成數組: 可以對集合中的元素操作的方法進行限定,不允許對其進行增刪;

2、toArray方法需要傳入一個指定類型的數組;

3、數組長度定義:
如果長度小於集合的size,那麼該方法會創建一個同類型並和集合相同size的數組;
如果長度大於集合的size,那麼該方法就會使用指定的數組,存儲集合中的元素,其他位置默認爲null;
所以建議,最後長度就指定爲集合的size;

List<String> list = new ArrayList<String>();   //注意要加泛型
list.add("abc1"); 	list.add("abc2"); 	list.add("abc3");
String[] arr = list.toArray(new String[list.size()]);		
System.out.println(Arrays.toString(arr));		

~ foreach - 高級for循環

1、foreach語句:
格式:for(類型 變量 :Collection集合|數組){ } eg: for(String s : list){}

2、傳統for和高級for的區別:
傳統for可以完成對語句執行很多次,因爲可以定義控制循環的增量和條件;高級for是種簡化形式;必須有被遍歷的目標;該目標是數組或Collection單列集合;對於數組的遍歷如果僅僅是獲取數組中的元素,可以使用高級for;如果要對數組的角標進行操作建議使用傳統for;

3、可以使用高級for遍歷map集合嗎?
不能直接用,但是可以將map轉成單列的set,就可以用了;

public static void main(String[] args) {
	List<String> list = new ArrayList<String>();     // 遍歷集合
	list.add("abc1"); list.add("abc2"); list.add("abc3");
	for (String s : list) { System.out.println(s); } // 簡化書寫
	
	int[] arr = { 3, 1, 5, 7, 4 }; // 遍歷數組
	for (int i : arr) { System.out.println(i); }
	
	Map<Integer, String> map = new HashMap<Integer, String>();
	map.put(3, "zhagsan"); map.put(1, "wangyi"); map.put(7, "wagnwu"); 
	for (Integer key : map.keySet()) {
		String value = map.get(key);
		System.out.println(key + "::" + value);
	}
	for (Map.Entry<Integer, String> me : map.entrySet()) {
		Integer key = me.getKey();
		String value = me.getValue();
		System.out.println(key + ":" + value);
	} 
}

~ 函數可變參數

函數的可變參數,其實就是一個數組,但是接收的是數組的元素,自動將這些元素封裝成數組;簡化了調用者的書寫;
注意:可變參數類型,必須定義在參數列表的結尾;
對:public static int newAdd(int a,int... arr)
錯:public static int newAdd(int... arr,int a)
對:public static int add(int a,int[] arr)
對:public static int add(int[] arr,int a)

public static int newAdd(int a,int...  arr){		
		int sum = 0;
		for (int i = 0; i < arr.length; i++) {	sum+=arr[i];	}
		return sum;
}
int sum = newAdd(5,1,4,7,3);

>> 泛型

~ 基礎 概述

1、泛型: jdk1.5出現的安全機制;

2、好處:
(1)將運行時期的問題ClassCastException轉到了編譯時期;
(2)避免了強制轉換的麻煩;

3、<>:什麼時候用: 當操作的引用數據類型不確定的時候,就使用<>,將要操作的引用數據類型傳入即可,但是不能傳入基本數據類型;

其實<>就是用來接收具體引用數據類型的參數範圍的:ArrayList<Person> al = new ArrayList<Person>();

4、在程序中,只要用到了帶有<>的類或者接口,就要明確傳入的具體引用數據類型 ;

5、泛型技術是給編譯器使用的技術,用於編譯時期,確保了類型的安全;
運行時,會將泛型去掉,生成的class文件中是不帶泛型的,這個稱爲泛型的擦除
爲什麼擦除呢:因爲爲了兼容運行的類加載器;
泛型的補償:在運行時,通過獲取元素的類型進行轉換動作,不用使用者再強制轉換了;

6、代碼:

public static void main(String[] args) {
	int[] arr = new int[4]; // int型數組,創建時就指定好了數據類型[]
	arr[0] = 3.0;           //傳入的不是int型的就會報錯	
	ArrayList<String> al = new ArrayList<String>();//<>指定傳入的數據類型		
	al.add("abc"); //public boolean add(Object obj)
	al.add(4);     //al.add(new Integer(4));  報錯
	Iterator<String> it = al.iterator();
	while(it.hasNext()){
		String str = it.next(); //使用泛型後不用再強轉
	}
}

7、泛型在TreeSet集合中的應用:
TreeSet集合裏面的數據是有序的,所以需要比較,所以需要在比較器中加入泛型;

class Person implements Comparable<Person>{ // 限制傳入比較器的數據類型是Person類型;
	private String name;
	private int age;
	public Person(String name, int age){ this.name=name; this.age=age; }
	// set、 get、toString方法......
	public int compareTo(Person p){ // 直接使用Person接收參數; 
		int temp = this.age-p.age;
		return temp==0?this.name.compareTo(p.name):temp;
	} 
}
class ComparatorByName implements Comparator<Person>{ // 加入泛型
	@Override
	public int compare(Person p1, Person p2) {
		int temp = p1.getName().compareTo(p2.getName());
		return temp==0?p1.getAge()-p2.getAge():temp;
	}
}
public class test {
	public static void main(String[] args) {
		// 加入泛型,限制集合中只能傳入Person類型的參數;
		TreeSet<Person> ts = new TreeSet<Person>(); 
		ts.add(new Person("zhangsan",32));
		ts.add(new Person("lisi",22));
		ts.add(new Person("wangwu",12));
		Iterator<Person> it = ts.iterator();
		while(it.hasNext()) {
			// 使用泛型限制類迭代器中的數據類型是Person,不用再進行強轉了;
			Person p = it.next(); 
			System.out.println(p);
		}
	}
}

~ 泛型類

1、在jdk1.5後,使用泛型來接收類中要操作的引用數據類型;

2、泛型類什麼時候用: 當類中的操作的引用數據類型不確定的時候,就使用泛型來表示;

3、定義類時沒有使用泛型:

class Tool{   // 定義類時沒有使用泛型
	private Object object;
	public Object getObject(){ return object; }
	public void setObject(Object object){ this.object = object; }
}
class Student{}
class Worker{}
public class test {
	public static void main(String[] args) {
		 Tool tool = new Tool();
		 tool.setObject(new Worker());
		 // 沒使用泛型的時候,Tool用object接收,set方法傳入Worker對象,
		 // get方法用Student接收,編譯通過,運行失敗;
		 Student stu = (Student) tool.getObject();
	}
}

4、定義泛型類:

class Tool<Q>{
	private Q object;
	public Q getObject(){ return object; }
	public void setObject(Q object){ this.object = object; }
}
public class test {
	public static void main(String[] args) {
		 Tool<Student> tool = new Tool<Student>();
		 tool.setObject(new Worker()); // 使用泛型,傳入Worker,編譯報錯;
		 Student stu = (Student) tool.getObject();
	}
}

~ 泛型方法

1、泛型<>必須放在返回值前邊,修飾符後邊: public <W> void show(W str){}

2、當方法靜態時,不能訪問類上定義的泛型;

3、如果靜態方法要使用泛型,只能將泛型定義在方法上;

class Tool<Q>{
	private Q object;
	public Q getObject(){ return object; }
	public void setObject(Q object){ this.object = object; }
	// 將泛型定義在方法上;
	public <W> void show(W str) { // 泛型<>必須放在返回值前邊,修飾符後邊
		// show的泛型是在方法上自定義的,所以接收什麼類型,str就是什麼類型;
		// 不能使用length方法,因爲泛型定義在方法上,接受數據類型不確定;
		System.out.println("show: "+str.toString());
	}
	// 靜態方法不能訪問類上定義的泛型,要使用泛型,只能將泛型定義在方法上;
	public static <Y> void method(Y obj){
		System.out.println("method:"+obj);
	}
	public void print(Q str){ // print的泛型是跟着對象走的;
		System.out.println("print : "+str);
	}
}
public class test {
	public static void main(String[] args) {
		 Tool<String> tool = new Tool<String>();
		 tool.show(new Integer(3)); // show方法定義泛型了,所以可以傳入任意類型的數據;
		 tool.show("abc");
		 tool.print("haha"); // print方法上沒有定義泛型,所以泛型是跟着對象走的,只能傳入指定類型的數據;
		 Tool.method("static"); // 靜態方法可以直接類名調用;
		 Tool.method(new Integer(2)); 
	}
}

~ 泛型接口

interface Inter<T>{ public void show(T t); }
// 實現接口時,知道接受的數據的類型,可以在類裏面直接使用;
class InterImpl1 implements Inter<String>{
	public void show(String str){ System.out.println("show: "+str);}
}
// 實現接口時,不知道傳入的數據的類型,需要在類上定義泛型;
class InterImpl2<T> implements Inter<T>{ // 類定義的泛型要和接口定義的泛型一樣;
	public void show(T t){System.out.println("show: "+t); }
}
public class test {
	public static void main(String[] args) {
		 InterImpl1 i1 = new InterImpl1();
		 i1.show("abs");
		 InterImpl2<Integer> i2 = new InterImpl2<Integer>();
		 i2.show(5);
	}
}

~ 泛型限定

1、泛型的通配符: ? ,指的是 未知類型;

2、泛型的限定:
(1)上限 ? extends E:接收E類型或者E的子類型對象;
上限一般存儲對象的時候用,比如 添加元素 addAll;

(2)下限 ? super E:接收E類型或者E的父類型對象;
下限一般取出對象的時候用,比如比較器;

3、泛型的限定:單獨使用<?>,表示接受任意類型的數據;

public class test {
	public static void main(String[] args) {
		 ArrayList<String> al1 = new ArrayList<String>();
		 al1.add("abc");
		 al1.add("def");
		 printCollection1(al1);
		 ArrayList<Integer> al2 = new ArrayList<Integer>();
		 al2.add(1);
		 al2.add(2);
		 printCollection2(al2);
	}
	// 迭代 並打印集合中的元素:兩種迭代器方法差不多,第一種更常用,寫法簡單;
	public static void printCollection1(Collection<?> coll) {
		Iterator<?> it = coll.iterator();
		while(it.hasNext()){ System.out.println(it.next().toString()); }
	}
	public static <T> void printCollection2(Collection<T> coll) {
		Iterator<T> it = coll.iterator();
		while(it.hasNext()) {
			T t = it.next();
			System.out.println(t);
		}
	}
}

4、泛型的限定:上、下限;

class Person{}
class Student extends Person{}
class Worker extends Person{}
public class test {
	public static void main(String[] args) {
		ArrayList<Person> al = new ArrayList<Person>();		
		al.add(new Person("abc1",30));
		al.add(new Person("abc2",34));		
		ArrayList<Student> al2 = new ArrayList<Student>();		
		al2.add(new Student("stu1",11));
		al2.add(new Student("stu2",22));
		ArrayList<String> al3 = new ArrayList<String>();	
		al3.add("stu3331");
		al3.add("stu33332");	
		printCollection1(al2);
		printCollection2(al);
	}
	// 當想讓方法接收Person類及其子類 類型的數據時,可以使用上線進行泛型限定;
	// <Person>:Person是一個單獨的類型,Worker和Student也是一個單獨的類型,這個表示Collection中只能存儲Person的對象;
	//           相當於 Collection<Person> al = new ArrayList<Student>(); 左右兩邊的泛型類型不匹配;
	// <?>:表示可以接收任意類型的數據;
	// <? extends Person>:接收Person及其子類 類型的數據;既不想只操作一個,也不想操作所有;
	// 只能傳入Person類及其子類Worker/Student,不能傳入String類;
	public static void printCollection1(Collection<? extends Person> al){
		Iterator<? extends Person> it = al.iterator();
		while(it.hasNext()) {
			Person p = it.next();
			System.out.println(p.getName()+":"+p.getAge());
		}
	}
	// 只能傳入Student或其父類Person,不能傳入Worker或String類型;
	public static void printCollection2(Collection<? super Student> al){
		Iterator<? super Student> it = al.iterator();
		while(it.hasNext()){ System.out.println(it.next()); }
	}
}

5、泛型的上、下限體現:
一般在存儲元素的時候都是用上限,因爲這樣取出都是按照上限類型來運算的,不會出現類型安全隱患;
通常對集合中的元素進行取出操作時,可以是用下限:取出元素可以用父類型來接收;

public class test {
	public static void main(String[] args) {
		ArrayList<Person> al1 = new ArrayList<Person>();		
		al1.add(new Person("person1",30));
		al1.add(new Person("person2",34));		
		ArrayList<Student> al2 = new ArrayList<Student>();		
		al2.add(new Student("student1",11));
		al2.add(new Student("student2",22));
		ArrayList<Worker> al3 = new ArrayList<Worker>();
		al3.add(new Worker("worker1",12));
		al3.add(new Worker("worker2",13));
		ArrayList<String> al4 = new ArrayList<String>();	
		al4.add("stu3331");
		al4.add("stu33332");	
	}
	 
}
// 類裏面加入的元素不確定的情況下,使用E進行泛型的限定:類裏面可以存入任意類型的數據;
// al1不定義泛型時,可以接收al4類型的數據,但是取的時候有問題:al4的數據是按照Person類型還是String型?
// 若創建al1時限定<person>,這樣addAll方法添加al4集合整體時,也會限定al4集合中的存入的數據類型是Person類,
// 否則就會報錯:類型不匹配;這樣al1集合中存儲的數據就都是Person類型的類;
// 但是,若al1集合中添加入Student類型的數據,取元素時可以將Student類型的數據按照Person類型的數據取出,
// 取出元素時不存在安全隱患,所以al1集合可以接受Student類型的數據;
// 所以addAll方法接收數據的類型可以限定爲E及其子類:<? extends E>
// 一般存儲元素時,使用上限,因爲這樣取出都是按照上限類型來運算的,不會出現類型安全隱患;
class MyCollection<E>{ // 
	public void add(E e) {}
	// 類定義時加入泛型,這裏方法傳入集合類型的參數時也要加入相同的泛型限定;
	public void addAll(MyCollection<? extends E> e) {}
}
public class GenericAdvanceDemo4 {
	public static void main(String[] args) {
	①	TreeSet<Person> al1 = new TreeSet<Person>(new CompByName());	
		al1.add(new Person("abc4",34));
		al1.add(new Person("abc1",30));	
	②	TreeSet<Student> al2 = new TreeSet<Student>(new CompByName())	
		al2.add(new Student("stu1",11));
		al2.add(new Student("stu7",20));
	③	TreeSet<Worker> al3 = new TreeSet<Worker>();	
		al3.add(new Worker("person1",11));
		al3.add(new Worker("person2",22));			
//		al1.addAll(al2);   //存到al1以後,存儲爲Person型
//		al1.addAll(al3);
		Iterator<Student> it = al2.iterator();
		while(it.hasNext()){ System.out.println(it.next()); }	
	}
}
class TreeSet<Worker>{ Tree(Comparator<? super Worker> comp);   }
class CompByName implements Comparator<Person>{
	public int compare(Person o1, Person o2) {	
		int temp = o1.getName().compareTo(o2.getName());	
		return temp==0? o1.getAge()-o2.getAge():temp;
	}	
}
class CompByStuName implements Comparator<Student>{
	public int compare(Student o1, Student o2) {		
		int temp = o1.getName().compareTo(o2.getName());		
		return temp==0? o1.getAge()-o2.getAge():temp;
	}	
}

6、泛型的通配符?的體現:

public class test {
	public static void main(String[] args) {
		ArrayList<Person> al1 = new ArrayList<Person>();	
		al1.add(new Person("abc",30));
		al1.add(new Person("abc4",34));
	 	ArrayList<Person> al2 = new ArrayList<Person>();	
		al2.add(new Person("abc22222",30));
		al2.add(new Person("abc42222",34));		
	 	ArrayList<String> al4 = new ArrayList<String>();
		al4.add("abcdeef");	
		al4.add("abc");	
		// al1集合和al2集合中存儲的數據類型一致,可以判斷al1集合中是否包含al2集合中的元素;
		al1.containsAll(al2); 
        // al1集合和al2集合中存儲的數據類型不一樣,同樣可以判斷al1集合中是否包含al4集合中的元素;
		al1.containsAll(al4);	
	}
	public static void printCollection(Collection<?> al){
		Iterator<?> it = al.iterator();	
		while(it.hasNext()){			
			System.out.println(it.next().toString());
		}
	}
}
// containsAll原理是使用equals做判斷,而equals方法任何對象都具備,equals的參數是Object,
// 可以接收任意類型的數據,所以字符串可以跟對象進行比較: "abc".equals(new Person(“ahahah",20));
// 所以containsAll比較一個集合裏是否包含另一個集合裏的數據時,兩個集合中存儲的數據類型不用保持一致,
// 所以Collection<?>接收的數據類型不確定,可以用?來限定;
class MyCollection<E>{
	public boolean containsAll(Collection<?> coll){	return true;}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章