<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          架構(gòu)師日記-聊聊開發(fā)必掌握的那些實(shí)踐技能

          共 23628字,需瀏覽 48分鐘

           ·

          2023-10-08 09:34



          一、引言

          盡管軟件開發(fā)一直致力于追求高效、可讀性強(qiáng)、易于維護(hù)的特性,但這些特性卻像是一個(gè)不可能三角,相互交織,此消彼長。就像底層語言(如匯編和C語言)能夠保持高效的運(yùn)行性能,但在可讀性和維護(hù)性方面卻存在短板和劣勢;而高級(jí)語言(如Java和Python)在可讀性和可維護(hù)性方面表現(xiàn)出色,但在執(zhí)行效率方面卻存在不足。

          構(gòu)建語言生態(tài)的優(yōu)勢,彌補(bǔ)其存在短板,始終是編程語言的一個(gè)演進(jìn)方向。

          不同編程語言,擁有不同的特性和規(guī)約,下面就以JAVA語言為例,細(xì)數(shù)那些開發(fā)過程中容易被人忽略,但必須掌握的知識(shí)點(diǎn)和實(shí)踐技能。


          二、基礎(chǔ)篇

          1999年,美國太空總署(NASA)的火星任務(wù)失?。涸谶@次任務(wù)中,火星氣候探測者號(hào)上的飛行系統(tǒng)軟件使用公制單位牛頓計(jì)算推進(jìn)器動(dòng)力,而地面人員輸入的方向校正量和推進(jìn)器參數(shù)則使用英制單位磅力,導(dǎo)致探測器進(jìn)入大氣層的高度有誤,最終瓦解碎裂。

          這是由于國際標(biāo)準(zhǔn)(牛)和本土化(磅)的沖突導(dǎo)致的一起事故。由此引出了程序需要關(guān)注可維護(hù)性這個(gè)話題,由于軟件生產(chǎn)往往需要多人協(xié)作,可維護(hù)性正是協(xié)作共識(shí)里的重要一環(huán)。關(guān)于這方面,讓人最容易想到的就是命名和注釋兩個(gè)方面了,下面就展開來探討一下。

          2.1 關(guān)于命名

          按照閱讀習(xí)慣,程序的變量命名法都需要克服單詞間的空格問題,從而把不同單詞串連起來,最終達(dá)到創(chuàng)造出一種易于閱讀的新“單詞”的效果。常見的命名方法有以下幾種:
          • 蛇形命名法(snake case):又叫下劃線命名法,使用下劃線,單詞小寫,比如:my_system;
          • 駝峰命名法(camel case):按照單詞首字母區(qū)分大小寫,又可細(xì)分為大駝峰命名法和小駝峰命名法,比如:MySystem,mySystem;
          • 匈牙利命名法(HN case):屬性+類型+描述,比如:nLength,g_cch,hwnd;
          • 帕斯卡命名法(Pascal case):全部首字母大寫,等同于大駝峰命名法,比如:MySystem;
          • 脊柱命名法(spinal case):使用中劃線,比如:my-system;
          • 自由命名法(studly caps):大小寫混雜,無簡明規(guī)則,比如:mySYSTEM,MYSystem;
          按照受眾量與知名程度排名,駝峰命名法和蛇形命名法更受到大家的歡迎,畢竟它們在可讀性,易寫性等方面比較有優(yōu)勢。

          2.1.1 命名字典

          見名知意:好的命名就是一種注釋。
          建議研發(fā)同學(xué)將業(yè)內(nèi)常見業(yè)務(wù)場景的命名熟記,當(dāng)然,已經(jīng)有人幫我們總結(jié)過了,這里不再做過多的說明。這里摘錄如下,可供參考:
          管理類命名:Bootstrap,Starter,Processor,Manager,Holder,F(xiàn)actory,Provider,Registrar,Engine,Service,Task
          傳播類命名:Context,Propagator
          回調(diào)類命名:Handler,Callback,Trigger,Listener,Aware
          監(jiān)控類命名:Metric,Estimator,Accumulator,Tracker
          內(nèi)存管理類命名:Allocator,Chunk,Arena,Pool
          過濾檢測類命名:Pipeline,Chain,F(xiàn)ilter,Interceptor,Evaluator,Detector
          結(jié)構(gòu)類命名:Cache,Buffer,Composite,Wrapper,Option, Param,Attribute,Tuple,Aggregator,Iterator,Batch,Limiter
          常見設(shè)計(jì)模式命名:Strategy,Adapter,Action,Command,Event,Delegate,Builder,Template,Proxy
          解析類命名:Converter,Resolver,Parser,Customizer,F(xiàn)ormatter
          網(wǎng)絡(luò)類命名:Packet,Encoder、Decoder、Codec,Request,Response
          CRUD命名:Controller,Service,Repository
          輔助類命名:Util,Helper
          其他類命名:Mode,Type,Invoker,Invocation,Initializer,F(xiàn)uture,Promise,Selector,Reporter,Constants,Accessor,Generator

          2.1.2 命名實(shí)踐

          工程通用命名規(guī)則都有哪些呢?不同的語言可能會(huì)有不同的習(xí)慣,以Java語言的駝峰命名規(guī)范舉例:
          1. 項(xiàng)目名全部小寫;
          2. 包名全部小寫;
          3. 類名首字母大寫,其余組成詞首字母依次大寫;
          4. 變量名,方法名首字母小寫,如果名稱由多個(gè)單詞組成,除首字母外的每個(gè)單詞首字母都要大寫;
          5. 常量名全部大寫;
          規(guī)范比較抽象,先來看看不好的命名有哪些呢?
          1. 自帶混淆功能的變量名:String zhrmghg = "極致縮寫型";
          2. 沒有意義的萬能變量名:String a,b,c="愛誰誰型";
          3. 長串拼音變量名:String HuaBuHua = "考古型";
          4. 各種符號(hào)混用:String $my_first_name_ = "打死記不住型";
          5. 大小寫,數(shù)字,縮寫混亂:String waitRPCResponse1 = "極易出錯(cuò)型";
          除了標(biāo)準(zhǔn)的規(guī)范之外,在實(shí)際的開發(fā)過程中還會(huì)有一些困擾我們的實(shí)際案例。
          1. 在定義一個(gè)成員變量的時(shí)候,到底是使用包裝類型還是使用基本數(shù)據(jù)類型呢?
          包裝類和基本數(shù)據(jù)類型的默認(rèn)值是不一樣的,前者是null,后者依據(jù)不同類型其默認(rèn)值也不一樣。從數(shù)據(jù)嚴(yán)謹(jǐn)?shù)慕嵌葋碇v,包裝類的null值能夠表示額外信息,從而更加安全。比如可以規(guī)避基本類型的自動(dòng)拆箱,導(dǎo)致的NPE風(fēng)險(xiǎn)以及業(yè)務(wù)邏輯處理異常風(fēng)險(xiǎn)。所以成員變量必須使用包裝數(shù)據(jù)類型,基本數(shù)據(jù)類型則在局部變量的場景下使用。
          2. 為什么不建議布爾類型的成員變量以is開頭?
          關(guān)于Java Bean中的getter/setter方法的定義其實(shí)是有明確的規(guī)定的,根據(jù)JavaBeans(TM) Specification規(guī)定,如果是普通的參數(shù),命名為propertyName,需要通過以下方式定義其setter/getter:
              public <PropertyType> get<PropertyName>();    public void set<PropertyName>(<PropertyType> p)
          但是,布爾類型的變量propertyName則是另外一套命名原則的:
              public boolean is<PropertyName>();    public void set<PropertyName>(boolean p)
          由于各種RPC框架和對(duì)象序列化工具對(duì)于布爾類型變量的處理方式存在差異,就容易造成代碼移植性問題。最常見的json序列化庫Jackson和Gson之間就存在兼容性問題,前者是通過通過反射遍歷出該類中的所有g(shù)etter方法,通過方法名截取獲得到對(duì)象的屬性,后者則是通過反射直接遍歷該類中的屬性。為了規(guī)避這種差異對(duì)業(yè)務(wù)的影響,建議所有成員變量都不要以is開頭,防止序列化結(jié)果出現(xiàn)不預(yù)知的情況發(fā)生。
          3. 看看單詞大小寫能引起的哪些副作用?
          JAVA語言本身是區(qū)分大小寫的,但是在用文件路徑、文件名對(duì)文件進(jìn)行操作時(shí),這里的文件名和路徑是不區(qū)分大小寫的,這是因?yàn)槲募到y(tǒng)不區(qū)分大小寫。典型的場景就是我們通過git等代碼管理平臺(tái)時(shí),將package路徑里的大寫的文件名稱,修改為小寫時(shí),git是無法更新的,為了規(guī)避不必要的麻煩,這里建議包路徑統(tǒng)一使用小寫單詞,多個(gè)單詞通過路徑層次來進(jìn)行定義。
          4. 不同jar包里的類也會(huì)出現(xiàn)沖突問題?
          • 一類是同一個(gè)jar包出現(xiàn)了多個(gè)不同的版本。應(yīng)用選擇了錯(cuò)誤的版本導(dǎo)致jvm加載不到需要的類或者加載了錯(cuò)誤版本的類;(借助maven管理工具相對(duì)容易解決)
          • 另一類是不同的jar包出現(xiàn)了類路徑相同的類,同樣的類出現(xiàn)在不同的依賴jar里,由于jar加載的先后順序?qū)е铝薐VM加載了錯(cuò)誤版本的類;(比較難以解決)
          這里著重介紹第二種情況,這種情況容易出現(xiàn)在系統(tǒng)拆分重構(gòu)時(shí),將原有的項(xiàng)目進(jìn)行了復(fù)制,然后刪減,導(dǎo)致部分工具或者枚舉類和原有的路徑和命名都一樣,當(dāng)?shù)谌秸{(diào)用方同時(shí)依賴了這兩個(gè)系統(tǒng)時(shí),就容易為以后的迭代埋下坑。要規(guī)避此類問題,一定要為系統(tǒng)起一個(gè)獨(dú)一無二的package路徑。
          補(bǔ)充:如果依賴的都是第三方的庫,存在著類沖突時(shí),可以通過引入第三方庫jarjar.jar,修改其中某個(gè)沖突jar文件的包名,以此來解決jar包沖突。
          5. 在變量命名的可讀性和占用資源(內(nèi)存,帶寬)方面,如何去做權(quán)衡?
          可以通過對(duì)象序列化工具為突破口,以常見的Json(Jackson)序列化方式來舉例:
             
             
          public class SkuKey implements Serializable {    @JsonProperty(value = "sn")    @ApiModelProperty(name = "stationNo", value = " 門店編號(hào)", required = true)    private Long stationNo;    @JsonProperty(value = "si")    @ApiModelProperty(name = "skuId", value = " 商品編號(hào)", required = true)    private Long skuId;    // 省略get/set方法}
          其中@JsonProperty注解的作用就是將JavaBean中的普通屬性在序列化的時(shí)候,重新命名成指定的新的名字。而這一實(shí)現(xiàn)對(duì)于業(yè)務(wù)實(shí)現(xiàn)沒有影響,依然以原來的命名操作為準(zhǔn),只在對(duì)外RPC需要序列化和反序列化的過程生效。如此,比較好地解決了可讀性和資源占用的沖突問題。
          6. 對(duì)外提供服務(wù)的入?yún)⒑统鰠?,我們是用class對(duì)象,還是Map容器?
          從靈活性的角度看,Map容器穩(wěn)定且更靈活。從穩(wěn)定性和可讀性上來看,Map容器是個(gè)黑盒子,不知道里面有什么,得有輔助的詳細(xì)說明文檔才能協(xié)作,由于維護(hù)文檔的動(dòng)作往往與工程代碼是分開的,這種機(jī)制就會(huì)導(dǎo)致信息的準(zhǔn)確性和實(shí)時(shí)性很難得到保障。所以還是建議使用class結(jié)構(gòu)對(duì)象維護(hù)出入?yún)⒔Y(jié)構(gòu)。

          2.2 關(guān)于注釋

          注釋是程序員和閱讀者之間交流的重要手段,是對(duì)代碼的解釋和說明,好的注釋可以提高軟件的可讀性,減少維護(hù)軟件的成本。

          2.2.1 好的注釋

          分層次:按照系統(tǒng),包,類,方法,代碼塊,代碼行等不同粒度,各有側(cè)重點(diǎn)的進(jìn)行注釋說明。
          1. 系統(tǒng)注釋:通過README.md文件體現(xiàn)宏觀的功能和架構(gòu)實(shí)現(xiàn);
          2. 包注釋:通過package-info文件體現(xiàn)模塊職責(zé)邊界,另外該文件也支持聲明友好類,包常量以及為標(biāo)注在包上的注解(Annotation)提供便利;
          3. 類注釋:主要體現(xiàn)功能職責(zé),版本支持,作者歸屬,應(yīng)用示例等相關(guān)信息;
          4. 方法注釋:關(guān)注入?yún)?,出參,異常處理聲明,使用場景舉例等相關(guān)內(nèi)容;
          5. 代碼塊和代碼行注釋:主要體現(xiàn)邏輯意圖,閉坑警示,規(guī)劃TODO,放大關(guān)注點(diǎn)等細(xì)節(jié)內(nèi)容;
          有規(guī)范:好的代碼優(yōu)于大量注釋,這和我們常說的“約定大于配置”是相同的道理。借助swagger等三方庫實(shí)現(xiàn)注解即接口文檔,是一個(gè)不錯(cuò)的規(guī)范方式;

          2.2.2 壞的注釋

          為了能使注釋準(zhǔn)確清晰的表達(dá)出功能邏輯,注釋的維護(hù)是有相當(dāng)?shù)木S護(hù)成本的,所以注釋并不是越多,越詳細(xì)越好。下面就舉一些壞的注釋場景,輔助理解:
          1. 冗余式:如果一個(gè)函數(shù),讀者能夠很容易的就讀出來代碼要表達(dá)的意思,注釋就是多余的;
          2. 錯(cuò)誤式:如果注釋地不清楚,甚至出現(xiàn)歧義,那還不如不寫;
          3. 簽名式:類似“add by liuhuiqing 2023-08-05”這種注釋,容易過期失效而且不太可信(不能保證所有人每次都采用這種方式注釋),其功能完全可以由git代碼管理工具來實(shí)現(xiàn);
          4. 長篇大論式:代碼塊里,夾雜了大篇幅的注釋,不僅影響代碼閱讀,而且維護(hù)困難;
          5. 非本地注釋:注釋應(yīng)該在離代碼實(shí)現(xiàn)最近的地方,比如:被調(diào)用的方法注釋就由方法本身來維護(hù),調(diào)用方無需對(duì)方法做詳細(xì)的說明;
          6. 注釋掉的代碼:無用的代碼應(yīng)該刪除,而不是注釋。歷史記錄交給git等代碼管理工具來維護(hù);

          2.3 關(guān)于分層

          系統(tǒng)分層設(shè)計(jì)的主要目的是通過分離關(guān)注點(diǎn),來降低系統(tǒng)的復(fù)雜度,同時(shí)提高可復(fù)用性和降低維護(hù)成本。所以懂得分層的概念,很大程度上系統(tǒng)的可維護(hù)性就有了骨架。

          2.3.1 系統(tǒng)分層

          在ISO((International Standardization Organization))于1981年制定網(wǎng)絡(luò)通信七層模型(Open System Interconnection Reference Model,OSI/RM)之前,計(jì)算機(jī)網(wǎng)絡(luò)中存在眾多的體系結(jié)構(gòu),其中以IBM公司的SNA(系統(tǒng)網(wǎng)絡(luò)體系結(jié)構(gòu))和DEC公司的DNA(DigitalNetworkArchitecture)數(shù)字網(wǎng)絡(luò)體系結(jié)構(gòu)最為著名。
          最早之前,各個(gè)廠家提出的不同標(biāo)準(zhǔn)都是以自家設(shè)備為基礎(chǔ)的,用戶在選擇產(chǎn)品的時(shí)候就只能用同一家公司的,因?yàn)椴煌鹃g大家的標(biāo)準(zhǔn)不一樣,工作方式也可能不一樣,結(jié)果就是不同廠商的網(wǎng)絡(luò)產(chǎn)品間,可能會(huì)出現(xiàn)不兼容的情況。如果說同一家的公司的產(chǎn)品都能滿足用戶的需求的話,那就看哪家公司實(shí)力強(qiáng)點(diǎn),實(shí)力強(qiáng)的,用戶粘性高的,用戶自然也不會(huì)說什么,問題是一家公司并不是對(duì)所有的產(chǎn)品都擅長。這就會(huì)導(dǎo)致廠商和用戶都面臨著痛苦的煎熬。類比一下當(dāng)前手機(jī)充電接口協(xié)議(Micro USB接口、Type- c接口、Lightning接口),手頭總是要備有各種充電線的場景,就能深刻理解標(biāo)準(zhǔn)的意義了。

          2.3.2 軟件伸縮性

          軟件伸縮性指的是軟件系統(tǒng)在面對(duì)負(fù)載壓力時(shí),能夠保持原有性能并擴(kuò)展以支持更多任務(wù)的能力。

          伸縮性可以有兩個(gè)方面,垂直伸縮性和水平伸縮性,垂直伸縮性是通過在同一個(gè)業(yè)務(wù)單元中增加資源來提高系統(tǒng)的吞吐量,比如增加服務(wù)器cpu的數(shù)量,增加服務(wù)器的內(nèi)存等。水平伸縮性是通過增加多個(gè)業(yè)務(wù)單元資源,使得所有的業(yè)務(wù)單元邏輯上就像是一個(gè)單元一樣。比如ejb分布式組件模型,微服務(wù)組件模型等都屬于此種方式。

          軟件系統(tǒng)在設(shè)計(jì)時(shí)需要考慮如何進(jìn)行有效的伸縮性設(shè)計(jì),以確保在面對(duì)負(fù)載壓力時(shí)能夠提供足夠的性能支持。
          系統(tǒng)分層從伸縮性角度看,更多的屬于水平伸縮性的范疇。在J2EE系統(tǒng)開發(fā)當(dāng)中,我們普遍采用了分層構(gòu)架的方式,一般分為表現(xiàn)層,業(yè)務(wù)層和持久層。采用分層以后,因?yàn)閷优c層之間通信會(huì)引來額外的開銷,所以給我們軟件系統(tǒng)帶來的就是每個(gè)業(yè)務(wù)處理開銷會(huì)變大。
          既然采用分層會(huì)帶來額外的開銷,那么我們?yōu)槭裁催€要進(jìn)行分層呢?
          這是因?yàn)閱渭円揽慷延布Y源的垂直伸縮方式來提高軟件性能和吞吐是有上限的,而且隨著系統(tǒng)規(guī)模的擴(kuò)大,垂直伸縮的代價(jià)也將變得非常昂貴。當(dāng)采用了分層以后,雖然層與層之間帶來了通信開銷,但是它有利于各層的水平伸縮性,并且各個(gè)層都可以進(jìn)行獨(dú)立的伸縮而不會(huì)影響到其它的層。也就是說當(dāng)系統(tǒng)要應(yīng)對(duì)更大的訪問量的時(shí)候,我們可以通過增加多個(gè)業(yè)務(wù)單元資源來增加系統(tǒng)吞吐量。

          2.4 小結(jié)

          本章內(nèi)容主要從可讀性和可維護(hù)性方面講述了在開發(fā)過程中,要做好命名和注釋的統(tǒng)一共識(shí)。除了共識(shí)之外,在設(shè)計(jì)層面也需要做好關(guān)注點(diǎn)的隔離,這包含系統(tǒng)職責(zé)的拆分,模塊功能的劃分,類能力的收斂,實(shí)體結(jié)構(gòu)的關(guān)系都需要做好規(guī)劃。


          三、實(shí)踐篇

          下面就從程序的擴(kuò)展性,維護(hù)性,安全性以及性能等幾個(gè)重要質(zhì)量指標(biāo),來學(xué)習(xí)那些經(jīng)典的實(shí)踐案例。

          3.1 類定義

          3.1.1 常量定義

          常量是一種固定值,不會(huì)在程序執(zhí)行期間發(fā)生改變。你可以使用枚舉(Enum)或類(Class)來定義常量。
          如果你需要定義一組相關(guān)的常量,那么使用枚舉更為合適。枚舉從安全性和可操作性(支持遍歷和函數(shù)定義)上面擁有更大的優(yōu)勢。
             
             
          public enum Color { RED, GREEN, BLUE;}
          如果你只需要定義一個(gè)或少數(shù)幾個(gè)只讀的常量,那么使用類常量更為簡潔和方便。
             
             
          public class MyClass { public static final int MAX_VALUE = 100;}

          3.1.2 工具類

          工具類通常包含具有通用性的、某一非業(yè)務(wù)領(lǐng)域內(nèi)的公共方法,不需要配套的成員變量,僅僅是作為工具方法被使用。因此,將其做成靜態(tài)方法最合適,不需要實(shí)例化,能夠獲取到方法的定義并調(diào)用就行。
          工具類不實(shí)例化的原因是可以節(jié)省內(nèi)存空間,因?yàn)楣ぞ哳愄峁┑氖庆o態(tài)方法,通過類就能調(diào)用,不需要實(shí)例化工具類對(duì)象。
             
             
          public abstract class ObjectHelper {    public static boolean isEmpty(String str) {        return str == null || str.length() == 0;    }}
          為了實(shí)現(xiàn)不需要實(shí)例化對(duì)象的約束,我們最好在類定義時(shí),加上abstract關(guān)鍵字進(jìn)行聲明限定,這也是為什么spring等開源工具類大都使用abstract關(guān)鍵字修飾的原因。

          3.1.3 JavaBean

          JavaBean的定義有兩種常見實(shí)現(xiàn)方式:手動(dòng)編寫和自動(dòng)生成。
             
             
          public class Person { private String name; private int age;
          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; }}
          使用lombok插件,通過注解方式來增強(qiáng)Java代碼的編寫,在編譯期動(dòng)態(tài)生成get和set方法。
             
             
          import lombok.Data;
          @NoArgsConstructor@Data@Accessors(chain = true)public class Person { private String name; private int age;}
          插件包還提供了@Builder和@Accessors等比較實(shí)用的鏈?zhǔn)骄幊棠芰?,在一定程度上能提高編碼效率。

          3.1.4 不可變類

          在某些場景下,類為了保證其功能和行為的穩(wěn)定性和一致性,會(huì)被設(shè)計(jì)為不能被繼承和重寫的。
          定義方式就是在類上面添加final關(guān)鍵字,示例:
             
             
          public final class String implements Serializable, Comparable<String>, CharSequence {
          }
          以下是一些不能被繼承和重寫的類,這在一些底層中間件中會(huì)有應(yīng)用:
             
             
          java.lang.Stringjava.lang.Mathjava.lang.Booleanjava.lang.Characterjava.util.Datejava.sql.Datejava.lang.Systemjava.lang.ClassLoader

          3.1.5 匿名內(nèi)部類

          匿名內(nèi)部類通常用于簡化代碼,它的定義和使用通常發(fā)生在同一處,它的使用場景如下:
          1. 直接作為參數(shù)傳遞給方法或構(gòu)造函數(shù);

          2. 用于實(shí)現(xiàn)某個(gè)接口或抽象類的匿名實(shí)例;

             
             
          public class Example {    public static void main(String[] args) {        // 創(chuàng)建一個(gè)匿名內(nèi)部類        Runnable runnable = new Runnable() {            @Override            public void run() {                System.out.println("Hello, World!");            }        };        // 調(diào)用匿名內(nèi)部類的方法        runnable.run();    }}

          3.1.6 聲明類

          聲明類是Java語言中的基本類型或接口,用于定義類的行為或特性,有的甚至只是個(gè)聲明,沒有具體的方法定義。
          • AutoCloseable:表示實(shí)現(xiàn)了該接口的類可以被自動(dòng)關(guān)閉,通常用于資源管理。
          • Comparable:表示實(shí)現(xiàn)了該接口的類可以與其他實(shí)現(xiàn)了該接口的對(duì)象進(jìn)行比較。
          • Callable:表示實(shí)現(xiàn)了該接口的類可以作為參數(shù)傳遞給線程池,并返回結(jié)果。
          • Cloneable:表示實(shí)現(xiàn)了該接口的類可以被克隆。
          • Enum:表示實(shí)現(xiàn)了該接口的類是一個(gè)枚舉類型。
          • Iterable:表示實(shí)現(xiàn)了該接口的類可以迭代。
          • Runnable:表示實(shí)現(xiàn)了該接口的類可以作為線程運(yùn)行。
          • Serializable:表示實(shí)現(xiàn)了該接口的類可以被序列化和反序列化。
          • interface:表示實(shí)現(xiàn)了該接口的類是一個(gè)接口,可以包含方法聲明。
          • Annotation:表示實(shí)現(xiàn)了該接口的類是一個(gè)注解,可以用于元數(shù)據(jù)描述。

          3.1.7 Record 類

          Record 類在 Java14 中就開始預(yù)覽,一直到Java17 才正式發(fā)布。根據(jù) JEP395 的描述,Record 類是不可變數(shù)據(jù)的載體,類似于當(dāng)下廣泛應(yīng)用的各種 model,dto,vo 等 POJO 類,但 record 本身在構(gòu)造之后不再可賦值。所有的 record 類都繼承自 java.lang.Record。Record 類默認(rèn)提供了全字段的構(gòu)造器,屬性的訪問,還有 equals,hashcode,toString 方法,其作用和 lombok 插件非常類似。
          定義方式
             
             
          /** * 關(guān)鍵定義的類是不可變類 * 將所有成員變量通過參數(shù)的形式定義 * 默認(rèn)會(huì)生成全部參數(shù)的構(gòu)造方法 * @param name * @param age */public record Person(String name, int age) {    public Person{        if(name == null){            throw new IllegalArgumentException("提供緊湊的方式進(jìn)行參數(shù)校驗(yàn)");        }    }    /**     * 定義的類中可以定義靜態(tài)方法     * @param name     * @return     */    public static Person of(String name) {        return new Person(name, 18);    }}
          使用方式
             
             
          Person person = new Person("John", 30);// Person person = Person.of("John");String name = person.name();int age = person.age();
          使用場景
          通過Record 構(gòu)建一個(gè)臨時(shí)存儲(chǔ)對(duì)象,將 Person 數(shù)組對(duì)象按照年齡排序。
             
             
          public List<Person> sortPeopleByAge(List<Person> people) {
          record Data(Person person, int age){};
          return people.stream() .map(person -> new Data(person, computAge(person))) .sorted((d1, d2) -> Integer.compare(d2.age(), d1.age())) .map(Data::person) .collect(toList());}public int computAge(Person person) { return person.age() - 1;}

          3.1.8 密封類

          Java 17推出的新特性密封類(Sealed Classes),主要作用就是限制類的繼承。我們知道之前對(duì)類繼承功能的限制主要有兩種:
          1. final修飾類,這樣類就無法被繼承了;
          2. package-private類,可以控制只能被同一個(gè)包下的類繼承;
          但很顯然,這兩種限制方式的力度都非常粗,而密封類正是對(duì)類繼承的更細(xì)粒度的控制。
             
             
          sealed class SealedClass permits SubClass1, SubClass2 {
          }
          class SubClass1 extends SealedClass {
          }
          class SubClass2 extends SealedClass {
          }
          在上面的示例中,SealedClass是一個(gè)密封類,它包含兩個(gè)子類SubClass1和SubClass2。在SubClass1和SubClass2的定義中,必須使用extends關(guān)鍵字來繼承自SealedClass,并且使用permits關(guān)鍵字來指定它們允許哪些子類來繼承。通過使用密封類,可以確保只有符合特定條件的子類才能繼承或?qū)崿F(xiàn)該協(xié)議或規(guī)范。

          3.2 方法定義

          3.2.1 構(gòu)造方法

          構(gòu)造方法是一種特殊的方法,用于創(chuàng)建和初始化對(duì)象。構(gòu)造方法的名稱必須與類名相同,并且沒有返回類型。在創(chuàng)建對(duì)象時(shí),可以通過使用 new 關(guān)鍵字來調(diào)用構(gòu)造方法。
             
             
          public class MyClass {    private int myInt;    private String myString;
          // 構(gòu)造方法 public MyClass(int myInt, String myString) { this.myInt = myInt; this.myString = myString; }}
          實(shí)現(xiàn)單例模式的一個(gè)重要特性就是不允許用戶隨意創(chuàng)建(new)對(duì)象,如何做到安全控制呢?將構(gòu)造方法聲明為私有(private)是必不可少的一步。

          3.2.2 方法重寫

          方法重寫是指在子類中重新定義與父類中同名的方法。方法重寫允許子類覆蓋父類中的方法實(shí)現(xiàn),以便根據(jù)子類的需要實(shí)現(xiàn)其自己的行為。
             
             
          class Animal {    public void makeSound() {        System.out.println("Animal is making a sound");    }}
          class Cat extends Animal { @Override public void makeSound() { System.out.println("Meow"); }}
          public class Main { public static void main(String[] args) { Animal myCat = new Cat(); myCat.makeSound(); // 輸出 "Meow" }}
          面向?qū)ο蟮娜筇匦灾坏亩鄳B(tài),方法重寫是其核心。

          3.2.3 方法重載

          類中定義多個(gè)方法,它們具有相同的名稱但參數(shù)列表不同。方法重載允許我們使用同一個(gè)方法名執(zhí)行不同的操作,根據(jù)傳遞給方法的參數(shù)不同來執(zhí)行不同的代碼邏輯。
             
             
          public class Calculator {    public int add(int a, int b) {        return a + b;    }
          public double add(double a, double b) { return a + b; }}
          public class Main { public static void main(String[] args) { Calculator calculator = new Calculator(); int result1 = calculator.add(2, 3); double result2 = calculator.add(2.5, 3.5); System.out.println(result1); // 輸出 5 System.out.println(result2); // 輸出 6.0 }}

          3.2.4 匿名方法

          Java 8 引入了 Lambda 表達(dá)式,可以用來實(shí)現(xiàn)類似匿名方法的功能。Lambda 表達(dá)式是一種匿名函數(shù),可以作為參數(shù)傳遞給方法,或者直接作為一個(gè)獨(dú)立表達(dá)式使用。
             
             
          public static void main(String args[]) {    List<String> names = Arrays.asList("hello", "world");    // 使用 Lambda 表達(dá)式作為參數(shù)傳遞給 forEach 方法    names.forEach((String name) -> System.out.println("Name: " + name));
          // 使用 Lambda 表達(dá)式作為獨(dú)立表達(dá)式使用 Predicate<String> nameLengthGreaterThan5 = (String name) -> name.length() > 5; boolean isLongName = nameLengthGreaterThan5.test("John"); System.out.println("Is long name? " + isLongName);}

          3.3 對(duì)象定義

          3.3.1 單例對(duì)象

          單例對(duì)象是一種可以重復(fù)使用的對(duì)象,但只有一個(gè)實(shí)例。它有以下幾個(gè)作用:
          1. 控制資源的使用:通過線程同步來控制資源的并發(fā)訪問。
          2. 控制實(shí)例產(chǎn)生的數(shù)量:達(dá)到節(jié)約資源的目的。
          3. 作為通信媒介使用:也就是數(shù)據(jù)共享,它可以在不建立直接關(guān)聯(lián)的條件下,讓多個(gè)不相關(guān)的兩個(gè)線程或者進(jìn)程之間實(shí)現(xiàn)通信。
          比如,使用枚舉實(shí)現(xiàn)單例模式:
             
             
          public enum Singleton { INSTANCE;  public void someMethod() { // ...其他代碼... }}

          3.3.2 不可變對(duì)象

          Java中的不可變對(duì)象是指那些一旦被創(chuàng)建,其狀態(tài)就不能被修改的對(duì)象。不可變對(duì)象是一種非常有用的對(duì)象,因?yàn)樗鼈兛梢源_保對(duì)象的狀態(tài)在任何時(shí)候都是一致的,從而避免了因?yàn)樾薷膶?duì)象狀態(tài)而引發(fā)的問題。實(shí)現(xiàn)不可變對(duì)象有以下幾種方式:
          1. 將對(duì)象的狀態(tài)存儲(chǔ)在不可變對(duì)象中:String、Integer等就是內(nèi)置的不可變對(duì)象類型;
          2. 將對(duì)象的狀態(tài)存儲(chǔ)在final變量中:final變量一旦被賦值就不能被修改;
          3. 將對(duì)象的所有屬性都設(shè)為不可變對(duì)象:這樣就可以確保整個(gè)對(duì)象都是不可變的;
          一些容器類的操作也有對(duì)應(yīng)的包裝類實(shí)現(xiàn)容器對(duì)象的不可變,比如定義不可變數(shù)組對(duì)象:
             
             
          Collections.unmodifiableList(new ArrayList<>());
          當(dāng)領(lǐng)域內(nèi)的對(duì)象作為入?yún)⑼鈧鬟f時(shí),將其定義為不可變對(duì)象,這在保持?jǐn)?shù)據(jù)一致性方面非常重要,否則對(duì)象屬性變更的不可預(yù)測性,在進(jìn)行問題定位時(shí),將會(huì)非常麻煩。

          3.3.3 元組對(duì)象

          元組(Tuple)是函數(shù)式編程語言中的常見概念,元組是一個(gè)不可變,并且能夠以類型安全的形式保存多個(gè)不同類型的對(duì)象。它是一種非常有用的數(shù)據(jù)結(jié)構(gòu),可以讓開發(fā)者在處理多個(gè)數(shù)據(jù)元素時(shí)更加方便和高效。但原生的Java標(biāo)準(zhǔn)庫并沒有提供元組的支持,需要我們自己或借助第三方類庫來實(shí)現(xiàn)。
          二元組實(shí)現(xiàn)
             
             
          public class Pair<A,B> {    public final A first;    public final B second;
          public Pair(A a, B b) { this.first = a; this.second = b; }
          public A getFirst() { return first; }
          public B getSecond() { return second; }}
          三元組實(shí)現(xiàn)
             
             
          public class Triplet<A,B,C> extends Pair<A,B>{    public final C third;
          public Triplet(A a, B b, C c) { super(a, b); this.third = c; }
          public C getThird() { return third; }
          public static void main(String[] args) { // 表示姓名,性別,年齡 Triplet<String,String,Integer> triplet = new Triplet("John","男",18); // 獲得姓名 String name = triplet.getFirst(); }
          }
          多元組實(shí)現(xiàn)
             
             
          public class Tuple<E> {    private final E[] elements;
          public Tuple(E... elements) { this.elements = elements; }
          public E get(int index) { return elements[index]; }
          public int size() { return elements.length; } public static void main(String[] args) { // 表示姓名,性別,年齡 Tuple<String> tuple = new Tuple<>("John", "男", "18"); // 獲得姓名 String name = tuple.get(0); }}
          Tuple主要有以下幾個(gè)功能:
          1. 存儲(chǔ)多個(gè)數(shù)據(jù)元素:Tuple可以存儲(chǔ)多個(gè)不同類型的數(shù)據(jù)元素,這些元素可以是基本類型、對(duì)象類型、數(shù)組等;
          2. 簡化代碼:Tuple可以使代碼更加簡潔,減少重復(fù)代碼的編寫。通過Tuple,我們可以將多個(gè)變量打包成一個(gè)對(duì)象,從而減少了代碼量;
          3. 提高代碼可讀性:Tuple可以提高代碼的可讀性。通過Tuple,我們可以將多個(gè)變量打包成一個(gè)對(duì)象,從而使代碼更加易讀;
          4. 支持函數(shù)返回多個(gè)值:Tuple可以支持函數(shù)返回多個(gè)值。在Java中,函數(shù)只能返回一個(gè)值,但是通過Tuple,我們可以將多個(gè)值打包成一個(gè)對(duì)象返回;
          除了自定義之外,實(shí)現(xiàn)了元組概念的第三方類庫有:Google Guava,Apache Commons Lang,JCTools,Vavr等。
          Google Guava庫的Tuple提供了更多的功能,并且被廣泛使用。比如,為了使元組的含義更加明確,Guava提供了命名元組(NamedTuple)的概念。通過給元組命名,可以更清晰地表示每個(gè)元素的意義。示例:
             
             
          NamedTuple namedTuple = Tuples.named("person", "name", "age");

          3.3.4 臨時(shí)對(duì)象

          臨時(shí)對(duì)象是指在程序執(zhí)行過程中臨時(shí)需要,但生命周期較短的對(duì)象。這些對(duì)象通常只在使用過程中短暫存在,不需要長期存儲(chǔ)或重復(fù)使用。
          關(guān)于臨時(shí)對(duì)象的優(yōu)化建議如下:
          1. 盡量重用對(duì)象。由于系統(tǒng)不僅要花時(shí)間生成對(duì)象,以后可能還需花時(shí)間對(duì)這些對(duì)象進(jìn)行垃圾回收和處理,因此,生成過多的對(duì)象將會(huì)給程序的性能帶來很大的影響,重用對(duì)象的策略有緩存對(duì)象,也可以針對(duì)具體場景進(jìn)行定向優(yōu)化,比如使用StringBuffer代替字符串拼接的方式;
          2. 盡量使用局部變量。調(diào)用方法時(shí)傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時(shí)變量都保存在棧中,速度較快。其他變量,如靜態(tài)變量、實(shí)例變量等,都在堆中創(chuàng)建,速度較慢;
          3. 分代收集。分代垃圾回收策略,是基于這樣一個(gè)事實(shí):不同的對(duì)象的生命周期是不一樣的。因此,不同生命周期的對(duì)象可以采取不同的收集方式,以便提高回收效率;

          3.3.5 Valhalla

          Java作為高級(jí)語言,和更為底層的C語言,匯編語言在性能方面一直存在著不小的差距。為了彌補(bǔ)這一差距,Valhalla 項(xiàng)目于 2014 年啟動(dòng),目標(biāo)是為基于 JVM 的語言帶來更靈活的扁平化數(shù)據(jù)類型。
          我們都知道Java支持原生類型和引用類型兩種。原生數(shù)據(jù)類型按值傳遞,賦值和函數(shù)傳參都會(huì)把值給復(fù)制一份,復(fù)制之后兩份之間就再無關(guān)聯(lián);引用類型無論什么情況傳的都是指針,修改指針指向的內(nèi)容會(huì)影響到所有的引用。而Valhalla又引入了值類型(value types),一種介于原生類型和引用類型之間的概念。
          由于應(yīng)用程序中的大多數(shù)Java數(shù)據(jù)結(jié)構(gòu)都是對(duì)象,因此我們可以將Java視為指針密集型語言。這種基于指針的對(duì)象實(shí)現(xiàn)用于啟用對(duì)象標(biāo)識(shí),對(duì)象標(biāo)識(shí)本身用于語言特性,如多態(tài)性、可變性和鎖定。默認(rèn)情況下,這些特性適用于每個(gè)對(duì)象,無論它們是否真的需要。這就是值類型(value types)發(fā)揮作用的地方。
          值類型(value types)的概念是表示純數(shù)據(jù)聚合,這會(huì)刪除常規(guī)對(duì)象的功能。因此,我們有純數(shù)據(jù),沒有身份。當(dāng)然,這意味著我們也失去了使用對(duì)象標(biāo)識(shí)可以實(shí)現(xiàn)的功能。由于我們不再有對(duì)象標(biāo)識(shí),我們可以放棄指針,改變值類型的一般內(nèi)存布局。讓我們來比較一下對(duì)象引用和值類型內(nèi)存布局。
          ??去掉了對(duì)象頭信息,在64位操作系統(tǒng)中值類型節(jié)約了對(duì)象頭16個(gè)字節(jié)的空間。同時(shí),也意味著放棄對(duì)象唯一身份(Identity)和初始化安全性,之前的wait(),notify(),synchronized(obj),System.identityHashCode(obj)等關(guān)鍵字或方法都將失效,無法使用。
          Valhalla 在提高性能和減少泄漏的抽象方面將會(huì)顯著提高:
          • 性能增強(qiáng)通過展平對(duì)象圖和移除間接來解決。這將獲得更高效的內(nèi)存布局和更少的分配和垃圾回收。

          • 當(dāng)用作泛型類型時(shí),原語和對(duì)象具有更相似的行為,這是更好的抽象。

          截止到2023年9月,Valhalla 項(xiàng)目仍在進(jìn)行中,還沒有正式版本的發(fā)布,這一創(chuàng)新項(xiàng)目值得期待的。


          四、總結(jié)

          本文總結(jié)了軟件開發(fā)過程中經(jīng)常用到的基礎(chǔ)常識(shí),分為基礎(chǔ)篇和實(shí)踐篇兩個(gè)篇章,其中基礎(chǔ)篇中著重講述了類,方法,變量的命名規(guī)范以及代碼注釋好壞的評(píng)判標(biāo)準(zhǔn)。實(shí)踐篇中從類,方法以及對(duì)象三個(gè)層面分析了常見的技術(shù)概念和落地實(shí)踐,希望這些常識(shí)能夠?yàn)樽x者帶來一些思考和幫助。
          -end-

          瀏覽 809
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产一区精品视频 | 老司机二三区 | 大香蕉综合在线视频 | 久久大鸡吧 | 国产美女裸身网站 |