文章目录
- 一、反射机制
-
- 1.1 基本概念
- 1.2 获取字节码文件
- 1.3 路径问题
- 1.4 资源绑定器
- 1.5 Field相关操作
-
- 1.5.1 获取Filed
- 1.5.2 反编译Field
- 1.5.3 通过反射机制访问对象属性(重点)
- 1.6 可变长参数
- 1.6 Method相关操作
-
- 1.6.1 反射Method
- 1.6.2 反编译Method
- 1.6.3 通过反射机制调用一个对象的方法(重点)
- 1.7 Constructor相关操作
-
- 1.7.1 反编译Constructor
- 1.7.2 反射机制调用构造方法
- 1.8 获取父类和父接口
- 1.9 类加载器
- 二、注解
-
- 2.1 基础
- 2.2 注解中的属性
- 2.3 反射注解
- 2.4 注解在开发中的作用
一、反射机制
1.1 基本概念
1 通过反射机制可以操作字节码文件(.class文件),虽然代码变复杂了,但是很灵活。
2 包:java.lang.reflect.*
- 重要类
1 java.lang.Class:代表整个字节码。
2 java.lang.reflect.Method:代表字节码中的方法字节码。
3 java.lang.reflect.Constructor:代表字节码中的构造方法字节码。
4 java.lang.reflect.Field:代表字节码中的属性字节码(静态变量+实例变量)。
1.2 获取字节码文件
- 三种方法
1 Class c = Class.forName(“完整类名带包名”);
该方法是一个静态方法,参数是一个字符串,这个字符串要是一个完整类名。
2 Class c = 对象.getClass();
java中每一个对象都有一个getClass()方法。
3 Class c = 任何类型.class;
java语言中任何一种类型,包括基本数据类型,它都有.class属性。
- 获取字节码的用处
1 获取到字节码后,可以通过Class的newInstance()方法来实例化对象。这样就可以做到在不改变代码的情况下,做到不同对象的实例化。
2 一些高级框架比如ssh,ssm的底层实现原理就是反射机制。
3 注意:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
- 只执行静态代码块
1 如果你只是希望一个类的静态代码块执行,其它代码一律不执行,你可以使用:Class.forName(“完整类名”);
2 这个方法的执行会导致类加载,类加载时,静态代码块执行。
3 后面JDBC技术的时候我们会使用到这个。
- 示例
1 以下代码是灵活的,代码不需要改动,可以修改配置文件,配置文件修改之后,可以创建出不同的实例对象。
// 创建classinfo.properties配置文件。之后想要实例化其他对象,只需修改此文件即可。
className=java.lang.String
import java.io.FileReader;
import java.util.Properties;
public class ReflectTest02 {
public static void main(String[] args) throws Exception {
// 通过IO流读取classinfo.properties文件
FileReader reader = new FileReader("chapter05/src/classinfo.properties");
// 创建属性类对象Map
Properties pro = new Properties();
// 加载
pro.load(reader);
// 关闭流
reader.close();
// 通过key获取value
String className = pro.getProperty("className");
// 通过反射机制实例化(创建)对象
Class c = Class.forName(className);
Object obj = c.newInstance();
System.out.println(obj);
}
}
1.3 路径问题
1 对于路径会存在这样一个问题,把程序换到另一个地方进行运行时,有时候路径就无效了。
2 有一种通用的方式,但是这种方式需要文件再类路径下,类路径就是src文件。
- 示例
1 Thread.currentThread() 当前线程对象
2 getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象。
3 getResource() 【获取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。
4 Thread.currentThread().getContextClassLoader().getResource(“classinfo.properties”) 直接以流(InputStream)的形式返回。
//创建一个classinfo.properties文件在src下。
public class AboutPath {
public static void main(String[] args) {
// 获取绝对路径,该文件需要在src文件下。
String path = Thread.currentThread().getContextClassLoader()
.getResource("classinfo.properties").getPath();
System.out.println(path);
}
}
1.4 资源绑定器
1 java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。
2 资源绑定器,只能绑定xxx.properties文件。并且这个文件必须在类路径下。文件扩展名也必须是properties,并且在写路径的时候,路径后面的扩展名不能写。
- 示例
import java.util.ResourceBundle;
public class ResourceBoundleTest {
public static void main(String[] args) {
ResourceBundle bundle = ResourceBundle.getBundle("classinfo");
String className = bundle.getString("className");
System.out.println(className);
}
}
1.5 Field相关操作
只要看懂重点即可。
1.5.1 获取Filed
Class studentClass = Class.forName(“com.malidong.reflect.Student”);
1获取简单类名
String simpleName = studentClass.getSimpleName();
2 获取完整类名
String className = studentClass.getName();
3 获取类中所有的public修饰的Field
Field[] fields = studentClass.getFields();
4 获取所有的Field
Field[] fs = studentClass.getDeclaredFields();
5 获取属性的修饰符列表
int i = field.getModifiers(); // 返回的修饰符是一个数字,每个数字是修饰符的代号!!!
6 可以将这个“代号”数字转换成“字符串”吗?
String modifierString = Modifier.toString(i);
7 获取属性的类型
Class fieldType = field.getType();
String fName = fieldType.getName();
String fName = fieldType.getSimpleName();
field.getName()
1.5.2 反编译Field
通过反射机制,反编译一个类的属性Field。就是把类的属性全部打印出来。
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ReflectTest05 {
public static void main(String[] args) throws Exception{
// 创建这个是为了拼接字符串。
StringBuilder s = new StringBuilder();
Class studentClass = Class.forName("java.lang.Thread");
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);
}
}
1.5.3 通过反射机制访问对象属性(重点)
给属性赋值set,获取属性的值get。
将框架采用,很少使用。
import java.lang.reflect.Field;
public class ReflectTest06 {
public static void main(String[] args) throws Exception{
// 我们不使用反射机制,怎么去访问一个对象的属性呢?
Student s = new Student();
// 给属性赋值
s.no = 1111;
// 读属性值
System.out.println(s.no);
// 使用反射机制,怎么去访问一个对象的属性。(set get)
Class studentClass = Class.forName("com.malidong.reflect.Student");
Object obj = studentClass.newInstance(); // obj就是Student对象。(底层调用无参数构造方法)
Field noFiled = studentClass.getDeclaredField("no"); // 获取no属性(根据属性的名称来获取Field)
noFiled.set(obj, 22222); // 给obj对象的no属性赋值2222
// 读取属性的值
System.out.println(noFiled.get(obj));
// 可以访问私有的属性吗?
Field nameField = studentClass.getDeclaredField("name");
// 打破封装(反射机制的缺点:打破封装,可能会给不法分子留下机会!!!)
// 这样设置完之后,在外部也是可以访问private的。
nameField.setAccessible(true);
// 给name属性赋值
nameField.set(obj, "jackson");
// 获取name属性的值
System.out.println(nameField.get(obj));
}
}
1.6 可变长参数
1 可变长度参数要求的参数个数是:0~N个。
2 可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个。
3 可变长度参数可以当做一个数组来看待。
- 示例
m();
m(10);
m(10, 20);
public static void m(int... args){
System.out.println("m方法执行了!");
}
1.6 Method相关操作
只要看懂重点即可。
1.6.1 反射Method
1 获取类了
Class userServiceClass = Class.forName(“com.malidong.reflect.UserService”);
2 获取所有的Method(包括私有的!)
Method[] methods = userServiceClass.getDeclaredMethods();
3 获取修饰符列表
Modifier.toString(method.getModifiers())
4 获取方法的返回值类型
method.getReturnType().getSimpleName()
5 获取方法名
method.getName()
6 方法的修饰符列表(一个方法的参数可能会有多个。)
Class[] parameterTypes = method.getParameterTypes();
for(Class parameterType : parameterTypes){
System.out.println(parameterType.getSimpleName());
}
1.6.2 反编译Method
只能返回方法名,不能返回方法里的内容。反编译就是利用编译把方法打出来。
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class ReflectTest08 {
public static void main(String[] args) throws Exception{
StringBuilder s = new StringBuilder();
Class userServiceClass = Class.forName("java.lang.String");
s.append(Modifier.toString(userServiceClass.getModifiers()) + " class "+userServiceClass.getSimpleName()+" {\n");
Method[] methods = userServiceClass.getDeclaredMethods();
for(Method method : methods){
s.append("\t");
s.append(Modifier.toString(method.getModifiers()));
s.append(" ");
s.append(method.getReturnType().getSimpleName());
s.append(" ");
s.append(method.getName());
s.append("(");
// 参数列表
Class[] parameterTypes = method.getParameterTypes();
for(Class parameterType : parameterTypes){
s.append(parameterType.getSimpleName());
s.append(",");
}
// 删除指定下标位置上的字符
s.deleteCharAt(s.length() - 1);
s.append("){}\n");
}
s.append("}");
System.out.println(s);
}
}
1.6.3 通过反射机制调用一个对象的方法(重点)
反射机制,让代码很具有通用性,可变化的内容都是写到配置文件当中,将来修改配置文件之后,创建的对象不一样了,调用的方法也不同了,但是java代码不需要做任何改动。这就是反射机制的魅力。
import java.lang.reflect.Method;
public class ReflectTest09 {
public static void main(String[] args) throws Exception{
// 不使用反射机制,怎么调用方法
// 创建对象
UserService userService = new UserService();
// 调用方法
boolean loginSuccess = userService.login("admin","123");
System.out.println(loginSuccess ? "登录成功" : "登录失败");
// 使用反射机制来调用一个对象的方法该怎么做?
Class userServiceClass = Class.forName("com.malidong.reflect.UserService");
// 创建对象
Object obj = userServiceClass.newInstance();
// 获取Method
Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
/*
四要素:
loginMethod方法
obj对象
"admin","123" 实参
retValue 返回值
*/
Object retValue = loginMethod.invoke(obj, "admin","123123");
System.out.println(retValue);
}
}
1.7 Constructor相关操作
只要看懂重点即可。
1.7.1 反编译Constructor
反编译一个类的Constructor构造方法。操作class文件,把所有的构造方法打印出来。
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
public class ReflectTest10 {
public static void main(String[] args) throws Exception{
StringBuilder s = new StringBuilder();
Class vipClass = Class.forName("java.lang.String");
s.append(Modifier.toString(vipClass.getModifiers()));
s.append(" class ");
s.append(vipClass.getSimpleName());
s.append("{\n");
// 拼接构造方法
Constructor[] constructors = vipClass.getDeclaredConstructors();
for(Constructor constructor : constructors){
//public Vip(int no, String name, String birth, boolean sex) {
s.append("\t");
s.append(Modifier.toString(constructor.getModifiers()));
s.append(" ");
s.append(vipClass.getSimpleName());
s.append("(");
// 拼接参数
Class[] parameterTypes = constructor.getParameterTypes();
for(Class parameterType : parameterTypes){
s.append(parameterType.getSimpleName());
s.append(",");
}
// 删除最后下标位置上的字符
if(parameterTypes.length > 0){
s.deleteCharAt(s.length() - 1);
}
s.append("){}\n");
}
s.append("}");
System.out.println(s);
}
}
1.7.2 反射机制调用构造方法
通过反射机制调用构造方法实例化java对象。
import java.lang.reflect.Constructor;
public class ReflectTest11 {
public static void main(String[] args) throws Exception{
// 不使用反射机制怎么创建对象
Vip v1 = new Vip();
Vip v2 = new Vip(110, "zhangsan", "2001-10-11", true);
// 使用反射机制怎么创建对象呢?
Class c = Class.forName("com.malidong.reflect.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, "jackson", "1990-10-11", true);
System.out.println(newObj);
// 获取无参数构造方法
Constructor con2 = c.getDeclaredConstructor();
Object newObj2 = con2.newInstance();
System.out.println(newObj2);
}
}
1.8 获取父类和父接口
public class ReflectTest12 {
public static void main(String[] args) throws Exception{
// String举例
Class stringClass = Class.forName("java.lang.String");
// 获取String的父类
Class superClass = stringClass.getSuperclass();
System.out.println(superClass.getName());
// 获取String类实现的所有接口(一个类可以实现多个接口。)
Class[] interfaces = stringClass.getInterfaces();
for(Class in : interfaces){
System.out.println(in.getName());
}
}
}
1.9 类加载器
专门负责加载类的命令/工具。
JDK自带有3个类加载器
1启动类加载器:rt.jar
2 扩展类加载器:ext/*.jar
3 应用类加载器:classpath
假设有这样一段代码:String s = “abc”;
1 首先通过“启动类加载器”加载。注意:启动类加载器专门加载:C:\ProgramFiles\Java\jdk1.8.0_101\jre\lib\rt.jar。rt.jar中都是JDK最核心的类库。
2 如果通过“启动类加载器”加载不到的时候,会通过"扩展类加载器"加载。注意:扩展类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext*.jar。
3 如果“扩展类加载器”没有加载到,那么会通过“应用类加载器”加载。注意:应用类加载器专门加载:classpath中的类。
- 双亲委派机制
1 java中为了保证类加载的安全,使用了双亲委派机制。
2 优先从启动类加载器中加载,这个称为“父”,“父”无法加载到,再从扩展类加载器中加载,这个称为“母”。双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。
二、注解
2.1 基础
[修饰符列表] @interface 注解类型名{
}
1 注解又叫做注释,是一种引用数据类型,编译后也是生成.class文件。
2 注解使用时的语法格式是:@注解类型名
3 注解可以出现在类上,属性上,方法上,变量上,等等。注解还可以出现在注解类型上。
- jdk内置注解
java.lang包下一共有三个注解类型:Deprecated,Override,SuppressWarnings。重点掌握前两个。
Override:
1 只能注解方法,是给编译器参考的,和运行阶段没有关系。
2 凡是java中的方法带有这个注解的,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器报错,就是@override下有红曲线。
Deprecated:
1 用 @Deprecated 注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。
2 表示注解的东西已过时。调用的话,会直接被横线划掉。
- 元注解
1 元注解是用来标注“注解类型”的“注解”。常用的有Target,Retention。
Target:
1 用来标注“被标注的注解”可以出现在哪些位置上。
2 @Target(ElementType.METHOD):表示“被标注的注解”只能出现在方法上。
3 @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
Retention:
1 用来标注“被标注的注解”最终保存在哪里。
2 Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中。
3 @Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中。
4 @Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取。
2.2 注解中的属性
1 如果一个注解当中有属性,那么必须给属性赋值。(除非该属性使用default指定了默认值。)
2 如果属性名是value,并且只有一个属性,名字可以省略。@MyAnnotation(“zhangsan”)
3 属性的类型可以是: byte short int long float double boolean char String Class 枚举类型以及以上每一种的数组形式。
public @interface MyAnnotation {
String name();
/*
年龄属性
*/
int age() default 25; //属性指定默认值
}
public class MyAnnotationTest {
//@MyAnnotation(属性名=属性值)
@MyAnnotation(name = "zhangsan")
public void doSome(){
}
}
2.3 反射注解
读取注解的class文件,如果能被反射,则需要是@Retention(RetentionPolicy.RUNTIME):
public class ReflectAnnotationTest {
public static void main(String[] args) throws Exception{
// 获取这个类
Class c = Class.forName("com.bjpowernode.java.annotation5.MyAnnotationTest");
// 判断类上面是否有@MyAnnotation
//System.out.println(c.isAnnotationPresent(MyAnnotation.class)); // true
if(c.isAnnotationPresent(MyAnnotation.class)){
// 获取该注解对象
MyAnnotation myAnnotation = (MyAnnotation)c.getAnnotation(MyAnnotation.class);
//System.out.println("类上面的注解对象" + myAnnotation); // @com.bjpowernode.java.annotation5.MyAnnotation()
// 获取注解对象的属性怎么办?和调接口没区别。
String value = myAnnotation.value();
System.out.println(value);
}
// 判断String类上面是否存在这个注解
Class stringClass = Class.forName("java.lang.String");
System.out.println(stringClass.isAnnotationPresent(MyAnnotation.class)); // false
}
}
- 通过反射获取方法上的注解信息
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
/*
username属性
*/
String username();
/*
password属性
*/
String password();
}
import java.lang.reflect.Method;
public class MyAnnotationTest {
@MyAnnotation(username = "admin", password = "456456")
public void doSome(){
}
public static void main(String[] args) throws Exception{
// 获取MyAnnotationTest的doSome()方法上面的注解信息。
Class c = Class.forName("com.bjpowernode.java.annotation6.MyAnnotationTest");
// 获取doSome()方法
Method doSomeMethod = c.getDeclaredMethod("doSome");
// 判断该方法上是否存在这个注解
if(doSomeMethod.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation myAnnotation = doSomeMethod.getAnnotation(MyAnnotation.class);
System.out.println(myAnnotation.username());
System.out.println(myAnnotation.password());
}
}
}
2.4 注解在开发中的作用
1 假设有这样一个注解,叫做:@Id
2 这个注解只能出现在类上面,当这个类上有这个注解的时候,要求这个类中必须有一个int类型的id属性。如果没有这个属性就报异常。如果有这个属性则正常执行!
/*
自定义异常
*/
public class HasNotIdPropertyException extends RuntimeException {
public HasNotIdPropertyException(){
}
public HasNotIdPropertyException(String s){
super(s);
}
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 表示这个注解只能出现在类上面
@Target(ElementType.TYPE)
// 该注解可以被反射机制读取到
@Retention(RetentionPolicy.RUNTIME)
public @interface MustHasIdPropertyAnnotation {
}
// 这个注解@Id用来标注类,被标注的类中必须有一个int类型的id属性,没有就报异常。
@MustHasIdPropertyAnnotation
public class User {
int id;
String name;
String password;
}
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception{
// 获取类
Class userClass = Class.forName("com.bjpowernode.java.annotation7.User");
// 判断类上是否存在Id注解
if(userClass.isAnnotationPresent(MustHasIdPropertyAnnotation.class)){
// 当一个类上面有@MustHasIdPropertyAnnotation注解的时候,要求类中必须存在int类型的id属性
// 如果没有int类型的id属性则报异常。
// 获取类的属性
Field[] fields = userClass.getDeclaredFields();
boolean isOk = false; // 给一个默认的标记
for(Field field : fields){
if("id".equals(field.getName()) && "int".equals(field.getType().getSimpleName())){
// 表示这个类是合法的类。有@Id注解,则这个类中必须有int类型的id
isOk = true; // 表示合法
break;
}
}
// 判断是否合法
if(!isOk){
throw new HasNotIdPropertyException("被@MustHasIdPropertyAnnotation注解标注的类中必须要有一个int类型的id属性!");
}
}
}
}