Javaだって簡単にFizzBuzz書けるもん! ~ ClassLoader利用

  • 6
    Like
  • 0
    Comment
More than 1 year has passed since last update.

更に簡単なFizzBuzzになりました。

コードはこちらです。

package classloader;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import static java.util.Collections.addAll;

public class FizzBuzz
{
    public static void main(String[] args)
        throws Exception
    {
        final ClassLoader loader= new ClassLoader(){
            private final Map<String, Class<?>> defined= new HashMap<>();

            @Override
            protected Class<?> findClass(String name)
                throws ClassNotFoundException
            {
                final int i= Integer.parseInt(name);
                final String cname;
                if(i % 15 == 0)
                {
                    cname= "FizzBuzz";
                }
                else if(i % 3 == 0)
                {
                    cname= "Fizz";
                }
                else if(i % 5 == 0)
                {
                    cname= "Buzz";
                }
                else
                {
                    cname= "" + i;
                }

                if(this.defined.containsKey(cname))
                {
                    return this.defined.get(cname);
                }

                final byte[] bytes= this.makeClass(cname);
                final Class<?> clazz= this.defineClass(cname, bytes, 0, bytes.length);

                this.defined.put(cname, clazz);

                return clazz;
            }

            private byte[] makeClass(CharSequence name)
            {
                final List<Integer> bytes= new LinkedList<>();

                // magic
                addAll(bytes, 0xca, 0xfe, 0xba, 0xbe);
                // minor version
                addAll(bytes, 0x00, 0x00);
                // major version
                addAll(bytes, 0x00, 0x34);
                // constant pool count
                addAll(bytes, 0x00, 0x05);
                // constant pool
                // CONSTANT_Class_info
                addAll(bytes, 0x07);
                addAll(bytes, 0x00, 0x02);
                // CONSTANT_Utf8_info
                addAll(bytes, 0x01);
                addAll(bytes, 0x00, name.length());
                name.chars().forEach(bytes::add);
                // CONSTANT_Class_info
                addAll(bytes, 0x07);
                addAll(bytes, 0x00, 0x04);
                // CONSTANT_Utf8_info
                addAll(bytes, 0x01);
                {
                    final String superclazz= "java/lang/Object";
                    addAll(bytes, 0x00, superclazz.length());
                    superclazz.chars().forEach(bytes::add);
                }
                // access flags
                addAll(bytes, 0x00, 0x21);
                // this class
                addAll(bytes, 0x00, 0x01);
                // super class
                addAll(bytes, 0x00, 0x03);
                // interfaces count
                addAll(bytes, 0x00, 0x00);
                // fields count
                addAll(bytes, 0x00, 0x00);
                // methods count
                addAll(bytes, 0x00, 0x00);
                // attributes count
                addAll(bytes, 0x00, 0x00);

                final byte[] transformed= new byte[bytes.size()];
                for(int i= 0; i < bytes.size(); ++i)
                {
                    transformed[i]= bytes.get(i).byteValue();
                }
                return transformed;
            }
        };

        for(int i= 1; i <= 100; ++i)
        {
            System.out.println(loader.loadClass("" + i).getSimpleName());
        }
    }
}

ClassLoader#loadClass()で指定の名前のクラスをロードし、読み込んだクラスの単純名を表示することでFizzBuzzっています。
さて、本来であれば1や2のような数字からのみなるクラス名はjava言語仕様で不正とされていますが、これを実現しているのがClassLoaderの実装です{*1}。
loadClass()で指定した名前のクラスがまだロードされていない場合、findClass()が呼ばれます。
findClass()で該当するクラスオブジェクトを返却するか、ClassNotFoundExceptionをスローする必要がありますが、今回はここで動的にクラスオブジェクトを作成することで、java上不正なクラス{*2}をロードすることができます。
また本来であれば、findClass()が呼ばれるのは未ロードのクラスに対してだけなのですが、例えば3や6というクラス名からFizzというクラスを読み込ませているため、Fizzというクラス名については複数回defineClass()が実行されてしまい、実行時エラーとなります。
これを回避するためにMapに定義済のクラスオブジェクトを持つことで、複数回defineClass()を実行しないように制御しています。

Javaでも簡単なFizzBuzzが書けたよ!

ということでgithubにも上げてあります。
こちらからどうぞ。

{*1} ClassLoaderのjavadocにも、loadClass()などに渡す名前はjava言語仕様でvalidとされる名前しか渡しちゃいやん、と書かれています。今回の実装は完全に黒に近いグレーのため、良い子は真似しちゃいやーよ
{*2} 今回は面倒だったため、以下のようなクラスを作成しました。

  1. クラス名が数字のみ
  2. コンストラクタを持たない(つまりインスタンス化不可能)