Java で書いた FizzBuzz を短くしながら仕様について学ぶ

Java で書いた FizzBuzz を短くしながら仕様について学ぶ

by mdstoy
1 / 22

とりあえず書いてみる

class FizzBuzz{
    public static void main(String[] args){
        for(int i = 1; i <= 100; ++i){
            if(i % 3 == 0 && i % 5 == 0){
                System.out.println("FizzBuzz");
            }else if(i % 3 == 0){
                System.out.println("Fizz");
            }else if(i % 5 == 0){
                System.out.println("Buzz");
            }else{
                System.out.println(i);
            }
        }
    }
}

短くしてみる

現在最も短い Java による FizzBuzz のソースコードは 96byteらしいです。
せっかくだから(何が?)短くしてみましょう。


条件演算子を使って短くする

class FizzBuzz{
    public static void main(String[] args){
        for(int i = 1; i <= 100; ++i){
            System.out.println(((i % 3 == 0 && i % 5 == 0) ? "FizzBuzz" : ((i % 5 == 0) ? "Buzz" : ((i % 3 == 0) ? "Fizz" : i + ""))));
        }
    }
}

使いどころを間違えなければ便利で見やすいコードになります。(好みが分かれますけどね)
ただし、上記のような使い方はどう考えてもやり過ぎですので、特別な理由のない限りはしないようにしましょう。


アルゴリズムを工夫する

これは純粋に思いつくか否かの問題です。
日頃の訓練とセンスがものをいいます。
いいコードをたくさん読んで、自分でもコードをたくさん書けばいいと思います。


アルゴリズムを工夫する

Java 1.4 (JLS 2nd Edition) までのコード

class FizzBuzz{
    public static void main(String[] args){
        for(int i = 1; i <= 100; ++i){
            System.out.println((i % 3 > 0 ? "" : "Fizz") + (i % 5 > 0 ? (i % 3 > 0 ? i + "" : "") : "Buzz"));
        }
    }
}

アルゴリズムを工夫する

Java5 (JLS 3rd Edition) 以降のコード

class FizzBuzz{
    public static void main(String[] args){
        for(int i = 1; i <= 100; ++i){
            System.out.println((i % 3 > 0 ? "" : "Fizz") + (i % 5 > 0 ? (i % 3 > 0 ? i : "") : "Buzz"));
        }
    }
}

何が違うのか?

  • JLS 2nd Edition までは条件演算子の二番目と三番目の式は同じ型を返さねばならなかったです
    • なので、i + "" などという力技で無理やり String にしています
      • あ、String#valueOf とかにしていないのは、ショートコーディング目的で書いているからです
  • JLS 3rd Edition で型の自動変換規則が変更されたため、i を明示的に String に変換しなくても通るようになりました
    • auto boxing や generics などが導入された関係?

括弧を減らす

class FizzBuzz{
    public static void main(String[] args){
        for(int i = 1; i <= 100; ++i)
            System.out.println((i % 3 > 0 ? "" : "Fizz") + (i % 5 > 0 ? i % 3 > 0 ? i : "" : "Buzz"));
    }
}
  • 演算子の優先順序が理解できていれば、無駄な括弧は減らせます
    • ただし、可読性を損なわないよう多少冗長になっても処理の区切りが明確にわかるように適度に括弧はつけておいた方がいいと思います
  • for のブロックも 1 文しかない場合は消せます
    • なお、私個人は普段は if や for のブロックは 1 文でも絶対括弧を省略しない派です

インクリメントの仕様

class FizzBuzz{
    public static void main(String[] args){
        for(int i = 0; ++i < 101;)
            System.out.println((i % 3 > 0 ? "" : "Fizz") + (i % 5 > 0 ? i % 3 > 0 ? i : "" : "Buzz"));
    }
}
  • Java の場合、言語仕様によりインクリメント演算子の評価順序が保障されているので、前置インクリメント演算子を使った場合は 必ず < 101 が評価される前にインクリメントされます
    • それにともない、開始を 1 から 0 にしています
    • インクリメントとは関係ないですが、ついでに <= 100< 101 にして 1b 削減しています

仕上げ

class F{public static void main(String[]a){for(int i=0;++i<100;)System.out.println((i%3>0?"":"Fizz")+(i%5>0?i%3>0?i:"":"Buzz"));}}

余分な空白や改行を削除し、変数を一文字にします。
まともな?ソースコードではこの 130 byte が限界です。多分。


ひとまずお疲れ様でした


ちょっと余談

わたしはショートコーディングのおかげで、配列の宣言が

String[]a

のようにくっつけて書けることに気付きました。
また

String

[]

a

のようにばらしたってかまわなかったりします。

まあ、いまどき配列を自分の意思で選択して使うことはありませんがね・・・。


96 byte への道

冒頭で最短は 96 byte といいましたが、さっきのコードでは 130 byte です。
実は特別な条件付きでまだ短くなるのです。


不具合を利用する

あるバージョンまでの VM には不具合があるらしく、main メソッドがなくても文字を出力させるくらいならできるのでした。
(今時の VM だと main メソッドがないとなにも動かない、はずです。)


スタティックイニシャライザ

class F{static{for(int i=0;++i<100;)System.out.println((i%3>0?"":"Fizz")+(i%5>0?i%3>0?i:"":"Buzz"));}}

102 byte になりました。
あるバージョンまでの VM は、本来の VM の仕様で規定されたタイミングより早くスタティックイニシャライザの中を実行してしまうらしく、こんなコードでも実行されてしまうそうです。
このコードの場合、FizzBuzz が実行されたあと、メインメソッドがないといって怒られます。

ちなみにスタティックイニシャライザとは、クラスのロード時に一度だけ実行される static で宣言されたコードブロックのことです。
使い方はググってください。


enum とインスタンスイニシャライザ

enum F{A;{for(int i=0;++i<100;)System.out.println((i%3>0?"":"Fizz")+(i%5>0?i%3>0?i:"":"Buzz"));}}

97 byte になりました。


enum

Java の enum については Effective Java 2nd Edition を読むのが一番です。間違いない。
上記コードに関していえば以下の性質を利用してます。

  • enum もクラスの一種である
    • クラスだけに、フィールドもメソッドもイニシャライザも定義可能
  • 列挙子は列挙型のオブジェクトであり、列挙型がロードされたときにインスタンスが生成される

インスタンスイニシャライザ

インスタンスイニシャライザとは、インスタンスが生成されるときに一度だけ実行されるコードブロックのことです。
コンストラクタより先に呼ばれます。
使い方はググってください。
使う機会はそうそうないと思いますが。


フィールドはデフォルト値を持つ

enum F{A;int i;{for(;++i<100;)System.out.println((i%3>0?"":"Fizz")+(i%5>0?i%3>0?i:"":"Buzz"));}}

最後の 1 byte は、フィールドはデフォルト値を持つ(int は 0)という性質を使うことで、明示的に 0 を代入しなくてよくなる分で削減しました。


まとめ

FizzBuzz でショートコーディングすることによって

  • 条件演算子
  • 演算子の優先順位
  • インクリメント演算子
  • スタティックイニシャライザ
  • インスタンスイニシャライザ
  • enum
  • フィールド

の仕様を学ぶことができました。
たかが FizzBuzz とはいえ侮れませんね!!


お疲れ様でした