If you are short of time, you can ignore this for now and come back to it later. Below are important Question & Answers taken from the Q&A discussions in this section.
Q1. Difference between fail-fast Iterator and fail-safe iterator?
Note on Question: Most Collection implementations implement iterators as fail-fast iterators and this question is about how are they different from fail-safe iterators. Below, is a pretty nice explanation as it takes into account a multi-threaded environment.
Once an iterator is created and before the elements are iterated, the list cannot be modified structurally. It makes more sense in a multi-threaded environment. Below is an example.
Let's assume that a single ArrayList object 'studentIdList' is shared between two threads t1 & t2. Let's say studentIdList has 3 elements [1001, 1002, 1003]. Now let's say t2 starts iterating 'studentIdList' (via iterator()) and it iterated once and extracted the value 1001 and did some operation on the student whose ID is 1001. Now, in multi-threading, it is perfectly possible that before t2 iterates again, some statement in thread t1 is executed. Let's say that statement in t1 is adding 1001 to studentIdList. So, at this instance studentIdList has [1001, 1001, 1002, 1003]. Now,next let's say thread t2 continues its execution and continues from where it stopped, i.e., second iteration. Now, it once again sees 1001 and performs the operation once again, which probably is meaningless and wrong and could leave the system in a corrupted state. To avoid this situation, a fail-fast iterator would throw a ConcurrentModificationException during the second iteration as it knew that the list was structurally modified after iterator() was invoked. So, it failed fast and did not allow the system to get into an inconsistent state. So, once you create an iterator on an arraylist object, you cannot make any structural changes to that object (without using iterator methods) and expect to still iterate using the created iterator.
fail-safe iterator it seems would be unaffected by structural modifications as it would make a copy of the original arraylist and would iterate on it.
Below is some additional notes from ArrayList specification:
"Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future."
It also goes on to say:
"Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs."
I am pretty sure what it means here when threads do not use synchronization (discussed in Concurrency chapter), then any changes made in studentIdList in t1 MAY not be VISIBLE to thread t2. In this case, iterator would continue to execute (like fail-safe). So, in the presence of unsynchronized access, fail-fast behavior is not guaranteed. The concept of changes made in one thread and that is not visible in another thread is related to a multithreading concept called 'memory visibility' and will be covered in Concurrency chapter.
You may also take a look at this stack overflow discussion. I think it is inline with the above description.
http://stackoverflow.com/questions/17377407/what-is-fail-safe-fail-fast-iterators-in-java-how-they-are-implemented
https://www.baeldung.com/java-fail-safe-vs-fail-fast-iterator
Q2. I dont understand this statement,"collection view methods backed by map..so any changes made to returned view will affect the original map and vice versa" ..could you please elaborate it or what are the basics behind it?
By "backed by original map" (or "backed by original list" in case of a Collection like ArrayList), they are still going to use the manipulate the original map (or list). So, although a new object is returned, it still references the same underlying data structure that the original map or list is using. For the sake of this discussion, lets actually consider ArrayList.subList(fromIndex, toIndex) method as the concept is similar. A/C API spec of this method, it returns a view of the portion of this list between the specified indexes. And this view is a new Object (a List) and it simply refers to the underlying array that the original arraylist is using (Recall that ArrayList uses an array internally). So, any changes made via the returned object is done in the underlying array itself. So, I guess it should be now clear what "backing by original list/map" means. Now, next question is why did they do it? One reason could be performance. They "probably" did not want to create a whole new array and populate it with elements. So, they implemented it in a smart way. Another benefit that is actually mention in the API spec of the above method is this "method eliminates the need for explicit range operation", i.e., it would allow the following:
list.subList(from, to).clear();
This whole thing approach of providing a view of the original list/map is referred to as "adapter pattern" and is done using nested classes. In Nested Classes section (Section 17 - couple of sections from here), we actually discuss all of this. Below is the link to API spec of subList method.
https://docs.oracle.com/javase/10/docs/api/java/util/ArrayList.html#subList(int,int)
Q3. Are the Hashes discussed in this section same as Hashes used in security?
Answer: The idea is similar, but the algorithms are very different. In security, it is very important that string generated is irreversible. Passwords for instance are stored in database by applying some hash function. By looking at the hashed password, one shouldn't be able to figure out the actual password. With hash table on the other hand, speed and distribution of keys is more important. With passwords, speed is not that critical; the irreversibility of password is the key.
Q4. Every bucket in HashMap initially is a linked list up to the list size reaches 8. If the list size in a bucket is more than 8 then linked list will be converted to tree (in Java8). At the time of this conversion to Tree structure, some order is needed for elements as Trees have some order. Should the keys implement Comparable interface for this?
Answer: They seem to have changed the implementation with Java 8. Seems like if Comparable is not implemented by keys, it uses an alternative internal strategy where System.identityHashCode() is used. Below is the code and below I am also including a post that discusses this. If Comparable is implemented, then it uses it. Second post below gives a more conceptual explanation on the need for doing this.
static int tieBreakOrder(Object a, Object b) {
int d;
if (a == null || b == null ||
(d = a.getClass().getName().
compareTo(b.getClass().getName())) == 0)
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
-1 : 1);
return d;
}https://stackoverflow.com/questions/47921663/when-and-how-does-hashmap-convert-the-bucket-from-linked-list-to-red-black-trees
https://stackoverflow.com/questions/43911369/hashmap-java-8-implementation
Q5. In a HashMap, if we insert an entry with a null key, how will the hash be computed for it?
Answer: For null key, it is handled in a special way. Hash is not computed for null key and the corresponding entry is stored at 0th index. Below is the actual source code where 0 is being returned when key is null.
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}Q6. This question is related to ArrayList lecture. I don't get why I can't add element in the below list. I don't get why I can add & set in list3 and only can set in list1.
List<Integer> list3 = list1.subList(2, 3);
list3.set(0, 6);
System.out.println("list1: " + list1);
list3.add(0, 7);
System.out.println("list1: " + list1);
list1.set(2, 8);
System.out.println("list3: " + list3);
list1.add(0, 8);
System.out.println("list3: " + list3); // Gives java.util.ConcurrentModificationExceptionAnswer: That is how it has been defined in ArrayList.subList() API. Below is the link. It mainly says "Returns a view of the portion of this list between the specified fromIndex, inclusive, and toIndex, exclusive.". Key here is "view" of the original list and for that instead of making a brand new ArrayList for the returned sub-list, they are referencing the original list itself. So, as far as I see it, the main use-case seems to be that we have the main ArrayList and we want to get a view of a subset of it so that we can do certain operations like simply viewing the elements in the given range. But, as the returned list is backed by original list, they are permitting non-structural changes (i.e., set() operations) in either direction. As far as, structural changes are concerned they are permitting one way from sublist --> original list so that you can do things like ' list.subList(from, to).clear()'. But, for structural changes from original --> sublist, they (designers) consider it to be confusing, i.e., say we are displaying elements in the sublist and at around the same time (in a different thread in multi-threaded environment), something gets added to the original list, then reflect that in sublist could produce confusing results. That's how I see it as the conceptual reasons are not documented anywhere (at least I could not find it).
https://docs.oracle.com/javase/10/docs/api/java/util/ArrayList.html#subList(int,int)