Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Javaのルールをねじ曲げろ!禁断の操作とその代償から学ぶメタプログラミング入門 ...

Avatar for nrs nrs
June 06, 2025

Javaのルールをねじ曲げろ!禁断の操作とその代償から学ぶメタプログラミング入門 / A Guide to Metaprogramming: Lessons from Forbidden Techniques and Their Price

JJUG CCC 2025 Spring における講演の資料です。

メタプログラミングの解説資料になっています。

# URL
YouTube: https://www.youtube.com/c/narusemi
HomePage: https://nrslib.com
Twitter: https://twitter.com/nrslib
Instagram: https://www.instagram.com/nrslib/

Avatar for nrs

nrs

June 06, 2025
Tweet

More Decks by nrs

Other Decks in Programming

Transcript

  1. public class MyClass { private int privateField = 1; private

    void privateMethod() { System.out.println("This is a private method."); } }
  2. public class MyClass { private int privateField = 1; private

    void privateMethod() { System.out.println("This is a private method."); } } 恥ずかしがり屋さん
  3. try { var myClass = new MyClass(); var targetClazz =

    myClass.getClass(); var privateField = targetClazz.getDeclaredField("privateField"); privateField.setAccessible(true); var privateFieldValue = privateField.get(myClass); System.out.printf("My private field is: %s\n", privateFieldValue); var privateMethod = targetClazz.getDeclaredMethod("privateMethod"); privateMethod.setAccessible(true); privateMethod.invoke(myClass); } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { System.err.println("基本リフレクションエラー: " + e.getMessage()); }
  4. try { var myClass = new MyClass(); var targetClazz =

    myClass.getClass(); var privateField = targetClazz.getDeclaredField("privateField"); privateField.setAccessible(true); var privateFieldValue = privateField.get(myClass); System.out.printf("My private field is: %s\n", privateFieldValue); var privateMethod = targetClazz.getDeclaredMethod("privateMethod"); privateMethod.setAccessible(true); privateMethod.invoke(myClass); } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { System.err.println("基本リフレクションエラー: " + e.getMessage()); } クラス定義を取得
  5. try { var myClass = new MyClass(); var targetClazz =

    myClass.getClass(); var privateField = targetClazz.getDeclaredField("privateField"); privateField.setAccessible(true); var privateFieldValue = privateField.get(myClass); System.out.printf("My private field is: %s\n", privateFieldValue); var privateMethod = targetClazz.getDeclaredMethod("privateMethod"); privateMethod.setAccessible(true); privateMethod.invoke(myClass); } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { System.err.println("基本リフレクションエラー: " + e.getMessage()); } フィールド情報を取得
  6. try { var myClass = new MyClass(); var targetClazz =

    myClass.getClass(); var privateField = targetClazz.getDeclaredField("privateField"); privateField.setAccessible(true); var privateFieldValue = privateField.get(myClass); System.out.printf("My private field is: %s\n", privateFieldValue); var privateMethod = targetClazz.getDeclaredMethod("privateMethod"); privateMethod.setAccessible(true); privateMethod.invoke(myClass); } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { System.err.println("基本リフレクションエラー: " + e.getMessage()); } privateなのでアクセスできるようにする
  7. try { var myClass = new MyClass(); var targetClazz =

    myClass.getClass(); var privateField = targetClazz.getDeclaredField("privateField"); privateField.setAccessible(true); var privateFieldValue = privateField.get(myClass); System.out.printf("My private field is: %s\n", privateFieldValue); var privateMethod = targetClazz.getDeclaredMethod("privateMethod"); privateMethod.setAccessible(true); privateMethod.invoke(myClass); } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { System.err.println("基本リフレクションエラー: " + e.getMessage()); } フィールド情報でインスタンスから データを取得
  8. try { var myClass = new MyClass(); var targetClazz =

    myClass.getClass(); var privateField = targetClazz.getDeclaredField("privateField"); privateField.setAccessible(true); var privateFieldValue = privateField.get(myClass); System.out.printf("My private field is: %s\n", privateFieldValue); var privateMethod = targetClazz.getDeclaredMethod("privateMethod"); privateMethod.setAccessible(true); privateMethod.invoke(myClass); } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { System.err.println("基本リフレクションエラー: " + e.getMessage()); } private なデータを取得できました
  9. try { var myClass = new MyClass(); var targetClazz =

    myClass.getClass(); var privateField = targetClazz.getDeclaredField("privateField"); privateField.setAccessible(true); var privateFieldValue = privateField.get(myClass); System.out.printf("My private field is: %s\n", privateFieldValue); var privateMethod = targetClazz.getDeclaredMethod("privateMethod"); privateMethod.setAccessible(true); privateMethod.invoke(myClass); } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { System.err.println("基本リフレクションエラー: " + e.getMessage()); } メソッドも同様にメソッド情報を取得
  10. try { var myClass = new MyClass(); var targetClazz =

    myClass.getClass(); var privateField = targetClazz.getDeclaredField("privateField"); privateField.setAccessible(true); var privateFieldValue = privateField.get(myClass); System.out.printf("My private field is: %s\n", privateFieldValue); var privateMethod = targetClazz.getDeclaredMethod("privateMethod"); privateMethod.setAccessible(true); privateMethod.invoke(myClass); } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { System.err.println("基本リフレクションエラー: " + e.getMessage()); } 触れるようにして
  11. try { var myClass = new MyClass(); var targetClazz =

    myClass.getClass(); var privateField = targetClazz.getDeclaredField("privateField"); privateField.setAccessible(true); var privateFieldValue = privateField.get(myClass); System.out.printf("My private field is: %s\n", privateFieldValue); var privateMethod = targetClazz.getDeclaredMethod("privateMethod"); privateMethod.setAccessible(true); privateMethod.invoke(myClass); } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { System.err.println("基本リフレクションエラー: " + e.getMessage()); } メソッド呼び出し
  12. private int privateCalculate(int a, int b) { int result =

    a * b + a - b; System.out.println("Private calculation: " + a + " * " + b + " + " + a + " - " + b + " = " + result); return result; } private void privateSetValue(String message, int number) { this.privateMessage = message; this.privateNumber = number; System.out.println("Private values updated: message='" + message + "', number=" + number); } private String privateComplexMethod(String[] words, boolean uppercase) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < words.length; i++) { if (i > 0) sb.append(" "); sb.append(uppercase ? words[i].toUpperCase() : words[i].toLowerCase()); } String result = sb.toString(); System.out.println("Complex method processing: " + result); return result; } private static String privateStaticMethod(double value) { String formatted = String.format("%.2f", value); System.out.println("Static method processing: " + formatted); return "Processed: " + formatted; } いろんなメソッド用意しました
  13. var privateCalculateMethod = targetClazz.getDeclaredMethod( "privateCalculate", int.class, int.class); privateCalculateMethod.setAccessible(true); var result

    = privateCalculateMethod.invoke(myClass, 10, 5); System.out.printf("Private calculation result: %s\n", result);
  14. var privateCalculateMethod = targetClazz.getDeclaredMethod( "privateCalculate", int.class, int.class); privateCalculateMethod.setAccessible(true); var result

    = privateCalculateMethod.invoke(myClass, 10, 5); System.out.printf("Private calculation result: %s\n", result); privateCalculate(int foo, int bar)を指定
  15. var privateCalculateMethod = targetClazz.getDeclaredMethod( "privateCalculate", int.class, int.class); privateCalculateMethod.setAccessible(true); var result

    = privateCalculateMethod.invoke(myClass, 10, 5); System.out.printf("Private calculation result: %s\n", result); 呼び出し時に引数を渡す
  16. var privateCalculateMethod = targetClazz.getDeclaredMethod( "privateCalculate", int.class, int.class); privateCalculateMethod.setAccessible(true); var result

    = privateCalculateMethod.invoke(myClass, 10, 5); System.out.printf("Private calculation result: %s\n", result); 戻り値も受け取れる
  17. var complexMethod = targetClazz.getDeclaredMethod( "privateComplexMethod", String[].class, boolean.class); complexMethod.setAccessible(true); String[] args

    = {"Hello", "World", "Java"}; var complexResult = complexMethod.invoke(myClass, args, true); System.out.printf("Complex method result: %s\n", complexResult);
  18. var complexMethod = targetClazz.getDeclaredMethod( "privateComplexMethod", String[].class, boolean.class); complexMethod.setAccessible(true); String[] args

    = {"Hello", "World", "Java"}; var complexResult = complexMethod.invoke(myClass, args, true); System.out.printf("Complex method result: %s\n", complexResult); ちょっと複雑な引数も
  19. var complexMethod = targetClazz.getDeclaredMethod( "privateComplexMethod", String[].class, boolean.class); complexMethod.setAccessible(true); String[] args

    = {"Hello", "World", "Java"}; var complexResult = complexMethod.invoke(myClass, args, true); System.out.printf("Complex method result: %s\n", complexResult); 同じように呼び出せます
  20. var staticMethod = targetClazz.getDeclaredMethod("privateStaticMethod", double.class); staticMethod.setAccessible(true); var staticResult = staticMethod.invoke(null,

    3.14159); System.out.printf("Static method result: %s\n", staticResult); static メソッドであっても
  21. var staticMethod = targetClazz.getDeclaredMethod("privateStaticMethod", double.class); staticMethod.setAccessible(true); var staticResult = staticMethod.invoke(null,

    3.14159); System.out.printf("Static method result: %s\n", staticResult); 基本は一緒 インスタンスがないから null を渡す
  22. public class SecureClass { private String secretData; private final String

    FINAL_VALUE = "変更不可"; public SecureClass(String data) { this.secretData = data; } private SecureClass() { this.secretData = "デフォルト値"; } @Override public String toString() { return "SecureClass{secretData='" + secretData + "'}"; } }
  23. public class SecureClass { private String secretData; private final String

    FINAL_VALUE = "変更不可"; public SecureClass(String data) { this.secretData = data; } private SecureClass() { this.secretData = "デフォルト値"; } @Override public String toString() { return "SecureClass{secretData='" + secretData + "'}"; } } 恥ずかしがりなコンストラクタ
  24. Constructor<SecureClass> privateConstructor = SecureClass.class.getDeclaredConstructor(); privateConstructor.setAccessible(true); SecureClass instance = privateConstructor.newInstance(); System.out.println("private

    コンストラクタから作成されたインスタンス: " + instance); public class SecureClass { private String secretData; private final String FINAL_VALUE = "変更不可"; public SecureClass(String data) { this.secretData = data; } private SecureClass() { this.secretData = "デフォルト値"; } @Override public String toString() { return "SecureClass{secretData='" + secretData + "'}"; } }
  25. Constructor<SecureClass> privateConstructor = SecureClass.class.getDeclaredConstructor(); privateConstructor.setAccessible(true); SecureClass instance = privateConstructor.newInstance(); System.out.println("private

    コンストラクタから作成されたインスタンス: " + instance); private なコンストラクタを取得 public class SecureClass { private String secretData; private final String FINAL_VALUE = "変更不可"; public SecureClass(String data) { this.secretData = data; } private SecureClass() { this.secretData = "デフォルト値"; } @Override public String toString() { return "SecureClass{secretData='" + secretData + "'}"; } }
  26. Constructor<SecureClass> privateConstructor = SecureClass.class.getDeclaredConstructor(); privateConstructor.setAccessible(true); SecureClass instance = privateConstructor.newInstance(); System.out.println("private

    コンストラクタから作成されたインスタンス: " + instance); インスタンス生成 public class SecureClass { private String secretData; private final String FINAL_VALUE = "変更不可"; public SecureClass(String data) { this.secretData = data; } private SecureClass() { this.secretData = "デフォルト値"; } @Override public String toString() { return "SecureClass{secretData='" + secretData + "'}"; } }
  27. public class SecureClass { private String secretData; private final String

    FINAL_VALUE = "変更不可"; public SecureClass(String data) { this.secretData = data; } private SecureClass() { this.secretData = "デフォルト値"; } @Override public String toString() { return "SecureClass{secretData='" + secretData + "'}"; } }
  28. public class SecureClass { private String secretData; private final String

    FINAL_VALUE = "変更不可"; public SecureClass(String data) { this.secretData = data; } private SecureClass() { this.secretData = "デフォルト値"; } @Override public String toString() { return "SecureClass{secretData='" + secretData + "'}"; } } 再代入不可
  29. SecureClass instance = new SecureClass("テスト"); Field finalField = SecureClass.class.getDeclaredField("FINAL_VALUE"); finalField.setAccessible(true);

    try { finalField.set(instance, "変更された値"); System.out.println("final フィールドの値を変更しました: " + finalField.get(instance)); instance.printFinalValue();
  30. SecureClass instance = new SecureClass("テスト"); Field finalField = SecureClass.class.getDeclaredField("FINAL_VALUE"); finalField.setAccessible(true);

    try { finalField.set(instance, "変更された値"); System.out.println("final フィールドの値を変更しました: " + finalField.get(instance)); instance.printFinalValue(); いつも通りに取得
  31. SecureClass instance = new SecureClass("テスト"); Field finalField = SecureClass.class.getDeclaredField("FINAL_VALUE"); finalField.setAccessible(true);

    try { finalField.set(instance, "変更された値"); System.out.println("final フィールドの値を変更しました: " + finalField.get(instance)); instance.printFinalValue(); 変更禁止? そんなの関係ネェ!!
  32. SecureClass instance = new SecureClass("テスト"); Field finalField = SecureClass.class.getDeclaredField("FINAL_VALUE"); finalField.setAccessible(true);

    try { finalField.set(instance, "変更された値"); System.out.println("final フィールドの値を変更しました: " + finalField.get(instance)); instance.printFinalValue(); いつも通りに取得
  33. SecureClass instance = new SecureClass("テスト"); Field finalField = SecureClass.class.getDeclaredField("FINAL_VALUE"); finalField.setAccessible(true);

    try { finalField.set(instance, "変更された値"); System.out.println("final フィールドの値を変更しました: " + finalField.get(instance)); instance.printFinalValue(); ↓実行結果
  34. SecureClass instance = new SecureClass("テスト"); Field finalField = SecureClass.class.getDeclaredField("FINAL_VALUE"); finalField.setAccessible(true);

    try { finalField.set(instance, "変更された値"); System.out.println("final フィールドの値を変更しました: " + finalField.get(instance)); instance.printFinalValue(); ↓実行結果 ???
  35. public class SecureClass { private String secretData; private final String

    FINAL_VALUE = "変更不可"; public SecureClass(String data) { this.secretData = data; } private SecureClass() { this.secretData = "デフォルト値"; } @Override public String toString() { return "SecureClass{secretData='" + secretData + "'}"; } public void printFinalValue() { System.out.println("FINAL_VALUE: " + FINAL_VALUE); } }
  36. public class SecureClass { private String secretData; private final String

    FINAL_VALUE = "変更不可"; public SecureClass(String data) { this.secretData = data; } private SecureClass() { this.secretData = "デフォルト値"; } @Override public String toString() { return "SecureClass{secretData='" + secretData + "'}"; } public void printFinalValue() { System.out.println("FINAL_VALUE: " + FINAL_VALUE); } } 実際の中身を 見てみると?
  37. public class SecureClass { private String secretData; private final String

    FINAL_VALUE = "変更不可"; public SecureClass(String data) { this.secretData = data; } private SecureClass() { this.secretData = "デフォルト値"; } @Override public String toString() { return "SecureClass{secretData='" + secretData + "'}"; } public void printFinalValue() { System.out.println("FINAL_VALUE: " + FINAL_VALUE); } } ←出力結果
  38. public class SecureClass { public final String FINAL_VALUE = "変更不可";

    public final String FINAL_VALUE2; private SecureClass() { FINAL_VALUE2 = "変更不可"; } public void printFinalValue() { System.out.println("FINAL_VALUE: " + FINAL_VALUE); } public void printFinalValue2() { System.out.println("FINAL_VALUE2: " + FINAL_VALUE2); } }
  39. public class SecureClass { public final String FINAL_VALUE = "変更不可";

    public final String FINAL_VALUE2; private SecureClass() { FINAL_VALUE2 = "変更不可"; } public void printFinalValue() { System.out.println("FINAL_VALUE: " + FINAL_VALUE); } public void printFinalValue2() { System.out.println("FINAL_VALUE2: " + FINAL_VALUE2); } } コンストラクタで設定するタイプを追加
  40. public class SecureClass { private final String FINAL_VALUE = "変更不可";

    private final String FINAL_VALUE2; private final MyClass myClass = new MyClass(); : public void printMyClassName() { System.out.println("MyClass name: " + myClass.name); } }
  41. public class SecureClass { private final String FINAL_VALUE = "変更不可";

    private final String FINAL_VALUE2; private final MyClass myClass = new MyClass(); : public void printMyClassName() { System.out.println("MyClass name: " + myClass.name); } } クラスのパターンも追加
  42. Field finalClass = SecureClass.class.getDeclaredField("myClass"); finalClass.setAccessible(true); var myClass = new MyClass();

    myClass.name = "changed"; finalClass.set(instance, myClass); instance.printMyClassName();
  43. Field finalClass = SecureClass.class.getDeclaredField("myClass"); finalClass.setAccessible(true); var myClass = new MyClass();

    myClass.name = "changed"; finalClass.set(instance, myClass); instance.printMyClassName();
  44. private static DynamicInterface createDynamicImplementation(String result) { return (DynamicInterface) java.lang.reflect.Proxy.newProxyInstance( DynamicInterface.class.getClassLoader(),

    new Class<?>[]{DynamicInterface.class}, (proxy, method, args) -> { System.out.println("動的実装: " + method.getName() + " が呼び出されました"); if (args != null && args.length > 0) { System.out.println("引数: " + args[0]); } return result; } ); }
  45. private static DynamicInterface createDynamicImplementation(String result) { return (DynamicInterface) java.lang.reflect.Proxy.newProxyInstance( DynamicInterface.class.getClassLoader(),

    new Class<?>[]{DynamicInterface.class}, (proxy, method, args) -> { System.out.println("動的実装: " + method.getName() + " が呼び出されました"); if (args != null && args.length > 0) { System.out.println("引数: " + args[0]); } return result; } ); } 標準ライブラリ
  46. private static DynamicInterface createDynamicImplementation(String result) { return (DynamicInterface) java.lang.reflect.Proxy.newProxyInstance( DynamicInterface.class.getClassLoader(),

    new Class<?>[]{DynamicInterface.class}, (proxy, method, args) -> { System.out.println("動的実装: " + method.getName() + " が呼び出されました"); if (args != null && args.length > 0) { System.out.println("引数: " + args[0]); } return result; } ); } メソッド呼び出しはすべてここに転送
  47. DynamicInterface dynamic = createDynamicImplementation("動的に渡された戻り値"); String result = dynamic.dynamicMethod("動的生成されたメソッドの呼び出し"); System.out.println("動的に生成されたクラス: "

    + dynamic.getClass().getName()); System.out.println("実装されたインターフェース: " + java.util.Arrays.toString(dynamic.getClass().getInterfaces())); System.out.println("戻り値: " + result);
  48. DynamicInterface dynamic = createDynamicImplementation("動的に渡された戻り値"); String result = dynamic.dynamicMethod("動的生成されたメソッドの呼び出し"); System.out.println("動的に生成されたクラス: "

    + dynamic.getClass().getName()); System.out.println("実装されたインターフェース: " + java.util.Arrays.toString(dynamic.getClass().getInterfaces())); System.out.println("戻り値: " + result); インターフェースに定義されたメソッド呼び出し
  49. private static DynamicInterface createDynamicImplementation(String result) { return (DynamicInterface) java.lang.reflect.Proxy.newProxyInstance( DynamicInterface.class.getClassLoader(),

    new Class<?>[]{DynamicInterface.class}, (proxy, method, args) -> { System.out.println("動的実装: " + method.getName() + " が呼び出されました"); if (args != null && args.length > 0) { System.out.println("引数: " + args[0]); } return result; } ); }
  50. private static DynamicInterface createDynamicImplementation(String result) { return (DynamicInterface) java.lang.reflect.Proxy.newProxyInstance( DynamicInterface.class.getClassLoader(),

    new Class<?>[]{DynamicInterface.class}, (proxy, method, args) -> { System.out.println("動的実装: " + method.getName() + " が呼び出されました"); if (args != null && args.length > 0) { System.out.println("引数: " + args[0]); } return result; } ); }
  51. private static TargetInterface createEnhancedProxy(TargetClass original) { return (TargetInterface) java.lang.reflect.Proxy.newProxyInstance( TargetInterface.class.getClassLoader(),

    new Class<?>[]{TargetInterface.class}, (proxy, method, args) -> { switch (method.getName()) { case "originalMethod": System.out.println("プロキシ: 元のメソッドを呼び出し前の処理"); original.originalMethod(); System.out.println("プロキシ: 元のメソッドを呼び出し後の処理"); return null; case "newMethod": System.out.println("プロキシ: 動的に追加されたメソッドの実行"); System.out.println("引数: " + (args != null && args.length > 0 ? args[0] : "なし")); return "動的メソッドの戻り値: " + (args != null && args.length > 0 ? args[0] : ""); default: return method.invoke(original, args); } } ); }
  52. private static TargetInterface createEnhancedProxy(TargetClass original) { return (TargetInterface) java.lang.reflect.Proxy.newProxyInstance( TargetInterface.class.getClassLoader(),

    new Class<?>[]{TargetInterface.class}, (proxy, method, args) -> { switch (method.getName()) { case "originalMethod": System.out.println("プロキシ: 元のメソッドを呼び出し前の処理"); original.originalMethod(); System.out.println("プロキシ: 元のメソッドを呼び出し後の処理"); return null; case "newMethod": System.out.println("プロキシ: 動的に追加されたメソッドの実行"); System.out.println("引数: " + (args != null && args.length > 0 ? args[0] : "なし")); return "動的メソッドの戻り値: " + (args != null && args.length > 0 ? args[0] : ""); default: return method.invoke(original, args); } } ); } メソッド追加
  53. @RestController // ← リフレクション public class UserController { @Autowired //

    ← 動的プロキシ UserService userService; @GetMapping("/users/{id}") // ← リフレクション public User getUser(@PathVariable Long id) { // ← リフレクション return userService.findById(id); // ← 動的プロキシ } }
  54. 95 • X ◦ @nrslib • HomePage ◦ https://nrslib.com/ •

    YouTube ◦ https://www.youtube.com/@nrslib おしまい