java 反射学习(下)

2020-04-15  本文已影响0人  tingtingtina
知识导图

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

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 的范围:

如果要在编写的程序可以在运行的时候使用,使用 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,但是在 Annotation 使用的时候里面可以设置属性,现在有一个参数内容 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 类里面提供以下的操作方法:

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

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

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 类里面提供有一个专门负责实例化的方法,这个方法可以传递指定参数的具体内容

操作类中方法

继续看普通方法的调用,在 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()等方法之外,还有以下方法

取得了 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 类里面提供了两组可以获取成员的方法:

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

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

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

范例:反射调用属性

虽然可以通过反射进行属性的直接调用,但千万要记住,不要通过反射进行属性的内容操作,所有操作必须通过 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 三个类共同完成。

上一篇下一篇

猜你喜欢

热点阅读