「收藏」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文件)。如果沒有找到就會報錯!CLASSPATH有點像c/c++編譯器中的INCLUDE路徑的設置哦,是不是?當c/c++編譯器遇到include 這樣的語句,它是如何運作的?哦,其實道理都差不多!搜索INCLUDE路徑,檢視文件!當你自己開發一個package時,然後想要用這個package中的類;自然,你也得把這個package所在的目錄設置到CLASSPATH中去!CLASSPATH的設定,對JAVA的初學者而言是一件棘手的事。所以Sun讓JAVA2的JDK更聰明一些。你會發現,在你安裝之後,即使完全沒有設定CLASSPATH,你仍然能夠編譯基本的JAVA程序,並且加以執行。

PATH環境變量

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函數.

標示符:

1),數字不可以開頭。

2),不可以使用關鍵字。

變量的作用域和生存期:

1. 變量的作用域:作用域從變量定義的位置開始,到該變量所在的那對大括號結束;

生命週期:變量從定義的位置開始就在內存中活了;

變量到達它所在的作用域的時候就在內存中消失了;

 

數據類型:

1):基本數據類型:byte、short、int、long、float、double、char、boolean

 

 

運算符號:

4)、邏輯運算符。

& | ^ ! && ||

邏輯運算符除了 ! 外都是用於連接兩個boolean類型表達式。

&: 只有兩邊都爲true結果是true。否則就是false。

|:只要兩邊都爲false結果是false,否則就是true

^:異或:和或有點不一樣。

兩邊結果一樣,就爲false。

兩邊結果不一樣,就爲true.

& 和 &&區別: & :無論左邊結果是什麼,右邊都參與運算。

&&:短路與,如果左邊爲false,那麼右邊不參數與運算。

| 和|| 區別:|:兩邊都運算。

||:短路或,如果左邊爲true,那麼右邊不參與運算。

5)、位運算符:用於操作二進制位的運算符。

& | ^

<< >> >>>(無符號右移)

練習:對兩個變量的數據進行互換。不需要第三方變量。

int a = 3,b = 5;-->b = 3,a = 5;

方法一:

a = a + b; a = 8;

b = a - b; b = 3;

a = a - b; a = 5;

方法二:

a = a ^ b;//

b = a ^ b;//b = a ^ b ^ b = a

a = a ^ b;//a = a ^ b ^ a = b;

練習:高效的算出 2*8 = 2<<3;

重載的定義是:在一個類中,如果出現了兩個或者兩個以上的同名函數,只要它們的參數的個數,或者參數的類型不同,即可稱之爲該函數重載了。

如何區分重載:當函數同名時,只看參數列表。和返回值類型沒關係。

重寫:父類與子類之間的多態性,對父類的函數進行重新定義。如果在子類中定義某方法與其父類有相同的名稱和參數,我們說該方法被重寫 (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 塊。

1. synchronized 方法:通過在方法聲明中加入 synchronized關鍵字來聲明 synchronized 方法。如:

public synchronized void accessVal(int newVal);

synchronized 方法控制對類成員變量的訪問:每個類實例對應一把鎖,每個 synchronized 方法都必須獲得調用該方法的類實例的鎖方能執行,否則所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可執行狀態。這種機制確保了同一時刻對於每一個類實例,其所有聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態(因爲至多隻有一個能夠獲得該類實例對應的鎖),從而有效避免了類成員變量的訪問衝突(只要所有可能訪問類成員變量的方法均被聲明爲 synchronized)。

在 Java 中,不光是類實例,每一個類也對應一把鎖,這樣我們也可將類的靜態成員函數聲明爲 synchronized ,以控制其對類的靜態成員變量的訪問。

synchronized 方法的缺陷:若將一個大的方法聲明爲synchronized 將會大大影響效率,典型地,若將線程類的方法 run() 聲明爲synchronized ,由於在線程的整個生命期內它一直在運行,因此將導致它對本類任何 synchronized 方法的調用都永遠不會成功。當然我們可以通過將訪問類成員變量的代碼放到專門的方法中,將其聲明爲 synchronized ,並在主方法中調用來解決這一問題,但是 Java 爲我們提供了更好的解決辦法,那就是 synchronized 塊。

2. 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);

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章