什么是面向?qū)ο?/h2>
面向?qū)ο蠛兔嫦蜻^程區(qū)別
面向過程:面向過程是將解決問題的思路轉(zhuǎn)為一個個方法。
面向?qū)ο?/strong>:面向?qū)ο髣t是編寫一個對象,將這些思路封裝成一個個對象方法,后續(xù)調(diào)用這個對象解決問題,相對面向過程而言,這種思路更符合人的思維并且更易擴(kuò)展、復(fù)用、維護(hù)。
面向?qū)ο蠛兔嫦蜻^程性能差距:人們常常認(rèn)為面向過程性能高于面向?qū)ο?,因為?chuàng)建的對象開銷遠(yuǎn)遠(yuǎn)大于面向過程,實際上Java面向?qū)ο笮阅懿畹脑虿⒉皇沁@個,真正的原因是Java為半編譯語言,運(yùn)行并不是直接拿著二進(jìn)制機(jī)械碼執(zhí)行,而是需要結(jié)果字節(jié)碼轉(zhuǎn)換這一步。
而且面向過程的性能并不一定比面向過程快,面向過程也需要計算偏移量以及某些腳本語言的性能也一定比Java好。
創(chuàng)建一個對象用什么運(yùn)算符?
用new運(yùn)算符,創(chuàng)建的對象的實例會在堆內(nèi)存中開辟一個空間。而引用則在棧內(nèi)存中,指向?qū)ο髮嵗?/p>
面向?qū)ο髮崿F(xiàn)偽代碼
以人開門為例,人需要開門,所以我們需要創(chuàng)建一個門對象,描述門的特征,這個門可以開或者關(guān)。所以門的偽代碼如下:
門{
開()
{
操作門軸
}
}
上文說到了,面向?qū)ο蟮奶攸c就是符合人的思維,而人開門這個功能,我們就可以創(chuàng)建一個人的對象,編寫一個開門的動作,把門打開。通過這種對象調(diào)用對象的方式完成了功能。后續(xù)我們需要狗開門,貓開門也只是編寫一個方法調(diào)用門對象的開的動作。
人 {
開門(門對象){
門.打()
}
}
面向?qū)ο笕筇卣?/h3>
- 封裝
- 繼承
- 多態(tài)
類和對象的關(guān)系。
以生活事務(wù)為例,現(xiàn)實生活中的對象:張三 李四。他們都有姓名、性別、學(xué)習(xí)Java的能力。
所以我們要想通過面向?qū)ο笏枷雽崿F(xiàn)抽象出對象,就得提取共性,編寫一個類有姓名、性別、學(xué)習(xí)Java的能力。
public class Student {
private String name;
private int sex;
public void studyJava(){
System.out.println(this.name+"學(xué)習(xí)java");
}
}
描述時,這些對象的共性有:姓名,年齡,性別,學(xué)習(xí)java功能。再將這些分析映射到j(luò)ava中,就是以class定義的類進(jìn)行展開。
public static void main(String[] args) {
Student zhangsan=new Student();
zhangsan.setName("張三");
zhangsan.studyJava();
Student lisi=new Student();
lisi.setName("李四");
lisi.studyJava();
// 輸出結(jié)果
// 張三學(xué)習(xí)java
// 李四學(xué)習(xí)java
}
而具體對象就是對應(yīng)java在堆內(nèi)存中用new建立實體。
基礎(chǔ)案例
需求:描述汽車(顏色,輪胎數(shù))。描述事物其實就是在描述事物的屬性和行為。
屬性對應(yīng)在類中即變量,行為對應(yīng)的類中的函數(shù)(方法)。
代碼實現(xiàn)
public class Car {
//描述顏色
String color = "紅色";
//描述輪胎數(shù)
int num = 4;
//運(yùn)行行為。
public void run() {
System.out.println("顏色:"+color + " 輪胎數(shù):" + num);
}
}
實例化
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.run();
}
}
創(chuàng)建car對象時car引用的內(nèi)存圖
對象調(diào)用方法過程
首先我們看一段代碼,這是一個人類的class類代碼
public class Person {
private String name;
private int age;
private static String country = "cn";
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static void showCountry() {
System.out.println("showCountry " + country);
}
public void speak() {
System.out.println(this.getName() + " speak");
}
}
假如我們在main中編寫這樣一段代碼,請問在內(nèi)存中他是如何工作的呢?
public static void main(String[] args) {
Person p = new Person("張三", 18);
p.setName("李四");
}
我們先從類類加載時開始分析,由于static關(guān)鍵字修改的變量或者方法會隨著jvm加載類時一起創(chuàng)建,所以country
和showCountry()
在方法區(qū)是這樣的。
然后main方法開始執(zhí)行對應(yīng)代碼,首先main方法入棧,初始化一個p引用
堆區(qū)開辟一個空間,創(chuàng)建一個person實例,p引用指向這個內(nèi)存空間
調(diào)用setName,setName入棧,完成name值修改之后銷毀
成員變量和局部變量
作用范圍
成員變量作用于整個類中。
局部變量變量作用于函數(shù)中,或者語句中。
在內(nèi)存中的位置
成員變量:在堆內(nèi)存中,因為對象的存在,才在內(nèi)存中存在。
局部變量:存在棧內(nèi)存中。
關(guān)于對象的引用關(guān)系
簡介
對象引用用于指向0個或者多個對象實例,對象實例可以被多個對象引用指向。
相關(guān)代碼
假如我們使用上文car類執(zhí)行以下代碼,那么在內(nèi)存中會如何執(zhí)行呢?
car c=new car();
c.num=5;
car c1=c;
c.run();
內(nèi)存圖解
- 首先堆區(qū)開辟一個空間創(chuàng)建car對象,初始化值
- 修改num為5
- c1引用指向c,如下圖所示
對象相等和引用相等的區(qū)別
- 對象相等:兩個對象內(nèi)存中的存放的內(nèi)容都相等
- 引用相等:兩個引用指向的內(nèi)存地址相等。
類的構(gòu)造方法的作用是什么
完成對象初始化,首先在堆區(qū)創(chuàng)建對象實例。
構(gòu)造方法的特點
- 與類名相同
- 無返回值
- 生成對象時自動執(zhí)行
- 不可重寫可以重載
深拷貝和淺拷貝區(qū)別
淺拷貝
對象進(jìn)行拷貝時,如果內(nèi)部有引用類型,克隆對象僅僅是復(fù)制被克隆內(nèi)部對象的引用地址
為了介紹淺拷貝我們貼出這樣一段代碼,可以看到一個學(xué)生類有id和name,以及一個Vector的引用對象
public class Student implements Cloneable {
private String id;
private String name;
private Vector<String> vector;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Vector<String> getVector() {
return vector;
}
public void setVector(Vector<String> vector) {
this.vector = vector;
}
public Student() {
try {
System.out.println("創(chuàng)建對象需要三秒......");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public Student newInstance() {
try {
return (Student) this.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
然后我們使用下面這段代碼進(jìn)行測試,可以看到輸出結(jié)果為true,說明student2的vector是student1的。如下圖所示,克隆對象的內(nèi)部引用對象和student1是通用的
@Test
public void cloneTest() throws CloneNotSupportedException {
long start,end;
start=System.currentTimeMillis();
Student student=new Student();
end=System.currentTimeMillis();
System.out.println("學(xué)生1創(chuàng)建時間長 "+(end-start));
student.setId("1");
student.setName("小明");
Vector<String> v = new Vector<>();
v.add("000000");
v.add("000001");
student.setVector(v);
start=System.currentTimeMillis();
Student student2= student.newInstance();
end=System.currentTimeMillis();
System.out.println("學(xué)生2創(chuàng)建時間長 "+(end-start));
for (String s : student2.getVector()) {
System.out.println(s);
}
// false則說明深拷貝成功
System.out.println(student.getVector()==student2.getVector());
}
深拷貝
了解了淺拷貝之后,我們就可以解釋深拷貝了,克隆對象的內(nèi)部引用對象都是全新復(fù)制出來的一份
基于上文student代碼我們對此進(jìn)行改造,重寫以下clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
Student clone = new Student();
clone.setId(this.getId());
clone.setName(this.getName());
//避免clone導(dǎo)致淺拷貝問題
Vector<String> srcVector = this.getVector();
Vector<String> dstVector = new Vector<>();
for (String v : srcVector) {
dstVector.add(v);
}
clone.setVector(dstVector);
return clone;
}
匿名對象
實例代碼
如下所示,在堆區(qū)創(chuàng)建一個對象實例,用后即被銷毀。為了介紹匿名對象,我們首先需要編寫一個汽車類
public class Car {
//描述顏色
private String color = "紅色";
//描述輪胎數(shù)
private int num = 4;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
//運(yùn)行行為。
public void run() {
this.setNum(++num);
System.out.println("顏色:" + color + " 輪胎數(shù):" + num);
}
}
然后我們使用測試單元進(jìn)行測試
@Test
public void anonymously(){
new Car().run();
}
上述代碼的工作過程如下所示,可以看到完成方法調(diào)用之后
匿名對象與實例對象的區(qū)別
實例代碼
可以看到我們現(xiàn)實創(chuàng)建一個非匿名的汽車類和匿名汽車類,并作為show方法的參數(shù)傳入
public static void main(String[] args) {
Car car = new Car();
show(car);
show(new Car());
/**
* 輸出結(jié)果
* 顏色:black 輪胎數(shù):4
* 顏色:black 輪胎數(shù):4
*/
}
public static void show(Car c) {
c.setNum(3);
c.setColor("black");
c.run();
}
圖解匿名與非匿名內(nèi)存運(yùn)行
非匿名對象內(nèi)存運(yùn)行過程圖解
匿名對象完成方法調(diào)用后即被銷毀
使用場景
-
當(dāng)對對象的方法只調(diào)用一次時,可以用匿名對象來完成,這樣寫比較簡化。如果對一個對象進(jìn)行多個成員調(diào)用,必須給這個對象起個名字。
-
可以將匿名對象作為實際參數(shù)進(jìn)行傳遞。
封裝
什么是封裝
以生活為例子,某公司老板招開發(fā)人員,招得開發(fā)人員后,開發(fā)人員工作過程不用看到,老板只關(guān)注開發(fā)結(jié)果,而老板只看到開發(fā)結(jié)果這一現(xiàn)象即封裝。
什么時private修飾
private :私有,權(quán)限修飾符:用于修飾類中的成員(成員變量,成員函數(shù))。私有只在本類中有效。
代碼示例
如下所示,setAge就是對age賦值的封裝,隱藏對年齡操作的細(xì)節(jié),用戶只需通過這個方法完成自己需要的賦值動作即可
public class Person {
private int age;
public void setAge(int a) {
if (a > 0 && a < 130) {
age = a;
speak();
} else
System.out.println("feifa age");
}
public int getAge() {
return age;
}
private void speak() {
System.out.println("age=" + age);
}
}
構(gòu)造函數(shù)
什么是構(gòu)造函數(shù)
- 對象一建立就會調(diào)用與之對應(yīng)的構(gòu)造函數(shù)。
- 構(gòu)造函數(shù)的作用:可以用于給對象進(jìn)行初始化。
構(gòu)造函數(shù)的小細(xì)節(jié)
- 類默認(rèn)有構(gòu)造函數(shù),顯示創(chuàng)建后默認(rèn)構(gòu)造類就消失。
- 默認(rèn)構(gòu)造函數(shù)權(quán)限和類權(quán)限修飾符一致,例如類權(quán)限為public,則構(gòu)造方法默認(rèn)也為public,除非顯示修改權(quán)限。
構(gòu)造代碼塊
構(gòu)造代碼塊示例以及與構(gòu)造方法的區(qū)別
構(gòu)造代碼塊。
作用:給對象進(jìn)行初始化。
對象一建立就運(yùn)行,而且優(yōu)先于構(gòu)造函數(shù)執(zhí)行。
和構(gòu)造函數(shù)的區(qū)別:
1. 構(gòu)造代碼塊是給所有對象進(jìn)行統(tǒng)一初始化,在jvm完成類加載時就會運(yùn)行方法,也就是說調(diào)用靜態(tài)方法的情況下,構(gòu)造代碼塊也會被執(zhí)行
2. 而構(gòu)造函數(shù)是給對應(yīng)的對象初始化。
構(gòu)造代碼塊會隨著對象實例的創(chuàng)建和運(yùn)行。
public class Person {
private String name;
private int age;
{
System.out.println("person 類的構(gòu)造代碼塊執(zhí)行了");
run();
}
public void run(){
System.out.println("person run");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
如下便是main函數(shù)的運(yùn)行結(jié)果
public static void main(String[] args) {
Person p=new Person();
/**
* person 類的構(gòu)造代碼塊執(zhí)行了
* person run
*/
}
this關(guān)鍵字
什么是this關(guān)鍵字
代表它所在函數(shù)所屬對象的引用。簡單來說,調(diào)用對象方法的對象就是this關(guān)鍵字多代表的對象。
this的應(yīng)用
解決構(gòu)造函數(shù)初始化的問題
如下代碼,假如所有成員變量不加this,編譯器則不會找成員變量name,導(dǎo)致賦值過程毫無意義。
輸出結(jié)果
對此我們就可以使用this關(guān)鍵字即可解決問題
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
用于構(gòu)造函數(shù)之間進(jìn)行互相調(diào)用
注意:this語句只能定義在構(gòu)造函數(shù)的第一行。
public Person(String name, int age) {
this(name);
this.age = age;
}
public Person(String name) {
this.name = name;
}
static關(guān)鍵字
什么是static關(guān)鍵字
用法:是一個修飾符,用于修飾成員(成員變量,成員函數(shù)).
當(dāng)成員被靜態(tài)修飾后,就多了一個調(diào)用方式,除了可以被對象調(diào)用外,還可以直接被類名調(diào)用。類名.靜態(tài)成員。
static特點
- 隨著類的加載而加載。隨著jvm完成類加載該變量或者方法就會被加載到方法區(qū)。
- 靜態(tài)會隨著類的消失而消失。說明它的生命周期最長。
- 優(yōu)先于對象存在,即靜態(tài)變量先存在,對象后存在。
- 被所有對象所共享,所以有時候我們需要考慮線程安全問題。
- 可以直接被類名所調(diào)用。
實例變量和類變量的區(qū)別
- 實例變量隨著對象的創(chuàng)建而存放在堆內(nèi)存上,而類變量即靜態(tài)變量隨著類加載而存放在方法區(qū)上。
- 實例變量隨著對象實例消亡而消亡,而類變量隨著類的消亡和消亡。
靜態(tài)使用注意事項:
- 靜態(tài)方法只能訪問靜態(tài)變量,實例對象則靜態(tài)非靜態(tài)都可以訪問
- 靜態(tài)方法不可使用this和super關(guān)鍵字,因為this和super都是對象實例的關(guān)鍵字,this關(guān)鍵字是指向?qū)ο髮嵗瑂tatic關(guān)鍵字在類加載時候就能被指向,故不可使用這兩個關(guān)鍵字。
靜態(tài)有利有弊
利處
隨著類加載而創(chuàng)建,每個對象公用一份,無需每個實例到堆區(qū)創(chuàng)建一份。
弊處
- 生命周期長
- 使用不當(dāng)可能造成線程安全問題。
- 訪問有局限性,靜態(tài)方法只能訪問靜態(tài)相關(guān)變量或者方法。
錯誤代碼示范
圖解對象如何調(diào)用static變量
我們首先編寫這樣一段代碼
public class Person {
private String name;
private int age;
public static int staticVar=4;
}
然后我們的main方法進(jìn)行這樣的調(diào)用,在jvm內(nèi)存是如何執(zhí)行的呢?
public static void main(String[] args) {
Person person=new Person();
person.staticVar=5;
}
- main方法入棧
- 堆區(qū)創(chuàng)建person對象實例,p指向?qū)嵗?/li>
- p實例通過堆區(qū)對象操作方法的靜態(tài)變量,修改值為5
main函數(shù)
主函數(shù)
主函數(shù)是一個特殊的函數(shù),程序執(zhí)行的入口,可以被jvm執(zhí)行。
主函數(shù)的定義格式以及關(guān)鍵字含義
public:代表著該函數(shù)訪問權(quán)限是最大的。
static:代表主函數(shù)隨著類的加載就已經(jīng)存在了。
void:主函數(shù)沒有具體的返回值。
main:不是關(guān)鍵字,但是是一個特殊的單詞,可以被jvm識別。
(String[] args):函數(shù)的參數(shù),參數(shù)類型是一個數(shù)組,該數(shù)組中的元素是字符串。字符串類型的數(shù)組。
主函數(shù)是固定格式的
只有符合上述的固定格式,jvm才能識別。
jvm在調(diào)用主函數(shù)時,傳入的是new String[0];即可長度為0的String
如何使用args
class MainDemo
{
public static void main(String[] args)//new String[]
{
String[] arr = {"hah","hhe","heihei","xixi","hiahia"};
MainTest.main(arr);
}
}
class MainTest
{
public static void main(String[] args)
{
for(int x=0; x<args.length; x++)
System.out.println(args[x]);
}
}
靜態(tài)代碼塊
格式
static
{
靜態(tài)代碼塊中的執(zhí)行語句。
}
我們在person類中編寫一個靜態(tài)代碼塊,然后調(diào)用其他靜態(tài)方法,可以發(fā)現(xiàn)靜態(tài)代碼塊會隨著類的加載而完成執(zhí)行,并且只執(zhí)行一次
public class Person {
static {
System.out.println("靜態(tài)代碼塊,隨著方法執(zhí)行而執(zhí)行.....");
}
public static void func(){
System.out.println("靜態(tài)方法執(zhí)行了");
}
}
main方法調(diào)用示例
public static void main(String[] args) {
Person.func();
Person.func();
/**
* 輸出結(jié)果
* 靜態(tài)代碼塊,隨著方法執(zhí)行而執(zhí)行.....
* 靜態(tài)方法執(zhí)行了
* 靜態(tài)方法執(zhí)行了
*/
}
設(shè)計優(yōu)化
單例模式
簡介
對于重量級對象的創(chuàng)建可能會導(dǎo)致以下問題:
- 創(chuàng)建對象開銷大
- GC壓力大,可能導(dǎo)致系統(tǒng)卡頓
餓漢式
代碼如下所示,可以看到對象隨著類的加載就會立刻完成創(chuàng)建,這就導(dǎo)致假如我們使用這個類的某些不需要單例的方法也會完成對象的創(chuàng)建。
例如我們就像調(diào)用以下Singleton 的sayHello這個靜態(tài)方法就會導(dǎo)致單例實例被創(chuàng)建,所以如果非必要我們不建議采用這種非延遲加載的單例模式
public class Singleton {
private Singleton() {
System.out.println("創(chuàng)建單例");
}
public static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
public static void sayHello(){
System.out.println("hello");
}
}
測試代碼,可以看到調(diào)用靜態(tài)方法單例就被被創(chuàng)建了
public static void main(String[] args) {
Singleton.sayHello();
/**
* 輸出:
* 創(chuàng)建單例
* hello
*/
}
原理也很簡單,靜態(tài)變量和方法都在方法區(qū),隨著類被加載這些變量或者方法都會被加載。
懶漢式(線程不安全)
懶漢式即實現(xiàn)延遲加載的有效手段,代碼如下所示
/**
* 延遲加載的單例類 避免jvm加載時創(chuàng)建對象
*/
public class LazySingleton {
private LazySingleton() {
System.out.println("懶加載單例類");
}
private static LazySingleton instance = null;
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
public static void sayHello(){
System.out.println("hello");
}
}
調(diào)用示例如下所示,可以看到靜態(tài)方法調(diào)用后并沒有創(chuàng)建實例,只有調(diào)用獲取對象時才會得到對象實例
public static void main(String[] args) {
LazySingleton.sayHello();
LazySingleton.getInstance();
/**
* 輸出結(jié)果
* hello
* 懶加載單例類
*/
}
懶漢式的工作原理如下圖所示,可以看到只有調(diào)用getInstance后,才會在堆內(nèi)存中開辟一塊內(nèi)存空間創(chuàng)建對象
實際上,當(dāng)前的懶漢式存在線程安全問題,如上內(nèi)存圖解所示,可能會有兩個線程走到==null的判斷中進(jìn)而出現(xiàn)創(chuàng)建多個單例對象的情況。我們使用JUC的倒計時門閂調(diào)用獲取單例的情況,可以看到對象被創(chuàng)建了多次。
/**
* 不加synchronized的懶加載 加上則沒有下面這樣輸出結(jié)果
*/
@Test
public void threadTest0(){
ExecutorService threadPool = Executors.newFixedThreadPool(1000);
CountDownLatch countDownLatch=new CountDownLatch(1);
for (int i = 0; i < 10000; i++) {
threadPool.submit(()->{
System.out.println(LazySingleton.getInstance());
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
countDownLatch.countDown();
/**
* 懶加載單例類
* 懶加載單例類
* 懶加載單例類
* 懶加載單例類
* com.optimize.design.LazySingleton@12423874
* com.optimize.design.LazySingleton@350c55ec
* com.optimize.design.LazySingleton@350c55ec
* com.optimize.design.LazySingleton@350c55ec
* 懶加載單例類
* com.optimize.design.LazySingleton@350c55ec
* com.optimize.design.LazySingleton@5897fc07
* 懶加載單例類
* com.optimize.design.LazySingleton@5897fc07
* 懶加載單例類
* com.optimize.design.LazySingleton@39d8305
* com.optimize.design.LazySingleton@1a0eae7f
* com.optimize.design.LazySingleton@1a0eae7f
* 懶加載單例類
* com.optimize.design.LazySingleton@1a0eae7f
* 懶加載單例類
*/
}
懶漢式(線程安全)
要想實現(xiàn)線程安全,我們只需要通過下面這種方式上鎖即可保線程安全,但是缺點也很明顯,在高并發(fā)情況下,獲取對象的實踐會隨著增加
/**
* 增加 synchronized確保線程安全
* @return
*/
public synchronized static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
測試用例如下,可以看到餓漢式和線程安全懶漢式時間的差距
@Test
public void test(){
long start=System.currentTimeMillis();
for (int i = 0; i < 100_0000; i++) {
Singleton.getInstance();
}
long end=System.currentTimeMillis();
System.out.println(end-start);
start=System.currentTimeMillis();
for (int i = 0; i < 100_0000; i++) {
LazySingleton.getInstance();
}
end=System.currentTimeMillis();
System.out.println(end-start);
/**
* 輸出結(jié)果
*
* 創(chuàng)建單例
* 3
* 懶加載單例類
* 20
*/
}
內(nèi)部類模式
上文提到的懶漢式的性能問題,所以我們可以使用內(nèi)部類模式解決該問題,代碼如下所示,可以看到我們在單例類的內(nèi)部增加一個靜態(tài)內(nèi)部類,該類被加載時靜態(tài)內(nèi)部類并不會被加載,只有調(diào)用getInstance才會創(chuàng)建單例對象,并且該對象的創(chuàng)建是隨著類的加載就完成創(chuàng)建,故這是一種線程友好的單例模式
/**
* 線程安全的單例 但還是會被反射攻破
*/
public class StaticSingleton {
private StaticSingleton() {
System.out.println("靜態(tài)內(nèi)部類延遲加載");
}
private static class SingletonHolder {
private static StaticSingleton instance = new StaticSingleton();
}
public static StaticSingleton getInstance(){
return SingletonHolder.instance;
}
public static void sayHello(){
System.out.println("hello");
}
}
測試代碼和輸出結(jié)果
public static void main(String[] args) {
StaticSingleton.sayHello();
StaticSingleton.getInstance();
/**
* 輸出結(jié)果
*
* hello
* 靜態(tài)內(nèi)部類延遲加載
*/
}
內(nèi)部類單例模式工作過程
實際上這種模式也有缺點,就是會被發(fā)射攻破,后續(xù)我們會介紹對應(yīng)的解決方案
雙重鎖校驗(線程安全)
雙重鎖校驗的單例模式如下所示,可以看到雙重鎖校驗的編碼方式和簡單,第一次判斷避免沒必要的執(zhí)行,第二次判斷避免第一次判定為空走到創(chuàng)建對象代碼塊的線程,從而避免線程安全問題
public class DoubleCheckLockSingleton {
private static DoubleCheckLockSingleton instance = null;
private DoubleCheckLockSingleton() {
System.out.println("雙重鎖單例對象被創(chuàng)建");
}
public static DoubleCheckLockSingleton getInstance() {
if (instance != null) {
return instance;
}
synchronized (DoubleCheckLockSingleton.class) {
//這一重校驗是為了避免上面判空后進(jìn)入休眠走到這個代碼塊的線程
if (null == instance) {
instance = new DoubleCheckLockSingleton();
return instance;
}
}
return instance;
}
}
性能上我們可以看到雙重鎖校驗的性能要好于靜態(tài)內(nèi)部類的方式
@Test
public void test(){
long start=System.currentTimeMillis();
for (int i = 0; i < 100_0000; i++) {
Singleton.getInstance();
}
long end=System.currentTimeMillis();
System.out.println(end-start);
start=System.currentTimeMillis();
for (int i = 0; i < 100_0000; i++) {
LazySingleton.getInstance();
}
end=System.currentTimeMillis();
System.out.println(end-start);
start=System.currentTimeMillis();
for (int i = 0; i < 100_0000; i++) {
StaticSingleton.getInstance();
}
end=System.currentTimeMillis();
System.out.println(end-start);
start=System.currentTimeMillis();
for (int i = 0; i < 100_0000; i++) {
DoubleCheckLockSingleton.getInstance();
}
end=System.currentTimeMillis();
System.out.println(end-start);
/**
* 創(chuàng)建單例
* 6
* 懶加載單例類
* 20
* 靜態(tài)內(nèi)部類延遲加載
* 4
* 雙重鎖單例對象被創(chuàng)建
* 3
*/
}
枚舉單例模式(線程安全)
/**
* 使用枚舉保證類單例
*/
public enum Elvis {
INSTANCE;
private String name="elvis";
public String getName() {
return name;
}
public static Elvis getInstance(){
return INSTANCE;
}
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
}
可以看到這種方式不會被反射攻破
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class<Elvis> elvisClass = Elvis.class;
Elvis elvis1 = elvisClass.newInstance();
System.out.println(elvis1.getName());
}
輸出結(jié)果
相關(guān)面試題
下面這段代碼。new StaticCode(4)的輸出結(jié)果?
public class StaticCode {
int num = 9;
StaticCode() {
System.out.println("b");
}
static {
System.out.println("a");
}
{
System.out.println("c" + this.num);
}
StaticCode(int x) {
System.out.println("d");
}
public static void show() {
System.out.println("show run");
}
}
答案:a c9 d
創(chuàng)建類,加載順序為:
- 加載靜態(tài)代碼塊
- 加載構(gòu)造代碼塊
- 加載構(gòu)造方法
參考文獻(xiàn)
Java基礎(chǔ)常見面試題總結(jié)(中)
Effective Java中文版(第3版)文章來源:http://www.zghlxwxcb.cn/news/detail-757735.html
Java系統(tǒng)性能優(yōu)化實戰(zhàn)文章來源地址http://www.zghlxwxcb.cn/news/detail-757735.html
到了這里,關(guān)于Java面向?qū)ο笏枷胍约霸硪约皟?nèi)存圖解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!