Java反射和泛型的本质
来源:互联网 发布:欧洲的经济数据 编辑:程序博客网 时间:2024/05/21 07:58
1.Class类的使用
首先,我们要知道。在面向对象的世界里,万事万物都是对象。在java中,除了静态成员、普通数据类型不是对象,其他的都是类。那么类的对象是什么呢?
其实类也是对象,类是java.lang.Class类的实例对象。那么这个对象我们如何去表示呢?
我们通过以下这段代码去理解:
public class ClassDemo1 { public static void main(String[] args) { Foo foo1 = new Foo(); //Foo类也是一个实例对象,Class的实例对象,但是它不可以new出来 //任何一个类都是Class类的实例对象,这个实例对象有三种表达方式 //第一种表达方式 --> 实际在告诉我们任何一个类都有一个隐含的静态成员 Class c1 = Foo.class; //第二种表达方式 --> 已经知道该类的对象通过getClass方法 Class c2 = foo1.getClass(); /** * 官网上对c1, c2解释为:表示了Foo类的类类型(Class type) * 类也是对象,是Class类的对象 * 这个对象,我们成为该类的类类型 */ //不管c1 or c2代表了Foo类的类类型,一个类只能是Class类的一个实例对象 System.out.println(c1 == c2); //第三种表示方式 Class c3 = null; try { //引号里面的name根据个人命名自行修改 c3 = Class.forName("com.inheritance.Foo"); }catch (ClassNotFoundException e){ e.printStackTrace(); } //同上 System.out.println(c2 == c3); //我们可以通过类类型创建该类的对象实例 --> 通过c1 try { Foo foo = (Foo)c1.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }}class Foo{ void print(){ System.out.println(); }}
总而言之,Class类是所有类的类,且我们知道有三种方式去得到它。类也是对象,是Class类的对象,这个对象,我们称之为类类型。
2.Java动态加载类
如果直接说动态加载,可能很多人和我一样是糊涂的,不知道动态加载到底是什么,有什么用。那接下来,将详细地讲一讲。
首先,我们要分清楚什么是编译,什么是运行。编译时刻加载类是静态加载类,运行时刻加载类是动态加载类。在前面我们讲到的Class.forName(“类的全称”),其实不仅表示类的类类型,还表示了动态加载类。
我们通过下面这段代码来看一下什么是静态加载,什么是动态加载。以下代码和例子,建议在记事本上写,然后在cmd下用javac编译。
我们首先写一个Office类:
public class Office { public static void main(String[] args) { if ("Word".equals(args[0])) { Word w = new Word(); w.start(); } if ("Excel".equals(args[0])) { Excel e = new Excel(); e.start(); } }}
很明显,这段代码是不能通过编译的,会告诉你这些问题:
但是,我们现在有没有想过这样一个问题,这个Word类和Excel类我们真的一定要用到吗?其实我们是不一定要用到的!
比如现在我们就写一个这个Word类:
class Word{ public static void start(){ System.out.println(); }}
好,这个时候你再去编译,会发现只有两个错了,提示你Excel类不存在。那有什么问题呢?你可能就会想,没什么问题啊,Excel类不存在当然跑不起来啊。可是Word类已经存在了啊!我又不一定要用到Excel类。所以问题就在于,new创建对象是静态加载类,在编译时刻就需要加载所有可能使用到的类。
这就导致我想用Word用不了,它总在告诉我Excel有问题,可是我不一定就要用Excel类啊。在实际情况中,我们当然希望,Word类存在我们就可以使用;Excel类不存在,当我用的时候你再告诉我不能用。比如:一个程序有100个功能,但是就是其中一个功能没写好,导致我其他99个都不能用,这是很不好的。
所以,我们希望我用的时候你再加载,不用的时候就不加载。也就是说在运行的时候加载,通过动态加载类我们就可以解决这个问题。
那我们怎么去动态加载类呢?请看下面这段代码,重新写一个OfficeBetter类:
class OfficeBetter { public static void main(String[] args) { try { //动态加载类,在运行时刻加载 Class c = Class.forName(args[0]); //通过类类型,创建该对象 /** * 通过newInstance()创建实例化对象的时候, * 必须进行强转,因为这个c本身只是类类型 * 如果我们往Word强转:Word wd = (Word)c.newInstance() * 但是加载的是Excel类怎么办呢? * 所以我们就要统一标准,去实现一个OfficeAble接口 */ OfficeAble oa = (OfficeAble) c.newInstance(); oa.start(); } catch (Exception e){ e.printStackTrace(); } }}
在此处我们重新写一个文件,实现一个OfficeAble的接口:
interface OfficeAble{ public void start();}
然后Word类,我们也要重新写一下,让它去实现这个接口:
class Word implements OfficeAble{ public void start(){ System.out.println("word...start"); }}
这个时候你去编译,不会有错,使用Word也不会有错。只有当你使用Excel的时候才会报错。当你想使用Excel的时候,你随便换个人只要去实现这个接口就好了,那么Excel也能用了。而且,就算你加进来了,OfficeBetter的代码也不会变,也不需要重新编译,直接运行一样可以正常使用。甚至你可以轻松地增加其他的功能,比如:PPT、OneNote等等。
如果换做之前,我们想增加功能,必须再增加代码。而且只要有一个出问题了,其他的也不能使用了。所以,我们在写功能性的类的时候,一般都会采取动态加载的方式。
3.Java获取方法信息
包括基本数据类型在内的,几乎只要是属于类里面的东西都有类类型。甚至void等关键字也有类类型,那我们怎么通过类类型去获得它的类的名称呢?
代码如下:
public class ClassDemo2 { public static void main(String[] args) { Class c1 = int.class; Class c2 = String.class; Class c3 = double.class; Class c4 = Double.class; Class c5 = void.class; System.out.println(c1.getName()); System.out.println(c2.getName()); System.out.println(c3.getName()); System.out.println(c4.getSimpleName()); //不包含包名 System.out.println(c5.getName()); }}
既然我们已经通过这种方法得到了类的名称,那么接下来我们就可以得到这个类的所有的方法信息,下面是一些相关的API,先新写一个类:
public class ClassUtil { /** * 打印类的信息,包括类的成员函数、成员变量 * @param obj */ public static void printClassMessage(Object obj) { //要获取类的信息,首先要获取类的类类型 Class c = obj.getClass(); //传递的是那个子类的对象,c就是该子类的类类型 //获取类的名称 System.out.println("类的名称是:" + c.getName()); /** * Method类,方法对象 * 一个成员方法就是一个Method对象 * getMethods()方法获取的是所有public的函数,包括继承而来的 * getDeclaredMethods()获取的是所有自己的方法,不问访问权限 */ Method[] ms = c.getMethods(); for (int i = 0; i < ms.length; i++) { //得到方法的返回值类型的类类型 Class returnType = ms[i].getReturnType(); System.out.print(returnType.getName() + " "); //得到方法的名称 System.out.print(ms[i].getName() + "("); //获取参数类型-->得到的是参数列表的类型的类类型 Class[] paramTypes = ms[i].getParameterTypes(); for (Class class1 : paramTypes) { System.out.print(class1.getName() + ","); } System.out.println(")"); } }}
然后再在主函数里面调用:
public class ClassDemo3 { public static void main(String[] args) { String s = "hello"; ClassUtil.printClassMessage(s); }}
我们可以在运行结果看到,所有的方法已经打印:
有兴趣的可以自己试试其他的!
4.Java获取成员变量构造函数信息
其实原理同上,直接在ClassUtil类中补充一下代码,然后直接调用就可以看到效果:
public static void printConstructMessage(Object obj) { Class c = obj.getClass(); /** * 构造函数也是对象 * java.lang.Constructor中封装了构造函数的信息 * getConstructors获取所有的public的构造函数 * getDeclaredConstructors得到所有的构造函数 */ Constructor[] cs = c.getDeclaredConstructors(); 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(")"); } }
也就是说,以后如果你要获取类的任何信息,你首先得到该类的类类型就可以任意施为了。
5.Java方法反射的基本操作
那我们如何通过一个对象去获取它的方法信息,并进行方法的反射操作呢?首先,让我们写一个A类,并给两个print方法:
class A { public void print(int a, int b) { System.out.println(a + b); } public void print(String a, String b) { System.out.println(a.toUpperCase() + " " + b.toUpperCase()); }}
然后,我们通过A类实例化的对象,去获取它的类类型,并且获取它的方法。最后通过 invoke 去对它的方法进行反射操作。
public class MethodDemo1 { public static void main(String[] args) { /** * 获取print(int, int)方法,首先获取类的信息 * 然后得到其类类型 */ A a1 = new A(); Class c = a1.getClass(); /** * 获取方法名称和参数列表来决定 * getMethod获取的是public的方法 * getM=DeclaredMethod是自己声明的方法 */ try { //Method m = c.getMethod("print", new Class[]{int.class, int.class}); Method m = c.getMethod("print", int.class, int.class); //方法的反射操作是用m对象来操作的 //方法如果没有返回值,则返回null;有则返回相应的 //Object o = m.invoke(a1, new Object[]{10, 20}); Object o = m.invoke(a1, 10, 20); System.out.println("======================"); Method m1 = c.getMethod("print", String.class, String.class); o = m1.invoke(a1, "hello", "world"); }catch (Exception e){ e.printStackTrace(); } }}
6.通过反射了解泛型的本质
泛型的本质其实就是防止错误输入,只在编译阶段有效,绕过编译就无效了,我们可以通过下面这段代码来看看:
public class MethodDemo2 { public static void main(String[] args) { ArrayList list = new ArrayList(); ArrayList<String> list1 = new ArrayList<String>(); list1.add("Hello"); //list1.add(20)是错误的,加不进去 Class c1 = list.getClass(); Class c2 = list.getClass(); System.out.println(c1 == c2); //反射的操作都是编译后的操作 /** * c1 == c2的结果返回true说明,编译之后集合的泛型是去泛型的 * Java中集合的泛型,是防止错误输入的,只在编译阶段有效 * 绕过编译就无效了 * 验证:我们通过方法的反射操作,绕过编译 */ try { Method m = c2.getMethod("add", Object.class); m.invoke(list1, 20); //绕过编译就绕过了泛型 System.out.println(list1.size()); System.out.println(list1); //现在不能用foreach遍历,或有类型转换的错误 /*for (String string : list1) { System.out.println(string); }*/ }catch (Exception e){ e.printStackTrace(); } }}
- Java反射和泛型的本质
- Java反射与泛型的本质
- Java反射-泛型的本质
- Java反射--通过反射了解集合泛型的本质
- Java反射机制之集合泛型的本质
- java反射:通过Method、Class发现泛型的本质
- 黑马程序员——【Java反射学习】反射的应用:测试泛型的本质
- Java--Reflect(反射)专题6——通过反射了解集合泛型的本质
- 【慕课网笔记】Java 反射机制 6 通过反射了解集合泛型的本质
- java反射之通过反射了解集合泛型的本质
- Java反射(六)----- 通过反射了解集合泛型的本质
- JAVA反射之通过反射了解集合泛型的本质
- 24、Java入门—反射之通过反射了解集合泛型的本质
- java反射学习笔记(4)----通过反射来了解泛型的本质
- 反射了解集合泛型的本质
- 通过反射了解泛型的本质
- 反射:集合泛型的本质
- Java 反射:Class类,动态加载类获取方法和成员变量构造信息,方法反射的基本操作,集合泛型的本质
- Access denied for user 'root'@'localhost' (using password:YES)
- POJ 2186
- Android真正的沉浸式状态栏
- 寻找数组中唯一的数
- 第八周—小明借书
- Java反射和泛型的本质
- C语言中scanf函数与输入缓冲区
- 用Python改變system environment variable
- 第四十七讲 项目 简化的银行储蓄系统V1.0(有待完善......)
- GitHub for Windows 2.0使用教程
- LeetCode 406 Queue Reconstruction by Height (排序 思维)
- AndroidStudio最常用快捷键总结
- 数据结构导论(2)——考后小结
- java类StringTokenizer