O yıllarda bastığımız Java çukurları

Yazar | Chang Yi

Kaynak | Alibaba Middleware (ID: Aliware_2018)

Mühür haritası | Oriental IC

"Üçten fazla yoktur" diye eski bir Çin atasözü vardır, yani aynı hatayı yapan bir kişi bir, iki veya üç kez affedilebilir ve üç defadan fazla affedilemez. Birisi bu "üç" ün hayali bir sayı olduğuna ve birden çok kez ifade etmek için kullanıldığına dikkat çekti, bu nedenle "üçten başka hiçbir şey" "üç" ü içermez. "Üç şey" paketinin "üç" içermediğine gelince, herkesin alt çizgisiyle bir ilgisi olabilir, felsefe kategorisine aittir ve bu makalenin kapsamı dışındadır.

Aynısı kod yazmak için de geçerlidir. Aynı kod "çukur", ilk seferde adım atmak "büyüme deneyimi", ikinci seferde adım atmak "izlenimi derinleştirmek", üçüncü kez adım atmak "iyi görünmemek", üç defadan fazla adım atmak " Umutsuz". Bu makalede yazar bazı kod çukurlarını özetledi, sorun olgusunu tanımladı, sorun analizi yaptı ve çukurlardan kaçınmak için yöntemler sağladı. Umarım günlük kodlamanızda bu tür kod çukurlarından kaçınabilirsiniz.

Nesne karşılaştırma yöntemi

JDK1.7 tarafından sağlanan Objects.equals yöntemi, nesnelerin karşılaştırılmasını kolaylaştırır ve sıkıcı boş işaretçi denetimlerini etkili bir şekilde önler.

1.1. Sorun fenomeni

JDK1.7'den önce, kısa bir tam sayı, tam sayı veya uzun tam sayı paket veri türünün bir sabite eşit olup olmadığına karar verirken, genellikle şunu yazardık:

Short shortValue = (kısa) 12345; System.out.println (shortValue == 12345); // doğru System.out.println (12345 == shortValue); // doğru Tamsayı intValue = 12345; System.out.println (intValue == 12345); // doğru System.out.println (12345 == intValue); // doğru Uzun longValue = 12345L; System.out.println (longValue == 12345); // doğru System.out.println (12345 == longValue); // doğru

JDK1.7'den bu yana, Objects.equals yöntemi sağlanmıştır ve işlevsel programlama önerilir. Kodu aşağıdaki şekilde değiştirin:

Short shortValue = (kısa) 12345; System.out.println (Objects.equals (shortValue, 12345)); // false System.out.println (Objects.equals (12345, shortValue)); // false Tamsayı intValue = 12345; System.out.println (Objects.equals (intValue, 12345)); // doğru System.out.println (Objects.equals (12345, intValue)); // doğru Uzun longValue = 12345L; System.out.println (Objects.equals (longValue, 12345)); // false System.out.println (Objects.equals (12345, longValue)); // false

Neden doğrudan Objects.equals yöntemini değiştirmek çıktı sonucunun farklı olmasına neden olur?

1.2. Problem analizi

İlk kod parçasını yeniden derleyerek, "System.out.println (shortValue == 12345);" ifadesinin bayt kodu talimatını aşağıdaki gibi alırız:

7 getstatic java.lang.System.out: java.io.PrintStream 10 aload_111 invokevirtual java.lang.Short.shortValue: kısa 14 sipush 1234517 if_icmpne 2420 iconst_121 goto 2524 iconst_025 invokevirtual java.io.PrintStream.println (boolean): void

Derleyicinin, paket veri türüne karşılık gelen temel veri türünü belirleyeceği ve karşılaştırma için bu temel veri türünün talimatlarını (yukarıdaki bayt kodu talimatlarında sipush ve if_icmpne gibi) kullanacağı ortaya çıkmıştır; bu, derleyicinin sabit verileri otomatik olarak vermesine eşdeğerdir. Zorunlu dönüştürme türü.

Derleyici, Objects.equals yöntemini kullandıktan sonra neden sabitlerin veri türü dönüşümünü otomatik olarak zorlamıyor? İkinci kod parçasını yeniden derleyerek, "System.out.println (Objects.equals (shortValue, 12345));" ifadesinin bayt kodu talimatlarını aşağıdaki gibi alırız:

7 getstatic java.lang.System.out: java.io.PrintStream 10 aload_111 sipush 1234514 invokestatic java.lang.Integer.valueOf (int): java.lang.Integer 17 invokestatic java.util.Objects.equals (java.lang.Object, java.lang.Object): boole 20 invokevirtual java.io.PrintStream.println (boolean): void

Birebir anlama dayalı derleyicinin, 12345 sabitinin varsayılan temel veri türünün int olduğunu düşündüğü, bu nedenle otomatik olarak paketleme veri türüne Tamsayı'ya dönüştürüleceği ortaya çıktı.

Java dilinde, tamsayılar için varsayılan veri türü int'dir ve ondalık sayılar için varsayılan veri türü double'dır.

Objects.equals yönteminin kod uygulamasını inceleyelim:

public static boolean equals (Nesne a, Nesne b) { dönüş (a == b) || (a! = a.equals (b)); }

Bunlar arasında, "a.equals (b)" ifadesi Short.equals yöntemini kullanacaktır.

Short.equals yönteminin kodu şu şekilde uygulanır:

public boolean equals (Object obj) { if (obj instanceof Short) { dönüş değeri == ((Kısa) obj) .shortValue; } yanlış dönüş; }

Kod uygulaması yoluyla analiz: karşılık gelen ifade "System.out.println (Objects.equals (shortValue, 12345));", çünkü Objects.equals öğesinin iki parametre nesne türü tutarsızdır, biri paketleme veri türü Short, diğeri paketleme verileridir Tamsayı yazın, bu nedenle son karşılaştırma sonucu yanlış olmalıdır. Benzer şekilde, "System.out.println (Objects.equals (intValue, 12345));" ifadesi, çünkü Objects.equals öğesinin iki parametre nesne türü aynıdır ve her ikisi de tamsayı veri türünü sarar ve aynı değere sahiptir, dolayısıyla son karşılaştırma sonucu Doğru olmalı.

1.3. Çukurlardan kaçınma yöntemi

1. İyi kodlama alışkanlıklarını sürdürün ve veri türlerinin otomatik dönüştürülmesinden kaçının.

Veri türlerinin otomatik olarak dönüştürülmesinden kaçınmak için, daha bilimsel bir yazma yolu, sabitleri doğrudan karşılık gelen temel veri türleri olarak bildirmektir.

İlk kod parçası şu şekilde yazılabilir:

Short shortValue = (kısa) 12345; System.out.println (shortValue == (kısa) 12345); // doğru System.out.println ((kısa) 12345 == shortValue); // doğru Tamsayı intValue = 12345; System.out.println (intValue == 12345); // doğru System.out.println (12345 == intValue); // doğru Uzun longValue = 12345L; System.out.println (longValue == 12345L); // doğru System.out.println (12345L == longValue); // doğru

İkinci kod parçası şu şekilde yazılabilir:

Short shortValue = (kısa) 12345; System.out.println (Objects.equals (shortValue, (short) 12345)); // true System.out.println (Objects.equals ((kısa) 12345, shortValue)); // true Tamsayı intValue = 12345; System.out.println (Objects.equals (intValue, 12345)); // doğru System.out.println (Objects.equals (12345, intValue)); // doğru Uzun longValue = 12345L; System.out.println (Objects.equals (longValue, 12345L)); // true System.out.println (Objects.equals (12345L, longValue)); // doğru

2. Veri türü uyuşmazlığı sorunlarını olabildiğince erken bulmak için geliştirme araçlarını veya eklentileri kullanın.

Eclipse soru penceresinde şu soruyu göreceğiz:

Eşittir için olası olmayan bağımsız değişken türü: int, Short ile ilgisiz görünüyor Eşittir için olası olmayan bağımsız değişken türü: Kısa, int ile ilgisiz görünüyor Eşittirler için olası olmayan bağımsız değişken türü: int, Long ile ilgisiz görünüyor Eşittirler için olası olmayan bağımsız değişken türü: Uzun, int ile ilgisiz görünüyor

FindBugs eklentisini taradığımızda şu uyarıyı göreceğiz:

Xxx.Xxx.main (Dize) içinde Short.equals (Tamsayı) çağrısı Xxx.Xxx.main (Dize) içinde Tamsayı.equals (Kısa) çağrısı Xxx.Xxx.main (Dize) içinde Long.equals (Tamsayı) çağrısı Xxx.Xxx.main (Dize) içinde Tamsayı.equals (Uzun) çağrısı

3. Düzenli birim testleri yapın, Sorun, araştırma ve geliştirme aşamasında bulunur.

"İyi bir şey yapmadan küçük bir şey yapmayın." Değişiklikler küçük olduğu için birim testi yapmayın. Hatalar genellikle aşırı güvenli kodunuzda görünür. Bu tür sorunlar için, bir birim testi yaptığınız sürece, sorunu bulabilirsiniz.

Üçlü ifade paketten çıkarma

Üçlü ifade, Java kodlamasında sabit bir dilbilgisi biçimidir: "Koşullu ifade? İfade 1: İfade 2". Üçlü ifadenin mantığı şudur: "Koşullu ifade doğruysa, ifade 1'i çalıştırın; aksi takdirde, ifade 2'yi çalıştırın".

2.1. Sorun fenomeni

boole koşulu = yanlış; Çift değer1 = 1.0D; Çift değer2 = 2.0D; Çift değer3 =; Çift sonuç = koşul? Değer1 * değer2: değer3; // Boş işaretçi istisnası atın

Koşullu ifade koşulu false değerine eşit olduğunda, Double nesne değeri3 doğrudan Double nesnesi sonucuna atanır Prensipte bir sorun yoktur Neden boş işaretçi istisnası (PointerException) atılır?

2.2. Problem analizi

Kodu yeniden derleyerek, aşağıdaki gibi "Double sonuç = koşul? Değer1 * değer2: değer3;" ifadesinin bayt kodu talimatını alırız:

17 iload_118 ifeq 3321 aload_222 invokevirtual java.lang.Double.doubleValue: double 25 aload_326 invokevirtual java.lang.Double.doubleValue: çift 29 dmul 30 goto 3833 aload 435 invokevirtual java.lang.Double.doubleValue: çift 38 invokestatic java.lang.Double.valueOf (double): java.lang.Double 41 astore 543 getstatic java.lang.System.out: java.io.PrintStream 46 aload 5

33. satırda, Double nesne değeri3'ü işlenen yığınına yükleyin; 35. satırda, Double nesne değeri3'ün doubleValue yöntemini çağırın. Şu anda, değer3 bir boş nesne olduğundan, doubleValue yönteminin çağrılması boş bir işaretçi istisnası atmalıdır. Ama neden boş nesne değeri3'ü temel veri türüne double dönüştürelim?

Üçlü ifadelerin tür dönüştürme kurallarını almak için ilgili bilgileri kontrol edin:

  • İki ifade aynı türdeyse, dönüş değeri türü bu türdür;

  • İki ifade farklı türdeyse, ancak türler dönüştürülebilir değilse, dönüş değeri türü Object olur;

  • İki ifade türü farklıysa, ancak türler dönüştürülebiliyorsa, önce paketlenmiş veri türünü temel veri türüne dönüştürün ve ardından temel veri türünün dönüştürme kurallarını (bayt < kısa (karakter) < int < uzun < yüzer < double) dönüştürmek için, dönüş değeri türü en yüksek önceliğe sahip temel veri türüdür.

  • Kural analizine göre, ifade 1 (değer1 * değer2) hesaplamadan sonra çift temel veri türünü döndürür ve ifade 2 (değer3) Double paket veri türünü döndürür.Ternary ifadesinin tür dönüştürme kurallarına göre, son dönüş türü temel verilerdir Double yazın. Bu nedenle, koşullu ifade koşulu false değerine eşit olduğunda, boş nesne değerinin double temel veri türüne dönüştürülmesi gerekir, bu nedenle value3'ün doubleValue yöntemi çağrılır ve bir boş işaretçi istisnası atılır.

    Aşağıdaki durumlar, üçlü ifadelerin tür dönüştürme kurallarını doğrulamak için kullanılabilir:

    boole koşulu = yanlış; Çift değer1 = 1.0D; Çift değer2 = 2.0D; Çift değer3 =; Tamsayı değeri4 =; // Dönüş türü Double, boş işaretçi istisnası atılmaz Çift sonuç1 = koşul? Değer1: değer3; // Dönüş türü çift, boş gösterici istisnası atılacak Çift sonuç2 = koşul? Değer1: değer4; // Dönüş türü çift, boş işaretçi istisnası atılmaz Çift sonuç3 =! Koşul? Değer1 * değer2: değer3; // Dönüş türü çift, boş gösterici istisnası atılacak Çift sonuç4 = koşul? Değer1 * değer2: değer3;

    2.3. Çukurlardan kaçınma yöntemi

    1. Üçlü ifadeler kullanmaktan kaçınmaya çalışın, bunun yerine if-else ifadelerini kullanabilirsiniz.

    Üçlü ifadede aritmetik hesaplamalar ve paketleme veri türleri varsa, bunun yerine if-else ifadelerini kullanmayı düşünebilirsiniz. Kodu aşağıdaki gibi yeniden yazın:

    boole koşulu = yanlış; Çift değer1 = 1.0D; Çift değer2 = 2.0D; Çift değer3 =; Çift sonuç; if (koşul) { sonuç = değer1 * değer2; } Başka { sonuç = değer3; }

    2. Veri türlerinin otomatik olarak dönüştürülmesini önlemek için temel veri türlerini kullanmaya çalışın.

    Üçlü ifadede aritmetik hesaplamalar ve paketleme veri türleri varsa, bunun yerine if-else ifadelerini kullanmayı düşünebilirsiniz. Kodu aşağıdaki gibi yeniden yazın:

    boole koşulu = yanlış;

    çift değer1 = 1.0D;

    çift değer2 = 2.0D;

    çift değer3 = 3.0D;

    çift sonuç = koşul? değer1 * değer2: değer3;

    3. Kapsama birimi testi yapın ve sorunu geliştirme aşamasında bulmaya çalışın.

    Bazı birim test durumları yazdığınız ve bazı kapsam testleri yaptığınız sürece, bunun gibi sorunlar önceden keşfedilebilir.

    Genel nesne ataması

    Java jenerikleri, JDK1.5'te sunulan yeni bir özelliktir. Özü, parametreleştirilmiş bir türdür, yani veri türü bir parametre olarak kullanılır.

    3.1. Sorun fenomeni

    Kullanıcı verileri sayfalama sorgusu yapılırken, yazım hataları nedeniyle aşağıdaki kod yazılmıştır:

    1. PageDataVO.java:

    / ** Sayfalama verisi VO sınıfı * / @Getter @ Ayarlayıcı @Hayalhanemersin @NoArgsConstructor @AllArgsConstructor genel sınıf PageDataVO < T > { /** Toplam sayısı */ özel Uzun totalCount; /** Veri sayfaları */ özel Liste < T > dataList; }

    2. UserDAO.java:

    / ** Kullanıcı DAO arayüzü * / @Mapper genel arayüz UserDAO { / ** Kullanıcı sayısını sayın * / public Long countUser (@Param ("sorgu") UserQueryVO sorgusu); / ** Kullanıcı bilgilerini sorgulayın * / genel Liste < UserDO > queryUser (@Param ("sorgu") UserQueryVO sorgusu); }

    3. UserService.java:

    / ** Kullanıcı hizmeti * / @Hizmet public class UserService { / ** Kullanıcı DAO * / @Autowired özel UserDAO userDAO; / ** Kullanıcı bilgilerini sorgulayın * / genel PageDataVO < UserVO > queryUser (UserQueryVO sorgusu) { Liste < UserDO > dataList =; Uzun totalCount = userDAO.countUser (sorgu); eğer (Objects.non (totalCount) totalCount.compareTo (0L) ise > 0) { dataList = userDAO.queryUser (sorgu); } yeni PageDataVO (totalCount, dataList) döndürür; } }

    4. UserController.java:

    / ** Kullanıcı denetleyici sınıfı * / @Kontroller @RequestMapping ("/ kullanıcı") public class UserController { / ** Kullanıcı Hizmeti * / @Autowired özel UserService userService; / ** Sorgu kullanıcısı * / @Kafadergisi @RequestMapping (value = "/ sorgu", yöntem = RequestMethod.POST) genel Sonuç < PageDataVO < UserVO > > queryUser (@RequestBody UserQueryVO sorgusu) { PageDataVO < UserVO > pageData = userService.queryUser (sorgu); ResultBuilder.success (pageData) döndür; } }

    Yukarıdaki kodun herhangi bir derleme sorunu yoktur, ancak UserDO'daki bazı gizli alanları ön uca döndürür. Dikkatli okuyucular, UserService sınıfının queryUser yönteminin "yeni PageDataVO (totalCount, dataList);" ifadesine List koyduğumuzu keşfetmiş olabilirler. < UserDO > DataList nesnesi PageDataVO'ya atanır < UserVO > Liste < UserVO > Alan verileri

    Soru şudur: Geliştirme aracı neden derleme hatalarını rapor etmiyor?

    3.2. Problem analizi

    Tarihsel nedenlerden dolayı, parametreli türlerin ve ilkel türlerin uyumlu olması gerekir. Nasıl uyumlu olduğunu görmek için ArrayList'i örnek olarak alalım.

    Önceki yazı:

    ArrayList listesi = new ArrayList;

    Güncel yazı:

    Dizi Listesi < Dize > list = new ArrayList < Dize > ;

    Önceki kod ile uyumlu olduğu ve çeşitli nesne referansları arasında değer aktarıldığı düşünüldüğünde, aşağıdakiler kaçınılmaz olarak ortaya çıkacaktır:

    // ilk durum ArrayList list1 = new ArrayList < Dize > ; // ikinci durum Dizi Listesi < Dize > list2 = new ArrayList;

    Bu nedenle, Java derleyicisi yukarıdaki iki türle uyumludur, derleme hatası oluşmaz, ancak derleme uyarıları görüntülenir. Ancak, geliştirme aracım derleme sırasında herhangi bir uyarı vermedi.

    Karşılaştığımız sorunları analiz edelim ve aslında aynı anda iki duruma çarpalım:

    1. Listeyi koyun < UserDO > Nesneyi List'e atamak ilk duruma isabet eder;

    2. PageDataVO nesnesini PageDataVO'ya atayın < UserVO > , İkinci durumu vur.

    Nihai sonuç: Listeyi sihirli bir şekilde koyduk < UserDO > Listeye atanan nesne < UserVO > .

    Sorunun kökü, PageDataVO nesnesini başlattığımızda zorunlu tür kontrolüne ihtiyaç duymadığımızdır.

    3.3. Çukurlardan kaçınma yöntemi

    1. Genel nesneleri başlatırken, elmas sözdizimi önerilir.

    "Alibaba Java Geliştirme Kılavuzu" nda, böyle bir önerilen kural vardır:

    [Öneri] Genel tanımları ayarlarken, elmas sözdizimi kullanın veya JDK7 ve üzeri sürümlerde tümünü atlayın. Açıklama: Doğrudan kullanılan elmas jenerik türü, yani elmas < > Önceden belirtilen türe başvurmak için. Olumlu örnek:

    // < > elmas yolu HashMap < Dize, Dize > userCache = yeni HashMap < > (16); // Tümü atlandı Dizi Listesi < Kullanıcı > kullanıcılar = new ArrayList (10);

    Aslında, genel nesneleri başlatırken tümünün çıkarılması önerilmez. Bu, tür kontrolünü önleyecek ve yukarıdaki sorunlara neden olacaktır.

    Genel nesneleri başlatırken, elmas sözdiziminin kullanılması önerilir, kod aşağıdaki gibidir:

    yeni PageDataVO döndür < > (totalCount, dataList);

    Şimdi Eclipse'in sorun penceresinde şu hatayı göreceğiz:

    PageDataVO için tür bağımsız değişkenleri çıkarılamıyor < >

    Yani Listeyi koymayı unuttuğumuzu biliyoruz < UserDO > Listeye dönüştürülen nesne < UserVO > Nesne.

    2. Birim testi yapılırken, veri içeriğinin karşılaştırılması gerekir.

    Birim testinde normal çalışma bir göstergedir ancak doğru veri daha önemli bir göstergedir.

    Genel öznitelik kopyası

    Spring'in BeanUtils.copyProperties yöntemi, çok kullanışlı bir özellik kopyalama aracı yöntemidir.

    4.1. Sorun fenomeni

    Veritabanı geliştirme spesifikasyonuna göre, veritabanı tablosu üç alan içermelidir: id, gmt_create ve gmt_modified. Bunların arasında, kimlik alanı, veri miktarına bağlı olarak int veya long tipinde olabilir (not: Ali'nin belirtimi, uzun tipte olmasını gerektirir, burada örneğin, int veya long tipinde olmasına izin verilir).

    Bu nedenle, bu üç alanı çıkarın ve bir BaseDO temel sınıfı tanımlayın:

    / ** Temel DO sınıfı * /

    @Getter

    @ Ayarlayıcı

    @Hayalhanemersin

    genel sınıf BaseDO < T > {

    özel T id;

    özel Tarih gmtCreate;

    özel Tarih gmtModified;

    }

    Kullanıcı tablosu için bir UserDO sınıfı tanımlanmıştır:

    / ** Kullanıcı DO sınıfı * /

    @Getter

    @ Ayarlayıcı

    @Hayalhanemersin

    public class UserDO, BaseDO'yu genişletir < Uzun > {

    özel Dize adı;

    private String açıklaması;

    }

    Sorgu arabirimi için bir UserVO sınıfı tanımlanır:

    / ** Kullanıcı VO kategorisi * / @Getter @ Ayarlayıcı @Hayalhanemersin public statik sınıf UserVO { özel Long id; özel Dize adı; private String açıklaması; }

    Sorgu kullanıcı hizmeti arabirimini uygulayın, uygulama kodu aşağıdaki gibidir:

    / ** Kullanıcı hizmeti * / @Hizmet public class UserService { / ** Kullanıcı DAO * / @Autowired özel UserDAO userDAO; / ** Sorgu kullanıcısı * / genel Liste < UserVO > queryUser (UserQueryVO sorgusu) { // Kullanıcı bilgilerini sorgulayın Liste < UserDO > userDOList = userDAO.queryUser (sorgu); if (CollectionUtils.isEmpty) { Return Collections.emptyList; } // Dönüştürülen kullanıcıların listesi Liste < UserVO > userVOList = new ArrayList < > (userDOList.size); for (UserDO userDO: userDOList) { UserVO userVO = yeni UserVO; BeanUtils.copyProperties (userDO, userVO); userVOList.add (userVO); } // Kullanıcı listesine dön userVOList'i döndür; } }

    Test yoluyla, kullanıcı hizmeti arayüzünü sorgulayan bir problem bulacağız, kullanıcı kimliğinin değeri geri dönmüyor.

    4.2. Problem analizi

    Mantıksal olarak, UserDO ve UserVO sınıflarının id alanları Uzun tiptedir, eğer tip yoksa dönüştürülemez ve normal olarak atanabilmelidir. Manuel atamayı deneyin, kod aşağıdaki gibidir:

    for (UserDO userDO: userDOList) { UserVO userVO = yeni UserVO; userVO.setId (userDO.getId); userVO.setName (userDO.getName); userVO.setDescription (userDO.getDescription); userVOList.add (userVO); }

    Testten sonra, yukarıdaki kod normal olarak geri döner ve kullanıcı kimliği değeri başarıyla döndürülür.

    Dolayısıyla, BeanUtils.copyProperties aracı yönteminin sorunu budur. Hata Ayıklama modunda çalıştırın, BeanUtils.copyProperties aracı yöntemini girin ve aşağıdaki verileri alın:

    UserDO sınıfının getId yönteminin dönüş türünün Long türü olmadığı, ancak genel tür tarafından Object türüne geri yüklendiği ortaya çıktı. Ve aşağıdaki ClassUtils.isAssignable araç yöntemi, Object türünün Long tipine atanıp atanamayacağına karar verir ve elbette, özellik kopyasını engelleyen false değerini döndürür.

    Yazar neden "önce öznitelik değerini almayı ve sonra atanıp atanamayacağını yargılamayı" düşünmüyor? Önerilen kod aşağıdaki gibidir:

    Nesne değeri = readMethod.invoke (kaynak);

    if (Objects.non (value) ClassUtils.isAssignable (writeMethod.getParameterTypes, value.getClass)) {

    ... // Atama ile ilgili kod

    }

    4.3. Çukurlardan kaçınma yöntemi

    1. Üçüncü taraf araç setlerine körü körüne güvenmeyin. Herhangi bir araç setinde sorun olabilir.

    Java'da, Apachenin ortak-lang3 ve ortak-koleksiyonları, Google'ın guava gibi pek çok üçüncü taraf araç takımı vardır ... hepsi çok yararlı üçüncü taraf araç takımlarıdır. Ancak, üçüncü taraf araç setlerine körü körüne güvenmeyin. Herhangi bir araç setinde sorun olabilir.

    2. Kopyalanacak daha az öznitelik varsa, öznitelik kopyası için manuel olarak kodlayabilirsiniz.

    Kopyalama özelliklerini yansıtmak için BeanUtils.copyProperties kullanmanın temel avantajı, kod miktarını kaydetmesidir, ancak ana dezavantajı programın performansının düşmesidir. Bu nedenle, kopyalanacak daha az öznitelik varsa, öznitelik kopyası için manüel olarak kodlayabilirsiniz.

    3. Birim testi gerçekleştirilmeli ve veri içeriği karşılaştırılmalıdır.

    Kodu yazdıktan sonra, birim testleri yapmalı ve veri içeriğini karşılaştırmalısınız. Bunu hafife almayın: Araç seti olgunlaşmış ve kod basit ve problemler imkansız.

    Nesne ağırlığını ayarlayın

    Java dilinde Set data yapısı nesneleri sıralamak için kullanılabilir Ortak Set sınıfları HashSet ve LinkedHashSet'i içerir.

    5.1. Sorun fenomeni

    Şehir verilerini bir CSV dosyasından okumak için bir şehir yardımcı sınıfı yazılır:

    / ** Şehir yardımcı sınıfı * / @Filmdenkare public class CityHelper { / ** Test ana yöntemi * / public static void main (String args) { Toplamak < Kent > cityCollection = readCities2 ("Cities.csv"); log.info (JSON.toJSONString (cityCollection)); } / ** Şehri oku * / genel statik Koleksiyon < Kent > readCities (Dize dosyaAdı) { deneyin (FileInputStream stream = new FileInputStream (fileName); InputStreamReader okuyucu = yeni InputStreamReader (akış, "GBK"); CSVParser ayrıştırıcı = new CSVParser (okuyucu, CSVFormat.DEFAULT.withHeader)) { Ayarlamak < Kent > citySet = yeni HashSet < > (1024); Yineleyici < CSVRecord > iterator = ayrıştırıcı.iterator; while (iterator.hasNext) { citySet.add (parseCity (iterator.next)); } dönüş citySet; } catch (IOException e) { log.warn ("Tüm şehir istisnalarını oku", e); } Return Collections.emptyList; } / ** Şehri analiz edin * / özel statik Şehir parseCity (CSVRecord kaydı) { Şehir şehir = yeni Şehir; city.setCode (kayıt.get (0)); şehir.setName (kayıt.get (1)); dönüş şehri; } / ** Şehir sınıfı * / @Getter @ Ayarlayıcı @Hayalhanemersin özel statik sınıf Şehir { /** Şehir kodu */ özel Dize kodu; /** Şehir İsmi */ özel Dize adı; } }

    Kodda HashSet veri yapısı kullanılmıştır.Amaç, şehir verilerinin tekrarlanmasını önlemek ve okunan şehir verilerinin tekrarlanmasını zorlamaktır.

    Girdi dosyasının içeriği aşağıdaki gibi olduğunda:

    Kod adı 010, Pekin 020, Guangzhou 010, Pekin

    Ayrıştırılmış JSON sonuçları aşağıdaki gibidir:

    Ancak, "Pekin" şehri küme düşmedi.

    5.2. Problem analizi

    Koleksiyon Setine bir nesne eklerken, koleksiyon önce eklenecek nesnenin hashCode'unu hesaplar ve mevcut nesneyi saklamak için bu değere dayalı bir konum elde eder. Bu konumda herhangi bir nesne yoksa, küme nesnenin kümede olmadığını düşünür ve onu doğrudan ekler. Bu konumda bir nesne varsa, koleksiyona eklenecek nesneyi bu konumdaki nesneyle eşittir yöntemiyle karşılaştırın: eşittir yöntemi false değerini döndürürse, koleksiyon nesnenin koleksiyonda olmadığını düşünür ve ardından Nesne bu nesnenin arkasına yerleştirilir; eşittir yöntemi true değerini döndürürse, nesnenin koleksiyonda zaten var olduğu kabul edilir ve nesne koleksiyona eklenmez. Bu nedenle, hash tablosunda iki öğenin yinelenip yinelenmediğini belirlemek için hashCode yöntemi ve eşittir yöntemi kullanılır. HashCode yöntemi, tablodaki verilerin depolama konumunu belirler ve eşittir yöntemi, aynı verilerin tabloda bulunup bulunmadığını belirler.

    Yukarıdaki problem analiz edildiğinde, City sınıfının hashCode yöntemi ve eşittir yöntemi geçersiz kılınmadığından, Object sınıfının hashCode yöntemi ve eşittir yöntemi benimsenecektir. Uygulaması aşağıdaki gibidir:

    genel yerel int hashCode; public boolean equals (Object obj) { dönüş (this == obj); }

    Object sınıfının hashCode yönteminin nesnenin adresini döndüren yerel bir yöntem olduğu görülebilir; Object sınıfının eşittir yöntemi yalnızca nesnelerin eşit olup olmadığını karşılaştırır. Bu nedenle, iki özdeş Pekin verisi için, ayrıştırma sırasında farklı City nesneleri başlatıldığından, hashCode yöntemi ve eşittir yöntemi farklı değerlere sahiptir ve bunlar, Set tarafından farklı nesneler olarak kabul edilmelidir, bu nedenle tekrar olmaz.

    Ardından, City sınıfının hashCode yöntemini ve eşittir yöntemini yeniden yazıyoruz, kod aşağıdaki gibidir:

    / ** Şehir sınıfı * / @Getter @ Ayarlayıcı @Hayalhanemersin özel statik sınıf Şehir { /** Şehir kodu */ özel Dize kodu; /** Şehir İsmi */ özel Dize adı; / ** Hakim eşittir * / @Override public boolean equals (Object obj) { if (obj == this) { doğruya dön; } if (Objects.is (obj)) { yanlış dönüş; } eğer (obj.getClass! = this.getClass) { yanlış dönüş; } return Objects.equals (this.code, ((City) obj) .code); } / ** Karma kodlama * / @Override public int hashCode { return Objects.hashCode (this.code); } }

    Test programı tekrar desteklenir ve ayrıştırılmış JSON sonuçları aşağıdaki gibidir:

    Sonuç doğrudur ve "Pekin" şehri sıralanmıştır.

    5.3. Çukurlardan kaçınma yöntemi

    1. Verinin benzersiz olduğunu belirlerken, Set yerine List'i kullanabilirsiniz.

    Ayrıştırılan şehir verilerinin benzersiz olduğu belirlendiğinde, yeniden yerleştirme işlemini gerçekleştirmeye gerek yoktur ve bunları saklamak için doğrudan Listeyi kullanabilirsiniz.

    Liste < Kent > citySet = new ArrayList < > (1024); Yineleyici < CSVRecord > iterator = ayrıştırıcı.iterator; while (iterator.hasNext) { citySet.add (parseCity (iterator.next)); } dönüş citySet;

    2. Verinin benzersiz olmadığı belirlendiğinde Set yerine Map kullanılabilir.

    Ayrıştırılan şehir verilerinin benzersiz olmadığı belirlendiğinde, tekilleştirme işlemini gerçekleştirmek için şehir adını yüklemeniz gerekir ve bunları depolamak için doğrudan Map'i kullanabilirsiniz. Neden City sınıfının hashCode yönteminin uygulanması ve ardından kilo aldırmak için HashSet kullanılması önerilmiyor? Birincisi, DO sınıfına iş mantığını koymak istemiyorum; ikincisi, kodun okunmasını, anlaşılmasını ve sürdürülmesini kolaylaştırmak için tekilleştirme alanını koda koyun.

    Harita < Dize, Şehir > cityMap = yeni HashMap < > (1024); Yineleyici < CSVRecord > iterator = ayrıştırıcı.iterator; while (iterator.hasNext) { Şehir şehir = parseCity (iterator.next); cityMap.put (city.getCode, şehir); } return cityMap.values;

    3. Java dili belirtimini izleyin ve hashCode yöntemini ve eşittir yöntemini yeniden yazın.

    HashCode yöntemini ve eşittir yöntemini geçersiz kılmayan özel sınıflar, Set içinde kullanılmamalıdır.

    Kamu yöntemi ajansı

    SpringCGLIB proxy'si tarafından oluşturulan proxy sınıfı, proxy sınıfındaki nihai olmayan yöntemi geçersiz kılarak proxy'yi uygulayan miras alınan bir sınıftır. Bu nedenle, SpringCGLIB proxy sınıfı son bir sınıf olamaz ve proxy yöntemi, miras mekanizması tarafından kısıtlanan son bir yöntem olamaz.

    6.1. Sorun fenomeni

    İşte basit bir örnek: Yalnızca süper kullanıcılar şirketi silme yetkisine sahiptir ve tüm hizmet işlevleri, istisnaları ele almak için AOP tarafından durdurulur. Örnek kod aşağıdaki gibidir:

    1. UserService.java:

    / ** Kullanıcı hizmeti * / @Hizmet public class UserService { /** kök */ özel Kullanıcı süperKullanıcısı; / ** Süper kullanıcı oluştur * / public void setSuperUser (User superUser) { this.superUser = superUser; } / ** Süper kullanıcı edinin * / genel son Kullanıcı getSuperUser { this.superUser döndür; } }

    2. CompanyService.java:

    / ** Şirket hizmeti * / @Hizmet public class CompanyService { / ** DAO Şirketi * / @Autowired özel ŞirketDAO şirketiDAO; / ** Kullanıcı Hizmeti * / @Autowired özel UserService userService; / ** Şirketi sil * / public void deleteCompany (Long companyId, Long operatorId) { // Süper kullanıcıyı ayarla userService.setSuperUser (yeni Kullanıcı (0L, "admin", "süper kullanıcı")); // Süper kullanıcıyı doğrula if (! Objects.equals (operatorId, userService.getSuperUser.getId)) { new ExampleException ("Yalnızca süper kullanıcılar şirketi silebilir"); } // Şirket bilgilerini sil companyDAO.delete (şirketKimliği, operatörKimliği); } }

    3. AopConfiguration.java:

    / ** AOP yapılandırma sınıfı * / @Filmdenkare @Görünüş @Yapılandırma public class AopConfiguration { / ** Surround yöntemi * / @Around ("yürütme (* org.changyi.springboot.service .. *. * (..))") public Object etrafında (ProceedingJoinPoint joinPoint) { Deneyin { log.info ("Hizmet yöntemini çağırmaya başla ..."); joinPoint.proceed; } catch (Throwable e) { log.error (e.getMessage, e); yeni ExampleException (e.getMessage, e); } } }

    CompanyService'in deleteCompany yöntemini çağırdığımızda, aslında bir PointerException da atıyoruz çünkü UserService sınıfının getSuperUser yöntemini çağırarak elde edilen süper kullanıcı. Bununla birlikte, CompanyService sınıfının deleteCompany yönteminde, süper kullanıcının her seferinde UserService sınıfının setSuperUser yöntemi aracılığıyla belirtilmesini zorlarız.KullanıcıService sınıfının getSuperUser yöntemi aracılığıyla elde edilen süper kullanıcının olmaması mantıklıdır. Aslında, bu soruna AOP proxy'si de neden olur.

    6.2. Problem analizi

    SpringCGLIB proxy sınıfını kullanırken Spring, UserService $$ EnhancerBySpringCGLIB $$ ???????? adlı bir proxy sınıfı oluşturacaktır. Bu proxy sınıfını yeniden derleyin ve aşağıdaki ana kodu alın:

    public class UserService $$ EnhancerBySpringCGLIB $$ a2c3b345 UserService'in SpringProxy, Advised, Factory { ...... public final void setSuperUser (Kullanıcı var1) { MethodInterceptor var10000 = this.CGLIB $ CALLBACK_0; eğer (var10000 ==) { CGLIB $ BIND_CALLBACKS (bu); var10000 = this.CGLIB $ CALLBACK_0; } eğer (var10000! =) { var10000.intercept (this, CGLIB $ setSuperUser $ 0 $ Method, new Object {var1}, CGLIB $ setSuperUser $ 0 $ Proxy); } Başka { super.setSuperUser (var1); } } ...... }

    Bu proxy sınıfının setSuperUser yöntemine proxy uygulayarak UserService sınıfını miras aldığı, ancak getSuperUser yöntemini proxy yapmadığı görülebilir. Bu nedenle setSuperUser yöntemini çağırdığımızda, orijinal nesne örneğinin superUser alan değerini belirleriz; getSuperUser yöntemini çağırdığımızda, proxy nesne örneğinin superUser alan değerini alırız. Bu iki yöntemin son değiştiricileri değiştirilirse, süper kullanıcı davranışı elde etme sorunu da vardır.

    6.3. Çukurlardan kaçınma yöntemi

    1. CGLIB vekil tayinini kesin bir şekilde takip edin, vekalet edilen sınıflara ve metotlara son değiştiriciler eklemeyin.

    CGLIB proxy belirtimine kesinlikle uyun. Farklı dinamik proxy işlem nesnesi örneklerinden (orijinal nesne örneği ve proxy nesne örneği) kaçınmak için proxy uygulanacak sınıflara ve yöntemlere son değiştiriciler eklemeyin, bu da veri tutarsızlığı veya boş işaretçi sorunlarına neden olur.

    2. CGLIB proxy sınıfının kapsamını azaltın ve proxy'ye ihtiyacınız yoksa proxy kullanmayın.

    CGLIB proxy sınıflarının kapsamını daraltın ve proxy kullanılabilen sınıfların proxy'ye tabi tutulması gerekmez. Bu, bellek ek yükünden tasarruf sağlar ve işlev çağrısı verimliliğini artırır.

    Genel alan proxy'si

    Fastjson 1.2.60'a yükseltmek zorunda kaldığında bir çukura adım attım. Hızlı bir şekilde geliştirmek için yazar ParseConfig'de şunları tanımladı:

    public class ParseConfig {

    genel son SymbolTable symbolTable = yeni SymbolTable (4096);

    ......

    }

    Projemizde, bu sınıf miras alındı ve dinamik olarak AOP tarafından vekalet edildi, bu nedenle bir kod satırı kanlı bir suça neden oldu.

    7.1. Sorun fenomeni

    Yine de önceki bölümdeki örneği kullanın, ancak get ve set yöntemlerini silin ve bir genel alan tanımlayın. Örnek kod aşağıdaki gibidir:

    1. UserService.java:

    / ** Kullanıcı hizmeti * / @Hizmet public class UserService { /** kök */ public son Kullanıcı superUser = yeni Kullanıcı (0L, "yönetici", "süper kullanıcı"); ...... }

    2. CompanyService.java:

    / ** Şirket hizmeti * / @Hizmet public class CompanyService { / ** DAO Şirketi * / @Autowired özel ŞirketDAO şirketiDAO; / ** Kullanıcı Hizmeti * / @Autowired özel UserService userService; / ** Şirketi sil * / public void deleteCompany (Long companyId, Long operatorId) { // Süper kullanıcıyı doğrula if (! Objects.equals (operatorId, userService.superUser.getId)) { new ExampleException ("Yalnızca süper kullanıcılar şirketi silebilir"); } // Şirket bilgilerini sil companyDAO.delete (şirketKimliği, operatörKimliği); } }

    3. AopConfiguration.java:

    Önceki bölümdeki AopConfiguration.java ile aynı.

    CompanyService'in deleteCompany yöntemini çağırdığımızda, aslında bir PointerException oluşturuyoruz. Hata ayıklama ve yazdırma işleminden sonra, UserService'in superUser değişkeninin olduğu bulundu. AopConfiguration silinirse, soruna AOP proxy'sinin neden olduğunu gösteren boş işaretçi istisnası olmayacaktır.

    7.2. Problem analizi

    SpringCGLIB proxy sınıfını kullanırken Spring, UserService $$ EnhancerBySpringCGLIB $$ ???????? adlı bir proxy sınıfı oluşturacaktır. Bu proxy sınıfı, UserService sınıfını miras alır ve UserService sınıfındaki tüm nihai olmayan genel yöntemleri kapsar. Ancak, bu proxy sınıfı süper temel sınıf yöntemlerini çağırmaz; bunun yerine, bir üye userService oluşturur ve orijinal UserService sınıfı nesne örneğini işaret eder. Artık bellekte iki nesne örneği vardır: biri orijinal UserService nesne örneğidir ve diğeri UserService'in proxy nesnesi örneğidir. Bu proxy sınıfı yalnızca sanal bir proxy'dir. UserService sınıfını miras alır ve UserService ile aynı alanlara sahiptir, ancak bunları hiçbir zaman başlatmaz ve kullanmaz. Bu nedenle, genel üye değişkeni bu proxy sınıfı nesne örneği aracılığıyla elde edildiğinde, varsayılan bir değer döndürülür.

    7.3. Çukurdan kaçınma yöntemi

    1. Alanın değişmez olduğu belirlendiğinde, genel statik sabit olarak tanımlanabilir.

    Alanın değişmez olduğu belirlendiğinde, genel statik sabit olarak tanımlanabilir ve sınıf adı + alan adı ile erişilebilir. Sınıf adı + alan adı, sınıf örneğinin dinamik proxy'si ile hiçbir ilgisi olmayan genel statik sabitlere erişir.

    / ** Kullanıcı hizmeti * / @Hizmet public class UserService { /** kök */ genel statik son Kullanıcı SUPER_USER = yeni Kullanıcı (0L, "yönetici", "süper kullanıcı"); ...... } / ** Kodu kullan * / eğer (! Objects.equals (operatorId, UserService.SUPER_USER.getId)) { new ExampleException ("Yalnızca süper kullanıcılar şirketi silebilir"); }

    2. Alan değişmez olarak belirlendiğinde özel üye değişkeni olarak tanımlanabilir.

    Alanın değişmez olduğu belirlendiğinde özel üye değişkeni olarak tanımlanabilir ve değişkenin değerini elde etmek için genel bir yöntem sağlanır. Sınıf örneği dinamik olarak proxy yapıldığında, proxy yöntemi proxy yöntemini çağırır ve böylece proxy sınıfının üye değişkeninin değerini döndürür.

    / ** Kullanıcı hizmeti * / @Hizmet public class UserService { /** kök */ özel Kullanıcı superUser = yeni Kullanıcı (0L, "admin", "süper kullanıcı"); / ** Süper kullanıcı edinin * / public User getSuperUser { this.superUser döndür; } ...... } / ** Kodu kullan * / if (! Objects.equals (operatorId, userService.getSuperUser.getId)) { new ExampleException ("Yalnızca süper kullanıcılar şirketi silebilir"); }

    3. JavaBean kodlama standartlarını izleyin ve genel üye değişkenlerini tanımlamayın.

    JavaBean kodlama standartlarını izleyin ve genel üye değişkenlerini tanımlamayın. JavaBean özellikleri aşağıdaki gibidir:

    (1) JavaBean sınıfı, genel bir sınıf olmalı ve erişim niteliği, aşağıdaki gibi, genel olarak ayarlanmış olmalıdır: public class Kullanıcı {......} (2) JavaBean sınıfı boş bir kurucuya sahip olmalıdır: sınıfta Parametreleri olmayan genel bir kurucu (3) Bir JavaBean sınıfının genel örnek değişkenleri olmamalıdır, sınıf değişkenlerinin tümü özeldir, örneğin: özel Tamsayı kimliği; (4) Özelliklere bir dizi alıcı / ayarlayıcı yöntemi aracılığıyla erişilmelidir.

    İnsanlar "analog" düşünceden yararlanır Çıkarım insan bilgeliğidir Yeni şeylerle karşılaştıklarında, insanlar genellikle benzer bilinen şeyleri referans olarak kullanırlar ve bu da yeni şeyleri kavrayışlarını hızlandırabilir.

    2018

    Xunfei Akıllı Ses Öncüsü: İnsan-bilgisayar etkileşimi insan iletişimi kadar doğal olduğunda, gerçek zeka çağı gelecek!

    Nginx'ten Pandownload'e, programcılar hapishane programlamasından nasıl kaçınabilir?

    Yalnızca lise matematiği algoritmaları keşfedebilir mi? Google'ın açık kaynaklı AutoML-Zero ne kadar güçlü?

    Spring Cloud bulut mimarisi altındaki mikro hizmet mimarisi: departman mikro hizmetleri (Dept)

    Spring Cloud'dan Service Mesh'e, mikro hizmet mimarisi yönetişim sistemi nasıl gelişir?

    İşe alıyor! MySQL röportajının ustalaşması gereken 8 bilgi noktası
    önceki
    Açık kaynak sadece arkadaş edinebilir mi?
    Sonraki
    Sunucusuzluk hızla artıyor, Ali, Microsoft ve AWS neden açık kaynaklı OAM'yi benimsiyor?
    Google'ın yaşamak için "kemeri germesi" gerekiyor
    5G Altyapısı: Yüz milyonlarca kullanıcının IPv6'yı sorunsuz bir şekilde desteklemesi nasıl sağlanır?
    Cidden | En iyi "toprak aromalı CP" hangisi? Lütfen Hu Yitian ve Zhang Yunlong'u bulun
    Bitki Dikimi Dudak Makyajı, lütfen Kendall ve Hyuna'yı masum olmaları için iade edin
    Erkek yıldız sokak çekimleri | Zhu Xingjienin dondurma takımı, yaz için sabırsızlanmamı sağlıyor
    Rainbow Fart | Liu Shishinin canlı yayınında ağladı mı? Bakalım beyaz giymeye ne kadar uygun
    Gökkuşağı osuruğu | Yang Mi'nin sadece aşık olmayı bildiğini kim söyledi? Bak, başka bir oyuncuyu popüler yaptı.
    Lin Yun'un kazağı, Yang Mi'nin çubuk bacakları ve orada hiçbir şekilde yaşayamayan Avrupalı ve Amerikalı yıldızlar "poster sokak çekimleri"
    Sana güzelliği öğret Bir konuşma, sonunda Liu Yifeinin özçekimini geliştirdiğini gördüm
    Patlamış mısır Kore'nin en güzel ünlüleri seçildi, Deppin oğlu biraz yakışıklı
    Cidden | "Grubumuz" u izledim ve yılın "kasetçilerini" hatırladım
    To Top