JavaSE基础复习总结
文章目錄
- 面向對象的三大特征
- 封裝
- 繼承
- 多態
- 多態的實現原理
- 靜態綁定
- 動態綁定
- final關鍵字
- 修飾屬性:
- 修飾方法:
- 修飾類:
- Static關鍵字
- Static和非Static的區別
- 變量
- 方法
- 代碼塊
- 單例模式
- 懶漢
- 餓漢
- 線程安全的懶漢
- 靜態內部類
- 雙重檢查懶漢式
- 訪問限定符
- 重載和重寫的區別
- 重載
- 重寫
- 接口和抽象類的區別
- 接口
- 抽象類
- 接口與抽象類相同點
- 接口與抽象類不同點
- 異常相關
- 常見的異常
- 異常處理機制
- Object常用方法
- equals
- toString
- Clone
- getClass
- HashCode
- wait
- notify
- nottifyAll
- finalize
- 類加載
- 類加載時機
- 類的初始化順序
- 類加載過程
- 加載:
- 鏈接:
- 驗證:
- 準備:
- 解析:
- 初始化:
- 雙親委派模型
- 雙親委派模型的工作過程:
- 使用雙親委派的好處:
- 破壞雙親委派模型
- 第一次破壞:
- 第二次破壞:
- 第三次破壞
- 雙親委派模型被破壞舉例(JDBC)
- 反射
- 什么是反射及反射機制
- 反射代碼實現
- 反射機制的優缺點
- 反射的原理
面向對象的三大特征
封裝
封裝就是隱藏對象的屬性和實現細節,僅對外公開接口,控制在程序中屬性的讀和修改的訪問級別,將抽象得到的數據和行為(或功能)相結合,形成一個有機的整體,也就是將數據與操作數據的源代碼進行有機的結合,形成“類”,其中數據和函數都是類的成員。
繼承
繼承是面向對象的基本特征之一,繼承機制允許創建分等級層次的類。繼承就是子類繼承父類的特征和行為,使得子類對象(實例)具有父類的實例域和方法,或子類從父類繼承方法,使得子類具有父類相同的行為
多態
多態同一個行為具有多個不同表現形式或形態的能力。是指一個類實例(對象)的相同方法在不同情形有不同表現形式。
多態存在的三個必要條件:
多態的實現原理
多態是面向對象編程語言的重要特性,它允許基類的指針或引用指向派生類的對象,而在具體訪問時實現方法的動態綁定。Java 對于方法調用動態綁定的實現主要依賴于方法表,但通過類引用調用(invokevitual)和接口引用調用(invokeinterface)的實現則有所不同。
下面來詳解其過程:
假如有兩個類A、B,在A類中我們調用了B類中的一個靜態方法,在編譯期,這個調用的動作會被編譯成一條靜態調用指令,該指令就對應常量池中的CONSTANT_Methodref表中所存儲的該方法的符號引用,通過這個符號引用可以得到靜態方法的全類名B,JVM加載B類,便會得到B類中方法的直接地址,該地址會被存儲到A類常量池中對應的常量表中,這便是常量池解析過程,再次發起調用時,就會直接根據這個直接地址調用對應方法。 以上過程可以看出,在編譯階段我們就已經確定了應該執行哪一個字節碼代碼。
靜態綁定
Java中的靜態方法、私有方法以及final修飾的方法的調用,都屬于靜態綁定,對于重載的實例方法的調用,也是采用靜態綁定。靜態綁定的原理主要是一個常量池解析的過程,
動態綁定
在運行時根據具體對象的類型進行綁定。
在JVM加載類的同時,會在方法區中為這個類存放很多信息(詳見《Java 虛擬機體系結構 》)。其中就有一個數據結構叫方法表。它以數組的形式記錄了當前類及其所有超類的可見方法字節碼在內存中的直接地址 。
打印的結果大家也都比較清楚,但是JVM是如何知道father.f()調用的是子類Sun中方法而不是Father中的方法呢?這就是運用到了動態綁定
在JVM加載類的同時,會在方法區中為這個類存放很多信息
其中就有一個數據結構叫方法表。它以數組的形式記錄了當前類及其所有超類的可見方法字節碼在內存中的直接地址 。
方法表的特點:
對于上面的源代碼,編譯器首先會把main方法編譯成下面的字節碼指令:
0 new hr.test.Son [13] //在堆中開辟一個Son對象的內存空間,并將對象引用壓入操作數棧 3 dup 4 invokespecial #7 [15] // 調用初始化方法來初始化堆中的Son對象 7 astore_1 //彈出操作數棧的Son對象引用壓入局部變量1中 8 aload_1 //取出局部變量1中的對象引用壓入操作數棧 9 invokevirtual #15 //調用f1()方法 12 return很明顯,根據對象(father)的聲明類型(Father)還不能夠確定調用方法f1的位置,必須根據father在堆中實際創建的對象類型Son來確定f1方法所在的位置。這種在程序運行過程中,通過動態創建的對象的方法表來定位方法的方式,我們叫做動態綁定機制 。
可參考文章
https://www.cnblogs.com/kaleidoscope/p/9790766.html
final關鍵字
修飾屬性:
private final int MAXSIZE = 255;對于一個final變量,如果是基本數據類型的變量,則其數值一旦在初始化之后便不能更改;如果是引用類型的變量,則在對其初始化之后便不能再讓其指向另一個對象。
修飾方法:
public final void add(){//方法邏輯}使用final方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修改它的含義;第二個原因是效率。在早期的Java實現版本中,會將final方法轉為內嵌調用。但是如果方法過于龐大,可能看不到內嵌調用帶來的任何性能提升。在最近的Java版本中,不需要使用final方法進行這些優化了。
**注意:**類的private方法會隱式地被指定為final方法。
修飾類:
public final class App { }當用final修飾一個類時,表明這個類不能被繼承。也就是說,如果一個類你永遠不會讓他被繼承,就可以用final進行修飾。final類中的成員變量可以根據需要設為final,但是要注意final類中的所有成員方法都會被隱式地指定為final方法。
Static關鍵字
static方法就是沒有this的方法。在static方法內部不能調用非靜態方法,反過來是可以的。而且可以在沒有創建任何對象的前提下,僅僅通過類本身來調用static方法。這實際上正是static方法的主要用途。
被static關鍵字修飾的方法或者變量不需要依賴于對象來進行訪問,只要類被加載了,就可以通過類名去進行訪問。
Static和非Static的區別
變量
靜態變量被所有的對象所共享,在內存中只有一個副本,它當且僅當在類初次加載時會被初始化。而非靜態變量是對象所擁有的,在創建對象的時候被初始化,存在多個副本,各個對象擁有的副本互不影響。
static成員變量的初始化順序按照定義的順序進行初始化。
方法
static方法一般稱作靜態方法,由于靜態方法不依賴于任何對象就可以進行訪問,因此對于靜態方法來說,是沒有this的,因為它不依附于任何對象,既然都沒有對象,就談不上this了。并且由于這個特性,在靜態方法中不能訪問類的非靜態成員變量和非靜態成員方法,因為非靜態成員方法/變量都是必須依賴具體的對象才能夠被調用。
代碼塊
static關鍵字還有一個比較關鍵的作用就是 用來形成靜態代碼塊以優化程序性能。static塊可以置于類中的任何地方,類中可以有多個static塊。在類初次被加載的時候,會按照static塊的順序來執行每個static塊,并且只會執行一次。
單例模式
懶漢
線程不安全
class A{ private static A a; private A(){ } public static A getA(){if(a==null){a = new A(); }return a; } }餓漢
//線程安全
class B{private static B b = new B();private B(){}public static B getB(){return b;} }線程安全的懶漢
class C{private static C c; private C(){}public synchronized static C getC(){if(c == null){c= new C();}return c;}}靜態內部類
class D{private D(){} private static class Dd{private static final D DD = new D(); } public static D getD(){ return Dd.DD; } }雙重檢查懶漢式
class E{private static E e;private E(){}public static E getE(){if(e==null){synchronized (E.class) {if(e==null) {e = new E();}}}return e;} }訪問限定符
重載和重寫的區別
重載
方法重載是讓類以統一的方式處理不同類型數據的一種手段。多個同名函數同時存在,具有不同的參數個數/類型。重載是一個類中多態性的一種表現。
Java的方法重載,就是在類中可以創建多個方法,它們具有相同的名字,但具有不同的參數和不同的定義。調用方法時通過傳遞給它們的不同參數個數和參數類型給它們的不同參數個數和參數類型給它們的不同參數個數和參數類型來決定具體使用哪個方法, 這就是多態性。
重載的時候,方法名要一樣,但是參數類型和個數不一樣,返回值類型可以相同也可以不相同。無法以返回型別作為重載函數的區分標準。
重載規則:
被重載的方法必須改變參數列表(參數個數或類型不一樣);
被重載的方法可以改變返回類型;
被重載的方法可以改變訪問修飾符;
被重載的方法可以聲明新的或更廣的檢查異常;
方法能夠在同一個類中或者在一個子類中被重載。
無法以返回值類型作為重載函數的區分標準。
重寫
重寫是子類對父類的允許訪問的方法的實現過程進行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!
方法的重寫規則:
Super 關鍵字的使用
當需要在子類中調用父類的被重寫方法時,要使用 super 關鍵字。
接口和抽象類的區別
接口
Java中接口使用interface關鍵字修飾,特點為:
接口可以包含變量、方法;變量被隱士指定為public static final,方法被隱士指定為public abstract(JDK1.8之前); 接口支持多繼承,即一個接口可以extends多個接口,間接的解決了Java中類的單繼承問題; 一個類可以實現多個接口; JDK1.8中對接口增加了新的特性:(1)、默認方法(default method):JDK 1.8允許給接口添加非抽象的方法實現,但必須使用default關鍵字修飾;定義了default的方法可以不被實現子類所實現,但只能被實現子類的對象調用;如果子類實現了多個接口,并且這些接口包含一樣的默認方法,則子類必須重寫默認方法;(2)、靜態方法(static method):JDK 1.8中允許使用static關鍵字修飾一個方法,并提供實現,稱為接口靜態方法。接口靜態方法只能通過接口調用(接口名.靜態方法名)。抽象類
在Java中被abstract關鍵字修飾的類稱為抽象類,被abstract關鍵字修飾的方法稱為抽象方法,抽象方法只有方法的聲明,沒有方法體。抽象類的特點:
抽象類不能被實例化只能被繼承; 包含抽象方法的一定是抽象類,但是抽象類不一定含有抽象方法; 抽象類中的抽象方法的修飾符只能為public或者protected,默認為public; 一個子類繼承一個抽象類,則子類必須實現父類抽象方法,否則子類也必須定義為抽象類; 抽象類可以包含屬性、方法、構造方法,但是構造方法不能用于實例化,主要用途是被子類調用。接口與抽象類相同點
(1)都不能被實例化
(2)接口的實現類或抽象類的子類都只有實現了接口或抽象類中的方法后才能實例化。
接口與抽象類不同點
(1)接口只有定義,不能有方法的實現,java 1.8中可以定義default方法體,而抽象類可以有定義與實現,方法可在抽象類中實現。 (2)實現接口的關鍵字為implements,繼承抽象類的關鍵字為extends。一個類可以實現多個接口,但一個類只能繼承一個抽象類。所以,使用接口可以間接地實現多重繼承。 (3)接口強調特定功能的實現,而抽象類強調所屬關系。 (4)接口成員變量默認為public static final,必須賦初值,不能被修改;其所有的成員方法都是public、abstract的。抽象類中成員變量默認default,可在子類中被重新定義,也可被重新賦值;抽象方法被abstract修飾,不能被private、static、synchronized和native等修飾,必須以分號結尾,不帶花括號。異常相關
常見的異常
空指針異常: NullPointerException
數據類型轉換異常:java.lang.ClassCastException
數組下標越界異常:ArrayIndexOutOfBoundsException
下標越界異常:IndexOutOfBoundsExecption
加載類失敗異常:ClassNotFoundExecption
文件讀寫異常:IOExecption
操作數據庫異常:SQLException
異常處理機制
try{
//可能會發生異常的代碼
}catch(異常類){
執行異常
}finally{
一般情況下無論代碼是否異常都會被執行的部分,
作為資源的釋放使用\事務的提交
如果在執行代碼塊中使用了System.exit(1)
}
int peek(){
if(size==0){
throw new Exception
}
return element(size-1);
}
void fun() throws 異常類{
Thread.sleep(1000);
}
Object常用方法
Object是所有類的父類,任何類都默認繼承Object。Object類到底實現了哪些方法?
equals
默認比較兩個對象地址
String、Date、File、包裝類等都重寫了Object類中的equals()方法。重寫以后,比較的不是兩個引用的地址是否相同,而是比較兩個對象的"實體內容"是否相同。
toString
默認返回對象名+@+對象的十六進制的哈希值。
String、Date、File、包裝類等都重寫了Object類中的toString()方法。使得在調用對象的toString()時,返回"實體內容"信息
Clone
保護方法,實現對象的淺拷貝,只有實現了Cloneable接口才可以調用該方法,否則拋出CloneNotSupportedException異常。
getClass
final方法,獲得運行時類型。
HashCode
該方法用于哈希查找,重寫了equals方法一般都要重寫hashCode方法。這個方法在一些具有哈希功能的Collection中用到。
一般必須滿足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就滿足equals。不過為了提高效率,應該盡量使上面兩個條件接近等價。
wait
wait方法就是使當前線程等待該對象的鎖,當前線程必須是該對象的擁有者,也就是具有該對象的鎖。wait()方法一直等待,直到獲得鎖或者被中斷。wait(long timeout)設定一個超時間隔,如果在規定時間內沒有獲得鎖就返回。
調用該方法后當前線程進入睡眠狀態,直到以下事件發生。
此時該線程就可以被調度了,如果是被中斷的話就拋出一個InterruptedException異常。
notify
該方法喚醒在該對象上等待的某個線程。
nottifyAll
該方法喚醒在該對象上等待的所有線程。
finalize
該方法用于釋放資源。因為無法確定該方法什么時候被調用,很少使用。
類加載
類加載時機
1、創建對象實例,new對象的時候,會對類進行初始化,前提是類沒有被初始化 2、調用類的靜態屬性或者是類的靜態方法 3、通過class文件反射創建對象 4、初始化一個類的子類,使用子類的時候先初始化父類 5、java虛擬機啟動時被標記為啟動類的類,如:main方法所在的類注:java類的加載是動態的,并不會一次性加載所有的類到JVM中才執行,保證程序能夠正常運行的基礎類,其他的類則在需要時才會加載,節約內存開銷
不會進行分類加載的情況:
類的初始化順序
(1)父類的靜態變量 靜態塊 (2)子類的靜態變量 靜態塊 (3)父類的實例變量 實例塊 構造器 (4)子類的實例變量 實例塊 構造器類加載過程
加載:
通過雙親委派機制加載類 返回一個class對象
鏈接:
驗證:
驗證字節碼的正確性 和JDK版本匹配
準備:
為類的靜態的變量分配內存,并初始化默認值
解析:
把類中的符號引用轉化為直接引用
初始化:
為類的靜態變量賦予正確的初始值并且執行靜態代碼塊
雙親委派模型
各個加載器的工作責任:
Bootstrap ClassLoader:
負責加載JAVA_HOME中jre/lib/rt.jar里所有的class,由C++實現,不是ClassLoader子類
Extension ClassLoader:
負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包
App ClassLoader:
負責加載classpath中指定的jar包及目錄中class
雙親委派模型的工作過程:
1、當前的類加載器從自己已經加載的類中查詢是否此類已經加載,如果已經加載則返回已經加載的類型
2、如果沒有找到,就去委托父類加載器去加載,父類加載器采用相同的策略,查看自己已經加載過的類中是否包含這個類,有就返回,沒有就委托其父類去加載,直到委托到啟動類加載器為止,
3、從啟動類開始加載,如果啟動加載器失敗,就是使用擴展類加載器來嘗試加載,繼續失敗則會使用Application ClassLoader加載器類加載,繼續失敗就會拋出一個異常:ClassNotFoundException
使用雙親委派的好處:
1、安全性,避免用戶自己編寫的類動態替換java的核心類
2、避免類的重復加載,因為JVM判定兩個類是否是同一個類,不僅僅根據類名是否相同進行判定,還需要判斷加載該類的類加載器是否是同一個類加載器,相同的class文件被不同的類加載器加載得到的結果就是兩個不同的類。
破壞雙親委派模型
第一次破壞:
由于雙親委派模型是在JDK1.2之后才被引入的,而類加載器和抽象類java.lang.ClassLoader則在JDK1.0時代就已經存在,面對已經存在的用戶自定義類加載器的實現代碼,Java設計者引入雙親委派模型時不得不做出一些妥協。在此之前,用戶去繼承java.lang.ClassLoader的唯一目的就是為了重寫loadClass()方法,因為虛擬機在進行類加載的時候會調用加載器的私有方法loadClassInternal(),而這個方法唯一邏輯就是去調用自己的loadClass()。
第二次破壞:
雙親委派模型的第二次“被破壞”是由這個模型自身的缺陷所導致的,雙親委派很好地解決了各個類加載器的基礎類的同一問題(越基礎的類由越上層的加載器進行加載),基礎類之所以稱為“基礎”,是因為它們總是作為被用戶代碼調用的API,但世事往往沒有絕對的完美。
如果基礎類又要調用回用戶的代碼,那該么辦?
一個典型的例子就是JNDI服務,JNDI現在已經是Java的標準服務,
它的代碼由啟動類加載器去加載(在JDK1.3時放進去的rt.jar),但JNDI的目的就是對資源進行集中管理和查找,它需要調用由獨立廠商實現并部署在應用程序的ClassPath下的JNDI接口提供者的代碼,但啟動類加載器不可能“認識”這些代碼。
為了解決這個問題,Java設計團隊只好引入了一個不太優雅的設計:線程上下文類加載器(Thread Context ClassLoader)。這個類加載器可以通過java.lang.Thread類的setContextClassLoader()方法進行設置,如果創建線程時還未設置,他將會從父線程中繼承一個,如果在應用程序的全局范圍內都沒有設置過的話,那這個類加載器默認就是應用程序類加載器。
有了線程上下文加載器,JNDI服務就可以使用它去加載所需要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載的動作,這種行為實際上就是打通了雙親委派模型層次結構來逆向使用類加載器,實際上已經違背了雙親委派模型的一般性原則,但這也是無可奈何的事情。Java中所有涉及SPI的加載動作基本上都采用這種方式,例如JNDI、JDBC、JCE、JAXB和JBI等。
第三次破壞
雙親委派模型的第三次“被破壞”是由于用戶對程序動態性的追求導致的,這里所說的“動態性”指的是當前一些非常“熱門”的名詞:代碼熱替換、模塊熱部署等,簡答的說就是機器不用重啟,只要部署上就能用。
OSGi實現模塊化熱部署的關鍵則是它自定義的類加載器機制的實現。每一個程序模塊(Bundle)都有一個自己的類加載器,當需要更換一個Bundle時,就把Bundle連同類加載器一起換掉以實現代碼的熱替換。在OSGi幻境下,類加載器不再是雙親委派模型中的樹狀結構,而是進一步發展為更加復雜的網狀結構,當受到類加載請求時,OSGi將按照下面的順序進行類搜索:
1)將java.*開頭的類委派給父類加載器加載。
2)否則,將委派列表名單內的類委派給父類加載器加載。
3)否則,將Import列表中的類委派給Export這個類的Bundle的類加載器加載。
4)否則,查找當前Bundle的ClassPath,使用自己的類加載器加載。
5)否則,查找類是否在自己的Fragment Bundle中,如果在,則委派給Fragment Bundle的類加載器加載。
6)否則,查找Dynamic Import列表的Bundle,委派給對應Bundle的類加載器加載。
7)否則,類加載器失敗。
雙親委派模型被破壞舉例(JDBC)
原生的JDBC中Driver驅動本身只是一個接口,并沒有具體的實現,具體的實現是由不同數據庫類型去實現的。例如,MySQL的mysql-connector-.jar中的Driver類具體實現的。 原生的JDBC中的類是放在rt.jar包的,是由啟動類加載器進行類加載的,在JDBC中的Driver類中需要動態去加載不同數據庫類型的Driver類,而mysql-connector-.jar中的Driver類是用戶自己寫的代碼,那啟動類加載器肯定是不能進行加載的,既然是自己編寫的代碼,那就需要由應用程序啟動類去進行類加載。于是乎,這個時候就引入線程上下文件類加載器(Thread Context ClassLoader)。有了這個東西之后,程序就可以把原本需要由啟動類加載器進行加載的類,由應用程序類加載器去進行加載了。
反射
什么是反射及反射機制
反射就是把java類中的各個成分映射成一個個java對象
即在運行狀態時
1.對與任意一個類,都能夠知道這個類的所有屬性和方法,
2.對與任意一個對象,都能夠調用這個對象的所有屬性和方法。
反射代碼實現
new實現
//創建一個動物對象:湯姆貓Animal tomcat = new Animal();//輸入跑步時間,返回跑步距離int length = tomcat.run(2);反射實現
Class clz = Class.forName("com.liangcheng.app.Animal");Method method = clz.getMethod("run", int.class);Constructor constructor = clz.getConstructor();Object object = constructor.newInstance();Object length = method.invoke(object, 2);反射機制的優缺點
優點:
可以實現動態創建對象和編譯,體現出很大的靈活性,特別是在J2EE的開發中它的靈活性就表現的十分明顯。比如,一個大型的軟件,不可能一次就把把它設計的很完美,當這個程序編譯后,發布了,當發現需要更新某些功能時,我們不可能要用戶把以前的卸載,再重新安裝新的版本,假如這樣的話,這個軟件肯定是沒有多少人用的。采用靜態的話,需要把整個程序重新編譯一次才可以實現功能的更新,而采用反射機制的話,它就可以不用卸載,只需要在運行時才動態的創建和編譯,就可以實現該功能。
缺點:
對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么并且它滿足我們的要求。這類操作總是慢于只直接執行相同的操作。
反射的原理
總結
以上是生活随笔為你收集整理的JavaSE基础复习总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SPringMVC使用总结
- 下一篇: SSM整合使用