反射定义
反射式编程(英语:reflective programming)或反射(英语:reflection)或者内省
是指计算机程序在运行时可以访问、检测和修改它本身状态或行为的一种能力。[1]用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。
“通俗讲” - 通过反射技术在程序的运行过程中,来获取类/接口的信息[修饰符,类的名称,父类],属性的信息[修饰符,数据类型,属性名],方法的信息[修饰符,数据类型,名称]等.
在运行的时候才知道我操作的是哪个类
掌握目标:
3-1. 通过反射技术来动态获取属性的信息以及操作属性[反射的技术来对属性的值进行设置和获取]
3-2. 通过反射技术来动态调用类中的构造方法以及获取构造方法的信息[修饰符,方法名,参数列表]
3-3. 通过反射技术来动态获取类里面的方法的信息[修饰符,数据类型,方法名以及方法参数列表]以及反射调用方法
api包 - java.lang.reflect
反射优势 - “很流氓” - 破坏封装性.
学好反射和设计模式 - 帮助我们未来来读懂一些框架源码的.所有的框架的底层都是基于反射技术来实现的.
java.lang.Class
class实例 - 一个类无论被实例化多少次,那么它在JVM中的class实例永远只有1个.
它是学习反射技术的必备的类 - 提供了很多api来完成掌握目标中的动作.
所有被类加载器加载到内存中的类都是属于Class的对象 - Class类是用来描述类的类[用来描述类的元信息]
我们的类在Class面前,就是一个Class的对象而已
反射相关api
Field getDeclaredField(String name);//根据属性的名称来得到Field对象
Field[] getDeclaredFields();//获取类里面所有的[包括私有的]声明的属性对应的Field数组
只能获取到非私有的 1. Field getField(String name); 2. Field[] getFields();
String getName();//获取类的全限定名
String getSimpleName();//获取类的简称
T newInstance();//调用空参构造
Constructor
getDeclaredConstructor(Class<?>… parameterTypes); 根据指定的参数类型来得到指定的构造对应的实例Constructor对象.
如果什么参数都不传入,拿到的就是空参构造对应的Constructor对应的实例.
Constructor<?>[] getDeclaredConstructors();
直接获取类中的所有的构造,每个构造对应一个Constructor.
Method getDeclaredMethod(String methodName,Class<?>… parameterTypes);
Method][] getDeclaredMethods();
获取类的Class实例的方式
类名.class
调用java.lang.Object类提供的方法Class<?> getClass();
框架底层喜欢的使用一种,更加灵活
Class类中提供的static Class<?> forName(“类的全限定名”);//需要抓取一个非运行时异常java.lang.ClassNotFoundException类型找不到异常
基本类型.class
demo
package tech.aistar.day16;
/**
* 本类用来演示: java.lang.Class<T>
*
*
* @author: success
* @date: 2021/8/12 10:26 上午
*/
public class ClassDemo {
public static void main(String[] args) {
//获取类的class实例的方式
//每个类在JVM中的class实例永远只有1个 - 无论构建了多少个对象
//1. 类名.class
Class<?> c1 = Point.class;
Class<?> c2 = Point.class;
System.out.println(c1);//class tech.aistar.day16.Point
System.out.println(c1 == c2);
Class<?> c3 = String.class;
System.out.println(c3);//class java.lang.String
//2. 对象.getClass();
Point p1 = new Point();
Point p2 = new Point();
Class<?> c4 = p1.getClass();//对象的类型
Class<?> c5 = p2.getClass();
// 类型判断 对象 instanceof 类
System.out.println(c4);//class tech.aistar.day16.Point
System.out.println(c4 == c5);//true
//3. Class类中提供的static Class<?> forName("类的全限定名");
//TODO... 为甚鼓励使用这种 ???
try {
Class<?> c6 = Class.forName("tech.aistar.day16.Point");
System.out.println("c6:"+c6);//c6:class tech.aistar.day16.Point
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//4. 基本类型.class
Class<?> c7 = int.class;
System.out.println(c7);//int
}
}
Field字段实例
java.lang.reflect.Field
api
int getModifiers();
返回由该 Field对象表示的字段的Java语言修饰符,作为整数。//默认的 - 0 //public -1 //private - 2 //protected - 4
Class<?> getType();//返回属性的数据类型
String getName();//属性的名称
void set(Object obj,Object value);//通过属性对应的Field对象来告知JVM,应该把value设置到哪个obj对象上去.
void setAccessible(boolean on);//反射操作私有属性,必须要设置为true,否则会抛出-java.lang.IllegalAccessException
Object get(Object obj);//返回obj中属性字段的对应的属性值.
Constructor构造实例
调用空参构造
直接调用java.lang.Class
提供的方法T newsIntance(); java.lang.reflect.Constructor提供了方法
public T newInstance(Object... initargs) Constructor<?> c1 = c.getDeclaredConstructor(); Point p2 = (Point) c1.newInstance();//可变长列表的方法
调用带参构造
java.lang.reflect.Constructor - 提供的方法
public T newInstance(Object … initargs)
常用方法
int getModifiers();//获取修饰符对应的数字
String getName();//构造方法的名称 - [类的全限定名]
void setAccessible(boolean on);//如果设置true,直接调用私有的的构造方法
反射可以破坏单例
Modifier
java.lang.reflect
传入一个修饰符对应的数字,来返回修饰的具体的中文的名称
public static String toString(int mod) { StringBuilder sb = new StringBuilder(); int len; if ((mod & PUBLIC) != 0) sb.append("public "); if ((mod & PROTECTED) != 0) sb.append("protected "); if ((mod & PRIVATE) != 0) sb.append("private "); /* Canonical order */ if ((mod & ABSTRACT) != 0) sb.append("abstract "); if ((mod & STATIC) != 0) sb.append("static "); if ((mod & FINAL) != 0) sb.append("final "); if ((mod & TRANSIENT) != 0) sb.append("transient "); if ((mod & VOLATILE) != 0) sb.append("volatile "); if ((mod & SYNCHRONIZED) != 0) sb.append("synchronized "); if ((mod & NATIVE) != 0) sb.append("native "); if ((mod & STRICT) != 0) sb.append("strictfp "); if ((mod & INTERFACE) != 0) sb.append("interface "); if ((len = sb.length()) > 0) /* trim trailing space */ return sb.toString().substring(0, len-1); return ""; }
拓展应用
反射可以破坏单例
反射破坏不了枚举单例
package tech.aistar.day16; import tech.aistar.design.singleton.version03.Singleton03; import tech.aistar.design.singleton.version04.Singleton04; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /** * 本类用来演示: 反射可以破坏单例 * * @author: success * @date: 2021/8/12 2:25 下午 */ public class ReflectPoSingleton { public static void main(String[] args) { //1. 获取单例的Class实例 try { Class<?> c = Class.forName("tech.aistar.design.singleton.version03.Singleton03"); //2. 获取空参构造对应的Constructor实例 Constructor<?> c1 = c.getDeclaredConstructor(); //3. 调用私有的空参构造 c1.setAccessible(true); //4. 调用 Singleton03 s1 = (Singleton03) c1.newInstance(); //反射连续调用俩次私有的空参构造 Singleton03 s2 = (Singleton03) c1.newInstance(); System.out.println(s1 == s2);//false } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
为何破坏不了枚举类型单例
分析-NoSuchMethodException
//记载类,初始化静态属性,调用空参构造
Class<?> c5 = Class.forName("tech.aistar.design.singleton.version05.Singleton05");
Constructor<?> cc = c5.getDeclaredConstructor();
控制台效果
比较繁琐的操作的事情,费时费力的事情
//不存在一个空参构造的方法让我们去调用
java.lang.NoSuchMethodException: tech.aistar.design.singleton.version05.Singleton05.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at tech.aistar.day16.ReflectPoSingleton.main(ReflectPoSingleton.java:36)
控制台
hello.java
public enum hello{
INSTANCE
}
确认jdk-bin-jad.exe - 没有下载 - http://varaneckas.com/jad/
javac hello
jad -s java hello
反编译之后出来之后 hello(String.class,int.class)
解决方案
Constructor<?> cc = c5.getDeclaredConstructor(String.class,int.class);
分析 - IllegalArgumentException
//加载这个类 Class<?> c5 = Class.forName("tech.aistar.design.singleton.version05.Singleton05"); Constructor<?> cc = c5.getDeclaredConstructor(String.class,int.class); //java.lang.NoSuchMethodException - 抛出一个不存在这个方法 cc.setAccessible(true); //Exception in thread "main" java.lang.IllegalArgumentException: // Cannot reflectively create enum objects Singleton05 s05 = (Singleton05) cc.newInstance();
原因剖析newInstance方法
@CallerSensitive public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { //判断是否为枚举类型,如果是枚举类型直接抛出了这个异常了. if ((clazz.getModifiers() & Modifier.ENUM) != 0){ throw new IllegalArgumentException("Cannot reflectively create enum objects"); } return inst; }
结论 - 不允许我们用反射的技术来构建枚举类型的实例,底层会进行类型的判断,发现如果是枚举类型对应的class实例,直接抛出异常
作业
一个对象的中的所有String类型的成员变量所对应的字符串内容中的”b”改为”a”
public class Obj{ private int age = 10; private String s2 = "b"; private String name = "tbm"; private String s1 = "adminb"; //... } //反射技术 - 所有字段拿出来 - Field数组中 //获取到Field的类型,判断是不是String类型 //如果是String-"b"改为"a"
反射工厂
回忆工厂设计模式[GOF]
- 简单工厂方法,多方法工厂,静态方法工厂 - 本质上不属于GOF
- 工厂方法设计模式以及抽象工厂[专注于产品族] - 属于GOF
- 反射工厂 - 工厂类中利用反射技术来构建某个类的/接口的具体的实例.
补充Properties
属于集合框架的类 - 属于Map[I]
java.util.Properties extends java.util.Hashtable[哈希表,多线程安全的]
作用:通过io流把本地的.properties文件读取到内存中,然后映射到Properties对象
Properties对象就是.properties属性文件[encoding=utf-8]在内存中的映射.
File - 本地磁盘的文件在java内存中的映射的那个对象.
存储数据的格式然后是一个键值对的形式
# key=value - key也是不要重复的. username=tom password=123
根据key来获取value的方法
String getProperty(String key);
demo
package tech.aistar.day16.prop; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * 本类用来演示: 读取Properties文件 * * @author: success * @date: 2021/8/13 8:41 上午 */ public enum ReadPropDemo { INSTANCE; // 定义一个Properties属性 private Properties properties; ReadPropDemo(){ //可以在构造方法 - 初始化的 //Properties对象就是.properties属性文件[encoding=utf-8]在内存中的映射. properties = new Properties(); //读取属性文件 //InputStream in = new FileInputStream("src/tech/aistar/day16/prop.bean.properties"); //固定的语法 - 死记住 //获取属性文件字节输入流 InputStream in = Thread.currentThread() .getContextClassLoader() .getResourceAsStream("tech/aistar/day16/prop/bean.properties"); //加载 try { //Properties对象就是.properties属性文件[encoding=utf-8]在内存中的映射. properties.load(in); } catch (IOException e) { e.printStackTrace(); } } //普通方法 public String getValue(String key){ //确认properties不为null return properties == null?null:properties.getProperty(key); } }
单元测试
package tech.aistar.day16.prop; /** * 本类用来演示: * * @author: success * @date: 2021/8/13 8:47 上午 */ public class ReadPropDemoTest { public static void main(String[] args) { String value = ReadPropDemo.INSTANCE.getValue("username"); System.out.println(value); } }
反射工厂demo
简单工厂的缺点 - 如果有新的产品的加入,需要修改工厂类的 - 违背了软件开发的设计原则 - “开闭原则”
优点 - 够简单
工厂方法设计模式 - 优点:一个工厂类只负责生产一个产品,如果有新的产品的加入.不需要修改工厂类,只需要增加一个工厂类
缺点 - 项目中会存在很多的工厂类.
抽象工厂 - 负责创建一个产品族.
反射工厂既能够保证在新增一个产品的时候,能够遵守”开闭原则”,又能够保证始终仅仅只有一个工厂类.
package tech.aistar.design.factory.reflect;
/**
* 本类用来演示: 反射工厂 - 反射工厂既能够保证在新增一个产品的时候,能够遵守"开闭原则",
* 又能够保证始终仅仅只有一个工厂类.
*
* Properties + 反射 + 泛型方法/泛型类
*
* @author: success
* @date: 2021/8/13 9:13 上午
*/
public class BaseFactory<T>{
//面向接口编程
//泛型方法 - 静态方法<T>,同时也要设置泛型类
public static<T> T getInstance(String type){
T t = null;
//如果传入进来的是type - 是某个类的全限定名,比如
//tech.aistar.design.factory.reflect.TeacherDaoImpl
//获取class实例的方式
try {
Class<?> c = Class.forName(type);
//反射调用空参构造
try {
//Point p = (Point) c.newInstance();
t = (T) c.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return t;
}
}
Method
java.lang.reflect.Method
常用方法
int getModifiers();
返回由该 method对象表示的字段的Java语言修饰符,作为整数。//默认的 - 0 //public -1 //private - 2 //protected - 4
Class<?> getReturnType();//返回方法的返回类型
String getName();//方法的名称
Class<?>[] getParameterTypes();//返回方法的参数列表.
Object invoke(Object obj,Object… args);//反射调用方法
void setAccessible(boolean on);//调用私有方法需要调用之前来设置可见性 - true
Array
java.lang.reflect.Array -
Array
类提供静态方法来动态创建和访问Java数组 - 反射技术操作java数组面试题 - Arrays和Array的区别!
java.util.Arrays - 数组工具类.
常用用法
static int getLength(Object array)
返回指定数组对象的长度,如 int 。static Object get(Object array, int index)
返回指定数组对象中的索引组件的值。static Object newInstance(Class<?> componentType, int length)
创建具有指定组件类型和长度的新数组。static void set(Object array, int index, Object value)
将指定数组对象的索引组件的值设置为指定的新值。
demo
package tech.aistar.day16;
import java.lang.reflect.Array;
import java.util.Arrays;
/**
* 本类用来演示: 反射操作java数组
*
* @author: success
* @date: 2021/8/13 1:32 下午
*/
public class ArrayDemo {
public static void main(String[] args) {
Integer[] arr1 = {10,20,30};
String[] arr2 = {"java","python","db","web"};
//用一个方法,来遍历上面俩个不同元素类型数组
//反射技术来遍历数组
printArr(arr1);
System.out.println("====华丽丽的分割线====");
printArr(arr2);
System.out.println("===反射动态创建数组===");
//对任何元素类型的数组,都可以进行扩容操作
//1. 确定新的数组的长度 - 原来数组的长度+扩容长度[反射创建新的数组]
//2. 读取原来数组中的每个下标的数据[反射读取]一一复制到新的数组中[反射设置值]
//3. 返回类型确定[扩容之后的] - Object
Integer[] temp = (Integer[]) extendsArr(arr1);
printArr(temp);
System.out.println("---");
String[] strTemp = (String[]) extendsArr(arr2);
printArr(strTemp);
}
//数组的扩容
public static Object extendsArr(Object arr){
//1. 创建数组必不可少的俩个条件.a. 数组的元素类型 b.数组的长度
//获取旧数组长度
int len = Array.getLength(arr);
//确定新的数组的长度
int newLen = len*2;
//2. 获取原来数组的组件的类型 - 数组的元素类型
Class<?> type = arr.getClass().getComponentType();
//3. 反射创建新的数组了
Object newArr = Array.newInstance(type,newLen);
//4. 把arr旧数组中的元素拷贝新的数组中去
for (int i = 0; i < len; i++) {
//根据下标获取值
Object o = Array.get(arr,i);
//把o设置到newArr数组的下标i处
Array.set(newArr,i,o);
}
return newArr;
}
//反射访问
//Integer[] String[] - 数组 - extends Object
//并不是extends Object[]
private static void printArr(Object arr) {
//1. 数组的长度
int len = Array.getLength(arr);
//2. 遍历这个数组
for (int i = 0; i < len; i++) {
//反射的技术通过下标去取元素
Object obj = Array.get(arr,i);
System.out.println(obj);
}
}
}
注解
只要了解即可.jdk5.0开始引入了注解的机制.
现在框架的配置,框架的使用 - 1. 基于xml的配置方式 2. 基于注解的配置/开发方式 - 简洁
学习注解的目的是为了以后能够知道我们框架的使用中遇到注解,知道这个注解背后是个大概什么底层即可.
内置注解
@Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
@SuppressWarnings - 指示编译器去忽略注解中声明的警告。
@SuppressWarnings(“all”) - 抑制所有的警告
@FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
自定义注解
使用@interface来修饰注解,自定义的注解默认都会继承java.lang.Annotation
@Target - 指定你这个注解可以在什么地方被使用
哪些地方 - 类,方法,参数,接口,局部变量上,属性,构造
值可以设置成枚举类型java.lang.annotation.ElementType中的枚举常量
public enum ElementType { /** Class, interface (including annotation type), or enum declaration */ TYPE, /** Field declaration (includes enum constants) */ FIELD, /** Method declaration */ METHOD, /** Formal parameter declaration */ PARAMETER, /** Constructor declaration */ CONSTRUCTOR, /** Local variable declaration */ LOCAL_VARIABLE, /** Annotation type declaration */ ANNOTATION_TYPE, /** Package declaration */ PACKAGE, /** * Type parameter declaration * * @since 1.8 */ TYPE_PARAMETER, /** * Use of a type * * @since 1.8 */ TYPE_USE }
@Retention(RetentionPolicy.RUNTIME)
可以在程序的运行过程中,通过反射的技术来得到注解的信息.
注解中只有方法的概念,没有的属性的概念
反射获取注解的值
有办法来判断类或者方法是否加入了注解
有办法通过反射技术来获取类/方法上加入注解的属性值
package tech.aistar.day16.anno;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 本类用来演示: 反射获取注解的值
*
* @author: success
* @date: 2021/8/13 2:39 下午
*/
public class MyAnnoTest {
public static void main(String[] args) {
//1. 判断某个类或者某个方法上是否有注解
//有注解 - 标志 - 对这个有注解的类该干嘛干嘛...
//注解还配置了属性值 - 需要取出属性值,然后再进行进一步的处理...
//类
Class<?> c = UseMyAnno.class;
//1. 判断类上是否加入了MyAnno注解
boolean flag = c.isAnnotationPresent(MyAnno.class);
if(flag){
System.out.println(c.getSimpleName()+"加入了注解!");
//获取注解的属性值 - 前提是有
MyAnno myAnno = c.getAnnotation(MyAnno.class);
//获取属性值
String[] arr = myAnno.value();
System.out.println(Arrays.toString(arr));
}else{
System.out.println(c.getSimpleName()+"上没有加入注解!");
}
System.out.println("====方法上的注解的信息 - 反射技术===");
try {
Method m = c.getDeclaredMethod("add");
if(m.isAnnotationPresent(MyAnno.class)){
System.out.println("方法上有注解!");
MyAnno myAnno = m.getAnnotation(MyAnno.class);
String[] arr = myAnno.value();
System.out.println(Arrays.toString(arr));
}else{
System.out.println("方法上没有注解...");
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
mybatis注解开发方法
mybatis底层使用到的是jdbc技术 - 和数据库打交道的技术
jdbc步骤很繁琐,步骤比较多~
打开
Connection con = DriverManager.getConnection(url,"root","root");
String sql="delete from user";
PreparedStatement pstmt = con.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
关闭
rs.close();
pstmt.close();
con.close();
mybatis就是会对jdbc的代码进行一个封装
public interface IUserDao { /** * 查询所有用户 * @return */ @Select("select * from user") List<User> findAll(); }
尝试走一遍mybatis作者的路线
自定义一个注解
public @interface Select{
String value();
}
注解本身是不会完成任何的业务逻辑的.
通过反射的技术->select * from user
工具类{
Connection con = DriverManager.getConnection(url,"root","root");
String sql="反射技术获取的";
PreparedStatement pstmt = con.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
关闭
rs.close();
pstmt.close();
con.close();
}
第一阶段corejava总结
- JVM内存模型+GC算法 - 面试必问
- 静态代理和动态代理 - 设计模式
体系回顾
- part01-基础语法-数组的排重/排序算法/递归算[阶乘,杨辉,斐波那契数列]
- part02
- 封装,继承,多态
- 四种访问修饰符
- static关键字,final,abstract修饰符
- String,StringBuffer,StringBuilder
- 包装类型Integer - [IntegerCache缓冲区,-128~127]
- 设计模式 - 单例[双重锁检测]+工厂+模板+装饰器
- Object - equals&hashcode,clone,toString,wait,notify,notifyAll,finalize,getClass()
- Date&Calendar&SimpleDateFormat&BigInteger - 常用的内置的api
- part03 - 集合框架
- ArrayList&LinkedList&Vector
- HashSet&TreeSet
- HashMap&Properties
- 多线程体系
- 反射+注解
第二阶段的知识点
周一之前 - mysql环境装好
围绕数据库
- mysql学习 - 关系型数据
- jdbc学习 - java和db进行交互的技术
- myabtis框架学习 - 持久层技术,对jdbc的封装
相关知识点 - mybatis-plus框架,redis - 非关系型数据