今日内容
- JUnit单元测试-------------->必须重点掌握(简单)
- 反射
- 获取Class对象-------------->必须重点掌握(简单)
- 反射操作构造方法
- 反射操作成员方法-------------->必须重点掌握(难点)
- 反射操作成员变量
- 注解
- 使用注解-------------->必须重点掌握(简单)
- 自定义注解或者解析注解
- 动态代理
- 如何生成一个代理对象(动态代理)------>掌握(难点)
- JDK8新特性
- 方法引用------->建议掌握
- Base64编码和解码------->必须重点掌握(简单)
第一章 Junit单元测试
1.1 Junit单元测试
Junit的概念
- 概述 : Junit是Java语言编写的第三方单元测试框架(工具类)
- 作用 : 用来做“单元测试”——针对某个普通方法,可以像main()方法一样独立运行,它专门用于测试某个方法。
Junit的使用步骤
-
1.在模块下创建lib文件夹,把Junit的jar包复制到lib文件夹中
-
2.选中Junit的jar包,右键选中 add as Library,把JUnit4的jar包添加到classPath中
-
3.在测试方法上面写上@Test注解
-
4.执行测试方法
/** * Created by PengZhiLin on 2021/8/15 8:59 */ public class TestDemo { @Test public void test1(){ System.out.println("测试方法test1..."); } @Test public void test2(){ System.out.println("测试方法test2..."); } }
执行测试方法
-
1.选中方法名--->右键--->选中执行 只执行选中的测试方法
-
2.选中类名----->右键--->选中执行 执行该类中所有的测试方法
-
3.选中模块---- ->右键--->选中all tests 执行 执行该模块中所有的测试方法
-
如何查看测试结果
- 绿色:表示测试通过
- 红色:表示测试失败,有问题
Junit单元测试的注意事项
- 1.测试方法的权限修饰符一定是public
- 2.测试方法的返回值类型一定是void
- 3.测试方法一定没有参数
- 4.测试方法 的声明之上一定要使用@Test注解
1.2 Junit其他注解
- @Before:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。
- @After:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。
- @BeforeClass:用来修饰静态方法,该方法会在所有测试方法之前执行一次,而且只执行一次。
- @AfterClass:用来修饰静态方法,该方法会在所有测试方法之后执行一次,而且只执行一次。
/**
* Created by PengZhiLin on 2021/8/15 9:11
*/
public class TestDemo {
@BeforeClass
public static void bc(){
System.out.println("@BeforeClass修饰的方法...");
}
@Before
public void b(){
System.out.println("@Before修饰的方法...");
}
@Test
public void test1(){
System.out.println("测试方法test1...");
}
@Test
public void test2(){
System.out.println("测试方法test2...");
}
@After
public void a(){
System.out.println("@After修饰的方法...");
}
@AfterClass
public static void ac(){
System.out.println("@AfterClass修饰的方法...");
}
}
1.3 Junit断言
-
断言:预先判断某个条件一定成立,如果条件不成立,则直接报错。 使用Assert类中的assertEquals()方法
-
案例:
/** * Created by PengZhiLin on 2021/8/15 9:24 */ public class TestDemo { @Test public void test(){ // 断言 /* Assert.assertEquals(参数1,参数2): 断言 参数1: 期望的值 参数2: 实际的值 如果期望的值与实际的值相等,就不报错,如果不相等,就报错 */ int sum = getSum(10, 20); Assert.assertEquals(30,sum); } /** * 计算2个数的和 * @param num1 * @param num2 * @return 和 */ public int getSum(int num1,int num2){ return num1 * num2; } }
第二章 反射
2.1 类加载器
类的加载
- 当我们的程序在运行后,第一次使用某个类的时候,类加载器会将此类的class文件读取到内存,并将此类的所有信息存储到一个Class对象中
类的加载时机
-
创建类的实例。
-
类的静态变量,或者为静态变量赋值。
-
类的静态方法。
-
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
-
初始化某个类的子类对象。
-
直接使用java.exe命令来运行某个主类。
以上六种情况的任何一种,都可以导致JVM将一个类加载到方法区。
/** * Created by PengZhiLin on 2021/8/15 10:11 */ class Student extends Person { } public class Test { public static void main(String[] args) throws Exception { //1. 创建类的实例。 //new Person(); //2. 类的静态变量,或者为静态变量赋值。 //System.out.println(Person.num); //3. 类的静态方法。 //Person.method(); //4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。 //Class.forName("com.itheima.demo4_类加载时机.Person"); //5. 初始化某个类的子类对象。 new Student(); //6. 直接使用java.exe命令来运行某个主类。 // // 以上六种情况的任何一种,都可以导致JVM将一个类加载到方法区。 } }
/** * Created by PengZhiLin on 2021/8/15 10:11 */ public class Person { static { System.out.println("Person 静态代码块..."); } static int num = 10; public static void method(){ System.out.println("静态method..."); } }
类加载器
类加载器:是负责将磁盘上的某个class文件读取到内存并生成Class的对象。
/**
* Created by PengZhiLin on 2021/8/15 10:16
*/
public class Test {
public static void main(String[] args) {
// 需求:获取Person类的类加载器
// 1.获取Person类的字节码对象
// 2.使用字节码对象调用getClassLoader()方法
ClassLoader classLoader = Person.class.getClassLoader();
System.out.println("Person类的类加载器:"+classLoader);
// 需求:获取String类的类加载器
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println("String类的类加载器:"+classLoader1);
// API中说明:一些实现可能使用null来表示引导类加载器。 如果此类由引导类加载器加载,则此方法将在此类实现中返回null
}
}
2.2 扩展类加载器结合Properties的使用
/**
* Created by PengZhiLin on 2021/8/15 10:27
*/
public class Utils {
public static String value1;
public static String value2;
public static String value3;
public static String value4;
static {
try {
// 1.创建Properties对象
Properties pro = new Properties();
// 2.调用load方法加载配置文件中的键值对
//pro.load(new FileInputStream("day13\\src\\db.properties"));
// 这种方式获得的流,路径已经到了src路径
InputStream is = Utils.class.getClassLoader().getResourceAsStream("db.properties");
pro.load(is);
// 3.获取数据
value1 = pro.getProperty("k1");
value2 = pro.getProperty("k2");
value3 = pro.getProperty("k3");
value4 = pro.getProperty("k4");
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Created by PengZhiLin on 2021/8/15 10:23
*/
public class Test {
public static void main(String[] args) throws Exception{
System.out.println(Utils.value1);
System.out.println(Utils.value2);
System.out.println(Utils.value3);
System.out.println(Utils.value4);
}
}
2.3 反射的概述
反射的引入
- 问题:IDEA中的对象是怎么知道类有哪些属性,哪些方法的呢?
通过反射技术对类进行了解剖得到了类的所有成员。
反射的概念
反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员(成员变量,成员方法,构造方法)
使用反射操作类成员的前提
要获得该类字节码文件对象,就是Class对象
反射在实际开发中的应用
* 开发IDE(集成开发环境),比如IDEA,Eclipse
* 各种框架的设计和学习 比如Spring,Hibernate,Struct,Mybaits....
2.4 获取Class对象
-
通过类名.class获得
-
通过对象名.getClass()方法获得
-
通过Class类的静态方法获得: static Class forName("类全名")
-
示例代码
/** * Created by PengZhiLin on 2021/8/15 10:51 */ public class Test { public static void main(String[] args) throws Exception{ //- 通过类名.class获得 Class<Person> c1 = Person.class; //- 通过对象名.getClass()方法获得 Person p = new Person(); Class<? extends Person> c2 = p.getClass(); //- 通过Class类的静态方法获得: static Class forName("类全名") Class<?> c3 = Class.forName("com.itheima.demo7_获取Class对象.Person"); System.out.println(c1); System.out.println(c2); System.out.println(c3); System.out.println(c1 == c2);// true System.out.println(c2 == c3);// true } }
2.5 Class类常用方法
String getSimpleName(); 获得类名字符串:类名
String getName(); 获得类全名:包名+类名
T newInstance() ; 创建Class对象关联类的对象---相当于调用该类的空参构造方法
-
示例代码
/** * Created by PengZhiLin on 2021/8/15 10:54 */ public class Test { public static void main(String[] args)throws Exception { // 获取Person类的字节码对象 Class<Person> c = Person.class; //String getSimpleName(); 获得类名字符串:类名 System.out.println("类的名称:" + c.getSimpleName()); //String getName(); 获得类全名:包名+类名 System.out.println("类的全名: " + c.getName()); //T newInstance() ; 创建Class对象关联类的对象---相当于调用该类的空参构造方法 Person p = c.newInstance(); // Person p = new Person(); System.out.println(p); } }
2.6 反射之操作构造方法
Constructor类概述
Constructor类概述
* 类中的每一个构造方法都是一个Constructor类的对象
通过反射获取类的构造方法
Class类的方法
1. Constructor getConstructor(Class... parameterTypes)
* 根据参数类型获得对应的Constructor对象。
* 只能获得public修饰的构造方法
2. Constructor getDeclaredConstructor(Class... parameterTypes)---推荐
* 根据参数类型获得对应的Constructor对象
* 可以是public、protected、(默认)、private修饰符的构造方法。
3. Constructor[] getConstructors()
获得类中的所有构造方法对象,只能获得public的
4. Constructor[] getDeclaredConstructors()---推荐
获得类中的所有构造方法对象
可以是public、protected、(默认)、private修饰符的构造方法。
/**
* Created by PengZhiLin on 2021/8/15 10:58
*/
public class Person {
String name;
int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name) {
this.name = name;
}
private Person(int age) {
this.age = age;
}
}
/**
* Created by PengZhiLin on 2021/8/15 10:58
*/
public class Test {
public static void main(String[] args) throws Exception {
// 获取Person类的字节码对象
Class<Person> c = Person.class;
// 需求:获取Person类的构造方法
// 获取第1个构造方法
Constructor<Person> con1 = c.getDeclaredConstructor();
System.out.println("con1:" + con1);
// 获取第2个构造方法
Constructor<Person> con2 = c.getDeclaredConstructor(String.class, int.class);
System.out.println("con2:" + con2);
// 获取第3个构造方法
Constructor<Person> con3 = c.getDeclaredConstructor(String.class);
System.out.println("con3:" + con3);
// 获取第4个构造方法
Constructor<Person> con4 = c.getDeclaredConstructor(int.class);
System.out.println("con4:" + con4);
// 需求:获取所有的构造方法
Constructor<?>[] arr = c.getDeclaredConstructors();
for (Constructor<?> con : arr) {
System.out.println(con);
}
}
}
通过反射执行构造方法
Constructor对象常用方法
1. T newInstance(Object... initargs)
根据指定的参数创建对象
参数:被执行的构造方法需要的实际参数
2. void setAccessible(true)
设置"暴力反射"——是否取消权限检查,true取消权限检查,false表示不取消
示例代码
/**
* Created by PengZhiLin on 2021/8/15 10:58
*/
public class Test {
public static void main(String[] args) throws Exception {
// 获取Person类的字节码对象
Class<Person> c = Person.class;
// 需求:获取Person类的构造方法
// 获取第1个构造方法
Constructor<Person> con1 = c.getDeclaredConstructor();
System.out.println("con1:" + con1);
// 获取第2个构造方法
Constructor<Person> con2 = c.getDeclaredConstructor(String.class, int.class);
System.out.println("con2:" + con2);
// 获取第3个构造方法
Constructor<Person> con3 = c.getDeclaredConstructor(String.class);
System.out.println("con3:" + con3);
// 获取第4个构造方法
Constructor<Person> con4 = c.getDeclaredConstructor(int.class);
System.out.println("con4:" + con4);
// 需求:获取所有的构造方法
Constructor<?>[] arr = c.getDeclaredConstructors();
for (Constructor<?> con : arr) {
System.out.println(con);
}
// 通过反射执行构造方法
// 执行第1个构造方法
Person p1 = con1.newInstance();
System.out.println("p1:" + p1);
// 执行第2个构造方法
Person p2 = con2.newInstance("黑马程序员", 18);
System.out.println("p2:" + p2);
// 执行第3个构造方法
Person p3 = con3.newInstance("传智播客");
System.out.println("p3:" + p3);
// 执行第4个构造方法
con4.setAccessible(true);// 取消权限检查
Person p4 = con4.newInstance(19);
System.out.println("p4:" + p4);
}
}
2.7 反射之操作成员方法
Method类概述
Method类概述
* 每一个成员方法都是一个Method类的对象。
通过反射获取类的成员方法
Class类中与Method相关的方法
* Method getMethod(String name,Class... args);
* 根据方法名和参数类型获得对应的成员方法对象,只能获得public的
* Method getDeclaredMethod(String name,Class... args);----->推荐
* 根据方法名和参数类型获得对应的成员方法对象,包括public、protected、(默认)、private的
参数1:要获取的方法的方法名
参数2:要获取的方法的形参类型的Class对象
* Method[] getMethods();
* 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
* Method[] getDeclaredMethods();----->推荐
* 获得类中的所有成员方法对象,返回数组,只获得本类的,包括public、protected、(默认)、private的
/**
* Created by PengZhiLin on 2021/8/15 11:17
*/
public class Person {
public void method1() {
System.out.println("无参数无返回值的成员方法...");
}
public void method2(int num) {
System.out.println("有参数无返回值的成员方法...,参数num的值:" + num);
}
public int method3() {
System.out.println("无参数有返回值的成员方法...");
return 100;
}
private int method4(String name, int age) {
System.out.println("有参数有返回值的成员方法...参数name的值:" + name + "参数age的值:" + age);
return 200;
}
}
/**
* Created by PengZhiLin on 2021/8/15 11:17
*/
public class Test {
public static void main(String[] args) throws Exception{
// 获取Person类的字节码对象
Class<Person> c = Person.class;
// 需求: 获取成员方法对象
// 获得第1个成员方法
Method m1 = c.getDeclaredMethod("method1");
// 获得第2个成员方法
Method m2 = c.getDeclaredMethod("method2", int.class);
// 获得第3个成员方法
Method m3 = c.getDeclaredMethod("method3");
// 获得第4个成员方法
Method m4 = c.getDeclaredMethod("method4", String.class, int.class);
System.out.println(m1);
System.out.println(m2);
System.out.println(m3);
System.out.println(m4);
// 需求:获取所有成员方法对象
Method[] arr = c.getDeclaredMethods();
for (Method method : arr) {
System.out.println(method);
}
}
}
通过反射执行成员方法
Method对象常用方法
* Object invoke(Object obj, Object... args)
* 参数1:调用该方法的对象
* 参数2:调用该法时传递的实际参数
返回值:该方法执行完毕后的返回值
* void setAccessible(true)
设置"暴力访问"——是否取消权限检查,true取消权限检查,false表示不取消
示例代码
/**
* Created by PengZhiLin on 2021/8/15 11:17
*/
public class Test {
public static void main(String[] args) throws Exception {
// 获取Person类的字节码对象
Class<Person> c = Person.class;
// 需求: 获取成员方法对象
// 获得第1个成员方法
Method m1 = c.getDeclaredMethod("method1");
// 获得第2个成员方法
Method m2 = c.getDeclaredMethod("method2", int.class);
// 获得第3个成员方法
Method m3 = c.getDeclaredMethod("method3");
// 获得第4个成员方法
Method m4 = c.getDeclaredMethod("method4", String.class, int.class);
System.out.println(m1);
System.out.println(m2);
System.out.println(m3);
System.out.println(m4);
// 需求:获取所有成员方法对象
Method[] arr = c.getDeclaredMethods();
for (Method method : arr) {
System.out.println(method);
}
System.out.println("--------------");
// 通过反射执行成员方法
// 通过反射创建Person对象
Person p = c.newInstance();
//Person p = c.getDeclaredConstructor().newInstance();
// 通过反射执行第1个成员方法
m1.invoke(p);// 等价于:p.method1();
// 通过反射执行第2个成员方法
m2.invoke(p, 10);// 等价于:p.method2(10);
// 通过反射执行第3个成员方法
Object res1 = m3.invoke(p);// 等价于: Object res1 = p.method3();
System.out.println("res1:" + res1);
// 通过反射执行第4个成员方法
m4.setAccessible(true);// 取消权限检查
Object res4 = m4.invoke(p, "张三", 19);
System.out.println("res4:"+res4);// 等价于: Object res2 = p.method4("张三", 19);
}
}
2.8 反射之操作成员变量【自学】
Field类概述
Field类概述
* 每一个成员变量都是一个Field类的对象。
通过反射获取类的成员变量
Class类中的方法
* Field getField(String name);
* 根据成员变量名获得对应Field对象,只能获得public修饰
参数:属性名
* Field getDeclaredField(String name);----->推荐
* 根据成员变量名获得对应Field对象,包括public、protected、(默认)、private的
参数:属性名
* Field[] getFields();
* 获得所有的成员变量对应的Field对象,只能获得public的
* Field[] getDeclaredFields();---->推荐
* 获得所有的成员变量对应的Field对象,包括public、protected、(默认)、private的
/**
* Created by PengZhiLin on 2021/8/15 11:50
*/
public class Person {
public String name;
private int age;
}
/**
* Created by PengZhiLin on 2021/8/15 11:52
*/
public class Test {
public static void main(String[] args)throws Exception {
// 获得Person类的字节码对象
Class<Person> c = Person.class;
// 通过反射获取类的成员变量
// 获得第1个成员变量
Field f1 = c.getDeclaredField("name");
// 获得第2个成员变量
Field f2 = c.getDeclaredField("age");
System.out.println(f1);
System.out.println(f2);
// 获取所有的成员变量
Field[] arr = c.getDeclaredFields();
for (Field field : arr) {
System.out.println(field);
}
}
}
通过反射访问成员变量
Field对象常用方法
给对象的属性赋值的方法
void set(Object obj, Object value) ----->推荐
参数1: 给哪个对象的属性赋值---该类的对象
参数2: 给属性赋的值
void setInt(Object obj, int i)
void setLong(Object obj, long l)
void setBoolean(Object obj, boolean z)
void setDouble(Object obj, double d)
...
获取对象属性的值的方法
Object get(Object obj) ----->推荐
int getInt(Object obj)
long getLong(Object obj)
boolean getBoolean(Object ob)
double getDouble(Object obj)
...
void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。 ----->推荐
Class getType(); 获取属性的类型,返回Class对象。
setXxx方法都是给对象obj的属性设置使用,针对不同的类型选取不同的方法。
getXxx方法是获取对象obj对应的属性值的,针对不同的类型选取不同的方法。
示例代码
/**
* Created by PengZhiLin on 2021/8/15 11:52
*/
public class Test {
public static void main(String[] args) throws Exception {
// 获得Person类的字节码对象
Class<Person> c = Person.class;
// 通过反射获取类的成员变量
// 获得第1个成员变量
Field f1 = c.getDeclaredField("name");
// 获得第2个成员变量
Field f2 = c.getDeclaredField("age");
System.out.println(f1);
System.out.println(f2);
// 获取所有的成员变量
Field[] arr = c.getDeclaredFields();
for (Field field : arr) {
System.out.println(field);
}
// 通过反射得到Person类的对象
Person p = c.newInstance();
// 通过反射访问成员变量
// 访问第1个成员变量
f1.set(p, "张三");
System.out.println("p对象的name属性值:" + f1.get(p));
// 访问第2个成员变量
f2.setAccessible(true);// 取消权限检查
f2.set(p,18);
System.out.println("p对象的age属性值:" + f2.get(p));
// 获取成员变量的类型
System.out.println(f1.getType());
System.out.println(f2.getType());
}
}
第三章 注解
3.1 注解概述
注解概述
-
注解(annotation),是一种代码级别的说明,和类 接口平级关系.
-
注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无标记,看你的程序有什么标记,就去干相应的事
-
我们之前使用过的注解:
1).@Override:子类重写方法时——编译时起作用
2).@FunctionalInterface:函数式接口——编译时起作用
3).@Test:JUnit的测试注解——运行时起作用
-
注解的作用
-
生成帮助文档:@author和@version
-
执行编译期的检查 例如:@Override
-
框架的配置(框架=代码+配置)
- 具体使用请关注框架课程的内容的学习。
3.2 JDK提供的三个基本的注解
@Override:描述方法的重写.
@SuppressWarnings:压制\忽略警告.
@Deprecated:标记过时
/**
* Created by PengZhiLin on 2021/8/15 12:07
*/
public class Test {
public static void main(String[] args) {
@SuppressWarnings("all")
int num = 10;
method1();
}
@Override
public String toString() {
return super.toString();
}
@Deprecated
public static void method1(){
}
}
3.3 自定义注解
-
格式
public @interface 注解名{ 注解属性
}
- 注解属性
- 格式: `数据类型 属性名();`
- 属性类型
1.基本类型
2.String
3.Class类型
4.注解类型
5. 枚举类型
6.以上类型的一维数组类型
- 示例代码
```java
// 有属性的注解
public @interface MyAnnotation2 {
// 1.基本类型(4类8种)
int a();
byte b();
// 2.String
String str();
// 3.Class类型
Class c();
// 4.注解类型
MyAnnotation1 ma();
// 5. 枚举类型
Sex sex();
// 6.以上类型的一维数组类型
int[] arr1();
String[] arr2();
Class[] arr3();
MyAnnotation1[] arr4();
Sex[] arr5();
}
/**
* Created by PengZhiLin on 2021/8/15 12:22
*/
// 无属性的注解
public @interface MyAnnotation1 {
}
3.4 使用注解
- 使用注解:
如果一个注解没有属性,那么就不需要给注解属性赋值,直接使用即可 @注解名
如果一个注解中有属性,那么使用注解的时候一定要给注解所有属性赋值 @注解名(属性名=值,属性名2=值2,...)
如何给注解属性赋值:
@注解名(属性名=值,属性名2=值2,...)
-
案例演示
// 没有属性的注解 public @interface MyAnnotation1 { } // 有属性的注解 public @interface MyAnnotation2 { String name(); int age(); }
/** * Created by PengZhiLin on 2021/8/15 14:31 */ @MyAnnotation1 @MyAnnotation2(name="itheima",age=100) public class Test { @MyAnnotation1 @MyAnnotation2(name="itheima",age=100) String str; @MyAnnotation1 @MyAnnotation2(name="itheima",age=100) public void method(){ @MyAnnotation1 @MyAnnotation2(name="itheima",age=100) int num = 10; } }
3.5 使用注解的注意事项
-
一旦注解有属性了,使用注解的时候,属性必须有值
-
若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
-
如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略
-
注解属性可以有默认值 格式:属性类型 属性名() defaul t 默认值;
// 有属性的注解 public @interface MyAnnotation1 { String name(); int age(); } // 有属性的注解 public @interface MyAnnotation2 { String[] names(); } // 有属性的注解 public @interface MyAnnotation3 { String value(); } // 有属性的注解 public @interface MyAnnotation33 { String[] value(); } // 有属性的注解 public @interface MyAnnotation4 { String name() default "itheima"; int age() default 100; }
/** * Created by PengZhiLin on 2021/8/15 14:36 */ public class Test { //- 一旦注解有属性了,使用注解的时候,属性必须有值 @MyAnnotation1(name="itheima",age=100) public void method1(){ } //- 若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{} //@MyAnnotation2(names={"itheima"}) // 标准写法 @MyAnnotation2(names="itheima") // 省略写法 public void method2(){ } //- 如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略 //@MyAnnotation3(value="java") // 标准写法 @MyAnnotation3("java") // 省略写法 public void method3(){ } //@MyAnnotation33(value={"java"}) // 标准写法 @MyAnnotation33("java") // 标准写法 public void method33(){ } //- 注解属性可以有默认值 格式:属性类型 属性名() default 默认值; //@MyAnnotation4 @MyAnnotation4(name="itcast") public void method4(){ } }
3.6 元注解
什么是元注解
定义在注解上的注解(修饰注解的注解)
常见的元注解
@Target:表示该注解作用在什么上面(位置),默认注解可以在任何位置. 值为:ElementType的枚举值
METHOD:方法
TYPE:类 接口
FIELD:字段
CONSTRUCTOR:构造方法声明
LOCAL_VARIABLE:局部变量
....
/**
* Created by PengZhiLin on 2021/8/15 14:49
*/
@Target(value={ElementType.TYPE,ElementType.METHOD})
public @interface MyAnnotation1 {
}
@Retention:定义该注解保留到那个代码阶段, 值为:RetentionPolicy类型,默认只在源码阶段保留
SOURCE:只在源码上保留(默认)
CLASS:在源码和字节码上保留
RUNTIME:在所有的阶段都保留
.java (源码阶段) ----编译---> .class(字节码阶段) ----加载内存--> 运行(RUNTIME)
案例:
//@Retention(value= RetentionPolicy.SOURCE) // 该注解只能保留到源码阶段
//@Retention(value= RetentionPolicy.CLASS) // 该注解只能保留到字节码阶段
@Retention(value= RetentionPolicy.RUNTIME) // 该注解只能保留到运行阶段
public @interface MyAnnotation2 {
}
3.7 注解解析
java.lang.reflect.AnnotatedElement接口: Class、Method、Field、Constructor等实现了AnnotatedElement
- T getAnnotation(Class
annotationType):得到指定类型的注解对象。没有返回null。 - boolean isAnnotationPresent(Class<?extends Annotation> annotationType):判断指定的注解有没有。
// 有属性的注解
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name();
int age();
}
/**
* Created by PengZhiLin on 2021/8/15 14:59
*/
public class Test {
public static void main(String[] args) throws Exception {
// 需求:获取method1方法上注解的属性值
// 1.获取method1方法上的注解
// 1.1 获取Test类的字节码对象
Class<Test> c = Test.class;
// 1.2 获取Test类中method1方法对应的Method对象
Method m1 = c.getDeclaredMethod("method1");
// 1.3 根据Method对象调用getAnnotation方法获得注解对象
MyAnnotation annotation = m1.getAnnotation(MyAnnotation.class);
// 2.根据method1方法上的注解获取属性值
System.out.println(annotation.name());
System.out.println(annotation.age());
// 需求: 执行Test类中含有MyAnnotation注解的所有方法
// 1.获取Test类中所有的方法
Method[] arr = c.getDeclaredMethods();
// 2.通过反射创建Test对象
Test test = c.newInstance();
// 3.循环遍历所有的方法
for (Method m : arr) {
// 3.判断遍历出来的方法上是否有MyAnnotation注解
boolean flag = m.isAnnotationPresent(MyAnnotation.class);
// 4.如果有,就执行
if (flag){
m.invoke(test);
}
}
}
@MyAnnotation(name = "itheima", age = 100)
public void method1() {
System.out.println("method1方法....");
}
@MyAnnotation(name = "itheima", age = 100)
public void method2() {
System.out.println("method2方法....");
}
public void method3() {
System.out.println("method3方法....");
}
}
3.8 完成注解的MyTest案例
需求
在一个类(测试类,TestDemo)中有三个方法,其中两个方法上有@MyTest,另一个没有.还有一个主测试类(MainTest)中有一个main方法. 在main方法中,让TestDemo类中含有@MyTest方法执行. 自定义@MyTest, 模拟单元测试.
思路分析
-
定义两个类和一个注解
-
在MainTest的main()方法里面:
//1.获得TestDemo字节码对象
//2.反射获得TestDemo里面的所有的方法
//3.遍历方法对象的数组. 判断是否有@MyTest(isAnnotationPresent)
//4.有就执行(method.invoke())
代码实现
- MyTest.java
/**
* Created by PengZhiLin on 2021/8/15 15:16
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
- TestDemo.java
/**
* Created by PengZhiLin on 2021/8/15 15:16
*/
public class TestDemo {
@MyTest
public void test1(){
System.out.println("测试方法test1...");
}
public void test2(){
System.out.println("测试方法test2...");
}
@MyTest
public void test3(){
System.out.println("测试方法test3...");
}
}
- MainTest.java
/**
* Created by PengZhiLin on 2021/8/15 15:17
*/
public class MainTest {
public static void main(String[] args) throws Exception{
// 1.获得TestDemo类的字节码对象
Class<TestDemo> c = TestDemo.class;
// 2.获得TestDemo类中的所有方法
Method[] arr = c.getDeclaredMethods();
// 通过反射创建TestDemo对象
TestDemo td = c.newInstance();
// 3.循环遍历所有的方法
for (Method m : arr) {
// 4.判断方法上是否有@MyTest注解
boolean flag = m.isAnnotationPresent(MyTest.class);
// 5.如果就,就执行该方法
if (flag){
m.invoke(td);
}
}
}
}
第四章 动态代理
代理模式概述
为什么要有“代理”?生活中就有很多代理的例子,例如,我现在需要出国,但是我不愿意自己去办签证、预定机票和酒店(觉得麻烦 ,那么就可以找旅行社去帮我办,这时候旅行社就是代理,而我自己就是被代理了。
代理模式的定义:被代理者没有能力或者不愿意去完成某件事情,那么就需要找个人代替自己去完成这件事,这个人就是代理者, 所以代理模式包含了3个角色: 被代理角色 代理角色 抽象角色(协议)
静态代理:
/**
* Created by PengZhiLin on 2021/8/15 15:46
*/
public interface FindHappy {
void happy();
}
/**
* Created by PengZhiLin on 2021/8/15 15:46
*/
public class JinLian implements FindHappy {
@Override
public void happy() {
System.out.println("金莲正在happy...");
}
}
/**
* Created by PengZhiLin on 2021/8/15 15:46
*/
public class YanPoXi implements FindHappy {
@Override
public void happy() {
System.out.println("阎婆惜正在happy...");
}
}
/**
* Created by PengZhiLin on 2021/8/15 15:48
*/
public class WangPo implements FindHappy {
FindHappy findHappy;
public WangPo(FindHappy findHappy) {
this.findHappy = findHappy;
}
@Override
public void happy() {
System.out.println("王婆以做衣服的名义开好房间...");
findHappy.happy();
System.out.println("王婆打扫战场...");
}
}
/**
* Created by PengZhiLin on 2021/8/15 15:46
*/
public class Test {
public static void main(String[] args) {
// 创建金莲对象---被代理者
JinLian jinLian = new JinLian();
//jinLian.happy();
// 创建王婆对象---代理者
WangPo wp = new WangPo(jinLian);
wp.happy();
System.out.println("---------------");
// 创建阎婆惜对象---被代理者
YanPoXi ypx = new YanPoXi();
// 创建王婆对象---代理者
WangPo wp1 = new WangPo(ypx);
wp1.happy();
}
}
动态代理介绍
-
概述 : 动态代理就是在程序运行期间,直接通过反射生成一个代理对象,代理对象所属的类是不需要存在的
-
动态代理的获取:
jdk提供一个Proxy类可以直接给实现接口类的对象直接生成代理对象
动态代理相关api介绍
Java.lang.reflect.Proxy类可以直接生成一个代理对象
- Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成一个代理对象
- 参数1:ClassLoader loader 被代理对象的类加载器
- 参数2:Class<?>[] interfaces 被代理实现的所有接口 的Class对象
- 参数3:InvocationHandler h (接口)执行处理类
- 返回值: 代理对象
- 前2个参数是为了帮助在jvm内部生成被代理对象的代理对象,第3个参数,用来监听代理对象调用方法,帮助我们调用方法
- InvocationHandler中的Object invoke(Object proxy, Method method, Object[] args)方法:调用代理类的任何方法,此方法都会执行
- 参数1:代理对象(慎用)
- 参数2:当前执行的方法
- 参数3:当前执行的方法运行时传递过来的参数
- 返回值:当前方法执行的返回值
案例演示
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Created by PengZhiLin on 2021/8/15 15:59
*/
public class Test {
public static void main(String[] args) {
// 1.创建金莲对象
JinLian jinLian = new JinLian();
// 程序运行到这里,直接通到反射生成一个代理对象
// 2.获得被代理类的类加载器
ClassLoader classLoader = jinLian.getClass().getClassLoader();
// 3.获得被代理类实现的所有接口的Class对象
Class<?>[] interfaces = jinLian.getClass().getInterfaces();
// 4.处理接口---用来监听代理对象的
InvocationHandler iv = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 只要代理对象调用方法,就会来到这里执行
// 参数1: 代理对象(慎用)
// 参数2: 代理对象调用的方法
// 参数3: 代理对象调用方法时传递的实际参数
// 返回值: 代理对象调用的方法的返回值
if (method.getName().equals("happy")){
System.out.println("王婆以做衣服的名义开好房间...");
//jinLian.happy();
method.invoke(jinLian,args);
System.out.println("王婆打扫战场...");
}
if (method.getName().equals("toString")){
System.out.println("toString...");
}
return null;
}
};
// 5.动态生成一个代理对象
FindHappy proxy = (FindHappy)Proxy.newProxyInstance(classLoader,interfaces,iv);
// 6.使用代理对象
proxy.happy();
System.out.println("-----------");
proxy.toString();
}
}
第五章JDK8新特性
5.1 方法引用
方法引用概述
- 方法引用使用一对冒号 :: , 方法引用就是用来在一定的情况下,替换Lambda表达式
方法引用基本使用
- 使用场景:
- 如果一个Lambda表达式大括号中的代码和另一个方法中的代码一模一样,那么就可以使用方法引用把该方法引过来,从而替换Lambda表达式
- 如果一个Lambda表达式大括号中的代码就是调用另一方法,那么就可以使用方法引用把该方法引过来,从而替换Lambda表达式
/**
* Created by PengZhiLin on 2021/8/15 16:37
*/
public class Test {
public static void printStr(){
System.out.println("Hello World...");
System.out.println("Hello World...");
System.out.println("Hello World...");
}
public static void main(String[] args) {
// 创建并启动线程
/* new Thread(()->{
System.out.println("Hello World...");
System.out.println("Hello World...");
System.out.println("Hello World...");
}).start();*/
/*new Thread(()->{
Test.printStr();
}).start();*/
new Thread(Test::printStr).start();
}
}
5.2 方法引用的分类
构造方法引用
/**
* Created by PengZhiLin on 2021/8/15 16:43
*/
public class Test1_引用构造方法 {
public static void main(String[] args) {
/*
引入构造方法的格式: 类名::new
*/
//创建集合
ArrayList<String> list = new ArrayList<>();
list.add("杨紫");
list.add("迪丽热巴");
list.add("陈钰琪");
// 需求: 使用Stream流把集合中的元素转换为Person对象,打印输出
//list.stream().map((String name)->{return new Person(name);}).forEach(p->System.out.println(p));
// map方法中的Lambda表达式大括号中就是调用Person类的构造方法,可以使用方法引用
list.stream().map(Person::new).forEach(p->System.out.println(p));
}
}
静态方法引用
/**
* Created by PengZhiLin on 2021/8/15 16:50
*/
public class Test2_引用静态方法 {
public static void main(String[] args) {
/*
引用静态方法的格式: 类名::方法名
*/
//创建集合
ArrayList<String> list = new ArrayList<>();
list.add("110");
list.add("111");
list.add("112");
// 需求: 使用Stream流把集合中的元素转换为int值,打印输出
//list.stream().map((String str)->{return Integer.parseInt(str);}).forEach(i-> System.out.println(i+1));
// map方法的Lambda表达式大括号中就是调用Integer的parseInt方法,可以使用方法引用
list.stream().map(Integer::parseInt).forEach(i-> System.out.println(i+1));
}
}
有参数成员方法引用
-
成员方法有参数
/** * Created by PengZhiLin on 2021/8/15 16:53 */ public class Test3_引用有参数的成员方法 { public static void main(String[] args) { /* 成员方法有参数引用: 对象名::方法名 */ //创建集合 ArrayList<String> list = new ArrayList<>(); list.add("杨紫"); list.add("迪丽热巴"); list.add("陈钰琪"); // 需求:使用Stream流把集合中所有元素打印输出 //list.stream().forEach(name-> System.out.println(name)); // froEach方法的Lambda表达式就是调用System.out的println()方法,可以使用方法引用 list.stream().forEach(System.out::println); } }
没有参数成员方法引用
-
成员方法没有参数
/** * Created by PengZhiLin on 2021/8/15 16:56 */ public class Test4_引用没有参数的成员方法 { public static void main(String[] args) { /* 成员方法没有参数引用: 类名::方法名 */ //创建集合 ArrayList<String> list = new ArrayList<>(); list.add("杨紫"); list.add("迪丽热巴"); list.add("陈钰琪"); // 需求: 使用Stream流把集合中的元素转换为该元素对应的字符长度,打印输出 //list.stream().map((String name)->{return name.length();}).forEach(System.out::println); // map方法的Lambda表达式大括号中就是调用String类的length方法,可以使用方法引用 list.stream().map(String::length).forEach(System.out::println); } }
-
小结:
1.首先分析Lambda表达式大括号中代码(是否就是调用另一个方法\代码和另一个方法一模一样) 2.如果是,就可以使用方法引用替换Lambda表达式 3.确定引用的方法是什么类型(构造方法,静态方法,没有参数的成员方法,有参数的成员方法) 4.根据方法引用的格式去应用方法 构造方法: 类名::new 静态方法: 类名::方法名 没有参数的成员方法: 类名::方法名 有参数的成员方法: 对象名::方法名
5.3 Base64
Base64概述
- Base64是jdk8提出的一个新特性,可以用来进行按照一定规则编码和解码
Base64编码和解码的相关方法
-
编码的步骤:
- 获取编码器
- 调用方法进行编码
-
解码步骤:
- 获取解码器
- 调用方法进行解码
-
Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:
- 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
- URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
- MIME:输出隐射到MIME友好格式。输出每行不超过76字符,并且使用'\r'并跟随'\n'作为分割。编码输出最后没有行分割。
-
获取编码器和解码器的方法
static Base64.Decoder getDecoder() 基本型 base64 解码器。 static Base64.Encoder getEncoder() 基本型 base64 编码器。 static Base64.Decoder getMimeDecoder() Mime型 base64 解码器。 static Base64.Encoder getMimeEncoder() Mime型 base64 编码器。 static Base64.Decoder getUrlDecoder() Url型 base64 解码器。 static Base64.Encoder getUrlEncoder() Url型 base64 编码器。
-
编码和解码的方法:
Encoder编码器: encodeToString(byte[] bys)编码 Decoder解码器: decode(String str) 解码
案例演示
- 基本
public class Test1 {
public static void main(String[] args) {
// 使用基本型的编码器和解码器对数据进行编码和解码:
// 1.获取编码器
Base64.Encoder encoder = Base64.getEncoder();
// 2.对字符串进行编码
String str = "name=中国?password=123456";
String str1 = encoder.encodeToString(str.getBytes());
// 3.打印输出编码后的字符串
System.out.println("编码后的字符串:"+str1);
// 4.获取解码器
Base64.Decoder decoder = Base64.getDecoder();
// 5.对编码后的字符串进行解码
byte[] bys = decoder.decode(str1);
String str2 = new String(bys);
// 6.打印输出解码后的字符串
System.out.println("解码后的字符串:"+str2);
}
}
- URL
public class Test2 {
public static void main(String[] args) {
// 使用URL型的编码器和解码器对数据进行编码和解码:
// 1.获取编码器
Base64.Encoder encoder = Base64.getUrlEncoder();
// 2.对字符串进行编码
String str = "name=中国?password=123456";
String str1 = encoder.encodeToString(str.getBytes());
// 3.打印输出编码后的字符串
System.out.println("编码后的字符串:"+str1);
// 4.获取解码器
Base64.Decoder decoder = Base64.getUrlDecoder();
// 5.对编码后的字符串进行解码
byte[] bys = decoder.decode(str1);
String str2 = new String(bys);
// 6.打印输出解码后的字符串
System.out.println("解码后的字符串:"+str2);
}
}
- MIME
public class Test3 {
public static void main(String[] args) {
// 使用MIME型的编码器和解码器对数据进行编码和解码:
// 1.获取编码器
Base64.Encoder encoder = Base64.getMimeEncoder();
// 2.对字符串进行编码
String str = "";
for (int i = 0; i < 100; i++) {
str += i;
}
System.out.println("编码前的字符串:"+str);
String str1 = encoder.encodeToString(str.getBytes());
// 3.打印输出编码后的字符串
System.out.println("编码后的字符串:"+str1);
// 4.获取解码器
Base64.Decoder decoder = Base64.getMimeDecoder();
// 5.对编码后的字符串进行解码
byte[] bys = decoder.decode(str1);
String str2 = new String(bys);
// 6.打印输出解码后的字符串
System.out.println("解码后的字符串:"+str2);
}
}
总结
必须练习:
1.Junit必须掌握--->@Test,@Before,@After,@BeforeClass,@AfterClass
2.反射获取构造方法\成员方法\成员变量,并使用--->必须掌握,特别反射操作成员方法
3.使用注解(带有属性\不带有属性)=-->必须掌握
4.动态代理---->难点\重点\必须掌握
5.建议方法引用理解,Base64
- 能够使用Junit进行单元测试
1.下载Junit的jar包
2.把Junit的jar包拷贝到模块下的lib文件夹中,并添加到classpath路径中
3.编写测试方法
4.在测试方法上书写@Test注解
5.运行测试
- 能够通过反射技术获取Class字节码对象
1.1 类名.class
1.2 对象名.getClass();
1.3 Class.forName("类的全路径");
- 能够通过反射技术获取构造方法对象,并创建对象。
Constructor getDeclaredConstructor(Class... parameterTypes)
Constructor[] getDeclaredConstructors()
T newInstance(Object... initargs)
void setAccessible(true);暴力反射
- 能够通过反射获取成员方法对象,并且调用方法。--------特别
Method getDeclaredMethod(String name,Class...args);
Method[] getDeclaredMethods();
Object invoke(Object obj, Object... args)
void setAccessible(true);暴力反射
- 能够通过反射获取属性对象,并且能够给对象的属性赋值和取值。
Field getDeclaredField(String name);
Field[] getDeclaredFields();
void setAccessible(true);暴力反射
void set(Object obj, Object value)
Object get(Object obj)
- 能够说出注解的作用
作为配置\编译检查
- 能够自定义注解和使用注解
格式:
public @interface 注解名{
属性
}
属性格式:
数据类型 属性名();
数据类型:
1.基本类型
2.String类型
3.Class类型
4.枚举类型
5.注解类型
6.以上类型的一维数组类型
使用注解:
无属性的注解: @注解名
有属性的注解: @注解名(属性名=属性值,...)
注意事项:
1.如果注解属性是数组类型,并且数组的值只有一个,那么大括号可以省略
2.如果注解只有一个属性,并且属性名为value,那么给属性赋值的时候value属性名可以省略
3.注解属性有默认值,可以不给注解属性赋值 格式: 数据类型 注解名() default 属性值;
- 能够说出常用的元注解及其作用
@Target:表示该注解作用在什么上面(位置),默认注解可以在任何位置
@Retention:定义该注解保留到那个代码阶段, 值为:RetentionPolicy类型,==默认只在源码阶段保留==
- 能够解析注解并获取注解中的数据
java.lang.reflect.AnnotatedElement接口: Class、Method、Field、Constructor等实现了该接口
- T getAnnotation(Class<T> annotationType):得到指定类型的注解引用。没有返回null。
- boolean isAnnotationPresent(Class<?extends Annotation> annotationType):
判断指定的注解有没有。
获取注解的属性值: 注解对象.属性名();
- 能够完成注解的MyTest案例
1.获取类的字节码对象
2.获取该类的所有方法
3.循环遍历所有的方法
4.判断遍历出来的方法是否包含指定的注解,如果包含,就执行该方法
- 能够说出动态代理模式的作用
作用: 为了增强被代理类的方法
- 能够使用Proxy的方法生成代理对象
Java.lang.reflect.Proxy类可以直接生成一个代理对象
- public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成一个代理对象
- 参数1:ClassLoader loader 被代理对象的类加载器
- 参数2:Class<?>[] interfaces 被代理对象的要实现的接口
- 参数3:InvocationHandler h (接口)执行处理类
- 返回值: 代理对象
- 前2个参数是为了帮助在jvm内部生成被代理对象的代理对象,第3个参数,用来监听代理对象调用方法,帮助我们调用方法
- InvocationHandler中的Object invoke(Object proxy, Method method, Object[] args)方法:调用代理类的任何方法,此方法都会执行
- 参数1:代理对象(慎用)
- 参数2:当前执行的方法
- 参数3:当前执行的方法运行时传递过来的参数
- 返回值:当前方法执行的返回值
- 能够使用四种方法的引用
总结:使用方法引用的步骤
1.分析要写的Lambda表达式的大括号中是否就是调用另一个方法
2.如果是,就可以使用方法引用替换,如果不是,就不能使用方法引用
3.确定引用的方法类型(构造方法,成员方法,静态方法,类的成员方法)
4.按照对应的格式去引用:
构造方法: 类名::new
成员方法(有参数): 对象名::方法名
静态方法: 类名::方法名
类的成员方法\成员方法(无参数): 类名::方法名
- 能够使用Base64对基本数据、URL和MIME类型进行编解码
static Base64.Decoder getDecoder() 基本型 base64 解码器。
static Base64.Encoder getEncoder() 基本型 base64 编码器。
static Base64.Decoder getUrlDecoder() Url型 base64 解码器。
static Base64.Encoder getUrlEncoder() Url型 base64 编码器。
static Base64.Decoder getMimeDecoder() Mime型 base64 解码器。
static Base64.Encoder getMimeEncoder() Mime型 base64 编码器。
Encoder编码器: encodeToString(byte[] bys)编码
Decoder解码器: decode(String str) 解码