Java:リフレクションで得たjava.lang.reflect.Typeから型文字列を復帰



何の因果か、こんなことをやる必要に迫られた。

以下を実行すると、

package test;

import java.util.*;

public class Foo {

  static class Bar {    
  }

  public List<String>foo(ArrayList<? extends Bar> a, Set<String>b, Bar[][]array, int[]test) {
    return null;    
  }

  public void foo(Map<String, ? extends Class<?>>map, Class<Bar>[]aaa) {    
  }

  public static void main(String[]args) throws Exception {
    TypeString typeString = new TypeString();
    Arrays.stream(Foo.class.getDeclaredMethods()).forEach(m-> {
      System.out.println("" + typeString.constructMethod(m));            
    });
  }
}

以下が出力される。

void main(String[] args)
void foo(java.util.Map<String, ? extends Class<?>> map, Class<test.Foo.Bar>[] aaa)
java.util.List foo(java.util.ArrayList<? extends test.Foo.Bar> a, java.util.Set<String> b, test.Foo.Bar[][] array, int[] test)
void lambda$0(test.TypeString arg0, java.lang.reflect.Method arg1)

メソッドの定義が必要だったので、他の部分は省略している。

※lambda$0というのは勝手にできているメソッドのようだ。

ここで使用しているTypeStringは以下である。

package test;

import java.lang.reflect.*;
import java.util.*;
import java.util.stream.*;

/**
 * リフレクションで取得したJavaのメソッドの返り値や引数の型オブジェクトから
 * 元の定義文字列を取得する。
 * 
 * @author ysugimura
 */
public class TypeString {

  /** 省略するパッケージ */
  Set<String>omitPackages = new HashSet<String>() {{
    add("java.lang");
  }};

  /** メソッドの定義を作成する */
  String constructMethod(Method method) {
    return
      constructType(method.getReturnType()) + " " +
      method.getName() + "(" +
        Arrays.stream(method.getParameters()).map(p->
          constructType(p.getParameterizedType()) + " " + p.getName()
        ).collect(Collectors.joining(", ")) +
      ")";
  }

  /** 型の定義を作成する */
  String constructType(Type type) {    

    // TypeがClass
    if (type instanceof Class) {
      Class<?>clazz = (Class<?>)type;
      if (clazz.isArray()) return arrayType(clazz);
      return simpleClass((Class<?>)type);
    }

    // WildcardType
    if (type instanceof WildcardType) {
      return wildcardType((WildcardType)type);
    }

    // GenericArrayType
    if (type instanceof GenericArrayType) {
      return genericArrayType((GenericArrayType)type);
    }

    // ParameterizedType
    return parameterizedType((ParameterizedType)type);
  }

  /** 
   * パラメータ付型
   * @param type
   * @return
   */
  String parameterizedType(ParameterizedType type) {
    StringBuilder s = new StringBuilder();

    Type ownerType = type.getOwnerType();
    if (ownerType != null) s.append(constructType(ownerType) + ".");

    Type rawType = type.getRawType();
    s.append(
      constructType(rawType) + 
      "<" +
        Arrays.stream(type.getActualTypeArguments())
          .map(a->constructType(a)).collect(Collectors.joining(", ")) +
      ">"
    );

    return s.toString();    
  }


  /**
   *  ワイルドカード型 
   * @param type
   * @return
   */
  String wildcardType(WildcardType type) {
    StringBuilder s = new StringBuilder();
    Type[]upperBounds = type.getUpperBounds();
    Type[]lowerBounds = type.getLowerBounds();

    // extends
    if (upperBounds.length > 0) {
      String extendsObjects = 
          Arrays.stream(upperBounds).map(b->constructType(b)).collect(Collectors.joining(","));
      if (extendsObjects.equals("Object")) {
        // "? extends Object" --> "?"
        s.append("?");
      } else {
        s.append("? extends " + extendsObjects);    
      }
    }

    // super
    if (lowerBounds.length > 0) {
      s.append("? super " +
        Arrays.stream(lowerBounds).map(b->constructType(b)).collect(Collectors.joining(",")));    
    }

    return s.toString();
  }

  /**
   * 要素がジェネリックタイプの配列
   * @param type
   * @return
   */
  String genericArrayType(GenericArrayType type) {
    return constructType(type.getGenericComponentType()) + "[]";
  }

  /**
   * 要素が単純クラスの配列
   * @param clazz
   * @return
   */
  String arrayType(Class<?>clazz) {
    return constructType(clazz.getComponentType()) + "[]";
  }

  /** 
   * 単純なクラスbの場合
   * パッケージ省略対象であれば、省略する。
   * '$'は'.'に変更する。
   * @param clazz
   * @return
   */
  String simpleClass(Class<?>clazz) {        
    String className = clazz.getName();
    int lastDot = className.lastIndexOf('.');
    if (lastDot < 0) return className;
    String pkg = className.substring(0, lastDot);
    className = className.replace('$',  '.');

    if (omitPackages.contains(pkg)) return className.substring(lastDot + 1);
    return className;
  }

}