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 [] = {{}}; }
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 の整数キャッシュをオーバーライドし、自動ボックス化と自動アンボックス化を使用することにあります。大人の許可なしにこれを行わないでください。言い換えると、
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
;
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 4) D
class Test
// Doesn't compile Test
execute((Serializable) (() -> {}));
execute((Runnable & Serializable) (() -> {}));
GO TO FULL VERSION