Class类的说明
- 在面向对象的世界里,万事万物皆对象。
其实在java语言中有两个东西不是面向对象的,一个是普通数据类型,但是java为其提供了包装类来弥补,还有一个是静态成员,静态成员是属于类的。 - 那么类是否是对象呢?如果是,类又是谁的对象呢?
其实类也是对象,类是java.lang.Class
类的实例对象,这个对象我们称为该类的类类型(class type)
反射概述
什么是java的反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制
Java反射机制的作用
是用来编写一些通用性较高的代码或者框架的时候使用
如何理解
首先java源代码经过编译生成.class文件,在JVM中会有ClassLoader
(类加载器),加载不同的class会有不同的ClassLoader,ClassLoader就会将编译好的class文件加载到java虚拟机中,然后来运行。
这个过程就称为运行,JVM运行class文件的期间称为运行期间,在运行期间可以去获取类的属性和方法、创建对象等等,这种机制就是反射。
反射常用对象概述
Class
Class类的实例表示正在运行的Java应用程序中的类和接口
Class对象:类的字节码文件被加载到内存以后所形成的一个对象
Constructor
关于类的单个构造方法的信息以及对它的访问权限
Field
提供有关类或接口的单个字段的信息,以及对它的动态访问权限
Method
Method提供关于类或接口上单独某个方法的信息
Class类
Java中java.lang.Class类用于表示一个类的字节码(.class)文件
首先添加一个测试类
public class User {
//成员属性
private String username;
private String userpass;
private int age;
//构造方法
public User(){}
public User(String username, String userpass, int age) {
super();
this.username = username;
this.userpass = userpass;
this.age = age;
}
private User(int age){
this.age = age;
}
public void run(){
System.out.println("user running...");
}
public int add(int a,int b){
return a + b;
}
public void print(String a,String b){
System.out.println(a.toUpperCase()+"-"+b.toUpperCase());
}
@Override
public String toString() {
return "User [username=" + username + ", userpass=" + userpass + ", age=" + age + "]";
}
//省略getter和setter放啊
}
获取Class类对象
Class类对象只有JVM可以创建,有三种方式可以获得类对应的的Class实例对象
- 类名.class
- 对象.getClass()
- Class.forName("包名.类名")
public static void main(String[] args) {
User user = new User();
//任何一个类都是Class的实例对象,这个实例对象有三种表示方式
//第一种表示方式
Class c1 = User.class;
//第二种表示方式
Class c2 = user.getClass();
//第三种表达方式
Class c3 = null;
try {
c3 = Class.forName("com.silly.reflect.User");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//c1,c2,c3都代表了User类的类类型,一个类只可能是Class类的一个实例对象
System.out.println(c1 == c2 && c2 == c3); //true
//打印class名称:com.silly.reflect.User
System.out.println(c1.getName());
//不包含包名:User
System.out.println(c1.getSimpleName());
}
注意
- 基本数据类型、void关键字也有类类型,并且基本类型的类类型和包装类的类类型是不同的。
即:void.class
、int.class == Integer.class (false)
、int.class == Integer.TYPE
- 所有带Declared的方法,均是获取声明的(包括private)内容。不带Declared的方法,均是获取公共的(public)内容
Java动态加载类
Class.forName("类的全称")
- 不仅表示了类的类类型,还代表了动态加载类
- 注意区分编译和运行
- 编译时刻加载类是静态加载类,运行时刻加载类是动态加载类
示例说明
编写如下Office类,此时该类肯定不能通过编译,因为Word和Excel类找不到
public class Office {
public static void main(String[] args) {
if("Word".equals(args[0])){
Word word = new Word();
word.start();
}else if("Excel".equals(args[0])){
Excel excel = new Excel();
excel.start();
}
}
}
我们继续编写Word类,此时程序还是无法通过编译,会报Excel类找不到的错误。我们如果只想用Word的功能也是无法使用的,因为我们的程序是做静态加载。即 New 创建对象是静态加载类,在编译时刻就需要加载所有的可能使用到的类。
public class Word {
public static void start(){
System.out.println("word start...");
}
}
这样做存在的问题就是如果将来有100个功能,只要有一个功能有问题,其他的功能就都无法使用。这并不是我们希望的,我们希望的是只要Word存在,就能使用Word,用哪个加载哪个,不用就不加载。
通过动态加载类来解决该问题
public class Office {
public static void main(String[] args) {
//动态加载类,在运行时刻加载
//此时forName方法参数的类无论是否存在编译都不报错
try {
Class clazz = Class.forName(args[0]);
//通过类类型,创建该对象
Object obj = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
此时又存在一个问题,通过动态加载类的方式创建的对象无法强制转换,因为我们无法预知运行时具体使用哪个功能。因此我们需要抽取一个公共的父类或者接口,使用多态方式调用方法实现功能。
//定义接口
interface OfficeAble{
void start();
}
//子功能实现该接口
public class Word implements OfficeAble {
public static void start(){
System.out.println("word start...");
}
}
//使用多态
public class Office {
public static void main(String[] args) {
//动态加载类,在运行时刻加载
//此时forName方法参数的类无论是否存在编译都不报错
try {
Class clazz = Class.forName(args[0]);
//通过类类型,创建该对象
OfficeAble oa = (OfficeAble)clazz.newInstance();
oa.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
此时我们就可以使用命令:javac Office.java
编译,执行命令:java Office Word
运行程序。未来如果希望有Excel功能,只需要定义该类并实现接口即可。这就是使用动态加载类的好处。
Constructor类
Constructor类的实例对象代表类的一个构造方法。反射创建对象的两种方式:
- 通过调用Constructor对象的newInstance(),可以调用任意一个构造方法
- 或者调用Class对象的newInstance(),只能调用public的无参构造
相关方法
方法示例
public static void main(String[] args) throws Exception{
//1:反射到其构造方法
//获取字节码文件对象
Class clazz = User.class;
//获取所有公共构造
Constructor[] constructors = clazz.getConstructors();
//[public com.User(java.lang.String,java.lang.String,int), public com.User()]
System.out.println(Arrays.toString(constructors));
//获取所有声明构造
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
//[private com.User(int), public com.User(java.lang.String,java.lang.String,int), public com.User()]
System.out.println(Arrays.toString(declaredConstructors));
//获取公共的单个构造
Constructor constructor1f = clazz.getConstructor(String.class,String.class,int.class);
//获取参数列表的类类型
Class[] paramTypes = constructor1f.getParameterTypes();
for (Class class1 : paramTypes) {
//java.lang.String, java.lang.String, int,
System.out.print(class1.getName()+", ");
}
//获取声明的单个构造
Constructor constructor2f = clazz.getDeclaredConstructor(int.class);
//2:将反射到的构造方法使用起来,对于构造方法,使用的方式是创建对象
User user1 = (User)constructor1f.newInstance("小明","xm123",18);
//User [username=小明, userpass=xm123, age=18]
System.out.println(user1);
//需要将private修饰的成员(私有的带参构造),进行暴力访问
constructor2f.setAccessible(true);
User user2 = (User)constructor2f.newInstance(18);
//User [username=null, userpass=null, age=18]
System.out.println(user2);
//无参构造
User user3 = (User)clazz.newInstance();
}
Field类
Field类代表某个类中的一个成员变量,并提供动态的访问权限
相关方法
方法示例
public static void main(String[] args) throws Exception{
//1.反射获取成员变量
Class clazz = User.class;
//获取声明的所有字段
Field[] fs = clazz.getDeclaredFields();
for (Field field : fs) {
//得到成员变量的类型的类类型
Class fieldType = field.getType();
String typeName = fieldType.getName();
//得到成员变量的名称
String fieldName = field.getName();
System.out.println(typeName + " " + fieldName);
//java.lang.String username
//java.lang.String userpass
//int age
}
//获取单个声明的字段
Field nameField = clazz.getDeclaredField("username");
//2.将反射到的成员变量使用起来。对于成员变量,使用的方式是赋值和取值
User user = (User)clazz.newInstance();
//需要将private修饰的成员,进行暴力访问
nameField.setAccessible(true);
//赋值
nameField.set(user, "小明");
//取值
Object value = nameField.get(user);
System.out.println(value); //小明
}
Method类
方法对象,一个成员方法就是一个Method对象
相关方法
获取方法信息
public static void main(String[] args) {
//打印类的方法信息
Class clazz = User.class;
Method[] ms = clazz.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(")");
//打印示例:int add(int,int,)
}
}
方法的反射操作
- 如何获取某个方法
方法的名称和方法的参数列表才能唯一决定某个方法 - 方法反射的操作
method.invoke(对象,参数列表)
public static void main(String[] args) throws Exception{
Class clazz = User.class;
User user = (User)clazz.newInstance();
//获取无参方法
Method runMethod = clazz.getDeclaredMethod("run");
//获取带参方法
Method addMethod = clazz.getDeclaredMethod("add", new Class[]{int.class, int.class});
Method printMethod = clazz.getDeclaredMethod("print", String.class, String.class);
//方法的反射操作
//方法如果没有返回值返回null,有返回值返回具体的返回值
Object obj = runMethod.invoke(user); //相当于user.run();
System.out.println(obj); //null
Object value = addMethod.invoke(user, 10,20); //相当于user.add(10,20);
System.out.println(value); //30
printMethod.invoke(user, "hello", "world");
}
应用:例如我希望通过输入来调用对象的具体方法,这个输入可以是多种多样的形式,例如某个注解上的属性值。
public static void main(String[] args) throws Exception{
UserService us = new UserService();
/*
* 通过键盘输入命令执行操作
* 输入update命令就调用update方法,输入delete命令就调用delete方法
*/
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入命令:");
String action = br.readLine();
/*if("update".equals(action)){
us.update();
}else if("delete".equals(action)){
us.delete();
}...*/
//通过方法对象然后进行反射操作
Class c = us.getClass();
Method m = c.getMethod(action);
m.invoke(us);
}
通过反射了解集合泛型的本质
- 泛型的本质是参数化类型,是为了防止错误输入的,只在编译阶段有效,绕过编译就无效了
- 泛型检查存在擦除泛型的动作,真正在运行时,泛型位置使用的仍然是Object
- 我们可以通过反射操作绕过泛型的语法检查
public static void main(String[] args) throws Exception{
ArrayList list = new ArrayList();
ArrayList<String> list1 = new ArrayList<String>();
list1.add("hello");
//list1.add(20); 编译报错
Class clazz1 = list.getClass();
Class clazz2 = list1.getClass();
System.out.println(clazz1 == clazz2); //true
//反射的操作都是编译之后的操作(运行时)
//c1==c2结果返回true说明编译之后集合的泛型是去泛型化的
/*
* 通过反射操作绕过泛型的语法检查
*/
Method method = clazz2.getMethod("add", Object.class);
method.invoke(list1, 12.66); //绕过编译操作就绕过了泛型
method.invoke(list1, 20);
System.out.println(list1);
//[hello, 12.66, 20]
}