一.反射
1.概念
反射:指我们的程序可以通过获取Class对象,去"反向"的加载"一个类",并创建一个对象,以及调用它的属性、方法。
作用:解开类和类之间的"耦合关系",利于程序的开发、后期维护!!!
2.类加载器
1.类的加载:
1).当我们的程序第一次使用某个类(创建对象、访问它的静态成员…)(除常量)时,
JVM会到硬盘上读取这个类的class文件。
2).Java对于任何在程序运行过程中使用过的类,都会将它们的类信息存储到"方法区",
都会为其创建一个、而且只创建一个Class对象。
例如:Student类、System类、Scanner类、Random类、ArrayList类…
这就是为什么我们之前学习"静态同步方法",它的锁对象是本类的Class对象。
public synchronized static void show(){
}
3).当JVM加载我们的class文件时,也是用一个"类"去加载,这种类在JVM中被称为:类加载器:
2.Java中有三种类加载器:
1).启动类加载器(Bootstrap ClassLoader):用于加载系统类库<JAVA_HOME>\bin目录下的class,例如:rt.jar。(例如:System类、Random类、ArrayList类)
2).扩展类加载器(Extension ClassLoader):用于加载扩展类库<JAVA_HOME>\lib\ext目录下的class。
3).应用程序类加载器(Application ClassLoader):用于加载我们自定义的加载器(例如:Student类、Phone类…)
类JVM加载一个类,使用"双亲委托模型":
当我们要加载一个类:
例如:Student类–>JVM–>应用程序类加载器–>委托给"扩展类加载器"–>委托给"启动类加载器",发现不能加载 应用程序类加载器 <–“扩展类加载器”<–
3.获取Class的对象三种方式,反射的前提
1).Object类:getClass()【适合获取类库的类的Class】
2).任何数据类型(包括基本数据类型):数据类型.class【适合获取类库的类的Class】
3).Class的静态方法forName(String 全类名):Class.forName(String 全类名)【常用-适合获取我们自定义类的Class对象】
注意:上述三种方式,都会先判断是否有Class对象,如果没有,会读取class文件,然后在方法区中创建它的Class对象。
4.反射获取构造方法(,通过Class对象获取),创建对象
1).批量获取:[了解]
1).public Constructor[] getConstructors():获取所有的"公有构造方法"
会将每个公有构造方法封装为一个Constructor对象,多个Constructor对象封装到一个数组中返回。
2).public Constructor[] getDeclaredConstructors():获取所有的构造方法,包括:公有、受保护、默认、私有。
2).获取单个:[掌握]
3).public Constructor getConstructor(Class ... parameterTypes):获取某个"公有构造方法"
4).public Constructor getDeclaredConstructor(Class ... parameterTypes):获取任何的构造方法。
调用这个类的构造方法:
Constructor(代表一个构造方法)-->newInstance(实参)
如果调用此构造方法,没有"权限(访问权限修饰符的限制)",要先设置:暴力访问
如果有访问权限,也可以设置"暴力访问"
Constructor-->setAccessible(true)
tip:
如果类的无参构造是公有的,可以直接使用 class对象.newInstance
5.反射获取属性(,通过Class对象获取)
1).批量获取:[了解]
1).public Field[] getFields():获取所有的"公有成员属性"
会将每个公有成员属性封装为一个Field对象,多个Field对象封装到一个数组中返回。
2).public Field[] getDeclaredFields():获取所有的成员属性,包括:公有、受保护、默认、私有。
2).获取单个:[掌握]
3).public Field getField(String fieldName):通过"属性名",获取某个"公有成员属性"
4).public Field getDeclaredField(String fieldName):通过属性名,获取任何访问权限的成员属性。
注意:
1).要为属性赋值,必须先创建此类对象;
2).赋值:Field-->set(Object targetObj,Object value):
如果没有访问权限,设置暴力访问
Field-->setAccessible(true)
3).获取值:Field-->get(Object targetObj)
6.反射获取属性(,通过Class对象获取)
1).批量获取:[了解]
1).public Method[] getMethods():获取所有的"公有成员方法"
会将每个公有成员方法封装为一个Method对象,多个Method对象封装到一个数组中返回。
2).public Method[] getDeclaredMethods():获取所有的成员方法,包括:公有、受保护、默认、私有。
2).获取单个:[掌握]
3).public Method getMethod(String methodName,Class... params):通过"方法名,参数的Class",获取某个"公有成员方法"
4).public Method getDeclaredMethod(String methodName,Class ... params):通过方法名、参数Class,获取任何访问权限的成员方法。
调用方法:Method类的-->invoke(Object targetObj,Object ... params)
如果没有访问权限,需要设置暴力访问:
Method类的-->setAccessible(true)
7.反射可以跳过集合的泛型检测.泛型擦除机制
//泛型在编译的时候都会确定为Object类
public class Practice {
public static void main(String[] args) throws Exception {
List<String> list = new ArrayList<>();
list.add("冰冰");
list.add("秋雅");
list.add("冬梅");
Class<? extends List> clazz = list.getClass();
Method addMethod = clazz.getMethod("add", Object.class);
//这里通过反射调用方法,添加了Student类型
addMethod.invoke(list,new Student("赵敏",23));
System.out.println(list);
}
}
二.Annotation 注解
1.使用方法
从JDK1.5开始,Java提出了"注解",
例如:@Override(重写方法时) //Class 生命周期 编译期
@FunctionalInterface(定义函数式接口) // 生命周期 Class
@Test(JUnit工具) //生命周期 Runtime
注解的作用:写在"源码"中,告诉"注解解析器",下面的代码怎样编译和运行。
注解的应用包含:注解 + 注解解析器(程序)
A).注解:
1).我们可以自己定义注解
public @interface MyTest {
}
2).使用注解:
@MyTest
public class Student {
@MyTest
private String name;
@MyTest
public void show(){
System.out.println("呵呵");
}
}
注意:此时该注解可以使用,但没有作用
3).元注解:Java类库中定义好的注解,用在"注解的定义"上,用于对注解进行"约束"的(即用于定义注解的注解):
1).@Target:约束新定义的注解的"使用位置"
例如:@Target(ElementType.METHOD)可以约束新注解只能用在:方法上
ElementType的常用值:
1).TYPE:类,接口
2).FIELD:成员变量
3).METHOD, 成员方法
4).PARAMETER, 方法参数
5).CONSTRUCTOR, 构造方法
6).LOCAL_VARIABLE, 局部变量
2).@Retention:约束新定义的注解的"生命周期"
例如:@Retention(RetentionPolicy.SOURCE)可以约束注解的生命周期:源码
RetentionPolicy的取值:
1).SOURCE:在源码中。不会编译到class文件中。作用:给编译器中的"注解解析器"看的。例如:@Override
2).CLASS:在源码中、class文件。但运行时,不会被加载到运行时内存。
3).RUNTIME:在源码中、class文件中,运行时内存。例如:@Test
B).注解解析器
使用反射方式,创建使用了注解的类的对象
反射后,获取类的组成部分,
调用isAnnotationPresent方法,判断是否使用了注定注解.class
C).注解的属性:刚才使用元注解,都是带属性:
@Target(ElementType.METHOD)
其中的ElementType.METHOD就是属性
属性的作用:可以”更加细致"的对新定义的注解进行"约束"
注解中定义属性的格式:
public @interface MyTest {
//属性的格式:数据类型名 属性名() [default 值];
int index() default 0; //属性定义后,使用时必须赋值,否则报错
//定义默认值后,在使用注解的类中可以不赋值使用默认值,或者显示的重新赋值
}
属性的获取:
可以反射获取加入了注解的部分,并调用getAnnotation(注解类.class)方法获取注解对象
注解对象.属性(),即可获得注解属性值
注意:
1).其中的数据类型,只能是:八种数据数据类型、String、Class、注解类型、枚举类型、上述几种类型的"数组"类型。【背下来】
2).如果一个注解中:有一个属性名叫:value,而且其他属性都有默认值,而且在使用这个注解时,只需要设置value属性,可以省略:"value = ",直接写值。
2.自定义注解
需求:
做一个"注解解析器",模拟使用JUnit的@Test注解时:右键-->Run "xxxx.show()"功能菜单。
1.自定义一个注解,并约束为定义在方法上,且生命周期为运行时
2.自定义类,Student,在某个方法上加入自定义注解
3.定义注解解析器,解析带注解的方法,并执行
4.1获取使用了注解的类的所有方法,使用方法名称进行对方法的排序
4.2在注解中定义int类型的index属性,使用反射的方式获取使用了注解的类的所有方法,使用注解的index属性对方法进行排序
注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
int index();
}
使用了注解的类
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@MyAnno(index = 3)
public void eat(){
System.out.println("eat");
}
@MyAnno(index = 19)
public void sleep(){
System.out.println("sleep");
}
@MyAnno(index = 9)
public void love(){
System.out.println("love");
}
public void fen(){
System.out.println("分");
}
}
注解解析类
public class Demo {
public static void main(String[] args) throws Exception {
List<Method> list = new ArrayList<>();
Class<Student> clazz = Student.class;
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if(method.isAnnotationPresent(MyAnno.class)){
method.invoke(stu);
list.add(method);
}
}
//4.1获取使用了注解的类的所有方法,使用方法名称进行对方法的排序
Collections.sort(list, new Comparator<Method>() {
@Override
public int compare(Method o1, Method o2) {
return o1.getName().compareTo(o2.getName());
}
});
System.out.println("-------方法名称进行对方法的排序----------");
for (Method method : list) {
System.out.println(method);
}
//4.2在注解中定义int类型的index属性,使用反射的方式获取使用了注解的类的所有方法,使用注解的index属性对方法进行排序
System.out.println("-------根据index的排序----------");
Collections.sort(list, new Comparator<Method>() {
@Override
public int compare(Method o1, Method o2) {
return o1.getAnnotation(MyAnno.class).index()-o2.getAnnotation(MyAnno.class).index();
}
});
for (Method method : list) {
System.out.println(method+" index="+method.getAnnotation(MyAnno.class).index());
}
}
}
三.代理模式
1.什么是"设计模式":
模式:解决某种问题的一些固定的方式、流程。例如:盈利模式、工作模式、学习模式…
Java的设计模式:指利用Java的一些语法特征(继承、封装、多态…)针对某些问题,提出的一种固定的设计方式。
2.Java中已定义的设计模式有很多种(20多种,扩展的有40-50多种),有专门的书介绍Java的设计模式。
3.代理模式:
解决的问题:在不更改原类代码的前提下,为原类的方法提供"功能增强"
“代理模式"就类似于现实中的"代理”。
代理增强,装饰者模式
4.java中动代理API
动态代理:指程序可以在运行时"动态的"为某个"被代理类"产生一个"动态代理对象",并可以进行方法增强。
注意:JDK提供的动态代理是基于"接口"的——要求所有"被代理的类"必须实现同一个接口,从而具有相同的功能
动态代理类:Proxy
产生代理方法 public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数:loader 类加载器,代理类的"类加载器",与"被代理类"的相同
参数:interfaces 代理类的"父接口",与"被代理类"相同
参数:h 执行代理控制对象
InvocationHandler接口内方法:
public Object invoke(Object proxy, Method method, Object[] args)
方法的作用:当代理调用任何方法,都会被该拦截,执行此方法
可以根据逻辑判断是否执行被代理者的原方法,
并且可以增强方法,即在原方法执行前后,执行其他逻辑。
参数说明:
proxy;产生的代理对象[不使用]
method:被代理执行的方法对象
args:被代理执行的方法的参数
return 代理对象
5.代理模式案例
需求
定义接口IStar#sing#dance
定义类Cxk,实现IStar接口
sing方法中打印真唱,很难听的唱
dance方法中打印,花式篮球玩法,篮球玩的好
Demo中:
创建Cxk对象
创建Cxk对象的代理对象
代理执行逻辑:
如果是sing方法,就代唱,唱出激昂高亢的真男人歌声
如果是dance方法,就让被代理者自己跳即可
使用代理对象执行sing方法与dance方法,完成代理调用。
被代理类
public class Cxk implements DaiLi{
public void sing(){
System.out.println("真唱,很难听的唱");
}
public void dance(){
System.out.println("花式篮球玩法,篮球玩的好");
}
@Override
public int yanDianYing(String name) {
System.out.println("经典再现 "+name);
return 9;
}
}
被代理类实现接口
public interface IStar {
void sing();
void dance();
}
代理测试类
public class Demo {
public static void main(String[] args) {
Cxk cxk = new Cxk();
//Proxy.newProxyInstance
//第一个参数 类加载器
ClassLoader classLoader = Cxk.class.getClassLoader();
//第二个参数 实现的所有接口数组
Class<?>[] interfaces = Cxk.class.getInterfaces();
IStar proxy = (IStar) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("sing".equals(method.getName())){
System.out.println("代唱,唱的真不戳");
}
if("dance".equals(method.getName())){
method.invoke("唱,跳,rap"+cxk);
}
return null;
}
});
proxy.sing();
proxy.dance();
}
}