この記事は、秋の会議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の性能は向上しており、足を踏み入れる方法も増えています。 したがって、ここにいくつかのヒントがあります。
- 読み取り可能なコードを記述します。
- 抵抗できない場合は、すべてのトリックにコメントしてください。
- Javaでさえ、あなたを混乱させるバグがあります。
- 静的コードアナライザールール! IntelliJ IDEAを使用します。
- すべてのバグはドキュメントに行を追加することで修正されるため、ドキュメントを知る必要があります。
- str孔を取得しないでください。 ちなみに、最新のIDEAでは、疲れている場合はストリームをサイクルに変えることができます。
パズルに出くわした場合は、puzzlers @ jfrog.comに送信してください。次のカンファレンスのいずれかで3シーズン目を過ごすことができます。 貴重なコピーと引き換えに、ブランドTシャツをお送りします。
私たちと同じようにJava開発のすべての詳細を楽しみたい場合は、おそらく4月のJPoint 2018カンファレンスでこれらのレポートに興味があるでしょう。
- JVMアプリケーション用のLinuxコンテナパフォーマンスツール (Sasha Goldshtein、Sela Group)
- プログラムの分析:優れたプログラマーであることを理解する方法 (Alexey Kudryavtsev、JetBrains)
- 大規模プロジェクトでのソフトウェア開発の典型的な問題 (Rustam Mehmandarov、Computas AS)