一、反射机制
1.反射机制的作用
通过java语言中的反射机制可以操作字节码文件(类似于黑客,可以读和写字节码文件)
可以操作代码片段(class文件)
反射机制相关的类在java.lang.reflect.*;包下
2.反射机制相关的类有哪些?
java.lang.Class:整个字节码,代表一个类型
java.lang.reflect.Method:字节码中的方法字节码
java.lang.reflect.Constructor:字节码中的构造方法字节码
java.lang.reflect.Field:字节码中的属性字节码
例:
//Class
public class User{
//Field
int no;
//Constructor
public User(){}
public User(int no){
this.no = no;
}
//Method
public void setNo(int no){
this.no = no;
}
public void getNo(int no){
return no;
}
}
二、获取Class 的三种方式
要操作一个类的字节码,需要首先获取到这个类的字节码,怎么获取java.lang.Class实例?
1.Class.forName()
Class c = Class.forName(“完整类名带包名”)
①静态方法
②方法的参数是一个字符串
③字符串需要的是一个完整类名
④完整类名必须带有包名,java.lang包也不能省略
Class c1 = Class.forName("java.lang.String");//c1代表String.class文件,或代表String类型
Class c2 = Class.forName("java.util.Date");//c2代表Date类型
Class c3 = Class.forName("java.lang.Integer");//c3代表Integer类型
Class c4 = Class.forName("java.lang.System");//c4代表System类型
重点:
如果只希望一个类的静态代码块执行,其他代码一律不执行,可以使用:
Class.forName(“完整类名”);
这个方法的执行会导致类加载,类加载时,静态代码块执行
2.getClass()
Class c = 引用.getClass()
java 中任何一个对象都有一个方法:getClass()
//getClass()
String s = "abc";
Class x = s.getClass();//x代表String.Class字节码文件,x代表String类型
System.out.println(c1 == x);//true(==判断的是对象的内存地址)
3. .Class属性
Class c = 任何类型.class;
java语言中任何一种类型,抱愧基本数据类型,都有.class属性
Class z = String.class;//z代表String类型
Class k = Date.class;
Class f = int.class;
Class e = double.class;
三、通过反射实例化对象
获取到Class文件能干什么?
通过Class的newInstance()方法来实例化对象
注意:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参数构造存在才可以
try {
//通过反射机制,获取Class,通过Class来实例化对象
Class c = Class.forName("bean.User");
try {
//newInstance()方法会调用User这个类的无参数构造方法,完成对象的创建
//重点是:newInstance()调用的是无参构造,必须保证无参构造是存在的
Object obj = c.newInstance();
System.out.println(obj);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
四、通过读属性文件实例化对象
验证反射机制的灵活性
非常灵活,符合OCP开闭原则:对扩展开放,对修改关闭
五、获取类路径下文件的绝对路径
FileReader reader = new FileReader("reflect/classinfo.properties");
这种方式的路径缺点是:移植性差,在IDEA中默认的当前路径是project的根
接下来是一种通用路径
注意:
使用一下通用方式的前提是:这个文件必须在类路径下
什么是类路径?在src下的都是类路径下
src是类的根路径
String path = Thread.currentThread().getContextClassLoader().getResource("").getPath();
解释:
Thread.currentThread():当前线程对象
getContextClassLoader():是线程对象的方法,可以获取到当前线程的类加载器对象
getResource():【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源
//获取一个文件的绝对路径
String path = Thread.currentThread().getContextClassLoader()
.getResource("classinfo.properties").getPath();
FileReader reader = new FileReader(path);
Properties pro = new Properties();
pro.load(reader);
reader.close();
//通过key获取value
String className = pro.getProperty("className");
System.out.println(className);
直接以流的形式返回:
InputStream reader = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("classinfo.properties");
Properties pro = new Properties();
pro.load(reader);
reader.close();
//通过key获取value
String className = pro.getProperty("className");
System.out.println(className);
六、资源绑定器
java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容
使用以下方式,属性配置文件xxx.properties必须放到类路径下
资源绑定器:
只能绑定xxx.properties文件,且这个文件必须在类路径下,文件筐扩展名也必须是properties
写路径时,路径后的扩展名不能写
ResourceBundle bundle = ResourceBundle.getBundle("classinfo2");
String className = bundle.getString("className");
System.out.println(className);
七、类加载器(了解即可)
1.什么是类加载器?
专门负责加载类的命令/工具----ClassLoader
2.JDK中自带了3个类加载器
启动类加载器、扩展类加载器、应用类加载器
3.如何进行加载?
首先会通过启动类加载器加载:专门加载rt/.jar中都是JDK最核心的类库
如果加载不到,会通过扩展类加载器加载:专门加载ext/*jar中的类
如果加载不到,会通过应用类加载器加载:专门加载classpath中的类
4.双亲委派机制
java中为了保证类加载的安全,使用了双亲委派机制
优先从启动类加载器中加载,称为“父”
“父”无法加载到,再从扩展类加载器中加载,称为“母”
双亲委派,如果都加载不到,才会考虑从应用类加载器中加载,知道加载到为止
八、获取Field
Field翻译为字段,就是属性/成员
//获取整个类
Class studentClass = Class.forName("bean.Student");
//bean.Student
String className = studentClass.getName();
System.out.println("完整类名:" + className);
//Student
String simpleName = studentClass.getSimpleName();
System.out.println("简类名:" + simpleName);
//获取类中所有的public修饰的Field
Field[] fields = studentClass.getFields();
System.out.println(fields.length);//测试数据中只有一个元素
//取出这个Field
Field f = fields[0];
//取出这个Field的名字
String filedName = f.getName();
System.out.println(filedName);
//获取所有的Field
Field[] fs = studentClass.getDeclaredFields();
System.out.println(fs.length);//4
System.out.println("--------------");
//遍历
for (Field field:fs){
//获取属性的修饰符列表
//返回的修饰符是一个数字,每个数字是修饰符的代号
int i = field.getModifiers();//修饰符可能有多个所以是复数
System.out.println(i);
//可以将代号数字转换成字符串吗?
String modFieldString = Modifier.toString(i);
System.out.println(modFieldString);
//获取属性的类型
Class fieldType = field.getType();//返回Class
String fName = fieldType.getName();
System.out.println(fName);
//获取属性的名字
System.out.println(field.getName());
1.反编译Field(以后不会用,了解即可)
//创建这个是为了拼接字符串
StringBuilder s = new StringBuilder();
Class studentClass = Class.forName("bean.Student");
s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() +" {\n");
Field[] fields = studentClass.getDeclaredFields();
//对属性进行循环
for (Field field : fields){
s.append("\t");
s.append(Modifier.toString(field.getModifiers()));
s.append(" ");
s.append(field.getType().getSimpleName());
s.append(" ");
s.append(field.getName());
s.append(";\n");
}
s.append("}");
System.out.println(s);
2.通过反射机制访问对象属性【必须掌握】
①怎么通过反射机制访问一个java对象的属性?
给属性赋值set
获取属性的值get
获取属性依靠名字区分
//不使用反射机制怎么访问对象属性?
Student s = new Student();
//给属性赋值
s.no = 111;
//读属性值
System.out.println(s.no);
//使用反射机制怎么访问?
Class studentClass = Class.forName("bean.Student");
Object obj = studentClass.newInstance();//obj就是Student对象
//获取no属性(根据属性名称获取Field)
Field noField = studentClass.getDeclaredField("no");
//给obj对象(Student对象)的no赋值
noField.set(obj,2222);//给obj对象的no属性赋值
//读取属性的值
System.out.println(noField.get(obj));
②私有属性的访问
//可以访问私有属性吗?
Field nameField = studentClass.getDeclaredField("name");
//打破封装(反射机制的缺点:打破封装,可能会给不法分子留下机会)
//此时,在外部也可以访问private
nameField.setAccessible(true);
//给name属性赋值
nameField.set(obj,"jackson");
//获取name属性的值
System.out.println(nameField.get(obj));
3.可变长参数【不属于Field】
int… args :这就是可变长参数
语法:类型…(注意:一定是三个点)
①可变长度参数要求的参数个数是:0~N个
②可变长度参数在参数列表中必须在最后一个位置上,且可变长度参数只能有一个
③可变长度参数可当做一个数组看待
九、反射Method(不需掌握)
//获取类
Class userServiceClass = Class.forName("bean.Service");
//获取所有的Method(包括私有的)
Method[] methods = userServiceClass.getDeclaredMethods();
System.out.println(methods.length);//2
//遍历Method
for (Method method : methods){
//获取修饰符列表
System.out.println(Modifier.toString(method.getModifiers()));
//获取方法的返回值类型
System.out.println(method.getReturnType().getSimpleName());
//获取方法名
System.out.println(method.getName());
//方法的修饰符列表(一个方法的参数可能会有多个)
Class[] parameterTypes = method.getParameterTypes();
for (Class parameterType : parameterTypes){
System.out.println(parameterType.getSimpleName());
}
}
反编译Method(了解)
类似反编译Field
十、反射机制调用方法(重点,必须掌握)
要素:
对象、方法名、实参列表、返回值
//不使用反射机制怎么调用方法?
//创建对象
UserService userService = new UserService();
//调用方法
boolean loginSuccess = userService.login("admin","123");
System.out.println(loginSuccess);//true
//使用反射机制来调用一个对象的方法
Class userServiceClass = Class.forName("bean.UserService");
//创建对象
Object obj = userServiceClass.newInstance();
//获取Method
Method loginMethod = userServiceClass.getDeclaredMethod("login",String.class,String.class);
//调用方法
Object retValue = loginMethod.invoke(obj,"admin","123");
System.out.println(retValue);//true
反射机制:让代码具有通用性,可变化的内容都是写到配置文件中,将来修改配置文件之后,创建的对象就不一样了,调用的方法也不同了,但java代码不需要任何改动
十一、反射机制创建对象(重点,掌握)
//不使用反射机制怎么创建对象
Vip v1 = new Vip();
Vip v2 = new Vip(110,"zhangsan","2001-10-11",true);
//使用反射机制
Class c = Class.forName("bean.Vip");
//调用无参数构造方法
Object obj = c.newInstance();
System.out.println(obj);
//调用有参数构造方法
//第一步:现货区这个有参数的构造方法
Constructor con = c.getDeclaredConstructor(int.class,String.class,String.class,boolean.class);
//第二步:调动构造方法new对象
Object newObj = con.newInstance(110,"jackon","1990-10-11",true);
System.out.println(newObj);
十二、获取父类和父接口(重点)
//String举例
Class stringClass = Class.forName("java.lang.String");
//获取String的父类
Class superClass = stringClass.getSuperclass();
System.out.println(superClass.getName());//java.lang.Object
//获取String类实现的所有接口(一个类可以实现多个接口)
Class[] interfaces = stringClass.getInterfaces();
for (Class in : interfaces){
System.out.println(in.getName());
}
十三、注解(Annotation)
是一种引用数据类型,变异之后也是生成xxx.class文件
1.如何自定义注解?语法格式?
[修饰符列表] @interface 注解类型名{
}
2.注解怎么使用,用在什么地方?
注解使用时的语法格式:
@注解类型名
注解可以出现在类上、属性上、方法上、变量上等。。。
注解还可以出现在注解类型上
默认情况下,注解可以出现在任意位置
3.JDK内置了哪些注解?
java.lang包下的注释类型:
【掌握】Deprecated用@Deprecated注释的程序元素,不鼓励使用这样的元素,通常是因为它很危险或存在更好的选择
这个注解标注的元素已过时,向其他程序员传达一个信息,告知已过时,有更好的解决方案存在
【掌握】Override表示一个方法声明打算重写超类中的另一个方法声明
@Override:
只能注解方法
这个注解是给编译器参考的,和运行阶段没有关系
凡是java中的方法带有这个注解的,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器报错
【无须掌握】SuppressWarnings指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告
4.元注解
用来标注“注解类型”的“注解”,是元注解
常见的元注解有哪些?
Target:用来标注标注“被标注的注解”可以出现在哪些位置上–构造方法上、字段上、局部变量上、方法上…类上…
@target(ElenmentType.METHOD):表示“被标注的注解”只能出现在方法上
Retention:用来标注“被标注的注解”可以出现在哪些位置上
public @interface Retention{
//属性
RetentionPolicy value();
}
@Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中
@Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中
@Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中
5.注解定义属性
如果一个注解中有属性,那么必须给属性赋值(除非该属性使用default制定了默认值)
@MyAnnotation(name = "zhangsan")
public void doSome(){
}
如果一个注解的属性的名字是value,且只有一个属性时,使用时,该属性名可以省略
@MyAnnotation("haha")
6.注解中的属性类型
注解中的属性可以是哪一种类型?
byte、short、int、long、float、double、boolean、char
String、Class、枚举类型、以及以上每一种的数组形式**
如果数组中只有一个元素,大括号可以省略
@MyAnnotation(age = 25,email = {"zhangsan@123.com","zhangsan@sohu.com"})
7.反射注解
//获取这个类
Class c = Class.forName("MyAnnotation.MyAnnotationTest");
//判断类上是否有@MyAnnotation
//System.out.println(c.isAnnotationPresent(MyAnnotation.class));//false
if (c.isAnnotationPresent(MyAnnotation.class)){
//获取该注解对象
MyAnnotation myAnnotation = (MyAnnotation)c.getAnnotation(MyAnnotation.class);
System.out.println("类上面的注解对象" + myAnnotation);
//获取注解对象的属性(和调用接口一样)
}
//判断String类上是否存在这个注解
Class stringClass = Class.forName("java.lang.String");
System.out.println(stringClass.isAnnotationPresent(MyAnnotation.class));//false
8.通过反射获取注解对象属性的值
9.注解在开发中有什么用?
需求
当一个类上面有@Id注解时,要求类中必须粗在int类型的id属性
如果没有int类型的id属性则报异常