LoginSignup
6
6

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-02-19

更に簡単な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. コンストラクタを持たない(つまりインスタンス化不可能)
6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6