日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > windows >内容正文

windows

拓展了个新业务枚举类型,资损了

發(fā)布時間:2023/12/24 windows 38 coder
生活随笔 收集整理的這篇文章主要介紹了 拓展了个新业务枚举类型,资损了 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

分享是最有效的學(xué)習(xí)方式。

案例背景

翻車了,為了cover線上一個業(yè)務(wù)場景,小貓新增了一個新的枚舉類型,盲目自信就沒有測試發(fā)生產(chǎn)了,由于是底層服務(wù),上層調(diào)用導(dǎo)致計算邏輯有誤,造成資損。老板很生氣,后果很嚴(yán)重。

產(chǎn)品提出了一個新的業(yè)務(wù)場景,新增一種套餐費(fèi)用的計算方式,由于業(yè)務(wù)比較著急,小貓覺得功能點(diǎn)比較小,開發(fā)完就決定迅速上線。不廢話貼代碼。

public enum BizCodeEnums {
    BIZ_CODE0(50),
    BIZ_CODE1(100),
    BIZ_CODE2(150); //新拓展

    private Integer code;

    BizCodeEnums(Integer code) {
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }
}

套餐計費(fèi)方式是一種枚舉類型,每一種枚舉代表一種套餐方式,因?yàn)樯婕暗牡劫Y金相關(guān)業(yè)務(wù),小貓想要穩(wěn)妥,于是拓展了一個新的業(yè)務(wù)類型BIZ_CODE2,接下來只要當(dāng)上層傳入指定的Code的時候,就可以進(jìn)行計費(fèi)了。下面為大概的演示代碼,

public class NumCompare {
    public static void main(String[] args) {

        Integer inputBizCode = 150; //上層業(yè)務(wù)
        if(BizCodeEnums.BIZ_CODE0.getCode() == inputBizCode) {
            method0();
        }else if(BizCodeEnums.BIZ_CODE1.getCode() == inputBizCode) {
            method1();

        //新拓展業(yè)務(wù)    
        }else if (BizCodeEnums.BIZ_CODE2.getCode() == inputBizCode) {
            method2();
        }
    }


    private static void method0(){
        System.out.println("method0 execute");
    }

    private static void method1(){
        System.out.println("method1 execute");
    }

    private static void method2(){
        System.out.println("method2 execute");
    }
}

上述可見,代碼也沒有經(jīng)過什么比較好的設(shè)計,純屬堆業(yè)務(wù)代碼,為了穩(wěn)妥起見,小貓就照著以前的老代碼拓展出來了新的業(yè)務(wù)代碼,見上述備注。也沒有經(jīng)過仔細(xì)的測試,然后欣然上線了。事后發(fā)現(xiàn)壓根他新的業(yè)務(wù)代碼就沒有生效,走的套餐計算邏輯還是默認(rèn)的套餐計算邏輯。

容咱們盤一下這個技術(shù)細(xì)節(jié),這可能也是很多初中級開發(fā)遇到的坑。

復(fù)盤分析

接下來,我們就來好好盤盤里面涉及的技術(shù)細(xì)節(jié)。其實(shí)造成這個事故的原因底層涉及兩種原因,

  1. 開發(fā)人員并沒有對Integer底層的原理吃透
  2. 開發(fā)人員對值比較以及地址比較沒有掌握好

Intger底層分析

從上述代碼中,我們先看一下發(fā)生了什么。
當(dāng)Integer變量inputBizCode被賦值的時候,其實(shí)java默認(rèn)會調(diào)用Integer.valueOf()方法進(jìn)行裝箱操作。

Integer inputBizCode = 100 
裝箱變成
Integer inputBizCode = Integer.valueOf(100)

接下來我們來扒一下Integer的源碼看一下實(shí)現(xiàn)。源代碼如下

    @IntrinsicCandidate
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

我們點(diǎn)開 IntegerCache.low 以及IntegerCache.high的時候就會發(fā)現(xiàn)其中對應(yīng)著兩個值,分別是最小值為-128 最大的值為127,那么如此看來,如果目標(biāo)值在-128~127之間的時候,那么直接會從cache數(shù)組中取值,否則就會新建對象。

我們再看一下IntegerCache中的cache是怎么被緩存進(jìn)去的。

public final class Integer extends Number
        implements Comparable<Integer>, Constable, ConstantDesc {
            ...此處省略無關(guān)代碼
        private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer[] cache;
        static Integer[] archivedCache;

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    h = Math.max(parseInt(integerCacheHighPropValue), 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            // Load IntegerCache.archivedCache from archive, if possible
            CDS.initializeFromArchive(IntegerCache.class);
            int size = (high - low) + 1;

            // Use the archived cache if it exists and is large enough
            if (archivedCache == null || size > archivedCache.length) {
                Integer[] c = new Integer[size];
                int j = low;
                for(int i = 0; i < c.length; i++) {
                    c[i] = new Integer(j++);
                }
                archivedCache = c;
            }
            cache = archivedCache;
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }
}

上述其實(shí)我們不難發(fā)現(xiàn),原來IntegerCache是Integer這個類的靜態(tài)內(nèi)部類,里面的數(shù)組進(jìn)行初始化的時候其實(shí)就是在Integer進(jìn)行初始化進(jìn)行類加載的時候就被緩存進(jìn)去了,被static修飾的屬性會存儲到我們的棧內(nèi)存中。在上面枚舉BizCodeEnums.BIZ_CODE1.getCode()也是Integer類型,說白了當(dāng)值在-127~128之間的時候,jvm拿到的其實(shí)是同一個地址的值。所以兩個值當(dāng)前相等。

當(dāng)然我們從上面的源碼中其實(shí)不難發(fā)現(xiàn)其實(shí)最大值128并不是一成不變的,也可以通過自定義設(shè)置變成其他范圍,具體的應(yīng)該是上述的這個配置:

java.lang.Integer.IntegerCache.high

本人自己親測設(shè)置了一下,如下圖,是生效了的。

那么Integer為什么是-127~128進(jìn)行緩存了呢?翻了一下Java API中,大概是這么解釋的:

Returns an Integer instance representing the specified int value. If a new Integer instance is not required, this method should generally be used in preference to the constructor Integer(int), as this method is likely to yield significantly better space and time performance by caching frequently requested values. This method will always cache values in the range -128 to 127, inclusive, and may cache other values outside of this range.

上述大概意思就是-128~127數(shù)據(jù)在int范圍內(nèi)使用最頻繁,為了減少頻繁創(chuàng)建對象帶來的內(nèi)存消耗,這里其實(shí)采用了以空間換時間的涉及理念,也就是設(shè)計模式中的享元模式。

其實(shí)在JDK中享元模式的應(yīng)用不僅僅只是局限于Integer,其實(shí)很多其他基礎(chǔ)類型的包裝類也有使用,咱們來看一下比較:

此處其實(shí)也是面試中的一個高頻考點(diǎn),需要大家注意,另外的話關(guān)于享元模式此處不展開討論,后續(xù)老貓會穿插到設(shè)計模式中和大家一起學(xué)習(xí)使用。

值比較以及對象比較

我們再來看一下兩種比較方式。

“==”比較

  1. 基本數(shù)據(jù)類型:byte,short,char,int,long,double,float,blooean,它們之間的比較,比較是它們的值;
  2. 引用數(shù)據(jù)類型:使用==比較的時候,比較的則是它們在內(nèi)存中的地址(heap上的地址)。

業(yè)務(wù)代碼中賦值為150的時候,底層代碼重新new出來一個新的Integer對象,那么此時new出來的那個對象的值在棧內(nèi)存中其實(shí)是新分配的一塊地址,和之前的緩存中的地址完全不同。兩分值進(jìn)行等號比較的時候當(dāng)然不會相等,所以也就不會走到method2方法塊中。

“equals”比較

equals方法本質(zhì)其實(shí)是屬于Object方法:

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

但是從上面這段代碼中我們可以明顯地看到 默認(rèn)的Object對象的equals方法其實(shí)和“==”是一樣的,比較的都是引用地址是否一致。

我們測試一下將上述的==變成equals的時候,其實(shí)代碼就沒有什么問題了

if (BizCodeEnums.BIZ_CODE2.getCode() == inputBizCode) 
改成
if (BizCodeEnums.BIZ_CODE2.getCode().equals(inputBizCode))

那么這個又是為什么呢?其實(shí)在一般情況下對象在集成Object對象的時候都會去重寫equals方法,Integer類型中的equals也不例外。我們來看一下重寫后的代碼:

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

上述我們看到如果使用Integer中的equals進(jìn)行比較的時候,最終比較的是基本類型值,就上述代碼比較的其實(shí)就是150==150?那么這種情況下,返回的就自然是true了,那么所以對應(yīng)的mthod也會執(zhí)行到了。

“hashCode”

既然已經(jīng)聊到equals重寫了,那么我們不得不再聊一下hashCode重寫。可能經(jīng)常會有面試官這么問“為什么重寫 equals方法時一定要重寫hashCode方法?”。

其實(shí)重寫equals方法時一定要重寫hashCode方法的原因是為了保證對象在使用散列集合(如HashMap、HashSet等)時能夠正確地進(jìn)行存儲和查找。
在Java中,hashCode方法用于計算對象的哈希碼,而equals方法用于判斷兩個對象是否相等。在散列集合中,對象的哈希碼被用作索引,通過哈希碼可以快速定位到存儲的位置,然后再通過equals方法判斷是否是相同的對象。

我們知道HashMap中的key是不能重復(fù)的,如果重復(fù)添加,后添加的會覆蓋前面的內(nèi)容。那么我們看看HashMap是如何來確定key的唯一性的(估計會有小伙伴對底層HashMap的完整實(shí)現(xiàn)感興趣,另外也是面試的高頻題,不過在此我們不展開,老貓后續(xù)盡量在其他文章中展開分析)。老貓的JDK版本是java17,我們一起看下源碼

  static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

查看代碼發(fā)現(xiàn),它是通過計算Map key的hashCode值來確定在鏈表中的存儲位置的。那么這樣就可以推測出,如果我們重寫了equals但是沒重寫hashCode,那么可能存在元素重復(fù)的矛盾情況。

咱們舉個例子簡單實(shí)驗(yàn)一下:

public class Person {
    private Integer age;
    private String name;

    public Person(Integer age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(age, person.age) && Objects.equals(name, person.name);
    }

//    @Override
//    public int hashCode() {
//        return Objects.hash(age, name);
//    }
}

public class TestPerson {
    public static void main(String[] args) {
        Person p1 = new Person(18,"ktdaddy");
        Person p2 = new Person(18,"ktdaddy");

        HashMap<Person,Object> map = new HashMap<>();

        map.put(p1, "1");

        System.out.println("equals:" + p1.equals(p2));
        System.out.println(map.get(p2));
    }
}

上述的結(jié)果輸出為

equals:true
null

由于沒有重寫hashCode方法,p1和p2的hashCode方法返回的哈希碼不同,導(dǎo)致它們在HashMap中被當(dāng)作不同的鍵,因此無法正確地獲取到值。如果重寫了hashCode方法,使得相等的對象返回相同的哈希碼,就可以正確地進(jìn)行存儲和查找操作。

案例總結(jié)

其實(shí)當(dāng)我們在日常維護(hù)的代碼的時候要勇于去質(zhì)疑現(xiàn)有代碼體系,如果發(fā)現(xiàn)不合理的地方,隱藏的坑點(diǎn),咱們還是需要立刻將其填好,以免發(fā)生類似小貓遇到的這種情況。
另外的話,寫代碼還是不能停留于會寫,必要的時候還是得翻看底層的源碼實(shí)現(xiàn)。只有這樣才能知其所以然,未來也才能夠更好地用好大神封裝的一些代碼。或者可以自主封裝一些好用的工具給他人使用。

派生面試題

上面的案例中涉及到的知識點(diǎn)可能會牽扯到這樣的面試題。

問題1: 如何自定義一個類的equals方法?

答案: 要自定義一個類的equals方法,可以按照以下步驟進(jìn)行:

  1. 在類中創(chuàng)建一個equals方法的覆蓋(override)。
  2. 確保方法簽名為public boolean equals(Object obj),并且參數(shù)類型是Object。
  3. 在equals方法中,首先使用==運(yùn)算符比較對象的引用,如果引用相同,返回true。
  4. 如果引用不同,檢查傳遞給方法的對象是否屬于相同的類。
  5. 如果屬于相同的類,將傳遞的對象強(qiáng)制轉(zhuǎn)換為相同類型,然后比較對象的字段,以確定它們是否相等。
  6. 最后,返回比較結(jié)果,通常是true或false。

問題2:equals 和 hashCode 之間有什么關(guān)系?

答案:
equals 和 hashCode 在Java中通常一起使用,以維護(hù)對象在散列集合(如HashMap和HashSet)中的正確行為。
如果兩個對象相等(根據(jù)equals方法的定義),那么它們的hashCode值應(yīng)該相同。
也就是說,如果重寫了一個類的equals方法,通常也需要重寫hashCode方法,以便它們保持一致。
這是因?yàn)樯⒘屑鲜褂脤ο蟮膆ashCode值來確定它們在內(nèi)部存儲結(jié)構(gòu)中的位置。

問題3:== 在哪些情況下比較的是對象內(nèi)容而不是引用?

答案:
在Java中,== 運(yùn)算符通常比較的是對象的引用。但在以下情況下,== 可以比較對象的內(nèi)容而不是引用:
對于基本數(shù)據(jù)類型(如int、char等),== 比較的是它們的值,而不是引用。
字符串常量池:對于字符串字面值,Java使用常量池來存儲它們,因此相同的字符串字面值使用==比較通常會返回true。

我是老貓,10Year+資深研發(fā)老鳥,讓我們一起聊聊技術(shù),聊聊人生。
個人公眾號,“程序員老貓”

總結(jié)

以上是生活随笔為你收集整理的拓展了个新业务枚举类型,资损了的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。