How to update HashMap key from value in a class? - java

I am trying to dynamically update the keys in a HashMap.
I have created an instance of a class and set the key to get the value from the class.
I am trying to get the value of the key to change when I update the value in the class. The program I am trying to do this for has multiple large hashmaps. However, I have simplified it to the example below.
Main class
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
HashMap<Integer, String> hashMap = new HashMap<>();
OtherClass otherClass = new OtherClass(5);
hashMap.put(otherClass.getId(), "a string");
otherClass.setId(0); // update value in class
System.out.println(hashMap.keySet()); // outputs 5 not 0
}
}
Other class
class OtherClass {
int id;
OtherClass (int id) {
this.id = id;
}
void setId(int id) {
this.id = id;
}
int getId() {
return id;
}
}
When I update the value in the class the key in the HashMap does not change.
Is what I'm trying to do even possible and if not how could I achieve this?

If you want the Map to auto-update when the id of an OtherClass object is modified, then you need to write the code for that yourself.
Unless the map and the object is tightly coupled, you should keep the logic decoupled, e.g. by implementing property change tracking.
I would recommend building it around the PropertyChangeSupport class in the Java Runtime Library.
OtherClass
First you need to enable property change tracking.
I added name property to improve test code output at end of this answer.
public final class OtherClass {
private final transient PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private int id;
private String name;
public OtherClass(int id, String name) {
this.id = id;
this.name = name;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
this.pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
this.pcs.removePropertyChangeListener(listener);
}
public int getId() {
return this.id;
}
public void setId(int id) {
int oldId = this.id;
this.id = id;
this.pcs.firePropertyChange("id", oldId, id);
}
public String getName() {
return this.name;
}
public void setName(String name) {
String oldName = this.name;
this.name = name;
this.pcs.firePropertyChange("name", oldName, name);
}
#Override
public String toString() {
return "OtherClass[" + this.id + ", " + this.name + "]";
}
}
OtherMap
Next you need to encapsulate the Map so the property change listener can be correctly handled.
To prevent memory leaks, it's important to clear() the OtherMap when it is no longer needed, otherwise a reference to a single OtherMap object that is in the OtherMap will keep the map and all the objects in the map alive in memory. To help with that, I made the object AutoCloseable, so it could be used with a try-with-resources statement, and to make code analyzers help highlight the need to close/clear the map.
final class OtherMap implements AutoCloseable {
private final PropertyChangeListener listener = this::onPropertyChange;
private Map<Integer, OtherClass> map = new HashMap<>();
public OtherMap() {
}
public Set<Integer> keys() {
return Collections.unmodifiableSet(this.map.keySet());
}
public Collection<OtherClass> values() {
return Collections.unmodifiableCollection(this.map.values());
}
public OtherClass get(int id) {
return this.map.get(id);
}
public OtherClass add(OtherClass other) {
OtherClass prev = this.map.put(other.getId(), other);
if (prev != null)
prev.removePropertyChangeListener(this.listener);
other.addPropertyChangeListener(this.listener);
return prev;
}
public OtherClass remove(int id) {
OtherClass other = this.map.remove(id);
if (other != null)
other.removePropertyChangeListener(this.listener);
return other;
}
public void clear() {
this.map.values().forEach(other -> other.removePropertyChangeListener(this.listener));
this.map.clear();
}
private void onPropertyChange(PropertyChangeEvent evt) {
if (! "id".equals(evt.getPropertyName()))
return;
Integer oldId = (Integer) evt.getOldValue();
Integer newId = (Integer) evt.getNewValue();
if (oldId.equals(newId))
return;
OtherClass other = (OtherClass) evt.getSource();
if (this.map.putIfAbsent(newId, other) != null)
throw new IllegalStateException("Duplicate key");
if (! this.map.remove(oldId, other)) {
this.map.remove(newId);
throw new IllegalStateException();
}
}
#Override
public String toString() {
return this.map.toString();
}
#Override
public void close() {
clear();
}
}
Test
OtherClass eeny = new OtherClass(3, "Eeny");
OtherClass meeny = new OtherClass(5, "Meeny");
OtherClass miny = new OtherClass(7, "Miny");
OtherClass moe = new OtherClass(9, "Moe");
OtherMap otherMap = new OtherMap();
otherMap.add(eeny);
otherMap.add(meeny);
otherMap.add(miny);
otherMap.add(moe);
System.out.println("Before: " + otherMap);
meeny.setId(2);
otherMap.remove(miny.getId());
miny.setId(4);
System.out.println("After: " + otherMap);
Output
Before: {3=OtherClass[3, Eeny], 5=OtherClass[5, Meeny], 7=OtherClass[7, Miny], 9=OtherClass[9, Moe]}
After: {2=OtherClass[2, Meeny], 3=OtherClass[3, Eeny], 9=OtherClass[9, Moe]}

Related

Thread safe Builder Pattern

I want to make my Builder pattern as Thread safe But facing issues in that, below is my code:
// Server Side Code
final class Student {
// final instance fields
private final int id;
private final String name;
private final String address;
public Student(Builder builder)
{
this.id = builder.id;
this.name = builder.name;
this.address = builder.address;
}
// Static class Builder
public static class Builder {
/// instance fields
private int id;
private String name;
private String address;
public static Builder newInstance()
{
return new Builder();
}
private Builder() {}
// Setter methods
public Builder setId(int id)
{
this.id = id;
return this;
}
public Builder setName(String name)
{
this.name = name;
return this;
}
public Builder setAddress(String address)
{
this.address = address;
return this;
}
// build method to deal with outer class
// to return outer instance
public Student build()
{
return new Student(this);
}
}
#Override
public String toString()
{
return "id = " + this.id + ", name = " + this.name +
", address = " + this.address;
}
}
----------
There is another class named StudentReceiver.java in which I am using multithreading:
class StudentReceiver {
// volatile student instance to ensure visibility
// of shared reference to immutable objects
private volatile Student student;
public StudentReceiver() throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
public void run() {
student = Student.Builder.newInstance().setId(1).setName("Ram").setAddress("Noida").build();
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
student = Student.Builder.newInstance().setId(2).setName("Shyam").setAddress("Delhi").build();
}
});
t1.start();
t2.start();
//t1.join();
//t2.join();
}
public Student getStudent() {
return student;
}
}
----------
Main class is below from where I am calling these methods:
//Driver class
public class BuilderDemo {
public static void main(String args[]) throws InterruptedException
{
for(int i=0; i<10;i++)
{
StudentReceiver sr = new StudentReceiver();
System.out.println(sr.getStudent());
}
}
}
----------
The output I am getting is like below:
null
null
null
null
null
null
null
null
id = 1, name = Ram, address = Noida
null
Why I am getting null here??
May anyone explain and How to make Builder Pattern thread safe so that it can be used in multithreaaded environment.
Your Builder Pattern is not the problem here. The Constructor of StudentReceiver is.
Starting a Thread inside it without joing it there will lead to the object being assigned, possibly and probably before the Thread even started. So the student Field will not be set for quite some time. So much time in fact, that executing the System.out.println(sr.getStudent()); line right after the constructor will (very probably) receive null from getStundent().
The fix would be to either:
Not use a separate Thread in the Constructor.
Or join the thread inside the Constructor ( which somewhat defeates the Thread's purpose ).
And the Builder class should not be static.
Here is an example of what I'd do:
public interface IBuilder
{
IBuilder setId( int id );
// ...
Student build();
}
final class Student {
// final instance fields
private final int id;
// + other fields - left out for brevity
private Student(Builder builder)
{
this.id = builder.id;
// + other fields
}
private static Object builderLock = new Object();
public static IBuilder getBuilder()
{
synchronized(builderLock)
{
return new Builder();
}
}
// Static class Builder
public class Builder implements IBuilder {
// instance fields
private int id = -1;
// ...
private Builder() {}
// Setter methods
public IBuilder setId(int id) {
this.id = id;
return this;
}
public Student build() {
return new Student(this);
}
}
}
Disclaimer: untested!

Java android realm check if objects is exist (check two )

This is my object :
public class ObjectsInGroupRealm extends RealmObject {
#PrimaryKey
private Long id;
private String name;
private String groupName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
}
And when I create a new object I want to check if object is exist this same name and this same groupName . A object name could be in few groups. This is my code how I save a objects :
public static void saveObjectsInGroup(ArrayList<String> objects, String groupName , Realm realm){
for(String object : objects){
ObjectsInGroupRealm objectsInGroupRealm = new ObjectsInGroupRealm();
Long key;
try {
key = (Long) realm.where(ObjectsInGroupRealm.class).max("id") + 1;
} catch (NullPointerException ex) {
key = 0L; // when there is no object in the database yet
}
objectsInGroupRealm.setId(key);
objectsInGroupRealm.setName(object);
objectsInGroupRealm.setGroupName(groupName);
realm.beginTransaction();
realm.copyToRealm(objectsInGroupRealm);
realm.commitTransaction();
}
}
So the easiest way is doing a query and checking if the returned Object is null:
ObjectsInGroupRealm object = realm.where(ObjectsInGroupRealm.class)
.equalTo("name", name)
.equalTo("groupName", groupName)
.findFirst();
if(object == null){
//add new object
} else {
//handle object already existing
}

Java HashMap: How to replace a own Key Object with put()-Method [duplicate]

This question already has answers here:
Setting own class as key in java Hashmap
(7 answers)
Closed 6 years ago.
lets say I have a Employee-class with Instant- and Id-Attribute:
public class Employee implements Comparable<Employee> {
private Instant workEnd;
private Integer id;
private String name;
public Instant getWorkEnd() {
return workEnd;
}
public void setWorkEnd(Instant workEnd) {
this.workEnd = workEnd;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public int compareTo(Employee employee) {
int workEndCompare = getWorkEnd().compareTo(employee.getWorkEnd());
int idCompare = getId().compareTo(employee.getId());
if (workEndCompare == 0) {
return idCompare;
} else {
return workEndCompare;
}
}
#Override
public String toString() {
return String.format("{Date: %s,Number: %d}", getWorkEnd(), getId());
}
}
As you can see each Employee-Object sorts dependent on workEnd and id.
Now I want to put these Employee-Objects as Keys in a HashMap. But I want that the HashMap replaces each Key-Employee with the put()-method if the attributes workend and id are equal. Thta means I want the normal behaviour of a HashMap but with own custom Objects as Mapkeys.
How I manage that? By implementing the equals-method?
I tried something like that, but it does not work:
#Override
public boolean equals(Object obj) {
if (obj instanceof Employee) {
Employee employee = (Employee) obj;
int workEndCompare = getWorkEnd().compareTo(employee.getWorkEnd());
int idCompare = getId().compareTo(employee.getId());
if ((idCompare + workEndCompare) == 0) {
return true;
} else {
return false;
}
} else {
return super.equals(obj);
}
}
When you implement the equals method you also need to implement the hashcode method too.
There is something called the hashcode - equals contract, both need to be implemented for your hashmap to work,
The answer here explains it well.

How to check if an array in an arraylist contains a certain value?

I have an array list which contains arrays of type String. I create the array list and add arrays to it with the following code:
List<String[]> transaction = new ArrayList<String[]>();
String[] transactionLine = new String[7];
transactionLine[0] = "0";
transactionLine[1] = "1";
//.....
transactionLine[6] = "some value";
transactionLines.add(transactionLine);
Now I want to test if one of the arrays contain a certain value. I tried it like this, but then it checks for an array and not an element of an array:
if(transactionLines.contains("some value")) {
//Do some stuff with it
}
I know this doesn't work, but I don't now how to do it otherwise. I couldn't find any post of this already on Stackoverflow (not with the logical search terms for this problem anyway).
Note: I have chosen this structure of arrays in an arraylist, because I have a fixed number of columns (as suggested in how to create dynamic two dimensional array in java?).
Any help is greatly appreciated!
#assylias suggestion to use the object oriented way is good, but his example does not tell if the list contains a transaction where one property has a certain value. This example does:
public class Test {
public static void main(final String[] args) {
final List<TransactionLine> transaction = new ArrayList<>();
transaction.add(new TransactionLine(1, "some value"));
transaction.add(new TransactionLine(2, "another value"));
transaction.add(new TransactionLine(3, "yet another value"));
System.out.println(containsName(transaction, "some value"));
System.out.println(containsName(transaction, "non-existent value"));
}
// Iterates over all transactions until a transaction is found that has the
// same name as specified in search
private static boolean containsName(final List<TransactionLine> transaction, final String search) {
for (final TransactionLine transactionLine : transaction) {
if (transactionLine.getName().equals(search)) {
return true;
}
}
return false;
}
private static class TransactionLine {
private int id;
private String name;
public TransactionLine(final int id, final String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(final int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
}
Here is an example with two classes (Transaction and TransactionLine):
Test:
public class Test {
public static void main(final String[] args) throws Exception {
final Transaction transaction = new Transaction();
transaction.add("some name");
transaction.add("another name");
transaction.add("yet another name");
System.out.println(transaction.containsName("some name"));
System.out.println(transaction.containsName("non-existent name"));
}
}
Transaction:
import java.util.ArrayList;
import java.util.List;
public class Transaction {
private final List<TransactionLine> transactionLines = new ArrayList<>();
public void add(final String name) {
final TransactionLine tl = new TransactionLine(transactionLines.size(), name);
transactionLines.add(tl);
}
public boolean containsName(final String name) {
for (final TransactionLine transactionLine : transactionLines) {
if (transactionLine.getName().equals(name)) {
return true;
}
}
return false;
}
}
TransactionLine:
public class TransactionLine {
private int id;
private String name;
public TransactionLine() {
}
public TransactionLine(final int id, final String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(final int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
The object oriented way of solving your problem would be to create a class:
class Transaction {
private final int id;
private final String name;
//etc.
}
Then if you need to test if a given transaction is in the list you could implement equals and hashcode in that class, which would enable you to call:
if(transactionLines.contains(someTransaction)) { ... }
If you just need to find transactions with a specific characteristics, you would need to iterate over the list and check each transaction, for example:
Transaction result = null;
for (Transaction t : transacionLines) {
if(t.getName().equals("some value") {
result = t;
break;
}
}
public static boolean isListOfStringArraysContainsString(List<String[]> arrayList, String s) {
for (String[] arr : arrayList) {
for (String string : arr) {
if ((string != null) && (string.equals(s))) {
return true;
}
}
}
return false;
}
Provided code do exactly what you are asking about, but solution provided by #assylias is proper
I got your point. By using ArrayList you are trying to make an array of another array of strings. But you have made one simple mistake.This is how you tried to retrieved a String inside an array inside an ArrayList:
if(transactionLines.contains("some value")) {
//Do some stuff with it
}
This "some value" is a string present in String array "transactionLine" and not referred by the List "transactionLines" (which is referring to ArrayList object).
Instead this is what you should have done:
List<String[]> transactionLines = new ArrayList<String[]>();
String[] transactionLine = new String[7];
transactionLine[0] = "0";
transactionLine[1] = "1";
transactionLine[2] = "something";
transactionLine[3] = "3";
transactionLine[4] = "4";
transactionLines.add(transactionLine);
String[] mySL=transactionLines.get(0);
System.out.println(mySL[2]);
if (mySL[2].equals("something")) {
//some code
} else {
//some code
}
Hope this helps.

Dynamic initialization of ArrayList<anyClassObject>

Normally if we want to initialize a generic non-primitive ArrayList we do this
ArrayList<?> arrayList = new ArrayList<MyClass.class>();
But I want to do something similar to this no matter which class object I pass, i.e
private void getModel(Class responseType){
//Something similar, because this does not work..
ArrayList<?> arrayList = new ArrayList<responseType>();
}
Any Help would be greatly appreciated.
Try something like this
private <T> void setModel(Class<T> type) {
ArrayList<T> arrayList = new ArrayList<T>();
}
If you want to get the list back then
private <T> ArrayList<T> getModel(Class<T> type) {
ArrayList<T> arrayList = new ArrayList<T>();
return arrayList;
}
EDIT
A FULL EXAMPLE SHOWING HOW TO USE GENERIC TYPE FOR ARRAYLIST
Tester class with main method and the generic Method
public class Tester {
private <T> ArrayList<T> getModels(Class<T> type) {
ArrayList<T> arrayList = new ArrayList<T>();
return arrayList;
}
public static void main(String[] args) {
Data data = new Data(12, "test_12");
Magic magic = new Magic(123, "test_123");
Tester t = new Tester();
ArrayList<Data> datas = (ArrayList<Data>) t.getModels(Data.class);
datas.add(data);
for(Data data2 : datas) {
System.out.println(data2);
}
ArrayList<Magic> magics = (ArrayList<Magic>) t.getModels(Magic.class);
magics.add(magic);
for(Magic magic2 : magics) {
System.out.println(magic2);
}
}
}
Another possibility to use the same things without parameter since we don't use it inside the method
public class Tester {
private <T> ArrayList<T> getModel() {
ArrayList<T> arrayList = new ArrayList<T>();
return arrayList;
}
public static void main(String[] args) {
Data data = new Data(12, "test_12");
Magic magic = new Magic(123, "test_123");
Tester t = new Tester();
ArrayList<Data> datas = t.getModel();
datas.add(data);
for(Data data2 : datas) {
System.out.println(data2);
}
ArrayList<Magic> magics = t.getModel();
magics.add(magic);
for(Magic magic2 : magics) {
System.out.println(magic2);
}
}
}
Model class (Data)
public class Data {
private Integer id;
private String name;
public Data() {
}
public Data(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "Data [" + (id != null ? "id=" + id + ", " : "") + (name != null ? "name=" + name : "") + "]";
}
}
Model class (Magic)
public class Magic {
private Integer id;
private String name;
public Magic() {
}
public Magic(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "Data [" + (id != null ? "id=" + id + ", " : "") + (name != null ? "name=" + name : "") + "]";
}
}
This works:
private void getModel(){
ArrayList<?> arrayList = new ArrayList<Object>();
}
I mean, it is unclear what you are trying to do. Generics is purely compile-timem, to perform compile-time type checking. Therefore, if the type parameter is not known at compile time, it would be useless.
Try using following
public <T> List<T> getList(Class<T> requiredType) {
return new ArrayList<T>();
}
public void useList() {
List<Integer> ints = getList(Integer.class);
List<String> lists = getList(String.class);
}

Categories

Resources