淺談Java開發規範與開發細節(下),Java開發實戰講解

代碼席爾瓦 2021-09-18 06:09:52 阅读数:178

java java

IllegalArgumentException, InvocationTargetException
{

//如果當前的類型為枚舉類型,那麼調用當前方法直接拋异常
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException(“Cannot reflectively create enum objects”);


return inst;
}


從這我們可以看出,枚舉為了**保證不能被克隆,維持單例的狀態,禁止了clone和反射創建實例。**那麼我們接著來看序列化,由於所有的枚舉都是Eunm類的子類及其實例,而Eunm類默認實現了`Serializable`和`Comparable`接口,所以默認允許進行排序和序列化,而排序的方法`compareTo`的實現大概如下:
```java
/**
* Compares this enum with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.
*
* Enum constants are only comparable to other enum constants of the
* same enum type. The natural order implemented by this
* method is the order in which the constants are declared.
*/
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

而ordinal則是代錶每個枚舉常量對應的申明順序,說明枚舉的排序方式默認按照申明的順序進行排序,那麼序列化和反序列化的過程是什麼樣的呢?我們來編寫一個序列化的代碼,debug跟代碼以後,可以看到最終是調用了java.lang.Enum#valueOf 方法來實現的反序列化的。而序列化後的內容大概如下:

arn_enum.CoinEnum?xr?java.lang.Enum?xpt?PENNYq?t?NICKELq?t?DIMEq~?t?QUARTER

  • 1.

大概可以看到,序列化的內容主要包含枚舉類型和枚舉的每個名稱,接著我們看看java.lang.Enum#valueOf方法的源碼:

/** * Returns the enum constant of the specified enum type with the * specified name. The name must match exactly an identifier used * to declare an enum constant in this type. (Extraneous whitespace * characters are not permitted.) * * <p>Note that for a particular enum type {@code T}, the * implicitly declared {@code public static T valueOf(String)} * method on that enum may be used instead of this method to map * from a name to the corresponding enum constant. All the * constants of an enum type can be obtained by calling the * implicit {@code public static T[] values()} method of that * type. * * @param <T> The enum type whose constant is to be returned * @param enumType the {@code Class} object of the enum type from which * to return a constant * @param name the name of the constant to return * @return the enum constant of the specified enum type with the * specified name * @throws IllegalArgumentException if the specified enum type has * no constant with the specified name, or the specified * class object does not represent an enum type * @throws NullPointerException if {@code enumType} or {@code name} * is null * @since 1.5 */
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

從源碼和注釋中我們都可以看出來,如果此時A服務使用的枚舉類為舊版本,只有五個常量,而B服務的枚舉中包含了新的常量,這個時候在反序列化的時候,由於name == null,則會直接拋出异常,從這我們也終於看出來,為什麼規範中會强制不允許使用枚舉類型作為參數進行序列化傳遞了

慎用可變參數

在翻閱各大規範手册的時候,我看到阿裏手册中有這麼一條:

【强制】相同參數類型,相同業務含義,才可以使用 Java 的可變參數,避免使用 Obje
ct 。說明:可變參數必須放置在參數列錶的最後。(提倡同學們盡量不用可變參數編程)
正例: public List<User> listUsers(String type, Long... ids) {...}

  • 1.
  • 2.
  • 3.

吸引了我,因為在以前開發過程中,我就遇到了一個可變參數埋下的坑,接下來我們就來看看可變參數相關的一個坑。

相信很多人都編寫過企業裏使用的工具類,而我當初在編寫一個Boolean類型的工具類的時候,編寫了大概如下的兩個方法:

private static boolean and(boolean... booleans) {
for (boolean b : booleans) {
if (!b) {
return false;
}
}
return true;
}
private static boolean and(Boolean... booleans) {
for (Boolean b : booleans) {
if (!b) {
return false;
}
}
return true;
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

這兩個方法看起來就是一樣的,都是為了傳遞多個布爾類型的參數進來,判斷多個條件連接在一起,是否能成為true的結果,但是當我編寫測試的代碼的時候,問題出現了:

public static void main(String[] args) {
boolean result = and(true, true, true);
System.out.println(result);
}

  • 1.
  • 2.
  • 3.
  • 4.

這樣的方法會返回什麼呢?其實當代碼剛剛編寫完畢的時候,就會發現編譯器已經報錯了,會提示:

Ambiguous method call. Both and (boolean...) in BooleanDemo and and (Boolea
n...) in BooleanDemo match.

  • 1.
  • 2.

模糊的函數匹配,因為編譯器認為有兩個方法都完全滿足當前的函數,那麼為什麼會這樣的呢?我們知道在Java1.5以後加入了自動拆箱裝箱的過程,為了兼容1.5以前的jdk版本,將此過程設置為了三個階段:

淺談Java開發規範與開發細節(下),Java開發實戰講解_後端

而我們使用的測試方法中,在第一階段,判斷jdk版本,是不是不允許自動裝箱拆箱,明顯jdk版本大於1.5,允許自動拆箱裝箱,因此進入第二階段,此時判斷是否存在更符合的參數方法,比如我們傳遞了三個布爾類型的參數,但是如果此時有三個布爾參數的方法,則會優先匹配此方法,而不是匹配可變參數的方法,很明顯也沒有,此時就會進入第三階段,完成裝箱拆箱以後,再去查找匹配的變長參數的方法,這個時候由於完成了拆箱裝箱,兩個類型會視為一個類型,發現方法上有兩個匹配的方法,這時候就會報錯了。

那麼我們有木有辦法處理這個問題呢?畢竟我們熟悉的org.apache.commons.lang3.BooleanUtils工具類中也有類似的方法,我們都明白,變長參數其實就是會將當前的多個傳遞的參數裝入數組後,再去處理,那麼可以在傳遞的過程中,將所有的參數通過數組包裹,這個時候就不會發生拆箱裝箱過程了!例如:

@Test
public void testAnd_primitive_validInput_2items() {
assertTrue(
! BooleanUtils.and(new boolean[] { false, false })
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

而參考其他框架源碼大神的寫法中,也有針對這個的編寫的範例:

淺談Java開發規範與開發細節(下),Java開發實戰講解_後端_02

通過此種方法可以保證如果傳入的是基本類型,直接匹配當前方法,如果是包裝類型,則在第二階段以後匹配到當前函數,最終都是調用了BooleanUtils中基本類型的and方法

List的去重與xxList方法

List作為我們企業開發中最常見的一個集合類,在開發過程中更是經常遇到去重,轉換等操作,但是集合類操作的不好很多時候會導致我們的程序性能緩慢或者出現异常的風險,例如阿裏手册中提到過:

【 强 制 】 ArrayList 的 subList 結 果 不 可 强 轉 成 ArrayList , 否 則 會 拋 出
ClassCastException 异 常,即 java.util.RandomAccessSubList cannot be cast to
java.util.ArrayList。
【强制】在 SubList 場景中,高度注意對原集合元素的增加或删除,均會導致子列錶的
遍曆、增加、删除產生 ConcurrentModificationException 异常。
【强制】使用工具類 Arrays.asList () 把數組轉換成集合時,不能使用其修改集合相關的
方法,它的 add/remove/clear 方法會拋出 UnsupportedOperationException 异常。

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

而手册中的這些xxList方法則是我們開發過程中比較常用的,那麼為什麼阿裏手册會有這些規範呢?我們來看看第一個方法subList,首先我們先看看SubList類和ArrayList類的區別,從類圖上我們可以看出來兩個類之間並沒有繼承關系:

淺談Java開發規範與開發細節(下),Java開發實戰講解_Java_03

所以手册上不允許使用subList强轉為ArrayList,那麼為什麼原集合不能進行增删改查操作呢?我們來看看其源碼:

/** * Returns a view of the portion of this list between the specified * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. (If * {@code fromIndex} and {@code toIndex} are equal, the returned list is * empty.) The returned list is backed by this list, so non-structural * changes in the returned list are reflected in this list, and vice-versa. * The returned list supports all of the optional list operations. * * <p>This method eliminates the need for explicit range operations (of * the sort that commonly exist for arrays). Any operation that expects * a list can be used as a range operation by passing a subList view * instead of a whole list. For example, the following idiom * removes a range of elements from a list: * <pre> * list.subList(from, to).clear(); * </pre> * Similar idioms may be constructed for {@link #indexOf(Object)} and * {@link #lastIndexOf(Object)}, and all of the algorithms in the * {@link Collections} class can be applied to a subList. * * <p>The semantics of the list returned by this method become undefined if * the backing list (i.e., this list) is <i>structurally modified</i> in * any way other than via the returned list. (Structural modifications are * those that change the size of this list, or otherwise perturb it in such * a fashion that iterations in progress may yield incorrect results.) * * @throws IndexOutOfBoundsException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.

我們可以看到代碼的邏輯只有兩步,第一步檢查當前的索引和長度是否變化,第二步構建新的SubList出來並且返回。從注釋我們也可以了解到,SubList中包含的範圍,如果對其進行增删改查操作,都會導致原來的集合發生變化,並且是從當前的index + offSet進行變化。那麼為什麼我們這個時候對原來的ArrayList進行增删改查操作的時候會導致SubList集合操作异常呢?我們來看看ArrayList的add方法:

/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

我們可以看到一點,每次元素新增的時候都會有一個 ensureCapacityInternal(size + 1);操作,這個操作會導致modCount長度變化,而modCount則是在SubList的構造中用來記錄長度使用的:

SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount; // 注意:此處複制了 ArrayList的 modCount
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

而SubList的get操作的源碼如下:

public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

可以看到每次都會去校驗一下下標和modCount,我們來看看checkForComodification方法:

private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}

  • 1.
  • 2.
  • 3.
  • 4.

可見每次都會檢查,如果發現原來集合的長度變化了,就會拋出异常,那麼使用SubList的時候為什麼要注意原集合是否被更改的原因就在這裏了。

文末java面試題,進階技術大綱,架構資料分享

我將這三次阿裏面試的題目全部分專題整理出來,並附帶上詳細的答案解析,生成了一份PDF文檔

 CodeChina開源項目:【一線大廠Java面試題解析+核心總結學習筆記+最新講解視頻】

  • 第一個要分享給大家的就是算法和數據結構

淺談Java開發規範與開發細節(下),Java開發實戰講解_Java_04

  • 第二個就是數據庫的高頻知識點與性能優化

淺談Java開發規範與開發細節(下),Java開發實戰講解_Java_05

  • 第三個則是並發編程(72個知識點學習)

淺談Java開發規範與開發細節(下),Java開發實戰講解_Java_06

  • 最後一個是各大JAVA架構專題的面試點+解析+我的一些學習的書籍資料

淺談Java開發規範與開發細節(下),Java開發實戰講解_Java_07

還有更多的Redis、MySQL、JVM、Kafka、微服務、Spring全家桶等學習筆記這裏就不一一列舉出來

版权声明:本文为[代碼席爾瓦]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/09/20210918060951430x.html