clone()
Büyük bir proje içinde çeşitli işlemler için birim testleri yazmaya çalışıyorum ve bir yerde aynı türden iki nesneyi alabilen, derin bir karşılaştırma yapabilen ve aynı mı değil mi?
clone()
Büyük bir proje içinde çeşitli işlemler için birim testleri yazmaya çalışıyorum ve bir yerde aynı türden iki nesneyi alabilen, derin bir karşılaştırma yapabilen ve aynı mı değil mi?
Yanıtlar:
Unitils bu işleve sahiptir:
Java varsayılan / boş değerlerini göz ardı etmek ve koleksiyonların sırasını göz ardı etmek gibi farklı seçeneklerle yansıtma yoluyla eşitlik iddiası
unitils
tam olarak kusurlu olduğunu söyleyebilirim, çünkü değişkenleri gözlemlenebilir bir etkiye sahip olmasalar bile karşılaştırır . Değişkenleri karşılaştırmanın bir başka (istenmeyen) sonucu, saf kapanışların (kendi durumları olmadan) desteklenmemesidir. Ayrıca, karşılaştırılan nesnelerin aynı çalışma zamanı türünde olmasını gerektirir. Kollarımı sıvadım ve bu endişeleri gideren kendi derin karşılaştırma aracımı yarattım .
Bu soruyu seviyorum! Temel olarak, neredeyse hiç cevaplanmadığı veya kötü yanıtlanmadığı için. Henüz kimse anlamamış gibi. El değmemiş topraklar :)
Öncelikle, kullanmayı düşünmeyin bile equals
. equals
Javadoc'ta tanımlandığı gibi sözleşmesi, bir eşitlik ilişkisi değil , bir eşdeğerlik ilişkisidir (dönüşlü, simetrik ve geçişli) . Bunun için de antisimetrik olması gerekir. Bunun tek uygulaması, equals
gerçek bir eşitlik ilişkisidir (veya olabilir) java.lang.Object
. equals
Grafikteki her şeyi karşılaştırmak için kullanmış olsanız bile , sözleşmeyi bozma riski oldukça yüksektir. Josh Bloch'un Effective Java'da belirttiği gibi , eşittir sözleşmesini bozmak çok kolaydır:
"Örneklenebilir bir sınıfı genişletmenin ve eşittir sözleşmesini korurken bir özellik eklemenin hiçbir yolu yoktur"
Ayrıca, bir boole yönteminin gerçekten ne faydası var ki? Orijinal ile klon arasındaki tüm farklılıkları özetlemek güzel olurdu, sence de öyle değil mi? Ayrıca, burada grafikteki her nesne için karşılaştırma kodu yazmak / sürdürmekle uğraşmak istemediğinizi, bunun yerine zamanla değiştikçe kaynakla ölçeklenecek bir şey aradığınızı varsayacağım.
Soooo, gerçekten istediğin şey bir tür durum karşılaştırma aracı. Bu aracın nasıl uygulandığı gerçekten alan modelinizin doğasına ve performans kısıtlamalarınıza bağlıdır. Deneyimlerime göre, genel bir sihirli mermi yok. Ve çok sayıda yinelemede yavaş olacaktır . Ancak bir klonlama işleminin bütünlüğünü test etmek için, işi oldukça iyi yapacak. En iyi iki seçeneğiniz serileştirme ve yansıtmadır.
Karşılaşacağınız bazı sorunlar:
XStream oldukça hızlıdır ve XMLUnit ile birleştirildiğinde, işi sadece birkaç satır kodla yapar. XMLUnit güzeldir çünkü tüm farklılıkları bildirebilir veya bulduğu ilk anda durabilir. Ve çıktısı farklı düğümlere giden xpath'i içerir, ki bu güzeldir. Varsayılan olarak sırasız koleksiyonlara izin vermez, ancak bunu yapacak şekilde yapılandırılabilir. Özel bir fark işleyicisinin enjekte edilmesi (a olarak adlandırılır DifferenceListener
), sıralamayı göz ardı etme dahil olmak üzere farklılıkları nasıl ele almak istediğinizi belirlemenize olanak tanır. Ancak, en basit özelleştirmenin ötesinde bir şey yapmak istediğinizde, yazmak zorlaşır ve ayrıntılar belirli bir etki alanı nesnesine bağlanma eğilimindedir.
Benim kişisel tercihim, bildirilen tüm alanlar arasında geçiş yapmak ve her birine derinlemesine inmek için yansımayı kullanmak ve ilerledikçe farklılıkları takip etmektir. Uyarı kelimesi: Yığın taşması istisnalarını sevmediğiniz sürece özyineleme kullanmayın. İşleri bir yığınla kapsam içinde tutun (birLinkedList
ya da başka birşey). Genellikle geçici ve statik alanları görmezden gelirim ve daha önce karşılaştırdığım nesne çiftlerini atlarım, bu nedenle birisi kendine referanslı kod yazmaya karar verirse sonsuz döngülerle karşılaşmam (Ancak, ne olursa olsun her zaman ilkel sarmalayıcıları karşılaştırırım. , aynı nesne başvuruları sıklıkla yeniden kullanıldığından). Koleksiyon sıralamayı yok saymak ve özel türleri veya alanları yok saymak için işleri önceden yapılandırabilirsiniz, ancak durum karşılaştırma ilkelerimi ek açıklamalar yoluyla alanların kendisinde tanımlamayı seviyorum. Bu, IMHO, çalışma zamanında sınıfla ilgili meta verileri kullanılabilir hale getirmek için ek açıklamaların tam olarak kastedildiği şeydir. Gibi bir şey:
@StatePolicy(unordered=true, ignore=false, exactTypesOnly=true)
private List<StringyThing> _mylist;
Bence bu gerçekten zor bir problem ama tamamen çözülebilir! Ve sizin için çalışan bir şeye sahip olduğunuzda, gerçekten, gerçekten, kullanışlıdır :)
Yani, iyi şanslar. Ve saf dahi olan bir şey bulursanız, paylaşmayı unutmayın!
Java-util'de DeepEquals ve DeepHashCode () görün: https://github.com/jdereg/java-util
Bu sınıf, orijinal yazarın istediği şeyi tam olarak yapar.
public
koleksiyon / haritalara uygulanabilir olmanın aksine, yalnızca tüm türler için geçerli olanı karşılaştırarak tartışırım .
EqualsBuilder.reflectionEquals () öğesini burada açıklandığı gibi kullanarak sınıfın equals () yöntemini geçersiz kılabilirsiniz :
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
In AssertJ , yapabileceğiniz:
Assertions.assertThat(expectedObject).isEqualToComparingFieldByFieldRecursively(actualObject);
Muhtemelen her durumda işe yaramayacaktır, ancak düşündüğünüz daha fazla durumda işe yarayacaktır.
Belgelerin söylediği şu:
Test edilen nesnenin (gerçek), özellik / alan karşılaştırmasına göre (devralınanlar dahil) özyinelemeli bir özelliğe / alana dayalı olarak verilen nesneye eşit olduğunu iddia edin. Bu, gerçek eşittir uygulaması size uygun değilse yararlı olabilir. Özyinelemeli özellik / alan karşılaştırması, özel eşittir uygulamasına sahip alanlara uygulanmaz, yani alan karşılaştırmasına göre alan yerine geçersiz kılınan eşittir yöntemi kullanılacaktır.
Özyinelemeli karşılaştırma döngüleri işler. Varsayılan olarak kayan değerler 1.0E-6 hassasiyetiyle karşılaştırılır ve 1.0E-15 ile iki katına çıkar.
ComparatorForFields (Comparator, String ...) ve usingComparatorForType (Karşılaştırıcı, Sınıf) kullanarak (iç içe geçmiş) alan başına özel bir karşılaştırıcı veya tür belirtebilirsiniz.
Karşılaştırılacak nesneler farklı türlerde olabilir ancak aynı özelliklere / alanlara sahip olmalıdır. Örneğin, gerçek nesnenin bir String alanı adı varsa, diğer nesnenin de bir tane olması beklenir. Bir nesnenin bir alanı ve aynı ada sahip bir özelliği varsa, özellik değeri alan üzerinde kullanılacaktır.
isEqualToComparingFieldByFieldRecursively
artık kullanımdan kaldırıldı. assertThat(expectedObject).usingRecursiveComparison().isEqualTo(actualObject);
Hibernate Envers tarafından revize edilen iki varlık örneğinin karşılaştırmasını uygulamak zorunda kaldı. Kendi farklılığımı yazmaya başladım ama sonra aşağıdaki çerçeveyi buldum.
https://github.com/SQiShER/java-object-diff
Aynı türden iki nesneyi karşılaştırabilirsiniz ve değişiklikleri, eklemeleri ve çıkarmaları gösterecektir. Değişiklik yoksa, nesneler eşittir (teoride). Kontrol sırasında göz ardı edilmesi gereken alıcılar için ek açıklamalar sağlanır. Çerçeve çalışması, eşitlik kontrolünden çok daha geniş uygulamalara sahiptir, yani bir değişiklik günlüğü oluşturmak için kullanıyorum.
Performansı iyi, JPA varlıklarını karşılaştırırken, önce onları varlık yöneticisinden ayırdığınızdan emin olun.
XStream kullanıyorum:
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
XStream xstream = new XStream();
String oxml = xstream.toXML(o);
String myxml = xstream.toXML(this);
return myxml.equals(oxml);
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
XStream xstream = new XStream();
String myxml = xstream.toXML(this);
return myxml.hashCode();
}
http://www.unitils.org/tutorial-reflectionassert.html
public class User {
private long id;
private String first;
private String last;
public User(long id, String first, String last) {
this.id = id;
this.first = first;
this.last = last;
}
}
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);
Hamcrest, Matcher ile aynıPropertyValuesAs'a sahiptir . Ancak JavaBeans Sözleşmesine dayanır (alıcılar ve ayarlayıcılar kullanır). Karşılaştırılacak nesnelerin öznitelikleri için alıcıları ve ayarlayıcıları yoksa, bu çalışmayacaktır.
import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
import static org.junit.Assert.assertThat;
import org.junit.Test;
public class UserTest {
@Test
public void asfd() {
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertThat(user1, samePropertyValuesAs(user2)); // all good
user2 = new User(1, "John", "Do");
assertThat(user1, samePropertyValuesAs(user2)); // will fail
}
}
Kullanıcı fasulyesi - alıcılar ve ayarlayıcılarla
public class User {
private long id;
private String first;
private String last;
public User(long id, String first, String last) {
this.id = id;
this.first = first;
this.last = last;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
isFoo
, bir Boolean
özellik için okuma yöntemi kullanan bir POJO'nuz olana kadar harika çalışır . Düzeltmek için 2016'dan beri açık olan bir PR var. github.com/hamcrest/JavaHamcrest/pull/136
Nesneleriniz Serializable uyguluyorsa, bunu kullanabilirsiniz:
public static boolean deepCompare(Object o1, Object o2) {
try {
ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
ObjectOutputStream oos1 = new ObjectOutputStream(baos1);
oos1.writeObject(o1);
oos1.close();
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
ObjectOutputStream oos2 = new ObjectOutputStream(baos2);
oos2.writeObject(o2);
oos2.close();
return Arrays.equals(baos1.toByteArray(), baos2.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Bağlantılı Liste örneğinizin işlenmesi o kadar da zor değil. Kod iki nesne grafiğini geçerken, ziyaret edilen nesneleri bir Set veya Haritaya yerleştirir. Başka bir nesne referansına geçmeden önce, bu küme nesnenin halihazırda geçiş yapılıp yapılmadığını görmek için test edilir. Eğer öyleyse, daha ileri gitmeye gerek yok.
LinkedList (Yığın gibi, ancak üzerinde senkronize yöntemler olmadan, bu yüzden daha hızlı) diyen yukarıdaki kişiye katılıyorum. Her alanı elde etmek için yansıma kullanırken bir Yığın kullanarak nesne grafiğini geçmek ideal çözümdür. Bir kez yazıldığında, bu "harici" eşittir () ve "harici" hashCode (), tüm equals () ve hashCode () yöntemlerinin çağırması gereken şeydir. Bir daha asla bir customer equals () yöntemine ihtiyacınız yok.
Google Code'da listelenen, eksiksiz bir nesne grafiğinden geçen bir miktar kod yazdım. Json-io (http://code.google.com/p/json-io/) sayfasına bakın. Bir Java nesne grafiğini JSON'a serileştirir ve ondan seri durumdan çıkarılır. Genel kurucular olsun veya olmasın, Serialize edilebilir veya Serialize edilemeyen, vb. Tüm Java nesnelerini işler. Bu aynı geçiş kodu, harici "equals ()" ve harici "hashcode ()" uygulamasının temelini oluşturacaktır. Btw, JsonReader / JsonWriter (json-io) genellikle yerleşik ObjectInputStream / ObjectOutputStream'den daha hızlıdır.
Bu JsonReader / JsonWriter karşılaştırma için kullanılabilir, ancak hashcode ile yardımcı olmayacaktır. Evrensel bir hashcode () ve equals () istiyorsanız, kendi koduna ihtiyacı vardır. Bunu genel bir grafik ziyaretçisiyle başarabilirim. Göreceğiz.
Diğer hususlar - statik alanlar - bu kolaydır - bunlar atlanabilir çünkü tüm equals () örnekleri statik alanlar için aynı değere sahip olacaktır çünkü statik alanlar tüm örneklerde paylaşılır.
Geçici alanlara gelince - bu seçilebilir bir seçenek olacaktır. Bazen geçici olayların diğer zamanları saymamasını isteyebilirsiniz. "Bazen deli gibi hissediyorsun, bazen hissetmiyorsun."
Json-io projesine tekrar bakın (diğer projelerim için) ve harici eşittir () / hashcode () projesini bulacaksınız. Henüz bir ismim yok ama belli olacak.
Sanırım bunu biliyorsunuz, ama teoride, iki nesnenin gerçekten eşit olduğunu iddia etmek için her zaman .equals değerini geçersiz kılmanız gerekiyor. Bu, üyelerinde geçersiz kılınan .equals yöntemlerini kontrol ettikleri anlamına gelir.
Bu tür şeyler neden .equals nesnesinde tanımlanır.
Bu tutarlı bir şekilde yapılsaydı bir sorun yaşamazdınız.
Böylesine derin bir karşılaştırma için durdurma garantisi sorun olabilir. Aşağıdakiler ne yapmalı? (Böyle bir karşılaştırıcı uygularsanız, bu iyi bir birim testi olur.)
LinkedListNode a = new LinkedListNode();
a.next = a;
LinkedListNode b = new LinkedListNode();
b.next = b;
System.out.println(DeepCompare(a, b));
İşte bir tane daha:
LinkedListNode c = new LinkedListNode();
LinkedListNode d = new LinkedListNode();
c.next = d;
d.next = c;
System.out.println(DeepCompare(c, d));
Using an answer instead of a comment to get a longer limit and better formatting.
Eğer bu bir yorumsa, neden cevap bölümünü kullanalım? Bu yüzden işaretledim. yüzünden değil ?
. Bu cevap, yorumu geride bırakmayan başka biri tarafından zaten işaretlendi. Bunu inceleme kuyruğuna yeni aldım. Benim hatam olabilir, daha dikkatli olmalıydım.
Ray Hulha çözümünden esinlenilen en kolay çözümün nesneyi seri hale getirmek ve ardından ham sonucu derinlemesine karşılaştırmak olduğunu düşünüyorum.
Serileştirme bayt, json, xml veya basit toString vb. Olabilir. ToString daha ucuz görünüyor. Lombok, bizim için ücretsiz, kolay özelleştirilebilir ToSTring üretir. Aşağıdaki örneğe bakın.
@ToString @Getter @Setter
class foo{
boolean foo1;
String foo2;
public boolean deepCompare(Object other) { //for cohesiveness
return other != null && this.toString().equals(other.toString());
}
}
Apache size bir şey verir, her iki nesneyi de dizeye dönüştürün ve dizeleri karşılaştırın, ancak toString () 'i Geçersiz Kılmalısınız
obj1.toString().equals(obj2.toString())
ToString () geçersiz kıl
Tüm alanlar ilkel türlerse:
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
@Override
public String toString() {return
ReflectionToStringBuilder.toString(this);}
İlkel olmayan alanlarınız ve / veya koleksiyonunuz ve / veya haritanız varsa:
// Within class
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
@Override
public String toString() {return
ReflectionToStringBuilder.toString(this,new
MultipleRecursiveToStringStyle());}
// New class extended from Apache ToStringStyle
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.*;
public class MultipleRecursiveToStringStyle extends ToStringStyle {
private static final int INFINITE_DEPTH = -1;
private int maxDepth;
private int depth;
public MultipleRecursiveToStringStyle() {
this(INFINITE_DEPTH);
}
public MultipleRecursiveToStringStyle(int maxDepth) {
setUseShortClassName(true);
setUseIdentityHashCode(false);
this.maxDepth = maxDepth;
}
@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
}
@Override
protected void appendDetail(StringBuffer buffer, String fieldName,
Collection<?> coll) {
for(Object value: coll){
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
}
}
@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Map<?, ?> map) {
for(Map.Entry<?,?> kvEntry: map.entrySet()){
Object value = kvEntry.getKey();
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
value = kvEntry.getValue();
if (value.getClass().getName().startsWith("java.lang.")
|| (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
buffer.append(value);
} else {
depth++;
buffer.append(ReflectionToStringBuilder.toString(value, this));
depth--;
}
}
}}
Java 7'den beri Objects.deepEquals(Object, Object)
.