Koleksi Berisi Perangkap di Java

Salah satu jebakan kecil yang bisa dialami oleh pengembang Java terjadi ketika Collection.contains (Object) tidak digunakan dengan pemahaman yang sesuai. Saya menunjukkan jebakan potensial ini di posting ini.

Collection.contains (Object) menerima Object, yang berarti pada dasarnya menerima instance dari kelas Java apa pun. Di sinilah letak jebakan potensial. Jika seseorang melewati sebuah instance dari kelas selain jenis kelas yang dapat disimpan dalam koleksi tertentu, bisa jadi metode ini hanya akan kembali false. Ini tidak benar-benar salah karena jenis yang berbeda dari koleksi tempat penyimpanan jelas bukan bagian dari koleksi itu. Namun, ini bisa menjadi jebakan jika pengembang mengandalkan nilai yang dikembalikan dari containspanggilan untuk mengimplementasikan logika lain.

Ini ditunjukkan dalam daftar kode berikutnya.

 public void demonstrateIllConceivedContainsBasedCode() { final Set favoriteChildrensBooks = new HashSet(); favoriteChildrensBooks.add("Mrs. Frisby and the Rats of NIMH"); favoriteChildrensBooks.add("The Penguin that Hated the Cold"); favoriteChildrensBooks.add("The Bears' Vacation"); favoriteChildrensBooks.add("Green Eggs and Ham"); favoriteChildrensBooks.add("A Fish Out of Water"); favoriteChildrensBooks.add("The Lorax"); final Date date = new Date(); if (favoriteChildrensBooks.contains(date)) { out.println("That is a great book!"); } } 

Dalam daftar kode di atas, pernyataan yang dicetak "Itu adalah buku yang luar biasa!" tidak akan pernah dieksekusi karena Tanggal tidak akan pernah terkandung dalam Set itu.

Tidak ada kondisi peringatan untuk ini, bahkan dengan -Xlintopsi set javac . Namun, NetBeans 6.8 memang memberikan peringatan untuk ini seperti yang ditunjukkan di snapshot layar berikutnya.

Seperti yang ditunjukkan oleh cuplikan layar, NetBeans 6.8 menyediakan pesan peringatan yang bagus dan cukup jelas, "Panggilan mencurigakan ke java.util.Collection.contains: Objek yang diberikan tidak dapat berisi instance Tanggal (String yang diharapkan)." Ini jelas "mencurigakan" dan hampir tidak pernah benar-benar diinginkan oleh pengembang.

Tidaklah mengherankan bahwa containsmetode tersebut mengembalikan falsedaripada beberapa jenis pesan kesalahan atau pengecualian karena memang benar bahwa Setdalam contoh ini tidak berisi Datepertanyaan yang diajukan. Salah satu taktik yang dapat digunakan untuk memiliki setidaknya pemeriksaan waktu proses untuk kelas yang tepat dalam panggilan ke containsadalah dengan menggunakan jenis koleksi yang mengimplementasikan pelemparan opsional ClassCastException jika diperlukan.

Dokumentasi Javadoc untuk containsmetode masing-masing antarmuka Collection, Set, List, dan Map menyatakan bahwa mereka melempar ClassCastException"jika jenis elemen yang ditentukan tidak sesuai dengan koleksi ini (opsional)" (Koleksi), "jika jenis yang ditentukan elemen tidak kompatibel dengan himpunan ini (opsional) "(Set)," jika jenis elemen yang ditentukan tidak kompatibel dengan daftar ini (opsional) "(Daftar), dan" jika kuncinya adalah jenis yang tidak sesuai untuk peta ini (opsional ) "(Map.containsKey). Hal yang paling penting untuk diperhatikan adalah bahwa masing-masing menyatakan lemparan ClassCastExceptionsebagai opsional .

Dalam kode di atas, saya menggunakan HashSet, yang tidak membuang ClassCastExceptionketika jenis objek yang tidak kompatibel diteruskan ke containsmetodenya. Memang, dokumentasi Javadoc untuk HashSet.contains (Object) tidak menyebutkan tentang melempar file ClassCastException. Demikian pula, LinkedHashSet memperluas HashSetdan mewarisi sama containsseperti HastSet. TreeSet, di sisi lain, memiliki komentar Javadoc yang menyatakan bahwa TreeSet.contains (Object) tidak menampilkan ClassCastException"jika objek yang ditentukan tidak dapat dibandingkan dengan elemen yang saat ini ada di set." Jadi, TreeSettidak memunculkan pengecualian saat objek tak tertandingi disediakan untuk containsmetodenya.

Sekarang saya akan menunjukkan perbedaan dalam perilaku ini dengan beberapa contoh kode.

Kelas pertama yang digunakan di sini adalah kelas Person.

Person.java

/* * //marxsoftware.blogspot.com/ */ package dustin.examples; import java.io.Serializable; public final class Person implements Comparable, Serializable { private final String lastName; private final String firstName; public Person(final String newLastName, final String newFirstName) { this.lastName = newLastName; this.firstName = newFirstName; } public String getLastName() { return this.lastName; } public String getFirstName() { return this.firstName; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Person other = (Person) obj; if (this.lastName == null ? other.lastName != null : !this.lastName.equals(other.lastName)) { return false; } if (this.firstName == null ? other.firstName != null : !this.firstName.equals(other.firstName)) { return false; } return true; } @Override public int hashCode() { int hash = 5; hash = 59 * hash + (this.lastName != null ? this.lastName.hashCode() : 0); hash = 59 * hash + (this.firstName != null ? this.firstName.hashCode() : 0); return hash; } public int compareTo(Object anotherPerson) throws ClassCastException { if (!(anotherPerson instanceof Person)) { throw new ClassCastException("A Person object expected."); } final Person theOtherPerson = (Person) anotherPerson; final int lastNameComparisonResult = this.lastName.compareTo(theOtherPerson.lastName); return lastNameComparisonResult != 0 ? lastNameComparisonResult : this.firstName.compareTo(theOtherPerson.firstName); } @Override public String toString() { return this.firstName + " " + this.lastName; } } 

Kelas lain yang digunakan dalam contoh saya adalah kelas InanimateObject.

InanimateObject.java Tidak Dapat Dibandingkan

/* * //marxsoftware.blogspot.com/ */ package dustin.examples; public class InanimateObject { private final String name; private final String secondaryName; private final int yearOfOrigin; public InanimateObject( final String newName, final String newSecondaryName, final int newYear) { this.name = newName; this.secondaryName = newSecondaryName; this.yearOfOrigin = newYear; } public String getName() { return this.name; } public String getSecondaryName() { return this.secondaryName; } public int getYearOfOrigin() { return this.yearOfOrigin; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final InanimateObject other = (InanimateObject) obj; if (this.name == null ? other.name != null : !this.name.equals(other.name)) { return false; } if (this.yearOfOrigin != other.yearOfOrigin) { return false; } return true; } @Override public int hashCode() { int hash = 3; hash = 23 * hash + (this.name != null ? this.name.hashCode() : 0); hash = 23 * hash + this.yearOfOrigin; return hash; } @Override public String toString() { return this.name + " (" + this.secondaryName + "), created in " + this.yearOfOrigin; } } 

Kelas utama yang dapat dieksekusi untuk menguji hal-hal ini adalah SetContainsExample.

SetContainsExample.java

/* * //marxsoftware.blogspot.com/ */ package dustin.examples; import static java.lang.System.out; import java.util.Arrays; import java.util.EnumSet; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; public class SetContainsExample { final Person davidLightman = new Person("Lightman", "David"); final Person willFarmer = new Person("Farmer", "Will"); final Person daveBowman = new Person("Bowman", "Dave"); final Person jerryShaw = new Person("Shaw", "Jerry"); final Person delSpooner = new Person("Spooner", "Del"); final InanimateObject wopr = new InanimateObject("War Operation Plan Response", "WOPR", 1983); final InanimateObject ripley = new InanimateObject("R.I.P.L.E.Y", "R.I.P.L.E.Y", 2008); final InanimateObject hal = new InanimateObject("Heuristically programmed ALgorithmic Computer", "HAL9000", 1997); final InanimateObject ariia = new InanimateObject("Autonomous Reconnaissance Intelligence Integration Analyst", "ARIIA", 2009); final InanimateObject viki = new InanimateObject("Virtual Interactive Kinetic Intelligence", "VIKI", 2035); public Set createPeople(final Class setType) { Set people = new HashSet(); if (validateSetImplementation(setType)) { if (HashSet.class.equals(setType)) { people = new HashSet(); } else if (LinkedHashSet.class.equals(setType)) { people = new LinkedHashSet(); } else if (TreeSet.class.equals(setType)) { people = new TreeSet(); } else if (EnumSet.class.equals(setType)) { out.println("ERROR: EnumSet is inappropriate type of Set here."); } else { out.println("WARNING: " + setType.getName() + " is an unexpected Set implementation."); } } else { out.println("WARNING: " + setType.getName() + " is not a Set implementation."); people = new HashSet(); } people.add(davidLightman); people.add(willFarmer); people.add(daveBowman); people.add(jerryShaw); people.add(delSpooner); return people; } private boolean validateSetImplementation(final Class candidateSetImpl) { if (candidateSetImpl.isInterface()) { throw new IllegalArgumentException( "Provided setType needs to be an implementation, but an interface [" + candidateSetImpl.getName() + "] was provided." ); } final Class[] implementedInterfaces = candidateSetImpl.getInterfaces(); final List implementedIFs = Arrays.asList(implementedInterfaces); return implementedIFs.contains(java.util.Set.class) || implementedIFs.contains(java.util.NavigableSet.class) || implementedIFs.contains(java.util.SortedSet.class); } public void testSetContains(final Set set, final String title) { printHeader(title); out.println("Chosen Set Implementation: " + set.getClass().getName()); final Person person = davidLightman; out.println( set.contains(person) ? person + " is one of my people." : person + " is NOT one of my people."); final Person luke = new Person("Skywalker", "Luke"); out.println( set.contains(luke) ? luke + " is one of my people." : luke + " is NOT one of my people."); out.println( set.contains(wopr) ? wopr + " is one of my people." : wopr + " is NOT one of my people."); } private void printHeader(final String headerText) { out.println(); out.println( "=================================================================="); out.println("== " + headerText); out.println( "=================================================================="); } public static void main(final String[] arguments) { final SetContainsExample me = new SetContainsExample(); final Set peopleHash = me.createPeople(HashSet.class); me.testSetContains(peopleHash, "HashSet"); final Set peopleLinkedHash = me.createPeople(LinkedHashSet.class); me.testSetContains(peopleLinkedHash, "LinkedHashSet"); final Set peopleTree = me.createPeople(TreeSet.class); me.testSetContains(peopleTree, "TreeSet"); } } 

Ketika kode di atas dijalankan sebagaimana adanya (tanpa InanimateObjectada Comparable), output muncul seperti yang ditunjukkan pada snapshot layar berikutnya.

Perintah tersebut ClassCastExceptionmemberi tahu kita, "dustin.examples.InanimateObject tidak dapat ditransmisikan ke java.lang.Comparable." Ini masuk akal karena kelas tersebut tidak mengimplementasikan Comparable, yang diperlukan untuk digunakan dengan TreeMap.contains(Object)metode tersebut. Saat dibuat Comparable, kelas akan terlihat seperti yang ditampilkan berikutnya.

InanimateObject.java sebanding