学新通技术网

java 反射(二)

juejin 11 1
java 反射(二)

知识导图

对于之前的程序实际上采用的模式是“程序类+配置文件”,但这种也是需要手工维护配置文件,所以现在考虑另一种方式。

注解 Annotation

获取类的注解

Class 类里面提供了一个获取全部 Annotaion 的操作方法:public Annotation[] getAnnotation(),这个返回的是 java.lang.annotaion.Annotation 接口对象数组。

范例:取得全部的 Annotation

@SuppressWarnings("demo")
@Deprecated
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = TestDemo.class;
        Annotation[] annotations = cls.getAnnotations();//获取全部Annotaion
        for (int i = 0; i < annotations.length; i++) {
            System.out.println(annotations[i]);
        }
    }
}
// 输出结果
@java.lang.Deprecated()

从结果看到只取到了一个 Annotaion,在整个 java 设计的过程中,针对于 Annotation 的作用范围是有定义的,只有“@Deprecated”是在程序运行的时候起作用的 Annotation。如果要知道 Annotation 的全部范围,要查询一个枚举类:java.lang.annotation.RetentionPolicy,在这个类里面定义了三种 Annotation 的范围:

  • CLASS: 保存在类之中
  • RUNTIME: 程序运行时起作用
  • SOURCE: 在源代码之中起作用

如果要在编写的程序可以在运行的时候使用,使用 RUNTIME 类型。可以使用 @interface 自定义一个 Annotation

@Retention(RetentionPolicy.RUNTIME)
public @interface MyTarget {
    String name() default "tina";
}
@SuppressWarnings("demo")
@MyTarget
@Deprecated
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = TestDemo.class;
        Annotation[] annotations = cls.getAnnotations();//获取全部Annotaion
        for (int i = 0; i < annotations.length; i++) {
            System.out.println(annotations[i]);
        }
    }
}
// 输出结果
@cn.tina.moduler.MyTarget(name=tina)
@java.lang.Deprecated()

上面取到了一个类中全部的注解。Annotation 中也可定义属性,在 MyTarget 注解类中有一个参数 name,要应用这个参数,需要取得指定的 Annotation。在 Class 类里面也定义了取得指定 Annotation 的方法:

public <A extends Annotation> A getAnnotation(Class<A> annotationClass)

范例:取得指定的 Annotation

@MyTarget
@Deprecated
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = TestDemo.class;
        // 取得指定的 Annotation 类型
        MyTarget annotation = cls.getAnnotation(MyTarget.class);
        System.out.println(annotation.name());
    }
}
// 输出结果
tina

应用注解实现工厂模式

既然能够取得 Annotation 的设置内容,那么下面将利用 Annotation 来进行工厂设置模式的修改,在使用的客户端上定义

范例:可以利用 Annotation 改善工厂设计

public @interface MessageFactory{
	String className();
}

@MessageFactory("cn.tina.moduler.News")
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = TestDemo.class;
        // 取得指定的 Annotation 类型
        MyTarget annotation = cls.getAnnotation(MessageFactory.class);
        Class<?> cls = Class.forName(annotation.className());
        Object obj = (Book)cls.newInstance(); // 表示实例化对象
        System.out.println(obj); // 输出对象调用 toString()
    }
}

Annotation 好用,而且利用 Annotation 编写代码非常简洁,但是开发麻烦,可是现阶段开发之中会出现“程序 + 配置文件”、“程序 + Annotation” 共存的状态。

利用反射调用类中其他结构

Class 是反射之中最为重要的类,也是所有反射的操作源头。对于每一个结构(构造、方法、成员)都将利用 Class 找到,所以下面利用反射操作类的其他结构。

类的构造方法

每一个简单的 Java类 都要提供无参构造方法

利用 Class 类对象的 newInstance() 方法可以进行对象实例化操作,但是该操作的前提是类中有无参构造方法

public class Book {

    String name;
    double price;
    
    public Book(String name) {
        this.name = name;
    }

    public Book(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Book.class;
        Book book = (Book) cls.newInstance();
        System.out.println(book);
    }
}

如果类中没有无参构造方法,使用 newInstatce() 会出现异常信息,也就需要明确调用指定参数的构造方法,并且在实例化对象的时候要传入所需要的参数内容。

Class 类里面提供以下的操作方法:

  • 取得全部构造:

    public Construction<?>[] getConstruction() throws SecurityException
    
  • 取得指定参数的构造:

    public Construction<T> getConstruction(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
    

在反射过程中,Class 是一个只认类型不认具体对象的工具类,包括在进行方法重载的时候,认的时候也只是方法名称和参数类型。以上两个方法返回的是 java.lang.reflect.Constructor 类对象,这个类中有如下方法:

  • 取得构造方法的名称:public String getName()
  • 取得构造方法的修饰符:public int getModefiers()
    • 所有的修饰符都是通过数字编号取得的,如果想取得具体的内容必须将相应的数字转化会可以读懂的关键字,所以可以使用 java.lang.reflect.Modifier 类完成。在这个类里面提供了一个方法实现数字到关键字的还原 public static String toString() 根据数字还原修饰符
  • 取得参数类型:public Class<?>[] getParameterTypes()
  • 取得构造方法上抛出的全部异常:public Class<?>[] getExceptionTypes()

范例:取得全部构造方法的信息:

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Book.class;
        Constructor<?> constructor[] = cls.getConstructors();
        for (int i = 0; i < constructor.length; i++) {
            System.out.println(constructor[i]);
        }
    }
}
// 输出结果
public cn.tina.moduler.Book(java.lang.String)
public cn.tina.moduler.Book(java.lang.String,double)

实例化对象

Constructor 类里面提供有一个专门负责实例化的方法,这个方法可以传递指定参数的具体内容

  • 实例化对象:

    public T newInstance(Object... initarges) throws 
        InstantiationExcetion, 	 
        IlleagelAccessException,
    	IlleagelArgumentException,
    	IvocationTargetException
    

    范例:

    public class TestDemo {
        public static void main(String[] args) throws Exception {
            Class<?> cls = Book.class;
            Constructor<?> con = cls.getConstructor(String.class);
            Book book = (Book) con.newInstance("Develop");
            System.out.println(book);
        }
    }
    // 输出结果
    Book{name='Develop', price=0.0}
    

操作类中方法

继续看普通方法的调用,在 Class 类里面提供两组普通方法信息的获取

  • 第一组方法取得本类定义的方法:
  public Method[] getDeclaredMethods() throws SecurityException
      
  public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
      throws NoSuchMethodException, SecurityException 
  • 第二组方法本类的所有方法:
public Method[] getMethods() throws SecurityException 

public Method getMethod(String name, Class<?>... parameterTypes)
          throws NoSuchMethodException, SecurityException

范例:验证区别

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.tina.moduler.Book");
        Method method[] = cls.getMethods();
        for (int i = 0; i < method.length; i++) {
            System.out.println(method[i]);
        }
    }
}

// 输出结果
public java.lang.String cn.tina.moduler.Book.toString()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

使用 getMethod() 方法可以取得一个类之中所有定义的方法,包括自己定义的以及继承而来的方法

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.tina.moduler.Book");
        Method method[] = cls.getDeclaredMethods();
        for (int i = 0; i < method.length; i++) {
            System.out.println(method[i]);
        }
    }
}
// 输出结果
public java.lang.String cn.tina.moduler.Book.toString()

现在取得的是本类之中定义的所有操作方法,与继承无关。

以上代码利用了 Method 类之中的 toString() 方法取得了每一个方法的信息,也可以自己定义方法的输出。

除了有和构造一样的 getName()getModefiers()getParameterTypes()等方法之外,还有以下方法

  • 取得方法返回值类型:public Class<?> getReturnType()

反射调用方法

取得了 Constructor 类的对象是为了明确调用类之中指定参数的构造方法,在 Method 类里面提供以下一个重要方法。

public native Object invoke(Object obj, Object... args)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;

在使用以上 invoke() 方法操作的时候,一定要保证已经存在了本类的实例化对象,这种实例化对象可以直接利用 Object 代替,可以直接利用 Class 类反射实例化对象,而后通过 Object 类对象操作,没有必要向下转型了。

范例:反射调用方法

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.tina.moduler.Book");
        Object obj = cls.newInstance();
        // public String toString()
        Method method = cls.getMethod("toString");
        String str = (String) method.invoke(obj);
        System.out.println(str);
    }
}
//输出结果
Book{name='null', price=0.0}

获取 getter setter 方法

public static void main(String[] args) throws Exception {
    Class<?> cls = Class.forName("cn.tina.moduler.Book");
    Object obj = cls.newInstance();
    String property = "name";
    String value = "Develop";
    Method setMet = cls.getMethod("set" + initCap(property), String.class);
    Method getMet = cls.getMethod("get" + initCap(property));
    setMet.invoke(obj, value); // 调用 setName() 方法
    System.out.println(getMet.invoke(obj)); // 调用 getName() 方法
}

/**
 * 首字母大写
 * @param value
 * @return
 */
public static String initCap(String value) {
    if (value == null) {
        return "";
    }
    return value.substring(0, 1).toUpperCase() + value.substring(1);
}

// 输出结果
Book 的无参构造方法
Develop

以上就通过 类名称、属性名称、value 就可以通过反射 getter 和 setter方法。但是上面的方法有个问题,就是要指定 set 的参数类型,除了这一点,都能达到通用的作用。

调用成员

一个类中可以定义的成员:全局常量、全局变量、普通常量、普通变量都可以成为 Field(成员),在 Class 类里面提供了两组可以获取成员的方法:

  • 第一组: 得到全部成员,包括继承而来的成员,但是无法取得私有的

    # 取得全部成员
    public Field[] getFields() throws SecurityException
    
    # 取得单个成员
    public Field getField(String name) throws NoSuchFieldException
    
  • 第二组:取得本类的属性

    # 取得全部成员
    Field[] getDeclaredFields()
    
    # 取得单个成员
    Field getDeclaredField(String name) throws NoSuchFieldException
    

Field 类里面有一下几个方法很重要:

  • 得到属性类型 public Class<?> getType()

  • 得到属性内容 public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException

  • 设置属性内容 public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException

其中 get 和 set 方法都是代名词方法,有具体的 getBoolean,setBoolean,getInt, setInt 等方法。

java.lang.reflect 包之中最为核心的类一共有三个:Constructor, Method, Filed, 而这三个类是有共同继承关系的,它们都是 java.lang.reflect.AccessibleObject 的子类,而在 AccessibleObject 定义有如下方法:

  • 取得全部的 Annotationpublic Annotation[] getAnnotations()
  • 设置是否可以访问: public void setAccessible(boolean flag) throws SecurityException

反射调用属性

范例:反射调用属性

虽然可以通过反射进行属性的直接调用,但千万要记住,不要通过反射进行属性的内容操作,所有操作必须通过 setter getter 方法。

范例:利用 Field 来解决 Method 定义方法问题

public static void main(String[] args) throws Exception {
    Class<?> cls = Class.forName("cn.tina.moduler.Book");
    Object obj = cls.newInstance();
    String property = "name";
    String value = "Develop";
    Field nameField = cls.getDeclaredField(property);// 取得成员对象
    Method setMet = cls.getMethod("set" + initCap(property), nameField.getType());
    Method getMet = cls.getMethod("get" + initCap(property));
    setMet.invoke(obj, value); // 调用 setName() 方法
    System.out.println(getMet.invoke(obj)); // 调用getName() 方法
}

如果要编写的程序代码可以被所有的类都用到,就必须使用 Constructor, Method, Filed 三个类共同完成。

本文出至:学新通技术网

标签:

上一篇:java 反射(一)

下一篇:Java 注解