工做了幾年以後才發現,越是資深的JAVA工程師,應該越注重java基礎知識,好比,整天和SpringBOOT、maven打交道,常常用apache提供的StringUtil類操做字符串,還有必要關心「String類爲何是final」這樣的問題,這是確定的哈。把基礎夯實了,纔不至於空中樓閣。java
對於應屆生來書基礎知識就更重要了,這決定你是否值得公司培養你,至於項目經驗之類的若是有固然更好,沒有也沒必要苛求,畢竟絕大部分剛畢業的學生哪來的什麼項目經驗,基礎紮實纔是王道。c++
我的整理了一些資料,有須要的朋友能夠直接點擊領取。程序員
好了,話很少說,坐穩扶好,發車嘍!面試
1,JDK:Java Development Kitjava的開發和運行環境,java的開發工具和jre。算法
2,JRE:Java Runtime Environmentjava程序的運行環境,java運行的所需的類庫+JVM(java虛擬機)。apache
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函數.
2),不可使用關鍵字。
變量的做用域和生存期:
變量的做用域:
做用域從變量定義的位置開始,到該變量所在的那對大括號結束;
生命週期:
變量從定義的位置開始就在內存中活了;
變量到達它所在的做用域的時候就在內存中消失了;
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虛擬機所管理的內存將會包括如下幾個運行時數據區域,以下圖所示:
1. 程序計數器
程序計數器(Program Counter Register)是一塊較小的內存空間,它的做用能夠看作是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型裏(僅是概念模型,各類虛擬機可能會經過一些更高效的方式去實現),字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。 因爲Java虛擬機的多線程是經過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個肯定的時刻,一個處理器(對於多核處理器來講是一個內核)只會執行一條線程中的指令。所以,爲了線程切換後能恢復到正確的執行位置,每條線程都須要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,咱們稱這類內存區域爲"線程私有"的內存。 若是線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;若是正在執行的是Natvie方法,這個計數器值則爲空(Undefined)。此內存區域是惟一一個在**Java**虛擬機規範中沒有規定任何OutOfMemoryError狀況的區域。
1. 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 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:★★★ 關鍵字,是一個修飾符,用於修飾成員(成員變量和成員函數)。
特色:
一、static變量
按照是否靜態的對類成員變量進行分類可分兩種:一種是被static修飾的變量,叫靜態變量或類變量;另外一種是沒有被static修飾的變量,叫實例變量。二者的區別是:
對於靜態變量在內存中只有一個拷貝(節省內存),JVM只爲靜態分配一次內存,在加載類的過程當中完成靜態變量的內存分配,可用類名直接訪問(方便),固然也能夠經過對象來訪問(可是這是不推薦的)。
對於實例變量,沒建立一個實例,就會爲實例變量分配一次內存,實例變量能夠在內存中有多個拷貝,互不影響(靈活)。
二、靜態方法
靜態方法能夠直接經過類名調用,任何的實例也均可以調用,所以靜態方法中不能用this和super關鍵字,不能直接訪問所屬類的實例變量和實例方法(就是不帶static的成員變量和成員成員方法),只能訪問所屬類的靜態成員變量和成員方法。由於實例成員與特定的對象關聯!這個須要去理解,想明白其中的道理,不是記憶!!!
由於static方法獨立於任何實例,所以static方法必須被實現,而不能是抽象的abstract。
三、static代碼塊
static代碼塊也叫靜態代碼塊,是在類中獨立於類成員的static語句塊,能夠有多個,位置能夠隨便放,它不在任何的方法體內,JVM加載類時會執行這些靜態的代碼塊,若是static代碼塊有多個,JVM將按照它們在類中出現的前後順序依次執行它們,每一個代碼塊只會被執行一次。
四、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類型的。
一、final類
final類不能被繼承,所以final類的成員方法沒有機會被覆蓋,默認都是final的。在設計類時候,若是這個類不須要有子類,類的實現細節不容許改變,而且確信這個類不會載被擴展,那麼就設計爲final類。
二、final方法
若是一個類不容許其子類覆蓋某個方法,則能夠把這個方法聲明爲final方法。
使用final方法的緣由有二:
第1、把方法鎖定,防止任何繼承類修改它的意義和實現。
第2、高效。編譯器在遇到調用final方法時候會轉入內嵌機制,大大提升執行效率。
三、final變量(常量)
用final修飾的成員變量表示常量,值一旦給定就沒法改變!
final修飾的變量有三種:靜態變量、實例變量和局部變量,分別表示三種類型的常量。
從下面的例子中能夠看出,一旦給final變量初值後,值就不能再改變了。
另外,final變量定義的時候,能夠先聲明,而不給初值,這中變量也稱爲final空白,不管什麼狀況,編譯器都確保空白final在使用以前必須被初始化。可是,final空白在final關鍵字final的使用上提供了更大的靈活性,爲此,一個類中的final數據成員就能夠實現依對象而有所不一樣,卻有保持其恆定不變的特徵。
四、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認爲全部的對象都具有一些基本的共性內容,這些內容能夠不斷的向上抽取,最終就抽取到了一個最頂層的類中的,該類中定義的就是全部對象都具有的功能。
具體方法:
1. 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 異常~~)。
處理方式有兩種:一、捕捉;二、拋出。
對於捕捉: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關鍵字(一)
1、當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程獲得執行。另外一個線程必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊。
2、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另外一個線程仍然能夠訪問該object中的非synchronized(this)同步代碼塊。
3、尤爲關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其餘線程對object中全部其它synchronized(this)同步代碼塊的訪問將被阻塞。
4、第三個例子一樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就得到了這個object的對象鎖。結果,其它線程對該object對象全部同步代碼部分的訪問都被暫時阻塞。
5、以上規則對其它對象鎖一樣適用.
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)的一些理解
1、當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程獲得執行。另外一個線程必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊。
2、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另外一個線程仍然能夠訪問該object中的非synchronized(this)同步代碼塊。
3、尤爲關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其餘線程對object中全部其它synchronized(this)同步代碼塊的訪問將被阻塞。
4、第三個例子一樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就得到了這個object的對象鎖。結果,其它線程對該object對象全部同步代碼部分的訪問都被暫時阻塞。
5、以上規則對其它對象鎖一樣適用。
解決安全問題的原理:
只要將操做共享數據的語句在某一時段讓一個線程執行完,在執行過程當中,其餘線程不能進來執行就能夠解決這個問題。
如何保障共享數據的線程安全呢?
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();
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類中的功能。那麼能夠用 ? 通配符來表未知類型。
反射技術:其實就是動態加載一個指定的類,並獲取該類中的全部的內容。並將字節碼文件中的內容都封裝成對象,這樣便於操做這些成員。簡單說:反射技術能夠對一個類進行解剖。
反射的好處:大大的加強了程序的擴展性。
反射的基本步驟:
一、得到Class對象,就是獲取到指定的名稱的字節碼文件對象。
二、實例化對象,得到類的屬性、方法或構造函數。
三、訪問屬性、調用方法、調用構造函數建立對象。
獲取這個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); }
寫到這裏差很少就結束了,因爲篇幅所限確定也是有些東西還沒寫到的,感興趣的能夠看一下我整理的
撤了xdm,下篇文章見,固然若是能夠點個贊加個關注那真是感激涕零