整理 | 蘇宓
創(chuàng)新互聯(lián)堅持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:網(wǎng)站制作、成都網(wǎng)站設(shè)計、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時代的貴港網(wǎng)站設(shè)計、移動媒體設(shè)計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
出品 | CSDN(ID:CSDNnews)
時隔六個月,9 月 20 日,Java 19 如期而至,這一版本是自 Oracle 宣布 Java 以六個月為一周期發(fā)布的第十個版本。不過值得注意的是,JDK 19 是標(biāo)準(zhǔn)的非 LTS(長期支持)版本。
話不多說,我們先來一起看一下最新版本的 Java 帶來了哪些更新?
?
Java 19 的七大亮點更新
根據(jù)官方發(fā)布的公告顯示,Java 19 版本帶來了七大主要功能更新,包括結(jié)構(gòu)化并發(fā)、記錄模式、外部函數(shù)和內(nèi)存 API 的預(yù)覽,以及對開源的 Linux/RISC-V 指令集架構(gòu)(ISA)的支持。除了 Linux/RISC-V 功能,所有的功能都處于預(yù)覽或孵化階段。
具體來看:
結(jié)構(gòu)化并發(fā)。當(dāng)前還處于孵化階段,旨在通過結(jié)構(gòu)化并發(fā) API 簡化多線程編程。這種并發(fā)性將不同線程中運行的多個任務(wù)視為單個工作單元,從而簡化錯誤處理、提高可靠性和可觀察性。這個功能來自 Project Loom,它引入了一個新的輕量級并發(fā)模型。
記錄模式,這一功能目前也處于預(yù)覽版,主要是用來解構(gòu)記錄值。記錄模式和類型模式可以被嵌套,以實現(xiàn)強(qiáng)大的、具有聲明性的和可組合的數(shù)據(jù)導(dǎo)航和處理形式。該提案的目標(biāo)包括擴(kuò)展模式匹配,用以表達(dá)更復(fù)雜的、可組合的數(shù)據(jù)查詢,同時不改變類型模式的語法或語義。該提案建立在 2021 年 3 月在 JDK 16 中交付的模式匹配的基礎(chǔ)上。未來,Oracle 可能會要求對記錄模式進(jìn)行擴(kuò)展,使之具備數(shù)組模式、Vararg 模式。記錄模式是 Project Amber 的一部分,該項目旨在探索和孵化較小的、面向生產(chǎn)力的 Java 功能。
外部函數(shù)和內(nèi)存 API 的預(yù)覽版。通過引入一個 API,Java 程序可以與 Java 運行時之外的代碼和數(shù)據(jù)進(jìn)行互操作。通過有效地調(diào)用外部函數(shù)(即 JVM 之外的代碼)和安全地訪問外部內(nèi)存(即不由 JVM 管理的內(nèi)存),該 API 使 Java 程序能夠調(diào)用本地庫并處理本機(jī)數(shù)據(jù),而不會出現(xiàn) Java 本地接口(JNI)的危險和脆弱。外部函數(shù)和內(nèi)存 API 結(jié)合了兩個早期的孵化 API:外部內(nèi)存訪問 API 和外部鏈接器 API。外部函數(shù)和內(nèi)存 API 曾在 JDK 17 中孵化,而后在 JDK 18 中重新孵化。該提案的目標(biāo)包括易用性、性能、通用性和安全性。
虛擬線程的預(yù)覽版。這是一種輕量級的線程,大大減少了編寫、維護(hù)和觀察高吞吐量并發(fā)應(yīng)用的工作量??梢砸院唵蔚?thread-per-request 風(fēng)格編寫的服務(wù)器應(yīng)用程序能夠以接近最佳的硬件利用率進(jìn)行擴(kuò)展,通過使用java.lang Thread API現(xiàn)有代碼能夠以最小的改動采用虛擬線程,并基于現(xiàn)有的 JDK 工具對虛擬線程進(jìn)行故障診斷、調(diào)試和分析。本提案的目標(biāo)不是要改變 Java 的基本并發(fā)模型,也不是要在 Java 語言或 Java 庫中提供新的數(shù)據(jù)并行結(jié)構(gòu)。它的目標(biāo)也不是去除線程的傳統(tǒng)實現(xiàn),或默默地將現(xiàn)有的應(yīng)用程序遷移到使用虛擬線程。這項功能也是 Project Loom 的一部分。
對 switch 表達(dá)式和語句的模式匹配進(jìn)行了第三次預(yù)覽。這項功能以前在 JDK 17、JDK 18 中進(jìn)行過預(yù)覽。這一次在 JDK 19 中將進(jìn)一步細(xì)化功能,包括用 switch 塊中的 when 子句替換受保護(hù)的模式。另外,當(dāng)選擇器表達(dá)式的值為 null 時,模式切換的運行時語義與傳統(tǒng)的語義更加一致。這項功能也是 Amber 項目的一部分。
Vector API 的第四次孵化,將表達(dá)向量計算,在運行時可靠地編譯為支持的 CPU 架構(gòu)上的最佳向量指令,從而實現(xiàn)優(yōu)于等效標(biāo)量計算的性能。該 API 的開發(fā)者使用 HotSpot 自動矢量器,可獲得了一種在 Java 中編寫復(fù)雜的向量算法的方法,但有一個用戶模型,使向量化更可預(yù)測和穩(wěn)健。Vector API 先前已被納入 JDK 16、JDK 17 和 JDK 19。在最新的 JDK 19 中,官方為該功能增加了兩個跨道向量操作,即壓縮和擴(kuò)展,以及一個互補(bǔ)的向量掩碼壓縮操作。壓縮向量操作將由掩碼選擇的源向量的通道按通道順序映射到目標(biāo)向量,而擴(kuò)展操作則做相反的操作。壓縮操作在過濾查詢結(jié)果時非常有用。
通過 Linux/RISC-V 移植,目前這一功能已正式可用。Java 將獲得對硬件指令集的支持,該指令集已經(jīng)被廣泛的語言工具鏈所支持。RISC-V 實際上是一系列相關(guān)的 ISA。Linux/RISC-V 端口將只支持 RISC-V 的 RV64GV 配置,這是一個包括矢量指令的通用 64 位 ISA。Java 的開發(fā)者可能會在將來考慮其他 RISC-V 的配置。
除了以上功能更新之外,甲骨文公司 Java 平臺開發(fā)高級副總裁、OpenJDK 管理委員會主席 Georges Saab 表示,最新的 Java 版本還修復(fù)了一千多處錯誤,提高了語言的穩(wěn)定性和性能。不過,Saab 稱,Java客戶經(jīng)常推遲升級,因為他們看了功能列表,沒有看到他們明顯需要的東西。但他敦促 Java 開發(fā)者跟上程序,"因為一切都會變得更好"。
Java 有望在三年內(nèi)成為最頻繁使用的編程語言平臺
事實上,近幾年間,在 Python 火速升溫之下,關(guān)于 Java 大廈將倒、步入下坡路等言論不絕于耳,很多人認(rèn)為 Java 的使用率已大不如以前。
不過,在這邊發(fā)布 Java 19 之際,Oracle 也在官方博客上發(fā)布了一篇《Java is #1 choice for cloud according to VDC Research》的文章,似乎在為 Java “站臺”,其中引用了咨詢公司 VDC Research 的一份關(guān)于 Java 在企業(yè)中使用和重要性的研究報告。
該研究報告考察了當(dāng)今主流的 20 多種頂級編程語言,發(fā)現(xiàn) Java 仍然是頂級技術(shù)趨勢中排名第一的語言,也是開發(fā)者信任的解決安全問題的最高評級語言。
?
這份報告數(shù)據(jù)顯示,企業(yè)在不使用 Java 的項目通常比使用 Java 的項目要多花費 22% 的成本。
同時,Java 有望在三年內(nèi)成為最頻繁使用的編程語言平臺,超過 JavaScript、C++ 和 Python 等。云開發(fā)人員將 Java 列為對其組織運營最重要的語言。這對于涉及在云端開發(fā)或部署的項目來說是真實的。
這份報告指出,多年來,Java 在云計算生態(tài)系統(tǒng)中的地位一直在提高,基于云計算的 JVM 現(xiàn)在正處于每年增長 12.5% 的軌道上,到 2025 年將達(dá)到 820 億的活躍安裝基數(shù)。
毋庸置疑,在企業(yè)級應(yīng)用以及云生態(tài)系統(tǒng)中,至今還未有一種語言能夠超越 Java。然而,Oracle 內(nèi)部對 Java 更新頻率的加快,讓不少開發(fā)者報以“你更新任你更新,我就不用”的態(tài)度,也讓很多人成為 Java 8、Java 11 等老版本的“釘子戶”。
對于這一點,據(jù)外媒 The Register 報道,Saab 在采訪時也表示 Oracle 關(guān)注到了這一點,"自從我們轉(zhuǎn)向這種模式以來,沒有任何延遲,你可能知道,我們以前的模式并非總是如此。在過去,用戶往往需要等待相當(dāng)長的時間才能得到 Java 中的任何新東西,然后他們會一下子得到太多的東西。
我們確實意識到,并不是每個人都想每六個月重新發(fā)布一次。因此,我們所做的一件重要的事情是為長期支持提供 Java SE 訂閱服務(wù),基本上使那些希望保持在一個版本上并每季度得到更新以保持其安全性的企業(yè)(可以這樣做)?!?/p>
你是否會升級到最新版本的 Java?
每年在 Java 最新版本到來之際,也有不少人陷入了“升級 or 不升級”的糾結(jié)中。根據(jù)知名科技公司 New Relic 之前發(fā)布的《Java 生態(tài)系統(tǒng)狀況報告》顯示,2018 年 9 月發(fā)布的 Java 11 是目前最受歡迎的 Java 版本(48%),其次是 2014 年 3 月發(fā)布的 Java 8(46%),兩者都是長期支持(LTS)版本。
?
每個Java LTS版本的使用百分比
最新的一個長期版本 Java 17 排名還不是很高,但它在發(fā)布后的幾個月里,已經(jīng)超過了Java 6、Java 10 和 Java 16 版本的占比。
此前,Oracle 還提議將 JDK LTS 的發(fā)布周期從每三年一次改為每兩年一次。如果該提案被接受,這意味著 JDK 17 之后的下一個 JDK LTS 版本將是 JDK 21,而不是 JDK 23。
因此,相較非 LTS 版本,LTS 版本帶來的穩(wěn)定支持更受 Java 開發(fā)者歡迎一些,那么,你當(dāng)前正在使用哪個版本的 Java 呢?這一次 Java 19 發(fā)布,你會去嘗鮮嗎?
項目結(jié)構(gòu)如下:
代碼如下
package?main;
public?class?Course?{
private?String?classNum;
private?String?credit;
private?String?startClassDate;
private?String?endClassDate;
private?String?teacher;
private?String?assessment;
public?String?getClassNum()?{
return?classNum;
}
public?void?setClassNum(String?classNum)?{
this.classNum?=?classNum;
}
public?String?getCredit()?{
return?credit;
}
public?void?setCredit(String?credit)?{
this.credit?=?credit;
}
public?String?getStartClassDate()?{
return?startClassDate;
}
public?void?setStartClassDate(String?startClassDate)?{
this.startClassDate?=?startClassDate;
}
public?String?getEndClassDate()?{
return?endClassDate;
}
public?void?setEndClassDate(String?endClassDate)?{
this.endClassDate?=?endClassDate;
}
public?String?getTeacher()?{
return?teacher;
}
public?void?setTeacher(String?teacher)?{
this.teacher?=?teacher;
}
public?String?getAssessment()?{
return?assessment;
}
public?void?setAssessment(String?assessment)?{
this.assessment?=?assessment;
}
@Override
public?String?toString()?{
return?"?課程號:"?+?classNum?+?",?學(xué)分:"?+?credit
+?",?上課時間:"?+?startClassDate?+?",?下課時間:"
+?endClassDate?+?",?教師:"?+?teacher?+?",??考核方式:"
+?assessment?+?"";
}
}
package?main;
import?java.io.BufferedInputStream;
import?java.io.BufferedReader;
import?java.io.File;
import?java.io.FileInputStream;
import?java.io.InputStreamReader;
import?java.math.BigDecimal;
import?java.util.ArrayList;
import?java.util.Arrays;
import?java.util.List;
import?java.util.Scanner;
public?class?Main?{
public?static?void?main(String[]?args)?throws?Exception?{
Scanner?sc?=?new?Scanner(System.in);???
System.out.println("創(chuàng)建學(xué)生請輸入姓名:");??
Student?stu1?=?new?Student();
stu1.setName(sc.nextLine());
Scanner?sc1?=?new?Scanner(System.in);???
System.out.println("創(chuàng)建學(xué)生請輸入學(xué)號:");
stu1.setNumber(sc1.nextLine());
System.out.println("請選擇課程,課程如下:");
ListCourse?list2?=?new?ArrayListCourse();
stu1.setCourses(list2);
ListCourse?list?=?readTxt();
for?(Course?course?:?list)?{
System.out.println(course.toString());
}
for?(int?i?=?1;?i??5;?i++)?{
System.out.println("請輸入第"+i+"個課程號:");
//輸入第一個課程號
list2?=?handleClass(list,?list2);
System.err.println("已選擇成功!");
list?=?notSelectClass(list,?list2);
System.out.println("請選擇課程,課程如下:");
if(i==4){
break;
}
for?(Course?course?:?list)?{
System.out.println(course.toString());
}
}
System.out.println("課程已選完!結(jié)果如下:");
System.err.println("學(xué)生:"+stu1.getName()+":");
for?(Course?course?:?list2)?{
System.err.println(course.toString());
}
}
/**
*?
*?查看未選擇可選課程
*?@param?list?全部可選課程
*?@param?list2?已選課程
*?@return
*/
private?static?ListCourse?notSelectClass(ListCourse?list,ListCourse?list2){
for?(int?i?=?0;?i??list.size();?i++)?{
for?(Course?course?:?list2)?{
//把已選課程剔除
if(course.getClassNum().equals(list.get(i).getClassNum())){
list.remove(i);
continue;
}
//把時間重合課程剔除??startdate1?=enddate2?and?enddate1=startdate2
if(daYuDengYu(course.getEndClassDate(),list.get(i).getStartClassDate())daYuDengYu(list.get(i).getEndClassDate(),?course.getStartClassDate())){
list.remove(i);
}
}
}
return?list;
}
public?static?boolean?daYuDengYu(String?first,?String?second){
BigDecimal?bd1?=?new?BigDecimal(first);
BigDecimal?bd2?=?new?BigDecimal(second);
return?bd1.compareTo(bd2)=0?true:false;
}
/**
*?處理選擇課程
*?@param?list
*?@param?list2
*?@return
*/
private?static?ListCourse?handleClass(ListCourse?list,ListCourse?list2){
while?(true)?{
Scanner?sssi?=?new?Scanner(System.in);
String?num?=?sssi.nextLine().trim();
for?(Course?course?:?list)?{
if(num.equals(course.getClassNum())){
list2.add(course);
return?list2;
}
}
System.out.println("課程號輸入錯誤,請重新輸入:");
}
}
/**
*?讀取txt獲取課程??每個課程按;分割每個屬性按,分割
*?@return
*?@throws?Exception
*/
private?static?ListCourse?readTxt()?throws?Exception{
/*?讀入TXT文件?*/??
String?pathname?=?Main.class.getResource("/").toString();
pathname?=?pathname.substring(6,?pathname.length()-4)+"/src/classs/class.txt";?//獲取絕對路徑
File?filename?=?new?File(pathname);?//?要讀取以上路徑的txt文件??
ListCourse?list?=?new?ArrayListCourse();
BufferedInputStream?bis?=?new?BufferedInputStream(new?FileInputStream(filename));??
//讀取文件內(nèi)容??
byte[]?b?=?new?byte[bis.available()];??
bis.read(b);??
if(b!=null){
String?[]?strs=?new?String(b).split(";");
if?(strs==null)?{
return?null;
}
for?(String?string?:?strs)?{
String[]?str?=?string.split(",");
Course?course?=?new?Course();
course.setAssessment(str[5].trim());
course.setClassNum(str[0].trim());
course.setCredit(str[1].trim());
course.setEndClassDate(str[3].trim());
course.setStartClassDate(str[2].trim());
course.setTeacher(str[4].trim());
list.add(course);
}
}
bis.close();//關(guān)閉流(關(guān)閉bis就可以了)??
return?list;
}
}
package?main;
import?java.util.List;
public?class?Student?{
private?String?name;
private?String?number;
private?ListCourse?courses;
public?String?getName()?{
return?name;
}
public?void?setName(String?name)?{
this.name?=?name;
}
public?String?getNumber()?{
return?number;
}
public?void?setNumber(String?number)?{
this.number?=?number;
}
public?ListCourse?getCourses()?{
return?courses;
}
public?void?setCourses(ListCourse?courses)?{
this.courses?=?courses;
}
}
001,1,7,8,Joyce,assignment;
002,1,8,9,Joyce,assignment;
003,1,12,15,Joyce,assignment;
004,1,18,19,Joyce,assignment;
005,1,7,8,LI,assignment;
006,1,8,9,LI,assignment;
007,1,12,15,LI,assignment;
008,1,18,19,LI,assignment;
一樓的說的夠全面了,不過稍有誤解.
再來表示抱歉,我對編程語言中的中文名詞非常不了解,所以如果以下的回復(fù)對你的閱讀或者理解造成困難,請見諒.
1.首先,要明白這個問題的答案,需要了解call?(pass)?by?value?和?call?(pass)?by?reference?的區(qū)別.簡單來說:
call?by?value通常是復(fù)制這個parameter的值去另外一塊內(nèi)存里,然后傳給function,?所以在method/function里邊對這個變量的所有變更,實際上都是對復(fù)制過來的鏡像進(jìn)行操作,不會對原本的variable有任何影響.
call?by?reference是將parameter的reference傳給function,簡單點理解就是直接把variable傳給function.所以說這個variable的值是可以被function改變的.這個用法在c/c++中非常常見,用法是variable_name.
2.再來,在Java里邊,你可以很簡單的理解為:?Java中只有call?by?value,?也就是說,所以所有傳給function的parameter本身都不會被改變.?(這是最簡單直白的理解,當(dāng)然也有另一種常從sun的人那邊聽到的說法:Java是call?by?value?+?call?by?reference?by?value)
3.那么現(xiàn)在的問題就是為什么第二個結(jié)果是2了.?首先說一下sun官方的解釋:?對于reference?type在作為parameter/argument的時候,也是call?by?value,?但是在你擁有足夠權(quán)限時(比方說那個變量是public的,?不是final的等等各種符合的情況),可以修改這個object中fields的值(也就是屬于這個object(嚴(yán)謹(jǐn)點講是an?instance?of?the?object)?內(nèi)部的變量,?在你的例子中,?ko?里邊的?a?就是一個field,?所以update(ko)會使ko.a變成2).
4.如果你是一個有過c/c++學(xué)習(xí)經(jīng)驗的人或者你以上的解釋很難理解,以下這種說法或許更適合你?(當(dāng)然了,這只是大多包括我在內(nèi)有c經(jīng)驗的人的一種理解方式)
這里可以引入一個新的概念,pointer.?這是一種比較特殊的變量,它內(nèi)部所儲存的東西,其實只是另外一個變量的內(nèi)存地址.?如果對內(nèi)存沒有概念,你可以把它簡單理解為是風(fēng)箏的線軸,雖然看它本身看不出什么端倪,但是順著摸過去總會找到風(fēng)箏,看到它是什么樣子.?以pointer方式理解Java的人,通常會說:?Type?variable?=?new?Type();?這個過程中,最后生成的這個variable其實就是一個pointer,而不是instance本身.
在Java中,?有c/c++經(jīng)驗的人通常認(rèn)為Java是call?by?value.同時,當(dāng)一個變量用在儲存reference?type的時候,實際上儲存的是它的pointer,這也一樣可以解釋為什么ko.a會有2這個結(jié)果,因為雖然pointer被傳到function里邊時,本身是call?by?value,無法被改變.但這并不影響function本身對這個pointer指向的object的內(nèi)容做任何改變.?當(dāng)然,再次聲明,這只是一種幫助有c/c++經(jīng)驗的人理解的方法.?Sun本身嚴(yán)正聲明Java里邊沒有pointer這個東西的存在.
5.?再來解釋一下為什么說樓上所說的(或者說樓上引用的)理解略有偏差.
引用"我們上面剛學(xué)習(xí)了JAVA的數(shù)據(jù)類型,則有:值類型就是按值傳遞的,而引用類型是按引用傳遞的"?這句話很明顯的有兩點錯誤.?第一點,如果我上面所說的,Java是沒有call?by?reference的.
第二點,暫且假設(shè)Java里邊是有call?by?reference的,?這句話依然不成立.
Java中的變量有兩種類型:?primitive?types?和?reference?type.
primitive?type包括byte,?short,?int,?long,?char,?boolean,?float和double.
而這8種之外的所有的,都是reference?type.
下面是一段對你的貼上來的code的一點延伸,希望可以幫助你更好的理解Java中的argument?/?parameter到底是如何運作的.
public?class?Test?{
public?static?void?main(String[]?args)?{
int?a?=?1;
Koo?koo?=?new?Koo();
Object?o?=?new?Integer(1);
Koo?newKoo?=?new?Koo();
update(a);
update(koo);
update(o);
update(newKoo);
newUpdate(newKoo);
System.out.println(a);
System.out.println(koo.a);
System.out.println(o);
System.out.println(newKoo.a);
}
static?void?update(int?a)?{
a++;
}
static?void?update(Koo?koo)?{
koo.a++;
}
static?void?update(Object?o)?{
o?=?(int)?(Integer.parseInt(o.toString())?+?1);
}
static?void?newUpdate(Koo?koo)?{
koo?=?new?Koo();
}
}
class?Koo?{
int?a?=?1;
}
/*
o?=?(int)?(Integer.parseInt(o.toString())?+?1);?這一行中的(int)純粹是多余的,是否有這個casting對code本身沒有任何影響.?如果你高興也可以用
o?=?new?Integer(Integer.parseInt(o.toString())?+?1);
或者干脆
o?=?Integer.parseInt(o.toString())?+?1;
*/
以上這些code運行之后會得到1?2?1?2的結(jié)果.?后面兩個結(jié)果可以很好的說明,?即使對objects?(reference?type?variables)?來看,?Java所應(yīng)用的也并不是call?by?reference.?否則的話,以上code運行結(jié)果應(yīng)該是1?2?2?1
希望你可以真正理解這個新的例子中,產(chǎn)生1212這個結(jié)果的原因,從而對Java中的arguments有一個系統(tǒng)全面的認(rèn)識.
圖片是相關(guān)資料的鏈接,知道里貌似不能加網(wǎng)址
我從我的博客里把我的文章粘貼過來吧,對于單例模式模式應(yīng)該有比較清楚的解釋:
單例模式在我們?nèi)粘5捻椖恐惺殖R姡?dāng)我們在項目中需要一個這樣的一個對象,這個對象在內(nèi)存中只能有一個實例,這時我們就需要用到單例。
一般說來,單例模式通常有以下幾種:
1.饑漢式單例
public class Singleton {
private Singleton(){};
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
這是最簡單的單例,這種單例最常見,也很可靠!它有個唯一的缺點就是無法完成延遲加載——即當(dāng)系統(tǒng)還沒有用到此單例時,單例就會被加載到內(nèi)存中。
在這里我們可以做個這樣的測試:
將上述代碼修改為:
public class Singleton {
private Singleton(){
System.out.println("createSingleton");
};
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
public static void testSingleton(){
System.out.println("CreateString");
}
}
而我們在另外一個測試類中對它進(jìn)行測試(本例所有測試都通過Junit進(jìn)行測試)
public class TestSingleton {
@Test
public void test(){
Singleton.testSingleton();
}
}
輸出結(jié)果:
createSingleton
CreateString
我們可以注意到,在這個單例中,即使我們沒有使用單例類,它還是被創(chuàng)建出來了,這當(dāng)然是我們所不愿意看到的,所以也就有了以下一種單例。
2.懶漢式單例
public class Singleton1 {
private Singleton1(){
System.out.println("createSingleton");
}
private static Singleton1 instance = null;
public static synchronized Singleton1 getInstance(){
return instance==null?new Singleton1():instance;
}
public static void testSingleton(){
System.out.println("CreateString");
}
}
上面的單例獲取實例時,是需要加上同步的,如果不加上同步,在多線程的環(huán)境中,當(dāng)線程1完成新建單例操作,而在完成賦值操作之前,線程2就可能判
斷instance為空,此時,線程2也將啟動新建單例的操作,那么多個就出現(xiàn)了多個實例被新建,也就違反了我們使用單例模式的初衷了。
我們在這里也通過一個測試類,對它進(jìn)行測試,最后面輸出是
CreateString
可以看出,在未使用到單例類時,單例類并不會加載到內(nèi)存中,只有我們需要使用到他的時候,才會進(jìn)行實例化。
這種單例解決了單例的延遲加載,但是由于引入了同步的關(guān)鍵字,因此在多線程的環(huán)境下,所需的消耗的時間要遠(yuǎn)遠(yuǎn)大于第一種單例。我們可以通過一段測試代碼來說明這個問題。
public class TestSingleton {
@Test
public void test(){
long beginTime1 = System.currentTimeMillis();
for(int i=0;i100000;i++){
Singleton.getInstance();
}
System.out.println("單例1花費時間:"+(System.currentTimeMillis()-beginTime1));
long beginTime2 = System.currentTimeMillis();
for(int i=0;i100000;i++){
Singleton1.getInstance();
}
System.out.println("單例2花費時間:"+(System.currentTimeMillis()-beginTime2));
}
}
最后輸出的是:
單例1花費時間:0
單例2花費時間:10
可以看到,使用第一種單例耗時0ms,第二種單例耗時10ms,性能上存在明顯的差異。為了使用延遲加載的功能,而導(dǎo)致單例的性能上存在明顯差異,
是不是會得不償失呢?是否可以找到一種更好的解決的辦法呢?既可以解決延遲加載,又不至于性能損耗過多,所以,也就有了第三種單例:
3.內(nèi)部類托管單例
public class Singleton2 {
private Singleton2(){}
private static class SingletonHolder{
private static Singleton2 instance=new Singleton2();
}
private static Singleton2 getInstance(){
return SingletonHolder.instance;
}
}
在這個單例中,我們通過靜態(tài)內(nèi)部類來托管單例,當(dāng)這個單例被加載時,不會初始化單例類,只有當(dāng)getInstance方法被調(diào)用的時候,才會去加載
SingletonHolder,從而才會去初始化instance。并且,單例的加載是在內(nèi)部類的加載的時候完成的,所以天生對線程友好,而且也不需要
synchnoized關(guān)鍵字,可以說是兼具了以上的兩個優(yōu)點。
4.總結(jié)
一般來說,上述的單例已經(jīng)基本可以保證在一個系統(tǒng)中只會存在一個實例了,但是,仍然可能會有其他的情況,導(dǎo)致系統(tǒng)生成多個單例,請看以下情況:
public class Singleton3 implements Serializable{
private Singleton3(){}
private static class SingletonHolder{
private static Singleton3 instance = new Singleton3();
}
public static Singleton3 getInstance(){
return SingletonHolder.instance;
}
}
通過一段代碼來測試:
@Test
public void test() throws Exception{
Singleton3 s1 = null;
Singleton3 s2 = Singleton3.getInstance();
//1.將實例串行話到文件
FileOutputStream fos = new FileOutputStream("singleton.txt");
ObjectOutputStream oos =new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
//2.從文件中讀取出單例
FileInputStream fis = new FileInputStream("singleton.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (Singleton3) ois.readObject();
if(s1==s2){
System.out.println("同一個實例");
}else{
System.out.println("不是同一個實例");
}
}
輸出:
不是同一個實例
可以看到當(dāng)我們把單例反序列化后,生成了多個不同的單例類,此時,我們必須在原來的代碼中加入readResolve()函數(shù),來阻止它生成新的單例
public class Singleton3 implements Serializable{
private Singleton3(){}
private static class SingletonHolder{
private static Singleton3 instance = new Singleton3();
}
public static Singleton3 getInstance(){
return SingletonHolder.instance;
}
//阻止生成新的實例
public Object readResolve(){
return SingletonHolder.instance;
}
}
再次測試時,就可以發(fā)現(xiàn)他們生成的是同一個實例了。
我覺得寫得還可以,累死了,50分不容易啊
你還可以修改一下
import java.applet.Applet;
import javax.swing.*;
import javax.swing.border.*;
public class Calculator extends JApplet implements ActionListener
{
private final String[] KEYS={"7","8","9","/","sqrt",
"4","5","6","*","%",
"1","2","3","-","1/x",
"0","+/-",".","+","="
};
private final String[] COMMAND={"Backspace","CE","C"};
private final String[] M={" ","MC","MR","MS","M+"};
private JButton keys[]=new JButton[KEYS.length];
private JButton commands[]=new JButton[COMMAND.length];
private JButton m[]=new JButton[M.length];
private JTextField display =new JTextField("0");
// public JTextField setHorizontalAlignment(int alignment);
// private JTextField display.setHorizontalAlignment(JTextField.RIGHT)=new JTextField("0");
private void setup()
{
display.setHorizontalAlignment(JTextField.RIGHT);
JPanel calckeys=new JPanel();
JPanel command=new JPanel();
JPanel calms=new JPanel();
calckeys.setLayout(new GridLayout(4,5,3,3));
command.setLayout(new GridLayout(1,3,3,3));
calms.setLayout(new GridLayout(5,1,3,3));
for(int i=0;iKEYS.length;i++)
{
keys[i]=new JButton(KEYS[i]);
calckeys.add(keys[i]);
keys[i].setForeground(Color.blue);
}
keys[3].setForeground(Color.red);
keys[4].setForeground(Color.red);
keys[8].setForeground(Color.red);
keys[9].setForeground(Color.red);
keys[13].setForeground(Color.red);
keys[14].setForeground(Color.red);
keys[18].setForeground(Color.red);
keys[19].setForeground(Color.red);
for(int i=0;iCOMMAND.length;i++)
{
commands[i]=new JButton(COMMAND[i]);
command.add(commands[i]);
commands[i].setForeground(Color.red);
}
for(int i=0;iM.length;i++)
{
m[i]=new JButton(M[i]);
calms.add(m[i]);
m[i].setForeground(Color.red);
}
JPanel panel1=new JPanel();
panel1.setLayout(new BorderLayout(3,3));
panel1.add("North",command);
panel1.add("Center",calckeys);
JPanel top=new JPanel();
top.setLayout(new BorderLayout());
display.setBackground(Color.WHITE);
top.add("Center",display);
getContentPane().setLayout(new BorderLayout(3,5));
getContentPane().add("North",top);
getContentPane().add("Center",panel1);
getContentPane().add("West",calms);
}
public void init()
{
setup();
for(int i=0;iKEYS.length;i++)
{
keys[i].addActionListener(this);
}
for(int i=0;iCOMMAND.length;i++)
{
commands[i].addActionListener(this);
}
for(int i=0;iM.length;i++)
{
m[i].addActionListener(this);
}
display.setEditable(false);
}
public void actionPerformed(ActionEvent e)
{
String label=e.getActionCommand();
// double zero=e.getActionCommand();
if(label=="C")
handleC();
else if(label=="Backspace")
handleBackspace();
else if(label=="CE")
display.setText("0");
else if("0123456789.".indexOf(label)=0)
{handleNumber(label);
// handlezero(zero);
}
else
handleOperator(label);
}
private boolean firstDigit=true;
private void handleNumber(String key)
{
if(firstDigit)
display.setText(key);
else if((key.equals("."))(display.getText().indexOf(".")0))
display.setText(display.getText()+".");
else if(!key.equals("."))
display.setText(display.getText()+key);
firstDigit=false;
}
//private void handlezero(String key)
//{
// if(!((double)display.setText(key))
// display.setText(0);
// }
private double number=0.0;
private String operator="=";
private void handleOperator(String key)
{
if(operator.equals("/"))
{
if(getNumberFromDisplay()==0.0)
display.setText("除數(shù)不能為零");
else
{
number/=getNumberFromDisplay();
long t1;
double t2;
t1=(long)number;
t2=number-t1;
if(t2==0)
display.setText(String.valueOf(t1));
else
display.setText(String.valueOf(number));
}
}
else if(operator.equals("1/x"))
{
if(number==0.0)
display.setText("零沒有倒數(shù)");
else
{
number=1/number;
long t1;
double t2;
t1=(long)number;
t2=number-t1;
if(t2==0)
display.setText(String.valueOf(t1));
else
display.setText(String.valueOf(number));
}
}
else if(operator.equals("+"))
number+=getNumberFromDisplay();
else if(operator.equals("-"))
number-=getNumberFromDisplay();
else if(operator.equals("*"))
number*=getNumberFromDisplay();
else if(operator.equals("sqrt"))
{
number=Math.sqrt(number);
}
else if(operator.equals("%"))
number=number/100;
else if(operator.equals("+/-"))
number=number*(-1);
else if(operator.equals("="))
number=getNumberFromDisplay();
long t1;
double t2;
t1=(long)number;
t2=number-t1;
if(t2==0)
display.setText(String.valueOf(t1));
else
display.setText(String.valueOf(number));
operator=key;
firstDigit=true;
}
private double getNumberFromDisplay()
{
return Double.valueOf(display.getText()).doubleValue();
}
private void handleC()
{
display.setText("0");
firstDigit=true;
operator="=";
}
private void handleBackspace()
{
String text=display.getText();
int i=text.length();
if(i0)
{
text=text.substring(0,i-1);
if(text.length()==0)
{
display.setText("0");
firstDigit=true;
operator="=";
}
else
display.setText(text);
}
}
public static void main(String args[])
{
JFrame f=new JFrame();
Calculator Calculator1=new Calculator();
Calculator1.init();
f.getContentPane().add("Center",Calculator1);
f.setVisible(true);
f.setBounds(300,200,380,245);
f.setBackground(Color.LIGHT_GRAY);
f.validate();
f.setResizable(false);
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
f.setTitle("計算器");
}
}