增近四倍 2015年Q1中國純電動車銷量達1.54萬輛

近日,中國汽車工業協會發佈最新統計資料顯示,今年第一季,中國汽車銷售達615.3萬輛,與去年同期相比增長3.9%,但相較去年同期9.2%的增幅,下滑明顯。此外新能源汽車產銷分別為2.73萬輛和2.66萬輛,與去年同期相比分別增長2.9倍和2.8倍。   其中,純電動車型銷售1.54萬輛,與去年同其相比增長3.7倍;插電式混合動力車型銷售1.12萬輛,與去年同期相比增長2.1倍。與新能源汽車相比,傳統車型銷量增幅卻明顯回落。根據資料,一季度乘用車共銷售530.51萬輛,與去年同其相比增長8.95%,增幅比去年同期回落1.14個百分點。在乘用車四類車型中,轎車銷量與去年同期相比下滑0.4%。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

歐洲純電動車市場熱絡 銷量同比勁增 73%

據《歐洲汽車新聞》報導,去年歐洲市場純電動車的銷量超越插電式混合電動車,不過今年由於車企將推出更多插電式混合車型,因此其銷量有可能超越純電動車。    歐洲汽車製造商協會 ACEA 數據顯示,2014 年在歐盟及自由貿易聯盟國家(EFTA)市場中,純電動車銷量同比勁增 73% 至 58,244 輛,插電式混合電動車及增程式電動車的銷量同比攀升 26% 至 39,547 輛,銷量最高的電動車為日產聆風,而該地區最暢銷的插電式混動車則為三菱歐藍德 PHEV。    汽車諮詢公司 IHS 預計到 2020 年,插混車的全球累計產量將達到 135 萬輛,且在 2020 年至 2025 年間將進一步翻倍至 270 萬輛。而純電動車 2020 年的銷量將在 100 萬輛以下。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

電動車電池與太陽能成本下降飛快,再生能源主宰的日子不遠了?

  電動車電池的成本下降速度超乎預期,甚至低於國際能源署(IEA)原本預估的 2020 年成本數值,且不少研究也指出,太陽能在 2020 年代將某些地區成為最便宜的能源選項,彭博新聞認為,便宜的電池與太陽能發電兩者相輔相成的結果,將加速再生能源主宰能源市場的趨勢。   國際能源署在 2013 年時,原本預估 2020 年電動車電池價格將會下降至每度電 300 美元,不過,由斯德哥爾摩環境機構最近登上《自然氣候變化(Nature Climate Change)》期刊的研究中指出,全球電動車電池價格已從 2007 年的每千瓦小時 1,000 美元,下降至 2014 年的每千瓦小時 410 美元,平均每年成本下滑 14%,且特斯拉(Tesla)及 Nissan 等電動車製造商領頭羊的價格也年減約 8%,來到 2014 年的每千瓦小時 300美元,提早 6 年達到國際能源署預定目標。   但這所謂的每千瓦小時 300 美元,除了顯示電動車電池成本下降速度超乎預期外,是否還意味著電動車躍起的時代即將來臨呢?全球顧問公司麥肯錫(Mckinsey & Company)在 2011 年曾做了一張圖表,顯示不同的油價與電池價格情況下,純電動車、插電式混合電動車、油電混合車及一般汽車何者較具競爭力,灰色區塊的部分則是 2011 年的價格區間。從下圖圖表中可以看出,當電動車電池價格位於每千瓦小時約 300 美元左右時,就是電動車競爭力逐漸起飛的時刻。    
 

  (Source:)     雖然在過去一年中,油價已下跌至每加侖 2 美元,但若電池價格也跟著不斷下跌,圖表中的灰色矩形就會繼續向左移動。該研究作者認為,若按照特斯拉執行長 Elon Musk 所言,特斯拉正在興建的超級電池工廠真能大舉降低電池組成本 30%,且天然氣價格若反彈回 3 美元區間,將可能使電動車成功在大多數地區取代一般汽車的主宰地位。   此外,根據聯合國環境署(UNEP)於 3 月 31 公布的數據指出,2014 年全球對於再生能源投資額達 2,700 億美元,而新增的再生能源裝置容量,也超過往年的成長數字,來到歷史新高的 103 GW。   即便如此,但 2014 年全球再生能源發電總量僅占 9.1%,較 2013 年的 8.5% 成長 0.6 個百分點,雖然成長速度相當緩慢,且若等到再生能源發電量占全球發電量一半的份額,得等到 2080 年才可望實現。不過,太陽能發電成本下降速度增快,加上不少研究都預估太陽能最快將在 2020 年代就能成為最便宜的能源類型,或許照這樣看來,不用等到 60 年後,就能預見再生能源主宰能源市場的世界了。   便宜的電池與太陽能發電兩者可說是相輔相成,彭博新聞認為,電動車電池價格下跌能將汽車從傳統的汽油切換到電網連結,雖然目前大多數的電網仍然由燃煤發電驅動,但若太陽能發電成本下降速度夠快,搭配如特斯拉等公司欲打造的便宜家用儲能電池組,或許能解決太陽能發電不夠穩定、無法成為基載電力的缺點,以取代燃煤發電在電網中的主導角色。     本文全文授權轉載自《科技新報》─〈〉     (首圖來源:Flickr/CC BY 2.0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

2015台灣國際電動車展 充電樁與電池技術備受矚目

隨著電動車普及,充電設備、車體以及電池等相關領域也成了相關業者爭相投入的目標。今年共有75家展商參與2015年台灣國際電動車展,許多台廠用充電樁與電動車電池等技術做為「電力」展示主軸。

2015年台北國際汽車零配件展&車用電子展於四月8日至11日在台北世貿、南港展覽館同時展出。其中,在世貿展覽的台北國際電動車展有來自台灣、中國大陸、日本、香港、紐西蘭等廠商參加。除了電動車外,其他主要展出內容包括:電池材料、模組與控制系統,馬達與驅控系統、充電樁相關設備與服務、檢測設備。電力是電動車之本,充電樁、電池或自動充電系統等取得電力的管道是本次展覽中十分重要的部分。

台灣的電動車展出

台灣地狹人稠,對電動載具的需求主要是電動機車和小貨卡、高球車、觀光區小車等工具車,且以電動機車最有發展潛力。受限於電池容量以及載重限制,電動機車體積仍較小,但在造型、避震、輪胎、車燈與置物空間等其他部分則已有多樣化發展,整體功能更接近傳統機車。

電動車的功能考量主要有幾個面向:

  • 充電方便性,如充電樁分布、快充功能、電池交換系統
  • 續航力(每次充電至少需可行駛40~50km)
  • 載重力
  • 爬坡力
  • 行駛速度

哈哈世代主要展出的電動車自動回充發電系統為針對電動車之充電需求所研發,小從電動機車,大到電動卡車、輪船或高鐵列車等皆可應用,車輛行駛一小時左右即可充飽電池,最低每公里耗電成本約為新台幣0.2~0.3元。

大同電子在本次展覽中未展出車輛,而是以電動車馬達為主要展示內容。大同所展示的馬達分成感應馬達、磁阻馬達以及內藏式永磁馬達,額定功率與電壓分別從1.8kW到30kW,以及24V到360V,可根據車輛需求的差別選擇不同功率與電壓的馬達。

晶片解決方案提供者Renesas展出了一種專為HEV/EV驅動馬達設計的變頻器套件解決方案,能縮小變頻器尺寸並提升其效率,且可適用各種馬達,也有助提升電動車馬達的效能。

 

電動車電池與充電樁

在本次展覽中,電動車電池的技術仍是最主要的內容。包括電池的蓄電力、充電後的續航力、充放電技術(快充功能)、防爆與安全性之檢測等,都有展示。

長泓能源科技展示氧化鋰鐵動力電池,這種電池的電性穩定、循環壽命長且能量密度高,加上它十分安全、重量又輕,很適合搭配電動車或其他儲設備使用。

充電樁與電池交換站是電動車的電力來源,密集分布的充電樁有助推廣民眾使用電動車。展場中常見的充電設備包括:壁掛式充電站、立柱式充電樁、電池交換器;而非家用充電設備的使用方法分成兩種,一種是經由卡片認證(可設計特殊RFID卡片或與悠遊卡等電子商務整合),另一種則是投幣式。據了解,台灣設置充電樁依縣市政策之別,可獲得約50%的補助;桃園市補助較高,可接近100%。

EValue 展出單槍、雙槍式充電樁,亦有裝設了液晶螢幕的多媒體充電站,可做為廣告看板。Phihong EV Charger 展出多種充電設備,立柱式充電樁的電容有40kW、70kW以及140kW等類型,也有簡易的小型充電站以及電池充電系統。

台灣喜洋洋公司展出的投幣式電動機車充電站可為裝設戶帶來一定的收益,每投幣10元可充100分鐘。目前有大學、社區等裝設與維護合作。

其他相關展示

汽車電控系統也是本次電動車展覽中可見的項目,例如Chroma就展示一體化的智慧電動車方案,可有效掌控電動車電力系統。EV Equipment  則展示更加環保的電動概念車Preeve。

台灣車輛研發聯盟主題館展示多種電動車相關技術,其中汰役電池的應用為未來將大量產生的廢電池找到了第二春。汰役電池的電容、續航力雖低,但可用於供應較低需求電力,或改作UPS系統。目前在金門烈嶼鄉東坑社區已有汰役電池應用測試,搭配太陽能與風能供應30戶住宅用電,也能支援當地的電動車用電。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

《HelloGitHub》第 51 期

興趣是最好的老師,HelloGitHub 就是幫你找到興趣!

簡介

分享 GitHub 上有趣、入門級的開源項目。

這是一個面向編程新手熱愛編程對開源社區感興趣 人群的月刊,月刊的內容包括:各種編程語言的項目讓生活變得更美好的工具書籍、學習筆記、教程等,這些開源項目大多都是非常容易上手,而且非常 Cool。主要是希望大家能動手用起來,加入到開源社區中。

  • 會編程的可以貢獻代碼
  • 不會編程的可以反饋使用這些工具中的 Bug
  • 幫着宣傳你覺得優秀的項目
  • Star 項目⭐️

在瀏覽、參与這些項目的過程中,你將學習到更多編程知識提高編程技巧找到編程的樂趣

最後 HelloGitHub 這個項目就誕生了

以下為本期內容|每個月 28 號發布最新一期|點擊查看往期內容

C 項目

1、goaccess:實時 Web 日誌分析工具

2、u6a:函數式編程語言 Unlambda 的一個樸素實現,包含字節碼編譯器和解釋器。此項目可以幫助初學者理解函數式編程的思想,並提供了實現函數式編程語言解釋器的一些樸素思路。

  • 性能優異:運行性能遠高於官方實現,且優於多數現有的開源實現
  • 穩定可靠:有豐富的測試樣例支撐,可靠性高
  • 簡單樸素:代碼簡單易讀,且提供了實現思路文檔,對初學或者完全沒有學過編譯原理的新手非常友好

C# 項目

3、Netch:一款 Windows 平台的開源遊戲加速工具

4、ScheduleMasterCore:一款基於 .NET Core 開發的分佈式任務調度系統。支持豐富的調度類型、靈活可控的系統參數、簡易的 UI 操作、支持多節點高可用、業務 API 集成等等特性。同時支持多樣化的部署方式,容易上手

5、HandyControl:一套 WPF 控件庫。它幾乎重寫了所有原生樣式,同時包含 70 餘款自定義控件。支持跨平台、國際化,適用於 MVVM 架構開發,扁平化設計、支持動態更換主題和背景色。豐富的自定義控件解決了 View 設計的痛點,讓程序員更加專註於業務邏輯的開發

C++ 項目

6、CnC_Remastered_Collection:EA 發布的《紅警》和《泰伯利亞黎明》遊戲源代碼

7、chinessChess:基於 Qt5 開發的中國象棋網絡對戰平台,支持單機和網絡對戰

Go 項目

8、grmon:Goroutine 的命令行監控工具

9、HackChrome:Go 語言實現的從 Chrome 中獲取自動保存的用戶名密碼工具。目前僅支持 Windows Chrome 中存儲的密碼,但是很有意思還可以學習怎麼用 Go 調用 DLL 動態鏈接庫的姿勢

10、seaweedfs:一款基於 Go 開發的部署方便、使用簡單且強大的分佈式文件系統

11、fate:起中文名工具,去吧!算名先生

Java 項目

12、JApiDocs:一個無需額外註解、開箱即用的 SpringBoot 接口文檔生成工具。特性:

  • 代碼即文檔
  • 支持導出 HTML
  • 同步導出客戶端 Model 代碼
  • 等等

13、PowerJob:基於 Akka 架構的新一代分佈式任務調度與計算框架。支持 CRON、API、固定頻率、固定延遲等調度策略,支持單機、廣播、MapReduce 等多種執行模式,支持在線任務治理與運維,提供 Shell、Python、Java 等功能豐富的任務處理器,提供工作流來編排任務解決依賴關係,使用簡單,功能強大,文檔齊全。同類產品對比:

JavaScript 項目

14、react-trello:任務狀態管理面板組件。實現了拖拽方式管理任務狀態,點擊即可編輯任務內容

15、perfume.js:用於測量第一個 dom 生成的時間、用戶最早可操作時間和組件的生命周期性能的庫。示例代碼:

perfume.start('fibonacci');
fibonacci(400);
perfume.end('fibonacci');
// Perfume.js: fibonacci 0.14 ms

16、Mongood:MongoDB 圖形化的管理工具。特性:

  • 基於微軟 Fluent UI,支持自動黑暗模式
  • 支持完整的 Mongo-shell 數據類型和查詢語法,利用索引實現的自動查詢和排序
  • 支持 Json 數據庫模式,既可用於 Server 也可用於 Client

17、TimeCat:一款 JS 的網頁錄屏工具。參考了遊戲錄像的原理而實現的渲染引擎,生成的錄像文件只有傳統視頻的百分之一!還可以在錄製語音的同時自動生成字幕,導出的視頻文件可以跨端播放。目前已經開發一段時間,後續還將實現更多有意思的功能,歡迎持續關注。在線預覽

18、react-visual-editor:基於 React 組件的可視化拖拽、搭建頁面的代碼生成工具。所見即所得,可以完美還原 UI 設計搞,並支持多款型號手機(可配置)和 PC 效果展示,模板功能可以使你分享你的頁面或者頁面中局部任何部分組件組合,減少相似頁面的重複操作。效果如下:

19、elevator.js:一個 back to top 返回頂部的插件。如他的名字一樣,網頁在返回頂部過程中像電梯向上運行,當頁面返回到頂部時,會有電梯“到達”的提示音。叮~頁面已到達頂部

PHP 項目

20、code6:一款 GitHub 代碼泄露監控系統,通過定期掃描 GitHub 發現代碼泄露行為。特性:

  • 全可視化界面,操作部署簡單
  • 支持 GitHub 令牌管理及智能調度
  • 掃描結果信息豐富,支持批量操作
  • 任務配置靈活,可單獨配置任務掃描參數
  • 支持白名單模式,主動忽略白名單倉庫

Python 項目

21、rich:一個讓你的終端輸出變得“花里胡哨”的三方庫。我的一位前輩告訴我,不要整那些花里胡哨的主題和樣式,這是在自尋煩惱。可是臣妾做不到啊,這麼好看的終端輸出,讓我的心情都愉悅起來了。瞧那性感的語法高亮、整齊的表格、舒服的顏色、進度條等,一切都是值得的

22、poetry:Python 虛擬環境、依賴管理工具。依賴管理工具有很多,我相上了它有三點:通過單文件 pyproject.toml 便可輕鬆的區別安裝、管理開發和正式環境、有版本鎖定可方便回滾、輸出界面簡單清爽。當然它還是個“新生兒”,嘗鮮的風險還是有的,選擇須謹慎

23、free-python-games:真入門級的 Python 遊戲集合庫。都是簡單的小遊戲:貪吃蛇、迷宮、Pong、猜字等,運行方便、代碼簡單易懂。用遊戲開啟的你 Python 學習之旅,玩完再學源碼,其樂無窮啊。安裝運行:

pip install freegames
python -m freegames.snake # freegames.遊戲名

24、py2sec:一款輕量級跨平台 Python “加密”、加速的腳本工具。原理是基於 Cython 將 .py 編譯成 run-time libraries 文件:.so(Linux && Mac)或 .pyd(Win),一定程度上實現了“加密”保護源代碼的功能。參數詳解如下:

-v,  --version    显示 py2sec 版本
-h,  --help       显示幫助菜單
-p,  --pyth       Python 的版本,默認為你的 Python 命令綁定的 Python 版本
-d,  --directory  Python 項目路徑(如果使用 -d 參數,將編譯整個 Python 項目)
-f,  --file       Python文件(如果使用 -f,將編譯單個 Python 文件)
-m,  --maintain   標記你不想編譯的文件或文件夾路徑
-x  --nthread     編譯啟用的線程數
-q  --quiet       靜默模式,默認 False
-r  --release     Release 模式,清除所有中間文件,只保留加密結果文件,默認 False
python py2sec.py -f test.py
python py2sec.py -f example/test1.py -r
python py2sec.py -d example/ -m test1.py,bbb/

25、oxfs:一個基於 sftp 協議的 fuse 網絡文件系統,功能上類似於 sshfs。特性:

  • 引入了異步併發讀遠端文件機制,提高了文件首次讀速度。
  • 緩存持久化到本地磁盤,下次掛載時訪問更加快速。
  • 異步任務負責同步文件,避免低速的網絡讀寫阻塞上層應用。

Swift 項目

26、Aerial:炫酷的蘋果系統屏保項目。該屏保視頻取材自蘋果零售店 Apple TV 的專用屏保,航拍質量超棒,快換上試試吧。直接下載 Aerial.saver.zip 文件,解壓后雙擊文件“即可食用”

其它

27、shan-shui-inf:自動生成一副山水畫

28、kuboard-press:一款基於 Kubernetes 的微服務管理界面。包含文檔、教程、管理界面和實戰分享

29、vscode-rainbow-fart:一款在你編程時花式誇你的 VSCode 擴展插件。可以根據代碼關鍵字,播放貼近代碼意義的真人語音,並且有一個醒目的項目名字“彩虹屁”

30、flink-training-course:Flink 視頻直播教程回放集合

31、raft-zh_cn:《分佈式 Raft 一致性算法論文》中文翻譯

32、GitHub-Chinese-Top-Charts:每周更新一次的 GitHub 中文項目排行榜

開源書籍

33、go-ast-book:《Go語法樹入門:開啟自製編程語言和編譯器之旅》

機器學習

34、Surprise:一款簡單易用基於 Python scikit 的推薦系統。如果你想用 Python 上手做一套推薦系統,那你可以試試它

35、djl:亞馬遜開源的一款基於 Java 語言的深度學習框架。對於 Java 開發者而言,可以在 Java 中開發及應用原生的機器學習和深度學習模型,同時簡化了深度學習開發的難度。通過 DJL 提供直觀的、高級的 API,Java 開發人員可以訓練自己的模型,或者利用數據科學家用 Python 預先訓練好的模型來進行推理。如果您恰好是對學習深度學習感興趣的 Java 開發者,那麼這個項目完全對口。運行效果如下:

36、data-science-ipython-notebooks:數據科學的 IPython 集合。包含:TensorFlow、Theano、Caffe、scikit-learn、Spark、Hadoop、MapReduce、matplotlib、pandas、SciPy 等方方面面

最後

如果你發現了 GitHub 上有趣的項目,歡迎在 HelloGitHub 項目提 issues 告訴我們。

關注 HelloGitHub 公眾號獲取第一手的更新

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

推薦一種通過刷leetcode來增強技術功底的方法

背景

如果前人認為這個一種學習提高或者檢驗能力的成功實踐。而自己目前又沒有更好的方法,那就不妨試一試。

而不管作為面試官還是被面試者,編碼題最近越來越流行。而兩種角色都需要思考的問題是希望考察什麼能力,通過什麼題目,需要達到怎樣的程度可以說明面試者具有了這樣的能力。

而要找到上面這些問題的答案,比較好的方式除了看一些理論性文章和接受培訓之外,自己動手刷一刷leetcode切身實踐一下不失為一個不錯的方式。而既然要花精力去做這件事情,那就需要解決一個問題:我從中可以獲得什麼提高。以下是個人的一些經驗和感受。

 

收益

對底層有了更深入的了解和思考

leetcode一些常見題也是平時工作中常用的一些底層數據結構的實現方法。 

先舉個大家使用比較多的算法:LRU(最近最少使用),在Java的實現中實現特別簡單。只是使用了LinkedHashMap這種數據結構。而如果看一些開源包里LRU的實現,發現也是這樣實現的。實際上動手實現一遍,LRU就再也不會忘了。

再舉個數據結構的例子:字典樹又叫前綴樹。它是搜索和推薦的基礎。標準點的定義是:

字典樹又稱單詞查找樹,Tire樹,是一種樹形結構,是一種哈希樹的變種。典型應用是用於統計,排序和保存大量的字符串(但不僅限於字符串),所以經常被搜索引擎系統用於文本詞頻統計。它的優點是:利用字符串的公共前綴來減少查詢時間,最大限度地減少無謂的字符串比較,查詢效率比哈希樹高。

因為之前做過搜索引擎,一直也對這塊很有興趣,所以對它底層知識的補充對個人而言,感覺深度有增加。

 

  

養成評估時空開銷的習慣

我刷leetcode必看官方解答里估算的時間和空間複雜度。這也是作為一個架構師的必備基本能力。

數組、哈希這些因為數據的位置不需要進行查找,只需要算數計算就可以得到,所以它們的時間複雜度是O(1)。

鏈表如果直接在頭部或者尾部插入,因為不需要查找,所以時間複雜度也是O(1),但是定位的話因為涉及查找,按遍歷查找來算是log(n)。所以對於jdk1.7之前,hashmap底層採用的是數組+鏈表的數據結構。所以如果不算哈希衝突和擴容的話,獲取和插入數據的時間複雜度是O(1);如果出現了哈希衝突,則插入因為是頭部插入,時間複雜度還是O(1);獲取時間複雜度因為涉及先查找,所以是O(n),這個n代表衝突的數量。

對於在有序數據中進行查找,因為可採用二分查找等優化,時間複雜度可降到log(n).

對於遞歸調用,如果遞歸方法內進行2次調用。對於層數n來說,時間複雜度是2的n次方。舉個例子就是一個數等於前面兩個數之和。當然,如果是前面3個數之和,不進行優化的情況下時間複雜度就是3的n次方。

對於一個n*m的二維數組等需要進行嵌套循環遍歷的,時間複雜度是O(n*m),有個特殊情況是n*m,這時候時間複雜度是n的平方。

對於全排列的情況,時間複雜度是O(n!)。

 

代碼簡化的方法

 

我習慣的一種學習方法是先做題,有了一定自己的總結和思考之後,再看書學習別人的總結思考方法。對於刷leetcode相關性高,也比較受認可的書是《Cracking the Coding interview(6th)》,中文版翻譯是《程序員面試金典》。這本書對於面試官和面試者來說讀了都會有一定的收穫。

 

我讀了這本書,對我印象最深的是介紹了兩種代碼優化的方法:BUD和BCR。

 

BUD 

BUD是瓶頸、不必要工作、重複工作 三個詞組首字母的縮寫。

 

作者提出拿到一道編程題,可先嘗試用暴力解法將題目寫出來,之後找到解法的性能瓶頸,針對瓶頸進行優化,之後在去掉不必要的工作,最後去掉重複的工作。

這個經典的編程優化方法不只可應用於編程,還可應用於整個程序的優化,也是最常規的優化方法。

 

BCR

BCR是Best Conceivable Runtime的縮寫,意思是想知道自己可以優化到什麼程度,先估算可達到的最優情況。

比如:在一個無序數組中,查找兩個兩個相同的數。直覺來說如果找到這兩個數,最起碼需要將每個數都遍歷一遍,所以可達到的最優情況是O(n),無論怎麼優化,都不可能比這個更好。所以這就是優化的上限。

這本書里還介紹了其他的優化方法如:使用額外數據結構、通過構建測試用例、根據題目的限制和提示來尋找線索,大家看這本書的時候可以了解下。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

【asp.net core 系列】14 .net core 中的IOC

0.前言

通過前面幾篇,我們了解到了如何實現項目的基本架構:數據源、路由設置、加密以及身份驗證。那麼在實現的時候,我們還會遇到這樣的一個問題:當我們業務類和數據源越來越多的時候,我們無法通過普通的構造對象的方法為每個實例進行賦值。同時,傳統意義上的賦值遇到底層切換或者其他修改的時候,就需要修改大量的代碼,對改變不友好。為了改變這種現狀,我們基於面向接口編程,然後使用一些DI功能和IOC框架。

1. IOC和DI

先來給大家解釋幾個概念,IOC全稱Inversion of Control,翻譯過來就是控制反轉,是面向對象編程的一種設計原則,用來降低代碼之間的耦合度。所謂的控制反轉簡單來講就是將類中屬性或者其他參數的初始化交給其他方處理,而不是直接使用構造函數。

public class Demo1
{
}

public class Demo2
{
	public Demo1 demo;
}

對於以上簡單示例代碼中,在Demo2類中持有了一個Demo1的實例。如果按照之前的情況來講,我們會通過以下方法為demo賦值:

// 方法一
public Demo1 demo = new Demo1();
// 方法二
public Demo2()
{
    demo = new Demo1();
}

這時候,如果Demo1變成下面的樣子:

public class Demo1
{
    public Demo1(Demo3 demo3)
    {
        // 隱藏
    }
}
public class Demo3
{
}

那麼,如果Demo2 沒有持有一個Demo3的實例對象,這時候創建Demo1的時候就需要額外構造一個Demo3。如果Demo3需要持有另外一個類的對象,那麼Demo2中就需要多創建一個對象。最後就會發現這樣就陷入了一個構造“地獄”(我發明的詞,指這種為了一個對象卻得構造一大堆其他類型的對象)。

實際上,對於Demo2並不關心Demo1的實例對象是如何獲取的,甚至都不關心它是不是Demo1的子類或者接口實現類。我在示例中使用了類,但這裏可以同步替換成Interface,替換之後,Demo2在調用Demo1的時候,還需要知道Demo1有實現類,以及實現類的信息。

為了解決這個問題,一些高明的程序員們提出了將對象的創建這一過程交給第三方去操作,而不是調用類來創建。於是乎,上述代碼就變成了:

public class Demo2
{
    public Demo1 Demo {get;set;}
    public Demo2(Demo1 demo)
    {
        Demo = demo;
    }
}

似乎並沒有什麼變化?對於Demo2來說,Demo2從此不再負責Demo1的創建,這個步驟交由Demo2的調用方去創建,Demo2從此從負責維護Demo1這個對象的大麻煩中解脫了。

但實際上構造地獄的問題還是沒有解決,只不過是通過IOC的設計將這一步后移了。這時候,那些大神們想了想,不如開發一個框架這些實體對象吧。所以就出現了很多IOC框架:AutoFac、Sping.net、Unity等。

說到IOC就不得不提一下DI(Dependency Injection)依賴注入。所謂的依賴注入就是屬性對應實例通過構造函數或者使用屬性由第三方進行賦值。也就是最後Demo2的示例代碼中的寫法。

早期IOC和DI是指一種技術,後來開始確定這是不同的描述。IOC描述的是一種設計模式,而DI是一種行為。

2. 使用asp.net core的默認IOC

在之前的ASP.NET 框架中,微軟並沒有提供默認的IOC支持。在最新的asp.net core中微軟提供了一套IOC支持,該支持在命名空間:

Microsoft.Extensions.DependencyInjection

里,在代碼中引用即可。

主要通過以下幾組方法實現:

public static IServiceCollection AddScoped<TService>(this IServiceCollection services) where TService : class;
public static IServiceCollection AddSingleton<TService>(this IServiceCollection services) where TService : class;
public static IServiceCollection AddTransient<TService>(this IServiceCollection services) where TService : class;

這裏只列出了這三組方法的一種重載版本。

這三組方法分別代表三種生命周期:

  • AddScored 表示對象的生命周期為整個Request請求
  • AddTransient 表示每次從服務容器進行請求時創建的,適合輕量級、 無狀態的服務
  • AddSingleton 表示該對象在第一次從服務容器請求后獲取,之後就不會再次初始化了

這裏每組方法只介紹了一個版本,但實際上每個方法都有以下幾個版本:

public static IServiceCollection AddXXX<TService>(this IServiceCollection services) where TService : class;
public static IServiceCollection AddXXX(this IServiceCollection services, Type serviceType, Type implementationType);
public static IServiceCollection AddXXX(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory);
public static IServiceCollection AddXXX<TService, TImplementation>(this IServiceCollection services)
            where TService : class
            where TImplementation : class, TService;
public static IServiceCollection AddXXX(this IServiceCollection services, Type serviceType);
public static IServiceCollection AddXXX<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService : class;
public static IServiceCollection AddXXX<TService, TImplementation>(this IServiceCollection services, Func<IServiceProvider, TImplementation> implementationFactory)
            where TService : class
            where TImplementation : class, TService;

其中:implementationFactory 表示通過一個Provider實現TService/TImplementation 的工廠方法。當方法指定了泛型的時候,會自動依據泛型參數獲取要注入的類型信息,如果沒有使用泛型則必須手動傳入參數類型。

asp.net core如果使用依賴注入的話,需要在Startup方法中設置,具體內容可以參照以下:

public void ConfigureServices(IServiceCollection services)
{
    //省略其他代碼
    services.AddScoped<ISysUserAuthRepository,SysUserAuthRepository>();
}

asp.net core 為DbContext提供了不同的IOC支持,AddDbContext:

public static IServiceCollection AddDbContext<TContext>(
      this IServiceCollection serviceCollection,
      Action<DbContextOptionsBuilder> optionsAction = null,
      ServiceLifetime contextLifetime = ServiceLifetime.Scoped,
      ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)
      where TContext : DbContext;

使用方法如下:

services.AddDbContext<DefaultContext>();

3. AutoFac 使用

理論上,asp.net core的IOC已經足夠好了,但是依舊原諒我的貪婪。如果有二三百個業務類需要我來設置的話,我寧願不使用IOC。因為那配置起來就是一場極其痛苦的過程。不過,可喜可賀的是AutoFac可以讓我免收這部分的困擾。

這裏簡單介紹一下如何使用AutoFac作為IOC管理:

cd Web  # 切換目錄到Web項目
dotnet package add Autofac.Extensions.DependencyInjection # 添加 AutoFac的引用

因為asp.net core 版本3更改了一些邏輯,AutoFac的引用方式發生了改變,現在不介紹之前版本的內容,以3為主。使用AutoFac需要先在 Program類里設置以下代碼:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    		Host.CreateDefaultBuilder(args)
    		.UseServiceProviderFactory(new AutofacServiceProviderFactory()) // 添加這行代碼
    		.ConfigureWebHostDefaults(webBuilder =>
			{
                webBuilder.UseStartup<Startup>();
            });

在Program類里啟用AutoFac的一個Service提供工廠類。然後在Startup類里添加如下方法:

public void ConfigureContainer(ContainerBuilder builder)
{
    builder.RegisterType<DefaultContext>().As<DbContext>()
                .WithParameter("connectStr","Data Source=./demo.db")
                .InstancePerLifetimeScope();
            

    builder.RegisterAssemblyTypes(Assembly.Load("Web"))
        .Where(t => t.BaseType.FullName.Contains("Filter"))
        .AsSelf();

    builder.RegisterAssemblyTypes(Assembly.Load("Domain"),
                    Assembly.Load("Domain.Implements"), Assembly.Load("Service"), Assembly.Load("Service.Implements"))
                .AsSelf()
                .AsImplementedInterfaces()
                .InstancePerLifetimeScope()
                .PropertiesAutowired();
}

修改:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
            {
                options.Filters.Add<UnitOfWorkFilterAttribute>();
            }).AddControllersAsServices();// 這行新增
    // 省略其他
}

4. 總結

這一篇簡單介紹了如何在Asp.net Core中啟用IOC支持,並提供了兩種方式,可以說是各有優劣。小夥伴們根據自己需要選擇。後續會為大家詳細深入AutoFac之類IOC框架的核心秘密。

更多內容煩請關注我的博客《高先生小屋》

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

作為一個Java開發你用過Jib嗎

1. 前言

JibGoogle開發的可以直接構建 Java應用的DockerOCI鏡像的類庫,以MavenGradle插件形式提供。它最騷操作的是可以在沒有Docker守護程序的情況下構建,也就是說,您不必在計算機上安裝docker守護程序!儘管Spring Boot 2.3.0.RELEASE已經推出了構建鏡像的功能,胖哥還是忍不住要試試Jib

其實最騷的還是名字。

2. Docker構建流程和Jib的構建流程

沒有對比就沒有傷害。我們還是要對比一下這兩者的構建流程。

Docker構建流程需要我們先把項目打成Jar然後編寫Dockerfile,然後使用Docker構建功能進行構建鏡像、運行容器。流程如下:

而Jib是這樣構建的:

作為一個Java開發者,不用再關心各種無關的命令和操作,只需要專註於Java,而且高效穩定以及可復用的增量構建。為什麼Jib能這麼快而高效?

傳統上,將Java應用程序與應用程序Jar一起構建為單個圖像層,而Jib的構建策略將Java應用程序分為多層,以進行更細化的增量構建。更改代碼時,僅重建更改,而不重建整個應用程序。

3. Jib構建Spring Boot應用

接下來我將演示如何將Spring Boot 應用打成鏡像並上傳到Dockerhub倉庫。

Maven工程為例,我們只需要在pom.xml中引入Jib Maven 插件。默認情況下Jib會把我們打好的鏡像上傳到Googlegcr.io倉庫,實際中我們會把打好的鏡像上傳到私有倉庫,所以我們要加一些個性化配置。這裏我以dockerhub倉庫為例添加一些個性化配置:

<plugin>
    <groupId>com.google.cloud.tools</groupId>
    <artifactId>jib-maven-plugin</artifactId>
    <version>2.4.0</version>
    <configuration>
        <!-- 相當於 Dockerfile 中的 FROM -->
        <from>
            <image>amazoncorretto:8</image>
        </from>
        <to>
            <!--構建鏡像名稱,這裏我使用maven中定義的項目名稱-->
            <image>daxus/${project.name}</image>
            <!--私有倉庫的賬號密碼-->
            <auth>
                <username>felordcn</username>
                <password>yourpassword</password>
            </auth>
            <!--Docker 鏡像的 tag 這裏使用maven定義的版本號-->
            <tags>
                <tag>
                    ${project.version}
                </tag>
            </tags>
        </to>
    </configuration>
</plugin>

然後在項目根目錄執行mvn clean compile jib:build就可以了。

其實也可以簡單引入Jib插件:

<plugin>
    <groupId>com.google.cloud.tools</groupId>
    <artifactId>jib-maven-plugin</artifactId>
    <version>2.4.0</version>
</plugin>

只不過我們的命令會更複雜一些,需要指定一些必要的參數,例如:

mvn clean compile jib:build \
    -Djib.to.image=myregistry/myimage:latest \
    -Djib.to.auth.username=$USERNAME \
    -Djib.to.auth.password=$PASSWORD

更多的定製命令可參考官方文檔:

https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#extended-usage

4. 總結

Jib使用起來非常簡單,讓開發人員以Java的風格來完成Docker鏡像的構建,能夠大大改善編程的體驗。多多關注:碼農小胖哥 獲取更多有用的編程乾貨教程。

關注公眾號:Felordcn 獲取更多資訊

個人博客:https://felord.cn

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

重學 Java 設計模式:實戰中介者模式「按照Mybatis原理手寫ORM框架,給JDBC方式操作數據庫增加中介者場景」

作者:小傅哥
博客:https://bugstack.cn – 原創系列專題文章

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言

同齡人的差距是從什麼時候拉開的

同樣的幼兒園、同樣的小學、一樣的書本、一樣的課堂,有人學習好、有人學習差。不只是上學,幾乎人生處處都是賽道,發令槍響起的時刻,也就把人生的差距拉開。編程開發這條路也是很長很寬,有人跑得快有人跑得慢。那麼你是否想起過,這一點點的差距到遙不可及的距離,是從哪一天開始的。摸摸肚子的肉,看看遠處的路,別人講的是故事,你想起的都是事故

思想沒有產品高才寫出一片的ifelse

當你承接一個需求的時候,比如;交易、訂單、營銷、保險等各類場景。如果你不熟悉這個場景下的業務模式,以及將來的拓展方向,那麼很難設計出良好可擴展的系統。再加上產品功能初建,說老闆要的急,儘快上線。作為程序員的你更沒有時間思考,整體一看現在的需求也不難,直接上手開干(一個方法兩個if語句),這樣確實滿足了當前需求。但老闆的想法多呀,產品也跟着變化快,到你這就是改改改,加加加。當然你也不客氣,回首掏就是1024個if語句!

日積月累的技術沉澱是為了厚積薄發

粗略的估算過,如果從上大學開始每天寫200行,一個月是6000行,一年算10個月話,就是6萬行,第三年出去實習的是時候就有20萬行的代碼量。如果你能做到這一點,找工作難?有時候很多事情就是靠時間積累出來的,想走捷徑有時候真的沒有。你的技術水平、你的業務能力、你身上的肉,都是一點點積累下來的,不要浪費看似很短的時間,一年年堅持下來,留下印刻青春的痕迹,多給自己武裝上一些能力。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. mysql 5.1.20
  4. 涉及工程一個,可以通過關注公眾號bugstack蟲洞棧,回復源碼下載獲取(打開獲取的鏈接,找到序號18)
工程 描述
itstack-demo-design-16-01 使用JDBC方式連接數據庫
itstack-demo-design-16-02 手寫ORM框架操作數據庫

三、中介者模式介紹

中介者模式要解決的就是複雜功能應用之間的重複調用,在這中間添加一層中介者包裝服務,對外提供簡單、通用、易擴展的服務能力。

這樣的設計模式幾乎在我們日常生活和實際業務開發中都會見到,例如;飛機降落有小姐姐在塔台喊話、無論哪個方向來的候車都從站台上下、公司的系統中有一个中台專門為你包裝所有接口和提供統一的服務等等,這些都運用了中介者模式。除此之外,你用到的一些中間件,他們包裝了底層多種數據庫的差異化,提供非常簡單的方式進行使用。

四、案例場景模擬

在本案例中我們通過模仿Mybatis手寫ORM框架,通過這樣操作數據庫學習中介者運用場景

除了這樣的中間件層使用場景外,對於一些外部接口,例如N種獎品服務,可以由中台系統進行統一包裝對外提供服務能力。也是中介者模式的一種思想體現。

在本案例中我們會把jdbc層進行包裝,讓用戶在使用數據庫服務的時候,可以和使用mybatis一樣簡單方便,通過這樣的源碼方式學習中介者模式,也方便對源碼知識的拓展學習,增強知識棧。

五、用一坨坨代碼實現

這是一種關於數據庫操作最初的方式

基本上每一個學習開發的人都學習過直接使用jdbc方式連接數據庫,進行CRUD操作。以下的例子可以當做回憶。

1. 工程結構

itstack-demo-design-16-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── JDBCUtil.java
  • 這裏的類比較簡單隻包括了一個數據庫操作類。

2. 代碼實現

public class JDBCUtil {

    private static Logger logger = LoggerFactory.getLogger(JDBCUtil.class);

    public static final String URL = "jdbc:mysql://127.0.0.1:3306/itstack-demo-design";
    public static final String USER = "root";
    public static final String PASSWORD = "123456";

    public static void main(String[] args) throws Exception {
        //1. 加載驅動程序
        Class.forName("com.mysql.jdbc.Driver");
        //2. 獲得數據庫連接
        Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
        //3. 操作數據庫
        Statement stmt = conn.createStatement();
        ResultSet resultSet = stmt.executeQuery("SELECT id, name, age, createTime, updateTime FROM user");
        //4. 如果有數據 resultSet.next() 返回true
        while (resultSet.next()) {
            logger.info("測試結果 姓名:{} 年齡:{}", resultSet.getString("name"),resultSet.getInt("age"));
        }
    }

}
  • 以上是使用JDBC的方式進行直接操作數據庫,幾乎大家都使用過這樣的方式。

3. 測試結果

15:38:10.919 [main] INFO  org.itstack.demo.design.JDBCUtil - 測試結果 姓名:水水 年齡:18
15:38:10.922 [main] INFO  org.itstack.demo.design.JDBCUtil - 測試結果 姓名:豆豆 年齡:18
15:38:10.922 [main] INFO  org.itstack.demo.design.JDBCUtil - 測試結果 姓名:花花 年齡:19

Process finished with exit code 0
  • 從測試結果可以看到這裏已經查詢到了數據庫中的數據。只不過如果在全部的業務開發中都這樣實現,會非常的麻煩。

六、中介模式開發ORM框架

`接下來就使用中介模式的思想完成模仿Mybatis的ORM框架開發~

1. 工程結構

itstack-demo-design-16-02
└── src
    ├── main
    │   ├── java
    │   │   └── org.itstack.demo.design
    │   │       ├── dao
    │   │       │	├── ISchool.java
    │   │       │	└── IUserDao.java
    │   │       ├── mediator
    │   │       │	├── Configuration.java
    │   │       │	├── DefaultSqlSession.java
    │   │       │	├── DefaultSqlSessionFactory.java
    │   │       │	├── Resources.java
    │   │       │	├── SqlSession.java
    │   │       │	├── SqlSessionFactory.java
    │   │       │	├── SqlSessionFactoryBuilder.java
    │   │       │	└── SqlSessionFactoryBuilder.java
    │   │       └── po
    │   │         	├── School.java
    │   │         	└── User.java
    │   └── resources
    │       ├── mapper
    │       │   ├── School_Mapper.xml
    │       │   └── User_Mapper.xml
    │       └── mybatis-config-datasource.xml
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

中介者模式模型結構

  • 以上是對ORM框架實現的核心類,包括了;加載配置文件、對xml解析、獲取數據庫session、操作數據庫以及結果返回。
  • 左上是對數據庫的定義和處理,基本包括我們常用的方法;<T> T selectOne<T> List<T> selectList等。
  • 右側藍色部分是對數據庫配置的開啟session的工廠處理類,這裏的工廠會操作DefaultSqlSession
  • 之後是紅色地方的SqlSessionFactoryBuilder,這個類是對數據庫操作的核心類;處理工廠、解析文件、拿到session等。

接下來我們就分別介紹各個類的功能實現過程。

2. 代碼實現

2.1 定義SqlSession接口

public interface SqlSession {

    <T> T selectOne(String statement);

    <T> T selectOne(String statement, Object parameter);

    <T> List<T> selectList(String statement);

    <T> List<T> selectList(String statement, Object parameter);

    void close();
}
  • 這裏定義了對數據庫操作的查詢接口,分為查詢一個結果和查詢多個結果,同時包括有參數和沒有參數的方法。

2.2 SqlSession具體實現類

public class DefaultSqlSession implements SqlSession {

    private Connection connection;
    private Map<String, XNode> mapperElement;

    public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {
        this.connection = connection;
        this.mapperElement = mapperElement;
    }

    @Override
    public <T> T selectOne(String statement) {
        try {
            XNode xNode = mapperElement.get(statement);
            PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
            ResultSet resultSet = preparedStatement.executeQuery();
            List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
            return objects.get(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <T> List<T> selectList(String statement) {
        XNode xNode = mapperElement.get(statement);
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
            ResultSet resultSet = preparedStatement.executeQuery();
            return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // ...

    private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {
        List<T> list = new ArrayList<>();
        try {
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            // 每次遍歷行值
            while (resultSet.next()) {
                T obj = (T) clazz.newInstance();
                for (int i = 1; i <= columnCount; i++) {
                    Object value = resultSet.getObject(i);
                    String columnName = metaData.getColumnName(i);
                    String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
                    Method method;
                    if (value instanceof Timestamp) {
                        method = clazz.getMethod(setMethod, Date.class);
                    } else {
                        method = clazz.getMethod(setMethod, value.getClass());
                    }
                    method.invoke(obj, value);
                }
                list.add(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    @Override
    public void close() {
        if (null == connection) return;
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  • 這裏包括了接口定義的方法實現,也就是包裝了jdbc層。
  • 通過這樣的包裝可以讓對數據庫的jdbc操作隱藏起來,外部調用的時候對入參、出參都有內部進行處理。

2.3 定義SqlSessionFactory接口

public interface SqlSessionFactory {

    SqlSession openSession();

}
  • 開啟一個SqlSession, 這幾乎是大家在平時的使用中都需要進行操作的內容。雖然你看不見,但是當你有數據庫操作的時候都會獲取每一次執行的SqlSession

2.4 SqlSessionFactory具體實現類

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(configuration.connection, configuration.mapperElement);
    }

}
  • DefaultSqlSessionFactory,是使用mybatis最常用的類,這裏我們簡單的實現了一個版本。
  • 雖然是簡單的版本,但是包括了最基本的核心思路。當開啟SqlSession時會進行返回一個DefaultSqlSession
  • 這個構造函數中向下傳遞了Configuration配置文件,在這個配置文件中包括;Connection connectionMap<String, String> dataSourceMap<String, XNode> mapperElement。如果有你閱讀過Mybatis源碼,對這個就不會陌生。

2.5 SqlSessionFactoryBuilder實現

public class SqlSessionFactoryBuilder {

    public DefaultSqlSessionFactory build(Reader reader) {
        SAXReader saxReader = new SAXReader();
        try {
            saxReader.setEntityResolver(new XMLMapperEntityResolver());
            Document document = saxReader.read(new InputSource(reader));
            Configuration configuration = parseConfiguration(document.getRootElement());
            return new DefaultSqlSessionFactory(configuration);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return null;
    }

    private Configuration parseConfiguration(Element root) {
        Configuration configuration = new Configuration();
        configuration.setDataSource(dataSource(root.selectNodes("//dataSource")));
        configuration.setConnection(connection(configuration.dataSource));
        configuration.setMapperElement(mapperElement(root.selectNodes("mappers")));
        return configuration;
    }

    // 獲取數據源配置信息
    private Map<String, String> dataSource(List<Element> list) {
        Map<String, String> dataSource = new HashMap<>(4);
        Element element = list.get(0);
        List content = element.content();
        for (Object o : content) {
            Element e = (Element) o;
            String name = e.attributeValue("name");
            String value = e.attributeValue("value");
            dataSource.put(name, value);
        }
        return dataSource;
    }

    private Connection connection(Map<String, String> dataSource) {
        try {
            Class.forName(dataSource.get("driver"));
            return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    // 獲取SQL語句信息
    private Map<String, XNode> mapperElement(List<Element> list) {
        Map<String, XNode> map = new HashMap<>();

        Element element = list.get(0);
        List content = element.content();
        for (Object o : content) {
            Element e = (Element) o;
            String resource = e.attributeValue("resource");

            try {
                Reader reader = Resources.getResourceAsReader(resource);
                SAXReader saxReader = new SAXReader();
                Document document = saxReader.read(new InputSource(reader));
                Element root = document.getRootElement();
                //命名空間
                String namespace = root.attributeValue("namespace");

                // SELECT
                List<Element> selectNodes = root.selectNodes("select");
                for (Element node : selectNodes) {
                    String id = node.attributeValue("id");
                    String parameterType = node.attributeValue("parameterType");
                    String resultType = node.attributeValue("resultType");
                    String sql = node.getText();

                    // ? 匹配
                    Map<Integer, String> parameter = new HashMap<>();
                    Pattern pattern = Pattern.compile("(#\\{(.*?)})");
                    Matcher matcher = pattern.matcher(sql);
                    for (int i = 1; matcher.find(); i++) {
                        String g1 = matcher.group(1);
                        String g2 = matcher.group(2);
                        parameter.put(i, g2);
                        sql = sql.replace(g1, "?");
                    }

                    XNode xNode = new XNode();
                    xNode.setNamespace(namespace);
                    xNode.setId(id);
                    xNode.setParameterType(parameterType);
                    xNode.setResultType(resultType);
                    xNode.setSql(sql);
                    xNode.setParameter(parameter);

                    map.put(namespace + "." + id, xNode);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }

        }
        return map;
    }

}
  • 在這個類中包括的核心方法有;build(構建實例化元素)parseConfiguration(解析配置)dataSource(獲取數據庫配置)connection(Map<String, String> dataSource) (鏈接數據庫)mapperElement (解析sql語句)
  • 接下來我們分別介紹這樣的幾個核心方法。

build(構建實例化元素)

這個類主要用於創建解析xml文件的類,以及初始化SqlSession工廠類DefaultSqlSessionFactory。另外需要注意這段代碼saxReader.setEntityResolver(new XMLMapperEntityResolver());,是為了保證在不聯網的時候一樣可以解析xml,否則會需要從互聯網獲取dtd文件。

parseConfiguration(解析配置)

是對xml中的元素進行獲取,這裏主要獲取了;dataSourcemappers,而這兩個配置一個是我們數據庫的鏈接信息,另外一個是對數據庫操作語句的解析。

connection(Map<String, String> dataSource) (鏈接數據庫)

鏈接數據庫的地方和我們常見的方式是一樣的;Class.forName(dataSource.get("driver"));,但是這樣包裝以後外部是不需要知道具體的操作。同時當我們需要鏈接多套數據庫的時候,也是可以在這裏擴展。

mapperElement (解析sql語句)

這部分代碼塊內容相對來說比較長,但是核心的點就是為了解析xml中的sql語句配置。在我們平常的使用中基本都會配置一些sql語句,也有一些入參的佔位符。在這裏我們使用正則表達式的方式進行解析操作。

解析完成的sql語句就有了一個名稱和sql的映射關係,當我們進行數據庫操作的時候,這個組件就可以通過映射關係獲取到對應sql語句進行操作。

3. 測試驗證

在測試之前需要導入sql語句到數據庫中;

  • 庫名:itstack-demo-design
  • 表名:userschool
CREATE TABLE school ( id bigint NOT NULL AUTO_INCREMENT, name varchar(64), address varchar(256), createTime datetime, updateTime datetime, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into school (id, name, address, createTime, updateTime) values (1, '北京大學', '北京市海淀區頤和園路5號', '2019-10-18 13:35:57', '2019-10-18 13:35:57');
insert into school (id, name, address, createTime, updateTime) values (2, '南開大學', '中國天津市南開區衛津路94號', '2019-10-18 13:35:57', '2019-10-18 13:35:57');
insert into school (id, name, address, createTime, updateTime) values (3, '同濟大學', '上海市彰武路1號同濟大廈A樓7樓7區', '2019-10-18 13:35:57', '2019-10-18 13:35:57');
CREATE TABLE user ( id bigint(11) NOT NULL AUTO_INCREMENT, name varchar(32), age int(4), address varchar(128), entryTime datetime, remark varchar(64), createTime datetime, updateTime datetime, status int(4) DEFAULT '0', dateTime varchar(64), PRIMARY KEY (id), INDEX idx_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into user (id, name, age, address, entryTime, remark, createTime, updateTime, status, dateTime) values (1, '水水', 18, '吉林省榆樹市黑林鎮尹家村5組', '2019-12-22 00:00:00', '無', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 0, '20200309');
insert into user (id, name, age, address, entryTime, remark, createTime, updateTime, status, dateTime) values (2, '豆豆', 18, '遼寧省大連市清河灣司馬道407路', '2019-12-22 00:00:00', '無', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 1, null);
insert into user (id, name, age, address, entryTime, remark, createTime, updateTime, status, dateTime) values (3, '花花', 19, '遼寧省大連市清河灣司馬道407路', '2019-12-22 00:00:00', '無', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 0, '20200310');

3.1 創建數據庫對象類

用戶類

public class User {

    private Long id;
    private String name;
    private Integer age;
    private Date createTime;
    private Date updateTime;
    
    // ... get/set
}

學校類

public class School {

    private Long id;
    private String name;
    private String address;
    private Date createTime;
    private Date updateTime;  
  
    // ... get/set
}
  • 這兩個類都非常簡單,就是基本的數據庫信息。

3.2 創建DAO包

用戶Dao

public interface IUserDao {

     User queryUserInfoById(Long id);

}

學校Dao

public interface ISchoolDao {

    School querySchoolInfoById(Long treeId);

}

3.3 ORM配置文件

鏈接配置

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/itstack_demo_design?useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/User_Mapper.xml"/>
        <mapper resource="mapper/School_Mapper.xml"/>
    </mappers>

</configuration>
  • 這個配置與我們平常使用的mybatis基本是一樣的,包括了數據庫的連接池信息以及需要引入的mapper映射文件。

操作配置(用戶)

<mapper namespace="org.itstack.demo.design.dao.IUserDao">

    <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="org.itstack.demo.design.po.User">
        SELECT id, name, age, createTime, updateTime
        FROM user
        where id = #{id}
    </select>

    <select id="queryUserList" parameterType="org.itstack.demo.design.po.User" resultType="org.itstack.demo.design.po.User">
        SELECT id, name, age, createTime, updateTime
        FROM user
        where age = #{age}
    </select>

</mapper>

操作配置(學校)

<mapper namespace="org.itstack.demo.design.dao.ISchoolDao">

    <select id="querySchoolInfoById" resultType="org.itstack.demo.design.po.School">
        SELECT id, name, address, createTime, updateTime
        FROM school
        where id = #{id}
    </select>

</mapper>

3.4 單個結果查詢測試

@Test
public void test_queryUserInfoById() {
    String resource = "mybatis-config-datasource.xml";
    Reader reader;
    try {
        reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
        SqlSession session = sqlMapper.openSession();
        try {
            User user = session.selectOne("org.itstack.demo.design.dao.IUserDao.queryUserInfoById", 1L);
            logger.info("測試結果:{}", JSON.toJSONString(user));
        } finally {
            session.close();
            reader.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
  • 這裏的使用方式和Mybatis是一樣的,都包括了;資源加載和解析、SqlSession工廠構建、開啟SqlSession以及最後執行查詢操作selectOne

測試結果

16:56:51.831 [main] INFO  org.itstack.demo.design.demo.ApiTest - 測試結果:{"age":18,"createTime":1576944000000,"id":1,"name":"水水","updateTime":1576944000000}

Process finished with exit code 0
  • 從結果上看已經滿足了我們的查詢需求。

3.5 集合結果查詢測試

@Test
public void test_queryUserList() {
    String resource = "mybatis-config-datasource.xml";
    Reader reader;
    try {
        reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
        SqlSession session = sqlMapper.openSession();
        try {
            User req = new User();
            req.setAge(18);
            List<User> userList = session.selectList("org.itstack.demo.design.dao.IUserDao.queryUserList", req);
            logger.info("測試結果:{}", JSON.toJSONString(userList));
        } finally {
            session.close();
            reader.close();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
  • 這個測試內容與以上只是查詢方法有所不同;session.selectList,是查詢一個集合結果。

測試結果

16:58:13.963 [main] INFO  org.itstack.demo.design.demo.ApiTest - 測試結果:[{"age":18,"createTime":1576944000000,"id":1,"name":"水水","updateTime":1576944000000},{"age":18,"createTime":1576944000000,"id":2,"name":"豆豆","updateTime":1576944000000}]

Process finished with exit code 0
  • 測試驗證集合的結果也是正常的,目前位置測試全部通過。

七、總結

  • 以上通過中介者模式的設計思想我們手寫了一個ORM框架,隱去了對數據庫操作的複雜度,讓外部的調用方可以非常簡單的進行操作數據庫。這也是我們平常使用的Mybatis的原型,在我們日常的開發使用中,只需要按照配置即可非常簡單的操作數據庫。
  • 除了以上這種組件模式的開發外,還有服務接口的包裝也可以使用中介者模式來實現。比如你們公司有很多的獎品接口需要在營銷活動中對接,那麼可以把這些獎品接口統一收到中台開發一個獎品中心,對外提供服務。這樣就不需要每一個需要對接獎品的接口,都去找具體的提供者,而是找中台服務即可。
  • 在上述的實現和測試使用中可以看到,這種模式的設計滿足了;單一職責開閉原則,也就符合了迪米特原則,即越少人知道越好。外部的人只需要按照需求進行調用,不需要知道具體的是如何實現的,複雜的一面已經有組件合作服務平台處理。

八、推薦閱讀

  • 1. 重學 Java 設計模式:實戰工廠方法模式「多種類型商品不同接口,統一發獎服務搭建場景」
  • 2. 重學 Java 設計模式:實戰原型模式「上機考試多套試,每人題目和答案亂序排列場景」
  • 3. 重學 Java 設計模式:實戰橋接模式「多支付渠道(微信、支付寶)與多支付模式(刷臉、指紋)場景」
  • 4. 重學 Java 設計模式:實戰組合模式「營銷差異化人群發券,決策樹引擎搭建場景」
  • 5. 重學 Java 設計模式:實戰外觀模式「基於SpringBoot開發門面模式中間件,統一控制接口白名單場景」
  • 6. 重學 Java 設計模式:實戰享元模式「基於Redis秒殺,提供活動與庫存信息查詢場景」

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

為什麼建議你使用枚舉?

枚舉是 JDK 1.5 新增的數據類型,使用枚舉我們可以很好的描述一些特定的業務場景,比如一年中的春、夏、秋、冬,還有每周的周一到周天,還有各種顏色,以及可以用它來描述一些狀態信息,比如錯誤碼等。

枚舉類型不止存在在 Java 語言中,在其它語言中也都能找到它的身影,例如 C# 和 Python 等,但我發現在實際的項目中使用枚舉的人很少,所以本文就來聊一聊枚舉的相關內容,好讓朋友們對枚舉有一個大概的印象,這樣在編程時起碼還能想到有“枚舉”這樣一個類型。

本文的結構目錄如下:

枚舉的 7 種使用方法

很多人不使用枚舉的一個重要的原因是對枚舉不夠熟悉,那麼我們就先從枚舉的 7 種使用方法說起。

用法一:常量

在 JDK 1.5 之前,我們定義常量都是 public static final... ,但有了枚舉,我們就可以把這些常量定義成一個枚舉類了,實現代碼如下:

public enum ColorEnum {  
  RED, GREEN, BLANK, YELLOW  
} 

用法二:switch

將枚舉用在 switch 判斷中,使得代碼可讀性更高了,實現代碼如下:

enum ColorEnum {
    GREEN, YELLOW, RED
}
public class ColorTest {
    ColorEnum color = ColorEnum.RED;

    public void change() {
        switch (color) {
            case RED:
                color = ColorEnum.GREEN;
                break;
            case YELLOW:
                color = ColorEnum.RED;
                break;
            case GREEN:
                color = ColorEnum.YELLOW;
                break;
        }
    }
}

用法三:枚舉中增加方法

我們可以在枚舉中增加一些方法,讓枚舉具備更多的特性,實現代碼如下:

public class EnumTest {
    public static void main(String[] args) {
        ErrorCodeEnum errorCode = ErrorCodeEnum.SUCCESS;
        System.out.println("狀態碼:" + errorCode.code() + 
                           " 狀態信息:" + errorCode.msg());
    }
}

enum ErrorCodeEnum {
    SUCCESS(1000, "success"),
    PARAM_ERROR(1001, "parameter error"),
    SYS_ERROR(1003, "system error"),
    NAMESPACE_NOT_FOUND(2001, "namespace not found"),
    NODE_NOT_EXIST(3002, "node not exist"),
    NODE_ALREADY_EXIST(3003, "node already exist"),
    UNKNOWN_ERROR(9999, "unknown error");

    private int code;
    private String msg;

    ErrorCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int code() {
        return code;
    }

    public String msg() {
        return msg;
    }

    public static ErrorCodeEnum getErrorCode(int code) {
        for (ErrorCodeEnum it : ErrorCodeEnum.values()) {
            if (it.code() == code) {
                return it;
            }
        }
        return UNKNOWN_ERROR;
    }
}

以上程序的執行結果為:

狀態碼:1000 狀態信息:success

用法四:覆蓋枚舉方法

我們可以覆蓋一些枚舉中的方法用於實現自己的業務,比如我們可以覆蓋 toString() 方法,實現代碼如下:

public class EnumTest {
    public static void main(String[] args) {
        ColorEnum colorEnum = ColorEnum.RED;
        System.out.println(colorEnum.toString());
    }
}

enum ColorEnum {
    RED("紅色", 1), GREEN("綠色", 2), BLANK("白色", 3), YELLOW("黃色", 4);
    //  成員變量
    private String name;
    private int index;

    //  構造方法
    private ColorEnum(String name, int index) {
        this.name = name;
        this.index = index;
    }

    //覆蓋方法
    @Override
    public String toString() {
        return this.index + ":" + this.name;
    }
}

以上程序的執行結果為:

1:紅色

用法五:實現接口

枚舉類可以用來實現接口,但不能用於繼承類,因為枚舉默認繼承了 java.lang.Enum 類,在 Java 語言中允許實現多接口,但不能繼承多個父類,實現代碼如下:

public class EnumTest {
    public static void main(String[] args) {
        ColorEnum colorEnum = ColorEnum.RED;
        colorEnum.print();
        System.out.println("顏色:" + colorEnum.getInfo());
    }
}

interface Behaviour {
    void print();

    String getInfo();
}

enum ColorEnum implements Behaviour {
    RED("紅色", 1), GREEN("綠色", 2), BLANK("白色", 3), YELLOW("黃色", 4);
    private String name;
    private int index;

    private ColorEnum(String name, int index) {
        this.name = name;
        this.index = index;
    }

    @Override
    public void print() {
        System.out.println(this.index + ":" + this.name);
    }

    @Override
    public String getInfo() {
        return this.name;
    }
}

以上程序的執行結果為:

1:紅色

顏色:紅色

用法六:在接口中組織枚舉類

我們可以在一個接口中創建多個枚舉類,用它可以很好的實現“多態”,也就是說我們可以將擁有相同特性,但又有細微實現差別的枚舉類聚集在一個接口中,實現代碼如下:

public class EnumTest {
    public static void main(String[] args) {
        // 賦值第一個枚舉類
        ColorInterface colorEnum = ColorInterface.ColorEnum.RED;
        System.out.println(colorEnum);
        // 賦值第二個枚舉類
        colorEnum = ColorInterface.NewColorEnum.NEW_RED;
        System.out.println(colorEnum);
    }
}

interface ColorInterface {
    enum ColorEnum implements ColorInterface {
        GREEN, YELLOW, RED
    }
    enum NewColorEnum implements ColorInterface {
        NEW_GREEN, NEW_YELLOW, NEW_RED
    }
}

以上程序的執行結果為:

RED

NEW_RED

用法七:使用枚舉集合

在 Java 語言中和枚舉類相關的,還有兩個枚舉集合類 java.util.EnumSetjava.util.EnumMap,使用它們可以實現更多的功能。

使用 EnumSet 可以保證元素不重複,並且能獲取指定範圍內的元素,示例代碼如下:

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

public class EnumTest {
    public static void main(String[] args) {
        List<ColorEnum> list = new ArrayList<ColorEnum>();
        list.add(ColorEnum.RED);
        list.add(ColorEnum.RED);  // 重複元素
        list.add(ColorEnum.YELLOW);
        list.add(ColorEnum.GREEN);
        // 去掉重複數據
        EnumSet<ColorEnum> enumSet = EnumSet.copyOf(list);
        System.out.println("去重:" + enumSet);

        // 獲取指定範圍的枚舉(獲取所有的失敗狀態)
        EnumSet<ErrorCodeEnum> errorCodeEnums = EnumSet.range(ErrorCodeEnum.ERROR, ErrorCodeEnum.UNKNOWN_ERROR);
        System.out.println("所有失敗狀態:" + errorCodeEnums);
    }
}

enum ColorEnum {
    RED("紅色", 1), GREEN("綠色", 2), BLANK("白色", 3), YELLOW("黃色", 4);
    private String name;
    private int index;

    private ColorEnum(String name, int index) {
        this.name = name;
        this.index = index;
    }
}

enum ErrorCodeEnum {
    SUCCESS(1000, "success"),
    ERROR(2001, "parameter error"),
    SYS_ERROR(2002, "system error"),
    NAMESPACE_NOT_FOUND(2003, "namespace not found"),
    NODE_NOT_EXIST(3002, "node not exist"),
    NODE_ALREADY_EXIST(3003, "node already exist"),
    UNKNOWN_ERROR(9999, "unknown error");

    private int code;
    private String msg;

    ErrorCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int code() {
        return code;
    }

    public String msg() {
        return msg;
    }
}

以上程序的執行結果為:

去重:[RED, GREEN, YELLOW]

所有失敗狀態:[ERROR, SYS_ERROR, NAMESPACE_NOT_FOUND, NODE_NOT_EXIST, NODE_ALREADY_EXIST, UNKNOWN_ERROR]

EnumMapHashMap 類似,不過它是一個專門為枚舉設計的 Map 集合,相比 HashMap 來說它的性能更高,因為它內部放棄使用鏈表和紅黑樹的結構,採用數組作為數據存儲的結構。

EnumMap 基本使用示例如下:

import java.util.EnumMap;

public class EnumTest {
    public static void main(String[] args) {
        EnumMap<ColorEnum, String> enumMap = new EnumMap<>(ColorEnum.class);
        enumMap.put(ColorEnum.RED, "紅色");
        enumMap.put(ColorEnum.GREEN, "綠色");
        enumMap.put(ColorEnum.BLANK, "白色");
        enumMap.put(ColorEnum.YELLOW, "黃色");
        System.out.println(ColorEnum.RED + ":" + enumMap.get(ColorEnum.RED));
    }
}

enum ColorEnum {
    RED, GREEN, BLANK, YELLOW;
}

以上程序的執行結果為:

RED:紅色

使用注意事項

阿里《Java開發手冊》對枚舉的相關規定如下,我們在使用時需要稍微注意一下。

【強制】所有的枚舉類型字段必須要有註釋,說明每個數據項的用途。

【參考】枚舉類名帶上 Enum 後綴,枚舉成員名稱需要全大寫,單詞間用下劃線隔開。說明:枚舉其實就是特殊的常量類,且構造方法被默認強制是私有。正例:枚舉名字為 ProcessStatusEnum 的成員名稱:SUCCESS / UNKNOWN_REASON。

假如不使用枚舉

在枚舉沒有誕生之前,也就是 JDK 1.5 版本之前,我們通常會使用 int 常量來表示枚舉,實現代碼如下:

public static final int COLOR_RED = 1;
public static final int COLOR_BLUE = 2;
public static final int COLOR_GREEN = 3;

但是使用 int 類型可能存在兩個問題:

第一, int 類型本身並不具備安全性,假如某個程序員在定義 int 時少些了一個 final 關鍵字,那麼就會存在被其他人修改的風險,而反觀枚舉類,它“天然”就是一個常量類,不存在被修改的風險(原因詳見下半部分);

第二,使用 int 類型的語義不夠明確,比如我們在控制台打印時如果只輸出 1…2…3 這樣的数字,我們肯定不知道它代表的是什麼含義。

那有人就說了,那就使用常量字符唄,這總不會還不知道語義吧?實現示例代碼如下:

public static final String COLOR_RED = "RED";
public static final String COLOR_BLUE = "BLUE";
public static final String COLOR_GREEN = "GREEN";

但是這樣同樣存在一個問題,有些初級程序員會不按套路出牌,他們可能會直接使用字符串的值進行比較,而不是直接使用枚舉的字段,實現示例代碼如下:

public class EnumTest {
    public static final String COLOR_RED = "RED";
    public static final String COLOR_BLUE = "BLUE";
    public static final String COLOR_GREEN = "GREEN";
    public static void main(String[] args) {
        String color = "BLUE";
        if ("BLUE".equals(color)) {
            System.out.println("藍色");
        }
    }
}

這樣當我們修改了枚舉中的值,那程序就涼涼了。

枚舉使用場景

枚舉的常見使用場景是單例,它的完整實現代碼如下:

public class Singleton {
    // 枚舉類型是線程安全的,並且只會裝載一次
    private enum SingletonEnum {
        INSTANCE;
        // 聲明單例對象
        private final Singleton instance;
        // 實例化
        SingletonEnum() {
            instance = new Singleton();
        }
        private Singleton getInstance() {
            return instance;
        }
    }
    // 獲取實例(單例對象)
    public static Singleton getInstance() {
        return SingletonEnum.INSTANCE.getInstance();
    }
    private Singleton() {
    }
    // 類方法
    public void sayHi() {
        System.out.println("Hi,Java.");
    }
}
class SingletonTest {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        singleton.sayHi();
    }
}

因為枚舉只會在類加載時裝載一次,所以它是線程安全的,這也是《Effective Java》作者極力推薦使用枚舉來實現單例的主要原因。

知識擴展

枚舉為什麼是線程安全的?

這一點要從枚舉最終生成的字節碼說起,首先我們先來定義一個簡單的枚舉類:

public enum ColorEnumTest {
    RED, GREEN, BLANK, YELLOW;
}

然後我們再將上面的那段代碼編譯為字節碼,具體內容如下:

public final class ColorEnumTest extends java.lang.Enum<ColorEnumTest> {
  public static final ColorEnumTest RED;
  public static final ColorEnumTest GREEN;
  public static final ColorEnumTest BLANK;
  public static final ColorEnumTest YELLOW;
  public static ColorEnumTest[] values();
  public static ColorEnumTest valueOf(java.lang.String);
  static {};
}

從上述結果可以看出枚舉類最終會被編譯為被 final 修飾的普通類,它的所有屬性也都會被 staticfinal 關鍵字修飾,所以枚舉類在項目啟動時就會被 JVM 加載並初始化,而這個執行過程是線程安全的,所以枚舉類也是線程安全的類。

小貼士:代碼反編譯的過程是先用 javac 命令將 java 代碼編譯字節碼(.class),再使用 javap 命令查看編譯的字節碼。

枚舉比較小技巧

我們在枚舉比較時使用 == 就夠了,因為枚舉類是在程序加載時就創建了(它並不是 new 出來的),並且枚舉類不允許在外部直接使用 new 關鍵字來創建枚舉實例,所以我們在使用枚舉類時本質上只有一個對象,因此在枚舉比較時使用 == 就夠了。

並且我們在查看枚舉的 equlas() 源碼會發現,它的內部其實還是直接調用了 == 方法,源碼如下:

public final boolean equals(Object other) {
    return this==other;
}

總結

本文我們介紹了枚舉類的 7 種使用方法:常量、switch、枚舉中添加方法、覆蓋枚舉方法、實現接口、在接口中組織枚舉類和使用枚舉集合等,然後講了如果不使用枚舉類使用 int 類型和 String 類型存在的一些弊端:語義不夠清晰、容易被修改、存在被誤用的風險,所以我們在適合的環境下應該盡量使用枚舉類。並且我們還講了枚舉類的使用場景——單例,以及枚舉類為什麼是安全的,最後我們講了枚舉比較的小技巧,希望本文對你有幫助。

查看 & 鳴謝

https://www.iteye.com/blog/softbeta-1185573

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!