自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何在Java中創(chuàng)建優(yōu)雅的對(duì)象來(lái)提升程序性能

開發(fā) 后端
Java 對(duì)象的創(chuàng)建方式是其語(yǔ)法明確規(guī)定,用戶不可能從外部改變的。本文仍然要使用上面的方式來(lái)創(chuàng)建對(duì)象,所以本文只能說(shuō)是構(gòu)建對(duì)象,而非創(chuàng)建對(duì)象也。

[[348621]]

 其實(shí)這個(gè)問(wèn)題很多人都進(jìn)行過(guò)解答,也有很多小伙伴在Java四大名著之一的《Effective Java》中的第1~5小節(jié)了解過(guò),不過(guò)我還是想結(jié)合自己的理解對(duì)這個(gè)問(wèn)題進(jìn)行總結(jié)和歸納,談?wù)劄槭裁磿?huì)最終選擇構(gòu)建器來(lái)實(shí)現(xiàn)我們的目的。

在 Java 中有多種方式可以創(chuàng)建對(duì)象,總結(jié)起來(lái)主要有下面的 4 種方式:

正常創(chuàng)建:通過(guò) new 操作符

反射創(chuàng)建:調(diào)用 Class 或 java.lang.reflect.Constructor 的 newInstance()方法

克隆創(chuàng)建:調(diào)用現(xiàn)有對(duì)象的 clone()方法

發(fā)序列化:調(diào)用 java.io.ObjectInputStream 的 getObject()方法反序列化

Java 對(duì)象的創(chuàng)建方式是其語(yǔ)法明確規(guī)定,用戶不可能從外部改變的。本文仍然要使用上面的方式來(lái)創(chuàng)建對(duì)象,所以本文只能說(shuō)是構(gòu)建對(duì)象,而非創(chuàng)建對(duì)象也。

假設(shè)有這樣一個(gè)場(chǎng)景,現(xiàn)在要構(gòu)建一個(gè)大型的對(duì)象,這個(gè)對(duì)象包含許多個(gè)參數(shù)的對(duì)象,有些參數(shù)有些是必填的,有些則是選填的。那么如何構(gòu)建優(yōu)雅、安全地構(gòu)建這個(gè)對(duì)象呢?

01 單一構(gòu)造函數(shù)

通常,我們第一反應(yīng)能想到的就是單一構(gòu)造函數(shù)方式。直接 new 的方式構(gòu)建,通過(guò)構(gòu)造函數(shù)來(lái)傳遞參數(shù),見下面的代碼:

  1. /*** 
  2. * 單一構(gòu)造函數(shù) 
  3. */ 
  4. public class Person { 
  5. // 姓名(必填) 
  6. private String name
  7. // 年齡(必填) 
  8. private int age; 
  9. // 身高(選填) 
  10. private int height; 
  11. // 畢業(yè)學(xué)校(選填) 
  12. private String school; 
  13. // 愛好(選填) 
  14. private String hobby; 
  15. public Person(String nameint age, int height, String school, String hobby) { 
  16. this.name = name
  17. this.age = age; 
  18. this.height = height; 
  19. this.school = school; 
  20. this.hobby = hobby; 
  21. } } 

上面的構(gòu)建方式有下面的缺點(diǎn):

有些參數(shù)是可以選填的(如 height, school),在構(gòu)建 Person 的時(shí)候必須要傳入可能并不需要的參數(shù)。

現(xiàn)在上面才 5 個(gè)參數(shù),構(gòu)造函數(shù)就已經(jīng)非常長(zhǎng)了。如果是 20 個(gè)參數(shù),構(gòu)造函數(shù)都可以直接上天了!

構(gòu)建的這樣的對(duì)象非常容易出錯(cuò)。

客戶端必須要對(duì)照 Javadoc 或者參數(shù)名來(lái)講實(shí)參傳入對(duì)應(yīng)的位置。如果參數(shù)都是 String 類型的,一旦傳錯(cuò)參數(shù),編譯是不會(huì)報(bào)錯(cuò)的,但是運(yùn)行結(jié)果卻是錯(cuò)誤的。

02 多構(gòu)造函數(shù)

對(duì)于第 1 個(gè)問(wèn)題,我們可以通過(guò)構(gòu)造函數(shù)重載來(lái)解決。見下面的代碼:

  1. /*** 
  2. * 多構(gòu)造函數(shù) 
  3. */ 
  4. public class Person { 
  5. // 姓名(必填) 
  6. private String name
  7. // 年齡(必填) 
  8. private int age; 
  9. // 身高(選填) 
  10. private int height; 
  11. // 畢業(yè)學(xué)校(選填) 
  12. private String school; 
  13. // 愛好(選填) 
  14. private String hobby; 
  15. public Person(String nameint age) { 
  16. this.name = name
  17. this.age = age; 
  18. public Person(String nameint age, int height) { 
  19. this.name = name
  20. this.age = age; 
  21. this.height = height; 
  22. public Person(String nameint age, int height, String school) { 
  23. this.name = name
  24. this.age = age; 
  25. this.height = height; 
  26. this.school = school; 
  27. public Person(String nameint age, String hobby, String school) { 
  28. this.name = name
  29. this.age = age; 
  30. this.hobby = hobby; 
  31. this.school = school; 
  32. } } 

上面的方式確實(shí)能在一定程度上降低構(gòu)造函數(shù)的長(zhǎng)度,但是卻有下面的缺陷:

 

導(dǎo)致類過(guò)長(zhǎng)。這種方式會(huì)使得 Person 類的構(gòu)造函數(shù)成階乘級(jí)增長(zhǎng)。按理來(lái)說(shuō),應(yīng)該要寫的構(gòu)造函數(shù)數(shù)是可選成員變量的組合數(shù)(實(shí)際并沒有這么多,原因見第 2 點(diǎn))。如果讓我調(diào)用這樣的類,絕對(duì)會(huì)在心里默念 xx!!

有些參數(shù)組合無(wú)法重構(gòu)。因?yàn)?Java 中重載是有限制的,相同方法簽名的方法不能構(gòu)成重載,編譯時(shí)無(wú)法通過(guò)。譬如包含(name, age, school)和(name, age, hobby)的構(gòu)造函數(shù)是不能重載的,因?yàn)?shcool 和 hobby 同為 String 類型。Java 只認(rèn)變量的類型,管你變量是什么含義呢。

03 JavaBean方式

上面的方法不行,莫急!還有法寶——JavaBean。一個(gè)對(duì)象的構(gòu)建通過(guò)多個(gè)方法來(lái)完成。直接見下面的代碼:

  1. public class Person { 
  2. // 姓名(必填) 
  3. private String name
  4. // 年齡(必填) 
  5. private int age; 
  6. // 身高(選填) 
  7. private int height; 
  8. // 畢業(yè)學(xué)校(選填) 
  9. private String school; 
  10. // 愛好(選填) 
  11. private String hobby; 
  12. public Person(String nameint age) { 
  13. this.name = name
  14. this.age = age; 
  15. public void setHeight(int height) { 
  16. this.height = height; 
  17. public void setSchool(String school) { 
  18. this.school = school; 
  19. public void setHobby(String hobby) { 
  20. this.hobby = hobby; 
  21. } } 
  22. 客戶端使用這個(gè)對(duì)象的代碼如下: 
  23. public class Client { 
  24. public static void main(String[] args) { 
  25. Person person = new Person("james", 12); 
  26. person.setHeight(170); 
  27. person.setHobby("reading"); 
  28. person.setSchool("xxx university"); 
  29. } } 

這樣看起來(lái)完美的解決了 Person 對(duì)象構(gòu)建的問(wèn)題,使用起來(lái)非常優(yōu)雅便捷。確實(shí),在單一線程的環(huán)境中這確實(shí)是一個(gè)非常好的構(gòu)建對(duì)象的方法,但是如果是在多線程環(huán)境中仍有其致命缺陷。在多線程環(huán)境中,這個(gè)對(duì)象不能安全地被構(gòu)建,因?yàn)樗皇遣豢勺儗?duì)象。一旦Person 對(duì)象被構(gòu)建,我們隨時(shí)可通過(guò) setXXX()方法改變對(duì)象的內(nèi)部狀態(tài)。假設(shè)有一個(gè)線程正在執(zhí)行與 Person 對(duì)象相關(guān)的業(yè)務(wù)方法,另外一個(gè)線程改變了其內(nèi)部狀態(tài),這樣得到莫名其妙的結(jié)果。由于線程運(yùn)行的無(wú)規(guī)律性,使得這問(wèn)題有可能不能重現(xiàn),這個(gè)時(shí)候真的就只能哭了。

04 Builder方式

為了完美地解決這個(gè)問(wèn)題,下面引出本文中的主角(等等等等?。?。我們使用構(gòu)建器(Builder)來(lái)優(yōu)雅、安全地構(gòu)建 Person 對(duì)象。廢話不說(shuō),直接代碼:

  1. /** 
  2. * 待構(gòu)建的對(duì)象。該對(duì)象的特點(diǎn): 
  3. * <ol> 
  4. * <li>需要用戶手動(dòng)的傳入多個(gè)參數(shù),并且有多個(gè)參數(shù)是可選的、順序隨意</li> 
  5. * <li>該對(duì)象是不可變的(所謂不可變,就是指對(duì)象一旦創(chuàng)建完成,其內(nèi)部狀態(tài)不可變, 
  6. 更通俗的說(shuō)是其成員變量不可改變)。* 不可變對(duì)象本質(zhì)上是線程安全的。</li> 
  7. * <li>對(duì)象所屬的類不是為了繼承而設(shè)計(jì)的。</li> 
  8. * </ol> 
  9. * 滿足上面特點(diǎn)的對(duì)象的構(gòu)建可是使用下面的 Build 方式構(gòu)建。這樣構(gòu)建對(duì)象有下面的好 
  10. 處: 
  11. * <ol> 
  12. * <li>不需要寫多個(gè)構(gòu)造函數(shù),使得對(duì)象的創(chuàng)建更加便捷</li> 
  13. * <li>創(chuàng)建對(duì)象的過(guò)程是線程安全的</li> 
  14. * </ol> 
  15. * @author xiaoyu 
  16. * @date 2020-10-25 
  17. */ 
  18. public class Person { 
  19. // 姓名(必填),final 修飾 name 一旦被初始化就不能再改變,保證了對(duì)象的不可變 
  20. 性。 
  21. private final String name
  22. // 年齡(必填) 
  23. private final int age; 
  24. // 身高(選填) 
  25. private final int height; 
  26. // 畢業(yè)學(xué)校(選填) 
  27. private final String school; 
  28. // 愛好(選填) 
  29. private final String hobby; 
  30. /** 
  31. * 這個(gè)私有構(gòu)造函數(shù)的作用: 
  32. * <ol> 
  33. * <li>成員變量的初始化。final 類型的變量必須進(jìn)行初始化,否則無(wú)法編譯成功</li> 
  34. * <li>私有構(gòu)造函數(shù)能夠保證該對(duì)象無(wú)法從外部創(chuàng)建,并且 Person 類無(wú)法被繼承</li> 
  35. * </ol> 
  36. */ 
  37. private Person(String nameint age, int height, String school, String hobby) { 
  38. this.name = name
  39. this.age = age; 
  40. this.height = height; 
  41. this.school = school; 
  42. this.hobby = hobby; 
  43. /** 
  44. * 要執(zhí)行的動(dòng)作 
  45. */ 
  46. public void doSomething() { 
  47. // TODO do what you want!! 
  48. /** 
  49. * 構(gòu)建器。為什么 Builder 是內(nèi)部靜態(tài)類? 
  50. * <ol> 
  51. * <li>必須是 Person 的內(nèi)部類。否則,由于 Person 的構(gòu)造函數(shù)私有,不能通過(guò) new 的 
  52. 方式創(chuàng)建 Person 對(duì)象</li> 
  53. * <li>必須是靜態(tài)類。由于 Person 對(duì)象無(wú)法從外部創(chuàng)建,如果不是靜態(tài)類,則外部無(wú) 
  54. 法引用 Builder 對(duì)象。</li> 
  55. * </ol> 
  56. * <b>注意</b>:Builder 內(nèi)部成員變量要與 Person 的成員變量保持一致。 
  57. * @author xiaoyu 
  58. */ 
  59. public static class Builder { 
  60. // 姓名(必填)。注意:這里不能是 final 的 
  61. private String name
  62. // 年齡(必填) 
  63. private int age; 
  64. // 身高(選填) 
  65. private int height; 
  66. // 畢業(yè)學(xué)校(選填) 
  67. private String school; 
  68. // 愛好(選填) 
  69. private String hobby; 
  70. public Builder(String nameint age) { 
  71. this.name = name
  72. this.age = age; 
  73. public Builder setHeight(int height) { 
  74. this.height = height; 
  75. return this; 
  76. public Builder setSchool(String school) { 
  77. this.school = school; 
  78. return this; 
  79. public Builder setHobby(String hobby) { 
  80. this.hobby = hobby; 
  81. return this; 
  82. /** 
  83. * 構(gòu)建對(duì)象 
  84. * @return 返回待構(gòu)建的對(duì)象本身 
  85. */ 
  86. public Person build() { 
  87. return new Person(name, age, height, school, hobby); 
  88. } } } 

客戶端構(gòu)建對(duì)象的方式見下面的代碼:

  1. /** 
  2. * 使用 Person 對(duì)象的客戶端 
  3. * @author xiaoyu 
  4. * @date 2020-10-25 
  5. */ 
  6. public class Client { 
  7. public static void main(String[] args) { 
  8. /* 
  9. * 通過(guò)鏈?zhǔn)秸{(diào)用的方式創(chuàng)建 Person 對(duì)象,非常優(yōu)雅! 
  10. */ 
  11. Person person = new Person.Builder("james", 12) 
  12. .setHeight(170) 
  13. .setHobby("reading"
  14. .build(); 
  15. person.doSomething(); 
  16. } } 

如果不想看代碼,可看下面對(duì)于上面代碼的總結(jié):

 

通過(guò) private Person(..)使得 Person 類不可被繼承

通過(guò)將 Person 類的成員變量設(shè)置為 final 類型,使得其不可變

通過(guò) Person 內(nèi)部的 static Builder 類來(lái)構(gòu)建 Person 對(duì)象

通過(guò)將 Builder 類內(nèi)部的 setXXX()方法返回 Builder 類型本身,實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用構(gòu)建 Person 對(duì) 象

 

總結(jié)

至此,我們就相對(duì)完美地解決這一類型的對(duì)象創(chuàng)建問(wèn)題!下面來(lái)總結(jié)一下本文的重點(diǎn)。待創(chuàng)建的對(duì)象特點(diǎn):

需要用戶手動(dòng)的傳入多個(gè)參數(shù),并且有多個(gè)參數(shù)是可選的、順序任意

對(duì)象不可變

對(duì)象所屬的類不是為了繼承而設(shè)計(jì)的。即類不能被繼承

 

依次使用的對(duì)象構(gòu)建方法:

單一構(gòu)造函數(shù)

多構(gòu)造函數(shù)

JavaBean 方式

Builder 方式

最終,通過(guò)比較得出 Builder 方法最為合適的解決。

責(zé)任編輯:姜華 來(lái)源: 淺羽的IT小屋
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)