反射
Class类的使用
在Java语言中,万事万物皆为对象,那么问题来了,"类"是谁的对象呢?
类是对象,任何一个类都是java.lang.Class
类的实例对象
基本的数据类型,乃至于void关键字,都存在其对应的类类型(class type)
下面是获取自定义类的类类型(class type)
三种方法分别是:
- 已知类名,通过类名.class 调用class静态成员变量
- 已知对应类的对象,通过对象.getClass() 获取对应类的Class
- 已知自定义类的路径,通过调用Class.forName("xxxx") 来获取
▲ 不管是通过上面哪种方法获取,任何一个类,其对应的Class ,都只会有一个。
java
代码解读
复制代码
public class demo1 {
public static void main(String[] args) {
reflectionDemo reflect = new reflectionDemo();
// 任何一个类都是Class的实例对象
//第一种表达方式 静态成员变量
Class c1 = reflectionDemo.class;
//第二种表达方式 已知某个类的对象,通过其对象调用getClass()方法来获取
Class c2 = reflect.getClass();
// 第三种表达方式
Class c3=null;
try{
c3 = Class.forName("Day2.reflectionDemo");
}
catch (ClassNotFoundException e){
e.printStackTrace();
}
System.out.println(c3==c2);
}
}
class reflectionDemo{
}
动态加载类
在Java中,所有直接用new 来创建对象的方式,都叫做静态加载类。只要用这种方法来new对象,那么在编译阶段,都会检查这个对应的类在代码中是否实实在在的存在。
然后在很多时候这都是不必要的。比如说:
java
代码解读
复制代码
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int num=scanner.nextInt();
if(num == 1){
Dog dog = new Dog();
}
else if(num==2){
Cat cat = new Cat();
}
}
上面的代码中,由于是通过new来创建类的对象,所以在编译阶段,就会检查代码中是否确实有Dog类和Cat类。
然而 ,换一个思路,实际上最终而言,最极端的可能,最终Dog类也没用上,Cat类也没用上。(比方说我输入的是3)。那么这时候就要考虑动态加载类。
换一个写法,获取命令行参数来创建对象(下面这儿还是静态创建):
java
代码解读
复制代码
public static void main(String[] args){
if(args[0].equals("Dog")){
Dog dog = new Dog();
}
if(args[0].equals("Cat")){
Cat cat = new Cat();
}
}
如果改成动态创建呢?(下面就暂时不考虑exception)
我们假设Dog类和Cat类都有makeNoise()方法
java
代码解读
复制代码
public static void main(String[] args) throws ClassNotFoundException{
Class c = Class.forName(args[0]);
//下面准备调用 c.newInstance()来动态创建类的对象
//但是args[0]究竟是什么呢?我们又究竟应该创建哪个类对象呢? 这都是未知的。
}
这时候再结合Java的多态
java
代码解读
复制代码
interface Noise{
public void makeVoice();
}
class Dog implements Noise{
public void makeVoice(){
System.out.println("小狗汪汪");
}
}
class Cat implements Noise{
public void makeVoice(){
System.out.println("小猫喵喵");
}
}
java
代码解读
复制代码
public static void main(String[] args) throws ClassNotFoundException{
Class c = Class.forName(args[0]);
Noise n = (Noise)c.newInstance();
n.makeVoice();
}
获取方法信息
以下是如何使用反射来获取这些信息的步骤:
- 获取
Class
对象:首先,需要获取到目标类的Class
对象,这可以通过类名的.class
语法或者Class.forName()
方法实现。 - 获取
Method
对象:使用Class
对象的getMethod()
或getDeclaredMethod()
方法来获取Method
对象。getMethod()
只能获取公共(public)方法,而getDeclaredMethod()
可以获取所有方法,不论其访问权限。
- 准确地说:
getMethod()
方法,能够获取到的是当前类的public
方法以及其父类的public
方法。 getDeclaredMethod()
方法能够获取到的是当前类的所有方法,无论其方法对应的访问权限修饰符是什么
- 获取方法的返回值类型:通过
Method
对象的getReturnType()
方法可以获取方法的返回值类型。 - 获取方法名:通过
Method
对象的getName()
方法可以获取方法名。 - 获取方法的参数类型:通过
Method
对象的getParameterTypes()
方法可以获取方法的参数类型数组。
获取成员变量的信息
java
代码解读
复制代码
public static void main(String[] args) {
Class c = Double.class;
Field[] fields = c.getFields();
for(Field field:fields){
Class<?> type = field.getType();
// 获取类型名称
String typeName = type.getName();
// 获取参数名称
String fieldName = field.getName();
System.out.println(typeName +" "+fieldName);
}
}
注: 这里 getFields()
方法获取的是所有public的成员变量的信息,如果是其他的权限修饰符的话,就无法获取。
如果是想获取构造函数信息的话 同理 通过Constructor获取
方法反射的基本操作
- 如何获取某个方法
- 方法的名称和方法的参数列表才能唯一决定某个方法
- 所以这里也就能够落实到Java中方法的重载——Java中方法的重载(已经同名了),仅体现在两个方面,方法的参数数量 以及 方法的类型上
- 方法反射的操作
- method.invoke(对象,参数列表)
java
代码解读
复制代码
public class demo1 {
public static void main(String[] args) {
Printer p = new Printer();
Class c = p.getClass();
try{
Method method1 = c.getMethod("print");
method1.invoke(p);
Method method2 = c.getMethod("print",new Class[]{int.class,int.class});
method2.invoke(p,new Object[]{10,20});
Method method3 = c.getMethod("print",new Class[]{int.class,String.class});
method3.invoke(p,new Object[]{10,"asdf"});
}
catch(Exception e){
e.printStackTrace();
}
}
}
class Printer{
public void print(){
System.out.println("无参print");
}
public void print(int a,int b){
System.out.println("这是双int形参的print方法");
}
public void print(String a,String b){
System.out.println("这是双String的print方法");
}
public void print(int a,String b){
System.out.println("这是int和String各一个形参的print方法");
}
}
输出如下:
无参print 这是双int形参的print方法 这是int和String各一个形参的print方法
通过反射了解集合泛型的本质
通过这里的反射,更好地去理解上面泛型章节中说的“类型擦除”究竟是什么
java
代码解读
复制代码
public class demo1 {
public static void main(String[] args) {
List list1 = new ArrayList();
List<String> list2 = new ArrayList<>();
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1);
System.out.println(c2);
System.out.println(c1==c2);
//--------------------
try{
Method method=c2.getMethod("add",Object.class);
method.invoke(list2,1);
System.out.println(list2.size());
}
catch (Exception e){
e.printStackTrace();
}
}
}
▲ 注意看这里的输出
首先打印出来c1 和 c2都是class java.util.ArrayList
并且c1==c2 为true
然后专门add一个1到list2
其size从0 -》1 明显看到已经add进来了。这就是类型擦除!!
class java.util.ArrayList class java.util.ArrayList true 1
反射小练习:
注意:
- 如果在动态创建对象的时候,希望传参,那么只能通过获取对应的有参构造函数,并通过该构造函数来进行创建对象
- 正如下面例子中的
bark
方法所示,如果希望通过invoke
来调用private
修饰的成员方法,那么必须要调用setAccessable()
方法,使其变得“可访问”
java
代码解读
复制代码
package com.nylonmin;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* 练习反射
*
*/
public class Demo1 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("com.nylonmin.Dog");
Method[] methods = c.getDeclaredMethods();
System.out.println("类 "+c.getName()+" 具有以下方法");
for(Method method:methods){
System.out.println(method.getName());
}
System.out.println("-----------");
//step1 动态创建对象 通过构造函数来创建 调用有参构造函数
Constructor constructor = c.getConstructor(String.class,int.class);
Dog dog = (Dog)constructor.newInstance("小灰灰", 2);
//step2 尝试调用bark方法
Method barkMethod = c.getDeclaredMethod("bark", String.class);
//因为bark这个方法被我定义成为私有的 所以要让其变得可访问
barkMethod.setAccessible(true);
barkMethod.invoke(dog,"汪汪汪");
}
}
class Dog{
private String name;
private int age;
public Dog(){}
public Dog(String name,int age){
this.name=name;
this.age=age;
}
//特意定义为私有方法 看看获取情况
private void bark(String voice){
System.out.println(this.name+"是这样叫的——"+voice);
}
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;
}
}
动态代理
程序为什么需要代理?代理长什么样?
- 对象如果嫌身上干的事太多的话,可以通过代理来转移部分职责。
- 对象有什么方法想被代理,代理就一定要有对应的方法。
代理就相当于是明星的经纪人,为什么说对象有什么方法想被代理,代理就一定要有对应的方法呢?
仍然是类比到明星和经纪人,比方说某老板想找明星唱歌,他势必首先会找到其经纪人进行对接,这时候如果老板看到经纪人根本不对外接唱歌业务,那肯定无法完成。
动态代理实例
从这个实例中实际上我们就能够一定程度地去理解Java中的AOP面向切面编程的概念了。
现在有这么个场景,比如某个类有一百个方法,现在要求对于执行该类中每个方法,都有一个性能指标,要求其运行时间不超过2秒钟。—— 那么最简单的思路就是在每个方法中都加入当前开始时间、当前结束时间 以及 计算时长的代码;这样导致的结果就是这100个方法中都充斥了重复且与业务无关的代码。
这时候就要用到动态代理了。
▲ 为什么呢?上面提到的这个类,实际上就相当于是我们前面说的杨超越大明星,那是她作为明星的业务功能,而计算开始时间结束时间之类的,是经纪人的活儿,我们需要用一个代理来帮大明星做。
JAVA
代码解读
复制代码
package com.nylonmin.Proxy;
public interface UserService {
public void login() throws InterruptedException;
public void sing(String songName) throws InterruptedException;
}
//------------------------
package com.nylonmin.Proxy;
public class UserServiceImpl implements UserService{
private String name;
private int age;
public UserServiceImpl(){}
public UserServiceImpl(String name,int age){
this.name=name;
this.age=age;
}
@Override
public void login() throws InterruptedException {
System.out.println("用户进入本系统");
Thread.sleep(1000);
}
@Override
public void sing(String songName) throws InterruptedException {
System.out.println("用户开始唱"+songName);
int num=0;
for(int i=0;i<100;i++){
num++;
}
Thread.sleep(1000);
}
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 static UserService createProxy(UserService userService111)
这里面的形参,传入的是大明星,也就是说要对谁进行代理,这里的返回值类型UserService
表示我们想要创建什么类型的代理。UserService userServiceProxy = (UserService) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),new Class[]{UserService.class}, new InvocationHandler()
- 第一个参数 直接是当前类的class loader加载就行了 第二个参数 new一个接口的数组,指定代理长什么样 第三个参数new InvocationHandler指定代理具体干啥事儿
java
代码解读
复制代码
package com.nylonmin.Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public interface ProxyUtil {
public static UserService createProxy(UserService userService111){
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{UserService.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("sing")||method.getName().equals("login")){
long startTime = System.currentTimeMillis();
Object rs = method.invoke(userService111, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+"执行耗时"+(endTime-startTime)/1000.0 +"秒");
return rs;
}
//传入的可能是其他方法 比如get set方法
else{
Object invoke = method.invoke(userService111, args);
return invoke;
}
}
});
return userServiceProxy;
}
}