Hello I am trying to avoid creating object by using normal array to store 2 key value, but it does not seems to work.
May I know is there any solution to avoid that one object creation or am I just trying too hard?
Forgotten to ADD:
1) I know why it does not work... I won't be implementing the equals() and hashcode() for key if I don't.
2) Basically I am trying to avoid 1 object creation when retrieving the key.
Usually in the service class there will be a method
public void get(String key1, String key2){
return keyMap.get(new Key(key1,key2)); <>>avoiding the new Key()
}
BREAK LINE
import java.util.HashMap;
import java.util.Map;
public class ArrayMap {
/**
* #param args
*/
public static void main(String[] args) {
/*start A Possible to get this to work? */
Map<String[], String> arrMap = new HashMap<>();
arrMap.put(new String[] { "hello", "hi" }, "hello motto");
System.out.println(arrMap);
System.out.println(arrMap.get(new String[] { "hello", "hi" })); // print
// null
/* end of A */
/*Start of B: Reason: to avoid this */
Map<Key, String> keyMap = new HashMap<Key, String>();
keyMap.put(new Key("hello", "hi"), "hello motto"); // I wish to avoid one object creation
System.out.println(keyMap.get(new Key("hello", "hi"))); // print
// "hello motto"
/*End of B: Reason: to avoid this */
}
}
class Key {
private final String key1;
private final String key2;
public Key(String key1, String key2) {
this.key1 = key1;
this.key2 = key2;
}
public String getKey1() {
return key1;
}
public String getKey2() {
return key2;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key1 == null) ? 0 : key1.hashCode());
result = prime * result + ((key2 == null) ? 0 : key2.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Key other = (Key) obj;
if (key1 == null) {
if (other.key1 != null)
return false;
} else if (!key1.equals(other.key1))
return false;
if (key2 == null) {
if (other.key2 != null)
return false;
} else if (!key2.equals(other.key2))
return false;
return true;
}
}
There are a couple of problems with this approach.
1. You can't override the equals()/hashcode() methods for arrays - This is an issue because the HashMap won't be able to correctly determine whether it has looked up the right key or not.
2. You're still creating new objects every time you want to create a key. Arrays are objects - you save nothing by creating a new one every time. Might as well use your original Key object.
Possible Solution
So I'm going to assume the reason why you want to avoid creating a new object every time is because you're going to be calling get(key) on that HashMap a LOT. If that's the case, why not create a mutable Key instance that remains internal to your ArrayMap object. Every time you want to key on two Strings, simply set them in your mutable Key instance and use that mutable instance for the lookup. Then you don't create a new Key every time you want to look up a value for a pair of Strings.
May I know is there any solution to avoid that one object creation or am I just trying too hard?
If you are unsure whether you're trying too hard, then you probably are. Based on the information you've given us, this looks like premature optimization.
A couple of pertinent points:
Object creation is not expensive, especially if the object is short-lived.
The only way to be sure of the performance characteristics of a piece of software is through profiling.
In these lines
arrMap.put(new String[] { "hello", "hi" }, "hello motto");
System.out.println(arrMap);
System.out.println(arrMap.get(new String[] { "hello", "hi" })); // print
// null
you use a String[] as a key. That object doesn't have a custom equals() method like the one you have in your Key class where you can compare the contents. So when you try to do map.get() passing in a new String[] (but with the same content), it won't find anything because it's not the same object.
What you might want to do is this
String[] array = new String[] { "hello", "hi" };
arrMap.put(array , "hello motto");
System.out.println(arrMap);
System.out.println(arrMap.get(array)); // print hello motto
You really shouldn't use array types as keys to Maps.
In java, array1.equals(array2) only if array1 == array2, i.e. they are the same exact instance in memory. So that causes the Map to view them as separate keys. You're better off going with your Key class for the map's key
Related
We have a special logic for a "equals" method, like below:
We use idTypeA/idTypeB/idTypeC as key to compare, only if they are not empty for both objects. How to correctly override the hashCode method, accordingly this kind of equals logic ?
public class Student {
private String idTypeA;
private String idTypeB;
private String idTypeC;
public Student(String idTypeA, String idTypeB, String idTypeC) {
this.idTypeA = idTypeA;
this.idTypeB = idTypeB;
this.idTypeC = idTypeC;
}
#Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Student)){
return false;
}
Student keyIn = (Student) obj;
if ((!idTypeA.isEmpty()) && (!keyIn.idTypeA.isEmpty())) {
if (0 == idTypeA.compareToIgnoreCase(keyIn.idTypeA)) {
return true;
} else {
return false;
}
}
if ((!idTypeB.isEmpty()) && (!keyIn.idTypeB.isEmpty())) {
if (0 == idTypeB.compareToIgnoreCase(keyIn.idTypeB)) {
return true;
} else {
return false;
}
}
if ((!idTypeC.isEmpty()) && (!keyIn.idTypeC.isEmpty())) {
if (0 == idTypeC.compareToIgnoreCase(keyIn.idTypeC)) {
return true;
} else {
return false;
}
}
return false;
}
#Override
public int hashCode() {
// TODO ??? How to correctly override the hashCode method
// according to above equals method
return super.hashCode();
}
}
Thanks,
Frank
This equals method isn't transitive:
("a", "", "c") is equal to ("a", "b", "c")
("a", "b", "c") is equal to ("", "b", "d")
But ("a", "", "c") isn't equal to ("", "b", "d")
This means that your equals method doesn't meet the requirements on overriding equals().
It's fine to have application-specific notions of "equality" (a reasonably common one is "almost equal to"). But you just can't shoe-horn these into Java's specific notions of equals() (and hashCode) because code relying on the documented properties of equals (reflexivity, symmetry, transitivity etc) won't behave as expected.
As such, it doesn't really make sense to ask how to implement hashCode(), because it can't meet the requirements of that either.
The way I see it you have two basic options:
Return 0 if one of the keys is empty, and a regular hash code based on the key values otherwise.
This might be inefficient if you expect to have a lot of empty keys, as most lookups will have to defer to equals() to verify that a match has been found.
Return a random hash code if one of the keys is empty, and a regular hash code based on the key values otherwise.
This is inefficient because generating random values is expensive in terms of processing, but lookups will be fast.
So it's a bit of a catch 22. Both approaches are valid and don't violate the equals()/hashCode() contract, but they come with different performance trade-offs.
Based on your original question and the comments discussed under it and Robby's answer, I would reformulate your question as follows: How to manage a set of objects having 3 different keys used for equality matching, in an optimal way? So I will be answering to this question, in case someone will blame for not answering to the original one.
My suggestion is to make a wrapper class to hold 3 hash-maps and compare your Student objects with those 3 String keys with its help.
Here's my implementation, with an assumption that your keys are public fields instead of being private as mentioned in your original post.
import java.util.HashMap;
import java.util.List;
public class StudentX {
private HashMap<String, Student> map1 = new HashMap();
private HashMap<String, Student> map2 = new HashMap();
private HashMap<String, Student> map3 = new HashMap();
public void addStudent(Student s) {
map1.put(s.idTypeA, s);
map2.put(s.idTypeB, s);
map3.put(s.idTypeC, s);
}
public void bulkAddStudents(List<Student> allStudents) {
for (Student s : allStudents) {
addStudent(s);
}
}
public Student findStudent(Student s) {
Student result;
result = map1.get(s.idTypeA);
if (result != null) {
return result;
}
result = map2.get(s.idTypeB);
if (result != null) {
return result;
}
result = map3.get(s.idTypeC);
if (result != null) {
return result;
}
return null;
}
}
and here's some code for testing it, or to understand how I imagined using it:
List<Student> students = new LinkedList();
students.add(new Student("spades", "hearts", "diamonds"));
students.add(new Student("hearts", "diamonds", "clubs"));
students.add(new Student("spades", "diamonds", "hearts"));
StudentX group = new StudentX();
group.bulkAddStudents(students);
Student noob = new Student("diamonds", "clubs", "hearts"); //try different values for proper testing
if (group.findStudent(noob) != null) {
System.out.println("Student found");
} else {
System.out.println("Student not found");
}
Hope this was clear what I meant with a wrapper class to hold 3 hash-maps for optimal searching and comparison.
CompareObj is a class in java It consists of three attributes String rowKey, Integer hitCount, Long recency
public CompareObj(String string, Integer i) {
this.rowKey = string;
this.hitCount = i%10;
this.recency= (Long) i*1000;
}
Now I created a treeMap
Comparator<CompareObj> comp1 = (e1,e2) -> e1.getHitCount().compareTo(e2.getHitCount());
Comparator<CompareObj> comp2 = (e1,e2) -> e2.getRecency().compareTo(e1.getRecency());
Comparator<CompareObj> result = comp1.thenComparing(comp2);
TreeMap<CompareObj, CompareObj> tM = new TreeMap<CompareObj, CompareObj>(result);
for(int i=0;i<=1000;i++)
{
CompareObj cO = new CompareObj("A"+i, i);
tM.put(cO,cO);
}
for(int i=0;i<=1000;i++)
{
CompareObj cO = new CompareObj("A"+i, i);
CompareObj values = tM.get(cO);
System.out.println(values.getRowKey()); // Line 28: get Null Pointer Exception
}
Also I overide hashCode and Equals. Still I get nullponter exception.
#Override
public int hashCode() {
return Objects.hash(getRowKey());
}
#Override
public boolean equals(Object obj) {
if(this==obj) return true;
if(!(obj instanceof CompareObj)) return false;
CompareObj compareObj = (CompareObj) obj;
return Objects.equals(this.getRowKey(), compareObj.getRowKey());
}
Here when I try to retrive value from treemap back I get Null Pointer exception in the line mentioned. How to solve this
If I want to implement comapareTo() of Comaprable interface, how should I implement if there is multiple sort conditions.
The first thing to understand, is the NullPointerException. If you get that exception on the exact line
System.out.println(values.getRowKey());
then either System.out or values is null. Since we can preclude System.out being null, it’s the values variable, which contains the result of get and can be null if the lookup failed.
Since you are initializing the TreeMap with a custom Comparator, that Comparatordetermines equality. Your Comparator is based on the properties getHitCount() and getRecency() which must match, which implies that when the lookup fails, the map doesn’t contain an object having the same values as reported by these two methods.
You show that you construct objects with the same values but not the code of these getters. There must be an inconsistency. As Misha pointed out, your posted code can’t be the code you have ran when getting the exception, therefore we can’t help you further (unless you post the real code you ran).
I have 2 classes.
public class klass1 {
String bir;
String myID;
klass1(String bir, String myID)
{
this.bir=bir;
this.myID=myID;
}
}
.
import java.util.*;
public class dd {
public static void main(String[] args) {
ArrayList<Object> ar=new ArrayList();
ar.add(new klass1("wer","32"));
ar.add(new klass1("das","23"));
ar.add(new klass1("vz","45"));
ar.add(new klass1("yte","12"));
ar.add(new klass1("rwwer","43"));
ar.remove(new klass1("vz","45"));//it's not worked!!!
System.out.println(ar.size());
}
}
What I want is removing or getting an object from array list with object's second attribute. How can I do that? Is there an easy way for it?
Just implement the equals method in the class Klass1.
public class Klass1 {
String bir;
String myID;
Klass1(String bir, String myID)
{
this.bir=bir;
this.myID=myID;
}
public boolean equals(Object o){
if(o instanceof Klass1)
return ((Klass1)o).myID.equals(myID);
else
return false;
}
}
Its because you are trying to delete a new object which isnt in the arraylist. When you use new klass1("vz","45") you are creating a new instance of this class which isnt in the arraylist.
What the system does internally is to compare those classes using equals. Why this doesn't work is explained in the following code:
Object o1 = new Object();
Object o2 = new Object();
System.out.println(o1 == o2); // false, obviously
System.out.println(o1.equals(o2)); // false
System.out.println(o1); // java.lang.Object#17046822
System.out.println(o2); // java.lang.Object#22509bfc
You can tell by the number following the # that these objects have a different hash values, and this is what the equals function of Object does check.
This is relevant for your klass, because unless you overwrite equals, you will use the equals of Object. And if you implement equals you should always implement hashcode as well. Because both tell you something about whether or not two objects are the "same", and if the one says something else than the other, some part of your code might get confused.
How to properly implement equals for your class:
#Override
public int hashCode() {
int hash = 7;
hash = 17 * hash + Objects.hashCode(this.bir);
hash = 17 * hash + Objects.hashCode(this.myID);
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final klass1 other = (klass1) obj;
if (!Objects.equals(this.bir, other.bir)) {
return false;
}
if (!Objects.equals(this.myID, other.myID)) {
return false;
}
return true;
}
This can be done in most IDEs btw with a shortcut (i.E. alt-insert in Netbeans). Note that I did this in Java 7 using Objects. If you are in Java 6, you need to manually type(a == b) || (a != null && a.equals(b)); with the appropriate objects to compare.
Creating a proper hashcode is not always trivial, for more complex objects you might want to read a bit about hashcodes first. For simple objects: multiply primes with something.
The equals method is usually trivial, it is just important to first check for null and for class equality. This is often forgotten by programmers and a common source for NullPointerExceptions and ClassCastExceptions.
I am currently throwing an obfuscation program for school homework together. I am trying to make the program read a file and then create a new file that replaces each letter in the file with some a corresponding value that I pull from a HashMap. I set up a whole bunch of keys and values, but later in the process of writing the new file I try to check if the map contains a key before appending to the new String. The characters that are checked are in fact in the file that I am reading from to test, and the file is read correctly. Yet it fails with the encryptionDict.containsKey() (my hashmap) method.
I hope some Java expert can help me figure this out! I'm pretty clueless, I'm more of a C and D guy. The only thought that struck me was that it would be something like with Strings where "foo" != "foo". But chars aren't objects.
The code is in a pastebin below, the key parts to look at is the class constructor, the method encrypt, and the method initDictionary, also can someone tell me why HashMap<char, String> is invalid, is it because I have to use an object?
The code: http://pastebin.com/NcHTHPfw
private HashMap<char [], String> initDictionary() {
HashMap<char [], String> d = new HashMap<char [], String>();
d.put("a".toCharArray(), "!\"#¤");
d.put("b".toCharArray(), "¤#\"!");
d.put("c".toCharArray(), "\"#¤%");
d.put("d".toCharArray(), "%¤#\"");
d.put("e".toCharArray(), "#¤%&");
d.put("f".toCharArray(), "&%¤#");
d.put("g".toCharArray(), "¤%&/");
d.put("h".toCharArray(), "/&%¤");
d.put("i".toCharArray(), "%&/(");
d.put("j".toCharArray(), "(/&%");
d.put("k".toCharArray(), "&/()");
d.put("l".toCharArray(), ")(/&");
d.put("m".toCharArray(), "/()=");
d.put("n".toCharArray(), "=)(/");
d.put("o".toCharArray(), "()=?");
d.put("p".toCharArray(), "?=)(");
d.put("q".toCharArray(), ")=?!");
d.put("r".toCharArray(), "!?=)");
d.put("s".toCharArray(), "=?!\"");
d.put("t".toCharArray(), "\"!?=");
d.put("u".toCharArray(), "?!\"#");
d.put("v".toCharArray(), "#\"!?");
d.put("w".toCharArray(), ";:*^");
d.put("x".toCharArray(), "^*:;");
d.put("y".toCharArray(), ":*^>");
d.put("z".toCharArray(), ">^*:");
// etc.
This is the problematic bit. You can't use arrays as hash keys in Java, as Array does not override the equals() and hashCode() methods.
The hashCode is used to find the correct bucket that contains the object you are looking for, and the equals() method compares the actual Objects. To make use of a HashMap, you need to override both of these methods in a sensible way, which you can't as array classes are final. So the only thing you could do if you absolutely insist on using char arrays is to use a wrapper class as key that has a char array.
Something like this:
public class Key {
private final char[] array;
public Key(final String string) {
this(string.toCharArray());
}
public Key(final char[] array) {
this.array = array;
}
public char[] getArray() {
return array;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (!Arrays.equals(array, key.array)) return false;
return true;
}
#Override
public int hashCode() {
return array != null ? Arrays.hashCode(array) : 0;
}
}
Now you can have a Map<Key, String> and bould a Key Object from either a String or a char[]
The problem is that all char[] are unique, regardless of their contents. It appears you really want to be using a String. Normally, you can write such a program without using a char[]
Instead you can write
private Map<Character, String> initDictionary() {
Map<Character, String> d = new HashMap<Character, String>();
d.put('a', "!\"#¤");
// etc
Don't use arrays as key, they are not comparable, so when it compares the value does not match
You are using a HashMap<char [], String>. This doesn't work as .equals and .hashCode are not defined on arrays. This simple test program verifiesthis:
public static void main(String[] args) {
char[] a = String.valueOf('a').toCharArray();
char[] b = String.valueOf('a').toCharArray();
System.out.println(a.hashCode() == b.hashCode());
}
You could use a Map<Character, String> for your purposes. Another solution would be to use an array String[MAX_INDEX] which is indexed by filecontent.charAt(index) - this may be even simpler if MAX_INDEX is small (like 'z') in your case.
I have a code to return an arrayList with the duplicates of an ArrayList
but seems it's not working, I am comparing all items in the array...
public ArrayList<ObjectList> duplicates(ArrayList<ObjectList> someObjectsList) {
ArrayList<ObjectList> ret = new ArrayList<ObjectList>();
for ( ObjectList aSomeObjectsList: someObjectsList) {
String field1 = aSomeObjectsList.get1();
String field2 = aSomeObjectsList.get2();
String field3 = aSomeObjectsList.get3();
String field4 = aSomeObjectsList.get4();
for (ObjectList someObject : ret) {
if (
field1.trim().equals(someObject.get1())&&
field2.trim().equals(someObject.get2())&&
field3.trim().equals(someObject.get3())&&
field4.trim().equals(someObject.get4())
){
ret.add(aSomeObjectsList);
}
}
}
return ret;
}
But i guess I am doing something wrong because it doesn't return anything, and I know it has duplictates under this 4 field criteria
Thanks in advance
for (Object someObject : ret) {
if (
field1.trim().equals(someObject.get1())&&
field2.trim().equals(someObject.get2())&&
field3.trim().equals(someObject.get3())&&
field4.trim().equals(someObject.get4())
){
ret.add(aSomeObjectsList);
}
}
The above loop wouldn't work, since it has the size of zero.
Here you go,
public Set<ObjectList> duplicates(ArrayList<ObjectList> someObjectsList) {
Set<ObjectList> originals = new HashSet<ObjectList>();
Set<ObjectList> duplicates = new HashSet<ObjectList>();
for ( ObjectList aSomeObjectsList: someObjectsList) {
boolean added = originals.add(aSomeObjectsList);
if(!added){
duplicates.add(aSomeObjectsList);
}
}
return duplicates;
}
This would work, provided your ObjectList class have the correct implementation of hashCode() and equals() methods.
Disclaimer: This implementation will not provide the information about how many times a particular object was duplicated in the provided list. It will just tell you that a particular object was duplicated. I assumed that that was your real intention. If you wanna count, how many times, you have to modify the code accordingly.
Hint/Suggestion: You should override the equals() method and place your field equality check in there instead, once and for all.
This shouldn't compile - if aSomeObjectsList is an Object then it doesn't have methods get1(), get2(), etc.
Your logic won't work because you aren't checking each element in your input List against the other elements in the input List; rather, you're trying to check the return List.
Also, this is not a really efficient way to check for duplicates in a collection. A better way would be to use a HashMap, where you could check set membership in roughly constant time. If you have to use a List, then sort it first (assuming your objects have a natural ordering) and check adjacent members for equality.
Barring those two, just use List.contains().
Here's a way you can do this. I have defined a basic class ObjectList that shows a way to implement equals and hashCode. Note that this assumes that all the internal variables are non-null. If these variables can contain null then you will need to check for that when computing the equals/hashCode. Also, the objects in this class must also themselves properly implement equals/hashCode.
public class ObjectList {
private int h;
private Object obj1;
private Object obj2;
private Object obj3;
private Object obj4;
#Override
public boolean equals(final Object o) {
if (!(o instanceof ObjectList))
return false;
final ObjectList that = (ObjectList) o;
return that.obj1.equals(obj1) && that.obj2.equals(obj2)
&& that.obj3.equals(obj3) && that.obj4.equals(obj4);
}
#Override
public int hashCode() {
// caches the hashcode since it could be costly to recompute every time
// but this assumes that your object is essentially immutable
// (which it should be if you are using equals/hashCode. If this is not
// true and you want to just temporarily use this when doing the duplicate
// test, move the h variable definition from the object level to this method
// and remove this if statement.
if (h != 0)
return h;
h = obj1.hashCode();
h = h * 31 + obj2.hashCode();
h = h * 31 + obj3.hashCode();
h = h * 31 + obj4.hashCode();
return h;
}
}
public Collection<ObjectList> duplicates(
final Collection<ObjectList> someObjectsList) {
final Set<ObjectList> unique = new HashSet<ObjectList>(someObjectsList);
final ArrayList<ObjectList> ret = new ArrayList<ObjectList>(someObjectsList);
for (final ObjectList o : unique) {
ret.remove(o);
}
// The ret list now contains the duplicate instances; instances
// with more than two occurrences will occur multiple times still in
// this list.
return ret;
// If you want a list of unique duplicate instances then, comment out the above
// return and uncomment this one.
// return new HashSet<ObjectList>(ret);
}
Using Collection<ObjectList> is better, if you can do that, for both the parameter and returned value so you can vary the implementations (ArrayList, Set, etc).