Unexpected results for TreeMap with inconsistent compareTO and equals.

If two objects are considered equal by compareTo() and not by equals() or vice-versa, then TreeSet and TreeMap may produce different output.

public class Employee implements Comparable <<>>
{
int empNo;
String empName;

@Override
public int hashCode() { return empNo; }

@Override
public boolean equals(Object obj)
{
Employee other = (Employee) obj;
if (empNo == other.empNo && empName.equals(other.empName))
return true;
return false;
}

@Override
public int compareTo(Employee other)
{
return (empNo – other.empNo);
}

@Override
public String toString() { return “[” + empNo + “, ” + empName + “]”; }

public Employee(int empNo, String empName)
{
this.empNo = empNo;
this.empName = empName;
}

public static void main(String[] args)
{
Employee employees[] = new Employee[5];
employees[0] = new Employee(1, “Grima”);
employees[1] = new Employee(2, “Krishma”);
employees[2] = new Employee(2, “Krishma”);
employees[3] = new Employee(4, “Krishma”);
employees[4] = new Employee(2, “Gurav”);

HashSet set = new HashSet();
set.addAll(Arrays.asList(employees));
System.out.println(set.toString());

TreeSet treeSet = new TreeSet();
treeSet.addAll(Arrays.asList(employees));
System.out.println(treeSet.toString());
}
}

[[1, Grima], [2, Krishma], [2, Gurav], [4, Krishma]] // HashSet
[[1, Grima], [2, Krishma], [4, Krishma]] //TreeSet

Now as per the “equals” method in Employee two employee objects will be equal if their empNo and empName are equal while “compareTo” method says that two employee objects will be equal if two employee objects have same empNo.

So the output of the hashset is expected, employees with different empNo. and empName are added to set, but if we look at output of TreeSet, employees only with different empNo are added.

Thus we can conclude that TreeMap does not use hashCode() or equals() and it uses the compareTo() method.
This could result in a serious application error where two objects considered equal by equals() return different values from TreeMap.

Summary: All 3 methods – compareTo, equals and hashCode should be consistent with each other.

Advertisements

Java 8 streams filter

package com.test;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Customer {

private String firstName;

private String lastName;

private int orderNo;

public Customer(String firstName, String lastName, int orderNo) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.orderNo = orderNo;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public int getOrderNo() {
return orderNo;
}

public void setOrderNo(int orderNo) {
this.orderNo = orderNo;
}

public static void main(String[] args) {
List customers=new ArrayList();
customers.add(new Customer(“garima”,”pandey”,1));
customers.add(new Customer(“ashu”,”dikshit”,2));
customers.add(new Customer(“gaurav”,”dixit”,3));

//1)Filter and foreach
//The following basically streams the collection and on a stream you can have multiple operations performed
//The stream has three parts, first is the source which in our case is customers collection, second part is all the operations that need to be performed on that stream, so below is a filter operation that is performed on the stream and finally we have a terminal operation that is the end condition, below for each is the end condition

customers.stream().filter(c->c.getLastName().startsWith("d")).forEach(c->System.out.println(c.getFirstName()));// output-->ashu,gaurav

//2)Filter and collect
//stream.filter() lets you to filter a list and collect() to convert stream into a list.

List collected=customers.stream().filter(c->c.getFirstName().startsWith("g")).collect(Collectors.toList());
collected.forEach(c->System.out.println(c.getFirstName()));//output-->garima,gaurav

//3)filter and .findAny().orElse(null)

Customer cust=customers.stream().filter(c->c.getFirstName().contains("garima")).findAny().orElse(null);
System.out.println(cust.getFirstName());//output-->garima

//4)filter and .findAny().orElse(null)

Customer custNull=customers.stream().filter(c->c.getFirstName().contains("test")).findAny().orElse(null);
System.out.println(custNull);//output-->null

//5)filter and map
//map function can be used to perform some operation on all of it’s elements.

customers.stream().filter(c->c.getFirstName().startsWith("g")).map(Customer::getFirstName).collect(Collectors.toList()).forEach(System.out::println);//output->garima,gaurav

}
}

String,String constant pool in java

String grima = “grima”;
String gri = “grima”;
if (grima == gri)
System.out.println(“grima == gri”); // The message is displayed

Both grima,grima references refer to same “grima” in string constant pool.

String pndey = “pandey”;
String pan = “pande”;
pan = pan + “y”; // The value for pan will be resolved during runtime.
if (pndey == pan)
System.out.println(“pndey == pan”); // The 2 references are different

Now in the second case if i change

pan = pan + “y”;this line to –>pan=”pande” +”y”, System.out.println(“pndey == pan”); message will be displayed because the compiler resolves the literal during compilation time itself.

Now as per java docs on string:

•Literal strings within the same class (§8 (Classes)) in the same package (§7 (Packages)) represent references to the same String object (§4.3.1).

•Literal strings within different classes in the same package represent references to the same String object.

•Literal strings within different classes in different packages likewise represent references to the same String object.

•Strings computed by constant expressions (§15.28) are computed at compile time and then treated as if they were literals.

•Strings computed by concatenation at run time are newly created and therefore distinct.

•The result of explicitly interning a computed string is the same string as any pre-existing literal string with the same contents.

Runtime exception over checked exception in java

When should you create a custom runtime exception??

You can create unchecked exceptions by subclassing RuntimeException whenever your method needs to signal a programming error.

For example there is some method which takes an int argument,performs some operation using that argument and returns. Now it is always a good practice to throw an IllegalArgumentException in the beginning of the method for method arguments that shouldnt be null , so that later you dont have to traverse the method to look what went wrong. Now this exception IllegalArgumentException should be a runtime exception as this is a programming error , user cannot do anything about it and you dont want to write a try catch in every other method where you throw this exception .

Business exceptions should be declared as checked exception as the user can always come out of that exception , like suppose a user tries to book a train ticket and somehow the booking fails in that scenario you wont give user an application error page but will ask to try after some time, such exceptions can be checked exceptions.

Kindly let me know your views on this by posting comments. Thanks 🙂

Hashcode() and equals() for StringBuilder and StringBuffer

StringBuilder and StringBuffer classes doesnt override equals and hashcode() methods in java.

As String class is immutable in java, mutable StringBuilder and StringBuffer were introduced in java to contruct Strings.

equals() and hashcode() method is mainly overridden to support collections so that if i use a map like hashmap i can retrieve my object that i saved using correct key on basis of hashcode() and equals() method.

A key is preferred to be a mutable one so that anytime i can recover my value for that key, now stringbuilder and stringbuffer being mutable are not preferred as keys and hence there is no point of overriding equals() and hashcode() method for them as they shouldnt be used as a candidate for key in collections.

String concatenation in java

String concatenation using + operator::


String sunita="sunita"
String pndey="pandey";
String sunitapandey=sunita+pndey;

Under the covers what does a compiler do:


String sunitapandey=new StringBuilder(String.valueOf(sunita)).append(pndey).toString();

ByteCode for the same—-

0 ldc [16]
2 astore_1 [sunita]
3 ldc [18]
5 astore_2 [pndey]
6 new java.lang.StringBuilder [20]
9 dup
10 aload_1 [sunita]
11 invokestatic java.lang.String.valueOf(java.lang.Object) : java.lang.String [22]
14 invokespecial java.lang.StringBuilder(java.lang.String) [28]
17 aload_2 [pndey]
18 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [31]
21 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [35]
24 astore_3 [sunitapandey]

//.toString method in Stringbuilder

@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}

the above concatenation will create a new StringBuilder with one char[] buffer with 16 size and at last .toString() method creates a new String object with a copy of StringBuilder buffer.

Suppose you are concatenating using + operator in a loop then imagine the number of temporary objects will be created.

so instead of doing like


String concat="";
for(int i=0;i<100;i++){
concat=concat+"abc";
}

one can efficiently do like

StringBuilder concat=new StringBuilder();
for(inti=0;i<100;i++){
concat.append("abc");
}
String con=concat.toString();

However ,one point to be noted here is , if i use “+” operator for a concatenation and if the result is resolved during compilation time itself then a new StringBuilder instance is not created , stringBuilder conversion is only done for those strings whose value is computed during runtime . Look at following example and their generated bytecode.


String grima="garima"+"pandey";

Bytecode for the same—
0 ldc [16]
2 astore_1 [grima]
3 return
Line numbers:
[pc: 0, line: 8]
[pc: 3, line: 9]
Local variable table:
[pc: 0, pc: 4] local: n index: 0 type: java.lang.String[]
[pc: 3, pc: 4] local: grima index: 1 type: java.lang.String

 Note–Whenever possible prefer .append(char[] str) or .append(char c) methods of StringBuilder over .append(String str) method of StringBuilder as its fast.