4.1java反射

反射的作用:反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。
反射的目的:获取一个class对应的Class实例。
反射的实现

  • 通过一个class的静态变量class获取:
    Class cls = String.class;
    
  • 若有实例变量,可用实例变量的.getClass()方法获取:
    String s = "Hello";
    Class cls = s.getClass();
    
  • 若知道class的完整类名,通过Class.forName()获取:
    Class cls = Class.forName("java.lang.String");
    

JVM的动态加载:JVM在执行java程序时,不是一次性将用到的class全部加载到内存,而是第一次使用才加载。
如何使用反射获取字段
需要注意的时,获取字段的field会经常出现错误,因此需要在方法之前抛出异常。

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
    返回的Field类似:
public int Student.score
public java.lang.String Person.name
private int Student.grade

一个Field对象包含了字段的所有信息,包括:

  • getName():返回字段名称,例如,“name”;
  • getType():返回字段类型,也是一个Class实例,例如,String.class;
  • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。
获取字段的值

字段值的获取是需要在有值时才能执行。
因此使用反射的第二种方法,根据实例值获取字段值。

public class Main {
    public static void main(String[] args) throws Exception {
        Object p = new Person("Xiao Ming");
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");
        f.setAccessible(true);//设置该字段在此类中可访问
        Object value = f.get(p);
        System.out.println(value); // "Xiao Ming"
    }
}
class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
}

根据以上demo,步骤为:

  1. 创建Obj实例对象p。
  2. 用.getClass()方法获取到实例的Class。
  3. 根据Class的.getField()方法获取到field。
  4. 根据获取到的field的get()方法获取到字段的值。
    注意:在获取private字段时用到getDeclaredField()方法,但即便获取到,在改类中也用不了,因此为了解决这个问题,将该字段设置为可访问即:f.setAccessible(true);,否则会出现异常。
    这是一个获取字段值的方法,体现了反射中不知道该类名并且能够获取到字段值的特点:
public static Object getFieldValue(Object obj,String name) throws Exception  {
		//设置为静态方法,可以不通过任何实例调用此方法。
		//方法参数有两个,第一个obj是传递的类,第二个是传递的类的属性。
		Class cla = obj.getClass();//获取到Class
		Field field = cla.getDeclaredField(name);//获取到field
		field.setAccessible(true);//设置field可访问
		return field.get(obj);//返回获取到的字段值
		
	}
设置字段值

有获取就有设置:
设置是通过Field.set(Object, Object)方法实现的。其中第一个是参数指定的实例,第二个是待修改的参数。

public static void setFieldValue(Object obj,String name,String setName) throws Exception  {
		//方法参数有三个,第一个为传递的类,第二个为传递类的字段名,第三个为设置的字段的值。
		Class cla = obj.getClass();
		Field field = cla.getDeclaredField(name);
		field.setAccessible(true);
		field.set(obj, setName);
		
	}

反射中的方法

和字段一样,Class类也提供了获取Method的方法:

  • Method getMethod(name, Class…):获取某个public的Method(包括父类)
  • Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有public的Method(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
    与字段不同的是,getMethod()的参数可以有两个,第一个是方法名,第二个是参数类型,可以无参数。
public class Main {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 获取public方法getScore,参数为String:
        System.out.println(stdClass.getMethod("getScore", String.class));
        // 获取继承的public方法getName,无参数:
        System.out.println(stdClass.getMethod("getName"));
        // 获取private方法getGrade,参数为int:
        System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
    }
}
class Student extends Person {
    public int getScore(String type) {
        return 99;
    }
    private int getGrade(int year) {
        return 1;
    }
}
class Person {
    public String getName() {
        return "Person";
    }
}

一个Method的对象包含的方法有:

  • getName():返回方法名称,例如:“getScore”;
  • getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
  • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};
  • getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。
获取到的方法调用:

使用Method.invoke(实例对象,参数);

public class Main {
    public static void main(String[] args) throws Exception {
        // String对象:
        String s = "Hello world";
        // 获取String substring(int)方法,参数为int:
        Method m = String.class.getMethod("substring", int.class);
        // 在s对象上调用该方法并获取结果:
        String r = (String) m.invoke(s, 6);
        // 打印调用结果:
        System.out.println(r);
    }
}

如果调用的是一个静态方法,则无需传入对象,invoke()方法的第一个参数填null
如果设置方法属性为public,则用.setAccessible()方法,参数为true,但注意有时候会报错。
接下来是一个例子,用于演示反射调用方法:

package test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class testMethod {
	
	public static void main(String[] args) throws Exception {
		testMethod t = new testMethod();
		Method method1 = getMethod(t, "print", int.class);
		method1.invoke(t, 1);
		Method method = getMethod(t, "print", int.class,String.class);
		method.invoke(t, 1,"这是一首简单的小情歌");
	}
	//使用java中的可变参数,注意可变参数一定要放在最后,因为当你可变参数之后还有参数的话,那么最后一个参数一定不能成功赋值
	public static Method getMethod(Object obj,String methodname,Class... classes) throws Exception {
		//参数:第一个实例对象,第二个获取的方法名,第三个参数类型,第四个参数值。
		Class class1 = obj.getClass();//获取实例的类
		//根据方法名和参数类型获取方法
		Method method = class1.getMethod(methodname, classes);
		//调用方法
		return method;
	}
	//重载,一个参数
	public void print(int a) {
		// TODO Auto-generated method stub
		System.out.println(a);
	}
	//重载,两个参数
	public void print(int a,String text) {
		// TODO Auto-generated method stub
		System.out.println(a+text);
	}
}

结果

1
1这是一首简单的小情歌
反射如何调用构造方法

反射中调用无参构造方法:
Class提供了一个方法是newInstance():供其创建一个新的实例,仅限于无参构造方法

Person p = Person.class.newInstance();

调用有参数的构造方法:
java反射的API提供了Constructor对象,它包含了构造方法的所有信息,能够去创建一个实例。
Constructor与Method非常相似,都是可以创建一个方法,不同的是Constructor创建的是一个构造方法。
使用Method和Constructor返回的都是该对象方法的一个实例,获得实例之后,调用的invoke和newInstance方法是Method和Constructor的方法(这里的newInstance和Class提供的newInstance方法不同。)

通过Class实例获取Constructor的方法:

  • getConstructor(Class…):获取某个public的Constructor;
  • getDeclaredConstructor(Class…):获取某个Constructor;
  • getConstructors():获取所有public的Constructor;
  • getDeclaredConstructors():获取所有Constructor。
public class TestConstructor  {
	public static void main(String[] args) throws Exception {
		Class class1 = Person.class;
		//参数和Method一样,是该构造方法传递的参数类型
		Constructor constructor = class1.getConstructor(String.class);
		//创建实例对象
		Object object = constructor.newInstance("张三");
		//创建getName方法对象
		Method m = getMethod(object, "getName");
		//调用getName方法
		System.out.println(m.invoke(object));	
	}
	public static Method getMethod(Object obj,String methodname,Class... classes) throws Exception {
		//参数:第一个实例对象,第二个获取的方法名,第三个参数类型,第四个参数值。
		Class class1 = obj.getClass();//获取实例的类
		//根据方法名和参数类型获取方法
		Method method = class1.getMethod(methodname, classes);
		//调用方法
		return method;
	}
}
向上获取类(父类或接口)

获取父类:用Class的getSuperclass()方法
获取接口:用Class的getInterface()方法
继承关系:instanceof判断的是该实例是否属于该类型
isAssignableForm()判断的是前者是否能够向上转型为后者

Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true
boolean isNumber = n instanceof Number; // true
boolean isSerializable = n instanceof java.io.Serializable; // true
// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer

代理

借鉴链接
静态代理:
一个接口,一个被代理类,一个代理类,代理和被代理类均继承这个接口,被代理类不做实际操作,一切由代理类来完成,在代理类中创建被代理类的对象进行操作。
接口:

public interface HelloInterface {
    void sayHello();
}

被代理类:

public class Hello implements HelloInterface{
    @Override
    public void sayHello() {
        System.out.println("Hello zhanghao!");
    }
}

代理类:

public class HelloProxy implements HelloInterface{
    private HelloInterface helloInterface = new Hello();
    @Override
    public void sayHello() {
        System.out.println("Before invoke sayHello" );
        helloInterface.sayHello();
        System.out.println("After invoke sayHello");
    }
}

调用代理类:

    public static void main(String[] args) {
        HelloProxy helloProxy = new HelloProxy();
        helloProxy.sayHello();
    }
    
输出:
Before invoke sayHello
Hello zhanghao!
After invoke sayHello

使用静态代理很容易就完成了对一个类的代理操作。但是静态代理的缺点也暴露了出来:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。
动态代理
动态创建接口对象的方式叫做动态代理。
在运行期动态创建一个interface实例的方法如下:

  1. 定义一个InvocationHandler实例,它负责实现接口的方法调用;
  2. 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
    - 使用的ClassLoader,通常就是接口类的ClassLoader;
    - 需要实现的接口数组,至少需要传入一个接口进去;
    - 用来处理接口方法调用的InvocationHandler实例。
  3. 将返回的Object强制转型为接口。
public class Main {
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                }
                return null;
            }
        };//创建接口的对象
        Hello hello = (Hello) Proxy.newProxyInstance(
            Hello.class.getClassLoader(), // 传入ClassLoader
            new Class[] { Hello.class }, // 传入要实现的接口
            handler); // 传入处理调用方法的InvocationHandler
        hello.morning("Bob");
    }
}
//一个接口
interface Hello {
    void morning(String name);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值