尚硅谷_Java零基础教程-java入门必备-初学者基从入门到精通全套完整版(宋红康主讲) P636-665
(*)表示仅需了解
文章目录
1. java反射机制概述
- 反射被视为动态语言的关键,反射机制允许程序在执行期借助于ReflectionAPI取得任何类内的内部信息,并能直接操作任意对象的内部属性及方法。
- 加载完类之后,在堆内存的方法区就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象地称之为:反射。
- 正常方式:
- 引入需要的“包类”名称
- 通过new实例化
- 取得实例化对象
- 反射方式:
- 实例化对象
- getClass()方法
- 取得完整的“包类”名称
- 正常方式:
* 动态语言 vs 静态语言
- 动态语言:是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
- 例如:Object-C、C#、JavaScript、PHP、Python、Erlang。
- 运行时结构不可变的语言就是静态语言。
- 例如:java、C、C++
- 但Java有一定的动态性,可以利用反射机制、字节码操作获得类似动态语言的特性。
代码体现反射的动态性:
@Test
public void test2() {
for (int i=0;i<10;i++) {
int num = new Random().nextInt(3);//0,1,2
String classPath = "";
switch (num){
case 0:
classPath = "java.util.Date";
break;
case 1:
// classPath = "java.sql.Date"; // 无空参构造器
classPath = "java.util.HashMap";
break;
case 2:
classPath = "learn.others.reflect.Person";
break;
}
try {
System.out.println(getInstance(classPath));
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
public Object getInstance(String classPath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
java反射机制研究与应用
- java反射机制提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
反射相关的主要API
- java.lang.Class
- java.lang.reflect.Method
- java.lang.reflect.Field
- java.lang.reflect.Constructor
反射测试1:构造对象,调用方法、属性(非私有)
try {
//1. 通过反射创建对象
Class clase = Person.class;
Constructor cons = clase.getConstructor(String.class, int.class);
Person tom = (Person) cons.newInstance("Tom",23);
System.out.println(tom.toString());
//通过反射调用属性、方法
// Field age = clase.getDeclaredField("age");//报错,因为是私有的
Field name = clase.getDeclaredField("name");
name.set(tom,"refa");
System.out.println(tom.toString());//Person{name='refa', age=23}
Method show = clase.getDeclaredMethod("show");
show.invoke(tom);//show a person
final Method showEmotion = clase.getDeclaredMethod("showEmotion", String.class);
showEmotion.invoke(tom,"sad");//I'm sad now
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException | NoSuchFieldException e) {
e.printStackTrace();
}
反射测试2:构造对象,调用方法、属性(私有)
通过setAccessible(true)
//通过反射调用 私有 的属性、方法
//私有构造器
Constructor pcons = clase.getDeclaredConstructor(String.class);
pcons.setAccessible(true);
Person privat = (Person) pcons.newInstance("privat");
System.out.println(privat.toString());//Person{name='privat', age=0}
//私有属性
Field age = clase.getDeclaredField("age");
age.setAccessible(true);
age.set(privat,20);
System.out.println(privat.toString());//Person{name='privat', age=20}
//私有方法
final Method showEmotion = clase.getDeclaredMethod("showEmotion", String.class);
showEmotion.setAccessible(true);
showEmotion.invoke(privat,"nervous");//I'm nervous now
?什么时候用反射
- 编译的时候无法确定new哪个类的对象。
- 例如:在web应用中,程序先启动,根据用户的选择是登录还是别的,去创建对象,也就是在编译之前无法确定new的是哪个类的对象。这种情况下就可以用反射。
?反射与封装性
封装性无法“强硬的阻止”反射调用私有的属性/方法/构造器,但是标识为私有,即是建议不要调用,或者说慎用。
2. 理解Class类并获取Class实例
对Class类的理解
- 类的加载过程:
程序经过javac.exe命令后,会生成一个或多个字节码文件(.class);接着我们使用java.exe命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中。此过程就成为类的加载。
加载到内存中的类,就称为运行时类,此运行时类,就作为java.lang.Class的一个实例。 - Class的实例就对应着一个运行时类
- 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
获取Class实例的方式
@Test
public void test3(){
//方式一:调用运行时类的属性:.class
Class clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
//方式三:调用Class的静态方法:forName(String classPath)
try {
Class clazz3 = Class.forName("java.lang.String");
System.out.println(clazz3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// class learn.others.reflect.Person
// class learn.others.reflect.Person
// class java.lang.String
System.out.println(clazz1 == clazz2);//true
}
哪些类型可以有Class对象
- class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface
- []:数组
- enum
- annotation
- primitive type:基本数据类型
- void
@Test
public void test4(){
Class c1 = Object.class;//class java.lang.Object
Class c2 = Comparable.class;//interface java.lang.Comparable
Class c3 = String[].class;//class [Ljava.lang.String;
Class c4 = int[][].class;//class [[I
Class c5 = ElementType.class;//class java.lang.annotation.ElementType
Class c6 = Override.class;//interface java.lang.Override
Class c7 = int.class;//int
Class c8 = void.class;//void
Class c9 = Class.class;//class java.lang.Class
int[] a = new int[10];
int[] b = new int[100];
System.out.println(a.getClass()==b.getClass());//true
}
3. 类的加载与ClassLoader的理解
类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化:
- 类的加载:将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成
- 类的链接:将类的二进制数据合并到JRE中
- 类的初始化:JVM负责对类进行初始化
类加载器
类加载器的作用
- 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
- 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
类加载器读取配置文件
public void test5(){
Properties pros = new Properties();
//读取配置文件方法一:
// FileInputStream fis = new FileInputStream("src\\learn\\others\\reflect\\jdbc.properties");
// pros.load(fis);
//读取配置文件方法二:
//配置文件默认是识别为当前module的src下
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
// InputStream is = classLoader.getResourceAsStream("jdbc.properties");
try (InputStream is = classLoader.getResourceAsStream("jdbc.properties");){
pros.load(is);
String user = pros.getProperty("user");
String pswd = pros.getProperty("password");
System.out.println("user:" + user + ",password:" + pswd);//user:km,password:123
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
4. 创建运行时类的对象
new Instance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参构造器。
Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
System.out.println(p);
要想此方法正常的创建运行时类的对象,要求:
- 运行时类必须提供空参的构造器
- 空参的构造器的访问权限足够。通常设置为public
在java bean中要求提供一个public的空参构造器,原因:
- 便于通过反射,创建运行时类的实例
- 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
5. 获取运行时类的完整结构
所在包、注解、接口、父类
属性、方法、构造器
6. 调用运行时类的指定结构
属性、方法、构造器
public class ReflectionTest {
//属性
@Test
public void test1() {
Class clazz = Person.class;
try {
final Person p = (Person) clazz.newInstance();
final Field id = clazz.getDeclaredField("id");
final Field name = clazz.getDeclaredField("name");
/*
设置对象的属性值
*/
id.setAccessible(true);
id.set(p,1001);
System.out.println(id.get(p));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
//方法
@Test
public void test2() {
Class clazz = Person.class;
try {
final Person p = (Person) clazz.newInstance();
final Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
showNation.invoke(p,"china");
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
//静态方法
@Test
public void test3() {
Class clazz = Person.class;
try {
final Person p = (Person) clazz.newInstance();
final Method display = clazz.getDeclaredMethod("display", String.class, int.class);
display.setAccessible(true);
//如果没有返回值,返回null
final Object aStatic = display.invoke(p,"static", 0);//static0
System.out.println(aStatic);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
7. 反射的应用:动态代理
- 代理设计模式的原理:
使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。 - 静态代理:特征是代理类和目标对象的类都是在编译期间就确定下来,这样不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来,程序开发中必然产生过多的代理。最好通过一个代理类来完成全部的代理
静态代理实例
interface ClothFactory{
void produceCloth();
}
class ProxyClothFactory implements ClothFactory{
private ClothFactory factory;
public ProxyClothFactory(ClothFactory factory) {
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("代理工厂pre工作");
factory.produceCloth();
System.out.println("代理工厂post工作");
}
}
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("Nike 工厂生产衣服");
}
}
public class StaticProxyTest{
@Test
public void test1(){
final NikeClothFactory nikeClothFactory = new NikeClothFactory();
final ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nikeClothFactory);
proxyClothFactory.produceCloth();
}
}
动态代理实例
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
@Override
public String getBelief() {
return "i believe i can fly";
}
@Override
public void eat(String food) {
System.out.println("I like "+food);
}
}
/*
动态代理,需要解决:
1. 如何根据被代理类,动态的创建代理类
2. 动态调用方法
*/
class ProxyFactory{
public static Object getProxyInstance(Object obj){
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj ;
public void bind(Object obj) {
this.obj = obj;
}
//当通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
//将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method 即为代理类调用的方法,即为被代理类调用的方法
System.out.println("pre invoke ...");
Object returnValue = method.invoke(obj, args);
System.out.println("post invoke ...");
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
final SuperMan superMan = new SuperMan();
// final ProxyFactory factory = new ProxyFactory();
Human human = (Human) ProxyFactory.getProxyInstance(superMan);
human.eat("sud");
}
}
动态代理与AOP
把代码段1、2和3中相同的代码段分离
略