javaSE反射(高级开发)

版权声明:白小姐 https://blog.csdn.net/bailerong123/article/details/88910097


在这个专题,我们主要谈一谈java的高级开发的反射问题,我们的目标有以下几个
1、认识反射机制
2、反射与类操作
3、反射与简单java类
4、ClassLoader类加载器
5、反射与代理设计模式
6、反射与Annotation
这些问题,我总共分为两篇博客来讲,上干货吧

认识反射机制

初识反射
我们大多数人听到反射两个字都会一头雾水,到底什么是反射,反射到底有什么用,我们应该怎么用我们的反射,这些问题我相信都困扰着你和我,所以我们就从最忌粗话的地方一点点抽丝剥茧吧
反射指的是对象的反向处理操作,既然是反向处理。我们先来观察以下“正向”的操作。在默认的情况下,必须要先导入一个包,然后才能实例化对象
我们现在来观察以下我们的正向处理的代码:

import java.util.Date;

public class Test {
    public static void main(String[] args) {
        Date date=new Date();
        System.out.println(date);
    }
}

我们都很熟悉上面的操作,我们现在来总结以下我们的操作,以上是我们正常的关于对象的处理流程:根据包名.类名找到类
所谓的“反”指的是根据对象来取得对象的来源信息,而这个“反”的操作核心的处理就在与Object类的一个方法:
取得Class对象:

    public final native Class<?> getClass();

该方法返回的是一个Class类对象,这个Class描述的就是类。
范例:调用getClass()方法

import java.util.Date;

public class Test {
    public static void main(String[] args) {
        Date date=new Date();
        System.out.println(date.getClass());
    }
}

我们来看一下它的运行结果:

class java.util.Date

Process finished with exit code 0

我们发现,我们此时通过对象取得了对象的包,这就是“反”的本质
在反射的世界里面,看中的不再是一个对象,而是对象后的组成(类,构造、普通、成员等)
1.2Class类对象的三种实例化模式:
Class类是描述整个类的概念,也是整个反射的操作源头,在使用Class类的时候需要关注的一虽然是这个类的对象,而这个类的对象的产生模式一共有三种:
1、任何类的实例化对象可以通过Object类中的getClass()方法取得Class类对象
2、“类.Class”直接根据某个具体的类来取得Class类的实例化对象。
3、使用Class类提供的方法:“publlic static Class<?> forName(String className)throws ClassNotFoundException”

import java.util.Date;
public class Test1 {
    public static void main(String[] args) throws ClassNotFoundException {
        //类加载的时候便产生
        Date date=new Date();
        //根据类,正向产生了对象
        //System.out.println(date);
        //1、通过对象。getClass
        System.out.println(date.getClass());
        //2、通过类名称.Class
        System.out.println(Date.class);
        //3.通过调用Class类提供的静态方法forName(String className)
        System.out.println(Class.forName("java.util.Date"));
    }
}

在以上给出的三个方法中我们可以发现,除了第一种方法会产生Date类的实例化对象之外,其他的两种都不会产生Date类的实例化对象。于是取得了Class类对象有一个最直接的好处:可以通过反射实例化对象,在Class类种定义有如下方法:

public T newInstance()
    throws InstantiationException, IllegalAccessException

范例:反射实例化对象

public class Test {
    public static void main(String[] args) throws Exception {
        Class<?> cls=Class.forName("java.util.Date");
        Object obj=cls.newInstance();
        System.out.println(obj);
    }
}
Fri Mar 29 13:03:34 CST 2019

Process finished with exit code 0

现在发现除了关键字new之外,对于对象的实例化模式有了第二种做法,通过反射进行。
取得了Class对象就以为着取得了一个指定类的操作权。
我们也可以通过上面看到,我们不需要再进行导包,便可以实例化出我们的Date对象

1.3反射与工厂设计模式
工厂设计模式曾经给过原则:如果是自己编写的接口,想要取得本接口的实例化对象,最好使用工厂类来设计。但是也需要知道传统工厂设计所带来的问题。
接下来,我们就进行我么的传统的工厂模式与我们的反射工厂模式的对比,从而看出我们的反射对与我们的工厂模式的优化。
传统工厂类:


interface IFruit{
    void eat();

}
class Apple implements IFruit{
    @Override
    public void eat() {
        System.out.println("eat an apple");
    }
}
class Orange implements IFruit{
    @Override
    public void eat() {
        System.out.println("eat an Orange");
    }

}
class Factor{
    private Factor(){}
    public static IFruit getInstance(String className){
        if(className.equals("apple")){
            return new Apple();
        }else if(className.equals("orange")){
            return new Orange();
        }
        return null;

    }
}
public class FactorTest {
    public static void main(String[] args) {
        IFruit fruit=Factor.getInstance("Apple");
        fruit.eat();
    }
}


interface IFruit{
    void eat();

}
class Apple implements IFruit{
    @Override
    public void eat() {
        System.out.println("eat an apple");
    }
}
class Orange implements IFruit{
    @Override
    public void eat() {
        System.out.println("eat an Orange");
    }

}
//用反射修改我们的工厂方法
class Factor{
    private Factor(){}
    public static IFruit getInstance(String className){
        IFruit fruit=null;
        try {
            //取得任意子类的反射对象
            Class<?> cls=Class.forName(className);
            //通过反射取得实例化对象
            fruit=(IFruit) cls.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return fruit;
    }
}
public class FactorTest {
    public static void main(String[] args) {
        IFruit fruit=Factor.getInstance("Apple");
        fruit.eat();
    }
}

通过对比我们可以发现,我们的传统的工厂模式,并不实用。我们每每增加新的“产品”时,我们的工厂当中就要增加新的分支,这往往会造成我们的代码冗长,每次修改的时候改动的代码量增加,我们再用来我们的反射机制之后,我么的工厂类不需要再进行变动。这样我们的工厂类才有了自己的意义。
我们将我们的利用反射机制构建的工厂类称为简单工厂类。

反射与类操作

利用反射可以做出一个对象具备的所有操作行为;最为关键的是这一切的操作都可以基于Object进行

2.1取得父类信息

在java中任何的程序都一定会有父类,在Class类中就可以通过如下方法来取得父类或者实现的父接口

//我们先来看以下我们需要用到的接口
取得类的包名称: public Package getPackage()
取得父类的Class对象: public native Class<? super T> getSuperclass()
取得实现的父接口:public Class<?>[] getInterfaces()

范例:取得包名称和取得父接口

class person{}
interface Massage{}
interface subject{}
class student extends person implements Massage ,subject{}
public class MypackgeTest {
    public static void main(String[] args) {
     Class<?> cls=student.class;
     //取得我们的父类信息
        System.out.println(cls.getSuperclass().getName());
        //取得我们的父接口的信息
        Class<?> []clases=cls.getInterfaces();
        for(Class clss:clases){
            System.out.println(clss.getName());
        }
    }
}

通过反射可以取得类结构上的所有关键信息

2.2反射调用构造

一个类中可以存在多个构造方法,如果想要取得类中构造的调用,就可以用Class类中提供的两个方法:

  • 取得指定参数类型的构造
public Constructor<T> getConstructor(Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException`
  • 取得类中所有的构造
 public Constructor<?>[] getConstructors() throws SecurityException 

以上两个方法返回类型都是java.lang.reflect.Constructor类的实例化对象,这个类之中大家只需要关注一个方法:
实例化对象:

  public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException

范例:取得类中所有构造信息

import java.lang.reflect.Constructor;

class Person{
    String name;
    Integer age;
   public Person(){}
    public Person(String name){}
    public Person(String name,Integer age){}
}
public class uniqueConstructor {
    public static void main(String[] args) {
        Class<?>cls=Person.class;
        Constructor<?>[] constructors=cls.getConstructors();
        for(Constructor<?> constructor:constructors){
            System.out.println(constructor);
        }
    }
}

运行结果是:

public Person()
public Person(java.lang.String)
public Person(java.lang.String,java.lang.Integer)

Process finished with exit code 0

所以我们通过上述结果,我们可以看到,以上结果是直接利用了Constructor类中的toString()方法取得了构造方法的完整信息(包含方法权限,参数列表),而如果你指使用了getName()方法,只会返回构造方法的包名.类名
在定义简单java类的时候一定要保留有一个无参构造
范例:观察Class实例化对象的问题

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

class Person{
    private String name;
    private int age;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class uniqueConstructor {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        Class<?> cls=Person.class;

        System.out.println(cls.newInstance());

    }
}

Exception in thread "main" java.lang.InstantiationException: Person
	at java.lang.Class.newInstance(Class.java:427)
	at uniqueConstructor.main(uniqueConstructor.java:24)
Caused by: java.lang.NoSuchMethodException: Person.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.newInstance(Class.java:412)
	... 1 more

Process finished with exit code 1

我们发现我们直接通过我们的Class实例化我们的对象,我们会出先我们的没有初始化异常,这是因为我们的Class类通过反射实例化对象的时候,只能够调用类中的午餐构造。如果现在类中没有无参构造则无法使用Class调用,只能够通过明确的构造调用实例化处理。
范例:通过Constructor类实例化对象

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

class Person{
    public String name;
    public int age;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class uniqueConstructor {
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        Class<?>cls=Person.class;
        Constructor<?> constructors
    = cls.getConstructor(String.class,int.class);
        System.out.println(constructors.newInstance("lele",18));

    }
}

我们来看一下我们的运行结果:

Person{name='lele', age=18}

Process finished with exit code 0

我们可以得到的经验是:以后我们的简单java类要写我们的无参构造

2.3反射调用普通方法(核心)

类中普通方法调用在我们的平常开发之中非常常见,如果使用合理可以节省大量的重复编码工作。在Class类中如下两种取得类中普通方法的函数

  • 取得全部普通方法
 public Method[] getMethods() throws SecurityException 
  • 取得指定普通方法
  public Method getMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException
       // 先是函数的名称进去,再传递我们的类型,因为存在我们的函数的重载。

以上两个方法的时java.lang.reflect.Method类的对象,在类中提供有一个调用方法的支持:

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

范例:取得一个类中的全部普通方法

import java.lang.reflect.Method;

class SPerson{
    private String name;
    private int age;
    public SPerson(){}
    public SPerson(String name,int age){
        this.name=name;
        this.age=age;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
public class getNewMethod {
    public static void main(String[] args) {
Class<?> cls=SPerson.class;
        Method[]methods=cls.getMethods();
        for(Method method:methods){
            System.out.println(method);
        }
    }
}

我们来观察以下我们的运行结果:

public java.lang.String SPerson.toString()
public java.lang.String SPerson.getName()
public void SPerson.setName(java.lang.String)
public void SPerson.setAge(int)
public int SPerson.getAge()
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()

Process finished with exit code 0

我们得到了我们的Person类的所有方法
之前程序编写的简单java类中的getter、settter方法采用的都是明确的对象调用。
而现在有了反射机制处理之后,即使你没有明确的Person类型对象(依然需要实例化对象,Object对象描述,所有的普通方法必须在有实例化对象之后才可以进行调用。)就可以通过反射调用。
范例:通过反射调用我们的setter、gettter方法

import java.lang.reflect.Method;

class SPerson{
    private String name;
    private int age;
    public SPerson(){}
    public SPerson(String name,int age){
        this.name=name;
        this.age=age;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
public class getNewMethod {
    public static void main(String[] args) throws Exception{
Class<?> cls=Class.forName("SPerson");
//任何时候调用类中的普通方法都需要实例化对象
        Object object=cls.newInstance();
//取得setName方法的实例化对象,并且设置方法名称与类型参数
        Method setMethod=cls.getMethod("setName",String.class);
        //随后需要通过Method类实例化对象,调用指定的方法
        setMethod.invoke(object,"lele");//相当于Person类,setName(lele)
        Method getMethod=cls.getMethod("getName");//相当于Person类,getName
        Object result=getMethod.invoke(object);
        System.out.println(result);
        }
    }

这种操作的好处在于不再局限于某一类型的对象,而是可以通过Object而类型进行所有类的方法调用。这个操作必须掌握

//取得本类以及父类中所有的public方法
public Metnod[] getMethods() throws SecurityException
//取得本类中全部方法,包含私有方法
public Method[] getDeclaredMethods() throws SercurityException

我们现在来看我们例子

package NewAdd;

import java.lang.reflect.Method;

class Person{
    public void Test(){};
    protected void lele(){};
    private void haha(){};
}
class Student extends Person{
    public void Test1(){};
    protected void lele1(){};
    private void haha1(){};
}
public class StudentTest
{
    public static void main(String[] args) {
        Class<?> cls=Student.class;
        {
            Method[] methods=cls.getMethods();
            for(Method method:methods){
                System.out.println(method);
            }
        }
        {
            System.out.println("================================");
            Method[] methods=cls.getDeclaredMethods();
            for(Method method:methods){
                System.out.println(method);
            }
        }
    }

}

我们来看一下我们的运行结果:

public void NewAdd.Student.Test1()
public void NewAdd.Person.Test()
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 java.lang.String java.lang.Object.toString()
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()
================================
public void NewAdd.Student.Test1()
private void NewAdd.Student.haha1()
protected void NewAdd.Student.lele1()

我们发现,我们在取得本类以及符集中的全部普通方法的时候,我们的getMethods方法会一直取到Object类中的public方法。

2.4反射调用类中属性

在之前已经成功的实现了类的构造调用,方法调用,除了这两种模式之外还有类中属性调用。
前提:类中的所有属性一定在类对象实例化之后才会进行空间分配,所以此时如果要想调用类的属性,必须保证其有实例化对象。通过反射的newInstance()可以直接取得实例化对象(Object类型)
在Class类中提供有两组取得属性的方法

//1、第一组(父类中)-取得类中全部属性:
public Field[] getFields() throws SecurityException 
//2、第一组(父类中)-取得类中指定名称属性
 public Field getField(String name) throws NoSuchFieldException, SecurityException
 //3、第二组(本类中)-取得类中全部属性:
  public Field[] getDeclaredFields() throws SecurityException
 //4、第二组(本类中)-取得类中指定名称属性:
  public Field getDeclaredField(String name)throws NoSuchFieldException, SecurityException 

范例:取得类中全部属性

import java.lang.reflect.Field;
/*
* 这是一个我们通过反射获取类属性的代码
* */
class Teacher{
    public int age;
    public String name;
}
class Student extends Teacher{
    private String school;
}
public class FiledsTest {
    public static void main(String[] args) throws Exception {
    Class<?> cls=Class.forName("Student");
        {
            Field[] fields=cls.getFields();
            for(Field field:fields){
                System.out.println(field);
            }
        }
        System.out.println("-----------------");
        {
            Field[] fields=cls.getDeclaredFields();
            for(Field field:fields){
                System.out.println(field);
            }
        }
    }
}

我们的运行结果:

//父类的
public int Teacher.age
public java.lang.String Teacher.name
-----------------
//子类的
private java.lang.String Student.school

在实际开发之中,属性基本上都会进行封装处理,所以没有必要去关注父类中的属性。也就是说以后取得的属性都以本类属性为主。
而后就需要关注属性的核心描述类:java.lang.reflect.Field,在这个类之中有两个重要方法:

//1、设置属性内容
  public void set(Object obj, Object value)
        throws IllegalArgumentException, IllegalAccessException
//2、取得属性内容
    public Object get(Object obj)
        throws IllegalArgumentException, IllegalAccessException

范例:通过反射操作属性

import java.lang.reflect.Field;

/*
* 这是一个我们通过反射设置属性的代码
*
* */
class Person1{
    public  String name;
}
public class SetField {
    public static void main(String[] args) throws Exception {
        Class<?> cls=Class.forName("Person1");
        Object object=cls.newInstance();
        //实例化操作我们的类属性
        Field nameField=cls.getDeclaredField("name");
        nameField.set(object,"lele");
        System.out.println(nameField.get(object));
    }
}

java反射继承
在AccessibleObject类中提供有一个方法:
动态设置封装:

//在本次JVM进程中有效且只能通过反射调用
public void setAccessible(boolean flag) throws SecurityException 

范例:动态设置封装:

import java.lang.reflect.Field;

/*
* 这是我们的一个破坏类中属性封装的代码
*/
class Person2{
    private String name;
}
public class rudeAccess {
    public static void main(String[] args) throws Exception {
        Class<?> cls=Class.forName("Person2");
        Object object=cls.newInstance();
        Field field=cls.getDeclaredField("name");
        field.setAccessible(true);
        field.set(object,"lele");
        System.out.println(field.get(object));
    }
}

我们现在来看一下我们的结果:

lele

Process finished with exit code 0

好了,代码就这些了,我们今天的代码量还是很大的,所以我们需要的就是将我们的今天学习的东西总结一下:
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/bailerong123/article/details/88910097