前言
这篇内容主要掌握的就是logback使用、理解类加载器、XML文件的编写,XML文档约束schema,用Dom4j解析XML文档,Xpath检索XML文档,完整使用Junit单元测试框架常用部分,注解的定义和使用,枚举类的定义和开发应用。
一、log日志
log日志类似输出语句,可以用来输出程序的信息,但是log日志更加好用。
1.输出语句的弊端
2. log日志的优点
可以将系统执行的信息选择性的记录到指定的位置(控制台、文件中、数据库中)。
可以随时以开关的形式控制是否记录日志,无需修改源代码。
日志技术的具体优势
3.日志框架体系
日志框架需要日志规范的要求实现,日志规范大多是一些接口,提供给实现框架去设计的。常见的规范有:Commons Logging、SimpleLoggingFacadeforJava。
志的实现框架有,Log4J、Logback(我们重点学习的,其他的都大同小异)
4.日志框架LogBack
(1)概述
Logback是基于slf4j的日志规范实现的框架,性能比之前使用的log4j要好。
官方网站:Logback Home
Logback主要分为三个技术模块:
logback-core:该模块为其他两个模块提供基础代码,必须有。
logback-classic:完整实现了slf4j APl的模块。
logback-access模块与Tomcat和Jetty等Servlet容器集成,以提供HTTP访问日志功能
(2)配置
1.创建一个maven项目,在pom文件将下面的依赖坐标导入项目
XML<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
刷新maven自动导入下面三个jar包
2. 将Logback的核心配置文件logback.xml直接拷贝到maven项目的resources目录下
logback.xml内容如下:
XML<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- CONSOLE :表示当前的日志信息是可以输出到控制台的。 --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <!--输出流对象 默认 System.out 改为 System.err--> <target>System.out</target> <encoder> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern> </encoder> </appender> <!-- File是输出的方向通向文件的 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> <charset>utf-8</charset> </encoder> <!--日志输出路径--> <file>C:/code/itpzh-data.log</file> <!--指定日志文件拆分和压缩规则--> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!--通过指定压缩文件名称,来确定分割文件方式--> <fileNamePattern>C:/code/itpzh-data2-%d{yyyy-MMdd}.log%i.gz</fileNamePattern> <!--文件拆分大小--> <maxFileSize>1MB</maxFileSize> </rollingPolicy> </appender> <!-- level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF , 默认debug <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。 --> <root level="info"> <appender-ref ref="CONSOLE"/> <appender-ref ref="FILE" /> </root> </configuration>
(3)入门程序
A.步骤
1.获取日志的对象。
javaprivate final static Logger LOGGER = LoggerFactory.getLogger("类对象");
注意:这个对象要全局共享唯一(static final修饰)
这个Logger一定要是org.slf4j.Logger这个第三方包下的
2.用日志对象的方法记录系统的日志信息。
B.入门实例代码
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Scanner;
public class LoggerDemo1 {
//获取一个共享的唯一的log日志对象
private final static Logger LOGGER = LoggerFactory.getLogger(LoggerDemo1.class);
public static void main(String[] args) {
//获取用户登录的日志
Scanner sr = new Scanner(System.in);
while (true) {
System.out.println("请输入用户名");
String username = sr.next();
System.out.println("请输入密码");
String password = sr.next();
if ("张三".equals(username) && "12345".equals(password)) {
LOGGER.info("用户{}登录成功,密码为{}", username, password);
break;
} else {
LOGGER.info("用户{}登录失败", username);
}
}
}
}
运行完,LOGGER.info的内容不仅显示在控制台,而且还被保存到文件里面
因为配置文件appender标签内容有配置,后面会说
(4)配置文件logback.xml解说
Logback日志系统的特性都是通过核心配置文件logback.xml控制的。
Loaback日志输出位置、格式设置:
1.通过logback.xml中的<appender>标签可以设置输出位置和日志信息的详细格式。
2.通常可以设置2个日志输出位置:一个是控制台、一个是系统文件中
下面的<appender>标签内的各种标签解说<appender>标签的属性name=CONSOLE时表示输出到控制台
class表示输出到控制台的功能由ch.qos.logback.core.ConsoleAppender这个类来实现
<target>标签表示System.out以标准输出流的形式输出到控制台上面
<encoder>和<pattern>标签规定输出内容的格式,%d{yyyy-MM-dd HH:mm:ss.SSS}表示按格式输出年月日 时分秒 [%-5level]表留五个空格的长度输出日志的等级 %c表示当前操作的类
%thread表示当前所在的线程,比如main线程 %msg表示输出的信息
下面讲解<root>标签内的属性和附属标签<root>标签控制哪几个<appender>标签能否生效的,里面用<appender-ref/>标签标记要生效的<appender>标签。
<root>标签的属性level用来设置打印级别,一共7个,All表示<root>标签所包含的<appender-ref/>标签全部生效,OFF表示全部不生效。
(5)日志级别
作用:
如果系统上线后只想记录一些错误的日志信息或者不想记录日志了,怎么办?
可以通过设置日志的输出级别来控制哪些日志信息输出或者不输出。
**解说:**日志级别是可以通过日志对象的.level方法去设置的,不设置的话默认是继承日志的顶级父类 root的级别debug。
当<root>标签的level属性设置为WARN时,日志对象如果日志级别为TRACE,DEBUG,INFO就不输出日志。
二、类加载器
1.作用
负责将.class文件(存储的物理文件)加载在到内存中
2.类加载的完整过程
(1)类加载时机
(简单理解:字节码文件什么时候会被加载到内存中?)
有以下的几种情况:
创建类的实例(对象)
调用类的类方法
访问类或者接口的类变量,或者为该类变量赋值
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
初始化某个类的子类
直接使用java.exe命令来运行某个主类
总结而言:用到了就加载,不用不加载
(2)类加载过程
类加载分为5个阶段
加载,验证,准备,解析,初始化。
其中验证,准备,解析归为链接阶段
加载
就是,将字节码文件通过IO流读取到JVM的方法区,并同时在堆中生成Class对象。
通过包名 + 类名,获取这个类,准备用流进行传输
在这个类加载到内存中
加载完毕创建一个class对象
注意:创建的class对象的变量类型如果是引用类型,就用符号代替
链接
A.验证
确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全
(文件中的信息是否符合虚拟机规范有没有安全隐患)
B.准备
负责为类的类变量(被static修饰的变量)分配内存,并设置默认初始化值
(初始化静态变量)
C.解析
将类的二进制数据流中的符号引用替换为直接引用
(本类中如果用到了其他类,此时就需要找到对应的类)
这里的符号引用,意思是,在类加载的时候将一个类的字节码文件以二进制数据流输入JVM,该类的成员变量的类型如果是像String类型这种引用类型,但是在加载的时候,String从哪里来那里去都不知道,于是先用符号代替String,这就是符号引用
初始化
根据程序员通过程序制定的主观计划去初始化类变量和其他资源
(静态变量赋值以及初始化其他资源)
3.类加载器分类
(1)分类
Bootstrap class loader:虚拟机的内置类加载器,通常表示为null ,并且没有父级类加载器
Platform class loader:平台类加载器,负责加载JDK中一些特殊的模块
System class loader:系统类加载器,负责加载用户类路径上所指定的类库
自定义类加载器
(2)类加载器的继承关系(逻辑上的继承,看后面的双亲委派模型)
自定义加载器的父加载器为System
System的父加载器为Platform
Platform的父加载器为Bootstrap
(3)代码演示证明
javapublic class ClassLoaderDemo1 { public static void main(String[] args) { //获取系统类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); //获取系统类加载器的父加载器 --- 平台类加载器 ClassLoader classLoader1 = systemClassLoader.getParent(); //获取平台类加载器的父加载器 --- 启动类加载器 ClassLoader classLoader2 = classLoader1.getParent(); System.out.println("系统类加载器" + systemClassLoader); System.out.println("平台类加载器" + classLoader1); System.out.println("启动类加载器" + classLoader2); } }
运行结果
4. 双亲委派模型
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式
5.ClassLoader 中的两个方法
|-----------------------------------------------------|-----------|
| 方法名 | 说明 |
| public static ClassLoader getSystemClassLoader() | 获取系统类加载器 |
| public InputStream getResourceAsStream(String name) | 加载某一个资源文件 |
实例代码
javapublic class ClassLoaderDemo2 { public static void main(String[] args) throws IOException { //获取系统类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); //利用加载器去加载一个指定的文件 //参数:文件的路径(放在maven项目的resources目录下,默认去那里加载) //返回值:字节流。 InputStream is = systemClassLoader.getResourceAsStream("prop.properties"); Properties properties = new Properties(); properties.load(is); System.out.println(properties); is.close(); } }
三、XML
1.配置文件
XML文件一般作为配置文件使用,那么什么是配置文件?
配置文件就是,用来保存程序在运行时需要的一些参数。比如说idea的配置文件,当我们第一次使用idea的时候,我们配置idea的主题,字体大小等等,这些参数会被保存在idea的配置文件里面,当我们再次打开idea的时候,idea会从配置文件中加载这些参数,就不用我们重新配置idea主题什么的了。
常见的配置文件有什么?
有.txt文件,有.properties文件,有.xml文件
三种配置文件的优缺点?
txt文件只记录参数的值,太初略
properties文件以键值对的形式记录参数,较为详细
但是对于复杂的多用户的配置文件,就不适应,选择使用xml文件
TXT文件:没有优点,缺点是不利于阅读
properties文件:优点就是键值对形式易于阅读,缺点就是无法配置一组一组的数据。
XML文件:优点是易于阅读,可以配置成组出现的数据
以后配置文件怎么选择?
数据量较少,一个键只对应一个值,使用properties
数据量较多,使用xml
2.XML概述
XML的全称为(EXtensible Markup Language),是一种可扩展的标记语言标记语言: 通过标签来描述数据的一门语言(标签有时我们也将其称之为元素)可扩展:标签的名字是可以自定义的,XML文件是由很多标签组成的,而标签名是可以自定义的
作用
用于进行存储数据和传输数据
作为软件的配置文件
作为配置文件的优势
可读性好
可维护性高
3.XML的基本语法
(1)XML文件的命名和位置
XML文件要放在src目录下,或者放在maven项目下面的resources目录下面
(2)标签(元素)规则
(3)XML其他组成
(4)示例
4.XML文档约束
(1)什么是文档约束?
于是需要文档约束来用来限定xml文件中的标签以及属性应该怎么写。以此强制约束程序员必须按照文档约束的规定来编写xml文件。
(2)约束分类
DTD
schema
(3)DTD约束
DTD约束编写
A.步骤
创建一个文件,这个文件的后缀名为.dtd
看xml文件中使用了哪些元素
<!ELEMENT> 可以定义元素
判断元素是简单元素还是复杂元素
简单元素:没有子元素。复杂元素:有子元素的元素;
简单元素修饰词
EMPTY: 表示标签体为空
ANY: 表示标签体可以为空也可以不为空
PCDATA: 表示该元素的内容部分为字符串
复杂元素修饰词 直接写子元素名称. 多个子元素可以使用","或者"|"隔开; ","表示定义子元素的顺序 ; "|": 表示子元素只能出现任意一个 "?"零次或一次, "+"一次或多次, "*"零次或多次;如果不写则表示出现一次
元素的属性的定义
格式
定义一个属性的格式为:<!ATTLIST 元素名称 属性名称 属性的类型 属性的约束>属性的类型: CDATA类型:普通的字符串
属性的约束:
// #REQUIRED: 必须的 // #IMPLIED: 属性不是必需的 // #FIXED value:属性值是固定的
B.代码
简单入门
XML<!ELEMENT persons (person)> <!ELEMENT person (name,age)> <!ELEMENT name (#PCDATA)> <!ELEMENT age (#PCDATA)>
复杂
XML<!ELEMENT persons (person+)> <!ELEMENT person (name,age)> <!ELEMENT name (#PCDATA)> <!ELEMENT age (#PCDATA)> <!ATTLIST person id CDATA #REQUIRED>
DTD约束引入
三种引入方式
简单入门代码
XML// 这是persondtd.dtd文件中的内容,已经提前写好 <!ELEMENT persons (person)> <!ELEMENT person (name,age)> <!ELEMENT name (#PCDATA)> <!ELEMENT age (#PCDATA)> // 在person1.xml文件中引入persondtd.dtd约束 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE persons SYSTEM 'persondtd.dtd'> <persons> <person> <name>张三</name> <age>23</age> </person> </persons>
复杂代码
XML//dtd代码 <!ELEMENT persons (person+)> <!ELEMENT person (name,age)> <!ELEMENT name (#PCDATA)> <!ELEMENT age (#PCDATA)> <!ATTLIST person id CDATA #REQUIRED> //xml代码 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE persons SYSTEM 'persondtd.dtd'> <persons> <person id="001"> <name>张三</name> <age>23</age> </person> <person id = "002"> <name>张三</name> <age>23</age> </person> </persons>
(4)schema约束
schema和dtd的区别
schema约束文件也是一个xml文件,符合xml的语法,这个文件的后缀名.xsd
一个xml中可以引用多个schema约束文件,多个schema使用名称空间区分(名称空间类似于java包名)
dtd里面元素类型的取值比较单一常见的是PCDATA类型,但是在schema里面可以支持很多个数据类型
schema 语法更加的复杂
schema约束编写
A.步骤
1,创建一个文件,这个文件的后缀名为.xsd。
2,定义文档声明(跟xml文件第一行一样)
3,schema文件的根标签为: <schema>
4,在<schema>中定义属性: xmlns=http://www.w3.org/2001/XMLSchema
5,在<schema>中定义属性 : targetNamespace =唯一的url地址,指定当前这个schema文件的名称空间。
6,在<schema>中定义属性 : elementFormDefault="qualified",表示当前schema文件是一个质量良好的文件。直接复制用就行
7,通过element定义元素
8,判断当前元素是简单元素还是复杂元素
代码
XML<?xml version="1.0" encoding="UTF-8" ?> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/b" elementFormDefault="qualified"> <element name="persons"> <complexType> <sequence maxOccurs="unbounded"> <element name="person"> <complexType> <sequence> <element name="name" type="string"/> <element name="age" type="int"/> </sequence> </complexType> </element> </sequence> </complexType> </element> </schema>
schema约束引入
直接在xml文件的第二行,先输入<,idea会弹出提示框,选择目标的xsd
选择后idea直接导入约束
入门代码
XML//xsd代码 <?xml version="1.0" encoding="UTF-8" ?> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/b" elementFormDefault="qualified"> <element name="persons"> <complexType> <sequence maxOccurs="unbounded"> <element name="person"> <complexType> <sequence> <element name="name" type="string"/> <element name="age" type="int"/> </sequence> </complexType> </element> </sequence> </complexType> </element> </schema> //xml代码 <?xml version="1.0" encoding="UTF-8" ?> <persons xmlns="http://www.example.org/b"> <person> <name>张三</name> <age>18</age> </person> <person> <name>张三</name> <age>18</age> </person> <person> <name>张三</name> <age>18</age> </person> </persons>
5.XML文件解析
XML的数据的作用是什么,最终需要怎么处理?
存储数据、做配置信息、进行数据传输,
最终需要被程序进行读取,解析里面的信息。
有几种解析方式?
SAX(一般不用)和DOM
SAX和DOM的优缺点
SAX: 不会把整体的xml文件都加载到内存,而是从上往下逐行进行扫描。
缺点:只能读取,不能添加,不能删除。
优点:因为他是逐行扫描不需要把整体的xml文件都加载到内存,所以他可以解析比较大的xml文件。
DOM: 会把整体的xml文件都加载到内存。
会把这个整天在内存中形成一个树形结构,我们可以通过这个树形结构去解析xml文件。
优点:可以读取,可以添加,可以删除,可以做任何事情。
缺点:需要xml文件全部加载到内存,所以不能解析非常大的xml文件。
(1)Dom解析的文档对象模型(一层一层解析的过程)
(2)Dom常见的解析工具
6.Dom4j解析XML
(1)快速入门
java
public class Dom4jDemo1 {
public static void main(String[] args) throws DocumentException {
//1.创建解析器对象
SAXReader saxReader = new SAXReader();
//2.利用解析器去读取XML文件,并返回文件对象
File file = new File("src\\main\\resources\\a.xml");
Document document = saxReader.read(file);
//拿到了document表示我已经拿到了xml文件的整体
//3.打印
System.out.println(document);
//下面一层一层的扒开获取里面的内容即可
}
}
(2)成员方法
Element类
(3)最终示例
需求:
将下面的XML文件的几个人的信息保存到内存中
建议:创建一个Person类来存储人物信息
XML<?xml version="1.0" encoding="UTF-8" ?> <persons> <person id="1"> <name>张三</name> <age>18</age> </person> <person id="2"> <name>李四</name> <age>20</age> </person> <person id="3"> <name>张三</name> <age>21</age> </person> </persons>
创建一个Person类
javapublic class Person { private int id; private String name; private int age; public Person(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
正文代码
javapublic class Dom4jDemo2 { public static void main(String[] args) throws DocumentException { //1.创建解析器对象 SAXReader saxReader = new SAXReader(); //2.利用解析器去读取XML文件,并返回文件对象 File file = new File("src\\main\\resources\\a.xml"); Document document = saxReader.read(file); //拿到了document表示我已经拿到了xml文件的整体 //创建一个集合来存储人物的信息 ArrayList<Person> list = new ArrayList<>(); //下面一层一层的扒开获取里面的内容即可 //获取根元素 Element rootElement = document.getRootElement(); //获取根元素下的所有person元素 List<Element> elements = rootElement.elements("person"); for (Element element : elements) { //获取子元素的id属性 Attribute id = element.attribute("id"); String idValue = id.getValue(); //获取子元素的name和age String nameValue = element.element("name").getText(); String ageValue = element.element("age").getText(); Person person = new Person(Integer.parseInt(idValue), nameValue, Integer.parseInt(ageValue)); list.add(person); } list.stream().forEach(System.out::println); } }
7.XML检索技术Xpath
如果需要从XML文件中检索需要的某个信息(如name)怎么解决?
Dom4j需要进行文件的全部解析,然后再寻找数据。
Xpath技术更加适合做信息检索。
XPath在解析XML文档方面提供了一独树一帜的路径思想,更加优雅,高效。XPath使用++路径表达式++来定位XML文档中的元素节点或属性节点
/元素/子元素/孙元素
//子元素//孙元素
(1)使用步骤
1.导入jar包(dom4j和jaxen-1.1.2.jar),Xpath技术依赖Dom4j技术
2.通过dom4j的SAXReader解析器获取Document对象
3.利用XPath提供的API,结合XPath的语法完成选取XML文档元素节点进行解析操作。
4.Document中与Xpath相关的API如下:
(2)路径表达式
绝对路径
相对路径
全文搜索
属性查找
(3)快速入门
需求:
利用Xpath检索技术解析XML中的name元素,XML文件内容如下
XML<?xml version="1.0" encoding="UTF-8" ?> <persons> <person id="1"> <name>张三</name> <age>18</age> </person> <person id="2"> <name>李四</name> <age>20</age> </person> <person id="3"> <name>王五</name> <age>21</age> </person> <name>基尼太妹</name> </persons>
代码
javapublic class XpathDemo { public static void main(String[] args) throws DocumentException { //1.获取解析器 SAXReader saxReader = new SAXReader(); //2.用解析器解析XML文件获得对于的Document对象 File file = new File("src/main/resources/a.xml"); Document document = saxReader.read(file); //3.通过Xpath的API获取name元素 //(1)绝对路径 List<Element> list1 = document.selectNodes("/persons/person/name"); System.out.println("绝对路径获取name内容:一共"+list1.size()+"个"); for (Element element : list1) { System.out.println(element.getText()); } //(2)相对路径 Element rootElement = document.getRootElement(); List<Element> list2 = rootElement.selectNodes("./person/name"); System.out.println("相对路径获取name内容:一共"+list2.size()+"个"); for (Element element : list2) { System.out.println(element.getText()); } //(3)全文搜索 List<Element> list3 = document.selectNodes("//name"); System.out.println("全文搜索获取name内容:一共"+list3.size()+"个"); for (Element element : list3) { System.out.println(element.getText()); } //(4)属性搜索 Element node = (Element) document.selectSingleNode("//person[@id='1']/name"); System.out.println("属性搜索获取name内容:"+node.getText()); } }
四、单元测试
1.什么是单元测试?为什么用它?
单元测试就是,针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法,因此,单元测试就是针对Java方法的测试,进而检查方法的正确性。
以前测试方法只有一个main方法,如果一个方法的测试失败了,其他方法测试会受到影响。还需要程序员自己去观察测试是否成功。
2.Junit单元测试框架
(1)简述
JUnit是使用Java语言实现的单元测试框架,它是开源的,Java开发者都应当学习并使用JUnit编写单元测试。
此外,几乎所有的IDE工具都集成了JUnit,这样我们就可以直接在IDE中编写并运行JUnit测试,JUnit目前最新版本是5。
(2)优点
1.JUnit可以灵活的选择执行哪些测试方法,可以一键执行全部测试方法。
2.单元测试中的某个方法测试失败了,不会影响其他测试方法的测试。
3.运行成功是绿色,运行失败是红色
(3)步骤
(4)快速入门
测试代码
javapublic class JunitDemo1 { @Test //输入注解@Test //如果爆红就按alt+回车 //选择Junit4导入 public void method1() { System.out.println(2/0); int a = 100; int b = 100; System.out.println(a+b); } @Test public void method2() { int a = 100; int b = 100; System.out.println(a+b); } @Test public void method3() { int a = 100; int b = 100; System.out.println(a+b); } }
测试结果
(5)Junit常用注解
一般实际开发常用的就前面的3个注解,@Before,@Test,@After这三个注释配套使用
实际开发的测试原则就是,保证测试前后的数据,原原本本不变
于是完整的单元测试步骤
1.先执行的@Before的方法,对数据进行一个初始化的动作和数据的备份
2.再执行@Test的方法,去真正的去测试方法,要配合++断言++,与期望数据做对比
3.最后执行@After的方法,去还原数据
Junit中一般的断言使用方法**参数1:**如果断言为假,就将message的信息输出到控制台
为真,就不输出
**参数2:**期望得到的结果
**参数3:**运行完测试方法得到的实际结果
Assert.assertEquals(message,expected, actual);
(6)完整的单元测试
以后在实际开发中,如果想要测试一个方法是否正确,并不是直接在当前方法的上面写@Test的
而是,自己独立编写一个测试类。(不要写main方法)
在这个类中,编写一些方法。
在方法里面调用要被测试的方法即可
需求:测试File类中的delete方法是否书写正确???
代码独立写一个测试类,创建要测试的方法所在类的对象File,运行FIle对象的delete方法,
测试方法必须数据备份和恢复
javapublic class JunitDemo2 { @Before public void method1() throws IOException { //数据备份 FileInputStream fis = new FileInputStream("logs\\a.txt"); FileOutputStream fos = new FileOutputStream("logs\\copy.txt"); int b; while ((b=fis.read())!=-1){ fos.write(b); } fos.close(); fis.close(); } @Test public void method2(){ //测试File类的delete方法 File file = new File("logs\\a.txt"); //判断是否删除成功 boolean delete = file.delete(); //检查a.txt是否存在 boolean exists = file.exists(); Assert.assertEquals("delete方法出错了",true,delete); Assert.assertEquals("delete方法出错了",false,exists); } @After public void method3() throws IOException { //数据恢复 FileInputStream fis = new FileInputStream("logs\\copy.txt"); FileOutputStream fos = new FileOutputStream("logs\\a.txt"); int b; while ((b=fis.read())!=-1){ fos.write(b); } fos.close(); fis.close(); new File("logs\\copy.txt").delete(); } }
五、注解
1.简述
Annotation表示注解。是JDK1.5的新特性。
注解的主要作用:对我们的程序进行标注。通过注解可以给类增加额外的信息。
注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。
(注释是给人看的,编译阶段会注释擦除)
2.常见的注解(掌握)
@Override:表示方法的重写
@Deprecated:表示修饰的方法已过时
@SuppressWarnings("all"): 压制警告
除此之外,还需要掌握第三方框架中提供的注解:
比如:Junit
@Test表示运行测试方法
@Before表示在Test之前运行,进行数据的初始化
@After表示在Test之后运行,进行数据的还原
3.自定义注解
(1)定义格式
类似类的定义
(2)使用格式
@注解名(属性名1=值1,属性名2=值2)
注意:
注解使用可以放在类,方法和属性的上面
使用自定义注解时要保证注解每个属性都有值
注解可以使用默认值 ,没有默认值的一定要赋值
创建一个注解
javapublic @interface MyAnno1 { String name(); int age() default 18; }
使用
java@MyAnno1(name = "张三") public class Test { @MyAnno1(name = "王五",age = 20) int age; @MyAnno1(name = "李四") public void method1(){ System.out.println("method1"); } }
(3)特殊属性value
value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写!!
但是如果有多个属性,且多个属性没有默认值,那么vaue名称是不能省略的。
常见的@SuppressWarnings("all"):就是只有一个value属性的注解
4.元注解
元注解就是注解的注解,就是写在注解上面的注解
比如下面
(1)两个元注解
@Target:约束自定义注解只能在哪些地方使用
@Retention:申明注解的生命周期
@Target
@Target中可使用的值定义在ElementType枚举类(底层写好的)中,常用值如下
TYPE,类,接口
FIELD,成员变量
METHOD,成员方法
PARAMETER,方法参数
CONSTRUCTOR,构造器
LOCAL_VARIABLE,局部变量
@Retention
@Retention中可使用的值定义在RetentionPolicy枚举类中,常用值如下
SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)
5.注解解析
(1)概念和作用
注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容。
举一个例子Junit框架中的@Test注解是怎么将测试方法运行起来的,这其中就是用到了注解解析。
当运行测试方法的时候,@Test注解就会通过反射来获取对应方法的Method类,然后执行方法。
(2)与注解解析相关的接口和方法
Annotation:注解的顶级接口
可以利用反射解析注解
有的类成分Class,Method,Field,Constructor,都实现了AnnotatedElement接口他们都拥有解析注解的能力
(3) 解析注解的技巧
注解在哪个成分上,我们就先拿哪个成分对象。
比如注解作用成员方法,则要获得该成员方法对应的Methed对象,再来拿上面的注解
比如注解作用在类上,则要该类的class对象,再来拿上面的注解
比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解
配合后面的 模拟Junit框架理解
6.模拟Junit框架
需求
定义若干个方法,只要加了MyTest注解,就可以在启动时被触发执行
分析
定义一个自定义注解MvTest,只能注解方法,存活范围是一直都在。
定义若干个方法,只要有@MyTest注解的方法就能在启动时被触发执行,没有这个注解的方法不能执行。
代码
java@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyTest { } public class MyTestMethod { @MyTest public void method1(){ System.out.println("method1"); } public void method2(){ System.out.println("method2"); } @MyTest public void method3(){ System.out.println("method3"); } } public class MyTestDemo { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException { //1,获取class对象 Class clazz = Class.forName("com.itheima.test2.MyTestMethod"); //获取对象 Object o = clazz.newInstance(); //2.获取所有方法 Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { //method依次表示类里面的每一个方法 method.setAccessible(true); //判断当前方法有没有MyTest注解 if(method.isAnnotationPresent(MyTest.class)){ method.invoke(o); } } } }
运行结果
method1
method3
六、枚举类
1.概念,使用和优缺点
可以查看【Java】枚举类型_java 枚举类-CSDN博客
2.主要的应用开发场景
状态管理:
使用枚举类表示对象的不同状态,例如订单状态(待支付、已支付、已发货、已完成等)。
错误代码:
定义各种错误类型或错误代码,以提高代码的可读性和可维护性。
配置选项:
使用枚举类来封装常见的配置选项,例如日志级别(DEBUG、INFO、WARN、ERROR)或环境(开发、测试、生产)。
类型安全的选择:
替代字符串常量,提供类型安全的选项,避免因拼写错误导致的问题,例如选择用户角色(ADMIN、USER、GUEST)。
策略模式:
在实现策略模式时,可以使用枚举类来封装策略的实现,从而使代码更清晰。
分类系统:
在需要分类的场景中,例如商品类别(电子产品、服饰、食品等),枚举类可以提供一种清晰的结构。
API 设计:
在 API 中使用枚举类可以清晰地表示可选参数,使接口更加易于理解和使用。
事件类型:
用于定义不同的事件类型,例如用户操作事件(点击、提交、查看等)。
数据库映射:
将数据库中的状态值映射到应用程序中的枚举类,简化数据操作。
网络协议:
定义网络协议中的操作类型或消息类型,以提高代码的可读性。
枚举类通过提供一组固定的常量值,能够使代码更清晰,更易于维护和扩展。