Javaの小ネタ、Tips


概要

徒然なるままに書き綴ってみるだけです。役に立つかどうかは知りません。また、必ずしもJava固有の話ではないと思います。

内容

多重代入

複数の変数に同じ値を代入することができます。

int x, y;
x = y = 10;
System.out.println(x); // 10
System.out.println(y); // 10

これは、下記のように解釈されるっぽいです。yに10が代入されますが、Javaでは代入式も値が返りますから、 (y = 10) 全体の評価値がxに代入されるということですね。

x = (y = 10);

mainメソッドの書き方

mainメソッドは一般的には下記のように書くと思います。

public static void main(String[] args) {
    System.out.println("Hello world!");
}

Java5以降なら下記のように書いてもOKです。

public static void main(String... args) {
    System.out.println("Hello world!");
}

String[]String... の違いだけです。前者は配列で後者は可変長引数ですが、mainメソッドはどちらでもいいようです。

ただのスコープ

if文やfor文などを使うと新しいスコープが作られますが、条件分岐やループなどをしない単なるスコープを作ることもできます。

{
    int x = 10;
    System.out.println(x);  // 10
}

{
    int x = 100;
    System.out.println(x); // 100
}

// System.out.println(x); // コンパイルエラー

x が2回定義されていますが、スコープが別なのでコンパイルエラーにはなりません。また、x のスコープはブレースで囲った範囲だけなので、ブレースの外で x を参照しようとするとコンパイルエラーです。

HashSetの実装

HashSetは、実装としてはただHashMapをラップしているだけのクラスです。
(※実装の詳細の話なので、将来変更される可能性はあります)

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

AbstractStringBuilder

StringBuilderStringBuffer はJavaDocではObjectクラス直下のサブクラスであるかのように書かれていますが、実際には AbstractStringBuilder のサブクラスです。
AbstractStringBuilder はパッケージプライベート(公開APIではない)のでJavaDocには書かれてないのかもしれません。
(※実装の詳細の話なので、将来変更される可能性はあります)

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, Comparable<StringBuilder>, CharSequence
{
// 後略
 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, Comparable<StringBuffer>, CharSequence
{
// 後略

メソッド内でメソッドを定義する

通常はJavaではメソッド内にメソッドを定義することはできません。しかし、Java10で導入された var を使えばそれっぽいことができます。(別の記事に書いたことがありますが)

Main.java
public class Main {
    public static void main(String[] args) {
        // func.capitalize() で呼び出せる
        // func はローカル変数なので、このメソッドの外からは呼べない
        var func = new Object() {
            private String capitalize(String s) {
                return s.substring(0, 1).toUpperCase() + s.substring(1, s.length()).toLowerCase();
            }
        };

        System.out.println(func.capitalize("tokyo")); // => Tokyo
        System.out.println(func.capitalize("TOKYO")); // => Tokyo
    }
}

Javaのソースコードをコンパイルなしで実行する

これも別の記事に書きましたが、Java11以降は javac を使わず java コマンドでソースファイルを指定して実行させることができます。ちょっとした動作確認のために便利かもしれません。
まあ、本当にコンパイルなしで実行するわけではなくて裏でコンパイルしているのだろうと思いますが。

既存クラスのメソッドに処理を埋め込む

Javaでは匿名クラスを使って既存のメソッドに処理を埋め込むことができます。

List<String> list = new ArrayList<>() {
    @Override
    public boolean add(String s) {
        System.out.println("add called. arg: " + s);
        return super.add(s);
    }
};
list.add("hello"); // add called. arg: hello
list.add("world"); // add called. arg: world
System.out.println(list); // [hello, world]

null に対して instanceof を使う

null に対して instanceof を使うことができます。

System.out.println(null instanceof Object); // false

true にならないのは当然ですが、 NullPointerException が投げられないというのが注目ポイントかと思います。 null が代入されている変数でも同じ結果になりますので、 instanceof の前に null チェックをしなくて済みます。

nullthrow する

例外型の変数に null が入っていた場合、 throw すると変数の型に関係なく NullPointerException が投げられます。

RuntimeException e = null;
throw e; // NullPointerException

null をキャストする

あまり意味がないですが、とりあえず何の例外も投げられません。

Object obj = null;
String str = (String)obj;
System.out.println(str); // null

サブクラスを許容しない型判定

instanceof を使うと、インスタンスの型が指定したクラスのサブクラスの場合も true となります。

System.out.println("str" instanceof Object); // true

サブクラスの場合は false にしたいなら下記のように Class 型同士を equals で比較するといいでしょう。

System.out.println(new Object().getClass().equals(Object.class)); // true
System.out.println("str".getClass().equals(Object.class)); // false

配列は型安全ではない

配列は、ある型の配列と、その型のサブクラスの配列に代入互換性があります。
そのため、下記のようなコードがコンパイルされてしまい、実行時にエラーとなります。

Object[] array = new String[3];
array[0] = new Integer(10); // ArrayStoreException

ジェネリクスを使ったListであれば、コンパイルエラーになるため上記のような問題は発生しません。

List<Object> list = new ArrayList<String>(); // incompatible types: ArrayList<String> cannot be converted to List<Object>

オーバーフローを検知する

演算時にオーバーフローが発生すると、何のエラーも出ずにただ不正な計算結果になります。
Math クラスのメソッドで、オーバーフローが発生した際に例外を投げるユーティリティーメソッドがあるため、その例外を捕捉することでオーバーフローを検知できます。

System.out.println(Integer.MAX_VALUE + 1); // -2147483648
Math.addExact(Integer.MAX_VALUE, 1); // ArithmeticException: integer overflow

まあ、最初から BigInteger を使えばいいような気もしますけれど。

終わりに

何か一つでも参考になる項目があれば幸いです。
他に何か思いついたら追記するかもしれませんし、しないかもしれません。