I have a class Contact with fields firstName, lastName and emails. I need to sort them using Collection.sort(...), but I got an exception:
java.lang.IllegalArgumentException: Comparison method violates its general contract!
My compareTo method:
#Override
public int compareTo(Contact another) {
int compareFirstName = 0;
if (this.getFirstName() != null && another.getFirstName() != null) {
compareFirstName = this.getFirstName().compareToIgnoreCase(
another.getFirstName());
if (compareFirstName == 0) {
int compareLastName = 0;
if (this.getLastName() != null && another.getLastName() != null) {
compareLastName = this.getLastName().compareToIgnoreCase(
another.getLastName());
if (compareLastName == 0) {
int compareEmail = 0;
if (this.getEmails() != null
&& another.getEmails() != null) {
compareEmail = this.getEmails()
.compareToIgnoreCase(another.getEmails());
return compareEmail;
} else {
return 0;
}
} else {
return compareLastName;
}
} else {
int compareEmail = 0;
if (this.getEmails() != null && another.getEmails() != null) {
compareEmail = this.getEmails().compareToIgnoreCase(
another.getEmails());
return compareEmail;
} else {
return 0;
}
}
} else {
return compareFirstName;
}
} else {
int compareLastName = 0;
if (this.getLastName() != null && another.getLastName() != null) {
compareLastName = this.getLastName().compareToIgnoreCase(
another.getLastName());
if (compareLastName == 0) {
int compareEmail = 0;
if (this.getEmails() != null && another.getEmails() != null) {
compareEmail = this.getEmails().compareToIgnoreCase(
another.getEmails());
return compareEmail;
} else {
return 0;
}
} else {
return compareLastName;
}
} else {
int compareEmail = 0;
if (this.getEmails() != null && another.getEmails() != null) {
compareEmail = this.getEmails().compareToIgnoreCase(
another.getEmails());
return compareEmail;
} else {
return 0;
}
}
}
}
Please help me to find error in my compareTo method. Thanks.
Your implementation does violate the contract.
Suppose you have 3 Contacts :
contact1 : First Name = "John", Last Name = "Doe", Email = "x#gmail.com"
contact2 : First Name = "John", Last Name = "Doe", Email = null
contact3 : First Name = "John", Last Name = null, Email = "y#gmail.com"
Based on your logic :
contact1.compareTo(contact2) returns 0 (since they have the same first and last name).
contact2.compareTo(contact3) also returns 0 (since you only compare by first name).
But contact1.compareTo(contact3) doesn't return 0 (since they have different emails).
compareTo must be transitive.
The way to fix this is not to ignore a property that is null only in one of the contacts you are comparing. For example, if this.getLastName()==null && another.getLastName() != null, return 1 (assuming you want to order the null last names after the non-null last names).
Related
The below code has many duplicate lines, looking for a short thing for this.
if(null!= commonShipmentSplInstr.getIsGiftOrder() && GIFT_ORDER_FLAG.equalsIgnoreCase(commonShipmentSplInstr.getIsGiftOrder())) {
printObj.setDOCUMENT(INVOICE_DOCUMENT_FOR_PROSHIP_GIFT_ORDER);
giftMessageLine1 = commonShipmentSplInstr.getGiftMessageLine1();
giftMessageLine2 = commonShipmentSplInstr.getGiftMessageLine2();
giftMessageLine3 = commonShipmentSplInstr.getGiftMessageLine3();
} else if(null!= commonShipmentSplInstr.getSplInstr6() && GIFT_ORDER_FLAG.equalsIgnoreCase(commonShipmentSplInstr.getSplInstr6())) {
printObj.setDOCUMENT(INVOICE_DOCUMENT_FOR_PROSHIP_GIFT_ORDER);
giftMessageLine1 = commonShipmentSplInstr.getGiftMessageLine1();
giftMessageLine2 = commonShipmentSplInstr.getGiftMessageLine2();
giftMessageLine3 = commonShipmentSplInstr.getGiftMessageLine3();
} else if (null != sellingChannel && InvoiceHelper.isZolaSellingChannel(sellingChannel)) {
printObj.setDOCUMENT(INVOICE_DOCUMENT_FOR_PROSHIP_ZOLA_ORDER);
} else {
printObj.setDOCUMENT(DOCUMENT_FOR_PRINT_INVOICE_LABEL_FOR_PROSHIP);
}
In the setDOCUMENT method, all are Constants.
Just combine the two first if clause into one, they seem to be doing exactly the same:
if(commonShipmentSplInstr != null && (GIFT_ORDER_FLAG.equalsIgnoreCase(commonShipmentSplInstr.getIsGiftOrder()) || GIFT_ORDER_FLAG.equalsIgnoreCase(commonShipmentSplInstr.getSplInstr6()))) {
printObj.setDOCUMENT(INVOICE_DOCUMENT_FOR_PROSHIP_GIFT_ORDER);
giftMessageLine1 = commonShipmentSplInstr.getGiftMessageLine1();
giftMessageLine2 = commonShipmentSplInstr.getGiftMessageLine2();
giftMessageLine3 = commonShipmentSplInstr.getGiftMessageLine3();
}
else if (null != sellingChannel && InvoiceHelper.isZolaSellingChannel(sellingChannel)) {
printObj.setDOCUMENT(INVOICE_DOCUMENT_FOR_PROSHIP_ZOLA_ORDER);
}
else {
printObj.setDOCUMENT(DOCUMENT_FOR_PRINT_INVOICE_LABEL_FOR_PROSHIP);
}
I want to write a method that allows me to insert a value to the list (generic implementation). Furthermore i want to check wether a value in already in the list or not. I just want to know wether this is how its supposed to be or not.
In order to check wether a value is already contained within the list:
public boolean contains(T value) {
boolean searchedValue;
if (head == null || baseListLength == 0) {
searchedValue= false;
} else {
IndexListEntry<T> blub= head;
while ((blub.next != null) && (blub.next.value.compareTo(value) <= 0)) {
value= blub.next;
}
searchedValue= baseList.contains(value, blub.pointer);
}
return searchedValue;
}
and to add any value to the list:
public boolean addValue(T value) {
BaseListEntry<T> offs= baseList.add(value);
if (indexListLength < indexMaxLength) {
if (head == null) {
head = new IndexListEntry<T>(value, null, offs);
} else {
IndexListEntry<T> temp= head;
while ((temp.next != null) && (temp.next.value.compareTo(value) < 0)) {
temp= temp.next;
}
if (temp.next == null) {
temp.next = new IndexListEntry<T>(value, null, offs);
} else {
temp.next = new IndexListEntry<T>(value, temp.next, offs);
}
}
indexListLength++;
} else {
IndexListEntry<T> currentIndex = head.next;
int intervalWidth = baseListLength / indexMaxLength;
int intervalAdj = baseListLength % indexMaxLength;
int indexPosition = 0;
for (int i = 1; i < indexMaxLength; i++) {
int rip= 0;
if (intervalAdj - i >= 0)
rip= 1;
indexPosition = indexPosition + intervalWidth + rip;
BaseListEntry<T> newIndexTarget = baseList.getBaseListEntry(indexPosition);
currentIndex.pointer = newIndexTarget;
currentIndex.value = newIndexTarget.value;
currentIndex = currentIndex.next;
}
}
return true;
}
Are there any other methods to accomplish that goal? is my code somewhat correct. I am doubting everything I code right now and I kinda need confirmation from someone who knows more than me about this.
This is of course not everything i got. there are more classes to it but these are not the problem at hand so i dont thnk i need to upload that code as well.
I have three methods that filter an array of devices by a field.
public void filtrateByType(Device[] devices, String type) {
if (devices == null) {
return;
}
for (int i = 0; i < devices.length; i++) {
if (devices[i] == null) {
continue;
}
if (devices[i].getType() == null && type == null) {
continue;
} else if (devices[i].getType() == null) {
devices[i] = null;
continue;
}
if (!devices[i].getType().equals(type)) {
devices[i] = null;
}
}
}
The other methods are similar. The only difference is calling another getter of field on which filtering is applied. For example, there is a call to getModel() instead getType(). Does this violate the DRY principle and how can I change it such that it doesn't (without generics)?
P.S.
It's a homework and unfortunatelly we don't use generics yet. I also can't change the signature of methods. I have a clue that I can create inner class with one method that will call needed getter and return a value. So, I need to put all my checks inside this method, but I don't really understand how do it with my logic (especially with "continue").
You can create an interface DeviceValueExtractor that looks like this:
#FunctionalInterface
public interface DeviceValueExtractor {
Object extractValue(Device device);
}
Now rewrite your method to:
public void filterByType(Device[] devices, DeviceValueExtractor extractor, Object expect) {
if (devices == null) {
return;
}
for (int i = 0; i < devices.length; i++) {
if (devices[i] == null) {
continue;
}
Object actual = extractor.extractValue(devices[i]);
if (actual == null && expect== null) {
continue;
} else if (actual == null) {
devices[i] = null;
continue;
}
if (!Objects.equals(actual, expect)) {
devices[i] = null;
}
}
}
Usage:
filterByType(devices, Device::getType, "Hello");
Note: I used Object due to the requirement not to have generics - because the only method called is equals this is actually No Big Deal.
However for some more type safety, you can introduce generics (and do away with DeviceValueExtractor:
public static <T> void filterByType(Device[] devices, Function<Device, T> extractor, T expect) {
if (devices == null) {
return;
}
for (int i = 0; i < devices.length; i++) {
if (devices[i] == null) {
continue;
}
Object actual = extractor.apply(devices[i]);
if (actual == null && expect== null) {
continue;
} else if (actual == null) {
devices[i] = null;
continue;
}
if (!Objects.equals(actual, expect)) {
devices[i] = null;
}
}
}
Perhaps a some Java 8 magic would help here:
public void filtrateByType(Device[] devices, String type) {
filtrateBy(devices, Device::getType, type);
}
public void filtrateBy(Device[] devices, Function<? super Device, String> attributeGetter, String attribute) {
if (devices == null) {
return;
}
for (int i = 0; i < devices.length; i++) {
if (devices[i] == null) {
continue;
}
if (attributeGetter.apply(devices[i]) == null && attribute == null) {
continue;
} else if (attributeGetter.apply(devices[i]) == null) {
devices[i] = null;
continue;
}
if (!attributeGetter.apply(devices[i]).equals(attribute)) {
devices[i] = null;
}
}
}
This is simpler version. You can use raw types, but this would be more error prone.
public static <T> void filtrateByType(T[] objects, Function<T, String> function, String type) {
if (objects == null || type == null)
return;
for (int i = 0; i < objects.length; i++) {
if (objects[i] == null) continue;
String match = function.apply(objects[i]);
if (match == null || !match.equals(type))
objects[i] = null;
}
}
However, I suspect what you really want is to use the Stream API
Device[] filtered = Stream.of(devices)
.filter(d -> Objects.equals(d.getType(), type))
.toArray(Device[]::new);
I am writing a compararator to sort log lines:
Code:
public static final Comparator<String> HTMLcomparator = new Comparator<String>()
{
#Override
public int compare(String line1, String line2)
{
HTMLLogLine htmlLogLine1 = new HTMLLogLine(line1);
HTMLLogLine htmlLogLine2 = new HTMLLogLine(line2);
int fullCompare = 0;
String requestId1 = htmlLogLine1.getRequestId();
String requestId2 = htmlLogLine2.getRequestId();
if(requestId1 != null && requestId2 != null)
{
fullCompare = requestId1.compareTo(requestId2);
}
else if(requestId1 == null && requestId2 != null)
{
fullCompare = -1;
}
else if(requestId1 != null)
{
fullCompare = 1;
}
else
{
fullCompare = 0;
}
if(fullCompare == 0)
{
String security1 = htmlLogLine1.getSecurity();
String security2 = htmlLogLine2.getSecurity();
if(security1 != null && security2 != null)
{
fullCompare = security1.compareTo(security2);
}
else if(security1 == null && security2 != null)
{
fullCompare = -1;
}
else if(security1 != null && security2 == null)
{
fullCompare = 1;
}
else
{
fullCompare = 0;
}
}
if(fullCompare == 0)
{
String scenario1 = htmlLogLine1.getScenario();
String scenario2 = htmlLogLine2.getScenario();
if(scenario1 != null && scenario2 != null)
{
fullCompare = scenario1.compareTo(scenario2);
}
else if(scenario1 == null && scenario2 != null)
{
fullCompare = -1;
}
else if(scenario1 != null)
{
fullCompare = 1;
}
else
{
fullCompare = 0;
}
}
if(fullCompare == 0)
{
Optional<Instant> timestamp1 = htmlLogLine1.getTimestamp();
Optional<Instant> timestamp2 = htmlLogLine2.getTimestamp();
if(timestamp1.isPresent() && timestamp2.isPresent())
{
fullCompare = timestamp1.get().compareTo(timestamp2.get());
}
else if(!timestamp1.isPresent() && timestamp2.isPresent())
{
fullCompare = -1;
}
else if(timestamp1.isPresent() && !timestamp2.isPresent())
{
fullCompare = 1;
}
else
{
fullCompare = 0;
}
}
return fullCompare;
}
};
The code seems very naive and has a lot of duplication. Any ideas how to make it concise?
Previous to this code, I had written the following code in compare method:
Comparator<HTMLLogLine > fullComparator = Comparator.comparing(HTMLLogLine ::getRequestId)
.thenComparing(HTMLLogLine ::getSecurity)
.thenComparing(HTMLLogLine ::getScenario);
fullCompare = fullComparator.compare(logLine1, logLine2);
However,to my surprise the code threw exception: Compare method does not follow the contract!
How can I make this comparator concise yet make it work?
I need to find best matched employee salary in the DB records as:
Name: City: State:
A (null) (null)
A (null) DEL
(null) (null) (null)
A SAKET DEL
Match order should be:
1. NAME = name, STATE = state, CITY = city
2. NAME = name, STATE = state , CITY = NULL
3. NAME = name, STATE = NULL, CITY = NULL
4. NAME = NULL, STATE = NULL, CITY = NULL
Means if in a row where all attributes matches – it should be selected, if we do not have that kind of data we should go to next best option like select state and city as NULL, etc.
My code as below, is giving me correct results but I need a more efficient way.
private static BigDecimal getsalaryForBestMatch(ResultSet results, EmployeeRq request) throws Exception{
BigDecimal salary = null;
BigDecimal salaryWithState = null;
BigDecimal salaryWithName = null;
BigDecimal salaryWithNoMatch = null;
while (results.next()) {
String billerName = results.getString("EMP_NAME") != null ? results.getString("EMP_NAME").trim() : null;
String city = results.getString("CITY") != null ? results.getString("CITY").trim() : null;
String state = results.getString("STATE") != null ? results.getString("STATE").trim() : null;
BigDecimal salaryRslt = null;
if(results.getString("SALARY") != null){
salaryRslt = BigDecimal.valueOf(results.getDouble("SALARY"));
}
if(billerName != null && !billerName.equals("") && billerName.equals(request.getBillPaymentsalaryCalculateInfo().getBillerName())){
if(city != null && !city.equals("") && city.equals(request.getMsgRqHdr().getCity()) &&
state != null && !state.equals("") && state.equalsIgnoreCase(request.getMsgRqHdr().getstate())){
salary = salaryRslt;
break;
} else if((city == null || city.equals("")) && state != null && !state.equals("") &&
state.equalsIgnoreCase(request.getMsgRqHdr().getState())){
salaryWithState = salaryRslt;
} else if((city == null || city.equals("")) && (state == null || state.equals(""))){
salaryWithName = salaryRslt;
}
} else if((billerName == null || billerName.equals("")) && (city == null || city.equals("")) &&
(state == null || state.equals(""))){
salaryWithNoMatch = salaryRslt;
}
}
if(salary != null){
return salary;
} else if(salaryWithState != null){
salary = salaryWithState;
} else if(salaryWithName != null){
salary = salaryWithName;
} else if(salaryWithNoMatch != null){
salary = salaryWithNoMatch;
}
return salary;
}
EDIT: I dont want to use 3 extra variables: salaryWithState, salaryWithName, salaryWithNoMatch.
I want just to give the general idea how this can be implemented, so I haven't actually tested and checked if it will give you the right salary.
public BigDecimal getSalaryForBestMatch(ResultSet resultSet, PaymentSalaryInfo paymentSalaryInfo) {
Map<String, Supplier<String>> m1 = new HashMap<>();
m1.put("EMP_NAME", paymentSalaryInfo::getBillerName);
m1.put("STATE", paymentSalaryInfo::getState);
m1.put("CITY", paymentSalaryInfo::getCity);
Map<String, Supplier<String>> m2 = new HashMap<>();
m2.put("STATE", paymentSalaryInfo::getState);
m2.put("CITY", paymentSalaryInfo::getCity);
Map<String, Supplier<String>> m3 = new HashMap<>();
m3.put("CITY", paymentSalaryInfo::getCity);
Optional<String> salary = Optional.empty();
while(resultSet.next() && !salary.isPresent()) {
salary = apply(m1, resultSet);
//check salary and then apply(m2, resultSet) ....
}
return salary.isPresent() ? new BigDecimal(salary.get()) : null;
}
public Optional<String> apply(Map<String, Supplier<String>> filter, ResultSet resultSet) {
boolean allMatch = filter.entrySet().stream().allMatch(entry -> {
String value = resultSet.getString(entry.getKey());
return value != null && value.equals(entry.getValue().get());
});
return allMatch ? Optional.of(resultSet.getString("salary")) : Optional.empty();
}
I have written the same logic in a different way with using arrays. If your environment can afford to use arrays, you can use this code. But I have not tested the code.
private static BigDecimal getsalaryForBestMatch(ResultSet results, EmployeeRq request) throws Exception{
BigDecimal salary = null;
int matchCount = 0;
String rBillerName = request.getBillPaymentsalaryCalculateInfo().getBillerName();
String rCity = request.getMsgRqHdr().getCity();
String rState = request.getMsgRqHdr().getstate();
String [] truthArray = new String[] {rBillerName, rCity, rState};
while (results.next()) {
String billerName = results.getString("EMP_NAME") != null ? results.getString("EMP_NAME").trim() : null;
String city = results.getString("CITY") != null ? results.getString("CITY").trim() : null;
String state = results.getString("STATE") != null ? results.getString("STATE").trim() : null;
BigDecimal salaryRslt = results.getString("SALARY") != null ? BigDecimal.valueOf(results.getDouble("SALARY")): null;
String [] testArray = new String[] {billerName, city, state};
int localMatchCount = 0;
for(int i = 0; i < testArray.length; i++) {
if(testArray[i] != null && testArray[i].equals(truthArray[i]))
localMatchCount++;
else {
break;
}
}
if(localMatchCount >= matchCount){
matchCount = localMatchCount;
salary = salaryRslt;
}
}
return salary;
}