Class 类
- 在 Java 中大部分元素都是对象(但也有例外,如静态成员、基本数据类型);
- 类是
java.lang.Class
类的实例对象,这个对象称为该类的类类型; -
Class
类是私有类,不能直接用于创建对象,只有 JVM 可以访问; - 通过
Class
可以访问系统为所有对象维护运行时的类型标识、从而可以通过类的类类型创建类的实例对象。
对于一个普通的类,可以使用 new
创建其对象
class Foo {
void print() {
System.out.println("foo");
}
}
Foo foo1 = new Foo();
但 Class
类不能直接访问;任何一个类都是 Class
类的实例对象,这个实例对象有三种表达方式
// 第一种表达方式,表示任何一个类都有一个隐含的静态成员变量 class
Class c1 = Foo.class;
// 第二种表达方式,已经知道该类的对象通过 getClass 方法
Class c2 = foo1.getClass();
// c1、c2 表示了 Foo 类的类类型(class type)
System.out.println(c1 == c2);
// 第三种表达方式
Class c3 = null;
c3 = Class.forName("com.ywh.reflect.Foo");
System.out.println(c2 == c3);
“可以通过类的类类型创建类的实例对象”,指的是在这里可以通过 c1
,c2
,c3
创建 Foo
的实例
try {
Foo foo = (Foo) c1.newInstance(); // 需要在类中定义无参数的构造方法,否则会抛出异常
foo.print();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
动态加载与静态加载
Class.forName("com.ywh.reflect.Foo")
- 表示类的类类型,同时代表动态加载类;
- 编译时加载类(使用
new
创建对象,类不存在时编译无法通过)是静态加载类,运行时加载类是动态加载类。
静态加载类
要求所有工具类都写在 Office 类中,耦合度高(Excel 不存在,Word 也不可用,因为无法通过编译)。
class Office {
public static void main(String[] args) {
if ("Word".equals(args[0])) {
Word w = new Word();
w.start();
}
else if ("Excel".equals(args[0])) {
Excel e = new Excel();
e.start();
}
}
}
动态加载类
- Word 和 Excel 继承标准接口,在动态加载时代替必须指定其中的某个类型的强制类型转换;
- 当需要扩展工具时,只需要添加符合标准(继承接口)的工具类,不需要再修改 Office 类;
// Word 和 Excel 的共同标准接口
interface OfficeAble {
public void start();
}
class Office {
public static void main(String[] args) {
try {
Class c = Class.forName(args[0]);
OfficeAble oa = (OfficeAble) c.newInstance();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
编译、运行
javac Office.java
java Office Word
获取类的信息
其他类型(基本数据类型、关键字等)都存在类类型
Class c1 = int.class; // int 的类类型
Class c2 = String.class; // String 类的类类型,相当于 String 类字节码
Class c3 = double.class;
Class c4 = Double.class;
Class c5 = void.class; // package 没有,因为不是在类中声明的
System.out.println(c1.getName());
System.out.println(c2.getName());
System.out.println(c2.getSimpleName()); // 不包含包名的类的名称
System.out.println(c5.getName());
对于一个类,Method
存放类的方法对象,Field
存放类的成员对象。
获取类方法的信息
public static void printClassMethodMessage(Object obj) {
// 首先要获取类的类类型
Class c = obj.getClass(); // 传递的是哪个子类的对象,c 就是该子类的类类型
System.out.println("类的名称是:" + c.getName()); // 获取类的名称
Method[] ms = c.getMethods(); // 获取所有有 public 方法,包括父类继承而来的
// c.getDeclaredMethods() 获取的是所有该类自己声明的方法
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(")");
}
}
获取类成员的信息
- 成员变量也是对象(
java.lang.reflect.Field
的对象); - 其中
Field
类封装了关于成员变量的操作。
public static void printFieldMessage(Object obj) {
Class c = obj.getClass();
// Field[] fs = c.getFields(); // 获取所有的public的成员变量的信息
Field[] fs = c.getDeclaredFields(); // 获取该类自己声明的成员变量的信息
for (Field field : fs) {
Class fieldType = field.getType(); // 得到成员变量的类型的类类型
String typeName = fieldType.getName();
String fieldName = field.getName(); // 得到成员变量的名称
System.out.println(typeName + " " + fieldName);
}
}
获取类构造方法的信息
- 构造方法也是对象(
java.lang.Constructor
的对象)
public static void printConMessage(Object obj) {
Class c = obj.getClass();
// Constructor[] cs = c.getConstructors(); // 获取所有的public的构造函数
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(")");
}
}
反射的基本操作
- 方法由名称、参数列表决定;
- 使用方法对象的
invoke(对象, 参数列表)
方法来实现反射操作。
对于一个普通类
class A {
public void print() {
System.out.println("helloworld");
}
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.toLowerCase());
}
}
执行反射操作
- 先获取类的类类型;
- 由名称、参数列表决定获取的方法;
- 使用方法对象来调用方法。
try {
A a1 = new A();
Class c = a1.getClass();
// 获取 a1 中,名称为 “print”、第一个参数类型为 “int”、第二个参数类型为 “int” 的方法并调用
Method m0 = c.getMethod("print", int.class, int.class);
Object o = m0.invoke(a1, 10, 20); // 等价于a1.print(10, 20)
Method m1 = c.getMethod("print", String.class, String.class);
o = m1.invoke(a1, "hello", "WORLD");
Method m2 = c.getMethod("print");
m2.invoke(a1);
} catch (Exception e) {
e.printStackTrace();
}
反射是常用于系统程序中的技术,但由于把程序逻辑插入到运行时、绕过了编译器的判断,从而让编译器无法帮助发现程序中的错误,很可能在运行时出现预期以外的错误而难以解决,因此不应在应用程序中过多使用反射。
泛型的本质
Java 中的集合常使用泛型来防止错误输入(不能放入类型不兼容的元素):
ArrayList list1 = new ArrayList();
ArrayList<String> list2 = new ArrayList<>();
list2.add("hello");
// list2.add(1);
但在以上两个集合中,其类类型是相等的
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1 == c2);
由于反射的操作都是编译后的操作,c1 == c2
返回 true
表明编译之后集合是去泛型化的(编译之后没有泛型),所以泛型只在编译阶段有效,绕过编译就无效了。
实例:通过方法反射操作绕过编译
try {
Method m = c2.getMethod("add", Object.class);
m.invoke(list1, 20); // 绕过编译操作,给 String 集合添加 int 类型
System.out.println(list1.size());
System.out.println(list1);
// for (String string : list1) {
// System.out.println(string);
// } //现在不能这样遍历,会报类型错误
} catch (Exception e) {
e.printStackTrace();
}