反射、泛型和注解
一、反射的概述
1.1 概念
不同于通过类new对象,反射是先得到对象,再由对象获取到类及该类的对象,调用该类中的成员属性和成员方法。让代码动态化的基础,有了反射可以做到需要对象的时候才创建对象。
1.2 过程
先通过反射获取字节码对象(Class类的对象),通过字节码对象里面的方法获取成员属性、成员方法和构造方法。
二、反射操作
通过反射操作某个类中的属性和方法,要先获取该类的字节码对象。
2.1 获取字节码对象
方式1:通过Object类中的getClass()
//获取Person类字节码对象,用Object类中定义的getClass();不推荐
Person p1 = new Person();
Class<?> clazz = p1.getClass();
方式2:通过类名调用静态属性class获取
//获取Person类字节码对象,不推荐
Class<?> clazz = Person.class;
方式3:通过Class类中定义的静态方法forName()
//获取Person类字节码对象,推荐方式
Class<?> clazz = Class.forName("com.woniuxy.demo.Person");
2.2 方法解析
getConstructor(Class<?>…parameterType):获取公有的构造方法,参数为可变参数
- getDeclaredConstructor(Class<?>…parameterType):获取所有的构造方法,一般只有在获取私有构造方法的时候才使用。
- geteMethod(String name,Class<?>…parameterType):获取公有的方法,参数为方法名和可变参数
- getDeclaredMethod(String name,Class<?>…parameterType):获取所有的方法,一般只有在获取私有方法的时候才使用。
- getField(String name):获取公有的属性,参数为属性名称。
getDeclaredField(String name):获取所有的属性,一般只是在获取私有属性的时候使用。
2.3 获取构造方法(目的是创建对象)
获取无参的公有方法 ```java Class<?> clazz = Class.forName(className);
//调用getConstructor()方法获取构造方法的对象 //有可变参数:parameterType:表示的是形式参数的数据类型的字节码文件,如果是无参,传递null Constructor<?> c = clazz.getConstructor(null);
//调用newInstance方法获取Person类的对象 //参数:initargs:实际参数,数据类型要和上面的一致 Person person = (Person)c.newInstance(null); System.out.println(person);
- 获取私有的构造方法
```java
//获取字节码文件
Class<?> clazz = Class.forName(className);
//获取构造方法对象,包装类和基本数据类型不一样。
Constructor<?> c = clazz.getDeclaredConstructor(int.class,String.class);
//暴力破解私有
c.setAccessible(true);
//调用newInstance方法创建对象
Person person = (Person)c.newInstance(19,"张三");
System.out.println(person);
2.3 用反射操作方法
- 公有的方法 ```java //获取字节码对象 Class<?> clazz = Class.forName(className);
/*
- 调用getMethod(),获取方法的对象
- String name:方法的名称
- Class<?>…parameterType:方法中参数数据类型的字节码对象 */ Method m = clazz.getMethod(“eat”, null);
/*调用invoke()方法执行eat方法
- obj:用哪个对象调用该方法
- args:方法的参数,没有写null */ Object obj = m.invoke(clazz.getConstructor(null).newInstance(null), null); System.out.println(obj); ```
私有的成员方法
//获取字节码对象
Class<?> clazz = Class.forName(className);
Method m = clazz.getDeclaredMethod("sleep", null);
m.setAccessible(true);
m.invoke(clazz.getConstructor(null).newInstance(null), null);
2.4 反射操作字段
公有的属性 ```java //获取 字节码对象 Class<?> clazz = Class.forName(className);
//通过字节码对象获取属性的对象 //参数:String name,属性名称 Field field = clazz.getField(“age”);
//通过调用get方法,获取该字段的值 //参数:obj:表是哪个对象调用该字段 Object obj = field.get(clazz.getConstructor(null).newInstance(null)); System.out.println(obj);
- 私有的属性
```java
//获取字节码对象
Class<?> clazz = Class.forName(className);
//获取属性对象
Field field = clazz.getDeclaredField("name");
//破解私有
field.setAccessible(true);
//调用get方法获取
Object obj = field.get(clazz.getConstructor(null).newInstance(null));
System.out.println(obj);
三、泛型
3.1 泛型的概念
- 概念:在集合中使用过,作用是用来约束规范集合中存入元素的数据类型,往广泛了定义,用来在不知道数据类型的时候,用一个泛型的字母来进行代替,当传入真实的数据类型时候,将其替换。
- 泛型存在:自定义泛型方法和自定义泛型类。
举例:
public class Demo1 {
public static void main(String[] args) {
Demo1 d1 = new Demo1();
//定义数组
Integer[] num = {1,2,3,4,5,6};
String[] str = {"a","b","c","d","e"};
//调用交换的方法
d1.change(num,1,3);
//遍历数组
d1.print(num);
System.out.println();
//字符串交换
d1.change(str, 1, 3);
//输出字符串数组
d1.print(str);
}
//定义一个方法,实现数组中任意两个元素的交换
public void change(Integer[] num,int start,int end) {
int temp = num[start];
num[start] = num[end];
num[end] = temp;
}
public void change(String[] str,int start,int end) {
String temp = str[start];
str[start] = str[end];
str[end] = temp;
}
//遍历数组
public void print(Integer[] num) {
for(Integer i:num) {
System.out.print(i+",");
}
}
public void print(String[] str) {
for(String i:str) {
System.out.print(i+",");
}
}
}
总结问题:同样是交换数组中的任意两个元素,因为数据类型的不一样,多写了两个相同功能的方法,有了泛型的概念,可以只用一个方法来实现任意数组中任意两个元素的交换。
3.2 自定义泛型方法
语法 ```java public
返回值数据类型 方法名(T t){ //其中T代表数据类型,t表示对象或者变量
}
- 将上面的方法进行改造
```java
public class Demo1 {
public static void main(String[] args) {
Demo1 d1 = new Demo1();
//定义数组
Integer[] num = {1,2,3,4,5,6};
String[] str = {"a","b","c","d","e"};
//交换整形
d1.change(num, 1, 3);
d1.print(num);
System.out.println();
//交换字符串
d1.change(str, 1, 3);
d1.print(str);
}
//定义 一个泛型方法,可以适用于所有数据类型的数组任意两个元素的交换
public <T> void change(T[] t,int start,int end) {
//T:表示数据类型,t表示数组名
T temp = t[start];
t[start] = t[end];
t[end] = temp;
}
public <T> void print(T[] t) {
for(T i : t) {
System.out.print(i+",");
}
}
}
3.3 自定义泛型类
- 语法:
```java
//如果一个类中存在多个泛型方法,那么可以将方法上面的泛型声明,提高到类层面,在类上面进行泛型声明,该类中的方法都
能够使用该泛型
public class 类名
{
}
- 注意:只有普通成员方法才能使用类上面的泛型,静态方法只能在静态方法上面定义泛型。
<a name="xhOkM"></a>
## 3.4 泛型的通配符及边界
- ? :表示在泛型中的通配符,还不知道是什么数据,则使用?来进行填充。
- 上边界:<? extends Object>,如果用了上边界,那么Object作为上面的边界,该泛型中能传入Object本类及其子类。
- 下边界:<? super Object>,如果用了下边界,那么Object作为下边界,该泛型中只能存入Object的本类及其父类或者接口。
<a name="8rdxn"></a>
# 四、注解
概念:以@来头的一些关键字,称为注解。
<a name="xfQ3k"></a>
## 4.1 定义注解
- 关键字:@interface
- 定义:实际本身如果注解的情况下是没有意义的,必须配合上元注解
```java
public @interface 注解名称{
注解中的属性;可以给属性赋值
}
- 以前见过的注解:
@override:重写注解
@Test:junit单元测试注解
4.2 元注解
元注解表示在jdk中已经定义好的注解,专门用来给注解使用的注解。
- 1、Retention:用来规定该注解作用的时机,一般使用RUNTIME表示在运行时起作用,CLASS字节码文件、RESOURCE源码。
- 2、Target:用来规定该注解使用的位置,位置包括:类(接口)、包、方法、属性、局部变量、方法中的形式参数。
对应:
在类或者接口上使用:TYPE
在包上使用:PACKAGE
在方法上使用:METHOD
在属性上使用:FIELD
在形式参数使用:PARAMETER
在局部变量使用:LOCAL_VARIABLE
- 3、Document:表示在生成javadoc文档的时候使用。
- 4、Inherited:表示注解的继承关系。
- 定义一个有效注解:
@Retention(RetentionPolicy.RUNTIME)//设置该注解的作用位置是在运行时起作用
@Target(ElementType.PARAMETER)//说明该注解只能在类或者接口上使用
public @interface 注解名称{
注解中的属性;可以给属性赋值
}