一:java概述:
1,JDK:Java Development Kit,java的開發和執行環境,java的開發工具和jre。
2,JRE:Java Runtime Environment,java程式的執行環境,java執行的所需的類庫+JVM(java虛擬機器)。
3,配置環境變數:讓java jdk\bin目錄下的工具,可以在任意目錄下執行,原因是,將該工具所在目錄告訴了系統,當使用該工具時,由系統幫我們去找指定的目錄。
環境變數的配置:
1):永久配置方式:JAVA_HOME=%安裝路徑%\Java\jdk
path=%JAVA_HOME%\bin
2):臨時配置方式:set path=%path%;C:\Program Files\Java\jdk\bin
特點:系統預設先去當前路徑下找要執行的程式,如果沒有,再去path中設定的路徑下找。
classpath的配置:
1):永久配置方式:classpath=.;c:;e:\
2):臨時配置方式:set classpath=.;c:\;e:\
注意:在定義classpath環境變數時,需要注意:
如果沒有定義環境變數classpath,java啟動jvm後,會在當前目錄下查詢要執行的類檔案;
如果指定了classpath,那麼會在指定的目錄下查詢要執行的類檔案。
還會在當前目錄找嗎?兩種情況:
CLASSPATH是什麼?它的作用是什麼?
它是javac編譯器的一個環境變數。它的作用與import、package關鍵字有關。當你寫下improt java.util.*時,編譯器面對import關鍵字時,就知道你要引入java.util這個
package中的類;但是編譯器如何知道你把這個package放在哪裡了呢?所以你首先得告訴編譯器這個package的所在位置;如何告訴它呢?就是設定CLASSPATH啦 ? 如果
java.util這個package在c:/jdk/ 目錄下,你得把c:/jdk/這個路徑設定到CLASSPATH中去!當編譯器面對import java.util.*這個語句時,它先會查詢CLASSPATH所指定的目
錄,並檢視子目錄java/util是否存在,然後找出名稱吻合的已編譯檔案(.class檔案)。如果沒有找到就會報錯!
PATH環境變數:
作用是指定命令搜尋路徑,在命令列下面執行命令如javac編譯java程式時,它會到PATH變數所指定的路徑中查詢看是否能找到相應的命令程式。我們需要把jdk安裝目錄下的bin
目錄增加到現有的PATH變數中,bin目錄中包含經常要用到的可執行檔案如javac/java/javadoc等待,設定好PATH變數後,就可以在任何目錄下執行javac/java等工具了。
4,javac命令和java命令做什麼事情呢?
java是分兩部分的:一個是編譯,一個是執行。
javac:負責的是編譯的部分,當執行javac時,會啟動java的編譯器程式。對指定副檔名的.java檔案進行編譯。 生成了jvm可以識別的位元組碼檔案。也就是class檔案,也就是java的執行程式。
java:負責執行的部分.會啟動jvm.載入執行時所需的類庫,並對class檔案進行執行.
一個檔案要被執行,必須要有一個執行的起始點,這個起始點就是main函式.
二:java語法基礎:
標示符:
1),數字不可以開頭。
2),不可以使用關鍵字。
變數的作用域和生存期
變數的作用域:
作用域從變數定義的位置開始,到該變數所在的那對大括號結束;
生命週期:
變數從定義的位置開始就在記憶體中活了;
變數到達它所在的作用域的時候就在記憶體中消失了;
資料型別:
基本資料型別:byte、short、int、long、float、double、char、boolean
封裝器類:Boolean Byte Character Short Integer Long Float Double Void
運算子號:
邏輯運算子。
& | ^ ! && ||
邏輯運算子除了 ! 外都是用於連線兩個boolean型別表示式。
&: 只有兩邊都為true結果是true。否則就是false。
|:只要兩邊都為false結果是false,否則就是true
^:異或:和或有點不一樣。
兩邊結果一樣,就為false。
兩邊結果不一樣,就為true.
& 和 &&區別: & :無論左邊結果是什麼,右邊都參與運算。
&&:短路與,如果左邊為false,那麼右邊不引數與運算。
| 和|| 區別:|:兩邊都運算。
||:短路或,如果左邊為true,那麼右邊不參與運算。
位運算子:用於操作二進位制位的運算子。
& | ^
<< >> >>>(無符號右移)
過載的定義是:在一個類中,如果出現了兩個或者兩個以上的同名函式,只要它們的引數的個數,或者引數的型別不同,即可稱之為該函式過載了。
如何區分過載:當函式同名時,只看引數列表。和返回值型別沒關係。
重寫:父類與子類之間的多型性,對父類的函式進行重新定義。如果在子類中定義某方法與其父類有相同的名稱和引數,我們說該方法被重寫 (Overriding)。
Java記憶體管理
Java記憶體管理:深入Java記憶體區域
Java與C++之間有一堵由記憶體動態分配和垃圾收集技術所圍成的高牆,牆外面的人想進去,牆裡面的人卻想出來。
概述:
對於從事C和C++程式開發的開發人員來說,在記憶體管理領域,他們既是擁有最高權力的皇帝,又是從事最基礎工作的勞動人民—既擁有每一個物件的"所有權",又擔負著每一個物件生命開始到終結的維護責任。
對於Java程式設計師來說,在虛擬機器的自動記憶體管理機制的幫助下,不再需要為每一個new操作去寫配對的delete/free程式碼,而且不容易出現記憶體洩漏和記憶體溢位問題,看起來由虛擬機器管理記憶體一切都很美好。不過,也正是因為Java程式設計師把記憶體控制的權力交給了Java虛擬機器,一旦出現記憶體洩漏和溢位方面的問題,如果不瞭解虛擬機器是怎樣使用記憶體的,那排查錯誤將會成為一項異常艱難的工作。
執行時資料區域
Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。這些區域都有各自的用途,以及建立和銷燬的時間,有的區域隨著虛擬機器程式的啟動而存在,有些區域則是依賴使用者執行緒的啟動和結束而建立和銷燬。根據《Java虛擬機器規範(第2版)》的規定,Java虛擬機器所管理的記憶體將會包括以下幾個執行時資料區域,如下圖所示:
程式計數器
程式計數器(Program Counter Register)是一塊較小的記憶體空間,它的作用可以看做是當前執行緒所執行的位元組碼的行號指示器。在虛擬機器的概念模型裡(僅是概念模型,各種虛擬機器可能會通過一些更高效的方式去實現),位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成。 由於Java虛擬機器的多執行緒是通過執行緒輪流切換並分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對於多核處理器來說是一個核心)只會執行一條執行緒中的指令。因此,為了執行緒切換後能恢復到正確的執行位置,每條執行緒都需要有一個獨立的程式計數器,各條執行緒之間的計數器互不影響,獨立儲存,我們稱這類記憶體區域為"執行緒私有"的記憶體。 如果執行緒正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機器位元組碼指令的地址;如果正在執行的是Natvie方法,這個計數器值則為空(Undefined)。此記憶體區域是唯一一個在Java虛擬機器規範中沒有規定任何OutOfMemoryError情況的區域。
Java虛擬機器棧
與程式計數器一樣,Java虛擬機器棧(Java Virtual Machine Stacks)也是執行緒私有的,它的生命週期與執行緒相同。虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於儲存區域性變數表、操作棧、動態連結、方法出口等資訊。每一個方法被呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。
經常有人把Java記憶體區分為堆記憶體(Heap)和棧記憶體(Stack),這種分法比較粗糙,Java記憶體區域的劃分實際上遠比這複雜。這種劃分方式的流行只能說明大多數程式設計師最關注的、與物件記憶體分配關係最密切的記憶體區域是這兩塊。其中所指的"堆"在後面會專門講述,而所指的"棧"就是現在講的虛擬機器棧,或者說是虛擬機器棧中的區域性變數表部分。
區域性變數表存放了編譯期可知的各種基本資料型別(boolean、byte、char、short、int、float、long、double)、物件引用(reference型別),它不等同於物件本身,根據不同的虛擬機器實現,它可能是一個指向物件起始地址的引用指標,也可能指向一個代表物件的控制程式碼或者其他與此物件相關的位置)和returnAddress型別(指向了一條位元組碼指令的地址)。
其中64位長度的long和double型別的資料會佔用2個區域性變數空間(Slot),其餘的資料型別只佔用1個。區域性變數表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的區域性變數空間是完全確定的,在方法執行期間不會改變區域性變數表的大小。 在Java虛擬機器規範中,對這個區域規定了兩種異常狀況:如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError異常;如果虛擬機器棧可以動態擴充套件(當前大部分的Java虛擬機器都可動態擴充套件,只不過Java虛擬機器規範中也允許固定長度的虛擬機器棧),當擴充套件時無法申請到足夠的記憶體時會丟擲OutOfMemoryError異常。
本地方法棧
本地方法棧(Native Method Stacks)與虛擬機器棧所發揮的作用是非常相似的,其區別不過是虛擬機器棧為虛擬機器執行Java方法(也就是位元組碼)服務,而本地方法棧則是為虛擬機器使用到的Native方法服務。虛擬機器規範中對本地方法棧中的方法使用的語言、使用方式與資料結構並沒有強制規定,因此具體的虛擬機器可以自由實現它。甚至有的虛擬機器(譬如Sun HotSpot虛擬機器)直接就把本地方法棧和虛擬機器棧合二為一。與虛擬機器棧一樣,本地方法棧區域也會丟擲StackOverflowError和OutOfMemoryError異常。
Java堆
對於大多數應用來說,Java堆(Java Heap)是Java虛擬機器所管理的記憶體中最大的一塊。Java堆是被所有執行緒共享的一塊記憶體區域,在虛擬機器啟動時建立。此記憶體區域的唯一目的就是存放物件例項,幾乎所有的物件例項都在這裡分配記憶體。這一點在Java虛擬機器規範中的描述是:所有的物件例項以及陣列都要在堆上分配,但是隨著JIT編譯器的發展與逃逸分析技術的逐漸成熟,棧上分配、標量替換優化技術將會導致一些微妙的變化發生,所有的物件都分配在堆上也漸漸變得不是那麼"絕對"了。
Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱做"GC堆"(Garbage Collected Heap,幸好國內沒翻譯成"垃圾堆")。如果從記憶體回收的角度看,由於現在收集器基本都是採用的分代收集演算法,所以Java堆中還可以細分為:新生代和老年代;再細緻一點的有Eden空間、From Survivor空間、To Survivor空間等。如果從記憶體分配的角度看,執行緒共享的Java堆中可能劃分出多個執行緒私有的分配緩衝區(Thread Local Allocation Buffer,TLAB)。不過,無論如何劃分,都與存放內容無關,無論哪個區域,儲存的都仍然是物件例項,進一步劃分的目的是為了更好地回收記憶體,或者更快地分配記憶體。在本章中,我們僅僅針對記憶體區域的作用進行討論,Java堆中的上述各個區域的分配和回收等細節將會是下一章的主題。
根據Java虛擬機器規範的規定,Java堆可以處於物理上不連續的記憶體空間中,只要邏輯上是連續的即可,就像我們的磁碟空間一樣。在實現時,既可以實現成固定大小的,也可以是可擴充套件的,不過當前主流的虛擬機器都是按照可擴充套件來實現的(通過-Xmx和-Xms控制)。如果在堆中沒有記憶體完成例項分配,並且堆也無法再擴充套件時,將會丟擲OutOfMemoryError異常。
方法區
方法區(Method Area)與Java堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。雖然Java虛擬機器規範把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區分開來。
對於習慣在HotSpot虛擬機器上開發和部署程式的開發者來說,很多人願意把方法區稱為"永久代"Permanent Generation),本質上兩者並不等價,僅僅是因為HotSpot虛擬機器的設計團隊選擇把GC分代收集擴充套件至方法區,或者說使用永久代來實現方法區而已。對於其他虛擬機器(如BEA JRockit、IBM J9等)來說是不存在永久代的概念的。即使是HotSpot虛擬機器本身,根據官方釋出的路線圖資訊,現在也有放棄永久代並"搬家"至Native Memory來實現方法區的規劃了。
Java虛擬機器規範對這個區域的限制非常寬鬆,除了和Java堆一樣不需要連續的記憶體和可以選擇固定大小或者可擴充套件外,還可以選擇不實現垃圾收集。相對而言,垃圾收集行為在這個區域是比較少出現的,但並非資料進入了方法區就如永久代的名字一樣"永久"存在了。這個區域的記憶體回收目標主要是針對常量池的回收和對型別的解除安裝,一般來說這個區域的回收"成績"比較難以令人滿意,尤其是型別的解除安裝,條件相當苛刻,但是這部分割槽域的回收確實是有必要的。在Sun公司的BUG列表中, 曾出現過的若干個嚴重的BUG就是由於低版本的HotSpot虛擬機器對此區域未完全回收而導致記憶體洩漏。根據Java虛擬機器規範的規定,當方法區無法滿足記憶體分配需求時,將丟擲OutOfMemoryError異常。
執行時常量池
執行時常量池(Runtime Constant Pool)是方法區的一部分。Class檔案中除了有類的版本、欄位、方法、介面等描述等資訊外,還有一項資訊是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類載入後存放到方法區的執行時常量池中。 Java虛擬機器對Class檔案的每一部分(自然也包括常量池)的格式都有嚴格的規定,每一個位元組用於儲存哪種資料都必須符合規範上的要求,這樣才會被虛擬機器認可、裝載和執行。但對於執行時常量池,Java虛擬機器規範沒有做任何細節的要求,不同的提供商實現的虛擬機器可以按照自己的需要來實現這個記憶體區域。不過,一般來說,除了儲存Class檔案中描述的符號引用外,還會把翻譯出來的直接引用也儲存在執行時常量池中。執行時常量池相對於Class檔案常量池的另外一個重要特徵是具備動態性,Java語言並不要求常量一定只能在編譯期產生,也就是並非預置入Class檔案中常量池的內容才能進入方法區執行時常量池,執行期間也可能將新的常量放入池中,這種特性被開發人員利用得比較多的便是String類的intern()方法。既然執行時常量池是方法區的一部分,自然會受到方法區記憶體的限制,當常量池無法再申請到記憶體時會丟擲OutOfMemoryError異常。
物件訪問
介紹完Java虛擬機器的執行時資料區之後,我們就可以來探討一個問題:在Java語言中,物件訪問是如何進行的?物件訪問在Java語言中無處不在,是最普通的程式行為,但即使是最簡單的訪問,也會卻涉及Java棧、Java堆、方法區這三個最重要記憶體區域之間的關聯關係,如下面的這句程式碼:
Object obj = new Object();
假設這句程式碼出現在方法體中,那"Object obj"這部分的語義將會反映到Java棧的本地變數表中,作為一個reference型別資料出現。而"new Object()"這部分的語義將會反映到Java堆中,形成一塊儲存了Object型別所有例項資料值(Instance Data,物件中各個例項欄位的資料)的結構化記憶體,根據具體型別以及虛擬機器實現的物件記憶體佈局(Object Memory Layout)的不同,這塊記憶體的長度是不固定的。另外,在Java堆中還必須包含能查詢到此物件型別資料(如物件型別、父類、實現的介面、方法等)的地址資訊,這些型別資料則儲存在方法區中。
由於reference型別在Java虛擬機器規範裡面只規定了一個指向物件的引用,並沒有定義這個引用應該通過哪種方式去定位,以及訪問到Java堆中的物件的具體位置,因此不同虛擬機器實現的物件訪問方式會有所不同,主流的訪問方式有兩種:使用控制程式碼和直接指標。 如果使用控制程式碼訪問方式,Java堆中將會劃分出一塊記憶體來作為控制程式碼池,reference中儲存的就是物件的控制程式碼地址,而控制程式碼中包含了物件例項資料和型別資料各自的具體地址資訊,如下圖所示:
如果使用的是直接指標訪問方式,Java 堆物件的佈局中就必須考慮如何放置訪問型別資料的相關資訊,reference中直接儲存的就是物件地址,如下圖所示:
這兩種物件的訪問方式各有優勢,使用控制程式碼訪問方式的最大好處就是reference中儲存的是穩定的控制程式碼地址,在物件被移動(垃圾收集時移動物件是非常普遍的行為)時只會改變控制程式碼中的例項資料指標,而reference本身不需要被修改。使用直接指標訪問方式的最大好處就是速度更快,它節省了一次指標定位的時間開銷,由於物件的訪問在Java中非常頻繁,因此這類開銷積少成多後也是一項非常可觀的執行成本。就本書討論的主要虛擬機器Sun HotSpot而言,它是使用第二種方式進行物件訪問的,但從整個軟體開發的範圍來看,各種語言和框架使用控制程式碼來訪問的情況也十分常見。
三:物件導向:
類
匿名物件使用場景:
1:當對方法只進行一次呼叫的時候,可以使用匿名物件。
2:當物件對成員進行多次呼叫時,不能使用匿名物件。必須給物件起名字。
類中怎麼沒有定義主函式呢?
注意:主函式的存在,僅為該類是否需要獨立執行,如果不需要,主函式是不用定義的。
主函式的解釋:保證所在類的獨立執行,是程式的入口,被jvm呼叫。
成員變數和區域性變數的區別:
1:成員變數直接定義在類中。
區域性變數定義在方法中,引數上,語句中。
2:成員變數在這個類中有效。
區域性變數只在自己所屬的大括號內有效,大括號結束,區域性變數失去作用域。
3:成員變數存在於堆記憶體中,隨著物件的產生而存在,消失而消失。
區域性變數存在於棧記憶體中,隨著所屬區域的執行而存在,結束而釋放。
建構函式:用於給物件進行初始化,是給與之對應的物件進行初始化,它具有針對性,函式中的一種。
特點:
1:該函式的名稱和所在類的名稱相同。
2:不需要定義返回值型別。
3:該函式沒有具體的返回值。
記住:**所有物件建立時,都需要初始化才可以使用**。
注意事項:
一個類在定義時,如果沒有定義過建構函式,那麼該類中會自動生成一個空引數的建構函式,為了方便該類建立物件,完成初始化。如果在類中自定義了建構函式,那麼預設的建構函式就沒有了。
一個類中,可以有多個建構函式,因為它們的函式名稱都相同,所以只能通過引數列表來區分。所以,一個類中如果出現多個建構函式。它們的存在是以過載體現的。
構造程式碼塊和建構函式有什麼區別?
構造程式碼塊:是給所有的物件進行初始化,也就是說,所有的物件都會呼叫一個程式碼塊。只要物件一建立。就會呼叫這個程式碼塊。
建構函式:是給與之對應的物件進行初始化。它具有針對性。
執行順序:(優先順序從高到低。)靜態程式碼塊>mian方法>構造程式碼塊>構造方法。其中靜態程式碼塊只執行一次。構造程式碼塊在每次建立物件是都會執行。
靜態程式碼塊的作用:比如我們在呼叫C語言的動態庫時會可把.so檔案放在此處。
構造程式碼塊的功能:(可以把不同構造方法中相同的共性的東西寫在它裡面)。例如:比如不論任何機型的電腦都有開機這個功能,此時我們就可以把這個功能定義在構造程式碼塊內。
Person p = new Person();
建立一個物件都在記憶體中做了什麼事情?
1:先將硬碟上指定位置的Person.class檔案載入進記憶體。
2:執行main方法時,在棧記憶體中開闢了main方法的空間(壓棧-進棧),然後在main方法的棧區分配了一個變數p。
3:在堆記憶體中開闢一個實體空間,分配了一個記憶體首地址值。new
4:在該實體空間中進行屬性的空間分配,並進行了預設初始化。
5:對空間中的屬性進行顯示初始化。
6:進行實體的構造程式碼塊初始化。
7:呼叫該實體對應的建構函式,進行建構函式初始化。()
8:將首地址賦值給p ,p變數就引用了該實體。(指向了該物件)
封 裝(物件導向特徵之一):是指隱藏物件的屬性和實現細節,僅對外提供公共訪問方式。
好處:將變化隔離;便於使用;提高重用性;安全性。
封裝原則:將不需要對外提供的內容都隱藏起來,把屬性都隱藏,提供公共方法對其訪問。
this:代表物件。就是所在函式所屬物件的引用。
this到底代表什麼呢?哪個物件呼叫了this所在的函式,this就代表哪個物件,就是哪個物件的引用。
開發時,什麼時候使用this呢?
在定義功能時,如果該功能內部使用到了呼叫該功能的物件,這時就用this來表示這個物件。
this 還可以用於建構函式間的呼叫。
呼叫格式:this(實際引數);
this物件後面跟上 . 呼叫的是成員屬性和成員方法(一般方法);
this物件後面跟上 () 呼叫的是本類中的對應引數的建構函式。
注意:**用this呼叫建構函式,必須定義在建構函式的第一行。因為建構函式是用於初始化的,所以初始化動作一定要執行。否則編譯失敗。**
static:關鍵字,是一個修飾符,用於修飾成員(成員變數和成員函式)。
特點:
1、static變數
按照是否靜態的對類成員變數進行分類可分兩種:一種是被static修飾的變數,叫靜態變數或類變數;另一種是沒有被static修飾的變數,叫例項變數。兩者的區別是:
對於靜態變數在記憶體中只有一個拷貝(節省記憶體),JVM只為靜態分配一次記憶體,在載入類的過程中完成靜態變數的記憶體分配,可用類名直接訪問(方便),當然也可以通過物件來訪問(但是這是不推薦的)。
對於例項變數,沒建立一個例項,就會為例項變數分配一次記憶體,例項變數可以在記憶體中有多個拷貝,互不影響(靈活)。
2、靜態方法
靜態方法可以直接通過類名呼叫,任何的例項也都可以呼叫,因此靜態方法中不能用this和super關鍵字,不能直接訪問所屬類的例項變數和例項方法(就是不帶static的成員變數和成員成員方法),只能訪問所屬類的靜態成員變數和成員方法。因為例項成員與特定的物件關聯!這個需要去理解,想明白其中的道理,不是記憶!!!
因為static方法獨立於任何例項,因此static方法必須被實現,而不能是抽象的abstract。
3、static程式碼塊
static程式碼塊也叫靜態程式碼塊,是在類中獨立於類成員的static語句塊,可以有多個,位置可以隨便放,它不在任何的方法體內,JVM載入類時會執行這些靜態的程式碼塊,如果static程式碼塊有多個,JVM將按照它們在類中出現的先後順序依次執行它們,每個程式碼塊只會被執行一次。
4、static和final一塊用表示什麼
static final用來修飾成員變數和成員方法,可簡單理解為"全域性常量"!
對於變數,表示一旦給值就不可修改,並且通過類名可以訪問。
對於方法,表示不可覆蓋,並且可以通過類名直接訪問。
備註:
1,有些資料是物件特有的資料,是不可以被靜態修飾的。因為那樣的話,特有資料會變成物件的共享資料。這樣對事物的描述就出了問題。所以,在定義靜態時,必須要明確,這個資料是否是被物件所共享的。
2,靜態方法只能訪問靜態成員,不可以訪問非靜態成員。
(這句話是針對同一個類環境下的,比如說,一個類有多個成員(屬性,方法,欄位),靜態方法A,那麼可以訪問同類名下其他靜態成員,如果訪問非靜態成員就不行)
因為靜態方法載入時,優先於物件存在,所以沒有辦法訪問物件中的成員。
3,靜態方法中不能使用this,super關鍵字。
因為this代表物件,而靜態在時,有可能沒有物件,所以this無法使用。
4,主函式是靜態的。
成員變數和靜態變數的區別:
1,成員變數所屬於物件。所以也稱為例項變數。靜態變數所屬於類。所以也稱為類變數。
2,**成員變數存在於堆記憶體中。靜態變數存在於方法區中**。
3,成員變數隨著物件建立而存在。隨著物件被回收而消失。靜態變數隨著類的載入而存在。隨著類的消失而消失。
4,成員變數只能被物件所呼叫 。靜態變數可以被物件呼叫,也可以被類名呼叫。所以,成員變數可以稱為物件的特有資料,靜態變數稱為物件的共享資料。
靜態程式碼塊:就是一個有靜態關鍵字標示的一個程式碼塊區域。定義在類中。
作用:可以完成類的初始化。靜態程式碼塊隨著類的載入而執行,而且只執行一次(new 多個物件就只執行一次)。如果和主函式在同一類中,優先於主函式執行。
final
根據程式上下文環境,Java關鍵字final有"這是無法改變的"或者"終態的"含義,它可以修飾非抽象類、非抽象類成員方法和變數。你可能出於兩種理解而需要阻止改變、設計或效率。
**final類不能被繼承,沒有子類**,final類中的方法預設是final的。
final方法不能被子類的方法覆蓋,但可以被繼承。
final成員變數表示常量,只能被賦值一次,賦值後值不再改變。
final不能用於修飾構造方法。
注意:父類的private成員方法是不能被子類方法覆蓋的,因此private型別的方法預設是final型別的。
1、final類
final類不能被繼承,因此final類的成員方法沒有機會被覆蓋,預設都是final的。在設計類時候,如果這個類不需要有子類,類的實現細節不允許改變,並且確信這個類不會載被擴充套件,那麼就設計為final類。
2、final方法
如果一個類不允許其子類覆蓋某個方法,則可以把這個方法宣告為final方法。
使用final方法的原因有二:
第一、把方法鎖定,防止任何繼承類修改它的意義和實現。
第二、高效。編譯器在遇到呼叫final方法時候會轉入內嵌機制,大大提高執行效率。
3、final變數(常量)
用final修飾的成員變數表示常量,值一旦給定就無法改變!
final修飾的變數有三種:靜態變數、例項變數和區域性變數,分別表示三種型別的常量。
從下面的例子中可以看出,一旦給final變數初值後,值就不能再改變了。
另外,final變數定義的時候,可以先宣告,而不給初值,這中變數也稱為final空白,無論什麼情況,編譯器都確保空白final在使用之前必須被初始化。但是,final空白在final關鍵字final的使用上提供了更大的靈活性,為此,一個類中的final資料成員就可以實現依物件而有所不同,卻有保持其恆定不變的特徵。
4、final引數
當函式引數為final型別時,你可以讀取使用該引數,但是無法改變該引數的值。
生成Java幫助文件:命令格式:javadoc –d 資料夾名 –auther –version *.java
/** //格式
*類描述
*@author 作者名
*@version 版本號
*/
/**
*方法描述
*@param 引數描述
*@return 返回值描述
*/
繼 承(物件導向特徵之一)
java中對於繼承,java只支援單繼承。java雖然不直接支援多繼承,但是可實現多介面。
1:成員變數。
當子父類中出現一樣的屬性時,子類型別的物件,呼叫該屬性,值是子類的屬性值。
如果想要呼叫父類中的屬性值,需要使用一個關鍵字:super
This:代表是本類型別的物件引用。
Super:代表是子類所屬的父類中的記憶體空間引用。
注意:子父類中通常是不會出現同名成員變數的,因為父類中只要定義了,子類就不用在定義了,直接繼承過來用就可以了。
2:成員函式。
當子父類中出現了一模一樣的方法時,建立子類物件會執行子類中的方法。好像父類中的方法被覆蓋掉一樣。所以這種情況,是函式的另一個特性:重寫
3:建構函式。
發現子類建構函式執行時,先執行了父類的建構函式。為什麼呢?
原因:子類的所有建構函式中的第一行,其實都有一條隱身的語句super();
super(): 表示父類的建構函式,並會呼叫於引數相對應的父類中的建構函式。而super():是在呼叫父類中空引數的建構函式。
為什麼子類物件初始化時,都需要呼叫父類中的函式?(為什麼要在子類建構函式的第一行加入這個super()?)
因為子類繼承父類,會繼承到父類中的資料,所以必須要看父類是如何對自己的資料進行初始化的。所以子類在進行物件初始化時,先呼叫父類的建構函式,這就是子類的例項化過程。
注意:
子類中所有的建構函式都會預設訪問父類中的空引數的建構函式,因為每一個子類構造內第一行都有預設的語句super();
**如果父類中沒有空引數的建構函式,那麼子類的建構函式內,必須通過super語句指定要訪問的父類中的建構函式。**
**如果子類建構函式中用this來指定呼叫子類自己的建構函式,那麼被呼叫的建構函式也一樣會訪問父類中的建構函式。**
問題:
super()和this()是否可以同時出現的建構函式中?
兩個語句只能有一個定義在第一行,所以只能出現其中一個。
super()或者this():為什麼一定要定義在第一行?
因為super()或者this()都是呼叫建構函式,建構函式用於初始化,所以初始化的動作要先完成。
在方法覆蓋時,注意兩點:
1:子類覆蓋父類時,必須要保證,子類方法的許可權必須大於等於父類方法許可權可以實現繼承。否則,編譯失敗。(舉個例子,在父類中是public的方法,如果子類中將其降低訪問許可權為private,那麼子類中重寫以後的方法對於外部物件就不可訪問了,這個就破壞了繼承的含義)
2:覆蓋時,要麼都靜態,要麼都不靜態。 (靜態只能覆蓋靜態,或者被靜態覆蓋)
繼承的一個弊端:破壞了封裝性。對於一些類,或者類中功能,是需要被繼承,或者複寫的。
這時如何解決問題呢?介紹一個關鍵字,final。
final特點:
1:這個關鍵字是一個修飾符,可以修飾類,方法,變數。
2:被final修飾的類是一個最終類,不可以被繼承。
3:被final修飾的方法是一個最終方法,不可以被覆蓋。
4:被final修飾的變數是一個常量,只能賦值一次。
抽象類: abstract
抽象類的特點:
1:抽象方法只能定義在抽象類中,抽象類和抽象方法必須由abstract關鍵字修飾(可以描述類和方法,不可以描述變數)。
2:抽象方法只定義方法宣告,並不定義方法實現。
3:抽象類不可以被建立物件(例項化)。
4:只有通過子類繼承抽象類並覆蓋了抽象類中的所有抽象方法後,該子類才可以例項化。否則,該子類還是一個抽象類。
抽象類的細節:
1:抽象類中是否有建構函式?有,用於給子類物件進行初始化。
2:抽象類中是否可以定義非抽象方法?
可以。其實,抽象類和一般類沒有太大的區別,都是在描述事物,只不過抽象類在描述事物時,有些功能不具體。所以抽象類和一般類在定義上,都是需要定義屬性和行為的。只不過,比一般類多了一個抽象函式。而且比一般類少了一個建立物件的部分。
3:抽象關鍵字abstract和哪些不可以共存?final , private , static
4:抽象類中可不可以不定義抽象方法?可以。抽象方法目的僅僅為了不讓該類建立物件。
接 口:
1:是用關鍵字interface定義的。
2:介面中包含的成員,最常見的有全域性常量、抽象方法。
注意:介面中的成員都有固定的修飾符
成員變數:public static final
成員方法:public abstract
interface Inter{
public static final int x = 3;
public abstract void show();
}
3:介面中有抽象方法,說明介面不可以例項化。介面的子類必須實現了介面中所有的抽象方法後,該子類才可以例項化。否則,該子類還是一個抽象類。
4:類與類之間存在著繼承關係,類與介面中間存在的是實現關係。
**繼承用extends ;實現用implements** ;
5:介面和類不一樣的地方,就是,介面可以被多實現,這就是多繼承改良後的結果。java將多繼承機制通過多現實來體現。
6:一個類在繼承另一個類的同時,還可以實現多個介面。所以介面的出現避免了單繼承的侷限性。還可以將類進行功能的擴充套件。
7:其實java中是有多繼承的。介面與介面之間存在著繼承關係,介面可以多繼承介面。
**java類是單繼承**的。classB Extends classA
**java介面可以多繼承**。Interface3 Extends Interface0, Interface1, interface……
不允許類多重繼承的主要原因是,如果A同時繼承B和C,而b和c同時有一個D方法,A如何決定該繼承那一個呢?
但介面不存在這樣的問題,介面全都是抽象方法繼承誰都無所謂,所以介面可以繼承多個介面。
抽象類與介面:
抽象類:一般用於描述一個體系單元,將一組共性內容進行抽取,特點:可以在類中定義抽象內容讓子類實現,可以定義非抽象內容讓子類直接使用。它裡面定義的都是一些體系中的基本內容。
介面:一般用於定義物件的擴充套件功能,是在繼承之外還需這個物件具備的一些功能。
抽象類和介面的共性:都是不斷向上抽取的結果。
抽象類和介面的區別:
1:抽象類只能被繼承,而且只能單繼承。介面需要被實現,而且可以多實現。
2:抽象類中可以定義非抽象方法,子類可以直接繼承使用。介面中都是抽象方法,需要子類去實現。
3:抽象類使用的是 is a 關係。介面使用的 like a 關係。
4:抽象類的成員修飾符可以自定義。介面中的成員修飾符是固定的。全都是public的。
多 態(物件導向特徵之一)
函式本身就具備多型性,某一種事物有不同的具體的體現。
體現:父類引用或者介面的引用指向了自己的子類物件。//Animal a = new Cat();父類可以呼叫子類中覆寫過的(父類中有的方法)
多型的好處:
提高了程式的擴充套件性。繼承的父類或介面一般是類庫中的東西,(如果要修改某個方法的具體實現方式)只有通過子類去覆寫要改變的某一個方法,這樣在通過將父類的應用指向子類的例項去呼叫覆寫過的方法就行了!
多型的弊端:
當父類引用指向子類物件時,雖然提高了擴充套件性,但是隻能訪問父類中具備的方法,不可以訪問子類中特有的方法。(前期不能使用後期產生的功能,即訪問的侷限性)
多型的前提:
1:必須要有關係,比如繼承、或者實現。
2:通常會有覆蓋操作。
如果想用子類物件的特有方法,如何判斷物件是哪個具體的子類型別呢?
可以通過一個關鍵字 instanceof ;//判斷物件是否實現了指定的介面或繼承了指定的類
格式:<物件 instanceof 型別> ,判斷一個物件是否所屬於指定的型別。
Student instanceof Person = true;//student繼承了person類
-------------------------------------------------------------------------------------java.lang.Object
Object:
所有類的直接或者間接父類,Java認為所有的物件都具備一些基本的共性內容,這些內容可以不斷的向上抽取,最終就抽取到了一個最頂層的類中的,該類中定義的就是所有物件都具備的功能。
具體方法:
boolean equals(Object obj):用於比較兩個物件是否相等,其實內部比較的就是兩個物件地址。
2,String toString():將物件變成字串;預設返回的格式:類名@雜湊值 = getClass().getName() + '@' + Integer.toHexString(hashCode())
為了物件對應的字串內容有意義,可以通過複寫,建立該類物件自己特有的字串表現形式。
public String toString(){
return "person : "+age;
}
3,Class getClass():獲取任意物件執行時的所屬位元組碼檔案物件。
4,int hashCode():返回該物件的雜湊碼值。支援此方法是為了提高雜湊表的效能。將該物件的內部地址轉換成一個整數來實現的。
通常equals,toString,hashCode,在應用中都會被複寫,建立具體物件的特有的內容。
內部類:
如果A類需要直接訪問B類中的成員,而B類又需要建立A類的物件。這時,為了方便設計和訪問,直接將A類定義在B類中。就可以了。A類就稱為內部類。內部類可以直接訪問外部類中的成員。而外部類想要訪問內部類,必須要建立內部類的物件。
`class Outer{
int num = 4;
class Inner {
void show(){
System.out.println("inner show run "+num);
}
}
public void method(){
Inner in = new Inner();//建立內部類的物件。
in.show();//呼叫內部類的方法。 //內部類直接訪問外部類成員,用自己的例項物件;
} //外部類訪問內部類要定義內部類的物件;
}`
當內部類定義在外部類中的成員位置上,可以使用一些成員修飾符修飾 private、static。
1:預設修飾符。
直接訪問內部類格式:外部類名.內部類名 變數名 = 外部類物件.內部類物件;
Outer.Inner in = new Outer.new Inner();//這種形式很少用。
但是這種應用不多見,因為內部類之所以定義在內部就是為了封裝。想要獲取內部類物件通常都通過外部類的方法來獲取。這樣可以對內部類物件進行控制。
2:私有修飾符。
通常內部類被封裝,都會被私有化,因為封裝性不讓其他程式直接訪問。
3:靜態修飾符。
如果內部類被靜態修飾,相當於外部類,會出現訪問侷限性,只能訪問外部類中的靜態成員。
注意;如果內部類中定義了靜態成員,那麼該內部類必須是靜態的。
內部類編譯後的檔名為:"外部類名$內部類名.java";
為什麼內部類可以直接訪問外部類中的成員呢?
那是因為內部中都持有一個外部類的引用。這個是引用是 外部類名.this
內部類可以定義在外部類中的成員位置上,也可以定義在外部類中的區域性位置上。當內部類被定義在區域性位置上,只能訪問區域性中被final修飾的區域性變數。
匿名內部類(物件):
沒有名字的內部類。就是內部類的簡化形式。一般只用一次就可以用這種形式。匿名內部類其實就是一個匿名子類物件。想要定義匿名內部類:需要前提,內部類必須繼承一個類或者實現介面。
匿名內部類的格式:new 父類名&介面名(){ 定義子類成員或者覆蓋父類方法 }.方法。
匿名內部類的使用場景:
當函式的引數是介面型別引用時,如果介面中的方法不超過3個。可以通過匿名內部類來完成引數的傳遞。
其實就是在建立匿名內部類時,該類中的封裝的方法不要過多,最好兩個或者兩個以內。
//面試
//1
new Object(){
void show(){
System.out.println("show run");
}
}.show(); //寫法和編譯都沒問題
//2
Object obj = new Object(){
void show(){
System.out.println("show run");
}
};
obj.show(); //寫法正確,編譯會報錯
1和2的寫法正確嗎?有區別嗎?說出原因。
寫法是正確,1和2都是在通過匿名內部類建立一個Object類的子類物件。
區別:
第一個可是編譯通過,並執行。
第二個編譯失敗,因為匿名內部類是一個子類物件,當用Object的obj引用指向時,就被提升為了Object型別,而編譯時會檢查Object類中是否有show方法,此時編譯失敗。
異 常:
--java.lang.Throwable:
Throwable:可丟擲的。
|--Error:錯誤,一般情況下,不編寫針對性的程式碼進行處理,通常是jvm發生的,需要對程式進行修正。
|--Exception:異常,可以有針對性的處理方式
這個體系中的所有類和物件都具備一個獨有的特點;就是可拋性。
可拋性的體現:就是這個體系中的類和物件都可以被throws和throw兩個關鍵字所操作。
throw與throws區別:
throws是用來宣告一個方法可能丟擲的所有異常資訊,而throw則是指丟擲的一個具體的異常型別。此外throws是將異常宣告但是不處理,而是將異常往上傳,誰呼叫我就交給誰處理。
throw用於丟擲異常物件,後面跟的是異常物件;throw用在函式內。
throws用於丟擲異常類,後面跟的異常類名,可以跟多個,用逗號隔開。throws用在函式上。
throws格式:方法名(引數)throws 異常類1,異常類2,.....
throw:就是自己進行異常處理,處理的時候有兩種方式,要麼自己捕獲異常(也就是try catch進行捕捉),要麼宣告丟擲一個異常(就是throws 異常~~)。
處理方式有兩種:1、捕捉;2、丟擲。
對於捕捉:java有針對性的語句塊進行處理。
try {
需要被檢測的程式碼;
}catch(異常類 變數名){
異常處理程式碼;
}fianlly{
一定會執行的程式碼;
}
定義異常處理時,什麼時候定義try,什麼時候定義throws呢?
功能內部如果出現異常,如果內部可以處理,就用try;
如果功能內部處理不了,就必須宣告出來,讓呼叫者處理。使用throws丟擲,交給呼叫者處理。誰呼叫了這個功能誰就是呼叫者;
自定義異常的步驟:
1:定義一個子類繼承Exception或RuntimeException,讓該類具備可拋性(既可以使用throw和throws去呼叫此類)。
2:通過throw 或者throws進行操作。
異常的轉換思想:當出現的異常是呼叫者處理不了的,就需要將此異常轉換為一個呼叫者可以處理的異常丟擲。
try catch finally的幾種結合方式:
1,try-catch-finally
這種情況,如果出現異常,並不處理,但是資源一定關閉,所以try finally集合只為關閉資源。
記住:finally很有用,主要使用者關閉資源。無論是否發生異常,資源都必須進行關閉。
System.exit(0); //退出jvm,只有這種情況finally不執行。
注意:
如果父類或者介面中的方法沒有丟擲過異常,那麼子類是不可以丟擲異常的,如果子類的覆蓋的方法中出現了異常,只能try不能throws。
如果這個異常子類無法處理,已經影響了子類方法的具體運算,這時可以在子類方法中,通過throw丟擲RuntimeException異常或者其子類,這樣,子類的方法上是不需要throws宣告的。
多執行緒:
返回當前執行緒的名稱:Thread.currentThread().getName()
執行緒的名稱是由:Thread-編號定義的。編號從0開始。
執行緒要執行的程式碼都統一存放在了run方法中。
執行緒要執行必須要通過類中指定的方法開啟。start方法。(啟動後,就多了一條執行路徑)
start方法:1)、啟動了執行緒;2)、讓jvm呼叫了run方法。
Thread類中run()和start()方法的區別:
start():用start方法來啟動執行緒,真正實現了多執行緒執行,這時無需等待run方法體程式碼執行完畢而直接繼續執行下面的程式碼。通過呼叫Thread類的start()方法來啟動一個執行緒,這時此執行緒處於就緒(可執行)狀態,並沒有執行,一旦得到cpu時間片,就開始執行run()方法,這裡方法run()稱為執行緒體,它包含了要執行的這個執行緒的內容,Run方法執行結束,此執行緒隨即終止。
run():run()方法只是類的一個普通方法而已,如果直接呼叫Run方法,程式中依然只有主執行緒這一個執行緒,其程式執行路徑還是隻有一條,還是要順序執行,還是要等待run方法體執行完畢後才可繼續執行下面的程式碼,這樣就沒有達到寫執行緒的目的。
總結:start()方法最本質的功能是從CPU中申請另一個執行緒空間來執行 run()方法中的程式碼,它和當前的執行緒是兩條線,在相對獨立的執行緒空間執行,也就是說,如果你直接呼叫執行緒物件的run()方法,當然也會執行,但那是 在當前執行緒中執行,run()方法執行完成後繼續執行下面的程式碼.而呼叫start()方法後,run()方法的程式碼會和當前執行緒併發(單CPU)或並行 (多CPU)執行。所以請記住一句話:呼叫執行緒物件的run方法不會產生一個新的執行緒,雖然可以達到相同的執行結果,但執行過程和執行效率不同
建立執行緒的第一種方式:繼承Thread ,由子類複寫run方法。
步驟:
1,定義類繼承Thread類;
2,目的是複寫run方法,將要讓執行緒執行的程式碼都儲存到run方法中;
3,通過建立Thread類的子類物件,建立執行緒物件;
4,呼叫執行緒的start方法,開啟執行緒,並執行run方法。
執行緒狀態:
被建立:start()
執行:具備執行資格,同時具備執行權;
凍結:sleep(time),wait()—notify()喚醒;執行緒釋放了執行權,同時釋放執行資格;
臨時阻塞狀態:執行緒具備cpu的執行資格,沒有cpu的執行權;
消亡:stop()
建立執行緒的第二種方式:實現一個介面Runnable。
步驟:
1,定義類實現Runnable介面。
2,覆蓋介面中的run方法(用於封裝執行緒要執行的程式碼)。
3,通過Thread類建立執行緒物件;
4,將實現了Runnable介面的子類物件作為實際引數傳遞給Thread類中的建構函式。
為什麼要傳遞呢?因為要讓執行緒物件明確要執行的run方法所屬的物件。
5,呼叫Thread物件的start方法。開啟執行緒,並執行Runnable介面子類中的run方法。
Ticket t = new Ticket();
/*
直接建立Ticket物件,並不是建立執行緒物件。
因為建立物件只能通過new Thread類,或者new Thread類的子類才可以。
所以最終想要建立執行緒。既然沒有了Thread類的子類,就只能用Thread類。
*/
Thread t1 = new Thread(t); //建立執行緒。
/*
只要將t作為Thread類的建構函式的實際引數傳入即可完成執行緒物件和t之間的關聯
為什麼要將t傳給Thread類的建構函式呢?其實就是為了明確執行緒要執行的程式碼run方法。
*/
t1.start();
為什麼要有Runnable介面的出現?
1:通過繼承Thread類的方式,可以完成多執行緒的建立。但是這種方式有一個侷限性,如果一個類已經有了自己的父類,就不可以繼承Thread類,因為java單繼承的侷限性。
可是該類中的還有部分程式碼需要被多個執行緒同時執行。這時怎麼辦呢?
只有對該類進行額外的功能擴充套件,java就提供了一個介面Runnable。這個介面中定義了run方法,其實run方法的定義就是為了儲存多執行緒要執行的程式碼。
所以,通常建立執行緒都用第二種方式。
因為實現Runnable介面可以避免單繼承的侷限性。
2:其實是將不同類中需要被多執行緒執行的程式碼進行抽取。將多執行緒要執行的程式碼的位置單獨定義到介面中。為其他類進行功能擴充套件提供了前提。
所以Thread類在描述執行緒時,內部定義的run方法,也來自於Runnable介面。
實現Runnable介面可以避免單繼承的侷限性。而且,繼承Thread,是可以對Thread類中的方法,進行子類複寫的。但是不需要做這個複寫動作的話,只為定義執行緒程式碼存放位置,實現Runnable介面更方便一些。所以Runnable介面將執行緒要執行的任務封裝成了物件。
//面試
new Thread(new Runnable(){ //匿名
public void run(){
System.out.println("runnable run");
}
})
{
public void run(){
System.out.println("subthread run");
}
}.start(); //結果:subthread run
synchronized關鍵字(一)
一、當兩個併發執行緒訪問同一個物件object中的這個synchronized(this)同步程式碼塊時,一個時間內只能有一個執行緒得到執行。另一個執行緒必須等待當前執行緒執行完這個程式碼塊以後才能執行該程式碼塊。
二、然而,當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,另一個執行緒仍然可以訪問該object中的非synchronized(this)同步程式碼塊。
三、尤其關鍵的是,當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,其他執行緒對object中所有其它synchronized(this)同步程式碼塊的訪問將被阻塞。
四、第三個例子同樣適用其它同步程式碼塊。也就是說,當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,它就獲得了這個object的物件鎖。結果,其它執行緒對該object物件所有同步程式碼部分的訪問都被暫時阻塞。
五、以上規則對其它物件鎖同樣適用.
package ths; public class Thread1 implements Runnable { public void run() { synchronized(this) { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName()+"synchronized loop " + i); } } } }
synchronized關鍵字(二)
synchronized 關鍵字,它包括兩種用法:synchronized 方法和 synchronized 塊。
- synchronized 方法:通過在方法宣告中加入 synchronized關鍵字來宣告 synchronized 方法。如:
public synchronized void accessVal(int newVal);
synchronized 方法控制對類成員變數的訪問:每個類例項對應一把鎖,每個 synchronized 方法都必須獲得呼叫該方法的類例項的鎖方能執行,否則所屬執行緒阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時才將鎖釋放,此後被阻塞的執行緒方能獲得該鎖,重新進入可執行狀態。這種機制確保了同一時刻對於每一個類例項,其所有宣告為 synchronized 的成員函式中至多隻有一個處於可執行狀態(因為至多隻有一個能夠獲得該類例項對應的鎖),從而有效避免了類成員變數的訪問衝突(只要所有可能訪問類成員變數的方法均被宣告為 synchronized)。
在 Java 中,不光是類例項,每一個類也對應一把鎖,這樣我們也可將類的靜態成員函式宣告為 synchronized ,以控制其對類的靜態成員變數的訪問。
synchronized 方法的缺陷:
若將一個大的方法宣告為synchronized 將會大大影響效率,典型地,若將執行緒類的方法 run() 宣告為synchronized ,由於線上程的整個生命期內它一直在執行,因此將導致它對本類任何 synchronized 方法的呼叫都永遠不會成功。當然我們可以通過將訪問類成員變數的程式碼放到專門的方法中,將其宣告為 synchronized ,並在主方法中呼叫來解決這一問題,但是 Java 為我們提供了更好的解決辦法,那就是 synchronized 塊。
- synchronized 塊:通過 synchronized關鍵字來宣告synchronized 塊。語法如下:
synchronized(syncObject) {
//允許訪問控制的程式碼
}
synchronized 塊是這樣一個程式碼塊,其中的程式碼必須獲得物件 syncObject (如前所述,可以是類例項或類)的鎖方能執行,具體機制同前所述。由於可以針對任意程式碼塊,且可任意指定上鎖的物件,故靈活性較高。
對synchronized(this)的一些理解
一、當兩個併發執行緒訪問同一個物件object中的這個synchronized(this)同步程式碼塊時,一個時間內只能有一個執行緒得到執行。另一個執行緒必須等待當前執行緒執行完這個程式碼塊以後才能執行該程式碼塊。
二、然而,當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,另一個執行緒仍然可以訪問該object中的非synchronized(this)同步程式碼塊。
三、尤其關鍵的是,當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,其他執行緒對object中所有其它synchronized(this)同步程式碼塊的訪問將被阻塞。
四、第三個例子同樣適用其它同步程式碼塊。也就是說,當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,它就獲得了這個object的物件鎖。結果,其它執行緒對該object物件所有同步程式碼部分的訪問都被暫時阻塞。
五、以上規則對其它物件鎖同樣適用。
解決安全問題的原理:
只要將操作共享資料的語句在某一時段讓一個執行緒執行完,在執行過程中,其他執行緒不能進來執行就可以解決這個問題。
如何保障共享資料的執行緒安全呢?
java中提供了一個解決方式:就是同步程式碼塊。
格式:
synchronized(物件) { //任意物件都可以。這個物件就是共享資料。
需要被同步的程式碼;
}
同步:
好處:解決了執行緒安全問題。Synchronized
弊端:相對降低效能,因為判斷鎖需要消耗資源,產生了死鎖。
同步的第二種表現形式: //對共享資源的方法定義同步
同步函式:其實就是將同步關鍵字定義在函式上,讓函式具備了同步性。
同步函式是用的哪個鎖呢? //synchronized(this)用以定義需要進行同步的某一部分程式碼塊
通過驗證,函式都有自己所屬的物件this,所以同步函式所使用的鎖就是this鎖。This.方法名
當同步函式被static修飾時,這時的同步用的是哪個鎖呢?
靜態函式在載入時所屬於類,這時有可能還沒有該類產生的物件,但是該類的位元組碼檔案載入進記憶體就已經被封裝成了物件,這個物件就是該類的位元組碼檔案物件。
所以靜態載入時,只有一個物件存在,那麼靜態同步函式就使用的這個物件。
這個物件就是 類名.class
同步程式碼塊和同步函式的區別?
同步程式碼塊使用的鎖可以是任意物件。
同步函式使用的鎖是this,靜態同步函式的鎖是該類的位元組碼檔案物件。
在一個類中只有一個同步的話,可以使用同步函式。如果有多同步,必須使用同步程式碼塊,來確定不同的鎖。所以同步程式碼塊相對靈活一些。
★考點問題:請寫一個延遲載入的單例模式?寫懶漢式;當出現多執行緒訪問時怎麼解決?加同步,解決安全問題;效率高嗎?不高;怎樣解決?通過雙重判斷的形式解決。
//懶漢式:延遲載入方式。
當多執行緒訪問懶漢式時,因為懶漢式的方法內對共性資料進行多條語句的操作。所以容易出現執行緒安全問題。為了解決,加入同步機制,解決安全問題。但是卻帶來了效率降低。
為了效率問題,通過雙重判斷的形式解決。
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){ //鎖是誰?位元組碼檔案物件;
if(s == null){
synchronized(Single.class){
if(s == null)
s = new Single();
}
}
return s;
}
}
等待喚醒機制:涉及的方法:
wait:將同步中的執行緒處於凍結狀態。釋放了執行權,釋放了資格。同時將執行緒物件儲存到執行緒池中。
notify:喚醒執行緒池中某一個等待執行緒。
notifyAll:喚醒的是執行緒池中的所有執行緒。
注意:
1:這些方法都需要定義在同步中。
2:因為這些方法必須要標示所屬的鎖。
你要知道 A鎖上的執行緒被wait了,那這個執行緒就相當於處於A鎖的執行緒池中,只能A鎖的notify喚醒。
3:這三個方法都定義在Object類中。為什麼操作執行緒的方法定義在Object類中?
因為這三個方法都需要定義同步內,並標示所屬的同步鎖,既然被鎖呼叫,而鎖又可以是任意物件,那麼能被任意物件呼叫的方法一定定義在Object類中。
wait和sleep區別: 分析這兩個方法:從執行權和鎖上來分析:
wait:可以指定時間也可以不指定時間。不指定時間,只能由對應的notify或者notifyAll來喚醒。
sleep:必須指定時間,時間到自動從凍結狀態轉成執行狀態(臨時阻塞狀態)。
wait:執行緒會釋放執行權,而且執行緒會釋放鎖。
sleep:執行緒會釋放執行權,但不是不釋放鎖。
執行緒的停止:通過stop方法就可以停止執行緒。但是這個方式過時了。
停止執行緒:原理就是:讓執行緒執行的程式碼結束,也就是結束run方法。
怎麼結束run方法?一般run方法裡肯定定義迴圈。所以只要結束迴圈即可。
第一種方式:定義迴圈的結束標記。
第二種方式:如果執行緒處於了凍結狀態,是不可能讀到標記的,這時就需要通過Thread類中的interrupt方法,將其凍結狀態強制清除。讓執行緒恢復具備執行資格的狀態,讓執行緒可以讀到標記,並結束。
---------< java.lang.Thread >----------
interrupt():中斷執行緒。
setPriority(int newPriority):更改執行緒的優先順序。
getPriority():返回執行緒的優先順序。
toString():返回該執行緒的字串表示形式,包括執行緒名稱、優先順序和執行緒組。
Thread.yield():暫停當前正在執行的執行緒物件,並執行其他執行緒。
setDaemon(true):將該執行緒標記為守護執行緒或使用者執行緒。將該執行緒標記為守護執行緒或使用者執行緒。當正在執行的執行緒都是守護執行緒時,Java 虛擬機器退出。該方法必須在啟動執行緒前呼叫。
join:臨時加入一個執行緒的時候可以使用join方法。
當A執行緒執行到了B執行緒的join方式。A執行緒處於凍結狀態,釋放了執行權,B開始執行。A什麼時候執行呢?只有當B執行緒執行結束後,A才從凍結狀態恢復執行狀態執行。
LOCK的出現替代了同步:lock.lock();………lock.unlock();
Lock介面:多執行緒在JDK1.5版本升級時,推出一個介面Lock介面。
解決執行緒安全問題使用同步的形式,(同步程式碼塊,要麼同步函式)其實最終使用的都是鎖機制。
到了後期版本,直接將鎖封裝成了物件。執行緒進入同步就是具備了鎖,執行完,離開同步,就是釋放了鎖。
在後期對鎖的分析過程中,發現,獲取鎖,或者釋放鎖的動作應該是鎖這個事物更清楚。所以將這些動作定義在了鎖當中,並把鎖定義成物件。
所以同步是隱示的鎖操作,而Lock物件是顯示的鎖操作,它的出現就替代了同步。
在之前的版本中使用Object類中wait、notify、notifyAll的方式來完成的。那是因為同步中的鎖是任意物件,所以操作鎖的等待喚醒的方法都定義在Object類中。
而現在鎖是指定物件Lock。所以查詢等待喚醒機制方式需要通過Lock介面來完成。而Lock介面中並沒有直接操作等待喚醒的方法,而是將這些方式又單獨封裝到了一個物件中。這個物件就是Condition,將Object中的三個方法進行單獨的封裝。並提供了功能一致的方法 await()、signal()、signalAll()體現新版本物件的好處。
< java.util.concurrent.locks > Condition介面:await()、signal()、signalAll();
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
}
finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
}
finally {
lock.unlock();
}
}
}
集合框架,用於儲存資料的容器。
對於集合容器,有很多種。因為每一個容器的自身特點不同,其實原理在於每個容器的內部資料結構不同。
集合容器在不斷向上抽取過程中。出現了集合體系。
在使用一個體系時,原則:參閱頂層內容。建立底層物件。
--< java.util >-- List介面:
List本身是Collection介面的子介面,具備了Collection的所有方法。現在學習List體系特有的共性方法,查閱方法發現List的特有方法都有索引,這是該集合最大的特點。
List:有序(元素存入集合的順序和取出的順序一致),元素都有索引。元素可以重複。
|--ArrayList:底層的資料結構是陣列,執行緒不同步,ArrayList替代了Vector,查詢元素的速度非常快。
|--LinkedList:底層的資料結構是連結串列,執行緒不同步,增刪元素的速度非常快。
|--Vector:底層的資料結構就是陣列,執行緒同步的,Vector無論查詢和增刪都巨慢。
可變長度陣列的原理:
當元素超出陣列長度,會產生一個新陣列,將原陣列的資料複製到新陣列中,再將新的元素新增到新陣列中。
ArrayList:是按照原陣列的50%延長。構造一個初始容量為 10 的空列表。
Vector:是按照原陣列的100%延長。
--< java.util >-- Set介面:
資料結構:資料的儲存方式;
Set介面中的方法和Collection中方法一致的。Set介面取出方式只有一種,迭代器。
|--HashSet:底層資料結構是雜湊表,執行緒是不同步的。無序,高效;
HashSet集合保證元素唯一性:通過元素的hashCode方法,和equals方法完成的。
當元素的hashCode值相同時,才繼續判斷元素的equals是否為true。
如果為true,那麼視為相同元素,不存。如果為false,那麼儲存。
如果hashCode值不同,那麼不判斷equals,從而提高物件比較的速度。
|--LinkedHashSet:有序,hashset的子類。
|--TreeSet:對Set集合中的元素的進行指定順序的排序。不同步。TreeSet底層的資料結構就是二叉樹。
對於ArrayList集合,判斷元素是否存在,或者刪元素底層依據都是equals方法。
對於HashSet集合,判斷元素是否存在,或者刪除元素,底層依據的是hashCode方法和equals方法。
Map集合:
|--Hashtable:底層是雜湊表資料結構,是執行緒同步的。不可以儲存null鍵,null值。
|--HashMap:底層是雜湊表資料結構,是執行緒不同步的。可以儲存null鍵,null值。替代了Hashtable.
|--TreeMap:底層是二叉樹結構,可以對map集合中的鍵進行指定順序的排序。
Map集合儲存和Collection有著很大不同:
Collection一次存一個元素;Map一次存一對元素。
Collection是單列集合;Map是雙列集合。
Map中的儲存的一對元素:一個是鍵,一個是值,鍵與值之間有對應(對映)關係。
特點:要保證map集合中鍵的唯一性。
5,想要獲取map中的所有元素:
原理:map中是沒有迭代器的,collection具備迭代器,只要將map集合轉成Set集合,可以使用迭代器了。之所以轉成set,是因為map集合具備著鍵的唯一性,其實set集合就來自於map,set集合底層其實用的就是map的方法。
把map集合轉成set的方法:
Set keySet();
Set entrySet();//取的是鍵和值的對映關係。
Entry就是Map介面中的內部介面;
為什麼要定義在map內部呢?entry是訪問鍵值關係的入口,是map的入口,訪問的是map中的鍵值對。
取出map集合中所有元素的方式一:keySet()方法。
可以將map集合中的鍵都取出存放到set集合中。對set集合進行迭代。迭代完成,再通過get方法對獲取到的鍵進行值的獲取。
Set keySet = map.keySet();
Iterator it = keySet.iterator();
while(it.hasNext()) {
Object key = it.next();
Object value = map.get(key);
System.out.println(key+":"+value);
}
取出map集合中所有元素的方式二:entrySet()方法。
Set entrySet = map.entrySet();
Iterator it = entrySet.iterator();
while(it.hasNext()) {
Map.Entry me = (Map.Entry)it.next();
System.out.println(me.getKey()+"::::"+me.getValue());
}
將非同步集合轉成同步集合的方法:Collections中的 XXX synchronizedXXX(XXX);
List synchronizedList(list);
Map synchronizedMap(map);
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<K,V>(m);
}
原理:定義一個類,將集合所有的方法加同一把鎖後返回。
List list = Collections.synchronizedList(new ArrayList());
Map<String,String> synmap = Collections.synchronizedMap(map);
Collection 和 Collections的區別:
Collections是個java.util下的類,是針對集合類的一個工具類,提供一系列靜態方法,實現對集合的查詢、排序、替換、執行緒安全化(將非同步的集合轉換成同步的)等操作。
Collection是個java.util下的介面,它是各種集合結構的父介面,繼承於它的介面主要有Set和List,提供了關於集合的一些操作,如插入、刪除、判斷一個元素是否其成員、遍歷等。
自動拆裝箱:java中資料型別分為兩種 : 基本資料型別 引用資料型別(物件)
在 java程式中所有的資料都需要當做物件來處理,針對8種基本資料型別提供了包裝類,如下:
int --> Integer
byte --> Byte
short --> Short
long --> Long
char --> Character
double --> Double
float --> Float
boolean --> Boolean
jdk5以前基本資料型別和包裝類之間需要互轉:
基本---引用 Integer x = new Integer(x);
引用---基本 int num = x.intValue();
1)、Integer x = 1; x = x + 1; 經歷了什麼過程?裝箱 à 拆箱 à 裝箱;
2)、為了優化,虛擬機器為包裝類提供了緩衝池,Integer池的大小 -128~127 一個位元組的大小;
3)、String池:Java為了優化字串操作 提供了一個緩衝池;
泛型:jdk1.5版本以後出現的一個安全機制。表現格式:< >
好處:
1:將執行時期的問題ClassCastException問題轉換成了編譯失敗,體現在編譯時期,程式設計師就可以解決問題。
2:避免了強制轉換的麻煩。
泛型中的萬用字元:可以解決當具體型別不確定的時候,這個萬用字元就是 ? ;當操作型別時,不需要使用型別的具體功能時,只使用Object類中的功能。那麼可以用 ? 萬用字元來表未知型別。
反射技術
反射技術:其實就是動態載入一個指定的類,並獲取該類中的所有的內容。並將位元組碼檔案中的內容都封裝成物件,這樣便於操作這些成員。簡單說:反射技術可以對一個類進行解剖。
反射的好處:大大的增強了程式的擴充套件性。
反射的基本步驟:
1、獲得Class物件,就是獲取到指定的名稱的位元組碼檔案物件。
2、例項化物件,獲得類的屬性、方法或建構函式。
3、訪問屬性、呼叫方法、呼叫建構函式建立物件。
獲取這個Class物件,有三種方式:
1:通過每個物件都具備的方法getClass來獲取。弊端:必須要建立該類物件,才可以呼叫getClass方法。
2:每一個資料型別(基本資料型別和引用資料型別)都有一個靜態的屬性class。弊端:必須要先明確該類。
前兩種方式不利於程式的擴充套件,因為都需要在程式使用具體的類來完成。
3:使用的Class類中的方法,靜態的forName方法。
指定什麼類名,就獲取什麼類位元組碼檔案物件,這種方式的擴充套件性最強,只要將類名的字串傳入即可。
// 1. 根據給定的類名來獲得 用於類載入
String classname = "cn.itcast.reflect.Person";// 來自配置檔案
Class clazz = Class.forName(classname);// 此物件代表Person.class
// 2. 如果拿到了物件,不知道是什麼型別 用於獲得物件的型別
Object obj = new Person();
Class clazz1 = obj.getClass();// 獲得物件具體的型別
// 3. 如果是明確地獲得某個類的Class物件 主要用於傳參
Class clazz2 = Person.class;
反射的用法:
1)、需要獲得java類的各個組成部分,首先需要獲得類的Class物件,獲得Class物件的三種方式:
Class.forName(classname) 用於做類載入
obj.getClass() 用於獲得物件的型別
類名.class 用於獲得指定的型別,傳參用
2)、反射類的成員方法:
Class clazz = Person.class;
Method method = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2});
method.invoke();
3)、反射類的建構函式:
Constructor con = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...})
con.newInstance(params...)
4)、反射類的屬性:
Field field = clazz.getField(fieldName);
field.setAccessible(true);
field.setObject(value);
獲取了位元組碼檔案物件後,最終都需要建立指定類的物件:
建立物件的兩種方式(其實就是物件在進行例項化時的初始化方式):
1,呼叫空引數的建構函式:使用了Class類中的newInstance()方法。
2,呼叫帶引數的建構函式:先要獲取指定引數列表的建構函式物件,然後通過該建構函式的物件的newInstance(實際引數) 進行物件的初始化。
綜上所述,第二種方式,必須要先明確具體的建構函式的引數型別,不便於擴充套件。所以一般情況下,被反射的類,內部通常都會提供一個公有的空引數的建構函式。
// 如何生成獲取到位元組碼檔案物件的例項物件。
Class clazz = Class.forName("cn.itcast.bean.Person");//類載入
// 直接獲得指定的型別
clazz = Person.class;
// 根據物件獲得型別
Object obj = new Person("zhangsan", 19);
clazz = obj.getClass();
Object obj = clazz.newInstance();//該例項化物件的方法呼叫就是指定類中的空引數建構函式,給建立物件進行初始化。當指定類中沒有空引數建構函式時,該如何建立該類物件呢?請看method_2();
public static void method_2() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//既然類中沒有空引數的建構函式,那麼只有獲取指定引數的建構函式,用該函式來進行例項化。
//獲取一個帶引數的構造器。
Constructor constructor = clazz.getConstructor(String.class,int.class);
//想要對物件進行初始化,使用構造器的方法newInstance();
Object obj = constructor.newInstance("zhagnsan",30);
//獲取所有構造器。
Constructor[] constructors = clazz.getConstructors();//只包含公共的
constructors = clazz.getDeclaredConstructors();//包含私有的
for(Constructor con : constructors) {
System.out.println(con);
}
}
反射指定類中的方法:
//獲取類中所有的方法。
public static void method_1() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
Method[] methods = clazz.getMethods();//獲取的是該類中的公有方法和父類中的公有方法。
methods = clazz.getDeclaredMethods();//獲取本類中的方法,包含私有方法。
for(Method method : methods) {
System.out.println(method);
}
}
//獲取指定方法;
public static void method_2() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//獲取指定名稱的方法。
Method method = clazz.getMethod("show", int.class,String.class);
//想要執行指定方法,當然是方法物件最清楚,為了讓方法執行,呼叫方法物件的invoke方法即可,但是方法執行必須要明確所屬的物件和具體的實際引數。
Object obj = clazz.newInstance();
method.invoke(obj, 39,"hehehe");//執行一個方法
}
//想要執行私有方法。
public static void method_3() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//想要獲取私有方法。必須用getDeclearMethod();
Method method = clazz.getDeclaredMethod("method", null);
// 私有方法不能直接訪問,因為許可權不夠。非要訪問,可以通過暴力的方式。
method.setAccessible(true);//一般很少用,因為私有就是隱藏起來,所以儘量不要訪問。
}
//反射靜態方法。
public static void method_4() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
Method method = clazz.getMethod("function",null);
method.invoke(null,null);
}