Java Rake

最近、当社では、開発者の間で、Java言語の落とし穴に関する知識をめぐる競争がありました。 受賞者には名誉と賞が与えられ、全員が多くの前向きなものと、反省と議論の理由を受け取りました。 ホットな追求では、クイズで表明された最も興味深い質問をhabraの一般の人々と共有します。 あなたがJavaやプロに慣れていないが、言語の暗黒面を磨きたいなら、catへようこそ。 そこでは、答え、正当化、結論を含む4つのトリッキーな質問があります。





プリミティブのパッキングに関する古典的なアコーディオンから始めましょう。

public static void main(String[] args) { System.out.println(Byte.valueOf((byte) 48) == Byte.valueOf((byte) 48)); System.out.println(Byte.valueOf((byte) 248) == Byte.valueOf((byte) 248)); System.out.println(Integer.valueOf(48) == Integer.valueOf(48)); System.out.println(Integer.valueOf(248) == Integer.valueOf(248)); }
      
      





答えは:
 true true true false
      
      







Java Language Specification(JLS)という本からほこりを一掃してください。 条項5.1.7。 バイトをボクシングするときに同じオブジェクトが返されることを保証します。 ボクシングintも。 ただし、-128..127の範囲のみです。 厳密に仕様に準拠したJVMは、最初の3つのケースでは同等の両方の部分に対して同じオブジェクトを返し、最後の2つの異なる部分では同じオブジェクトを返します。



UPD: JLSによると、 セニアがコメントで正しく述べているように、この-128..127の範囲外のintをキャッシュすることは可能ですが、必須ではありません。



道徳:ボクシングは難しいです。

別のモラル:混乱しないように、オブジェクトは==ではなくequals()で比較する必要があります。 2つのリンクが同じインスタンスを指していることを明示的に判断する必要がある場合を除きます。





先に進みます。 複雑な初期化の簡単な例。 このコードは何を出力しますか?

 class TrickyClass { { value = 10; } private int value = 20; { value = 30;} public int getValue() { return value; } public static void main(String[] args) { System.out.println(new TrickyClass().getValue()); } }
      
      





答えは:
 30
      
      





パラグラフ12.5を見てみましょう JLS オブジェクトの初期化の順序は次のように簡素化されます。

  1. 親クラスの初期化。
  2. フィールド初期化子とインスタンス初期化子(および中括弧で囲まれた奇妙なコードセクション-それが何であるか) を順番に実行します。
  3. クラスコンストラクターの実行。




この場合、初期化子{値= 30;}が最後に実行され、この値はフィールドに残ります。





初期化に関するもう1つの質問。 このコードは何を出力しますか?

 class Base { public Base() { System.out.println(getName()); } protected String getName() { return "Base";} } class Derived extends Base { private String name = "Derived"; @Override protected String getName() { return this.name;} public static void main(String[] args) { new Derived(); } }
      
      





答え
  null
      
      





12.5項によれば、すでに私たちに馴染みがある オーバーロードされたメソッドをコンストラクターから呼び出す場合、特別なルールはありません。 私たちの特定のケースでは、Derived子クラスメソッドがひっくり返り、nameフィールドの値を親コンストラクタに正直に返そうとします。 残念ながら、このフィールドの初期化はまだ行われていません。 フィールド初期化子は、親コンストラクターが呼び出された後に実行されます。 したがって、名前には有効なnullが含まれます



道徳 :コンストラクターで非最終メソッドを呼び出さないでください。 しかし、最終クラスがあれば、できます。





そして、デザートについては、少しシリアル化

 public class User implements Serializable { public final String name; public User(String name) { this.name = name; } private Object readResolve() { return new User("Darth Vader"); } public static void main(String[] args) throws Exception { User user = new User("Anakin Skywalker"); // Serialize user ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); new ObjectOutputStream(outputStream).writeObject(user); // Deserialize user ByteArrayInputStream inputStreamStream = new ByteArrayInputStream(outputStream.toByteArray()); User readUser = (User) new ObjectInputStream(inputStreamStream).readObject(); System.out.println(readUser.name); } }
      
      





答えは:
 Darth Vader
      
      







Javaでのシリアル化は広範で暗いトピックであるため、悪魔はそのためにを踏み入れます;別のJava Object Serialization Specificationがあります。 節3.7は、readResolveメソッドがデシリアライズ可能なオブジェクトで宣言されている場合、オブジェクトがストリームから読み取られた後にこのメソッドが呼び出されることを示しています。 そして、この特定のメソッドの結果は、逆シリアル化の結果と見なされます。 このトリックの標準的な使用例は、シングルトーンのシリアル化/非シリアル化です。 readResolveを他の特別なメソッドreadObject、writeObject、writeReplaceと組み合わせて使用​​すると、オブジェクトをシリアル化するプロセスを非常に柔軟に制御できます。



道徳: Javaでオブジェクトをシリアル化するプロセスは非常に慎重にアプローチし、その使用のためのさまざまなオプションを提供する必要があります。 シリアル化メカニズムなしで問題を解決できる場合は、




全体的に、Javaは適切に設計された言語です。 熊手では十分ではありません。足を踏み入れるのは困難です。 しかし、それらをいつか知ることで、多くの神経と時間のコードデバッグを節約できます。



All Articles