前提,开始序号为2是因为这里摘自我的笔记
2.对象有编译类型和运行类型
Object obj = new java.util.Date();
编译类型:Object
运行类型(obj对象真实的类型):java.util.Date
需求:通过obj对象来调用java.util.Date类中的toLocaleString方法
obj.toLocalString() //编译错误
因为toLocalString方法不在Object类中
解决方法:
java.util.Date d = (java.util.Date)obj;
d.toLocalString();
新的问题:
但,如果不知道对象的真实类型,无法做强制类型转换
特别是,当某一个对象通过方法返回,看不到底层
怎么办?->反射
这里的问题的解答在下面的第13点
3.类也是一种对象
类:描述对象
Class:描述类
那么,Class类和Object类有什么关系?
Object:表示一切对象
Class:表示一切类
4.到底,什么是反射?
在运行时期,动态的去获取类中的成员信息
(包、父类、接口、修饰符、类名、构造器、字段、方法等)
Eclipse中的outline(大纲视图)就是通过反射编写的
反射的API:
Class:描述一切的类
Constructor:描述一切的构造器
Method:描述一切的方法
使用反射:创建对象,调用方法
然而,反射操作性能较低。
但是-->Java中的框架都是基于反射编写
5.Class类与Class实例
Class类 :用来表示所有的类,是所有类的类型
Class实例:表示在JVM中运行的一份份字节码文件
Class类可以表示所有类,那如何明确是具体哪个类呢?
由此,SUN公司提高了一个泛型,用来表示当前所表示的是哪一个类
若当前描述String类: Class<String> clz
若当前描述ArrayList类: Class<ArrayList> clz
表示Class对象的三种方式
1.类型.class 表示一份字节码
2.对象.getClass() 获取当前对象的真实类型
3.使用Class.forName(String className) 根据类的全限定名来获取Class对象
Class<java.util.Date> clz1 = java.util.Date.class;
java.util.Date date = new java.util.Date();
Class clz2 = date.getClass();
Class clz3 = Class.forName("java.util.Date");
System.out.println(clz1 + "\r\n" + clz2 + "\r\n" + clz3);
/*class java.util.Date
class java.util.Date
class java.util.Date */
6.九大内置的Class实例和数据的Class实例
8大基本数据类型和void关键字都可以表示为Class的实例
如:Class intClz = int.class;
boolean blClz = boolean.class;
问题:int和Integer是同一数据类型吗?
System.out.println(int.class == Integer.class);
//false
在包装类中都有一个TYPE常量,返回对应的基本类型的Class对象
System.out.println(int.class == Integer.TYPE);
//true
表示数组的Class对象
方式1:数组名.getClass();
Class clz1 = arr.getClass();
方式2:数组类型.class;
Class clz2 = int[].class;
9.获取构造器
Class类描述了所有的类的成员(构造函数、方法等)
——>Class类具有获取某一个类的构造函数的方法
步骤:1)获取被操作的类的字节码对象
2)获取构造函数
获取构造器的方法:
Constructor<?>[] getConstructors() 返回包含一个数组 Constructor对象反射由此表示的类的所有公共构造 类对象
Constructor<?>[] getDeclaredConstructors() 返回一个反映 Constructor对象表示的类声明的所有 Constructor对象的类
Constructor<T> getConstructor(类<?>... parameterTypes) 返回一个 Constructor对象,该对象反映 Constructor对象表示的类的指定的公共类函数
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) 返回一个 Constructor对象,该对象反映 Constructor对象表示的类或接口的指定 类函数。
10.调用构造器创建对象
T newInstance(Object... initargs)
使用此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
通过构造器对象使用newInstance可以获得实例
c = clz.getConstructor(String.class);
obj = c.newInstance("公有、有参数");
但是,对于私有的构造器,会有“非法访问”的异常
c = clz.getDeclaredConstructor(String.class, int.class);
//obj = c.newInstance("私有", 1);
/*
* Exception in thread "main" java.lang.IllegalAccessException
* ...
* can not access a member of class pers.honhong._02_createinstance.User with modifiers "private"
*/
这里,需要使用setAccessible方法关闭安全检查
void setAccessible(boolean flag)
将此对象的 accessible标志设置为指示的布尔值。
c.setAccessible(true);
obj = c.newInstance("私有", 1);
11.获取类中的方法
|--获取所有方法
Method[] getMethods()
返回包含一个数组方法对象反射由此表示的类或接口的所有【公共】方法类对象
包括那些由类或接口和那些从超类和超接口继承的声明
Method[] getDeclaredMethods()
返回包含一个数组方法对象反射的类或接口的所有声明的方法,通过此表示类对象
包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法。
//获取User类中所有public的方法(包括继承)
Method[] methods = clz.getMethods();
for(Method method : methods) {
System.out.println(method);
}
//获取User类中所有的方法(不包括继承)
methods = clz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}
|--获取单个方法
Method getMethod(String name, 类<?>... parameterTypes)
返回一个方法对象它反映此表示的类或接口的指定公共成员方法 类对象
String name:方法名
类<?>... parameterTypes:参数的Class类型
//获取public void fun1()
Method method = clz.getMethod("fun1");
System.out.println(method);
//获取public void fun2(String name)
method = clz.getMethod("fun2", String.class);
System.out.println(method);
//获取private void fun3(String name, int age)
method = clz.getDeclaredMethod("fun3", String.class, int.class);
System.out.println(method);
12.使用反射调用方法
Object invoke(Object obj, Object... args)
在具有指定参数的Method对象上调用此Method对象表示的底层方法。
参数
obj - 从底层方法被调用的对象
args - 用于方法调用的参数
//调用方法public void fun1()
Method m = clz.getMethod("fun1");
m.invoke(clz.newInstance());
//public void fun1()
//调用方法public void fun2(String name)
m = clz.getMethod("fun2", String.class);
m.invoke(clz.newInstance(), "公有、有参数");
//public void fun2(String name)
但是,对于私有的方法,即便我们可以通过getDeclaredMethod方法获取,
我们也没有权限使用。
于是,需要使用java.lang.reflect.AccessibleObject中的setAccessible方法
关闭安全检查
//调用方法private void fun3(String name, int age)
m = clz.getDeclaredMethod("fun3", String.class, int.class);
m.setAccessible(true);
m.invoke(clz.newInstance(), "私有", 1);
//private void fun3(String name, int age)
13.解答第2点留下的问题,获得某一个以Object为编译类型(不知道运行类型)的对象时,
如何使用它的运行类型所特有的方法
//现在有一个日期对象,其编译类型是Object
Object o = new java.util.Date();
//现在不通过强转调用Date类中的toLocaleString方法
//利用反射
Method m = o.getClass().getMethod("toLocaleString");
System.out.println(m.invoke(o));
14.使用反射调用静态方法和可变数组参数
如果底层方法是静态的,则可以忽略指定的obj参数,传入null!
例如:
//调用public static void staticMethod1()
Method m = c.getMethod("staticMethod1");
m.invoke(null);
//输出:public static void staticMethod1()
如果底层方法是可变数组参数,则分为:
基本数据类型
例如:
//调用public static void staticMethod2(int ... arr)
m = c.getMethod("staticMethod2", int[].class);
//m.invoke(null, 1, 2, 3); 运行时错误
//这里需要把1,2,3封装成数组
m.invoke(null, new int[]{1, 2, 3});
//输出:[1, 2, 3]
引用数据类型
//调用public static void staticMethod3(String ... str_arr)
m = c.getMethod("staticMethod3", String[].class);
//m.invoke(null, new String[]{"a", "b", "c"});//运行时错误
这里,为什么会出现错误呢?
查看APIdoc,invoke方法下有这么一句话:
个别参数自动解包以匹配原始形式参数,原始参考参数和参考参数都需要进行方法调用转换。
再看看invoke方法的声明:
public Object invoke(Object obj, Object... args)
哦,需要使用Obejct的一维数组将其打包
m.invoke(null, new Object[]{new String[]{"a", "b", "c"}});
//输出:[a, b, c]
实际,对于其它非引用数据类型数组,也可以自己进行打包,如:
m.invoke(null, new Object[]{new int[]{1, 2, 3}});
m.invoke(null, new Object[]{1});
15.使用反射操作字段
获取字段
getField();
getFields();
设置字段
void setXX(Object obj, Object value);
16.其它API
获取修饰符
getModifiers();
获取类的全限定名
getName();
获取类的简单名称
getSimpleName();
17.单例设计模式
在应用中某一个类(工具类0),有且只有一个实例0
单例模式的写法有多种
编写规则
1):私有化构造器
2):在类中创建一个自身对象
3):向外暴露一个公共的静态方法用于返回该对象
使用反射机制之后,传统的单例设计,不安全
-->建议:使用枚举来做单例
Spring框架创建的对象默认就是单例的
public enum Util {
INSTANCE;
}