第一:先认识Class类
类也是一种实例,类的实例创建有三种方式。
类的实例对象创建的方式有两种。以下的代码详细的介绍:
package com.company; import java.util.*; public class Main { public static void main(String[] args) { //类的实例创建有两种方式 Vae xs = new Vae(); xs.music(); //每个类都是一个实例的对象,Vae类是一个Class类的对象,其实例表达方式有3种 //第1种,类的class Class c1 = Vae.class; //第2种,类的实例的getClass Class c2 = xs.getClass(); //第3种,类的名字,这种是最常用的方式,我们在写Java反射的时候会经常使用 Class c3 = null; try { c3 = Class.forName("com.company.Vae"); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println(c1 == c2); System.out.println(c2 == c3); //类的实例的第2种创建方式 try { Vae xs2 = (Vae) c1.newInstance(); xs2.music(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } class Vae{ public void music(){ System.out.println("许嵩是最佳歌手"); } }
这段代码的输出结果是:
下面来整理一下代码,首先,类的实例的创建的两种方法:
//第一种,最常见的new Vae xs = new Vae(); //第二种,newInstance()方式,这种方式必须加try catch,否则会报错 try { Vae xs2 = (Vae) c1.newInstance(); xs2.music(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }
其次,类实例的创建方法,其实就是获取这个类的类型,有三种:
//第1种,类的class Class c1 = Vae.class; //第2种,类的实例的getClass Class c2 = xs.getClass(); //第3种,类的名字,这种是最常用的方式,我们在写Java反射的时候会经常使用 Class c3 = null; try { c3 = Class.forName("com.company.Vae"); } catch (ClassNotFoundException e) { e.printStackTrace(); }
第三种的确是最常见的,而且c1,c2,c3是一样的,输出==的时候结果是true。
这些就是Class类的全部知识。先记住。接下来讲解Java的动态加载和静态加载。很快,就可以触及到反射了。
第二:Java的静态加载和动态加载
加载分为静态加载和动态加载。
什么是静态加载呢?你写的代码在编译的时候,就是静态加载。凡是编译不能通过的代码,都是属于静态加载的。例如类创建对象的new关键字。
什么是动态加载呢?动态加载就是在代码运行的时候才加载的,C#里面有一个类型是dynamic,而在Java里面就是利用的我们上面讲的Class.forName
我先来举个例子,解释一下上面两句话:
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scan=new Scanner(System.in); Start(scan.nextLine()); } public static void Start(String name) { if("Vae".equals(name)){ Vae xs=new Vae(); xs.music(); } else{ System.out.println("我是出口"); } }
这是我在idea里面写的代码,编译的时候,报错,说没有Vae这个类。这就说明了在new Vae的对象的时候,编译是不通过的,因为在编译的时候就已经开始加载了。这说明了,new 类的对象是属于静态加载的。
现在我们写一个Vae类出来:
public class Vae { public void music() { System.out.println("许嵩是最佳歌手"); } }
然后我们把Vae类名传递过去。运行,输入 Vae
结果如下:
非常完美。那么现在问题来了。很严重的问题。我现在不想用Vae类了,使用Vae类只能输出许嵩是最佳歌手。这个是许嵩的专属类。
我还想去输入其它歌手怎么办?例如我想添加一个shuyunquan类,去输出蜀云泉是一名歌手。那么我们要做两件事:
1.新写一个shuyunquan类
2.在Main类的Start方法中,必须新加一个if语句去判断我的输入是否等于shuyunquan
第一步是毫无疑问必须要写的,但是第二步!!!假如我们已经发布了代码,你怎么改?假如我想再增加10名歌手,你怎么改?
一个一个的在Start方法里面加?
这是一个很愚蠢的行为。这个时候我们需要使用我们第一步中学到的Class类的知识。
我改动了Main类中的Start方法,如下:
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scan=new Scanner(System.in); Start(scan.nextLine()); } public static void Start(String name) { try { Class c=Class.forName(name); singer s=(singer)c.newInstance(); s.music(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } }
可以看到,我使用Class.forName方法获取了我输入的类类型。然后使用newInstance方法new出了一个类的实例对象。但是newInstance方法是必须要加强制类型转换的怎么办?
因为我输入的都是歌手类。所以我写了一个接口,定义为singer:
interface singer { public void music(); }
然后给我的Vae类实现这个接口:
public class Vae implements singer { public void music() { System.out.println("许嵩是最佳歌手"); } }
现在,如果我想再加10个,20个歌手进来,都是完全没有问题的。我只需要写出我需要的歌手类。然后继承这个接口。再然后,我直接输入就可以了。
完全不需要修改我的Main类中的Start方法。这一点和QQ的在线更新啊,其它软件的升级功能啊,貌似都是这样实现的。
第三:Java获取类的方法名,方法返回值类型,方法的参数类型
在第一我们学到了Java如何去获取类类型,在第二我们使用了类类型去完成了Java的动态加载。这一次,我们学习下,Java如何去获取方法的详细信息。
import java.lang.reflect.Method; public class Main { public static void main(String[] args) { Vae xs=new Vae(); //getMethods()是获取所有的public方法,包括父类继承的,系统写的。getDeclaredMethods()是获取所有自己写的方法,public和private都获取。我喜欢getDeclaredMethods() //Method [] methods=xs.getClass().getMethods(); Method [] methods=xs.getClass().getDeclaredMethods(); for (Method temp:methods) { //获取方法的返回值类型 Class returnType=temp.getReturnType(); System.out.print("方法名"+temp.getName()+" 返回值类型"+ returnType); ///获取方法名 //System.out.println(temp.getName()); ///获取参数数组 Class [] params=temp.getParameterTypes(); for (Class param:params) { System.out.print(" 方法的参数"+param.getSimpleName()+","); } System.out.println(); } } }
第一亮点:import java.lang.reflect.Method 我们已经引用了Java反射
第二亮点:getMethods()和getDeclaredMethods()这两个方法的区别。
getMethods()是获取所有的public方法,包括父类继承的,系统写的。getDeclaredMethods()是获取所有自己写的方法,public和private都获取。
我特意传进去的我写的Vae类,我们来看看我的Vae类:
public class Vae implements singer { public void music() { System.out.println("许嵩是最佳歌手"); } public int test(int a,String b) { return 1; } }
里面有两个方法,参数,返回值一目了然。我们执行上面的代码,结果如下:
完美!自己手动写写吧。
第四:Java获取类的成员变量的名称,类型 。获取构造函数的名称,参数类型。
由于第四和第三步是差不多的。所以我直接贴出代码和结果了
//获取成员变量 Field [] fs=xs.getClass().getFields(); //只能获取public Field [] fs1=xs.getClass().getDeclaredFields(); //获取所有 for (Field field:fs1) { //成员变量的类型 Class fieldType=field.getType(); String typeName=fieldType.getName(); //成员变量的名称 String fileName=field.getName(); System.out.println("变量名:"+fileName+" 变量类型"+typeName); } //获取构造函数信息 Constructor [] cs=xs.getClass().getConstructors(); //获取public的构造函数 Constructor [] cs1=xs.getClass().getDeclaredConstructors(); //获取所有的构造函数,包括public和private for (Constructor constructor:cs) { System.out.print("构造函数名称是:" + constructor.getName() + " 构造函数的参数有:"); Class[] paramTypes = constructor.getParameterTypes(); for (Class class1:paramTypes){ System.out.print(class1.getName()+","); } System.out.println(); }
就这么多,还是以我的Vae类为例子
public class Vae implements singer { public int age=32; public String name="许嵩"; private String love="写歌"; public void music() { System.out.println("许嵩是最佳歌手"); } public int test(int a,String b) { return 1; } }
结果:
自己敲一敲代码,记一记就完事了。下面开始讲反射。
第五:方法的反射
终于,我们开始方法的反射了。先看代码:
package com.company; import java.lang.reflect.Method; public class Main { public static void main(String[] args) { Class c=Vae.class; Vae xs=new Vae(); //方法的反射 try { Method s=c.getMethod("music", int.class, int.class); //invoke方法就是反射的方法,Object是接收的返回值,如果是void方法,返回值是null Object object=s.invoke(xs,2,3); System.out.println(object); //有返回值的方法 Method s1=c.getMethod("music", String.class, String.class); object=s1.invoke(xs,"许嵩","蜀云泉"); System.out.println(object); //没有参数的方法 Method s2=c.getMethod("music"); object=s2.invoke(xs); System.out.println(object); } catch (Exception e) { e.printStackTrace(); } } } class Vae{ public void music(int a,int b){ System.out.println(a+b); } public String music(String a,String b){ return a+b; } public void music(){ System.out.println("没有参数的空方法");} }
看看输出的结果:
现在来讲解一下方法的反射。Invoke方法是重点,现在写到这里应该是有两个疑问的?
第一:我既然实例化了Vae类的对象,我为什么不直接xs.music(),反而要反射的去做。这不是麻烦吗?
存疑,这个我暂时没想通。
第二:在反射方法的时候,我怎么知道参数怎么填写?有几个?什么类型的?
这个疑问在想出来的时候就已经想到答案了,答案就是第四步,我们可以输出方法的名称和参数类型,个数还有方法的返回值类型。其实。。。这也完全没有必要,因为你的那些类都是继承了接口的,你只需要看看接口有哪些方法,就可以了。所以这个问题完全不是问题。
其它的在代码的注释里面,已经写的很清楚了。
还有一个反射去绕过泛型的例子待补充。我在网上搜了一下,貌似都说反射这个技能比较消耗资源,在普通情况下,是不建议使用的。反射大量应用的场景应该是写框架的时候。
我再找找看看有没有反射的最佳实践吧。反正我学习到这里,只觉得第二步是有意思的。