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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

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

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

分享是最有效的學習方式。

案例背景

翻車了,為了cover線上一個業務場景,小貓新增了一個新的枚舉類型,盲目自信就沒有測試發生產了,由于是底層服務,上層調用導致計算邏輯有誤,造成資損。老板很生氣,后果很嚴重。

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

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;
    }
}

套餐計費方式是一種枚舉類型,每一種枚舉代表一種套餐方式,因為涉及的到資金相關業務,小貓想要穩妥,于是拓展了一個新的業務類型BIZ_CODE2,接下來只要當上層傳入指定的Code的時候,就可以進行計費了。下面為大概的演示代碼,

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

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

        //新拓展業務    
        }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");
    }
}

上述可見,代碼也沒有經過什么比較好的設計,純屬堆業務代碼,為了穩妥起見,小貓就照著以前的老代碼拓展出來了新的業務代碼,見上述備注。也沒有經過仔細的測試,然后欣然上線了。事后發現壓根他新的業務代碼就沒有生效,走的套餐計算邏輯還是默認的套餐計算邏輯。

容咱們盤一下這個技術細節,這可能也是很多初中級開發遇到的坑。

復盤分析

接下來,我們就來好好盤盤里面涉及的技術細節。其實造成這個事故的原因底層涉及兩種原因,

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

Intger底層分析

從上述代碼中,我們先看一下發生了什么。
當Integer變量inputBizCode被賦值的時候,其實java默認會調用Integer.valueOf()方法進行裝箱操作。

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

接下來我們來扒一下Integer的源碼看一下實現。源代碼如下

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

我們點開 IntegerCache.low 以及IntegerCache.high的時候就會發現其中對應著兩個值,分別是最小值為-128 最大的值為127,那么如此看來,如果目標值在-128~127之間的時候,那么直接會從cache數組中取值,否則就會新建對象。

我們再看一下IntegerCache中的cache是怎么被緩存進去的。

public final class Integer extends Number
        implements Comparable<Integer>, Constable, ConstantDesc {
            ...此處省略無關代碼
        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() {}
    }
}

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

當然我們從上面的源碼中其實不難發現其實最大值128并不是一成不變的,也可以通過自定義設置變成其他范圍,具體的應該是上述的這個配置:

java.lang.Integer.IntegerCache.high

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

那么Integer為什么是-127~128進行緩存了呢?翻了一下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數據在int范圍內使用最頻繁,為了減少頻繁創建對象帶來的內存消耗,這里其實采用了以空間換時間的涉及理念,也就是設計模式中的享元模式。

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

此處其實也是面試中的一個高頻考點,需要大家注意,另外的話關于享元模式此處不展開討論,后續老貓會穿插到設計模式中和大家一起學習使用。

值比較以及對象比較

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

“==”比較

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

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

“equals”比較

equals方法本質其實是屬于Object方法:

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

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

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

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

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

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

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

“hashCode”

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

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

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

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

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

咱們舉個例子簡單實驗一下:

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));
    }
}

上述的結果輸出為

equals:true
null

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

案例總結

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

派生面試題

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

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

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

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

問題2:equals 和 hashCode 之間有什么關系?

答案:
equals 和 hashCode 在Java中通常一起使用,以維護對象在散列集合(如HashMap和HashSet)中的正確行為。
如果兩個對象相等(根據equals方法的定義),那么它們的hashCode值應該相同。
也就是說,如果重寫了一個類的equals方法,通常也需要重寫hashCode方法,以便它們保持一致。
這是因為散列集合使用對象的hashCode值來確定它們在內部存儲結構中的位置。

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

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

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

總結

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

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 起碰在线 | 天天爱天天射 | 丝袜视频在线观看 | 欧美aa| 欧美特黄aaaaaa | 在线观看黄色免费网站 | julia一区二区三区中文字幕 | 潘金莲一级淫片aaaaa武则天 | 久久九九热视频 | 性视频在线播放 | 在线日韩三级 | 日韩aa| 欧美视频三区 | 亚洲一区欧洲二区 | v8888av| 六月丁香婷婷综合 | 最新国产黄色网址 | 爱爱视频在线免费观看 | 亚洲av永久无码精品 | 亚洲无线看| 97视频免费观看 | 色网站免费 | 奇米亚洲| 日本在线观看一区 | 久久99久久98精品免观看软件 | 舐め犯し波多野结衣在线观看 | 中文av字幕| 在线视频a | 日韩欧美视频二区 | 久久免费在线观看视频 | 亚洲免费观看 | 精品一区二区三区在线观看 | 男女羞羞无遮挡 | 一级片在线免费播放 | 中文字幕25页 | 久久精品国产av一区二区三区 | 国产精品免费一区二区三区都可以 | 国产精品厕所 | 91在线观看免费高清 | 日本bbwbbw | 日日爱夜夜操 | 国产又粗又长又大视频 | 亚洲色图欧美色 | 国产亚洲网站 | av私库在线观看 | 激情开心成人网 | www.超碰| 美女扒逼 | 人妻一区二区在线 | 久久97精品久久久久久久不卡 | 国模私拍大尺度裸体av | 精品国产一区二区不卡 | 在线看国产视频 | 国产69精品久久久久777 | www在线视频| www.超碰97 | 国产精品自拍亚洲 | 凹凸av在线 | 欧美a级大片 | 欧美日本一区二区三区 | 激情深爱五月 | 午夜福利啪啪片 | 黄视频网站免费看 | 亚洲欧美在线不卡 | 啪啪资源| 国产乱码精品一区二三赶尸艳谈 | 国产免费观看av | 奇米精品一区二区三区在线观看 | 污片网站 | 精品国产不卡 | 伊人婷婷久久 | 亚洲精品偷拍 | 国产又爽又黄视频 | 国产精品一级二级 | 亚洲成人av电影 | 大地资源中文第三页 | 免费一级全黄少妇性色生活片 | 国产激情小视频 | 日本视频一区二区 | 亚洲成熟丰满熟妇高潮xxxxx | 久操视频免费看 | 国产大片黄 | 黄色的视频网站 | 欧美高清hd | 国产中文一区 | 免费网站在线高清观看 | 国产精品一区二区三区免费观看 | a午夜| 成全影视在线观看第8季 | 亚洲 另类 春色 国产 | 日韩欧美亚洲天堂 | 日韩av高清在线观看 | 免费麻豆av | 亚洲成人精品视频 | 一区二区免费av | 中文字幕久久av | 亚洲经典视频 | 国产午夜精品福利视频 | 91精品系列|