While designing a small API, i was about to write a static value that references to an array of String:
public static final String[] KEYS={"a","b","c"}
I have found this to be marked as a 'security hole' in Joshua Bloch's 'Effective Java' item 14, where he proposes as an alternative, declaring te array 'private' and provide a public getter that returns an unmodifiable list:
return Collections.unmodifiableList(Arrays.asList(KEYS))
I just cant see why would this be necessary, the array in the initial statement is declared final even though its public, and its elements are immutable, how could that be modified from an external piece of code?
The array is not immutable.
You can still write:
KEYS[0] = "d";
without any issues.
final just means you cannot write:
KEYS = new String[]{"d"};
I.e. you cannot assign a new value to the variable KEYS.
final means
You can't change the Basket. Still you can change the fruits inside.
Basket is your Array instance. Fruits are your keys inside.
In first case, from somewhere else in the code, I can do
ClassName.KEYS[2] ="MyOwnValue";
But you can't modify when it is unmodifiable list.
Give a shot to read : Why final instance class variable in Java?
While the array reference itself is immutable (you cannot replace it with a reference to another array) and the Strings themselves are immutable too it is still possible to write
KEYS[0] = "new Key";
A composite object ( array , Set , List etc ) being final doesn't mean that its constituent objects will not be changed - you can still change constituent objects . final for a composite object simply means that its reference can't be changed.
For your case, value of KEYS can't be changed but KEYS[0] etc can be changed.
Related
I'm turning crazy on this one.
What I want to do is to get an object from an ArrayList, and add it in 3 others after modifying it.
The problem is that the object I get from my ArrayList is modified aswell when I modify the others... Can't figure out why, is this normal?
Code below:
final Product tmpRef = productsRef.get(i);
Product tmp = tmpRef;
tmp.setPos(products1.size());
Log.d("test2","tmpRef:"+tmpRef.getPos()+";tmp:"+tmp.getPos());
Product tmp2 = tmpRef;
tmp2.setPos(products2.size());
Log.d("test2","tmpRef:"+tmpRef.getPos()+";tmp:"+tmp.getPos()+";tmp2:"+tmp2.getPos());
Product tmp3 = tmpRef;
tmp3.setPos(products3.size());
Log.d("test2","tmpRef:"+tmpRef.getPos()+";tmp:"+tmp.getPos()+";tmp2:"+tmp2.getPos()+";tmp3:"+tmp3.getPos());
tmp.setPos(products1.size());
"pos" is just a simple int, with a getter/setter.
LogCat output:
03-21 09:56:14.926: D/test2(6200): tmpRef:9;tmp:9
03-21 09:56:14.926: D/test2(6200): tmpRef:7;tmp:7;tmp2:7
03-21 09:56:14.926: D/test2(6200): tmpRef:0;tmp:0;tmp2:0;tmp3:0
With #FD_, #blackbelt and #Guidobaldo da Montefelt's explainations, I ended creating a simple new Porduct's constructor to be able to copy the object, and not only the reference. Thanks guys.
Essentially, you just store references in your ArrayList, so it does not matter whether you use something like Product tmp = tmpRef;. tmp still points to the same object, thus changes are applied to both.
In this case, final just means that a pointer cannot be changed once it has been set.
Just search for deep copy java for possible solutions.
final Product tmpRef
means that the Object's reference of tmpRef can not be change. Meaning that you can not do
tmpRef = null;
or
tmpRef = anotherProdocut;
after
final Product tmpRef = productsRef.get(i);,
but the content of the object could change if its member are not final.
Adding to what #blackbelt said, you are getting confused between
"final" and "immutable" concepts. If you don't want the "object's
value" to change, then you have to make it immutable (java doesn't
provide any built-in mechanism for this). final merely prevents you
from reassigning the reference to another object.
EDIT:
1. final - if a "reference" is made final, then you cannot reassign it to point to something else. if a primitive is made final, then it's value cannot change.
eg:
final int i=5;
i=10; // error
final List<String> l = new ArrayList<String>();
l.add("one"); // works
l =new ArrayList<String>() // fails
2. Immutable - you cannot change the values of attributes of the object.
if list l were immutable, then
List<String> l = new ArrayList<String>();
l.add("one"); // fails, depending on your definition/implementation of immutablility
l =new ArrayList<String>() // l is not final, so it works fine
Before doing such a thing, you should really try to understand the difference between references and objects, and more deeply, the difference between copying a reference vs copying an object.
Product is an object stored in a memory zone called Heap. "tmpRef" is a reference that contains the address that points to that object.
This code:
Product tmp = tmpRef;
does not copy any "Product" objects but it simply assign the tmpReference in a new reference variable called tmp. So, you´ll have 1 Object pointed by 2 references. Passing a reference (4 bytes in a 32bit system) is very cheap. That´s why you can pass objects in your methods as parameters. It´s what it is called "copy by reference".
The final keyword means that the reference is immutable, not the Product object.
Probably you have confusion because these logic is not applied to primitive objects, where reference copy does not exist, but primitives are always copied by value, and never by reference.
So if you want to do not change the original object, you do need to create new Product objects in the heap.
Hope this helps.
In JAVA, I have a HashMap with "Player" objects as Keys, and "ArrayList" as values. It is used to store the opponents for each player. the variable pointing to the hashmap is called playerOpponents.
Now I want to add an opponent to some player. Is it necessary to put the list in the map after editing like in method 1, or not, like in method2?
Method1:
private void addOpponent(Player p, Player opponent)
{
ArrayList<Player> allOpponents = playerOpponents.get(p);
allOpponents.add(opponent);
playerOpponents.put(p,allOpponents);
}
Method2:
private void addOpponent(Player p, Player opponent)
{
ArrayList<Player> allOpponents = playerOpponents.get(p);
allOpponents.add(opponent);
}
Don't need to put the list reference every time. Just put null check.
private void addOpponent(Player p, Player opponent){
List<Player> allOpponents = playerOpponents.get(p);
if(playerOpponents.get(p)==null){
allOpponents = new ArrayList<>();
allOpponents.add(opponent);
playerOpponents.put(p,allOpponents);
}else
allOpponents.add(opponent);
}
no. Since HashMap already has the list, you just get the reference not remove it, so its not required to add it again. just add the element to existing list. thats it.
It isn't necessary to re-put the object. What your get statement is doing is retrieving a reference to the ArrayList, NOT a copy of the ArrayList.
As another example, say you did this:
ArrayList a = playerOpponents.get(p);
ArrayList b = playerOpponents.get(p);
Any changes made to a are reflected in b and vice-versa. In your case, a is the ArrayList reference retrieved from the HashMap with a get method, and b is the ArrayList reference in the HashMap. Changes to one reference are reflected in the other.
No. HashMaps like all other collections store Object references. Any change that you make to an object in the Map is immediately reflected in the Object returned by HashMap#get as they are the same Object.
The most succinct way of doing this would be:
playerOpponents.get(p).add(opponent);
No. The ArrayList referenced by playerOpponent's mapping from Player p will contain 'opponent' without any other function calls.
It's OK, no need to re-put it because you're only changing the VALUE of the key-value map.
However, a subtle gotcha to watch out for: if you ever change the KEY, in other words the Player instance, in a way that affects its equals() or hashCode() methods, then your map can get royally messed up. If you are using the default equals() and hashCode() implementations, then changing any field in the Player will screw up the map.
In this latter scenario, then you DO need to remove it before the change and re-put it after the change so that it can hash properly to its new hash location. Or probably simpler, just override the Player class's hashCode() and equals() methods to only consider some immutable field of Player, such as the playerId; then you can use it freely without removing or re-putting (as long as that immutable field is never null).
This is a little confusing question for me to express, but I'll do my best.
So:
ArrayList<Object> fieldList = new ArrayList<Object>();
I then dump a lot of different variables to this array:
fieldList.add(objectsURL); //string
fieldList.add(X); //int
fieldList.add(Y); //int
...
If I change the variable, the values in the array change
too-confirming the array stores a reference to the memory, rather
then value itself.
However, if I then retrieve data from the array then set that...
Object object = ((String)this.fieldList.get(0));
Then set object
object = "meeep!"
objectsURL is not set to "meep!" but rather it retains its original
value.
I assume this is because the "object" is not referencing the original
variable anymore, that instead its pointing to a new immutable string
in the memory.
All expected Java behavior I think....but then, how would I go about
setting the actual original variable? is this possible in java?.
So, in other words. Given only access to "fieldList" is it possible to change the value of
"objectsURL"?
So, if:
String objectsURL = "www.google.com"
fieldList.add(objectsURL);
Is there a way to set objectsURL to "www.stackoverflow.com" using only a reference from fieldList?
I dont want to change the fact that fieldList contains "objectsURL", I want to change what string the variable "objectsURL" actualy contains.
If not, is there an alternative method to achieve the same thing?
I hope my question explains the problem well enough.
My use-case is trying to make a serialization/
deserialization system for a bunch of my objects. I was hoping to put
all the fields into a arraylist I could retrieve for both reading and
writing....thus avoiding having to hard-code long lists of
field[0]=blah and blah=field[0] and then going though constant pains
of needing to renumber them each time I add a new field before
another.
(I cant use Javas inbuilt serialization, as I am using GWT and this is client side only.)
Thanks,
I assume this is because the "object" is not referencing the original variable anymore, that instead its pointing to a new immutable string in the memory.
Correct, each time you use the assignment operator = on an object you change the object it refers to, not the object itself.
To change the values in the List, you use the .set method of an ArrayList
this.fieldList.set(0, newValue);
Since your variable is a String, there is no way you can change the String-variable through the list
Your alternatives:
using a char-array
List myList = new ArrayList();
char[] charArray = "My String".toCharArray();
myList.add(charArray);
charArray[0] = 'A';
String theString = new String(myList.get(0)); // "Ay String"
If you use a char-array, make sure that the length of the array is enough to contain the number of characters you want to have in the future, because to change the length of the array you will need to create a new array (array lists can be expanded dynamically, arrays can not)
Embed the String inside your own class (I have ignored getters and setters here)
class MyString {
public String value;
public MyString(String value) {
this.value = value;
}
}
MyString myStr = new MyString("some value");
list.add(myStr);
((MyString) list.get(0)).value = "a new value";
System.out.println(myStr.value); // will print "a new value"
Strings are immutable, so it is impossible to change the contents of a String object. Also, you cannot use the list to change what object the reference variable objectsURL points to. To achieve what you want, you will need to create a custom class that has a String member. You can then store instances of this class in a List and change the String references to via the references in the list. The changes will then be reflected in any other reference variables which refer to the objects in the list.
First, you declare a variable 'object' and assign some Object out of the ArrayList. Later you assign some other object "meeep!" to this variable. There is no reason that your 'object' variable is related to the ArrayList.
Can someone explain what the following does?
private HashSet nodes[];
nodes = new HashSet[21];
I'm a little confused... in the difference between
private HashSet nodes = new HashSet;
and the above, particularly in terms of the square brackets syntax. Is this an array of HashSets? Because normally I'm used to seeing
int[] myarray = new int[21];
Or something like that.
They're just alternatives - both are valid, unfortunately.
Heck, even this would be valid:
int[] bad [] = null;
That's equivalent to
int[][] bad = null;
Don't do this, obviously :)
From section 10.2 of the JLS:
The [] may appear as part of the type at the beginning of the declaration, or as part of the declarator for a particular variable, or both.
And
We do not recommend "mixed notation" in an array variable declaration, where brackets appear on both the type and in declarators.
Basically, use the form that keeps all the type information in one place - the form you're used to. That's the overwhelmingly idiomatic form.
private HashSet nodes = new HashSet;
is not valid Java. Unlike JavaScript, the new operator in Java always requires a parenthesized argument list.
private HashSet nodes = new HashSet(21);
differs from
private HashSet[] nodes = new HashSet[21];
in that the former constructs one HashSet set that initially has space enough for 21 set items while the latter is an array of 21 null values that can be filled with references to sets.
private HashSet nodes[];
declares a member variable that can refer to any array whose elements are of type HashSet.
nodes = new HashSet[21];
creates an array with space for 21 HashSet references and assigns it to that member variable.
Remember that in Java, unlike in C, HashSet[21] is not a type so you can't just allocate space for an array in Java by doing
int[21] myints;
At some point you have to create an array via
new <type>[size],
the abbreviated syntax new <type> { element0, element1, element2, ... },
or reflectively via java.lang.reflect.Array.newInstance.
Yes, it is an array of HashSets.
HashSet nodes[];
is the same as
HashSet[] nodes;
The difference in where you place the brackets only becomes important when you use commas to declare a bunch of variables at a time:
HashSet[] alpha, bravo, charlie; // Three arrays of hashsets
HashSet delta[], echo, foxtrot; // One array (delta) and two hashsets (echo and foxtrot)
In Java, the declaration
private HashSet nodes[];
is equivalent to the declaration
private HashSet[] nodes;
It can be pronounced "an array of HashSets" or "a HashSet array."
final Integer[] arr={1,2,3};
arr[0]=3;
System.out.println(Arrays.toString(arr));
I tried the above code to see whether a final array's variables can be reassigned[ans:it can be].I understand that by a final Integer[] array it means we cannot assign another instance of Integer[] apart from the one we have assigned initially.I would like to know if whether it is possible to make the array variables also unmodifiable.
This isn't possible as far as I know.
There is however a method Collections.unmodifiableList(..) which creates an unmodifiable view of e.g. a List<Integer>.
If you want to guarantee that not even the creator of the unmodifiable view list will be able to modify the underlying (modifiable) list, have a look at Guava's ImmutableList.
No. The contents of an array can be changed. You can't prevent that.
Collections has various methods for creating unmodifiable collections, but arrays aren't provided for.
The final keyword only prevents changing the arr reference, i.e. you can't do:
final int[] arr={1,2,3};
arr = new int[5];
If the object arr is referring to is mutable object (like arrays), nothing prevents you from modifying it.
The only solution is to use immutable objects.
Another way is to use this function:
Arrays.copyOf(arr, arr.length);
The keyword 'final' applies to only the references (pointer to the memory location of the object in the heap). You can't change the memory address (location) of the object. Its upto your object how it internally handles the immutability.
Added, although int is a primitive data type int[] should be treated as a object.
You can't do this
final int a = 5
a = 6
You can do this:
final int[] a = new int[]{2,3,4};
a[0] = 6;
You can't do this:
final int[] a = new int[]{2,3,4};
a = new int[]{1,2,3}
To anybody else reading this old question, keep in mind there is also Google Guava's immutable collections. ImmutableList has much stronger performance than Collections.unmodifiableList(), and is arguably safer as it truly is immutable,and not backed by a mutable collection.
I'd go with List.of(array), as available from Java 9 on, which creates a copy of the source array internally and an immutable List from that. Note though, there there're no null values allowed in this implementation.
If this is a requirement, Arrays.copyOf([T]source, int length) can be used, if no real immutability is needed, but only the source array mustn't be modified.
Otherwise, if immutability of the target Collection is required, Java's immutable Collection API or Guava might be your best shot e. g. Collections.immutableXX() or ImmutableList.of().
If additional dependencies are not desired, one of the following approaches should work, depending on, whether further pre-processing is needed, before making the result immutable:
final T[] objects = (T[])new Object[] { null };
final T obj = (T)new Object();
final BinaryOperator<ArrayList<T>> listCombiner = (a,b) -> { a.addAll(b); return a; };
final Collector<T, ArrayList<T>, List<T>> collector = Collector.of(
ArrayList<T>::new, List::add, listCombiner, Collections::unmodifiableList
);
final List<T> list = Arrays.stream(objects).collect(collector);
or simply
final List<T> list = Collections.unmodifiableList(Arrays.asList(objects ));
Edit:
As already outlined, creating an immutable array in itself is not possible in Java.