一、Java的RTTI
RTTI(Run-Time Type Identification,通过运行时类型判定)的含义就是在运行时识别一个对象的类型,其对应的类是Class对象,每个java里面的类都对应一个Class对象(在编写并且编译后),这个对象被保存在这个类的同名class文件里。
RTTI有两种形式:(1)传统的RTTI;(2)反射reflection机制。
1. 类型检查
作用: 避免类型向下转型是发生 ClassCastException 类型转换异常。
两种方法
1. 通过 instanceof 运算符
对象引用 instanceof 类名
2. Class对象调用 isInstance (对象引用)
//1. 获取类的 Class 对象
Class clazz = Class.forName("com.jq.reflection.Student");
//2. 反射创建类对象
Object obj = clazz.newInstance();
//第一种方法
if(obj instanceof Student) {
Student stu = (Student)obj;
System.out.println("instanceof 检查 向下转型成功 " + stu);
}
//第二种方式
if(clazz.isInstance(obj)) {
Student stu = (Student)obj;
System.out.println("clazz 检查 向下转型成功: " + stu);
}
----结果----
instanceof 检查 向下转型成功 com.jq.reflection.Student@7852e922
clazz 检查 向下转型成功: com.jq.reflection.Student@7852e922
二、类的加载与反射机制概述
1、JVM和类
当调用java命令来运行某个Java程序时,该命令将会启动一个JVM进程。同一个JVM中的所有线程,变量都处于同一个进程中,共享该JVM的内存区域。
当出现以下情况是,JVM会退出:
1)程序正常执行结束.
2)使用System.exit(0)方法;
3)出现异常时,没有捕获异常.
4)平台强制结束JVM进程.
JVM进程一旦结束,该进程中内存中的数据将会丢失.
当程序主动使用到某个类时,如果该类还未被加载进内存中,则系统会通过加载、连接、初始化三个步骤来对该类进行初始化操作。
1)类的加载:
类加载时是指将类的class文件(字节码文件)载入内存中,并为之创建一个 java.lang.Class对象,我们称之为字节码对象。
类的加载过程由类加载器(ClassLoader)完成,类加载器通常有JVM提供,我们称之为系统类加载器,我们也可以继承ClassLoader类来提供自定义类加载器。不同的类加载器可以实现加载本地字节码文件,jar包中的字节码,通过网络加载字节码等.
2)类的连接:
当类被加载进内存之后,系统为之生产一个对应的Class对象,接着把类的二进制数据合并到JRE中。
验证:检测被加载的类是否有正确的内部结构.
准备:负责为类的static变量分配内存,并设置默认值.
解析:把类的二进制数据中的符号引用替换为直接引用《深入分析JVM》.
3)类的初始化:
在此阶段,JVM负责对类进行初始化,主要就是对static变量进行初始化。类的初始化一个类包含以下几个步骤:
(1)如果该类还未被加载和连接,则程序先加载并连接该类.
(2)如果该类的直接父类还未被初始化,则先初始化其父类.
(3)如果类中有初始化语句(静态代码块),则系统依次执行这些初始化语句.
2、反射机制概述
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
1. Java反射机制的作用:
在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法等;在运行时调用任意一个对象的方法;生成动态代理。
1).反射创建该类的字节码对象
2)分析类的结构(类中的属性、方法)
反射机制的源头是Class对象,通过Class对象来获取构造器,方法,字段并进行操作。
元数据(metadata):描述数据的描述数据。
反射:得到类的元数据的过程,在运行时期,动态地去获取某一个类中的成员信息(构造器,方法,字段,父类,内部类等)。并且把类中的每一种成员都描述成一个新的类,常用:
Class:表示所有的类
Constructor:表示所有的构造器
Method:表示所有的方法
Field:表示所有的字段
三、获取Class对象引用的方式
1、获取Class对象引用的三种方式
Class类:用来描述正在运行的Java应用程序中的类和接口的类型
Class类的实例:是对一个类和接口运行时状态的描述。任何一个类都有一个对应的 Class对象,这个类第一次使用时, JVM会检查该类的 Class对象有没有被加载,只加载一次。
在JVM中的一份份字节码对象中,Class实例表示在JVM中的类或者接口,枚举是一种特殊的类,注解是一种特殊的接口。Class类可以表示N个类的字节码对象,怎么区分Class类此时表示的是那一个类的字节码呢? == Class类的设计者提供了泛型 Class<T>。
1)通过对象调用 getClass() 方法,getClass() 是Object类中的方法
需要提前 new一个对象, 一般用于类型检查。
2) 通过 Class类中的静态方法 Class.forName(String 全限定名) 即包名.类名
一般用于加载驱动。
3)通过 类名 .class 字面量(基本类型int,double等也可以使用.class)
一般用于参数传递。
//1.通过对象调用 getClass() 方法
Student stu = new Student();
Class c1 = stu.getClass();
System.out.println(c1);
//2. 通过 Class 类中的静态方法 forName(全限定名) 即包名.类名
Class c2 = null;
try {
c2 = Class.forName("com.jq.reflection.Student");
System.out.println(c2);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//3. 通过 类名.class 字面量(基本类型int,double等也可以使用.class)
Class<Student> c3 = Student.class;
System.out.println(c3);
//注意3种方式获得的Class对象是同一个
System.out.println(c1==c2);
System.out.println(c1==c3);
System.out.println(c2==c3);
----结果----
class com.jq.reflection.Student
class com.jq.reflection.Student
class com.jq.reflection.Student
true
true
true
注意:在运行时期,一个类,只有一个Class对象产生。即3种方式获得的Class对象是同一个 Class 对象引用。
2、九大内置Class实例
基本数据类型不能表示为对象,也就不能使用getClass的方式
基本类型没有类名的概念,也不能使用Class.forName的方式
但是,所有的基本数据类型都有class属性:Class clazz = 基本数据类型.class;
九大内置Class实例:JVM中预先提供好的Class实例,
分别:byte,short,int,long,float,double,boolea,char,void。表示:byte.class,short.class,int.class,...void.class.
在8大基本数据类型的包装类中都有一个常量:TYPE ,用于返回该包装类对应基本类型的字节码对象。
注意:包装类和基本类型是不同的数据类型
System.out.println(Integer.TYPE == int.class); // true
System.out.println(Integer.class == int.class); // false
数组的Class实例:数组是引用数据类型,数组其实是对象.
如何来表示数组的Class实例.
方式1: 数组类型.class;
方式2: 数组对象.getClass();
注意:所有的具有相同的维数和相同元素类型的数组共享同一份字节码对象,和元素的个数没有关系.
int[] arr1 ={1,2,3};
Class arrClass1 = arr1.getClass();
Class arrClass11 = int[].class;
System.out.println(arrClass1 == arrClass11); //true 同一份字节码
int[] arr2 ={4,5,6,7,8}; // 与元素个数无关
System.out.println(arr1.getClass() == arr2.getClass()); // true
通过反射机制获取类的结构
访问非公共的成员,必须先设置可访问的权限:成员对象.setAccessible(true);
构造器,方法,字段都继承 java.lang.reflect.AccessibleObject类:
四、获取类中的构造器并创建对象
public class User {
public User() {
System.out.println("public构造器,无参");
}
public User(String name) {
System.out.println("public构造器,参数:" + name);
}
private User(String name, int age) {
System.out.println("private构造器,参数:" + name + age);
}
}
1、获取类中的构造器
Class<T>类中获取构造器方法:
-
-
Constructor<T>
getConstructor(类<?>... parameterTypes)
返回一个
Constructor
对象,该对象反映Constructor
对象表示的类的指定的公共类
函数。Constructor<?>[]
getConstructors()
返回包含一个数组
Constructor
对象反射由此表示的类的所有公共构造类
对象。Constructor<T>
getDeclaredConstructor(类<?>... parameterTypes)
返回一个
Constructor
对象,该对象反映Constructor
对象表示的类或接口的指定类
函数。与访问权限无关Constructor<?>[]
getDeclaredConstructors()
返回一个反映
Constructor
对象表示的类声明的所有Constructor
对象的数组类
。
-
public static void main(String[] args) throws NoSuchMethodException {
getAll();
getOne();
}
// 获取所有的构造器
private static void getAll() {
// 1、获取构造器所在类的字节码对象
Class<User> clazz = User.class;
// 2、获取所有的构造器
// 该方法只能获取 public修饰的构造器
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
System.out.println("=======");
// 该方法获取类中所有构造器.与访问修饰权限无关
constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
}
// 获取指定的某一个构造器
private static void getOne() throws NoSuchMethodException {
// 1、获取构造器所在类的字节码对象
Class<User> clazz = User.class;
//2. 获取指定的某一个构造器
//获取public User()
Constructor constructor = clazz.getConstructor();
//获取public User (String name)
constructor = clazz.getConstructor(String.class);
//获取private User(String name ,int age)
constructor =clazz.getDeclaredConstructor(String.class,int.class);
}
2、创建对象
构造器最大的作用:创建对象并初始化。
1)构造器创建对象
Constructor<T>类中创建对象方法:
-
-
T
newInstance(Object... initargs)
使用此
Constructor
对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
-
public static void main(String[] args) throws IllegalAccessException,
InvocationTargetException,InstantiationException {
// 1、获取构造器所在类的字节码对象
Class<User> clazz = User.class;
//2. 获取指定的某一个构造器并创建对象
//获取public User()
Constructor constructor = clazz.getConstructor();
constructor.newInstance();
//获取public User (String name)
constructor = clazz.getConstructor(String.class);
constructor.newInstance("李斯");
//获取private User(String name ,int age)
constructor =clazz.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
constructor.newInstance("王五",17);
}
2)调用Class<T>类中的 newInstance( )方法,实例化对象
注意:类中必须要有无参数的公共构造器
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
// 1、获取构造器所在类的字节码对象
Class<User> clazz = User.class;
User user = clazz.newInstance();
}
五、获取类中的方法并执行
1、获取类中的方法并执行
public class User {
public void work() {
}
public String work(String name) {
return name;
}
private String say(String name, int age) {
return name + "," + age;
}
}
Class<T>类中获取方法:
-
-
Method
getMethod(String name, 类<?>... parameterTypes)
返回一个
方法
对象,它反映此表示的类或接口的指定公共成员方法类
对象。Method[]
getMethods()
返回包含一个数组
方法
对象反射由此表示的类或接口的所有公共方法类
对象,包括那些由类或接口和那些从超类和超接口继承的声明。Method
getDeclaredMethod(String name, 类<?>... parameterTypes)
返回一个
方法
对象,它反映此表示的类或接口的指定声明的方法类
对象。Method[]
getDeclaredMethods()
返回包含一个数组
方法
对象反射的类或接口的所有声明的方法,通过此表示类
对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法。
-
Method类:执行方法
public static void main(String[] args) throws Exception {
getAll();
getOne();
}
// 获取所有的方法
private static void getAll() {
// 1、获取类的字节码对象
Class<User> clazz = User.class;
// 2、获取所有的方法
// 只能获取 public修饰的方法
Method[] method = clazz.getMethods(); // 包括自身和继承过来的所有public方法
for (Method m : method) {
System.out.println(m);
}
System.out.println("=======");
// 获取类中所有方法.与访问修饰权限无关
Method[] method2 = clazz.getDeclaredMethods(); // 自身类中的所有方法,不包括继承 和访问权限无关
for (Method m : method2) {
System.out.println(m);
}
}
// 获取指定的某一个方法
private static void getOne() throws NoSuchMethodException, InvocationTargetException,
IllegalAccessException, InstantiationException {
// 1、获取类的字节码对象
Class<User> clazz = User.class;
//2. 获取指定的某一个方法
Method method = clazz.getMethod("work"); // /(方法名,参数):无参数即不写(类型.class))
method.invoke(clazz.newInstance(),null); // (被调用对象,实参) 无参数即null(实参)
method = clazz.getMethod("work", String.class);
method.invoke(clazz.newInstance(),"李斯");
method = clazz.getDeclaredMethod("say", String.class, int.class);
method.setAccessible(true); // 设置可访问权限
Object ret = method.invoke(clazz.newInstance(), "李斯", 17);
System.out.println(ret); // /null 表示空返回,方法使用void修饰
}
2、调用静态方法和可变参数(数组参数)的方法
public class User {
public void work1(int... arr) {
System.out.println("work1" + Arrays.toString(arr));
}
public static String work2(String... arr) {
System.out.println("work2" + Arrays.toString(arr));
return "a";
}
public static <T> List<T> asList(T...a){
return null ;
}
}
1)调用静态方法:静态方法不属于任何对象,静态方法属于类本身,所以被调用对象为null,
此时把 invoke方法的第一个参数设置为null即可
2)调用可变参数(数组参数)的方法:
王道:调用方法的时候把实际参数统统作为 Object数组的元素即可。
Method对象.invoke(方法底层所属对象, new Object[]{ 所有实参 });
public static void main(String[] args) throws Exception {
Class<User> clazz = User.class;
// 数组元素类型是基本类型
Method method = clazz.getMethod("work1", int[].class);
Object ret = method.invoke(clazz.newInstance(), new int[]{1, 2, 3});
// ret = method.invoke(clazz.newInstance(), new Object[]{ new int[]{1, 2, 3} });
System.out.println(ret); // null
// 数组元素类型是引用类型(引用类型的数组会自动解包)
method = clazz.getMethod("work2", String[].class);
Object ret2 = method.invoke(null, new Object[] { new String[] { "a", "b", "c" } });
System.out.println(ret2);//返回为a
//泛型方法的获得 (T只是占位符,底层还是Object)
method= clazz.getMethod("asList", Object[].class);
System.out.println(method);
}
六、获取类中的字段并操作
public class User {
public String username;
private int age;
//get/set/toString
}
1、获取类中的字段
Class<T>类中获取方法:
-
-
Field
getField(String name)
返回一个
Field
对象,它反映此表示的类或接口的指定公共成员字段类
对象。Field[]
getFields()
返回包含一个数组
Field
对象反射由此表示的类或接口的所有可访问的公共字段类
对象。Field
getDeclaredField(String name)
返回一个
Field
对象,它反映此表示的类或接口的指定已声明字段类
对象。Field[]
getDeclaredFields()
返回的数组
Field
对象反映此表示的类或接口声明的所有字段类
对象。
-
Field 类:操作方法等
public static void main(String[] args) throws Exception {
getAll();
getOne();
}
// 获取所有的字段
private static void getAll() {
// 1、获取类的字节码对象
Class<User> clazz = User.class;
// 2、获取所有的字段
// 只能获取 public修饰的字段
Method[] method = clazz.getMethods();
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("=======");
// 获取类中所有字段.与访问修饰权限无关
Method[] method2 = clazz.getDeclaredMethods();
fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
}
// 获取指定的某一个方法
private static void getOne() throws NoSuchFieldException {
// 1、获取类的字节码对象
Class<User> clazz = User.class;
//2. 获取指定的某一个方法
Field field = clazz.getField("username");
System.out.println(field);
field = clazz.getDeclaredField("age");
//field.setAccessible(true); // 设置可访问权限
System.out.println(field);
}
2、操作字段
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
// 1、获取类的字节码对象
Class<User> clazz = User.class;
User user = clazz.newInstance();
// 2、获取类中所有字段.与访问修饰权限无关
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true); // 设置可访问权限
Class<?> type = field.getType();
String name = field.getName();
System.out.println(type + " " + name);
if("username".equals(name)){
if(type.equals(String.class)){
field.set(user,"李斯");
}
}else if("age".equals(name)){
if(Integer.TYPE.equals(int.class) || type.equals(int.class)){ // int.class 与 Integer.class 不同
field.set(user,17);
}
}
}
System.out.println(user);
}
七、Class<T>类其他的API方法等
Constructor,Method,Filed的信息:查阅相应的API
-
-
int
getModifiers()
返回此类或接口的Java语言修饰符,以整数编码。
String
getName()
返回由
类
对象表示的实体(类,接口,数组类,原始类型或空白)的名称,作为String
。Package
getPackage()
获取此类的包。
String
getSimpleName()
返回源代码中给出的基础类的简单名称。
Class<? super T>
getSuperclass()
返回
类
表示此所表示的实体(类,接口,基本类型或void)的超类类
。
-
-
判断类型:isXxx 方法等等, 默认是 class类型
-
boolean
isAnnotation()
如果此
类
对象表示注释类型,则返回trueboolean
isArray()
确定此
类
对象是否表示数组类。boolean
isEnum()
当且仅当该类在源代码中被声明为枚举时才返回true。
-
public static void main(String[] args) {
Class clazz = User.class;
//获取类的修饰符
int mod = clazz.getModifiers(); //返回的是一个为int的数 查看Modifier类
System.out.println(mod);
String m = Modifier.toString(mod);
System.out.println(m);
System.out.println(clazz.getName());//获取全限定名称
System.out.println(clazz.getSimpleName()); //获取简单名称
System.out.println(clazz.getPackage()); //获取包名
System.out.println(clazz.getSuperclass());//获取类的父类:class java.lang.Object
System.out.println(clazz.isArray());//false 判断class是否数组
System.out.println(clazz.isEnum());//false 判断class是否枚举
}
八、加载资源文件路径
注意:加载properties文件,只能使用Properties类的load方法.
推荐使用:使用相对路径 -- 相对于classpath的根路径(字节码输出目录)
此时得使用ClassLoader(类加载器),ClassLoader(类加载器)默认就是从classpath根路径去寻找文件的.
public class ReflectDemo {
public static void main(String[] args) throws Exception {
test1();
test2(); //推荐使用
test3();
}
//方式3:使用相对路径-相对于当前加载资源文件的字节码路径
//ReflectDemo类的字节码文件路径去寻找,db.properties
private static void test3() throws Exception {
Properties p = new Properties();
InputStream inStream =ReflectDemo.class.getResourceAsStream("db.properties");
p.load(inStream);//加载
System.out.println(p); //{password=admin123, username=admin3}
}
//方式2:使用相对路径-相对于classpath的根路径(字节码输出目录).
//ClassLoader(类加载器),类加载器默认就是从classpath根路径去寻找文件的.
private static void test2() throws Exception {
Properties p = new Properties();
//两种获取类加载器的方法
//ClassLoader loader = LoadResourceDemo.class.getClassLoader();
ClassLoader loader = Thread.currentThread().getContextClassLoader(); //推荐
InputStream inStream =loader.getResourceAsStream("db.properties");
p.load(inStream);//加载
System.out.println(p); //{password=admin123, username=admin2}
}
//方式1:使用绝对路径的方式加载,该方式不推荐.
private static void test1() throws Exception {
Properties p = new Properties();
InputStream inStream = new FileInputStream("E:/javaSE/db.properties");
p.load(inStream);//加载
System.out.println(p); // {password=admin123, username=admin1}
}
}
ends