Surpass Day——Java 反射机制、注解

87 阅读10分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情

08 反射机制

1、关于反射机制

1.1 反射机制有什么用?

​ 通过java语言中的反射机制可以操作字节码文件。 ​ 优点类似于黑客。(可以读和修改字节码文件。) ​ 通过反射机制可以操作代码片段。(class文件。)

反射机制使得对象的创建更加地灵活,可以通过读取配置文件的方式随意改变new的对象,不会将java代码写死

1.2 反射机制的相关类在哪个包下?

java.lang.reflect.*;

1.3 反射机制相关的重要的类有哪些?

​ java.lang.Class:代表整个字节码,代表一个类型,代表整个类。

​ java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。

​ java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法

​ java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。

java.lang.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 int getNo(){
            return no;
        }
    }

1.4 在java中获取Class的三种方式

​ 第一种:

Class c = Class.forName("完整类名");
//这个完整的类名可以通过读取配置文件的方式获取

​ 第二种:

Class c = 对象.getClass();

​ 第三种:

Class c = int.class;
Class c = String.class;

1.5 获取了Class之后,可以调用无参数构造方法来实例化对象

​ //c代表的就是日期Date类型 ​ Class c = Class.forName("java.util.Date");

​ //实例化一个Date日期类型的对象 ​ Object obj = c.newInstance();

​ 一定要注意: ​ newInstance()底层调用的是该类型的无参数构造方法。 ​ 如果没有这个无参数构造方法会出现"实例化"异常。

1.6、如果你只想让一个类的“静态代码块”执行的话,你可以怎么做?

​ Class.forName("该类的类名"); ​ 这样类就加载,类加载的时候,静态代码块执行!!!! ​ 在这里,对该方法的返回值不感兴趣,主要是为了使用“类加载”这个动作。

1.7 关于路径问题

String path = Thread.currentThread().getContextClassLoader()
					 .getResource("写相对路径,但是这个相对路径从src出发开始找").getPath();	

String path = Thread.currentThread().getContextClassLoader()
					.getResource("abc").getPath();	//必须保证src下有abc文件。

String path = Thread.currentThread().getContextClassLoader()
					.getResource("a/db").getPath();	//必须保证src下有a目录,a目录下有db文件。
	
String path = Thread.currentThread().getContextClassLoader()
					 .getResource("com/bjpowernode/test.properties").getPath();	
	//必须保证src下有com目录,com目录下有bjpowernode目录。
	//bjpowernode目录下有test.properties文件。

​ 这种方式是为了获取一个文件的绝对路径。(通用方式,不会受到环境移植的影响。),但是该文件要求放在类路径下,换句话说:也就是放到src下面。

​ src下是类的根路径。

1.8 IO流方式读取配置文件

//获取一个文件的绝对路径
String path = Thread.currrntThread().getContextClassLoader()
    								.getResource("classinfo2.properties").getPath();
FileReader reader = new FileReader(path);
Properties pro = new Properties();
pro.load(reader);
reader.close();
//通过key获取value
String class Name = pro.getProperty("className");
//这里就得到了配置文件中的Name信息,从而可以使用反射机制来new对象
System.out.println(className);

1.9 直接以流的形式返回:

InputStream reader = Thread.currentThread().getContextClassLoader()
							.getResourceAsStream("com/bjpowernode/test.properties");
Properties pro = new Properties();
pro.load(reader);
reader.close();
//通过key获取value
String class Name = pro.getProperty("className");
System.out.println(className);

1.10 IO + Properties,怎么快速绑定属性资源文件

​ //要求:第一这个文件必须在类路径下 ​ //第二这个文件必须是以.properties结尾。

ResourceBundle bundle = ResourceBundle.getBundle("com/bjpowernode/test");
String value = bundle.getString(key);

2、关于JDK中自带的类加载器:

2.1 什么是类加载器?

​ 专门负责加载类的命令/工具。ClassLoader

2.2 JDK中自带了3个类加载器

​ 启动类加载器:rt.jar ​ 扩展类加载器:ext/*.jar ​ 应用类加载器:classpath

2.3 关于三个类加载器

假设有这样一段代码:

	String s = "abc";

代码在开始执行之前,会将所需要类全部加载到JVM当中。 通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载,那么是怎么进行加载的呢?

  • 首先通过“启动类加载器”加载。 注意:启动类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar。 rt.jar中都是JDK最核心的类库。
  • 如果通过“启动类加载器”加载不到的时候,会通过"扩展类加载器"加载。 注意:扩展类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext*.jar
  • 如果“扩展类加载器”没有加载到,那么会通过“应用类加载器”加载。 注意:应用类加载器专门加载:classpath中的类。

2.4 java中为了保证类加载的安全,使用了双亲委派机制。

​ 优先从启动类加载器中加载,这个称为“父”。“父”无法加载到,再从扩展类加载器中加载,这个称为“母”。双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。

3、Field

3.1 通过反射机制获取属性的常用方法

获取到所有public修饰的属性

Field[] fields = 类名.getFields();

获取到属性名

String fieldName = fields[1].getName();//com.bjpowernode.java.bean.Stu
String fieldName = fields[1].getSimpleName();//Stu

获取所有的属性

Field[] fields = 类名.getDeclaredFields();

获取属性的类型

Class fieldType = fields[1].getType();
String fName = fieldType.getName();

获取属性的修饰符列表

int i = fields[1].getModifiers();//返回的是修饰符的代号
String modifierString = Modifier.toString(i);

3.2 通过反射机制反编译一个类的属性

package reflect;
//通过反射机制反编译一个类的属性
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class reflextTest07 {
    public static void main(String[] args) throws ClassNotFoundException {
        StringBuilder stringBuilder = new StringBuilder();//为了拼接字符串

        Class studentClass = Class.forName("reflect.Student");
        Field[] fields = studentClass.getDeclaredFields();

        stringBuilder.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + "{" + "\n");
        for(Field field:fields){
            stringBuilder.append("\t");
            stringBuilder.append(Modifier.toString(field.getModifiers()) + " ");
            stringBuilder.append(field.getType() + " ");
            stringBuilder.append(field.getName());
            stringBuilder.append(";" + "\n");
        }


        stringBuilder.append("}");
        System.out.println(stringBuilder);
    }
}

3.3 通过反射机制访问一个java对象的属性(重点)

给属性赋值set、获取属性的值get

三要素:给s对象的no属性赋值1111 要素1:对象5 要素2:no属性 要素3:1111

我们不使用反射机制,怎么去访问一个对象的属性呢?

//创建对象
Student s = new Student();

//给属性赋值
s.no = 1111;

//读属性值
System.out.println(s.no);

使用反射机制,怎么去访问一个对象的属性。(set get)

//获取类,通过类实例化对象
Class studentClass = Class.forName("com.bjpowernode.java.bean.Student");
Object obj=studentClass.newInstance();// obj就是student对象。(底层调用无参数构造方法)

//获取no属性(根据属性的名称来获取Field)
Field noFiled = studentClass.getDeclaredField("no");

// 给obj对象(Student对象)的no属性赋值
noFiled.set(obj,22222);//给obj对象的no属性赋值2222

//读取属性的值
Object obj = noFiled.get(obj);

**出现的问题:**私有属性不可以通过上述访问进行赋值访问

**解决方案:**打破封装(缺点),这样设置之后,在外部也可以访问私有属性

Field noFiled = studentClass.getDeclaredField("no");
noField.setAccessible(true);

4、可变长度参数

int...args这就是可变长度参数 语法是:类型...(注意:一定是3个点。)

1、可变长度参数要求的参数个数是:0~N个。 2、可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个。 3、可变长度参数可以当中一个数组来看待

m2(100);
m2(200, "abe");
m2(200, "abc", "def");
m2(200, "abe", "def", "xyz");
m3("ab","de","kk","ff");
String[] strs = {"a","b","c"};
//也可以传1个数组
m3(strs);
m3(new String[]{“我","","","",“人"});


public static void m(int... angs){
	System.out.println("m方法执行了!");
}

//必须在最后,只能有1个。
public static void m2(int a, String... args1){
    
}

public static void m3(String.…. args)
	//args有ength属性,说明args是一个数组!
	//可以将可变长度参数当做一个数组来看。
	for(int i = 0; i < args.length; i++){
		System.out.println(args[i]);
    }
}

5、Method

5.1 通过反射机制获取类的Method的常用方法、

获取所有的Method(包括私有的!)

Method[] methods = userServiceClass.getDeclaredMethods();

获取修饰符列表

Modifier.toString(method.getModifiers());

获取方法的返回值类型

method.getReturnType().getSimpleName();

获取方法名

method.getName();

方法的修饰符列表(一个方法的参数可能会有多个。

Class[] parameterTypes = method.getParameterTypes();
//获取类
Class userServiceClass = Class.forName("com.UserService");
//获取所有的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.printIn(method.getReturnType().getSimpleName());
    //获取方法名
    System.out.println(method.getName());
    //方法的修饰符列表(一个方法的参数可能会有多个。)
	Class[] parameterTypes = method.getParameterTypes();
    for(Class parameterType : parameterTypes){
		System.out.println(parameterType.getSimpleName());
    }
}

5.2 通过反射机制访问一个java对象的属性(重点)

不使用反射机制,怎么调用方法

//创建对象
UserService userService = new UserService();
//调用方法
boolean loginSuccess = userService.login( name: "admin", password: "123");
//System.out.printLn(LoginSuccess);
System.out.println(loginSuccess?“登录成功":"登录失数");

使用反射机制来调用一个对象的方法该怎么做?

Class userServiceClass = Class.forName("com.bjpowernode.java.service.UserService");
// 创建对象
Object obj = userServiceClass.newInstance();
// 获取Method(区分Method的有参数名和参数列表)
Method loginMethod = userServiceClass.getDeclaredMethod( "login", String.class);
Method loginMethod2 = userServiceClass.getDeclaredMethod("login", int.class);

//调用方法有几个要素?也需要4要素。
//反射机制中最最最最最重要的一个方法,必须记住。                                 
//四要素:LoginMethod方法
//LoginMethod方法
//obj对象
//"odmin","123”实参
//retVal ue 返回值
obj invoke loginMethod方法(“admin”,“123”)
    
//调用方法
Object retValue = loginMethod.invoke(obj,"admin","123");

6、Construct

6.1 Construct常用的方法

获取无参构造方法

Construct[] constructors = 类名.getDeclaredConstructors();
String s = Modifier.toString(constructor.getModifiers());

6.2 通过反射机制new对象

不使用反射机制怎么创建对象

Vip v1 = new Vip();
Vip v2 = new Vip( 10, "zhangsan", "2001-10-11", true);

使用反射机制怎么创建对象呢?

Class c = Class.forName("com.bjpowernode.java.bean.Vip");

//调用无参数构造方法
Object obj = c.newInstance();//jdk8
System.out.println(obj);
//获取无参数构造方法
Constructor con2 = c.getDeclaredConstructor();//jdk13
Object newObj2 = con2.newInstance();

//调用有参数的构造方法怎么办?
//第一步:先获取到这个有参数的构造方法
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);

7、获取类的父类和接口

获取String的父类

Class superClass = 类名.getSuperclass();
System.out.println(superClass.getName());

获取所有的接口

//获取String类实现的所有接口(一个类可以实现多个接口。)
Class[] interfaces = stringClass.getInterfaces();
for(Class in:interfaces){
	System.out.println(in.getName());
}

09 注解

1、关于注解

1)注解,或者叫做注释类型,英文单词是:Annotation

2)注解Annotation是一种引用数据类型。编译之后也是生成xxx.class文件。

3)怎么自定义注解呢?语法格式?

 [修饰符列表] @interface 注解类型名{
 
}

4)注解怎么使用,用在什么地方?

​ 第一:注解使用时的语法格式是:@注解类型名 ​ ​ 第二:注解可以出现在类上、属性上、方法上、变量上、注解类型

2、JDK内置了哪些注解呢?

​ java.lang包下的注释类型:

​ 掌握: ​ Deprecated 用 @Deprecated 注释的程序元素, ​ 不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。

​ 掌握: ​ Override 表示一个方法声明打算重写超类中的另一个方法声明。

​ 不用掌握: ​ SuppressWarnings 指示应该在注释元素(以及包含在该注释元素中的 ​ 所有程序元素)中取消显示指定的编译器警告。

3、元注解

1)什么是元注解? 用来标注“注解类型”的“注解”,称为元注解。

2)常见的元注解有哪些? Target、Retention

3)关于Target注解: 这是一个元注解,用来标注“注解类型”的“注解” 这个Target注解用来标注“被标注的注解”可以出现在哪些位置上。

@Target(ElementType.METHOD)//表示“被标注的注解”只能出现在方法上。
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})//表示该注解可以出现在:构造方法上、字段上、局部变量上、方法上、....类上...

​ 4)关于Retention注解: ​ 这是一个元注解,用来标注“注解类型”的“注解” ​ 这个Retention注解用来标注“被标注的注解”最终保存在哪里。

@Retention(RetentionPolicy.SOURCE)//表示该注解只被保留在java源文件中。
@Retention(RetentionPolicy.CLASS)//表示该注解被保存在class文件中。
@Retention(RetentionPolicy.RUNTIME)//表示该注解被保存在class文件中,并且可以被反射机制所读取。

4、Retention的源代码

//元注解	
public @interface Retention {
	//属性
	RetentionPolicy value();
}
		
RetentionPolicy的源代码:
public enum RetentionPolicy {
	SOURCE,
	CLASS,
	RUNTIME
}

//@Retention(value=RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation{}

5、注解在开发中有什么用呢?

​ 需求: ​ 假设有这样一个注解,叫做:@Id,这个注解只能出现在类上面,当这个类上有这个注解的时候,要求这个类中必须有一个int类型的id属性。如果没有这个属性就报异常。如果有这个属性则正常执行!