1、Junit单元测试
1、背景:
在main方法中写代码测试存在的问题:所有代码都写在main方法中,都会运行,我们如果要测试,会执行所有代码,测试效率会变低
2、概念:
单元测试是对部分内容进行测试
Junit是java的一个第三方单元测试框架,其保存在junit.jar中。
PS:第一方:JDK提供的类;第二方:自己写的类;第三方:其他人写的很多类。
junit用处:可以对部分代码进行测试
3、Junit的使用步骤
1.定义一个测试类
2.编写测试方法
测试方法的要求:
1 必须是public修饰的
2 返回值必须是void
3 参数列表必须为空
3.在测试方法里面编写我们要测试的代码
4.在测试方法头上添加一个@Test注解
5.运行测试方法
5.1运行的方式
5.1.1在【测试方法左边】点击绿色三角形,运行一个测试方法
5.1.2在【测试类的左边】点击绿色三角形,运行一个类中的所测试方法
5.1.3在【包上右键】,点击绿色三角形,运行包里面所有类的测试方法
5.2运行结果:
5.2.1 绿色表示通过
5.2.2 红色表示不通过
5.2.3 黄色表示【断言】有问题
断言介绍:Assert.assertEquals(猜测值, 真实值)
public class Test01 {
@Test
public void test01() {
// 3.在测试方法里面编写我们要测试的代码
System.out.println("我是测试方法");
// int a = 10 / 0;
// 测试自己的类的功能
Calcuate calc = new Calcuate();
int sum = calc.sum(1, 3);
System.out.println("sum = " + sum);
// 断言: 看我们猜测的值,和真是的是是否一样
// Assert.assertEquals(猜测值, 真实值);
Assert.assertEquals(4, sum);
}
}
4、Junit常用注解
四个注解的作用(以下四个左边都没有三角形,是自动运行的)
@Before: 在每个测试方法前执行
@After: 在每个测试方法后执行
@BeforeClass: 在所有测试方法前执行
@AfterClass: 在所有测试方法后执行
public class Test02 {
@BeforeClass
public static void testBeforeclass22() {//要添加静态statc修饰
System.out.println("BeforeClass");
}
@AfterClass
public static void testAfterClass22() {//要添加静态statc修饰
System.out.println("AfterClass");
}
@Before
public void testBefore22() {
System.out.println("Before");
}
@After
public void testAfter22() {
System.out.println("After");
}
@Test
public void test01() {
System.out.println("我是测试方法");
}
@Test
public void test02() {
System.out.println("我是测试方法");
}
}
5、junit的使用需要导入两个jar包
hamcrest-core-1.3.jar 和 junit-4.12.jar
2、反射
1、Class对象
1、概念:
类名是Class,这个Class类创建的对象,我们称为Class对象。一个类只有一个class对象。常被命名为clazz或者cls
public final class Class<T> {
// 成员变量
// 成员方法
}
2、产生过程及作用
(1)过程
JDK1.8前:
当.class文件加载到内存中的方法区域时,JVM会创建一个Class对象,Class对象会保存class文件里面的成员变量,成员方法和构造方法等类里面的信息
JDK1.8后:
相比于JDK1.8前,没有了方法区,而是变成了元空间,被创建出来的class对象放到了元空间,但不由JVM控制,而是由OS(操作系统)控制。
(2)作用
Class对象是反射必须用到的d东西(因为拿到了Class对象,相当于拿到了类的所有的东西)
3、三种获取class对象的方法
1.类名.class
2.对象名.getClass()
3.Class.forName(类全名); 类全名就是包名.类名
注意:以后工作中,用第三种方式,因为它使用类全名,属于字符串,灵活
一个类只有一个class对象
public class Demo04 {
public static void main(String[] args) throws ClassNotFoundException {
// 1.类名.class
Class<Employee> cls1 = Employee.class;
System.out.println(cls1); //重写了toString方法 class com.itheima.bean.Employee//包名.类名
// 2.对象名.getClass()
Employee e = new Employee();//之所以下一句的泛型是向上限定,是因为本语句有可能new的是Employee的子类,也就是多态
Class<? extends Employee> cls2 = e.getClass();
System.out.println(cls2);
// 3.Class.forName(类全名);
Class<?> cls3 = Class.forName("com.itheima.bean.Employee");//点击Employee类--->copy Reference
System.out.println(cls3);
//说明以上三种方法得到的class对象是同一个对象(也就是一个类只有一个class对象)
System.out.println(cls1 == cls2); // true
System.out.println(cls1 == cls3); // true
}
}
4、Class类中的方法
获取Class对象信息的方法(2个)
String getName(); 获取【类全名】
String getSimpleName(); 获取【类名】
Object newInstance(); 创建对象
public class Demo05 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class<?> cls = Class.forName("com.itheima.bean.Employee");
// String getName(); 获取类全名
String name = cls.getName();
System.out.println("name = " + name); // com.itheima.bean.Employee
// String getSimpleName(); 获获取类名
String simpleName = cls.getSimpleName();
System.out.println("simpleName = " + simpleName); // Employee
// Object newInstance(); 创建对象
Object obj = cls.newInstance(); // Employee e = new Employee();(即该方法实际上是创建Employee这个类的对象)
System.out.println(obj);
}
}
2、反射
1、概念:
在程序运行的时候,通过Class对象得到类里面的【构造方法】,【成员方法】,【成员变量】,操作他们.(对类进行解剖)
2、应用场景:
IDEA软件的智能提示、“框架”
3、反射的步骤:
1.获取Class对象
2.调用方法操作对应的内容
4、反射得到并操作构造方法
(1)Constructor类
表示类中的构造方法
(2)得到public的Constructor对象
步骤:
1.得到Class对象
2.通过Class对象的方法得到Constructor对象,如下:
getConstructors: 得到所有的public的构造方法
getConstructor: 得到一个的public的构造方法
(3)得到声明的Constructor对象
声明的Constructor对象即指原本类中所有的构造方法
步骤:
1.得到Class对象
2.通过方法得到声明的Constructor
getDeclaredConstructors: 获取所有声明的构造方法
getDeclaredConstructor: 获取一个声明的构造方法
PS:有Declared的得到声明的,不管权限,没有Declared的得到public的
(4)Constructor类的方法(对得到的构造方法进行使用):
Object newInstance(...);
a.见代码1:Integer.class != int.class
b.见代码2:对通过声明的途径得到的私有构造方法,我们在使用之前必须得调用如下方法才能进行使用
暴力反射
setAccessible(true);
public class Demo061 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1.得到Class对象
Class<?> cls = Class.forName("com.itheima.bean.Employee");
// 2.通过Class对象的方法得到Constructor对象
// getConstructors得到所有的public的构造方法
/*Constructor<?>[] constructors = cls.getConstructors();
for (Constructor<?> c : constructors) {
System.out.println("c = " + c);
}*/
// getConstructor得到一个的public的构造方法
Constructor<?> c1 = cls.getConstructor();
System.out.println("c1 = " + c1); // public com.itheima.bean.Employee()
Object obj1 = c1.newInstance();//相当于创建了一个Employee对象
System.out.println("obj1 = " + obj1);//obj1 = Employee{name='null', age=0, weight=0.0, height=0.0}
Constructor<?> c2 = cls.getConstructor(String.class, int.class);//基本数据类型与引用数据类型的Class对象 Integer.clas != int.class,不写Integer
System.out.println("c2 = " + c2); // public com.itheima.bean.Employee(java.lang.String,int)
Object obj2 = c2.newInstance("凤姐", 18);//相当于创建了一个Employee对象
System.out.println("obj2 = " + obj2);//obj2 = Employee{name='凤姐', age=18, weight=0.0, height=0.0}
}
}
public class Demo062 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1.得到Class对象
Class<?> cls = Class.forName("com.itheima.bean.Employee");
// 2.通过方法得到声明的Constructor
// getDeclaredConstructors: 获取所有声明的构造方法
Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
/*for (Constructor<?> c : declaredConstructors) {
System.out.println("c = " + c);
}*/
// getDeclaredConstructor: 获取一个声明的构造方法
Constructor<?> c1 = cls.getDeclaredConstructor(String.class);
System.out.println("c1 = " + c1); // private com.itheima.bean.Employee(java.lang.String)
// 对于私有的方法,我们在使用前需要进行暴力反射
c1.setAccessible(true); // 设置可以访问为true
Object obj1 = c1.newInstance("凤姐");
System.out.println("obj1 = " + obj1);
}
}
5、反射得到并操作成员方法
(1)Method类
表示类中的普通方法
(2)得到Method对象
1.得到Class对象
2.调用Class对象的方法得到Method对象
getMethods: 得到所有的public方法,包括父类的
getMethod: 得到一个的public方法
getDeclaredMethods: 获取所有的声明的方法
getDeclaredMethod: 获取一个声明的方法
(3)Method类的方法(对得到的成员方法进行使用):
Object invoke(Object obj, Object... args)
Object obj: 调用方法的对象
Object... args: 调用方法时传递给方法的参数
Object返回值: 就是这个方法的返回值
public class Demo07 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
// 1.得到Class对象
Class<?> cls = Class.forName("com.itheima.bean.Employee");
Object obj = cls.newInstance();
// 2.调用Class对象的方法得到Method对象
// getMethods: 得到所有的public方法,包括父类的(如Object类)
// Method[] methods = cls.getMethods();
// for (Method m : methods) {
// System.out.println("m = " + m);
// }
// getMethod: 得到一个的public方法
Method m1 = cls.getMethod("setName", String.class);
System.out.println("m1 = " + m1);
Object r1 = m1.invoke(obj, "凤姐");
System.out.println("返回值1 = " + r1);//该方法没有返回值,所以打印出来是null
System.out.println(obj);//打印出来是一个对象的信息,说明方法执行了
// getDeclaredMethods: 获取所有的声明的方法(此时打印出来不再会有父类如Object类的方法)
// Method[] declaredMethods = cls.getDeclaredMethods();
// for (Method m : declaredMethods) {
// System.out.println("m = " + m);
// }
// getDeclaredMethod: 获取一个声明的方法
Method m2 = cls.getDeclaredMethod("sleep", int.class);
m2.setAccessible(true);//,方法私有,暴力反射
Object r2 = m2.invoke(obj, 8);
System.out.println("返回值2 = " + r2);
}
}
6、单射得到并操作成员变量
(1)Filed类
表示类中的成员变量
(2)得到Filed对象
1.获取Class对象
2.调用Class对象的方法获取Field对象
getFields: 获取所有public的成员变量
getField: 得到一个public的成员变量
getDeclaredFields: 得到所有声明的成员变量
getDeclaredField: 得到一个声明的成员变量
(3)Filed类的方法(对得到的成员变量进行使用)--------->少用,Method和Structor在反射常用
成员变量用于设置值/获取值
设置值:
基本数据类型: setXxx(Object obj, Xxx x) 例如: setDouble(Object obj, double d), setInt(Object obj, int i)
引用数据类型: set(Object obj, Object arg)
获取值:
基本数据类型: getXxx(Object obj) 例如:getDouble()/getInt()
引用数据类型: get(Object obj);
public class Demo08 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
// 1.获取Class对象
Class<?> cls = Class.forName("com.itheima.bean.Employee");
Object obj = cls.newInstance();
// 2.调用Class对象的方法获取Field对象
// getFields: 获取所有public的成员变量
/*Field[] fields = cls.getFields();
for (Field f : fields) {
System.out.println("f = " + f);
}*/
// getField: 得到一个public的成员变量
Field f1 = cls.getField("weight");
System.out.println("f1 = " + f1);
f1.setDouble(obj, 66.6);
System.out.println("obj = " + obj);
System.out.println(f1.getDouble(obj));
// getDeclaredFields: 得到所有声明的成员变量
// Field[] declaredFields = cls.getDeclaredFields();
// for (Field f : declaredFields) {
// System.out.println("f = " + f);
// }
// getDeclaredField: 得到一个声明的成员变量
Field f2 = cls.getDeclaredField("name");
System.out.println("f2 = " + f2);
f2.setAccessible(true);
f2.set(obj, "如花");
System.out.println("obj = " + obj);
System.out.println(f2.get(obj));
// 获取成员变量的类型
Class<?> type = f2.getType();//返回值是一个class对象
System.out.println(type.getName());//获取【类全名】
System.out.println("类型: " + type.getSimpleName());//想要得到类名,则用getSimpleName方法(
}
}
7、反射的应用(优势体现在哪里?)
根据配置文件中的不同类名和方法名,创建不同的对象并调用方法。
1.定义学生/老师等类
2.创建一个配置文件config.properties
//因为反射要把类名变成class对象,故写类全名;
//同时,名字不能出现中文,会出现错误
2.1配置类名
2.2配置方法名
3.加载properties的数据到Properties对象中
4.根据类名获取Class对象
5.根据方法名获取方法
6.使用反射执行方法
public class Demo09 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
// 3.加载properties的数据到Properties对象中
Properties pp = new Properties();
pp.load(new FileInputStream("day12demo/config.properties"));
System.out.println("pp = " + pp); // {classname=com.itheima.demo09Reflect.Student, methodname=study}
String classname = pp.getProperty("classname"); // com.itheima.demo09Reflect.Student
String methodname = pp.getProperty("methodname"); // study
// 4.根据类名获取Class对象
Class<?> cls = Class.forName(classname);
Object obj = cls.newInstance();
// 5.根据方法名获取方法
Method method = cls.getMethod(methodname);
// 6.使用反射执行方法
method.invoke(obj);
}
}
以上的代码所体现反射的好处:
反射降低耦合性,提高代码的灵活性和扩展性。(应用如:框架)
(本例中无需修改内部代码,只需修改一点点配置文件信息就可以得到不同的方法执行的结果)
3、注解
1、概念:
是JDK1.5的新特性(Annotation)。注解的实质是保存了一些数据。
注解给类增加额外的信息,而且它是给java编译器和JVM虚拟机使用的(给程序使用的)
2、作用:
编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
代码分析:通过代码里标识的注解对代码进行分析【使用反射】
3、自定义注解
(1)格式
自定义注解的格式:
@interface 注解名 {
// 注解属性
数据类型 变量名();
}
注解的属性类型可以有以下四种:
1.8种基本数据类型都可以
2.String,枚举,注解,Class
3.以上类型的一维数组
4、类中的 String和 Class可以
注意:鉴于注解是为了传输一些轻量级的小数据,所以它所支持的类型没有那么多。故对于注解来说自定义类型不行(如Integer、自定义的student类等等)
(2)使用
使用的时候必须要赋值,且每个属性都要有值,否则会编译报错。
@注解名(属性名=属性值, 属性名=属性值)
特殊情况:
(1)在自定义注解的时候,可以为其属性设置默认值。属性被设置了默认值的注解在使用的时候可以不用赋值,不赋值编译也不会报错。
(2)
a.当注解中只有一个属性,并且属性名是value的时候:在使用注解时,可以省略属性名value
b.当注解中由多个属性,且其中一个属性名是value,其他属性值都有默认值的时候:在使用注解时,可以省略属性名value
public @interface MyAnno2 {
// 属性
String name();
double price() default 18.8; // 默认值
String[] authros();
// WeekDay weekDay();//枚举可以
// MyAnno1 mya();//注解可以
// Class cls();//类可以
}
enum WeekDay {
SUN, MON
}
4、元注解
(1)概念:
修饰注解的注解
(2)@Target元注解
作用:限制注解使用的位置(PS:在未使用该元注解的时候,默认情况是注解在哪里都能使用)
@Target(ElementType.TYPE) // 让注解可以在类或接口上使用
@Target(ElementType.CONSTRUCTOR) // 让注解可以在构造方法上使用
@Target(ElementType.METHOD) // 让注解可以在普通方法上使用
@Target(ElementType.FIELD) // 让注解可以在成员变量上使用
@Target(ElementType.LOCAL_VARIABLE) // 让注解可以在局部变量上使用
(3)@Retention元注解
作用:限制注解能够存活到什么时候(PS:在未使用该元注解的时候,默认注解活到CLASS阶段)
@Retention(RetentionPolicy.RUNTIME)//RUNTIME阶段
@Retention(RetentionPolicy.CLASS)//CLASS阶段
@Retention(RetentionPolicy.SOURCE)//SOURCE阶段
SOURCE阶段 CLASS阶段 RUNTIME阶段
.java(源代码) -> 编译 -> .class -> 运行
5、注解解析
1、概念:
在程序运行的时候,得到注解里面属性值
2、注解解析相关接口:AnnotatedElement接口
AnnotatedElement接口定义了解析注解的方法
T getAnnotation(Class<T> annotationClass) 获取某个对象上的单个注解
Annotation[] getAnnotations() 获取某个对象上的所有注解
boolean isAnnotationPresent(Class annotationClass) 判断是否有这个注解
注意:Class, Constructor, Method, Field这类写都实现了AnnotatedElement接口.
3、如何解析注解
注解在谁头上就用谁来获取
假如注解在构造方法上,使用Constructor来获取
假如注解在成员方法上,使用Method来获取
假如注解在成员变量上,使用Field来获取
定义注解:
// 注解
@Retention(RetentionPolicy.RUNTIME)//必写,否则注解只能活到默认阶段class,运行时就获得不到注解内容
public @interface BookAnno {
String color();
double price();
}
Book类
public class Book {
@BookAnno(color = "黄色的", price = 0.99)
private String name;
@MyAnno4
@BookAnno(color = "绿色的", price = 99)
// 注解上的数据,在任何地方都可以解析出来(灵活)
public void sale(String color, double price) {
// 方法参数上的数据,只能在这个方法里面得到
}
}
public class Demo13 {
// 我们点左边绿色的三角形,程序就运行起来的.
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
// 获取方法上的注解
// 1.获取Class对象
Class<?> cls = Class.forName("com.itheima.demo14注解解析_练习.Book");
// 2.通过反射获取Method对象
Method sale = cls.getMethod("sale");
// 3.解析注解
// boolean isAnnotationPresent(Class annotationClass) 判断是否有这个注解
boolean b = sale.isAnnotationPresent(BookAnno.class);
System.out.println("b = " + b);
//if语句:先判断有无注解,再进行获取注解
if (b) {//判断条件别写b==false
BookAnno anno = sale.getAnnotation(BookAnno.class);
System.out.println(anno.color() + ":" + anno.price());
}
// Annotation[] getAnnotations() 获取某个对象上的所有注解
Annotation[] annos = sale.getAnnotations();
for (Annotation a : annos) {
System.out.println("a = " + a);
}
}
// 单元测试
@Test
public void test02() throws Exception {
Class<?> cls = Class.forName("com.itheima.demo14注解解析_练习.Book");
Field name = cls.getDeclaredField("name");
Annotation[] as = name.getAnnotations();
for (Annotation a : as) {
System.out.println("a = " + a);
}
}
}
模拟JUnit自带的@Test注解,自动运行带@Test注解的方法
// 1.自定义MyTest注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
// 2.自定义一个类,部分方法配置MyTest注解
public class Person {
public void getUp() {
System.out.println("起床啦");
}
@MyTest
public void eat() {
System.out.println("吃饭啦");
}
public void study() {
System.out.println("学习啦");
}
@MyTest
public void sleep() {
System.out.println("睡觉啦");
}
}
public class Demo15 {
public static void main(String[] args) throws Exception {
// 3.解析注解:
// 3.1 得到Class对象
Person s = new Person();
Class<?> cls = Class.forName("com.itheima.demo15注解案例_模拟Junit.Person");
// 3.2 得到所有Method
Method[] methods = cls.getMethods();
for (Method method : methods) {
// 3.3 如果Method上有注解就运行这个方法
if (method.isAnnotationPresent(MyTest.class)) {
method.invoke(s);
}
}
}
}
类加载器把class文件记载道内存中
4、动态代理
1、现实生活代理
卖票接口
卖票的功能
你买火车票 -> 黄牛 -> 12306
你买电脑->电脑的代理商-> 神州工厂
黄牛和1230 6实现 卖票接口
代理模式四要素
调用者: 你
真实对象: 真正调用功能的对象(12306)
代理对象: 在调用者和真实对象之间的对象(黄牛)
抽象对象: 代理对象和真实对象都要实现的接口
2、动态代理:
在程序运行的过程中,动态的创建出代理对象
3、代理模式好处
在不改变真实对象代码的情况下可以对真实对象的功能进行增强,还可以拦截方法(见下面的案例)
4、动态代理API
Proxy类: proxy翻译为代理,它的其中一个方法如下:
//创建一个代理对象
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
ClassLoader loader //类加载器
Class<?>[] interfaces//接口数组,创建出的代理对象就会实现这些接口
InvocationHandler h//执行处理器(是一个接口),作为方法参数,方法调用的时候,是一个实现类对象或者匿名内部类(重写invoke方法)。当执行代理对象的方法时,就会执行这个对象的invoke方法
Object//返回值:就是创建出的代理对象
总结:
Object 代理对象 = Proxy.newProxyInstance(类加载器, 接口, 执行处理器);
5、过程详解
Proxy.newProxyInstance: 创建出一个代理对象,
代理对象会实现参数(第二个参数)指定的接口(本案例实现的是list接口,如此一来,本例中的代理对象就会和ArryList有一样的功能(方法))。
当代理对象调用方法时,就会调用 执行处理器InvocationHandler的被重写的【invoke方法】,invoke方法被重写时候可以是功能增强,也可以是拦截方法
案例
/*
案例目标:
使用动态代理增强ArrayList的所有方法,统计ArrayList每个方法调用花费的时间
ArrayList是真实对象
main方法看作是调用者
代理对象是我们创建出来的daiLi对象
抽象对象是我们创建daiLi对象时候实现了InvocationHandler()接口
*/
public class Demo16 {
public static void main(String[] args) {
// 创建真实对象
ArrayList<String> arrayList = new ArrayList<>();
// Class对象就能够得到类加载器
// 创建出一个代理对象,代理对象实现了参数指定的接口
List daiLi = (List) Proxy.newProxyInstance(Demo16.class.getClassLoader(),
new Class[]{List.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
Object proxy: 代理对象,一般不处理
Method method: 被调用的方法
Object[] args: 调用方法时的参数
*/
// System.out.println("invoke: " + method.getName() + ", 参数: " + Arrays.toString(args));
// 调用前记录开始时间
long start = System.currentTimeMillis();
Object re = null;//避免最后的return re;语句的作用域不够
// 拦截方法(不给调用者使用remove方法)
if (method.getName().equals("remove")) {
System.out.println("老子什么都不干!");
return false;
} else {
// 调用真实对象的方法,代理对象调用什么方法,真实对象也调用什么方法
re = method.invoke(arrayList, args);
}
// 调用后记录时间
long end = System.currentTimeMillis();
System.out.println(method.getName() + " 消耗时间: " + (end - start));
return re;//真是对象返回啥,代理对象也返回啥
}
});
daiLi.add("aa"); // 当调用代理对象的方法,就会执行到 执行处理器的invoke
daiLi.add("bb");
daiLi.add("cc");
daiLi.add("dd");
daiLi.remove("bb");
daiLi.set(0, "凤姐");
System.out.println("arrayList: " + arrayList);
}
}
if (method.getName().equals("remove")) {
System.out.println("老子什么都不干!");
return false;
} else {
// 调用真实对象的方法,代理对象调用什么方法,真实对象也调用什么方法
re = method.invoke(arrayList, args);
}
// 调用后记录时间
long end = System.currentTimeMillis();
System.out.println(method.getName() + " 消耗时间: " + (end - start));
return re;//真是对象返回啥,代理对象也返回啥
}
});
daiLi.add("aa"); // 当调用代理对象的方法,就会执行到 执行处理器的invoke
daiLi.add("bb");
daiLi.add("cc");
daiLi.add("dd");
daiLi.remove("bb");
daiLi.set(0, "凤姐");
System.out.println("arrayList: " + arrayList);
}
}