Java Puzzlers NG S02:さらに素晴らしい、素晴らしい

Tagir Valeev( lany )とBaruch Sadogursky( jbaruch )は、Javaパズルの新しいコレクションを作成し、急いでそれらを共有しています。





この記事は、秋の会議JPoint 2017でのスピーチのデコードに基づいています。Java8の地平線でJava 8とかろうじて迫り来る謎がいくつあるかを示しています。私たちを混乱させるために。



彼らが話していることはすべて、それぞれ最新バージョンのJava 8および9で動作するはずです。 私たちはチェックしました-すべてが正直であるようです:それが書かれているように、これはそれがどのように動作するかです。



念のために、著者についてのいくつかの言葉は、あなたが既によく知っていると思いますが、BaruchはJFrogでDeveloper Relationsに従事しており、TagirはIntelliJ IDEA開発者であり、StreamExライブラリの作成者です。



Javaパズルの番号1:銀行を飛び越える方法



ウォームアップするために、最初の謎解きを求めます。未知のアメリカのハッカーが銀行を飛び越えようとしています。







基本的に、銀行のロジックをJava Semaphoreにマッピングしています。 Semaphoreコンストラクターには、初期バランスがあります。 -42オーバードラフトから開始し、セマフォメソッドを銀行取引メソッドにマッピングします。 つまり、 drainPermits



これは「銀行からすべてのお金を取る」ことavailablePermits



が、 availablePermits



「残高を確認する」ことです。



 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()); } }
      
      





そして、いくつかのロジックがあります。 PerfectRobbery



クラスでPerfectRobbery



オブジェクトを作成し、2つのメソッドを呼び出します。銀行からすべてのお金を収集し、実際にすべてのお金を受け取ったことを確認します。



負の初期値でセマフォを作成するにはどうすればよいですか? これは最初の答えなので、素晴らしい質問です。 そして、彼のほかに、さらに3つあります。



A. IllegalArgumentException



負のバランスのセマフォを作成できません。

B. UnsupportedOperationException



負のバランスのセマフォを作成できますが、その上でdrainPermitsを呼び出すことはできません。

C. 0-マイナスの残高のdrainPermits



は許可をゼロのままにします。

D. drainPermits



マイナスのバランスを持つdrainPermits



は、排出するものがないため、元のままになります。



聴衆の投票では、大半がオプションDであり、正解はCであることが示されました。



Javaのドキュメントでは、セマフォが負の値をとることができるという記述を見つけることができます。 さらに、 drainPermits



は使用可能なすべての許可を返します。







-42パーミットがある場合、どれくらい利用できますか? 0が利用可能であるため、Sergey Kursenkoがバグをオープンし、次のように言いました。 -42で利用可能な許可をドレーンすると、利用可能な許可0があるため、-42のままになります。-42から0をマージすると、-42になります。







しかし、そこにあったのは、Doug Leeがコメントに参加して言ったからです。 そのため、Javadocに行を追加して「修正」します。







Java Puzzler#2:シングルトーン



さらに進みましょう。 私たちからのちょっとしたヒント:シングルトーンを作成しないでください。シングルトーンを飲むことをお勧めします。







Java 7を見てみましょうemptyList



を使用して空のリストを作成し、 emptyList



を使用して空のイテレーターを作成できます。



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





そして質問は:彼らはシングルトンですか? 常に同じオブジェクトを返しますか? 4つの可能な答えがあります。



A. true / true-常に戻ります。

B. true / false-シングルトン-リストのみ。反復子は毎回異なります。

C. false / true-イテレーター-シングルトン。新しいリストが毎回作成されます。

D. false / false-これらはシングルトーンではありません。



聴衆の投票では、大半がオプションBであり、正解はAであることが示されました。ここで質問が始まります。 パズラーが次に来ます。



Java 8に進みましょう。空のオブジェクトを返す新しいメソッド、スプリッターとストリームがあります。



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





そして、彼らのために質問を繰り返しましょう:



A. true / true

B. true / false

C. false / true

D. false / false



聴衆の投票では、大半がオプションDであり、正解はBであることが示されました。状態がないため、スプリッターはシングルトンになります。空のスプリッターを何回もバイパスしようとすることができます。 ただし、ストリームには状態があり、少なくとも2つの要素で構成されています。最初に、ストリームにcloseHandler



をハングさせることができます。 それがシングルトンである場合、ある場所で1つのハンドラーを、別の場所で別のハンドラーを掛け、その後何が起こるかわからないことを想像してください。 もちろん、すべての空のストリームは、独立したフリーである必要があります。 第二に、ストリームは一度だけ使用する必要があります。 ストリームが再利用される場合、これを検出してIllegalStateException



をスローします。







Java Puzzler#3:同じリスト



次のパズルでは、やや奇妙な意味で「同一」という言葉を使用します。 同一-これらは、内部構造に関して同じです。 これは、それらが等しい、または同じハッシュコードを持つことを意味するものではなく、参照のチェックに関連していることを意味するものでもありません。



2つのリストの配列を作成します。 Java 8ではsetAll



メソッドが導入されたため、すぐにすべてを埋めることができます。 ArrayList



コンストラクタで埋めます。 2つのリストで構成される配列を取得します。



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





質問:どのリストがありますか? 回答オプション:



A.同一の空のリスト

B.同一の空でないリスト

C.同じ空のリストではない

D.同じではない空のリスト



聴衆の投票では、大半がオプションAであり、正解はCであることが示されました。



最初に、 setAll



supplier



受け入れず、 inputInt Function



受け入れますinputInt Function



には、配列インデックスが渡され、それに応じてこの配列インデックスが引数として渡されます。 空の引数からではなく、initialCapacityからArrayList



コンストラクターに自動的にマッピングします。 つまり、そこで送信されるこのインデックスはどこにも登録されず、表示されません。 これはGroovyの一部です。私たちは何かを書いており、何かをしているのですが、何を知っているのかわかりません。







ところで、これのおかげでOutOfMemory



飛ぶことができます。 10万の配列を作成した場合、リストには10​​万の事前定義された配列も含まれます。



Java Puzzler No.4:単一の抽象メソッド



機能的なインターフェースを作成してみましょう。 まず、インターフェイスを作成しますが、4つのメソッドを追加します。そのうち3つは抽象的です。 そして、そこから別のインターフェイスを継承し、機能的にします。 コンパイルしますか?



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





答えは次のとおりです。



A.一体何!? 「シングル」とは、3つではなく1つのメソッドを意味します!

B. (T)



メソッドの問題(T)



、削除しても問題はあり(T)





C.







する方法に問題がある

大丈夫です。

D.最後まで! 重複が崩壊し、







ために残されます。



聴衆の投票では、大半がオプションDであり、正解はBであることが示されました。実際、実装されていないメソッド(



)はデフォルトの実装によってブロックされておらず、コンパイラは使用するものを決定できません。 これはドキュメントに書かれています。インターフェイスから、オーバーライドと同等のシグネチャを持ついくつかの抽象メソッドを継承できます。 Tが文字列であると判断したとき、2つのメソッドは崩壊しました。 しかし、インターフェイスがデフォルトのメソッドを継承し、そのシグネチャが抽象メソッドと同等のオーバーライドである場合、あいまいさが生じるため、これはコンパイルエラーです。デフォルトの実装を使用するかどうか。





Java言語仕様のスクリーンショット



Javaパズル番号5:銀行をハッキングする方法。 第二版



すべての銀行ソフトウェアはJavaで書かれています。 Alfa BankはJava、Deutsche BankはJava、SberbankはJavaです。 すべての銀行はJavaで記述します。これはJavaの攻撃を意味し、多くのホールがあり、ハッキングが容易で、その後最大の口座を見つけてそこからお金を引き出します。



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





それらをまとめて印刷しましょう。 私たちがそれらを手に入れたのと同じ順序で見るのだろうか?



A.広告の順序は維持されます

B.順序は不明ですが、実行の間に残ります

C.順序は不明で、JVMを再起動するたびに変わります

D.順序は不明で、印刷ごとに変わります。



聴衆の投票では、大半がオプションBのものであり、これが正解であることが示されました。 ハッシュの内部がハッシュであり、ハッシュにはキーがあることは誰もが知っています。







これには何か関係があります! そのため、アーリーアクセスリリースJava 9に切り替えています(銀行は常にこれを行い、最新のものをすべて使用します)。 Set.of



メソッドがSet.of



に表示されたため、ここですべてがより興味深いものになりSet.of



。そのため、この長いすべての代わりに、簡単に書くことができます。



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





質問は同じままです。セットに入れて印刷すると、アカウントを取得したのと同じ順序で表示されますか?



A.広告の順序は維持されます

B.順序は不明ですが、実行の間に残ります

C.順序は不明で、JVMを再起動するたびに変わります

D.順序は不明で、印刷ごとに変わります。



聴衆の投票では、大半がオプションCであり、これが正解であることが示されました。 証拠があります。 9番目のJavaに登場し、 JShell



と呼ばれる新しい素晴らしいものを利用できます。 このコードを詰め込み、何らかの順序を取得し、繰り返し、異なる順序を取得し、繰り返し、3番目の順序を取得します。 どのように機能しますか?







これは意図的に行われます。 これは、同じSet.of



ハッシュコードによってテーブル要素が計算される方法Set.of







 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;     } } }
      
      





ハッシュコード^ SALT



があり、SALTはJVMの起動時に乱数で初期化される静的フィールドです。 これは意図的に行われました。なぜなら、ハッシュセットの順序が定義されていないとき、および白黒のドキュメントが「それの上に置かないで」と書いたときに、あまりにも多くの人がハッシュセットの順序を決めたからです。 したがって、開始しようとしたときにJVMを再起動するだけで、これは機能しなくなります。 あなたはそれを手に入れることができません。 危険はありますが、このことは偶然に機能すると考える人もいます。



Java Puzzler#6:ジグソーパズル



Jigsaw



に関するいくつかの声明があります。 どちらが正しいかを推測してみてください。



A.ジグソーパズルアプリケーションをモジュールにすると、 classpath



依存関係が正しく読み込まれます

B.依存関係の1つがジグソーモジュールである場合は、 module-info



ファイルを登録してmodule-info





C. module-infoファイルを登録した場合、すべての依存関係をclasspath



module-info



2回書き込むclasspath



ありmodule-info





D.何も真実ではない



正解はCです。もちろん、すべてを2回処方する必要があります。 良いニュースは、 Gradle



Maven



がこれらのコンポーネント(正しいclasspath



と正しいmodule-info



)の両方を生成することです。したがって、ペンでこれを行う必要はありません。 ただし、これらのツールを使用しない場合は、微妙な違いはありますが、2回実行する必要があります。 module-path



フラグを使用できます。これには独自のパズル機能がありますが、次回はそれについてです。



Java Puzzler#7:エクスペンダブルズ2



The Expendablesのコレクションがあり、それらをすべて破壊したいと考えています。 このように破棄します。イテレータを取得し、そのforEachRemaining



メソッドを呼び出します。 そして、各要素に対してこのようなことを行います。次のレコードがある場合、移動して破棄します(これはすべてforEachRemaining



内にあり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); }
      
      





オプションは何ですか?



A.みんな死んだ

B.偶数人だけが死んだ

C.全員が生き残った

D.奇数のみが死亡した

E.すべての答えは正しいです。



正解はEです。これは未定義の動作です。 ここで、異なるコレクションを送信することができます。これを実行しようとすると、異なる結果が得られます。



ここでArrayList



を指定すると、全員が死亡していることがわかります。



 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]
      
      





ここでArrayDeque



を提供する場合、全員が生存し、例外はありません。



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





そして、ここでTreeSet



すると、反対に、奇妙なものが死にます。



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





したがって、決して! 絶対にしないでください! 実際、それは偶然に起こりました-誰かがそれをするだろうと誰も考えなかったからです。 これをOracleに報告したとき、彼らは何をしましたか? そうです、ドキュメントにそれについて書くことで「この問題を修正しました」:







Java Puzzler#8:微妙な違い



オリジナルの本物のアディダスを、それが本当にアディダスであることを検証する述語の形で作成したいと思います。 機能的なインターフェースを作成し、それを何らかのT型でパラメーター化し、それに応じてLambdaの形式またはmethodRef



の形式でmethodRef



ます。



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





質問:これはすべてコンパイルされていますか?



A.両方がコンパイルされます

B. Lambdaコンパイル、メソッドリファレンス-いいえ

C.メソッド参照はコンパイルされますが、ラムダはコンパイルされません

D.機能的インターフェースではありません!



正解はAです。パズルはほとんどありません。 しかし、中国製の機能的なインターフェースを作りましょう。



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





前のコードとの違いは何ですか? アダダに加えて、インターフェイス自体からメソッドにジェネリックを転送し、ジェネリッククラスではなくジェネリックメソッドを取得しました。 ジェネリックメソッドを使用して機能的なインターフェイスを作成できますか?



A.両方がコンパイルされます

B. Lambdaコンパイル、メソッドリファレンス-いいえ

C.メソッド参照はコンパイルされますが、ラムダはコンパイルされません

D.機能的インターフェースではありません!



正解はSです。メソッドの方が優れていると警告されました。 Lambdaはジェネリックメソッドを実装できません。 Lambdaでは、パラメーターを渡し、それに型を指定する必要があります。 表示しない場合でも、何らかの方法で表示する必要がありますが、表示するには、ジェネリック変数が必要です。 つまり、そこでは汎用のラムダを作成する必要があります。山括弧内のどこかにTを書き込むかTを書き込まないでください(別の文字を使用できます)。 しかし、そのような構文はありません。彼らはそれを思い付かず、それから、大丈夫ですが、ラムダでは機能しないと決めました。



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





また、メソッド参照を使用すれば、すべて問題ありません。そのような問題はありません。 したがって、何かがうまくいかない場合は、ドキュメントを追加する必要があります。







Javaパズル番号9:参加する会議は?



会議に行きたい、多くの会議があります。 それらをフィルタリングし、それらをTreeSet



に入れ、それに応じて結果を印刷します。



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







あなたは何を得ますか?



A.ソートおよびフィルター処理[DotNext、HolyJS、Joker]

B.当初の内容[Joker、DotNext、HolyJS、HolyJS、DotNext、Joker]

C.初期だがフィルター済み[Joker、DotNext、HolyJS]

D.ソートされているがフィルタリングされていない[DotNext、DotNext、HolyJS、HolyJS、Joker、Joker]



正解はCです。これはreference



メソッドであり、1つのTreeSet



があるため、フィルタリングは機能します。 初心者は、 reference



メソッドとLambdaメソッドはほとんど同じものだと思いますが、まったく同じものではありません。 LambdaをTreeSet



た場合、新しいTreeSet



が毎回作成されます。これは参照メソッドであるため、このフィルタリングをすべて行う前に一度作成され、参照メソッドがアタッチされます。 結果としてTreeSetにあるものを使用しないため、何もソートされません。trueまたはfalseのいずれかを返すフィルターとしてadd



メソッドを使用するだけです(重複を破棄する必要があるかどうか)。 実際、あなたはまったく別個に書くことができ、それは同じです。 その後、このトリセットの結果はGarbageCollector



、何がそこにあるかは誰にもわかりません。







結論



Javaの性能は向上しており、足を踏み入れる方法も増えています。 したがって、ここにいくつかのヒントがあります。





パズルに出くわした場合は、puzzlers @ jfrog.comに送信してください。次のカンファレンスのいずれかで3シーズン目を過ごすことができます。 貴重なコピーと引き換えに、ブランドTシャツをお送りします。






私たちと同じようにJava開発のすべての詳細を楽しみたい場合は、おそらく4月のJPoint 2018カンファレンスでこれらのレポートに興味があるでしょう。






All Articles