This question already has answers here:
I have two lists containing the same objects. How do I change one list without changing the other? [duplicate]
(2 answers)
Is Java "pass-by-reference" or "pass-by-value"?
(93 answers)
Closed 2 years ago.
I am facing strange problem (At least awkward for me). I have a list of custom objects. I am adding this list of custom objects 2 other ArrayLists. Here is the problem, when I update one of the list (a property of any custom object), it updates the object of same location in other list. Below is the code
Below is the custom class for example:
public class TestClass {
String name;
}
Here is how I am creating data set:
TestClass testClass1 = new TestClass();
testClass1.name= "first";
TestClass testClass2 = new TestClass();
testClass2.name= "second";
List<TestClass> data = new ArrayList<>();
data.add(testClass1);
data.add(testClass2);
Here is how I am adding data set in other 2 Lists:
List<TestClass> testListFirst = new ArrayList<>();
testListFirst.addAll(data);
List<TestClass> testListSecond = new ArrayList<>();
testListSecond.addAll(data);
Here is the problem when I update an element of one list it gets updated in second list as well:
testListFirst.get(0).name = "third";
If I check testListFirst it is updated with new value, but testListSecond is also updated. My expectation was testListSecond It should not get updated because they both list are different object in memory pointing different objects. If I update one other should not be updated. Please correct me if I am wrong. Any help is highly appreciated.
The problem here is you are creating two separate lists, it's true. But the objects which are inside the lists are the same. So, once you do some change by retrieving an object from any list, it will update the state of the same object.
Sample Solutions:
Method 1:[Recommended]
List<TestClass> clonedist == new ArrayList<>(); //To store the clones
for (TestClass temp : originalList){ //Looping through the original list and cloning the each element
clonedList.add(new TestClass(temp.getName()));//Creating a new TestClass object and adding to the list.Name will be set to the object through it's overloaded constructor.
}
Note:Here new TestClass object will be created through the overloaded constructor of the TestClass. To be brief I didn't include the codes regarding the updated TestClass.But you can still create the new object and update it's state through it's relevant setter methods or directly calling the attributes names(Like in your code snippet if the access modifier of the attribute allows).
Method 2:[There might be some issues with the clone() method ]
More Details regarding the clone() method :Should I use Clone method in java?
List<TestClass> clonedist == new ArrayList<>(); //To store the clones
for (TestClass temp : originalList){ //Looping through the original list and cloning the each element
clonedList.add(temp.clone());//cloning the object and adding to the list
}
Instead of changing fields on an existing object it is best to simply provide a new object. Then you will avoid the problem you are experiencing. I modified your class with a constructor to take a name.
TestClass testClass1 = new TestClass("first");
TestClass testClass2 = new TestClass("second");
List<TestClass> data = new ArrayList<>();
data.add(testClass1);
data.add(testClass2);
System.out.println("data = " + data);
List<TestClass> testListFirst = new ArrayList<>();
testListFirst.addAll(data);
List<TestClass> testListSecond = new ArrayList<>();
testListSecond.addAll(data);
System.out.println("Before adding 'third`");
System.out.println("testListFirst = " + testListFirst);
System.out.println("testListSecond = " + testListSecond);
testListFirst.set(0, new TestClass("third"));
System.out.println("after adding 'third'");
System.out.println("testListFirst = " + testListFirst);
System.out.println("testListSecond = " + testListSecond);
Prints
data = [first, second]
Before adding 'third`
testListFirst = [first, second]
testListSecond = [first, second]
after adding 'third'
testListFirst = [third, second]
testListSecond = [first, second]
Your modified class
class TestClass {
public String name;
public TestClass(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
The lists are different objects, but the elements in the list are the same. You have to make a copy of each element for each list.
Related
I'm trying to write a program that handles the storing of students and corresponding subjects, but am relatively new to Java's Lists and am having trouble getting the class to store and output the students and subjects. So far I can get it to store a student and class but will not take multiple, or only returns the most recent one added.
private static Map<String, Set<String>> cohort = new HashMap<~>();
public static void signOn(String class, String student) {
Set<String> studentSet = new HashSet<String>();
studentSet.add(student);
cohort.put(class, studentSet);
}
public static Map<String, Set<String>> getCohort() {
return cohort;
}
When calling getCohort() I am trying to get it to return all students that have signed on, but it is only returning the most recent student added in. I'm not sure if I am missing something simple but I just can't seem to get it right, any help would be very appreciated.
in signOn when you are adding a new student, you are creating a new Set, adding one student to it and then overrides any Set that was already there. This means that you will always only have one student there.
What you need to do is to first get the current set of students in a class, and then add your student to them.
Example code:
public static void signOn(String class, String student)
{
Set<String> studentSet = cohort.get(class);
if (studentSet == null) {
studentSet = new HashSet<String>();
cohort.put(class, studentSet);
}
studentSet.add(student);
}
That should work as studentSet will be a reference to the studentSet that is stored in cohort. I just wrote that from memory so no promises it works in the first try, but that is the general idea.
You are always creating a new Set, thus overriding the already existing set you saved in cohort.
Try the following:
Set<String> studentSet = cohort.get(class);
if(studentSet == null){
studentSet = new HashSet<String>();
cohort.put(class, studentSet);
}
studentSet.add(student);
As a side note I would like to add that 'class' is properly not the best name for your String variable as it's a Java reserved word.
You are facing the problem as you are putting new Set every time (also when student class is the same), which basically override the old value. So, you need to put a student in the same set if the class is the same. Using Java 8 you can do as follows :-
public static void signOn(String cls, String student) {
cohort.computeIfAbsent(cls, k -> {
Set<String> studentSet = new HashSet<>();
studentSet.add(student);
return set;
});
cohort.computeIfPresent(cls, (k, v) -> {
v.add(student);
return v;
});
}
Note: You can't have a variable name class, it is a reserved keyword in java.
I have a problem to deep copy an ArrayList containing Attribute objects. After I have copied the ArrayList dataSet in a new one called trainingSet, I am trying to clear (of the trainingSet) all the content of the internal ArrayList of the Attribute called data. When I do so all the same content of the the ArrayList dataSet (data of dataSet) gets cleared, too. So in that case I have tried to deep copy all the content of the original list to the new one using the below tuts:
http://javarevisited.blogspot.gr/2014/03/how-to-clone-collection-in-java-deep-copy-vs-shallow.html#axzz4ybComIhC
https://beginnersbook.com/2013/12/how-to-clone-an-arraylist-to-another-arraylist/
How to make a deep copy of Java ArrayList
but I got the same behavior. So can someone please tell me how I can fix this problem and where the wrong thinking is?
Thank you for help.
ID3Algorithm.java
...
ArrayList<Attribute> dataSet = new ArrayList<dataSet>();
ArrayList<Attribute> trainingSet = new ArrayList<Attribute>(dataSet);
for(Attribute att : trainingSet) {
att.GetData().clear(); // At this point all the data in dataSet are cleared,too.
}
...
Attribute.java
public class Attribute
{
private String name;
private ArrayList<String> branchNames = new ArrayList<String>();
private ArrayList<String> data = new ArrayList<String>();
private ArrayList<Branch> branches = new ArrayList<Branch>();
private HashMap<String, Integer> classes = new HashMap<String, Integer>();
private ID3Algorithm id3;
private Leaf leaf = null;
public ArrayList<String> GetData() { return data; }
public Attribute(String attribName, ArrayList<String> attribBranchNames, ArrayList<String> attribData, ID3Algorithm algo) {
name = attribName;
branchNames = attribBranchNames;
data = attribData;
id3 = algo;
}
...
}
When you are assigning a value to trainingSet
ArrayList<Attribute> trainingSet = new ArrayList<Attribute>(dataSet);
You are only passing the references for the existing attributes into a new list. It is not a new list of different attribute objects. The first link you post, describes this process in detail. I would re-read it in depth.(The first example is a shallow copy)
http://javarevisited.blogspot.gr/2014/03/how-to-clone-collection-in-java-deep-copy-vs-shallow.html#axzz4ybComIhC
So when you call
att.GetData().clear();
You are clearing the orginal attribute objects data (which dataset also references)
Try creating new Attribute objects and assigning new data to each(copied from the orginal) then adding those to your trainingSet list.
have this class
public class ObjetoOS {
private ArrayList<String> atribute = new ArrayList<String>();
public ObjetoOS (String a){
atribute.add(a);
}
public ObjetoOS (ArrayList<String> a){
atribute = a;
}
which i use like this
public class TablaSimbolica {
private static ArrayList<ObjetoOS> tableOS = null;
public static void addAtributos(ArrayList<String> newOnes){
if (tableOS == null){
tableOS = new ArrayList<ObjetoOS>();
for (String s : newOnes){
ObjetoOS newObj = new ObjetoOS(s);
tableOS.add(newObj);
}
}
else{
ArrayList<ObjetoOS> aux = new ArrayList<ObjetoOS>();
for (ObjetoOS os : tableOS){
ArrayList<String> oldOnes = new ArrayList<String>();
oldOnes = os.getAtributo();
for (String s : newOnes){
oldOnes.add(s);
ObjetoOS newObj = new ObjetoOS(oldOnes);
aux.add(newObj);
newObj = null;
oldOnes.remove(s);
}
}
tableOS = aux;
}
}
so basically: addAtributos checks if the array its empty. if it is it just adds the strings, if its not, i have to combine it with the new ones, like a cartisan product thing.
when adding new strings, although im creating a new array, and a new objectOS which y turn to null after adding it, the elements overwrite with the last one.
for example, if i had strings "true" "false", and i have to add "female" "male", the output is:
[TRUE, Female]
[TRUE, Male]
[TRUE, Male]
[TRUE]
[TRUE]
[FALSE, Female]
[TRUE]
[TRUE]
[FALSE, Male]
[FALSE, Male]
i cant figure out where im missing the point here. it gives me an error if i erase the static both from the ArrayList tablaOS or from the addAtributos method.
EDIT: solved! i changed the object class making a new empty constructor and a method adding a string array with a for.
the loop it ended up being
for (String s : newOnes){
oldOnes.add(s);
ObjetoOS nuevo = new ObjetoOS();
nuevo.addAtribute(oldOnes);
aux.add(nuevo);
oldOnes.remove(s);
Some of your problems lies here:
ArrayList<String> oldOnes= new ArrayList<String>(); // This line is pointless
oldOnes= os.getAtributo(); // oldOnes will be overwritten here
for (String s : newOnes)
{
oldOnes.add(s);
ObjetoOS newObj = new ObjetoOS(oldOnes);
aux.add(newObj );
newObj = null;
oldOnes.remove(s);
}
When you here pass oldOnes to the ObjetoOS constructor, the newObj will have a reference to the same list as oldOnes, when you then later remove the element from oldOnes you also remove it from newObj's list(since they are the same).
The same does not happen if you use the String constructor because primitive datatypes, also including Strings, are copied (passed by value) when passed to a method.
There's a good explanation on how objects are passed in java here: Is Java "pass-by-reference" or "pass-by-value"?
Edit:
Also your tableOS will point to a new ArrayList<> every time you call your method, if I understand your solution correctly this might be more what you want:
...
for (ObjetoOS os : tableOS)
{
for (String s : newOnes)
{
ArrayList<String> oldOnesPlusNewOne = new ArrayList<>();
oldOnesPlusNewOne.addAll(os.getAtributo());
oldOnesPlusNewOne.add(s);
ObjetoOS newObj = new ObjetoOS(oldOnesPlusNewOne);
tableOS.add(newObj);
}
}
Might I suggest simplifying the code some? If your trying to add attributes to an array list, maybe create an attributes class instead. You can create a class for the atributos, give it some members etc... and easily change to and make the atributos a list to hold aritbutos. I'd suggest the class be name Atributo. Your trying to do to much in one class. Single responsibility is key. One class that defines the atributo and one to store a list of them. This should fix your problem and uncomplicate your code. Now you can only store 10 members to the array list without specifying the amount of elements you want it to hold. If you don't it will only hold the default of 10 and the last element will be over written.
import java.util.ArrayList;
public class Atributos
{
// attributes list
ArrayList<String> atributos = new ArrayList<String>();
// add string to attributes list
public void addAtributos(String atributo)
{
atributos.add(atributo);
}
// add list of strings to attribute list
public void addAtributos(ArrayList<String> atributo)
{
for(String attr : atributo)
{
atributos.add(attr);
}
}
}
I need a solution to change the value of an element in a multidimensional ArrayList in Java. This is my ArrayList:
public class Users {
ArrayList<ValidateUser> personer = new ArrayList<ValidateUser>();
public Users() {
personer.add(new ValidateUser("admin", "asdf123", 0.8, "admin"));
personer.add(new ValidateUser("jesper", "ukamm19", 2.5, "user"));
personer.add(new ValidateUser("lars", "lol123", 1.5, "user"));
}
I want to change the double value (0.8) at the user "admin", for example.
This would be done from another a class.
How to do so?
Thanks in advance! :)
As I've stated in my comment, just iterate through the list to find the object. If you're going to do this a lot, consider using a map.
for (ValidateUser user : personer)
if (user.getName().equals("admin"))
user.setNumber(someNumber);
First, note that this is not a multidimensional array, is just a list that holds elements of ValidateUser class object references. Second, you need to access to the element before updating it. You have several ways to accomplish this:
Implement the equals and hashCode methods in your ValidateUser class, then just retrieve the object from your List:
ValidateUser adminUser = personer.get(new ValidateUser("admin", "", 0.8, ""));
adminUser.set...
Note: this looks ridiculous but will work (assuming your equals method only checks by the field that holds this "admin" value.
Navigate through the array and seek for the desired element manually, then update it:
for (ValidateUser user : personer) {
if ("admin".equals(user.getXxx()) {
user.set...
break; //don't forget this!
}
}
Use a different data structure like a Map<String, ValidateUser> to store your data and faster retrieval:
Map<String, ValidateUser> personerMap = new LinkedHashMap<String, ValidateUser>();
personerMap.add("admin", new ValidateUser("admin", ...);
//fill the map with other values...
//if you still want a Collection<ValidateUser> personer variable
Collection<ValidateUser> personer = personerMap.values();
//now check for the desired element
ValidateUser admin = personerMap.get("admin");
if (admin != null) {
admin.set...
}
By comments, your ValidateUser is an immutable object, so you cannot update its fields using setters (because there aren't). So, the best approach here is to use a ListIterator<ValidateUser> instead (not to confuse with Iterator) and replace the element by your modified object. Here's an example:
//the new immutable ValidateUser that will replace the older one...
//set the parameters as needed
ValidateUser newAdmin = new ValidateUser("admin", ...);
ListIterator<ValidateUser> listIterator = personer.listIterator();
while (listIterator.hasNext()) {
ValidateUser validateUser = listIterator.next();
if ("admin".equals(validateUser.getXxx()) {
listIterator.set(newAdmin);
break;
}
}
I am currently working on one of the usecases where you are given 6 strings which has 3 oldValues and 3 newValues like given below:
String oldFirstName = "Yogend"
String oldLastName = "Jos"
String oldUserName = "YNJos"
String newFirstName = "Yogendra"
String newLastName ="Joshi"
String newUserName = "YNJoshi"
now what I basically want to do is compare each of the oldValue with its corresponding new value and return true if they are not equal i.e
if(!oldFirstName.equalsIgnoreCase(newFirstName)) {
return true;
}
Now, since I am having 3 fields and it could very well happen that in future we might have more Strings with old and new value I am looking for an optimum solution which could work in all cases no matter how many old and new values are added and without having gazillions of if else clauses.
One possibility I thought was of having Old values as OldArrayList and new values as newArraylist and then use removeAll where it would remove the duplicate values but that is not working in some cases.
Can anyone on stack help me out with some pointers on how to optimum way get this done.
Thanks,
Yogendra N Joshi
you can use lambdaj (download here,website) and hamcrest (download here,website), this libraries are very powerfull for managing collections, the following code is very simple and works perfectly:
import static ch.lambdaj.Lambda.filter;
import static ch.lambdaj.Lambda.having;
import static ch.lambdaj.Lambda.on;
import static org.hamcrest.Matchers.isIn;
import java.util.Arrays;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> oldNames = Arrays.asList("nameA","nameE","nameC","namec","NameC");
List<String> newNames = Arrays.asList("nameB","nameD","nameC","nameE");
List<String> newList = filter(having(on(String.class), isIn(oldNames)),newNames);
System.out.print(newList);
//print nameC, nameE
}
}
With this libraries you can solve your problem in one line. You must add to your project: hamcrest-all-1.3.jar and lambdaj-2.4.jar Hope this help serve.
NOTE: This will help you assuming you can have alternatives to your code.
You can use two HashMap<yourFieldName, yourFieldValue> instead of two Arrays / Lists / Sets of Strings (or multiple random Strings);
Then you need a method to compare each value of both maps by their keys;
The result will be an HashMap<String,Boolean> containing the name of each field key, and true if the value is equal in both maps, while false if it is different.
No matter how many fields you will add in the future, the method won't change, while the result will.
Running Example: https://ideone.com/dIaYsK
Code
private static Map<String,Boolean> scanForDifferences(Map<String,Object> mapOne,
Map<String,Object> mapTwo){
Map<String,Boolean> retMap = new HashMap<String,Boolean>();
Iterator<Map.Entry<String, Object>> it = mapOne.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String,Object> entry = (Map.Entry<String,Object>)it.next();
if (mapTwo.get(entry.getKey()).equals(entry.getValue()))
retMap.put(entry.getKey(), new Boolean(Boolean.TRUE));
else
retMap.put(entry.getKey(), new Boolean(Boolean.FALSE));
it.remove(); // prevent ConcurrentModificationException
}
return retMap;
}
Test Case Input
Map<String,Object> oldMap = new HashMap<String,Object>();
Map<String,Object> newMap = new HashMap<String,Object>();
oldMap.put("initials","Y. J.");
oldMap.put("firstName","Yogend");
oldMap.put("lastName","Jos");
oldMap.put("userName","YNJos");
oldMap.put("age","33");
newMap.put("initials","Y. J.");
newMap.put("firstName","Yogendra");
newMap.put("lastName","Joshi");
newMap.put("userName","YNJoshi");
newMap.put("age","33");
Test Case Run
Map<String,Boolean> diffMap = Main.scanForDifferences(oldMap, newMap);
Iterator<Map.Entry<String, Boolean>> it = diffMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String,Boolean> entry = (Map.Entry<String,Boolean>)it.next();
System.out.println("Field [" + entry.getKey() +"] is " +
(entry.getValue()?"NOT ":"") + "different" );
}
You should check too if a value is present in one map and not in another one.
You could return an ENUM instead of a Boolean with something like EQUAL, DIFFERENT, NOT PRESENT ...
You should convert your String to some Set.
One set for OLD and another for NEW. And your goal of varity number of elements will also be resolved using same.
As it's set order of it will be same.