文章目錄
- **>> 基礎**
- **~ 內存的劃分**
- **~ 交互方式**
- **~ JAVA靠語言的三種技術架構**
- **~ Java語言特點 - 跨平臺性 - JVM**
- **~ JAVA環境配置—系統變量**
- **~ cmd運行`.java`文件**
- **~ Java編碼**
- **~ Java 基礎**
- **~ Java語言基礎組成**
- **~ 自動類型提升與強制類型轉換**
- **~ 程序的流程控制**
- **>> 函數**
- **>> 數組**
- **>> 面向對象**
- **~ 面向過程與面向對象**
- **~ 類與對象的關係**
- **~ 匿名對象**
- **~ 面向對象特徵1 - 封裝**
- **~ 構造函數**
- **~ this關鍵字**
- **~ static關鍵字**
- **~ 靜態代碼塊、構造代碼塊、局部代碼塊區別**
- **~ 成員變量和局部變量的區別**
- **~ 成員變量(實例變量)和靜態變量(類變量)的區別**
- **~ 單例設計模式**
- **~ 面向對象特徵2 - 繼承**
- **~ 一個對象實例化過程**
- **~ final(最終)關鍵字**
- **~ 繼承 - 抽象類**
- **~ 接口**
- **~ 抽象類和接口的異同點**
- **~ 面向對象特徵3 - 多態**
- **~ 內部類**
- **~ 異常**
- **~ Object類**
- **~ 包 package**
- **~ 4種權限**
- **>> 多線程**
- **~ 多線程概述**
- **~ 線程的四種狀態**
- **~ 多線程 - wait和sleep的區別**
- **~ 多線程的創建方法一:繼承Thread類**
- **~ 多線程的創建方法二:實現Runnable接口 - 常用**
- **~ 線程安全問題 - synchronized**
- **~ 同步代碼塊、同步函數、靜態同步函數**
- **~ 多線程下的單例模式**
- **~ 死鎖代碼**
- **~ 線程間通信 - 等待喚醒機制**
- **~ 線程間通信 - 多生產者、多消費者問題**
- **~ 多生產者、多消費者 - JDK5.0新特性 - Lock、Condition**
- **~ 停止線程方式 - 定義標記 - Interrupt**
- **~ 其他方法 - join、setPriority(優先級)**
- **>> 常用對象API**
- **~ String 類**
- **~ StringBuffer 類**
- **~ StringBuilder 類**
- **~ System 類**
- **~ Runtime 類**
- **~ Math 類**
- **~ Date 類**
- **~ Calendar 類**
- **>> 集合框架**
- **~ 基礎 概述**
- **~ List 列表集合**
- **~ LinkedList 集合**
- **~ ArrayList 集合 - 存儲自定對象**
- **~ Set 集合 - HashSet、TreeSet**
- **~ HashSet 集合 - 存儲自定對象**
- **~ LinkedHashSet 集合**
- **~ TreeSet 集合**
- **~ List列表與Set集合選擇**
- **~ Map 集合**
- **~ HashMap - 存儲自定義對象**
- **~ TreeMap - 存儲自定義對象**
- **~ LinkedHashMap**
- **~ Map 集合練習**
- **~ 工具類 Collections**
- **~ 工具類 Arrays - 數組轉成集合**
- **~ 工具類 Collection的toArray方法 - 集合轉成數組**
- **~ foreach - 高級for循環**
- **~ 函數可變參數**
- **>> 泛型**
>> 基礎
~ 內存的劃分
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:執行語句;break;
default:執行語句;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++}
for(int 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)可拋性:是通過兩個關鍵字來體現的:throws
、throw
;凡是可以被這兩個關鍵字所操作的類和對象都具備可拋性;
2、異常體系: 以前正常流程代碼和問題處理代碼相結合,現在將正常流程代碼和問題處理代碼分離,提高閱讀性;所以最終問題(不正常情況)就分成了兩大類:Error
,Exception
,都繼承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.class
,this.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)顛倒reverse:sb.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;}
}