📜 ⬆️ ⬇️

Java Puzzlers NG S02: Wonderful and Wonderful

Tagir Valeev ( lany ) and Baruch Sadogursky ( jbaruch ) have assembled a new collection of Java-puzzlers and are in a hurry to share them.


The article is based on the decoding of their performance at the JPoint 2017 autumn conference. It shows how many riddles are hidden in Java 8 and Java 9 that barely loomed on the horizon. All of these streams, lambdas, monads, Optionals and Completable Future were added there exclusively in order to confuse us.

Everything they talk about should work on the latest version of Java 8 and 9, respectively. We checked - everything seems to be fair: as it is written, this is how it behaves.

Just in case, a couple of words about the authors, although we think you already know them well: Baruch deals with Developer Relations at JFrog, Tagir is the developer of IntelliJ IDEA and the author of the StreamEx library.
')

Java puzzle # 1: how to bank a bank


To warm up we ask the first puzzler: unknown American hackers are trying to bank the bank.



We here, in general, map banking logic to the Java Semaphore. In the Semaphore constructor, we will have an initial balance. We start at -42 overdraft and then we map some of Semaphore's methods to banking methods. That is, drainPermits - this will “take all the money from the bank” from us, while availablePermits will “check the balance”.

 public class PerfectRobbery {   private Semaphore bankAccount = new Semaphore(-42);   public static void main(String[] args) {           PerfectRobbery perfectRobbery = new PerfectRobbery();           perfectRobbery.takeAllMoney();           perfectRobbery.checkBalance();  }  public void takeAllMoney(){       bankAccount.drainPermits(); } public void checkBalance(){        System.out.println(bankAccount.availablePermits()); } } 

And then we will have some logic. We create the PerfectRobbery object in the PerfectRobbery class and call two methods: take all the money from the bank and check that we really took all the money.

How can I create a Semaphore with a negative initial value? This is an excellent question, because this is the first answer. And besides him, I have three more.

A. IllegalArgumentException - no negative balance semaphore can be created;
B. UnsupportedOperationException - you can create a negative balance semaphore, but you cannot call drainPermits on it;
C. 0 - drainPermits with a negative balance will leave zero permits;
D. -42 - drainPermits with a negative balance will leave as much as it was, because there is nothing to drain.

Voting in the audience showed that the majority - for option D, and the correct answer - C.

You can find in Java documentation that the semaphore can be negative. In addition, it says there that drainPermits returns all available permits.



How much do we have when we have -42 permits? We have 0 available, so Sergey Kurenko opened the bug and said: guys, you have something nonsense. Drain available permits at -42 should leave -42, because available permits 0. When we merge 0 out of -42, it will be -42.



But this was not the case, because Doug Lee came to the comments and said: “I want it so much! And so I will “fix this by adding a line to Javadoc.”



Java puzzle # 2: Singletons


Let's go further. A little advice from us: do not create singletons, drink singletons better.



Let's take a look at Java 7. You can create empty lists there using emptyList and empty iterators using emptyIterator .

 Collections.emptyList() == Collections.emptyList(); Collections.emptyIterator() == Collections.emptyIterator(); 

And here is the question: are they singltons? Does the same object always return to us? We have four possible answers:

A. true / true - always returns;
B. true / false - singleton is just a list, and the iterator is different each time;
C. false / true - the iterator is a singleton, and a list is created each time;
D. false / false is not a singleton at all.

Voting in the audience showed that the majority is for option B, and the correct answer is A. Here the question arises: where is the puzzle? Puzzler will be next.

Let us turn to Java 8. In it, new methods have appeared that are returned to us by empty pieces: splitter and stream.

 Spliterators.emptySpliterator() == Spliterators.emptySpliterator(); Stream.empty() == Stream.empty(); 

And let's repeat the question for them:

A. true / true
B. true / false
C. false / true
D. false / false

Voting in the audience showed that the majority is for option D, and the correct answer is B. The splitter can be a singleton because it has no state: you can try to get around the empty splitter many times, it will say that there is nothing to get around. However, the stream has a state, and it consists of at least two things: firstly, you can hang closeHandler on the stream. Imagine that if it were a singleton, you would have one handler hung on it in one place and another handler in another, and you won’t know what happens after that. Of course, every empty stream should be free, independent. Secondly, the stream should be used only once. If the stream is reused, it detects it and throws an IllegalStateException .



Java-puzzler number 3: the same lists


In the next puzzle we use the word “identical” in a somewhat strange sense. The same - this is the same as the internal structure. This does not mean that they are equal in equals or their hashcode is the same, and does not mean that they are related to the verification of references.

We create an array of two lists. In Java 8, the setAll method setAll , which allows you to fill it all at once. We fill it with the ArrayList constructor. We get an array consisting of two lists:

 List[] twins = new List[2]; Arrays.setAll(twins, ArrayList::new); 

Question: what lists will be there? Answer options:

A. Same empty lists
B. Same non-empty lists.
C. Not identical empty lists.
D. Not the same non-empty lists.

Voting in the audience showed that the majority - for option A, and the correct answer - C.

First, setAll accepts not a supplier , but inputInt Function , which is passed to the array index and, accordingly, this array index as an input. It automatically maps to the ArrayList constructor not from an empty argument, but from initialCapacity . That is, this index, which is transmitted there, is not registered anywhere and is not visible. And this is just some kind of Groovy: we are writing something, and we are doing something, and we do not know what.



By the way, we can fly to OutOfMemory thanks to this. If we created an array of 100 thousand, we would have lists in which there would be predefined arrays inside also of 100 thousand.

Java puzzle number 4: Single Abstract Method


Let's try to create a functional interface. At first we will create a simple interface, but we will stick four methods into it, three of them will be abstract. And then we inherit from it another interface and make it functional. Does it compile?

 public interface <T> {      default void (String ) {                 System.out.println(); }  void (T songName);  void (T );  void (String ); } @FunctionalInterface public interface  extends <String> { } 

Here are the answers:

A. What kind of nonsense ?! 'Single' means one method, not three!
B. The problem with the method is (T) , if you remove it, everything is OK
C. The problem with the methods of , if you remove one, then all
will be ok.
D. All the way! Duplicates collapse, and we stay with one to .

Voting in the audience showed that the majority is for option D, and the correct answer is B. The fact is that the method that is not implemented ( ) does not overlap with the default implementation, and the compiler cannot decide what to use. This is written in the documentation: you can inherit from the interface several abstract methods with override-equivalent signatures. When we determined that T is a string, two methods collapsed, which is good. But if the interface inherits the default method, and its signature is override-equivalent to the abstract method, this is a compilation error because ambiguity arises: whether we want to use the default implementation or not.


Screenshot from Java Language Specification

Java Puzzle 5: How to hack a bank. Second version


All banking software is written in Java. Alpha Bank is Java, Deutsche Bank is Java, Sberbank is Java. All banks write in Java, which means an attack on Java, there are many holes in it, it is easy to hack it, then find the largest accounts and withdraw money from them.

 Set<String> accounts =      new HashSet<>(Arrays.asList("Gates", "Buffett", "Bezos", "Zuckerberg")); System.out.println("accounts= " + accounts); 

Let's collect them in a set and print them. Interestingly, we will see them in the same order in which we brought them?

A. The order of ad is preserved
B. Order unknown, but persists between runs.
C. Order is unknown and changes each time JVM is restarted.
D. Order is unknown and changes with each printout.

Voting in the audience showed that the majority is for option B, and this is the correct answer. Everyone knows that inside a hashset is a hashmep, and in a hashmep there are keys.



We need to do something about it! Therefore, we turn to Early Access Release Java 9 (banks always do this, they use all the freshest). And here everything becomes more interesting, since the Set.of method appeared Set.of , so that instead of all this long one can write shortly.

 Set<String> accounts = Set.of("Gates", "Buffett", "Bezos", "Zuckerberg"); System.out.println("accounts= " + accounts); 

The question remains the same: if you put in a set and print, we will see invoices in the same order in which we brought them?

A. The order of the advertisement
B. Order unknown, but persists between runs.
C. Order is unknown and changes each time JVM is restarted.
D. Order is unknown and changes with each printout.

Voting in the audience showed that the majority - for option C, and this is the correct answer. We have proof. We can take advantage of the great new thing that appeared in the ninth Java and is called JShell . We thrust this code, we get some order, we repeat, we get another order, we repeat, we get the third order. How it works?



This is done on purpose. This is how the table element is calculated by the hash code in this Set.of itself:

 private int probe(Object pe) { int idx = Math.floorMod(pe.hashCode() ^ SALT, elements.length); while (true) {      E ee = elements[idx];      if (ee == null) {               return -idx - 1;      } else if (pe.equals(ee)) {               return idx;      } else if (++idx == elements.length) {               idx = 0;     } } } 

You see that there is a hash code ^ SALT , and SALT is a static field that is initialized with a random number when the JVM is started. This was done on purpose, because too many people were laid on the order of the hash set, when it was not defined and when the documentation read in black and white: “do not pledge on it”. Therefore, it was done in such a way that when you try to log in, you simply restart the JVM, and this will no longer work. You just can't do it. Although there is a danger: some may assume that this thing works randomly.

Java Puzzleplayer # 6: Jigsaw


There are several statements about Jigsaw . Try to guess which one is correct:

A. If you make a jigsaw application a module, the dependencies in the classpath will be loaded correctly
B. If one of the dependencies is a jigsaw module, then it is necessary to register the module-info file
C. If you have registered the module-info file, then all dependencies will have to be written twice, in the classpath and in module-info
D. None is not true

The correct answer is C. Of course, you will have to prescribe everything twice. The good news is that Gradle and Maven will generate both of these components for you: the correct classpath and the correct module-info , so you don’t have to handle it. But if you do not work with these tools, you will have to do it twice, although there is a nuance. You can use the module-path checkbox, and it has its own puzzler, but about it next time.

Java Puzzle Number 7: The Expendables 2


We have a collection of Expendables, and we want to destroy them all. We will destroy it this way: take an iterator and call its forEachRemaining method. And for each element we will do such a thing: if there is the next record, then we move and destroy (and this is all inside forEachRemaining ).

 static void killThemAll(Collection<Hero> expendables) {  Iterator<Hero> heroes = expendables.iterator();  heroes.forEachRemaining(e -> {           if (heroes.hasNext()) {                heroes.next();                heroes.remove();       }  });  System.out.println(expendables); } 

What are the options?

A. Everyone died
B. Only even died
C. All survived
D. Only the odd died
E. All answers are correct.

The correct answer is E. This is an undefined behavior. Here you can try to submit different collections, and if you try to do this, we get different results.

If we submit an ArrayList here, we will get everyone dead.

 killThemAll(new ArrayList<String>(Arrays.asList("N","S","W","S","L","S","L","V"))); []< /source>     <code>LinkedList</code>,   ,    <source lang="java">killThemAll(new LinkedList<String>(Arrays.asList("N","S","W","S","L","S","L","V"))); [S,S,S,V] 

If we submit ArrayDeque here, then everyone will stay alive, and no exceptions.

 killThemAll(new ArrayDeque<String>(Arrays.asList("N","S","W","S","L","S","L","V"))); [N,S,W,S,L,S,L,V] 

And if we give the TreeSet here, then, on the contrary, the odd ones will die.

 killThemAll(new TreeSet<String>(Arrays.asList("N","S","W","S","L","S","L","V"))); [N,W,L,L] 

Therefore, never! Never do that! In fact, it happened by chance - simply because no one thought that someone would do that. When we reported this to Oracle, they did what? That's right, “fixed this problem” by writing about it in the documentation:



Java Puzzle 8: An Imperceptible Difference


We want to create an original, real Adidas in the form of a predicate that will verify that this is really Adidas. We create a functional interface, parameterize it with some type T and, accordingly, implement it as Lambda or as methodRef :

 @FunctionalInterface public interface OriginalPredicate<T> {   boolean test(T t); } OriginalPredicate<Object> lambda = (Object obj) -> "adidas".equals(obj); OriginalPredicate<Object> methodRef = "adidas"::equals; 

Question: will this all be compiled or not?

A. Both compiled
B. Lambda will compile, reference to method is not.
C. Method reference compiles, lambda does not
D. Non-functional interface!

The correct answer is A, there is actually no puzzler at all. But let's make a functional interface made in china.

 @FunctionalInterface public interface CopyCatPredicate {  <T> boolean test(T t); } CopyCatPredicate lambda = (Object obj) -> "adadas".equals(obj); CopyCatPredicate methodRef = "adadas"::equals; 

What is the difference from the previous code? In addition to adadas, we moved generic from the interface itself to the method, and now we don’t have a generic class, but a generic method. Can we create a functional interface with a generic method?

A. Both compiled
B. Lambda will compile, reference to method –no
C. Method reference compiled, lambda –no
D. Non-functional interface!

The correct answer is C. You have been warned - the method is better. Lambda cannot implement the generic method. In Lambda, we pass a parameter; we have to specify a type for it. Even if we do not specify, it must be some sort of output, but in order for it to be derived, we must have a generic variable. That is, there you need to make a generic-lambda, somewhere in the corner brackets write T or not T (we can use another letter). But there is no such syntax, they did not come up and then decided that, well, let's, okay, but for Lambda it will not work.

 @FunctionalInterface public interface CopyCatPredicate {  <T> boolean test(T t); } CopyCatPredicate lambda = (Object obj) -> "adadas".equals(obj); 

And with the reference method, everything is fine, there is no such problem. Therefore, again, if something goes wrong with us, you need to add documentation.



Java Puzzle Number 9: what conference to attend?


You want to go to a conference, there are many conferences. You want to filter them, TreeSet them into the TreeSet and, accordingly, print the result.

 List<String> list = Stream.of("Joker", "DotNext", "HolyJS", "HolyJS", "DotNext", "Joker").sequential()            .filter(new TreeSet<>()::add).collect(Collectors.toList()); System.out.println(list); 


What will you get?

A. Sorted and filtered [DotNext, HolyJS, Joker]
B. Exactly what was at the beginning [Joker, DotNext, HolyJS, HolyJS, DotNext, Joker]
C. Initially, but filtered [Joker, DotNext, HolyJS]
D. Sorted but not filtered [DotNext, DotNext, HolyJS, HolyJS, Joker, Joker]

The correct answer is C. The filtering will work because this is the reference method, and there will be one TreeSet object. Newbies think that the reference and Lambda methods are almost the same, but they are not exactly the same. If we had written Lambda, the new TreeSet would be created every time, and since this is the reference method, it is created once before we do all of this filtering, and the reference method is attached to it. And nothing is sorted because we do not use what in the TreeSet as a result, we just use the add method as a filter that answers us with true or false (need to throw out duplicates or not). In fact, you could write just distinct, and it would be the same. The result of this triset will be GarbageCollector GarbageCollector then, and no one knows what will be there.



findings


Java is getting better, and there are more ways to shoot yourself in the foot. Therefore, here are a couple of tips:


If you come across a puzzler, send it to puzzlers@jfrog.com, we will be happy to spend the third season at one of the following conferences. In exchange for a valuable copy, we will send you a branded T-shirt.



If you like to savor all the details of Java development in the same way as we do, you probably will be interested in these reports at our April JPoint 2018 conference:

Source: https://habr.com/ru/post/352438/


All Articles