JavaRush /Java Blog /Random-JA /Java についてあなたが知らなかった 10 のこと
minuteman
レベル 32

Java についてあなたが知らなかった 10 のこと

Random-JA グループに公開済み
それで、最近 Java を使い始めましたか? それが「Oak」と呼ばれていた頃、オブジェクト指向がまだ注目の話題だった頃、C++ の人々が Java にはチャンスがないと考えていた頃、そして誰もアプレットのことさえ聞いていなかった頃を覚えていますか? あなたは次のことの半分も知らないと思います。Java の内部動作に関するいくつかの素晴らしい驚きから週を始めましょう。Java についてあなたが知らなかった 10 のこと - 11. チェック例外などというものはありません。 それは正しい!JVM はそのようなことについては知りません。Java 言語だけが知っています。今日では誰もが、チェック例外が間違いであったことに同意しています。Bruce Eckel がプラハの GeeCON での最後の講演で述べたように、Java はチェック例外を使用するため、他の言語は使用できません。Java 8 ですら、新しい Streams API でチェック例外をカバーしなくなりました (ラムダが IO または JDBC を使用する場合、少し面倒になる可能性があります)。 )。JVM がそのようなことを知らないという証拠が必要ですか? 次のコードを試してください。 これはコンパイルされるだけでなく、SQLException もスローされます。これには Lombok の @SneakyThrows を使用する必要さえありません。 2. 戻り値の型のみが異なるメソッドをオーバーロードできます。public class Test { // No throws clause here public static void main(String[] args) { doThrow(new SQLException()); } static void doThrow(Exception e) { Test. doThrow0(e); } @SuppressWarnings("unchecked") static void doThrow0(Exception e) throws E { throw (E) e; } } これではコンパイルできませんね? class Test { Object x() { return "abc"; } String x() { return "123"; } } 右。Java 言語では、スローや戻り値の型の違いに関係なく、同じクラス内で 2 つのメソッドを同時に同等にオーバーライドすることはできません。でも、ちょっと待ってください。Class.getMethod(String, Class…) についてドキュメントをもう一度確認してください。 Java 言語では同じシグネチャでも戻り値の型が異なる複数のメソッドを禁止しているのに対し、Java 仮想マシンでは禁止していないため、クラス内に対応するメソッドが複数存在する可能性があることに注意してください仮想マシンのこの柔軟性を利用して、さまざまな言語機能を実装できます。 たとえば、共変の戻り値はブリッジ メソッドを使用して実行できます。 ブリッジ メソッドとオーバーライドされたメソッドのシグネチャは同じですが、戻り値の型が異なります。 うわー、それは理にかなっています。次のように記述すると、実際には非常に多くの処理が行われます。 生成されたバイトコードを見てください。 つまり、 t は実際にはバイトコード内のオブジェクトです。これはよくわかります。呼び出しの特定の部分では Parent.x() の戻り値の型が予期されるため、合成ブリッジ メソッドは実際にはコンパイラによって生成されます。このようなブリッジ メソッドを使用せずにジェネリックを追加することは、バイナリ表現では不可能になります。したがって、そのような機能を許可するように JVM を変更すると、手間が少なくなります (これにより、副作用として共変メソッドのオーバーライドも可能になります...) 賢明ですね? 3. 以下はすべて 2 次元配列です。 これは実際に真実です。たとえあなたのメンタルアナライザーが上記のメソッドからの戻り値の型をすぐに理解できなかったとしても、それらはすべて同じです。次のコードのように。 これはおかしいと思いますか? 書く機会が増えるだけで想像力が爆発します! 注釈を入力します。 威力に次ぐ謎を秘めた装置。 言い換えると、4 週間の休暇の直前に最後のコミットを行ったときです。 ご希望の方法で使用することを許可します。 4. 条件分岐は取得できない では、条件分岐を使い始めたときには、すでに条件分岐についてすべてを知っていると思っていましたか? がっかりさせてください - あなたは間違っていました。ほとんどの人は、次の 2 つの例が同等であると考えるでしょう。 これと同等ですか? いいえ。簡単なテストを使用してみましょう。 プログラムは次のように出力します 。条件演算子は、必要に応じて型キャストを実行します。そうしないと、プログラムが NullPointerException をスローすることが期待されるからですか? 5. 複合代入演算子も取得できません。 機知に富むだけで十分ですか?次の 2 つのコード スニペットを見てみましょう。 abstract class Parent { abstract T x(); } class Child extends Parent { @Override String x() { return "abc"; } } // Method descriptor #15 ()Ljava/lang/String; // Stack: 1, Locals: 1 java.lang.String x(); 0 ldc [16] 2 areturn Line numbers: [pc: 0, line: 7] Local variable table: [pc: 0, pc: 3] local: this index: 0 type: Child // Method descriptor #18 ()Ljava/lang/Object; // Stack: 1, Locals: 1 bridge synthetic java.lang.Object x(); 0 aload_0 [this] 1 invokevirtual Child.x() : java.lang.String [19] 4 areturn Line numbers: [pc: 0, line: 1] class Test { int[][] a() { return new int[0][]; } int[] b() [] { return new int[0][]; } int c() [][] { return new int[0][]; } } class Test { int[][] a = {{}}; int[] b[] = {{}}; int c[][] = {{}}; } @Target(ElementType.TYPE_USE) @interface Crazy {} class Test { @Crazy int[][] a1 = {{}}; int @Crazy [][] a2 = {{}}; int[] @Crazy [] a3 = {{}}; @Crazy int[] b1[] = {{}}; int @Crazy [] b2[] = {{}}; int[] b3 @Crazy [] = {{}}; @Crazy int c1[][] = {{}}; int c2 @Crazy [][] = {{}}; int c3[] @Crazy [] = {{}}; } Java について知らなかった 10 のこと - 2 Object o1 = true ? new Integer(1) : new Double(2.0); Object o2; if (true) o2 = new Integer(1); else o2 = new Double(2.0); System.out.println(o1); System.out.println(o2); 1.0 1 Integer i = new Integer(1); if (i.equals(1)) i = null; Double d = new Double(2.0); Object o = true ? i : d; // NullPointerException! System.out.println(o); i += j; i = i + j; 直感的には、それらは等しいはずですよね?しかしご存知のように、それらは異なります。JLS 仕様には、次のように記載されています。 タイプ E1 op = E2 の複合式は、E1 = (T) ((E1) op (E2)) と同等です。ここで、T は E1 のタイプですが、E1 が 1 回だけ評価される点が異なります。 良い例は *= または /= を使用することです : byte b = 10; b *= 5.7; System.out.println(b); // prints 57 or: byte b = 100; b /= 2.5; System.out.println(b); // prints 40 or: char ch = '0'; ch *= 1.1; System.out.println(ch); // prints '4' or: char ch = 'A'; ch *= 1.5; System.out.println(ch); // prints 'a' では、これはまだ有用なツールでしょうか? 6. ランダムな整数 ここからはさらに難しい作業に移ります。解決策は読まないでください。自分で答えを見つけられるかどうかを確認してください。次のプログラムを実行すると、 for (int i = 0; i < 10; i++) { System.out.println((Integer) i); } 次のような出力が得られることがあります。 92 221 45 48 236 183 39 193 33 84 しかし、これはどのようにして可能でしょうか? 答えは、リフレクションを介して JDK の整数キャッシュをオーバーライドし、自動ボックス化と自動アンボックス化を使用することにあります。大人の許可なしにこれを行わないでください。言い換えると、 Java についてあなたが知らなかった 10 のこと - 3 7. GOTO 私のお気に入りの 1 つです。JavaにはGOTOがある!これを書く int goto = 1; と、次のようになります。 これは、goto が未使用の予約語だからです。念のため…しかし、興味深いのはそこではありません。素晴らしいのは、break、 continue、およびマークされたブロックと組み合わせた goto を含めることができることです: バイト コード内で 前方へジャンプ: バイト コード内で 後方へジャンプ : 8. Java には型エイリアスがあります 他の言語 (Ceylon など) では、型を定義できますエイリアスは非常に簡単です。 ここの People クラスは Set と交換できるように構築されています。 Test.java:44: error: expected int goto = 1; ^ label: { // do stuff if (check) break label; // do more stuff } 2 iload_1 [check] 3 ifeq 6 // Jumping forward 6 .. label: do { // do stuff if (check) continue label; // do more stuff break label; } while(true); 2 iload_1 [check] 3 ifeq 9 6 goto 2 // Jumping backward 9 .. interface People => Set ; : Java では、トップレベルでエイリアスを単純に定義することはできません。ただし、クラスまたはメソッドのニーズに応じてこれを行うことができます。Integer、Long などの名前に満足できないと仮定しましょう。そして短い名前が必要です: I と L。 簡単: 上の例では、Test クラスの可視性のために Integer が I に変換され、x() メソッドの必要性のために Long が L に変換されます。 このメソッドを次のように呼び出すことができます。 もちろん、このテクニックを真剣に受け止めるべきではありません。 この場合、Integer と Long は最終型です。これは、I と L が効率的な変換であることを意味します (変換はほぼ一方向のみです)。 非最終型 (たとえば、Object) を使用することにした場合は、通常のジェネリックを使用できます。 少し遊んだだけで十分でした。 非常に興味深いことに移りましょう。 9. 一部の型関係は決定不可能です。 さて、ここからは非常に興味深い内容になりますので、濃縮コーヒーを一杯飲みながら、次の 2 つのタイプを見てみましょう。 People? p1 = null; Set ? p2 = p1; People? p3 = p2; class Test { void x(I i, L l) { System.out.println( i.intValue() + ", " + l.longValue() ); } } new Test().x(1, 2L);// A helper type. You could also just use List interface Type {} class C implements Type > {} class D

implements Type >>> {} それでは、C と D は何を意味するのでしょうか? ある意味、それらは再帰的であり、java.lang.Enum の再帰と似ています。考慮してください: 上記の仕様を考慮すると、enum の実際の実装は単なる糖衣構文です: それを念頭に置いて、2 つの型に戻りましょう。次のコードはコンパイルできますか? 難しい質問ですが、実際には解決されていませんか? C は Type のサブタイプですか? これを Eclipse または Idea でコンパイルしてみてください。そうすれば、彼らが何を考えているかを教えてくれるでしょう。それを排水溝に流してください... Java の一部の型関係は決定不可能です。10. 型の交差 Java には、型の交差と呼ばれる非常に興味深い機能があります。実際には 2 つの型の共通部分である (ジェネリック) 型を宣言できます。例: Test クラスのインスタンスに関連付けるカスタム型パラメーター T には、Serializable インターフェイスと Cloneable インターフェイスの両方が含まれている必要があります。たとえば、String は制限できませんが、Date は制限できます。 この機能は Java8 で複数の用途があり、型をキャストできます。これはどのように役立ちますか? ほとんど何もありませんが、ラムダ式を必要な型にキャストしたい場合は、他に方法はありません。メソッドに非常にクレイジーな制限があるとします。Runnable が必要ですが、同時に別の場所で実行して結果をネットワーク経由で送信する場合にのみ Serializable になります。ラムダとシリアル化は少し皮肉を加えます。 ラムダ式のターゲット型と引数がシリアル化可能な場合は、ラムダ式をシリアル化できます。 ただし、これが真実であるとしても、Serializable インターフェイスが自動的に有効になるわけではありません。自分でこのタイプに持っていく必要があります。ただし、Serializable にのみキャストすると、 ラムダは Runnable ではなくなるため、両方の型にキャストします。 そして結論として: public abstract class Enum > { ... } // This enum MyEnum {} // Is really just sugar for this class MyEnum extends Enum { ... } class Test { Type c = new C(); Type> d = new D (); } Step 0) C Step 1) Type > >? Step 0) D > Step 1) Type >>> > Step 2) D >> Step 3) List >> > Step 4) D > >> Step . . . (expand forever) class Test { } // Doesn't compile Test s = null; // Compiles Test d = null; void execute(T t) {} execute((Serializable) (() -> {}));execute((Runnable & Serializable) (() -> {}));

Java は神秘的であると同時に強力です。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION