What am I doing:
I have a container class named Os
, that can contains different type elements and also instances of class Os
. When I compare this class, I want to see :
- shallow equals for elements
- deep equals for
Os
elements
I have ensured, that every single element contained in class:
- Can not be null.
- Is comparable to same type elements.
- Is immutable. Well, at least part that I'm checking.
Following is what I have at the moment.
Example:
For example, this test case will pass.
Os o1 = Os.of(3, 4d, Os.of("-"));
Os o2 = Os.of(Os.of(Character.toString('-')), 4.0, new Integer(3));
assertEquals(o1.toString(), "[3, 4.0, [-]]");
assertEquals(o2.toString(), "[[-], 4.0, 3]");
assertTrue(o1.reverse().compareTo(o2) == 0);
Code example:
compareTo
method:
@Override
public int compareTo(final Os that) {
final int BEFORE = -1;
final int EQUAL = 0;
final int AFTER = 1;
int subresult = 0;
Comparable<?> othis;
Comparable<?> othat;
if (that == null)
return AFTER;
if (this == that)
return EQUAL;
subresult = ((Integer) this.o.size()).compareTo(that.o.size());
if (subresult < 0)
return BEFORE;
else if (subresult > 0)
return AFTER;
try {
for (int i = 0; i < this.o.size(); i++) {
othis = this.o.get(i);
othat = that.o.get(i);
if (othis.getClass() == othat.getClass()) {
if (othat instanceof Os) {
subresult = ((Os) othis).compareTo(((Os) othat));
if (subresult < 0)
re开发者_运维百科turn BEFORE;
else if (subresult > 0)
return AFTER;
} else {
subresult = hackCMP(othis, othat);
if (subresult < 0)
return BEFORE;
else if (subresult > 0)
return AFTER;
}
} else {
subresult = othis.getClass().getName()
.compareTo(othat.getClass().getName());
if (subresult < 0)
return BEFORE;
else if (subresult > 0)
return AFTER;
}
}
return EQUAL;
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return BEFORE;
}
private static int hackCMP(Object val, Object val2)
throws SecurityException, NoSuchMethodException,
IllegalArgumentException, IllegalAccessException,
InvocationTargetException {
Method m = val.getClass().getMethod("compareTo", val.getClass());
return (Integer) m.invoke(val, val2);
}
Question:
I would like to refactor the code.
For example:
- I would prefer not using
hackCMP
method, if possible. Following code segment seems to repeat itself a lot. Can I replace it with something?
subresult = <expression>; if (subresult < 0) return BEFORE; else if (subresult > 0) return AFTER; //else ...
What can I refactor and how to do it?
Edit:
@wolfcastle : Data is stored in private final ImmutableList<Comparable<?>> o;
.
I'd like to mention, that every answer was useful. Following seems to work:
@Override
public int compareTo(final Os that) {
Ordering<Iterable<Comparable<?>>> order = //
Ordering.natural().<Comparable<?>> lexicographical();
int result = -1;
try {
result = ComparisonChain.start()
.compare(this.o.size(), that.o.size())
.compare(this.o, that.o, order).result();
} catch (Exception e) { //ignore: type mismatch
}
return result;
}
One option I would consider would be storing the elements in a class that allows them to be compared by class rather than by their compareTo
method if they aren't the same class:
private static class Element implements Comparable<Element> {
// raw Comparable allows you to call compareTo
private final Comparable comparable;
Element(Comparable comparable) {
this.comparable = comparable;
}
@Override @SuppressWarnings("unchecked")
public int compareTo(Element o) {
Comparable other = o.comparable;
if(comparable.getClass().isInstance(other)) {
return comparable.compareTo(other);
}
return comparable.getClass().getName().compareTo(other.getClass().getName());
}
@Override
public boolean equals(Object obj) {
return obj instanceof Element && comparable.equals(((Element) obj).comparable);
}
@Override
public int hashCode() {
return comparable.hashCode();
}
@Override
public String toString() {
return comparable.toString();
}
}
Then, with your internal list being a List<Element>
, your compareTo
method in Os
could be pretty simple. Using Guava, it could be extremely simple:
@Override
public int compareTo(Os o) {
return ComparisonChain.start()
.compare(list.size(), o.list.size())
.compare(list, o.list, Ordering.natural().<Element>lexicographical())
.result();
}
You could have a method that returned BEFORE | AFTER | INDETERMINATE (say), then call it.
result = newMethod(subresult);
if (result != INDETERMINATE) return result;
That's not much of an improvement, and it still needs to be duplicated everywhere, but it's a little tighter.
Since the generic type of the List<Comparable<?>> o
property is not fixed, I'd get rid of the generic type and rely on the raw type. It costs one @SuppressWarnings("rawtypes")
, but it minimizes a lot.
@Override
@SuppressWarnings("rawtypes")
public int compareTo(final Os that) {
final int BEFORE = -1;
final int EQUAL = 0;
final int AFTER = 1;
if (that == null)
return AFTER;
if (this == that)
return EQUAL;
int subresult = ((Integer) this.o.size()).compareTo(that.o.size());
if (subresult != EQUAL)
return subresult;
for (int i = 0; i < this.o.size(); i++) {
Comparable othis = this.o.get(i);
Comparable othat = that.o.get(i);
subresult = othis.compareTo(othat);
if (subresult != EQUAL)
return subresult;
}
return EQUAL;
}
精彩评论