更に簡単な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} 今回は面倒だったため、以下のようなクラスを作成しました。
- クラス名が数字のみ
- コンストラクタを持たない(つまりインスタンス化不可能)