Buggy Java Kodu: Java Geliştiricilerinin Yaptığı En Yaygın 10 Hata

Yayınlanan: 2022-03-11

Java, başlangıçta etkileşimli televizyon için geliştirilmiş bir programlama dilidir, ancak zamanla yazılımın kullanılabileceği her yerde yaygınlaşmıştır. C veya C++, çöp toplama ve mimari olarak agnostik bir sanal makine gibi diğer dillerin karmaşıklığını ortadan kaldıran nesne yönelimli programlama kavramıyla tasarlanan Java, yeni bir programlama yolu yarattı. Ayrıca, yumuşak bir öğrenme eğrisine sahiptir ve neredeyse her zaman doğru olan “Bir kez yaz, her yerde koş” sloganına başarıyla bağlı kalır; ancak Java sorunları hala mevcut. En yaygın hatalar olduğunu düşündüğüm on Java sorununu ele alacağım.

Yaygın Hata 1: Mevcut Kitaplıkları İhmal Etmek

Java Geliştiricilerinin Java ile yazılmış sayısız kitaplığı görmezden gelmesi kesinlikle bir hatadır. Tekerleği yeniden icat etmeden önce, mevcut kütüphaneleri aramaya çalışın - birçoğu var oldukları yıllar boyunca cilalanmıştır ve kullanımı ücretsizdir. Bunlar, logback ve Log4j gibi günlük kitaplıkları veya Netty veya Akka gibi ağla ilgili kitaplıklar olabilir. Joda-Time gibi bazı kütüphaneler fiili bir standart haline geldi.

Aşağıdakiler önceki projelerimden birinden kişisel bir deneyimdir. Kodun HTML'den kaçmaktan sorumlu kısmı sıfırdan yazılmıştır. Yıllardır iyi çalışıyordu, ancak sonunda sonsuz bir döngüye dönmesine neden olan bir kullanıcı girdisiyle karşılaştı. Hizmetin yanıt vermediğini bulan kullanıcı, aynı girdiyle yeniden denemeyi denedi. Sonunda, bu uygulama için ayrılan sunucudaki tüm CPU'lar bu sonsuz döngü tarafından işgal edildi. Bu saf HTML kaçış aracının yazarı, Google Guava'dan HtmlEscapers gibi, HTML kaçışı için mevcut iyi bilinen kitaplıklardan birini kullanmaya karar vermiş olsaydı, bu muhtemelen olmayacaktı. En azından, arkasında bir topluluk bulunan çoğu popüler kitaplık için doğru olan hata, bu kitaplık için topluluk tarafından daha önce bulunur ve düzeltilirdi.

Yaygın Hata #2: Anahtar-Durum Bloğunda 'break' Anahtar Kelimesini Eksik

Bu Java sorunları çok utanç verici olabilir ve bazen üretime girene kadar keşfedilmeden kalır. Switch deyimlerindeki geçiş davranışı genellikle yararlıdır; ancak, bu tür bir davranış istenmediğinde bir "break" anahtar sözcüğünü kaçırmak feci sonuçlara yol açabilir. Aşağıdaki kod örneğinde “case 0” içine “break” koymayı unuttuysanız, program “Zero” ve ardından “One” yazacaktır, çünkü buradaki kontrol akışı, “switch” ifadesinin tamamını geçene kadar geçecektir. bir “kırılma”ya ulaşır. Örneğin:

 public static void switchCasePrimer() { int caseIndex = 0; switch (caseIndex) { case 0: System.out.println("Zero"); case 1: System.out.println("One"); break; case 2: System.out.println("Two"); break; default: System.out.println("Default"); } }

Çoğu durumda, daha temiz çözüm, polimorfizm kullanmak ve belirli davranışlara sahip kodu ayrı sınıflara taşımak olacaktır. Bunun gibi Java hataları, örneğin FindBugs ve PMD gibi statik kod çözümleyicileri kullanılarak tespit edilebilir.

Yaygın Hata #3: Kaynakları Serbest Bırakmayı Unutmak

Bir program bir dosya veya ağ bağlantısını her açtığında, Java'ya yeni başlayanlar için, onu kullanmayı bitirdikten sonra kaynağı serbest bırakmaları önemlidir. Bu tür kaynaklarla ilgili işlemler sırasında herhangi bir istisna atılacaksa da benzer önlemler alınmalıdır. FileInputStream'in bir çöp toplama olayında close() yöntemini çağıran bir sonlandırıcıya sahip olduğu iddia edilebilir; ancak, bir çöp toplama döngüsünün ne zaman başlayacağından emin olamadığımız için, girdi akışı süresiz olarak bilgisayar kaynaklarını tüketebilir. Aslında, Java 7'de özellikle bu durum için tanıtılan, kaynaklarla deneme adı verilen gerçekten yararlı ve düzgün bir ifade vardır:

 private static void printFileJava7() throws IOException { try(FileInputStream input = new FileInputStream("file.txt")) { int data = input.read(); while(data != -1){ System.out.print((char) data); data = input.read(); } } }

Bu ifade, AutoClosable arabirimini uygulayan herhangi bir nesneyle kullanılabilir. Her kaynağın ifadenin sonuna kadar kapatılmasını sağlar.

İlgili: 8 Temel Java Mülakat Sorusu

Yaygın Hata #4: Bellek Sızıntıları

Java, otomatik bellek yönetimini kullanır ve belleği manuel olarak ayırmayı ve boşaltmayı unutmak bir rahatlama olsa da, yeni başlayan bir Java geliştiricisinin uygulamada belleğin nasıl kullanıldığının farkında olmaması gerektiği anlamına gelmez. Bellek ayırma ile ilgili sorunlar hala mümkündür. Bir program, artık ihtiyaç duyulmayan nesnelere referanslar oluşturduğu sürece, serbest bırakılmayacaktır. Bir bakıma bu bellek sızıntısı olarak da adlandırabiliriz. Java'da bellek sızıntıları çeşitli şekillerde olabilir, ancak en yaygın neden sonsuz nesne referanslarıdır, çünkü çöp toplayıcı hala referanslar varken nesneleri yığından kaldıramaz. Bir nesne koleksiyonunu içeren statik bir alanla sınıfı tanımlayarak ve koleksiyona artık ihtiyaç duyulmadığında bu statik alanı null olarak ayarlamayı unutarak böyle bir referans oluşturulabilir. Statik alanlar GC kökleri olarak kabul edilir ve asla toplanmaz.

Bu tür bellek sızıntılarının ardındaki bir başka olası neden de, çöp toplayıcının çapraz bağımlılık referanslarına sahip bu nesnelerin gerekli olup olmadığına karar verememesi için birbirine başvuran ve döngüsel bağımlılıklara neden olan bir grup nesnedir. Başka bir sorun, JNI kullanıldığında yığın olmayan bellekteki sızıntılardır.

İlkel sızıntı örneği aşağıdaki gibi görünebilir:

 final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); final Deque<BigDecimal> numbers = new LinkedBlockingDeque<>(); final BigDecimal divisor = new BigDecimal(51); scheduledExecutorService.scheduleAtFixedRate(() -> { BigDecimal number = numbers.peekLast(); if (number != null && number.remainder(divisor).byteValue() == 0) { System.out.println("Number: " + number); System.out.println("Deque size: " + numbers.size()); } }, 10, 10, TimeUnit.MILLISECONDS); scheduledExecutorService.scheduleAtFixedRate(() -> { numbers.add(new BigDecimal(System.currentTimeMillis())); }, 10, 10, TimeUnit.MILLISECONDS); try { scheduledExecutorService.awaitTermination(1, TimeUnit.DAYS); } catch (InterruptedException e) { e.printStackTrace(); }

Bu örnek, iki zamanlanmış görev oluşturur. İlk görev, “sayılar” adı verilen bir deque'den son sayıyı alır ve sayının 51'e bölünmesi durumunda sayıyı ve deque boyutunu yazdırır. İkinci görev, sayıları deque'ye koyar. Her iki görev de sabit bir oranda zamanlanır ve her 10 ms'de bir çalışır. Kod yürütülürse, deque boyutunun kalıcı olarak arttığını göreceksiniz. Bu, sonunda deque'nin tüm kullanılabilir yığın belleğini tüketen nesnelerle doldurulmasına neden olur. Bu programın semantiğini korurken bunu önlemek için deque'den sayı almak için farklı bir yöntem kullanabiliriz: “pollLast”. "peekLast" yönteminin aksine, "pollLast" öğeyi döndürür ve deque'den kaldırırken, "peekLast" yalnızca son öğeyi döndürür.

Java'daki bellek sızıntıları hakkında daha fazla bilgi edinmek için lütfen bu sorunu aydınlatan makalemize bakın.

Yaygın Hata #5: Aşırı Çöp Tahsisi

Program çok sayıda kısa ömürlü nesne oluşturduğunda aşırı çöp tahsisi gerçekleşebilir. Çöp toplayıcı sürekli çalışarak gereksiz nesneleri bellekten kaldırarak uygulamaların performansını olumsuz yönde etkiler. Basit bir örnek:

 String oneMillionHello = ""; for (int i = 0; i < 1000000; i++) { oneMillionHello = oneMillionHello + "Hello!"; } System.out.println(oneMillionHello.substring(0, 6));

Java geliştirmede dizeler değişmezdir. Böylece, her yinelemede yeni bir dize oluşturulur. Bunu ele almak için değişken bir StringBuilder kullanmalıyız:

 StringBuilder oneMillionHelloSB = new StringBuilder(); for (int i = 0; i < 1000000; i++) { oneMillionHelloSB.append("Hello!"); } System.out.println(oneMillionHelloSB.toString().substring(0, 6));

İlk sürümün yürütülmesi biraz zaman alırken, StringBuilder kullanan sürüm önemli ölçüde daha kısa sürede sonuç üretir.

Yaygın Hata #6: Gereksiz Boş Referansları Kullanmak

Aşırı null kullanımından kaçınmak iyi bir uygulamadır. Örneğin, NullPointerException'ı önlemeye yardımcı olabileceğinden, boş diziler veya yöntemlerden boş değerler yerine koleksiyonlar döndürmek tercih edilir.

Aşağıda gösterildiği gibi, başka bir yöntemden elde edilen bir koleksiyonu çaprazlayan aşağıdaki yöntemi göz önünde bulundurun:

 List<String> accountIds = person.getAccountIds(); for (String accountId : accountIds) { processAccount(accountId); }

Bir kişinin hesabı olmadığında getAccountIds() null değerini döndürürse, NullPointerException oluşturulacaktır. Bunu düzeltmek için bir boş kontrol gerekli olacaktır. Ancak, boş bir liste yerine boş bir liste döndürürse, NullPointerException artık bir sorun değildir. Ayrıca, hesap kimlikleri değişkenini boş kontrol etmemize gerek olmadığı için kod daha temizdir.

Boş değerlerden kaçınmak istendiğinde diğer durumlarla başa çıkmak için farklı stratejiler kullanılabilir. Bu stratejilerden biri, boş bir nesne veya bir değerin sarmalanması olabilen İsteğe bağlı türü kullanmaktır:

 Optional<String> optionalString = Optional.ofNullable(nullableString); if(optionalString.isPresent()) { System.out.println(optionalString.get()); }

Aslında, Java 8 daha özlü bir çözüm sunar:

 Optional<String> optionalString = Optional.ofNullable(nullableString); optionalString.ifPresent(System.out::println);

İsteğe bağlı tür, sürüm 8'den beri Java'nın bir parçası olmuştur, ancak işlevsel programlama dünyasında uzun süredir iyi bilinmektedir. Bundan önce, Java'nın önceki sürümleri için Google Guava'da mevcuttu.

Yaygın Hata #7: İstisnaları Yoksaymak

İstisnaları ele alınmadan bırakmak genellikle cezbedicidir. Ancak, hem yeni başlayanlar hem de deneyimli Java geliştiricileri için en iyi uygulama, bunları ele almaktır. İstisnalar bilerek atılır, bu nedenle çoğu durumda bu istisnalara neden olan sorunları ele almamız gerekir. Bu olayları göz ardı etmeyin. Gerekirse, yeniden gönderebilir, kullanıcıya bir hata iletişim kutusu gösterebilir veya günlüğe bir mesaj ekleyebilirsiniz. En azından, diğer geliştiricilerin nedenini bilmesini sağlamak için istisnanın neden işlenmeden bırakıldığı açıklanmalıdır.

 selfie = person.shootASelfie(); try { selfie.show(); } catch (NullPointerException e) { // Maybe, invisible man. Who cares, anyway? }

Bir istisnanın önemsizliğini vurgulamanın daha net bir yolu, bu mesajı istisnaların değişken adına kodlamaktır, örneğin:

 try { selfie.delete(); } catch (NullPointerException unimportant) { }

Yaygın Hata #8: Eşzamanlı Değişiklik İstisnası

Bu özel durum, yineleyici nesnesi tarafından sağlananlar dışındaki yöntemler kullanılarak üzerinde yinelenirken bir koleksiyon değiştirildiğinde ortaya çıkar. Örneğin, bir şapka listemiz var ve kulak kepçesi olan tüm şapkaları kaldırmak istiyoruz:

 List<IHat> hats = new ArrayList<>(); hats.add(new Ushanka()); // that one has ear flaps hats.add(new Fedora()); hats.add(new Sombrero()); for (IHat hat : hats) { if (hat.hasEarFlaps()) { hats.remove(hat); } }

Bu kodu çalıştırırsak, kod koleksiyonu yinelerken değiştirdiği için “ConcurrentModificationException” ortaya çıkar. Aynı istisna, aynı listeyle çalışan birden çok iş parçacığından biri koleksiyonu değiştirmeye çalışırken diğerleri üzerinde yinelenirse ortaya çıkabilir. Koleksiyonların birden çok iş parçacığında eşzamanlı olarak değiştirilmesi doğal bir şeydir, ancak eş zamanlı programlama araç kutusundaki senkronizasyon kilitleri, eşzamanlı değişiklik için benimsenen özel koleksiyonlar vb. gibi olağan araçlarla ele alınmalıdır. Bu Java sorununun nasıl çözülebileceği konusunda küçük farklılıklar vardır. tek iş parçacıklı durumlarda ve çok iş parçacıklı durumlarda. Aşağıda, bunun tek bir iş parçacığı senaryosunda ele alınabileceği bazı yolların kısa bir tartışması bulunmaktadır:

Nesneleri toplayın ve başka bir döngüde kaldırın

Kulak kapaklı şapkaları daha sonra başka bir döngüden çıkarmak için bir listede toplamak bariz bir çözümdür, ancak çıkarılacak şapkaları saklamak için ek bir koleksiyon gerektirir:

 List<IHat> hatsToRemove = new LinkedList<>(); for (IHat hat : hats) { if (hat.hasEarFlaps()) { hatsToRemove.add(hat); } } for (IHat hat : hatsToRemove) { hats.remove(hat); }

Iterator.remove yöntemini kullanın

Bu yaklaşım daha özlüdür ve oluşturulması için ek bir koleksiyona ihtiyaç duymaz:

 Iterator<IHat> hatIterator = hats.iterator(); while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.remove(); } }

ListIterator'ın yöntemlerini kullanın

Değiştirilen koleksiyon List arabirimini uyguladığında liste yineleyiciyi kullanmak uygundur. ListIterator arabirimini uygulayan yineleyiciler, yalnızca kaldırma işlemlerini değil, aynı zamanda ekleme ve ayarlama işlemlerini de destekler. ListIterator, Yineleyici arabirimini uygular, böylece örnek, Yineleyici kaldırma yöntemiyle neredeyse aynı görünür. Tek fark, hat yineleyicinin türü ve bu yineleyiciyi “listIterator()” yöntemiyle elde etme şeklimizdir. Aşağıdaki snippet, "ListIterator.remove" ve "ListIterator.add" yöntemlerini kullanarak kulak kapaklı her bir şapkayı fötr şapkalı fötr şapkalarla nasıl değiştireceğinizi gösterir:

 IHat sombrero = new Sombrero(); ListIterator<IHat> hatIterator = hats.listIterator(); while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.remove(); hatIterator.add(sombrero); } }

ListIterator ile, kaldırma ve ekleme yöntemi çağrıları, aşağıdakileri ayarlamak için tek bir çağrı ile değiştirilebilir:

 IHat sombrero = new Sombrero(); ListIterator<IHat> hatIterator = hats.listIterator(); while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.set(sombrero); // set instead of remove and add } }

Java 8'de tanıtılan akış yöntemlerini kullanın Java 8 ile programcılar bir koleksiyonu akışa dönüştürme ve akışı bazı kriterlere göre filtreleme yeteneğine sahiptir. Akış API'sinin şapkaları filtrelememize ve “ConcurrentModificationException”dan kaçınmamıza nasıl yardımcı olabileceğine dair bir örnek.

 hats = hats.stream().filter((hat -> !hat.hasEarFlaps())) .collect(Collectors.toCollection(ArrayList::new));

"Collectors.toCollection" yöntemi, filtrelenmiş şapkalarla yeni bir ArrayList oluşturacaktır. Filtreleme koşulu çok sayıda öğe tarafından karşılanacak ve büyük bir ArrayList ile sonuçlanacaksa, bu bir sorun olabilir; bu nedenle dikkatli kullanılmalıdır. Java 8'de sunulan List.removeIf yöntemini kullanın

 hats.removeIf(IHat::hasEarFlaps);

Bu kadar. Kaputun altında, davranışı gerçekleştirmek için “Iterator.remove” kullanır.

Özel koleksiyonları kullanın

En başta “ArrayList” yerine “CopyOnWriteArrayList” kullanmaya karar vermiş olsaydık, “CopyOnWriteArrayList” değişmeyen modifikasyon yöntemleri (ayarla, ekle ve kaldır gibi) sağladığı için hiç sorun olmazdı. koleksiyonun destek dizisi, bunun yerine yeni ve değiştirilmiş bir sürümünü oluşturun. Bu, "ConcurrentModificationException" riski olmadan aynı anda koleksiyonun orijinal versiyonu üzerinde yinelemeye ve üzerinde değişikliklere izin verir. Bu koleksiyonun dezavantajı açıktır - her değişiklikle yeni bir koleksiyon oluşturulması.

Farklı durumlar için ayarlanmış başka koleksiyonlar da vardır, örneğin “CopyOnWriteSet” ve “ConcurrentHashMap”.

Eşzamanlı koleksiyon değişiklikleriyle ilgili bir başka olası hata, bir koleksiyondan bir akış oluşturmak ve akış yinelemesi sırasında destek koleksiyonunu değiştirmektir. Akışlar için genel kural, akış sorgulaması sırasında temel alınan koleksiyonun değiştirilmesinden kaçınmaktır. Aşağıdaki örnek, bir akışı işlemenin yanlış bir yolunu gösterecektir:

 List<IHat> filteredHats = hats.stream().peek(hat -> { if (hat.hasEarFlaps()) { hats.remove(hat); } }).collect(Collectors.toCollection(ArrayList::new));

Peek yöntemi, tüm öğeleri toplar ve sağlanan eylemi bunların her biri üzerinde gerçekleştirir. Burada eylem, hatalı olan temeldeki listeden öğeleri kaldırmaya çalışıyor. Bunu önlemek için yukarıda açıklanan yöntemlerden bazılarını deneyin.

Yaygın Hata #9: Sözleşmeleri Bozmak

Bazen standart kitaplık veya üçüncü taraf satıcı tarafından sağlanan kod, işlerin yürümesi için uyulması gereken kurallara dayanır. Örneğin, takip edildiğinde Java toplama çerçevesinden bir dizi koleksiyon için ve hashCode ve equals yöntemlerini kullanan diğer sınıflar için çalışmayı garanti eden hashCode ve equals sözleşmesi olabilir. Sözleşmelere uymamak, her zaman istisnalara yol açan veya kod derlemesini bozan türden bir hata değildir; daha zor, çünkü bazen herhangi bir tehlike belirtisi olmadan uygulama davranışını değiştirir. Hatalı kod, üretim sürümüne geçebilir ve bir sürü istenmeyen etkiye neden olabilir. Bu, kötü UI davranışı, yanlış veri raporları, zayıf uygulama performansı, veri kaybı ve daha fazlasını içerebilir. Neyse ki, bu feci hatalar çok sık olmaz. Daha önce hashCode and equals sözleşmesinden bahsetmiştim. HashMap ve HashSet gibi nesneleri karmaya ve karşılaştırmaya dayanan koleksiyonlarda kullanılır. Basitçe söylemek gerekirse, sözleşme iki kural içerir:

  • İki nesne eşitse, karma kodları da eşit olmalıdır.
  • İki nesne aynı karma koda sahipse, eşit olabilir veya olmayabilir.

Sözleşmenin ilk kuralının çiğnenmesi, bir hashmap'ten nesneleri almaya çalışırken sorunlara yol açar. İkinci kural, aynı karma koda sahip nesnelerin mutlaka eşit olmadığını belirtir. Birinci kuralı çiğnemenin etkilerini inceleyelim:

 public static class Boat { private String name; Boat(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Boat boat = (Boat) o; return !(name != null ? !name.equals(boat.name) : boat.name != null); } @Override public int hashCode() { return (int) (Math.random() * 5000); } }

Gördüğünüz gibi, sınıfın Boat overriden equals ve hashCode yöntemlerine sahip. Ancak, hashCode her çağrıldığında aynı nesne için rastgele değerler döndürdüğü için sözleşmeyi bozmuştur. Bu tür bir tekneyi daha önce eklemiş olmamıza rağmen, aşağıdaki kod büyük olasılıkla hashset'te "Enterprise" adlı bir tekne bulmayacaktır:

 public static void main(String[] args) { Set<Boat> boats = new HashSet<>(); boats.add(new Boat("Enterprise")); System.out.printf("We have a boat named 'Enterprise' : %b\n", boats.contains(new Boat("Enterprise"))); }

Başka bir sözleşme örneği, sonuçlandırma yöntemini içerir. İşlevini açıklayan resmi Java belgelerinden bir alıntı:

Sonuçlandırmanın genel sözleşmesi, JavaTM sanal makinesinin bu nesneye artık herhangi bir iş parçacığı tarafından (henüz ölmemiş olan) erişilebileceğinin bir sonucu dışında, artık herhangi bir yol bulunmadığını belirlemesi durumunda çağrılmasıdır. kesinleştirilmeye hazır başka bir nesnenin veya sınıfın nihai hale getirilmesiyle gerçekleştirilen eylem. Sonlandırma yöntemi, bu nesneyi diğer iş parçacıkları için tekrar kullanılabilir hale getirmek de dahil olmak üzere herhangi bir işlem yapabilir; Bununla birlikte, sonlandırmanın genel amacı, nesne geri dönülmez bir şekilde atılmadan önce temizleme eylemleri gerçekleştirmektir. Örneğin, bir giriş/çıkış bağlantısını temsil eden bir nesne için sonlandırma yöntemi, nesne kalıcı olarak atılmadan önce bağlantıyı kesmek için açık G/Ç işlemleri gerçekleştirebilir.

Dosya işleyiciler gibi kaynakları serbest bırakmak için sonlandırma yöntemini kullanmaya karar verebilirsiniz, ancak bu kötü bir fikir olur. Bunun nedeni, çöp toplama sırasında çağrıldığından ve GC'nin süresi belirsiz olduğundan, sonlandırmanın ne zaman çağrılacağına dair bir zaman garantisinin olmamasıdır.

Yaygın Hata #10: Parametreli Bir Tip Yerine Ham Tip Kullanmak

Java belirtimlerine göre ham türler, parametrelendirilmemiş türlerdir ya da R sınıfının üst sınıfından veya üst arabiriminden miras alınmayan statik olmayan üyelerdir. Java'da genel türler tanıtılana kadar ham türlere alternatif yoktu. . 1.5 sürümünden beri jenerik programlamayı destekler ve jenerikler şüphesiz önemli bir gelişmeydi. Bununla birlikte, geriye dönük uyumluluk nedenlerinden dolayı, tip sistemini potansiyel olarak bozabilecek bir tuzak kalmıştır. Aşağıdaki örneğe bakalım:

 List listOfNumbers = new ArrayList(); listOfNumbers.add(10); listOfNumbers.add("Twenty"); listOfNumbers.forEach(n -> System.out.println((int) n * 2));

Burada ham ArrayList olarak tanımlanan bir sayı listemiz var. Type parametresi ile türü belirtilmediği için içine herhangi bir nesne ekleyebiliriz. Ancak son satırda elemanları int'ye çeviriyoruz, ikiye katlıyoruz ve ikiye katlanan sayıyı standart çıktıya yazdırıyoruz. Bu kod hatasız derlenecek, ancak bir kez çalıştırıldığında bir tamsayıya bir dize atmaya çalıştığımız için bir çalışma zamanı istisnası oluşturacaktır. Açıkçası, tip sistemi, gerekli bilgileri ondan gizlersek, güvenli kod yazmamıza yardımcı olamaz. Sorunu çözmek için koleksiyonda depolayacağımız nesnelerin türünü belirtmemiz gerekiyor:

 List<Integer> listOfNumbers = new ArrayList<>(); listOfNumbers.add(10); listOfNumbers.add("Twenty"); listOfNumbers.forEach(n -> System.out.println((int) n * 2));

Orijinalden tek farkı koleksiyonu tanımlayan satırdır:

 List<Integer> listOfNumbers = new ArrayList<>();

Sabit kod derlenmiyor çünkü bir koleksiyona yalnızca tamsayıları depolaması beklenen bir dize eklemeye çalışıyoruz. Derleyici bir hata gösterecek ve "Yirmi" dizesini listeye eklemeye çalıştığımız satırı gösterecek. Genel türleri parametrelendirmek her zaman iyi bir fikirdir. Bu şekilde, derleyici tüm olası tür kontrollerini yapabilir ve tür sistemi tutarsızlıklarından kaynaklanan çalışma zamanı istisnaları olasılığı en aza indirilir.

Çözüm

Java bir platform olarak, hem karmaşık JVM'ye hem de dilin kendisine güvenerek yazılım geliştirmedeki birçok şeyi basitleştirir. Ancak, manuel bellek yönetimini veya uygun OOP araçlarını kaldırmak gibi özellikleri, normal bir Java geliştiricisinin karşılaştığı tüm sorunları ve sorunları ortadan kaldırmaz. Her zaman olduğu gibi, bilgi, uygulama ve bunun gibi Java eğitimleri uygulama hatalarını önlemenin ve gidermenin en iyi yoludur - bu nedenle kitaplıklarınızı bilin, java'yı okuyun, JVM belgelerini okuyun ve program yazın. Statik kod çözümleyicilerini de unutmayın, çünkü bunlar gerçek hatalara işaret edebilir ve potansiyel hataları vurgulayabilir.

İlgili: Gelişmiş Java Sınıfı Eğitimi: Sınıf Yeniden Yükleme Kılavuzu