1##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 = list1.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();
}
}
}