junit,反射,注解,正则表达式(基础加强)
学习目标
01_能够通过反射技术获取Class字节码对象
02_能够通过反射技术获取构造方法对象,并创建对象。
03_能够通过反射获取成员方法对象,并且调用方法。
04_能够通过反射获取属性对象,并且能够给对象的属性赋值和取值。
06_能够说出注解的作用
07_能够自定义注解和使用注解
08_能够说出常用的元注解及其作用
09_能够解析注解并获取注解中的数据
10_可以使用Junit测试框架
11_可以理解正则表达式
第01章 反射【重点】
1.1反射的概述
1.1.1什么是反射
反射是一种机制/功能,利用该机制/功能可以在程序运行过程中对类进行解剖并操作类中的构造方法,成员方法,成员属性。
1.1.2反射的应用场景
- 开发工具中写代码时的提示
开发工具之所能够把该对象的方法和属性展示出来就使用利用了反射机制对该对象所有类进行了解剖获取到了类中的所有方法和属性信息,这是反射在IDE中的一个使用场景。
- 各种框架的设计
以上三个图标上面的名字就是Java的三大框架,简称SSH。
这三大框架的内部实现也大量使用到了反射机制,所以要想学好这些框架,则必须要
求对反射机制熟练了。
1.1.3使用反射机制解剖类的前提
必须先要获取到该类的字节码文件对象,即Class类型对象。关于Class描述字节码
文件如下图所示:
说明:
- Java中使用Class类表示class文件。
- 任何一个class文件都是Class类的一个对象。
1.2获取Class对象的三种方式
1.2.1通过类名.class获取
User.java /**
|
Demo01.java /**
|
1.2.2通过Object类的成员方法getClass()方法获取
Demo01.java /**
|
1.2.3通过Class.forName("全限定类名")方法获取
Demo01.java /**
|
1.3获取Class对象的信息
知道怎么获取Class对象之后,接下来就介绍几个Class类中常用的方法了。
1.3.1获取简单类名
String getSimpleName(); 获取简单类名,只是类名,没有包
Demo02.java /** |
1.3.2获取完整类名
String getName(); 获取完整类名,包含包名 + 类名
Demo02.java /** |
1.3.3创建对象
T newInstance() ; 创建此 Class对象所表示的类的一个新实例。要求:类必须有
public的无参数构造方法
Demo02.java /** |
1.4获取Class对象的Constructor信息
一开始在阐述反射概念的时候,我们说到利用反射可以在程序运行过程中对类进行
解剖并操作里面的成员。而一般常操作的成员:构造方法,成员方法,成员属性,
那么接下来看看怎么利用反射来操作这些成员以及操作这些成员能干什么,先来看
看怎么操作构造方法。而要通过反射操作类的构造方法,我们需要先知道一个
Constructor类。
1.4.1 Constructor类概述
Constructor是构造方法类,类中的每一个构造方法都是Constructor的对象,通过
Constructor对象可以实例化对象。
1.4.2 Class类中与Constructor相关方法
1. Constructor[] getConstructors()
获取所有的public修饰的构造方法
2. Constructor[] getDeclaredConstructors()
获取所有构造方法,包括privat修饰的
3. Constructor getConstructor(Class... parameterTypes)
根据参数类型获取构造方法对象,只能获得public修饰的构造方法
4. Constructor getDeclaredConstructor(Class... parameterTypes)
根据参数类型获取构造方法对象,包括private修饰的构造方法
1.4.3 Constructor类中常用方法
1. T newInstance(Object... initargs)
根据指定参数创建对象
2. void setAccessible(true)
暴力反射,设置为可以直接访问私有类型的构造方法
1.4.4示例代码
User.java /**
|
Demo03.java /**
|
1.5获取Class对象的Method信息
操作完构造方法之后,就来看看反射怎么操作成员方法了。同样在操作成员方法
之前我们需要学习一个类:Method类。
1.5.1 Method类概述
Method是方法类,类中的每一个方法都是Method的对象,通过Method对象可以
调用方法。
1.5.2 Class类中与Method相关方法
1. Method[] getMethods()
获取所有的public修饰的成员方法,包括父类
2. Method[] getDeclaredMethods()
获取当前类中所有的方法,包含私有的,不包括父类中
3. Method getMethod("方法名", 方法的参数类型... 类型)
根据方法名和参数类型获得一个方法对象,只能是获取public修饰的
4. Method getDeclaredMethod("方法名", 方法的参数类型... 类型)
根据方法名和参数类型获得一个方法对象,包括private修饰的
1.5.3 Method类中常用方法
1. Object invoke(Object obj, Object... args)
根据参数args调用对象obj的该成员方法
如果obj=null,则表示该方法是静态方法
2. void setAccessible(true)
暴力反射,设置为可以直接调用私有修饰的成员方法
1.5.4示例代码
User.java /**
|
Demo04.java /**
|
1.6获取Class对象的Field信息
1.6.1 Field类概述
Field是属性类,类中的每一个属性都是Field的对象,通过Field对象可以
给对应的属性赋值和取值。
1.6.2 Class类中与Field相关方法
1. Field[] getFields()
获取所有的public修饰的属性对象,返回数组
2. Field[] getDeclaredFields()
获取所有的属性对象,包括private修饰的,返回数组
3. Field getField(String name)
根据属性名获得属性对象,只能获取public修饰的
4. Field getDeclaredField(String name)
根据属性名获得属性对象,包括private修饰的
1.6.3 Field类中常用方法
set通用方法都是给对象obj的属性设置使用
get通用方法是获取对象obj对应的属性值的
void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性
1.6.4 示例代码
User.java /**
|
Demo05.java /**
|
第02章: Junit单元测试
Junit介绍
我们以后在开发中会经常对我们自己书写的代码进行测试,如果都在main方法里边进行测试,main方法就会显得臃肿。而且不便于以后其他人测试以及查看测试数据,用Junit 测试的话,一个方法对应一个测试方法,简单明了,也方便别人查看测试方法以及测试数据。
如果你的类里有多个方法,用main方法测试的话就很不方便,想测试全部方法的话就得把测试代码全部写到main里,或者你测一个重写一次。且更重要的是,这样会使测试代码与运行逻辑代码混在一起,不规范。在一个正规的java项目中(尤其是使用了SSH或者SSM之类的框架),几乎是不会写main方法的,写了就是无用代码。
但是使用junit就方便多了,这是单元测试,你想测哪个方法就写一个对应的测试方法,然后用junit运行。每个方法之间是独立的,非常灵活。而且测试方法一般不会直接写在原类中,而是单独的测试类,这样测试代码就完全与逻辑代码分开了。
Junit是Java语言编写单元测试框架,最直观的理解,就是取代java类中的main方法。Junit属于第三方工具,一般情况下需要导入jar包,而多数Java开发环境都集成了Junit。
使用方式
问题:在之前,我们写了一个方法,我们都需要使用通过main方法来调用。当我们需要测试的方法很多的时候,每次使用main
方法来调用时是非常麻烦的。所以我们有了接下来的单元测试,在开发中使用调用方法就显得特别的简单。
创建java项目,并创建“com.itheima.sh.junit”包
- 编写测试类,简单理解Junit可以用于取代java的main方法
说明:如果以前想让一个方法运行必须在main方法中调用该方法。
2.在测试类JunitDemo01方法上添加注解 @Test(说明:关于什么是注解,下面会详细讲解)
-
3.@Test修饰的方法要求:public void 方法名() {…} ,没有参数。
说明:单元测试的方法必须是public修饰,void表示没有返回值,没有参数列表,否则就会不满足单元测试要求,报异常。
4.添加Idea中集成的Junit库,鼠标放在“@Test”处,使用快捷键“alt+Enter”,点击“Add Junit …”
- 5.使用:选中方法右键,执行当前方法;选中类名右键,执行类中所有方法(方法必须标记@Test)
- 常用注解
@Test,用于修饰需要执行的方法
@Before,测试方法前执行的方法
@After,测试方法后执行的方法
创建一个测试类,代码如下:测试结果如下。
public class JunitDemo02 {
@Test
public void myTest(){
System.out.println("测试 test");
}
@Before
public void myBefore(){
System.out.println("方法前");
}
@After
public void myAfter(){
System.out.println("方法后");
}
}
运行结果:
第03章 注解
3.1注解的概述
3.1.1什么是注解
注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标
记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种
元素上有无标记,看你的程序有什么标记,就去干相应的事,标记可以加在
包、类,属性、方法,方法的参数以及局部变量上定义。
3.1.2注解的应用场景
- 重写父类方法时。
public class Demo01 { |
- 运行一个无main的方法时,带有@Test的成员方法可以直接运行,无需main方法。
public class Demo01 { |
3.框架中的配置。
现在框架都是基于注解的配置,简单方便。
3.1.3常见注解
JDK中内置了很多注解,以下例举一些:
1. @Override 重写
1. 被 override 注解修饰的方法必须是父类中重写方法或接口中的抽象方法
2. @FunctionalInterface(JDK8) : 检测是否是函数式接口
1. 被FunctionalInterface修饰的接口必须是函数式接口,才能通过编译
3. SuppressWarnings 抑制警告
1. 让编译器不要报出警告信息
3.2自定义注解
就算JDK中内置了很多注解,但也不一定能满足所有企业的需求,所以不排除我们需要
根据需求自定义注解。
3.2.1定义格式
public @interface 注解名{
}
如:定义一个名为Student的注解
public @interface Student {
}
以上定义出来的注解就是一个最简单的注解了,但这样的注解意义不大,因为注解中
没有任何内容,就好像我们定义一个类而这个类中没有任何成员变量和方法一样,这
样的类意义也是不大的,所以在定义注解时会在里面添加一些成员来让注解功能更加
强大,这些成员就是属性。接下来就看看怎么给注解添加属性。
3.2.2注解的属性
1. 属性的作用
可以让用户在使用注解时传递参数,让注解的功能更加强大。
2. 属性的格式
格式1:数据类型 属性名();
格式2:数据类型 属性名() default 默认值;
3. 属性定义示例
Book.java /** |
4. 属性适用的数据类型
八种基本数据类型(int,float,boolean,byte,double,char,long,short)
String类型,Class类型,枚举类型,注解类型
以上所有类型的一维数组
3.3使用自定义注解
3.3.1在成员方法上使用自定义注解
Demo02.java @Book |
- 使用注意事项
如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
如果属性有默认值,则使用注解的时候,这个属性如果赋值了,以赋值为准。
如果属性没有默认值,那么在使用注解时一定要给属性赋值。
3.3.2 一个特殊的注解属性value
1. 当注解中只有一个属性且名称是value,在使用注解时给value属性赋值可以直接给
属性值,无论value是单值元素还是数组类型。
Book.java /** |
2.如果注解中除了value属性还有其他属性,且至少有一个属性没有默认值,则在使
用注解给属性赋值时,value属性名不能省略
Book.java /** |
3.4元注解
3.4.1什么是元注解
用来定义注解的注解
@Target(ElementType.METHOD) |
3.4.2元注解的作用
用来说明自定义注解的在类中出现的位置和生命周期
3.4.3常用元注解
3.4.3.1 @Target
作用:定义该注解用在哪个位置,如果不写,默认是类中任何地方都可以使用。
可选的参数值在枚举类ElemenetType中包括:
TYPE: 用在类,接口上
FIELD:用在成员变量上
METHOD: 用在方法上
PARAMETER:用在参数上
CONSTRUCTOR:用在构造方法上
LOCAL_VARIABLE:用在局部变量上
3.4.3.2 @Retention
作用:定义该注解的生命周期
可选的参数值在枚举类型RetentionPolicy中包括:
SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了。
CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没
有,这个是默认值。
RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,
程序可以通过反射获取该注解
3.4.4使用元注解
Book.java /** |
|
3.5解析注解
3.5.1什么是解析注解
通过Java技术,在程序运行时,获取注解中所有属性的过程则称为解析注解。
3.5.2与注解解析相关的接口
Anontation:所有注解类型的公共接口,类似所有类的父类是Object。
AnnotatedElement:定义了与注解解析相关的方法,常用方法以下四个:
- boolean isAnnotationPresent(Class annotationClass); 判断当前对象是否有
指定的注解,有则返回true,否则返回false。
- T getAnnotation(Class<T> annotationClass); 获得当前对象上指定的注解对
象。
- Annotation[] getAnnotations(); 获得当前对象及其从父类上继承的所有的注解对
象。
- Annotation[] getDeclaredAnnotations();获得当前对象上所有的注解对象,不包
括父类的。
3.5.3获取注解数据的原理
注解作用在那个成员上,就通过反射获得该成员的对象来得到它的注解。
如:
如注解作用在方法上,就通过方法(Method)对象得到它的注解
// 得到方法对象 Method method = clazz.getDeclaredMethod("方法名"); // 根据注解名得到方法上的注解对象 Book book = method.getAnnotation(Book.class); |
如注解作用在类上,就通过Class对象得到它的注解
// 获得Class对象 Class c = 类名.class; // 根据注解的Class获得使用在类上的注解对象 Book book = c.getAnnotation(Book.class); |
3.5.4使用反射获取注解的数据
Demo03.java /** |
3.6注解案例
3.6.1需求
模拟JUnit测试的@Test注释
3.6.2分析
1. 模拟JUnit测试的@Test注释,首先需要编写自定义注解@MyTest,并添加元注解,
保证自定义注解只能修饰方法,且在运行时可以获得。
2. 编写测试类,给目标方法使用 @MyTest注解,编写三个方法,其中两个加上@MyTest注解。
3. 编写调用类,使用main方法调用测试类,模拟JUnit的运行,只要有@MyTest注
释的方法都会运行。
3.6.3实现
/** |
/** |
Demo04.java |
第04章 正则表达式
正则表达式
在开发中,通常很多数据都会使用String类存储。原因:操作字符串的功能比较多,比较方便。
在操作String类对象时,会经常遇到对字符串进行验证的功能,而按照我们之前学习的String类,我们使用String类中的诸多函数是可以完成对字符串校验功能的,但是代码相对来说比较麻烦,所以在Java中引入正则表达式的概念来解决上述问题,即简化代码。
正则表达式:专门用于操作字符串的技术,并且可以简化代码,用于对字符串的复杂操作。
正则表达式弊端:代码可读性比较差。
验证
案例1:
需求:验证QQ号码是否合法。
分析:
1、第一位不能是零;
2、QQ号码在5到12之间(包含);
3、Q号码都是由数字组成;
说明:
- String类中提供一个matches()函数,可以判断字符串对象是否匹配正则表达式。
1)如果匹配,则返回true;
2)如果不匹配,则返回false;
2、 [1-9]:表示字符串中第一位能够出现1~9任何一个数字;
3、 [0-9]{4,11}:表示字符串中从第2位开始后面的数字只能出现0~9之间的数字,并且最少出现4次,最多出现11次;
4、 如果满足上述条件则返回true,否则返回false
上述案例代码如下:
package cn.itcast.sh.a_regex_demo;
/*
* 需求:验证QQ号码是否合法。
分析:
1、第一位不能是零;
2、QQ号码在5到12之间(包含);
3、QQ号码都是有数字组成;
*/
public class RegexDemo {
public static void main(String[] args) {
method_1();
}
// 使用正则表达式完成QQ号码的验证
private static void method_1() {
// 定义一个字符串变量
String QQ = "12345";
/*
* String类中提供一个matches()函数,可以判断字符串对象是否匹配正则表达式
* 如果匹配,则返回true
* 如果不匹配,则返回false
* [1-9]:表示字符串中第一位能够出现1~9任何一个数字
* [0-9]{4,11}:表示字符串中从第2位开始后面的数字只能出现0~9之间的数字,并且最少出现4次,最多出现11次
* 如果满足上述条件则返回true,否则返回false
*/
boolean flag = QQ.matches("[1-9][0-9]{4,11}");
System.out.println(flag);
}
}
说明:
- 在正则中[]表示在某一位字符串中出现的范围;
- 在正则中{}表示前面离他最近的前面的正则出现的次数;
注意:正则表达式只能使用在字符串上。
案例2:需求:验证手机号码
分析:手机号码的规则:
- 长度必须是11位;
- 第一位只能是数字1;
- 第二位可以是3 4 5 7 8;
- 从第三位开始可以是0-9
步骤:
- 定义一个RegexDemo1类,在这个类中定义一个主函数main;
- 在main函数中定义一个String类型的变量tel,并赋值为15066668888;
- 定义一个手机号码的正则规则regex=”1[34578][0-9]{9}”;
- 使用字符串变量tel调用String类中的matches()函数,regex正则规则作为参数进行传递,打印结果;
package cn.itcast.sh.a_regex_demo;
/*
* 需求:验证手机号码
分析:手机号码的规则:
1)长度必须是11位;
2)第一位只能是数字1;
3)第二位可以是3 4 5 7 8;
4)从第三位开始可以是0-9
*/
public class RegexDemo1 {
public static void main(String[] args) {
// 定义一个字符串
String tel = "15066668888";
// 定义一个手机号的正则
String regex = "1[34578][0-9]{9}";
// 使用字符串对象tel调用String类中的matches函数,判断字符串是否匹配正则表达式
System.out.println(tel.matches(regex));
}
}
切割
需求:使用String类中的split函数根据正则表达式规则,以数字对已知的字符串进行切割。
- 定义RegexDemo2 类;
- 在这个类中定义一个字符串str,并赋值为”sfajs12321dbfj234d23sjfk454sdjf565sdhd757hf”;
- 定义一个正则表达式规则:regex=”[0-9]+”;
- 使用定义好的字符串str调用split()函数按照正则表达式进行切割;
- 遍历切割后的数组;
package cn.itcast.sh.a_regex_demo;
/*
* 需求:使用String类中的split函数根据正则表达式规则,以数字对已知的字符串进行切割。
1)定义RegexDemo2 类;
2)在这个类中定义一个字符串str,并赋值为”sfljs12321dlfj234d23sjfk454sdjf565sdhd757hf”;
3)定义一个正则表达式规则:regex=”\\d+”;
4)使用定义好的字符串str调用split()函数对正则表达式进行切割;
5)遍历切割后的数组;
*/
public class RegexDemo2 {
public static void main(String[] args) {
// 定义一个字符串
String str = "sfajs12321dbfj234d23sjfk454sdjf565sdhd757hf";
// 定义一个正则表达式,以数字对上述字符串进行切割{"sfajs","dbfj","d","sjfk"}
String regex = "\\d+";
String[] strs = str.split(regex);
// 遍历数组
for (int i = 0; i < strs.length; i++) {
// 打印数组中的数据
System.out.println(strs[i]);
}
}
}
说明:
- 在正则中\\d就是数字[0-9];
- 在正则中+ 表示1 n次,* 表示0 1 n次,? 表示0 1次;