implementing compareTo method for several fields - java

I want to compare two object based on 5-tuple which are:
srcAddr, dstAddr, srcPort, dstPort, protocol
here is what i have:
public class Flows implements Serializable, Comparable {
String srcAddr, dstAddr, srcPort, dstPort, protocol;
public int compareTo(Flows arg0) {
if(this.srcAddr == arg0.srcAddr &&
this.dstAddr == arg0.dstAddr &&
this.srcPort == arg0.srcPort &&
this.dstPort == arg0.dstPort &&
this.protocol == arg0.protocol)
return 0;
}
}
But it doesn't work. It says can not compare two strings.
Can anyone help me to know what is the problem?
Thanks.

The compiler / code checker is warning you that comparing String values with == is almost always a mistake.
But fixing that won't really help because your code does nothing like what a correctly implemented compareTo method should do.
A straight-forward implementation of compareTo for your Flows class would be:
public int compareTo(Flows other) {
int res = this.srcAddr.compareTo(other.srcAddr);
if (res != 0) {
return res;
}
res = this.dstAddr.compareTo(other.dstAddr);
if (res != 0) {
return res;
}
res = this.srcPort.compareTo(other.srcPort);
if (res != 0) {
return res;
}
res = this.dstPort.compareTo(other.dstPort);
if (res != 0) {
return res;
}
return this.protocol.compareTo(other.protocol);
}
That assumes the the fields are never null. If they are, then write a safeCompare(String, String) method that takes care with nulls and apply it to each field as above.
EDIT
Given that you are defining compareTo you also ought to declare equals and hashCode to be consistent with them. Otherwise certain collection methods are likely to behave incorrectly.
EDIT 2
The compiler error you mention in a comment on how to override compareTo method happens because the int compareTo(Flow flow) method actually implements the compareTo method of Comparable<Flow>. If you are going to declare Flow as implementing the raw interface type Comparable then the signature needs to be
public int compareTo(Object obj) {
Flow flow = (Flow) obj;
...
But a better solution would be to change the class declaration to:
public class Flows implements Serializable, Comparable<Flow> {
...

Try:
#Override
public int compareTo(final Flows that) {
return ComparisonChain.start().
compare(this.srcAddr, that.srcAddr).
compare(this.dstAddr, that.dstAddr).
compare(this.srcPort, that.srcPort).
compare(this.dstPort, that.dstPort).
compare(this.protocol, that.protocol).
result();
}
Requires Guava

Use string.equals() instead of ==.

You can write like this also, I have done like this in my project
public int compareTo(Flows arg0) {
int comp1, comp2, comp3, comp4;
comp1 = this.srcAddr.compareTo(arg0.srcAddr);
comp2 = this.dstAddr.compareTo(arg0.dstAddr);
comp3 = this.srcPort.compareTo(arg0.srcPort);
comp4 = this.protocol.compareTo(arg0.protocol);
if (comp1 == 0 && comp2 == 0 && comp3 == 0 && comp4 == 0) {
return 0;
} else {
if (comp1 != 0)
return comp1;
else {
if (comp2 != 0)
return comp2;
else {
if (comp3 != 0)
return comp3;
else {
if (comp4 != 0)
return comp4;
else
return 0;
}
}
}
}
}

Related

Short form for Java If Else statement

I have a method which checks for nulls. Is there a way to reduce the number of lines in the method? Currently, the code looks "dirty":
private int similarityCount (String one, String two) {
if (one == null && two == null) {
return 1;
} else if (one == null && two != null) {
return 2;
} else if (one != null && two == null) {
return 3;
} else {
if(isMatch(one, two))
return 4;
return 5;
}
}
private int similarityCount (String one, String two) {
if (one == null && two == null) {
return 1;
}
if (one == null) {
return 2;
}
if (two == null) {
return 3;
}
if (isMatch(one, two)) {
return 4;
}
return 5;
}
I prefer nested conditions in such cases :
private int similarityCount (String one, String two) {
if (one==null) {
if (two==null) {
return 1;
} else {
return 2;
}
} else {
if (two==null) {
return 3;
} else {
return isMatch(one, two) ? 4 : 5;
}
}
}
Of course you can achieve a shorter version by using more ternary conditional operators.
private int similarityCount (String one, String two) {
if (one==null) {
return (two==null) ? 1 : 2;
} else {
return (two==null) ? 3 : isMatch(one, two) ? 4 : 5;
}
}
Or even (now this is getting less readable) :
private int similarityCount (String one, String two) {
return (one==null) ? ((two==null) ? 1 : 2) : ((two==null) ? 3 : isMatch(one, two) ? 4 : 5);
}
Since the actual purpose of the function seems to be to handle non-null objects by matching them, I’d handle all the null checks in a guard statement at the beginning.
Then, once you’ve established that no argument is null, you can handle the actual logic:
private int similarityCount(String a, String b) {
if (a == null || b == null) {
return a == b ? 1 : a == null ? 2 : 3;
}
return isMatch(a, b) ? 4 : 5;
}
This is both more concise and more readable than the other options.
That said, real functions wouldn’t usually return such numeric codes. Unless your method was simplified to exemplify the problem, I’d strongly urge you to reconsider the logic and instead write something akin to what follows:
private boolean similarityCount(String a, String b) {
if (a == null || b == null) {
throw new NullPointerException();
}
return isMatch(a, b);
}
Or:
private boolean similarityCount(String a, String b) {
if (a == null) {
throw new IllegalArgumentException("a");
}
if (b == null) {
throw new IllegalArgumentException("b");
}
return isMatch(a, b);
}
These approaches would be more conventional. On the flip side, they may trigger an exception. We can avoid this by returning a java.util.Optional<Boolean> in Java 8:
private Optional<Boolean> similarityCount(String a, String b) {
if (a == null || b == null) {
return Optional.empty();
}
return Optional.of(isMatch(a, b));
}
At first glance this may seem to be no better than returning null but optionals are in fact far superior.
The code looks clear enough for me. You can make it shorter with nesting and ternary operators:
if(one==null) {
return two==null ? 1 : 2;
}
if(two==null) {
return 3;
}
return isMatch(one,two) ? 4 : 5;
It can be done in one line using Java conditional operator:
return (one==null?(two==null?1:2):(two==null?3:(isMatch(one,two)?4:5)));
You can create a pseudo-lookup-table. Some people frown on the nested ternary operators and it's highly dependent on whitespace for readability, but it can be a very readable approach to conditional returning:
private int similarityCount (String one, String two) {
return (one == null && two == null) ? 1
: (one == null && two != null) ? 2
: (one != null && two == null) ? 3
: isMatch(one, two) ? 4
: 5;
}
I like expressions.
private static int similarityCount (String one, String two) {
return one == null ?
similarityCountByTwoOnly(two) :
two == null ? 3 : (isMatch(one, two) ? 4 : 5)
;
}
private static int similarityCountByTwoOnly(String two) {
return two == null ? 1 : 2;
}
As an aside, I would probably challenge why you are doing this. I would assume you would do some kind of check on the returned integer after you've evaluated it and branch your logic based on it. If that is the case, you've just made a less readable check for null where the user of your method needs to understand the contract implicit in the value of the integer.
Also, here's a simple solution for when you need to check if strings are equal when they may be null:
boolean same = one == null ? two == null : one.equals(two);
Getting rid of IF statements is good fun. Using a Map is one way of doing this. It doesn't fit this case exactly because of the call to isMatch, but I offer it as an alternative which cuts the similarityCount method body to a single line with one IF
The following code has two IFs. If GetOrDefault didn't evaluate the second argument, it could be reduced to one. Unfortunately it does so the null check inside isMatch is necessary.
You could go a lot further with this if you wanted to. For example, isMatch could return 4 or 5 rather than a boolean which would help you simplify further.
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import java.util.Map;
public class SimilarityCount {
private Map<SimilarityCountKey, Integer> rtn = ImmutableMap.of(new SimilarityCountKey(null, null), 1, new SimilarityCountKey(null, ""), 2, new SimilarityCountKey("", null), 3);
public int similarityCount(String one, String two) {
return rtn.getOrDefault(new SimilarityCountKey(one, two), isMatch(one, two) ? 4 : 5);
}
private boolean isMatch(String one, String two) {
if (one == null || two == null) {
return false;
}
return one.equals(two);
}
private class SimilarityCountKey {
private final boolean one;
private final boolean two;
public SimilarityCountKey(String one, String two) {
this.one = one == null;
this.two = two == null;
}
#Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
#Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}
}
In case anyone else fancies a crack at another solution, here are some tests to help you get started
import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
public class SimilarityCountTest {
#Test
public void one(){
Assert.assertThat(new SimilarityCount().similarityCount(null,null), is(1));
}
#Test
public void two(){
Assert.assertThat(new SimilarityCount().similarityCount(null,""), is(2));
}
#Test
public void three(){
Assert.assertThat(new SimilarityCount().similarityCount("",null), is(3));
}
#Test
public void four(){
Assert.assertThat(new SimilarityCount().similarityCount("",""), is(4));
}
#Test
public void five(){
Assert.assertThat(new SimilarityCount().similarityCount("a","b"), is(5));
}
}
This should be slightly faster if neither is null, as it only performs one 'if' statement in this case.
private int similarityCount (String one, String two) {
if (one == null || two == null) { // Something is null
if (two != null) { // Only one is null
return 2;
}
if (one != null) { // Only two is null
return 3;
}
return 1; // Both must be null
}
return isMatch(one, two) ? 4 : 5;
}

How to handle null values when doing Collections.sort() with nested objects?

What is the best way to deal with null values, when doing Collections.sort() on nested objects?
I'd like to sort a couple of objects, basically applying this rule:
#Override
public int compare(final InvoicePos invoicePosOne, final InvoicePos invoicePosTwo) {
return invoicePosOne.getInvoice().getInvoiceNo().compareTo(invoicePosTwo.getInvoice().getInvoiceNo());
}
However, any of these objects can be null (i.e. invoice position, invoice and invoice number).
public class InvoicePos {
private Invoice invoice = null;
// ...
}
public class Invoice {
private String invoiceNo = "";
// ...
}
Do I have do do explicit null-checks on all my objects or is there an approach with less writing?
For clarification: I'm aware that my above example may raise NullPointerExceptions. Currently I'm doing the following and basically, I questioned myself, if there is any smarter approach.
Collections.sort(allInvoicePositions, new Comparator<InvoicePos>() {
#Override
public int compare(final InvoicePos invoicePosOne, final InvoicePos invoicePosTwo) {
if (null == invoicePosOne && null == invoicePosTwo) {
return 0;
}
if (null == invoicePosOne) {
return -1;
}
if (null == invoicePosTwo) {
return 1;
}
if (null == invoicePosOne.getInvoice() && null == invoicePosTwo.getInvoice()) {
return 0;
}
if (null == invoicePosOne.getInvoice()) {
return -1;
}
if (null == invoicePosTwo.getInvoice()) {
return 1;
}
if (null == invoicePosOne.getInvoice().getInvoiceNo() && null == invoicePosTwo.getInvoice().getInvoiceNo()) {
return 0;
}
if (null == invoicePosOne.getInvoice().getInvoiceNo()) {
return -1;
}
if (null == invoicePosTwo.getInvoice().getInvoiceNo()) {
return 1;
}
return invoicePosOne.getInvoice().getInvoiceNo().compareTo(invoicePosTwo.getInvoice().getInvoiceNo());
}
});
There is something called as NullComparator in org.apache.commons.collections.jar.
This might help you https://commons.apache.org/proper/commons-collections/javadocs/api-2.1.1/org/apache/commons/collections/comparators/NullComparator.html.
Do I have do do explicit null-checks on all my objects or is there an approach with less writing?
If these values don't represent anything in your collection, then the best thing you can do is avoid them; don't allow inserting them, so you won't have to handle them when comparing items.
If you insist to have them, then you must check if they're null to avoid NullPointerException.
If you have null values then you need to handle them explicitly and in a consistent way so to have a valid ordering relation. That is, something like:
compare (a, b) {
if (a == null && b == null) return 0;
if (a == null) return -1;
if (b == null) return +1;
return comp(a,b);
}
Don't be tempted to do something like:
compare (a, b) {
if (a == null || b == null) return -1;
return comp(a,b);
}
which would break the ordering relation.

How do you compare classes for order?

I am having issues finding an efficient way to sort classes by order. My following code completes the order in which i need to sort, but I believe there is another way (one I dont know).
What is an efficient way to sort classes?
public int compare(Object one, Object two)
{
//S = Salaried, W = Weekly, D = Daily
//SS == 0 -> SW == -1 -> SD == -1
//WS == 1 -> WW == 0 -> WD == -1
//DS == 1 -> DW == 1 -> DD == 0
Employee a = (Employee)one;
Employee b = (Employee)two;
SalariedEmployee s = new SalariedEmployee(0.0);
WeeklyEmployee w = new WeeklyEmployee (0.0);
DailyEmployee d = new DailyEmployee();
if(one.getClass() == s.getClass() && two.getClass() == s.getClass())
return Double.compare(b.grossPay(), a.grossPay());
if(one.getClass() == s.getClass() && two.getClass() == w.getClass())
return -1;
if(one.getClass() == s.getClass() && two.getClass() == d.getClass())
return -1;
if(one.getClass() == w.getClass() && two.getClass() == s.getClass())
return 1;
if(one.getClass() == w.getClass() && two.getClass() == w.getClass())
return Double.compare(b.grossPay(), a.grossPay());
if(one.getClass() == w.getClass() && two.getClass() == d.getClass())
return -1;
if(one.getClass() == d.getClass() && two.getClass() == s.getClass())
return 1;
if(one.getClass() == d.getClass() && two.getClass() == w.getClass())
return 1;
if(one.getClass() == d.getClass() && two.getClass() == d.getClass())
return Double.compare(b.grossPay(), a.grossPay());
return 0;
}
implement Comparable<> interface in your class and override compareTo() method in Employee class. the method takes Object class as a passed value. For example,
public class Employee implements Comparable<Employee> {
//omitted
public int compareTo(Employee other) {
return grossPay.compareTo(other.grossPay);
}
}
check out the following link to learn more
http://download.oracle.com/javase/tutorial/collections/interfaces/order.html
I am having issues finding an efficient way to sort classes by order
Depends what you mean by "efficient". Putting all the code in a single method will be the most efficient (if done properly) from a CPU perspective, but it is not very efficient from a flexibility point of view.
For an approach that will not be a fast but will be far more flexible check out the Group Comparator and Bean Comparator.
The GroupComparator allows you to combine multiple Comparators into one sort. The BeanComparator is a generic comparator that allows you to sort on any field within a given class. So to use the GroupComparator the basic code would be:
EmployeeComparator employee = new EmployeeComparator();
BeanComparator grossPay = new BeanComparator(Employee.class, "grossPay");
GroupComparator gc = new GroupComparator(employee, grossPay);
Collections.sort(list, gc);
So you would need to write a Comparator that sorts Employees by salaried, weekly and daily. The basic code for the EmployeeComparator might be something like:
if (one.getClass()equals(two.getClass())
return 0;
if (one instanceOf SalariedEmployee)
return 1;
if (two instanceOf SalariedEmployee)
return -1;
if (one instanceOf WeeklyEmployee)
return 1;
else
return -1;
A little more work to set up, but once you have an EmployeeComparator you can then sort of multiple different properties using the Bean and Group Comparators.
Here is my solution.
public int compare(Employee left, Employee right) {
int typeOrderLeft = getTypeOrder(left);
int typeOrderRight = getTypeOrder(right);
if (typeOrderLeft == typeOrderRight) {
return Double.compare(left.grossPay(), right.grossPay());
} else {
return typeOrderLeft - typeOrderRight;
}
}
private int getTypeOrder(Employee employee) {
if (employee instanceof DailyEmployee) {
return 1;
} else if (employee instanceof WeeklyEmployee) {
return 2;
} else if (employee instanceof SalaryEmployee) {
return 3;
}
return 0;
}
You will need to first implement the comparable interface. This lets you define a compareTo method which can be used to sort classes based on a specificed value that you think is viable for comparing classes.
Defining the compareTo method is useful for objects that don't have a predefined way of comparing themselves.

incomparable type in java

I have got this code, and I get an error incomparable types: java.lang.String and int, for this line of code
if ((this.name.String.compareTo(obj.name == 0)) && (this.age = obj.age))
The method is this:
public int compareTo(Object o)
{
int result;
AnyClass obj = (AnyClass)o;
if ((this.name.String.compareTo(obj.name == 0)) && (this.age = obj.age))
{
result = 0;
}
else if (this.name.compareTo(obj.name) > 0)
{
result = 1;
}
else
{
result = -1;
}
return result;
}
I think that position of your bracket isn't correct,
this.name.String.compareTo(obj.name == 0))
obj.name == 0 is the place where you probably compare String (name) to int (0). I guess you wanted to use compareTo on obj.name and then check if it's equal to zero.
I also think that in the second part
(this.age = obj.age)
You wanted to use == instead of =, so I think that the code you wanted to use is:
((this.name.compareTo(obj.name)==0) && (this.age == obj.age))
You can't compare a string to an integer :)
You can convert the string "001" into the integer "1"; or the integer "1" into the string "1".
See Integer.parseInt() or Integer.toString().
compareTo takes in Objec reference(a string in your case) as argument. But your code compareTo(obj.name == 0) passes in boolean which is not appropriate.
I think the code
if ((this.name.String.compareTo(obj.name == 0)) && (this.age = obj.age))
is actually supposed to read like this
if ((this.name.compareTo(obj.name) == 0) && (this.age == obj.age))
Changing where the == 0 is (and changing the second = to an ==) makes this code make sense.
There are a lot of problems with this implementation. It looks like this is an implementation of Comparable for class AnyClass which means the signature is wrong.
AnyClass should implement Comparable<AnyClass>, and the code should look like this:
#Override
public int compareTo(AnyClass other)
{
int ret = name.compareTo(other.name);
return ret != 0 ? ret : Integer.compare(age, other.age);
}
If you use Guava:
#Override
public int compareTo(AnyClass other)
{
return ComparisonChain.start().compare(name, other.name)
.compare(age, other.age).result();
}
((this.name.String.compareTo(obj.name == 0)) && (this.age = obj.age))
obj.name is a String, and 0 is an int. That's where you're getting the error
obj.name is String and you are comparing it with 0.
It is easier if you explain your intention with the code. The first error seems to be the comparison in compareTo obj.name == 0.
Try
if ((this.name.String.compareTo(obj.name) == 0) && (this.age == obj.age))
Which is what I guess you want to achieve.

Sorting a custom type array by a string attribute?

I have an array of a custom type that I want to sort by one of its String attributes. For some reason, the following code is producing wrong results. Could you point out where I might have made a mistake?
class PatientLNComparator implements Comparator<Patient>{
#Override
public int compare(Patient p1, Patient p2) {
String p1_LN = (p1 == null) ? null : p1.last_name;
String p2_LN = (p2 == null) ? null : p2.last_name;
if(p2_LN == null)
return -1;
else if(p1_LN == null)
return +1;
else if(p1_LN.equals(p2_LN))
return 0;
else if(p1_LN.compareTo(p2_LN) > 0)
return -1;
else
return +1;
}
}
One problem to start with - your comparator is inconsistent if you give it two patients with null names, or two null patient references. In particular:
Patient p1 = null;
Patient p2 = null;
int x = comparator.compare(p1, p2);
int y = comparator.compare(p2, p1);
The signs of x and y ought to be different - but they'll both be -1.
After that, it depends on how you want to compare the names. I would usually use
return p1_LN.compareTo(p2_LN);
if you want to sort in ascending order. Note that to sort in descending order you shouldn't just return -p1_LN.compareTo(p2_LN), as if the comparison returns the Integer.MIN_VALUE, the negation won't work. Instead you'd want to return p2_LN.compareTo(p1_LN);.
Note that if you're using this scheme, you don't need to call p1_LN.equals(p2_LN) either - that will be handled by the compareTo call.
You want patient to be ordered by alphabetical by last name, null patients and null last names up front?
class PatientLNComparator implements Comparator<Patient>{
#Override
public int compare(Patient p1, Patient p2) {
String p1_LN = (p1 == null) ? null : p1.last_name;
String p2_LN = (p2 == null) ? null : p2.last_name;
if (p1_LN == null && p2_LN == null)
return 0;
else if (p2_LN == null)
return -1;
else if(p1_LN == null)
return +1;
else
return p1_LN.compareTo(p2_LN);
}
}
To be stable, it really should order by some other fields, like first name, when last names are equal.
I'm assuming you want natural string ordering for this.
First of all, as it is, your compareTo branch is giving inversed results. Don't know if that's what you intended or not (as in you're saying p1 is greater than p2 when the p1's string is lower than p2's).
Furthermore, you can ditch the .equals branch of the if. The compareTo already handles this case.
Therefore a simple
if(p2_LN == null && p1_LN == null)
return 0;
else if(p1_LN == null)
return +1;
else if(p2_LN == null)
return -1;
else return p1_LN.compareTo(p2_LN)
would suffice.
I would use Guava's Ordering class for this:
class Patient {
// ...
public static final Function<Patient, String> GET_LAST_NAME =
new Function<Patient, String>() {
public String apply(Patient from) {
if (from == null) return null;
return from.last_name;
}
};
public static final Comparator<Patient> BY_LAST_NAME =
Ordering.natural()
.onResultOf(GET_LAST_NAME)
.nullsFirst();
}
This will resolve the issue with inconsistent comparison of nulls. It also makes it easy to add a secondary order (e.g. first name):
public static final Comparator<Patient> BY_LAST_NAME =
Ordering.natural()
.onResultOf(GET_LAST_NAME)
.compound(Ordering.natural().onResultOf(GET_FIRST_NAME))
.nullsFirst();

Categories