Unmodifiable List of modifiable objects - java

Let's say we have got a List<List<String>> and want to make it completely unmodifiable. Simply passing it to a Collection.unmodifiableList would not suffice because the inner Lists can still be modified.
I would go with the following approach:
List<List<String>> someList;
Make it unmodifiable:
List<List<String>> tempList = new ArrayList<>();
for(List<String> strList : someList) {
tempList.add(Collections.unmodifiableList(strList));
}
List<List<String>> safeList = Collections.unmodifiableList(tempList);
Is this approach okay?

This approach should work, as long as you don't keep references to the original, modifiable, lists. Such references could modify the lists wrapped by the unmodifiable lists.

The below code should do :-
List<List<String>> tempList = new ArrayList<>();
for(List<String> strList : someList) {
tempList.add(new ArrayList<>(strList));
}
List<List<String>> safeList = Collections.unmodifiableList(tempList);
Here goes the test for this :-
List<List<String>> someList = new ArrayList<>();
List<String> l1 = new ArrayList<String>();
List<String> l2 = new ArrayList<String>();
l1.add("STR1");
l1.add("STR2");
l1.add("STR3");
l2.add("STR4");
l2.add("STR5");
l2.add("STR6");
someList.add(l1);
someList.add(l2);
List<List<String>> tempList = new ArrayList<>();
for(List<String> strList : someList) {
tempList.add(new ArrayList<>(strList));
}
List<List<String>> safeList = Collections.unmodifiableList(tempList);
l1.add("STR7"); // The inner list reference is modified this doesnot cause the safelist internal structure to get changed
for(List<String> safeInnerList : safeList) {
System.out.println(safeInnerList);
}

I feel it doesnot work:-
List<List<String>> someList = new ArrayList<>();
List<String> l1 = new ArrayList<String>();
List<String> l2 = new ArrayList<String>();
l1.add("STR1");
l1.add("STR2");
l1.add("STR3");
l2.add("STR4");
l2.add("STR5");
l2.add("STR6");
someList.add(l1);
someList.add(l2);
List<List<String>> tempList = new ArrayList<>();
for(List<String> strList : someList) {
tempList.add(Collections.unmodifiableList(strList));
}
List<List<String>> safeList = Collections.unmodifiableList(tempList);
l1.add("STR7"); // The inner list reference is modified which causes the
safelist internal structure to get changed
for(List<String> safeInnerList : safeList) {
System.out.println(safeInnerList);
}

The way to achieve immutability is to create defensive copies.
Data in
Whenever a mutable object is passed to your method(s), you create a deep copy of it. This should be the first thing you do, even before you check validity, if you want maximum security.
Wrapping a list into a Collections.unmodifiableList() isn't going to work here because there's no guarantee that the underlying list won't be modified by a third party. In other words, you're not in control of the instance.
A good way of creating immutable copies of lists is by using Google Guava's ImmutableList.copyOf() method, but remember that you need a deep copy, so you need to create immutable copies of the lists within the main list.
Data out
whenever you return a value, you make another defensive copy, so that changes to the returned object don't reflect back. Here you can use unmodifiable wrappers (e.g. ImmutableList.of()) on your lists because you're holding the only reference to the original list.
If you do both (copy on the way in, copy/wrap on the way out), your code will be safe and correct. Any other solution and no such general guarantees can be given, your code may or many not be correct.

Related

How can i get a new object in arrayList.remove()

ArrayList<ArrayList<Integer>> treeList = new ArrayList<>();
ArrayList<Integer> aList = new ArrayList<>();
ArrayList<Integer> bList = new ArrayList<>();
aList.add(1);
treeList.add(aList);
bList = treeList.remove(0);
aList.clear(); //bList will be cleared
I know that bList and aList will refer to the same object,so when aList.clear(), bList will clear too, is there any way to make bList a new object.
... is there any way to make bList a new object.
You can copy it; e.g.
bList = new ArrayList<>(treeList.remove(0));
See How to copy Java Collections list
Actually, this looks like a case that would benefit from writing your own custom classes.
As written, your treeList is an open data structure. Anything that has access to treeList or any of its component ArrayList objects can interfere with it. That's OK in some circumstances. But if you want to protect against having different parts of your codebase "mess up" the data structure then you should put the data structure behind an abstraction boundary; e.g.
public class MyThing {
private ArrayList<ArrayList<Integer>> treeList = new ArrayList<>();
public void add(ArrayList<Integer> l){
treeList.add(new ArrayList<>(l));
}
public void remove(int index) {
return new ArrayList<>(treeList.remove(index));
}
}
Notice that MyThing carefully copies the lists when it adds them and when it removes them so that one client of the MyThing API cannot interfere with another one via shared lists.
Obviously, there is a cost in doing this.
Try this. Pass the return of the remove as an argument to the ArrayList constructor.
ArrayList<ArrayList<Integer>> treeList = new ArrayList<>();
ArrayList<Integer> aList = new ArrayList<>();
ArrayList<Integer> bList = new ArrayList<>();
aList.add(1);
treeList.add(aList);
bList = new ArrayList<>(treeList.remove(0));
aList.clear(); //bList will be cleared
System.out.println(bList);
Prints
[1]
Alternatively, instead of assigning:
bList = treeList.remove(0);
you can use List#addAll
bList.addAll(treeList.remove(0));

List Item is getting deleted Automatically when the list is a copy of another

I have a list say list1 and then I am making a copy of list1 as list2
Now if I remove one item from the list1 then the same item from list2 is also getting deleted.
ArrayList<Object> list1=new ArrayList<>();
//Then I am filling up only list1.
ArrayList<Object> list2=new ArrayList<>();
list1=list2;
for(i=0;i<Constants.list1.size();i++)
Constants.list1.remove(2);
Then if I write this:
if(Constants.list2.size()==0)
Toast.makeText(context,"Error",Toast.LENGTH_SHORT).show();
I am getting the Toast as ERROR.
I want to know whether copying one list to another makes them point to the same reference or not?
This code is not "copying":
list1 = list2;
It is making both variables (list1 and list2) point to the same List object. So changes are made to the same list object, regardless of which variable is used to reference it.
To make a copy of the list, you can use:
list1= new ArrayList<>(list2);
Or copy element by element:
for(i=0;i < list2.size();i++)
list1.add(list2.get(i));
This happens because you have two references (list1, list2)pointing to same memory object. It is a result of list1=list2 assignment.
If you want to make a clone of the initial list you need to use:
ArrayList<Object> list2 = new ArrayList<Object>(list1);
To avoid duplicate values first you need to clear the arraylist list2.clear(); then copy full arraylist
ArrayList<Object> list1=new ArrayList<>();
//Then I am filling up only list1.
ArrayList<Object> list2=new ArrayList<>();
list2.clear(); //Before add value clear the arraylist
list2.addAll(list1);
Another way to create a standalone copy of your list would be with using the clone function and casting it to the expected type:
Replace:
list1 = list2;
With this:
list1 = (ArrayList<Object>)list2.clone();

Created two Lists from same pojo array, Modifying one List, same thing affects on other list also

I have created two lists object from the same pojo and sorted one of them. When I tried to change one list, other lists also got updated.
List<FilterPojo.Data> filterList = new ArrayList<>();
List<FilterPojo.Data> subFilterList = new ArrayList<>();
If I change the value in filterList, same changes occur in subFilterList
With the limited information that is provided by you, it seems you are creating/populating subFilterList as subList of filterList. When you do that, all changes made in either of the list will be reflected in other.
This happens because List.subList(), returns a view of the list, so modifications to the original list will be reflected in the sub-list. As suggested by others, instead of subList use addAll to populate subFilterList
This could be reference problem. Lists maintains their references when items are copied to other list, if you do something like:
List<FilterPojo.Data> subFilterList = filterList;
Use addAll method instead,
subFilterList.clear();
subFilterList.addAll(filterList);
Try below
List<String> filterList = new ArrayList<String>();
List<String> subFilterList = new ArrayList<String>();
filterList.add("A");
filterList.add("B");
filterList.add("C");
/*subFilterList = filterList; // reference to same object , change will reflect in both
filterList.add("C");
System.out.println(filterList);
System.out.println(subFilterList);*/
subFilterList.addAll(filterList);
filterList.add("C");
System.out.println(filterList);
System.out.println(subFilterList);
I don't know exactly the context that you are asking.
Your lists are holding the same object. For example, in this case p1.
Person p1 = new Person();
List<Person> list1 = new ArrayList<Person>();
list1.add(p1);
List<Person> list2 = new ArrayList<Person>();
list2.add(p1);
p1.setName("new name");

Making a new ArrayList containing objects the same as the first list?

I have an ArrayList containing custom objects.
What is the best way to make a separate ArrayList that has the exact same content, but isn't using the same references? As in, if I edit the first object in list1, it doesn't touch the first object in list2, but otherwise they look the same through and through.
Is it considered correct / good practice to do the following, or is there a built-in way?
List<MyObject> firstList = getArrayListFromSQLiteDb(criteria);
List<MyObject> secondList = new ArrayList<>();
for (MyObject object : firstList) {
MyObject newObject = new MyObject();
newObject.setField1(object.getField1());
newObject.setField2(object.getField2());
newObject.setField3(object.getField3());
secondList.add(newObject);
}
A simple way of doing this would be to clone the original ArrayList, thus not sharing references and having the other list remain untouched when you alter the original one. As #911DidBush mentioned, this will only work if the lists contents are cloneable and implement the clone() method correctly.
List<MyObject> firstList = getArrayListFromSQLiteDb(criteria);
List<MyObject> secondList = new ArrayList<>();
for(MyObject obj : firstList) {
secondList.add(obj.clone());
}

How to initialize List<String> object in Java?

I can not initialize a List as in the following code:
List<String> supplierNames = new List<String>();
supplierNames.add("sup1");
supplierNames.add("sup2");
supplierNames.add("sup3");
System.out.println(supplierNames.get(1));
I face the following error:
Cannot instantiate the type List<String>
How can I instantiate List<String>?
If you check the API for List you'll notice it says:
Interface List<E>
Being an interface means it cannot be instantiated (no new List() is possible).
If you check that link, you'll find some classes that implement List:
All Known Implementing Classes:
AbstractList, AbstractSequentialList, ArrayList, AttributeList, CopyOnWriteArrayList, LinkedList, RoleList, RoleUnresolvedList, Stack, Vector
Some of those can be instantiated (the ones that are not defined as abstract class). Use their links to know more about them, I.E: to know which fits better your needs.
The 3 most commonly used ones probably are:
List<String> supplierNames1 = new ArrayList<String>();
List<String> supplierNames2 = new LinkedList<String>();
List<String> supplierNames3 = new Vector<String>();
Bonus:
You can also instantiate it with values, in an easier way, using the Arrays class, as follows:
List<String> supplierNames = Arrays.asList("sup1", "sup2", "sup3");
System.out.println(supplierNames.get(1));
But note you are not allowed to add more elements to that list, as it's fixed-size.
Can't instantiate an interface but there are few implementations:
JDK2
List<String> list = Arrays.asList("one", "two", "three");
JDK7
//diamond operator
List<String> list = new ArrayList<>();
list.add("one");
list.add("two");
list.add("three");
JDK8
List<String> list = Stream.of("one", "two", "three").collect(Collectors.toList());
JDK9
// creates immutable lists, so you can't modify such list
List<String> immutableList = List.of("one", "two", "three");
// if we want mutable list we can copy content of immutable list
// to mutable one for instance via copy-constructor (which creates shallow copy)
List<String> mutableList = new ArrayList<>(List.of("one", "two", "three"));
Plus there are lots of other ways supplied by other libraries like Guava.
List<String> list = Lists.newArrayList("one", "two", "three");
List is an Interface, you cannot instantiate an Interface, because interface is a convention, what methods should have your classes. In order to instantiate, you need some realizations(implementations) of that interface. Try the below code with very popular implementations of List interface:
List<String> supplierNames = new ArrayList<String>();
or
List<String> supplierNames = new LinkedList<String>();
You will need to use ArrayList<String> or such.
List<String> is an interface.
Use this:
import java.util.ArrayList;
...
List<String> supplierNames = new ArrayList<String>();
List is an interface, and you can not initialize an interface. Instantiate an implementing class instead.
Like:
List<String> abc = new ArrayList<String>();
List<String> xyz = new LinkedList<String>();
In most cases you want simple ArrayList - an implementation of List
Before JDK version 7
List<String> list = new ArrayList<String>();
JDK 7 and later you can use the diamond operator
List<String> list = new ArrayList<>();
Further informations are written here Oracle documentation - Collections
List is just an interface, a definition of some generic list. You need to provide an implementation of this list interface. Two most common are:
ArrayList - a list implemented over an array
List<String> supplierNames = new ArrayList<String>();
LinkedList - a list implemented like an interconnected chain of elements
List<String> supplierNames = new LinkedList<String>();
Depending on what kind of List you want to use, something like
List<String> supplierNames = new ArrayList<String>();
should get you going.
List is the interface, ArrayList is one implementation of the List interface. More implementations that may better suit your needs can be found by reading the JavaDocs of the List interface.
If you just want to create an immutable List<T> with only one object in it, you can use this API:
List<String> oneObjectList = Collections.singletonList("theOnlyObjectā€¯);
More info: docs
List is an Interface . You cant use List to initialize it.
List<String> supplierNames = new ArrayList<String>();
These are the some of List impelemented classes,
ArrayList, LinkedList, Vector
You could use any of this as per your requirement. These each classes have its own features.
Just in case, any one still lingering around this question. Because, i see one or two new users again asking the same question and everyone telling then , No you can't do that, Dear Prudence, Apart from all the answers given here, I would like to provide additional Information -
Yes you can actually do, List list = new List();
But at the cost of writing implementations of all the methods of Interfaces.
The notion is not simply List list = new List(); but
List<Integer> list = new List<Integer>(){
#Override
public int size() {
// TODO Auto-generated method stub
return 0;
}
#Override
public boolean isEmpty() {
// TODO Auto-generated method stub
return false;
}
#Override
public boolean contains(Object o) {
// TODO Auto-generated method stub
return false;
}
..... and So on (Cant write all methods.)
This is an example of Anonymous class. Its correct when someone states , No you cant instantiate an interface, and that's right. But you can never say , You CANT write List list = new List(); but, evidently you can do that and that's a hard statement to make that You can't do.
Instead of :
List<String> supplierNames = new List<String>();
Write this if you are using latest JDK:
List<String> supplierNames = new ArrayList<>();
It's the correct way of initializing a List.
We created soyuz-to to simplify 1 problem: how to convert X to Y (e.g. String to Integer). Constructing of an object is also kind of conversion so it has a simple function to construct Map, List, Set:
import io.thedocs.soyuz.to;
List<String> names = to.list("John", "Fedor");
Please check it - it has a lot of other useful features

Categories

Resources