Java单元测试和类加载

Java单元测试和类加载

回顾:

1 Lambda表达式:相等于匿名内部类,实现代码作为方法的参数传统。
   函数式接口 变量=(参数列表)->{
      	方法体
	};
   注意:  ->操作符 分成两部分 
   左侧:(参数列表)
   右侧:  方法体
   1 左侧的类型可以省略,类型推断
   2 左侧没有参数,写一个()
   3 左侧有一个参数,  ()可以省略
   4 右侧如果只有一条语句,可以省略{},如果有返回值,只有一条语句,return省略,如果有多条语句,必须写{}
   5 Labmda表达式没有生成内部类
   6 访问局部变量,加上final

  函数式接口:只有一个抽象方法
  Consumer 消费型    有参数没有返回值
  Supplier 供给型    没有参数有返回值
  Function 函数型    有参数有返回值
  Predicate 断言型   有参数 返回值boolean
2 方法引用: 如果方法体中只调用一条语句,可以使用方法引用
	对象::实例方法
		System.out::println
	类名::静态方法	
		Integer:compare
	类名::实例方法
		String::equals
		Employee::getName
	类名::new 	创建对象
		Employee::new
	数组引用
	String[]::new 	
3 Stream API  
	流:包含对集合和数组的操作
	三步
	1 创建流 
		Stream.of()
		Arrays.stream()
		集合的.stream()
		Stream.iterate()
		Stream.generate();
	2 中间操作
    	filter 排除不满足条件数据
    	limit  限制
    	skip   跳过
    	distinct  去重复 
    	
    	map 映射
    	
  
   3 终止操作 
   		forEach();
   		
   		allMatch()
   		anyMatch()
   		noneMath();
   		findFirst();
   		findAny();
   		count();
   		
   		
   		reduce() 归约
    	collect() 收集
   		
    
 时间和日期
 	LocalDate
 	LocalTime
 	LocalDateTime
 	
 	Instant 时间时刻
 	
 	时间矫正器
 	
 	时间格式化
 	DateTimeFormatter
   
   
	

今天任务

1.单元测试
2.注解
3.类加载器

教学目标

1.掌握单元测试
2.掌握注解
3.理解类加载器

第一节:单元测试

1.1 什么是软件测试

软件测试是程序的一种执行过程,目的是尽可能发现并改正被测试软件中的错误,提高软件的可靠性。

1.2 测试分类

按照是否知道源代码

​ 黑盒测试:不关心具体的逻辑代码,只测功能

​ 白盒测试:测试逻辑代码

​ 灰盒测试

从软件开发的过程

​ 单元测试Unit Testing

​ 集成测试Integrated Testing

​ 系统测试System Testing

根据测试的次数

​ 冒烟测试

​ 压力测试

1.3 Junit单元测试

Junit是一个基于Java语言的单元测试框架。是白盒测试的一种技术。

演示测试1

public class Operation {
	public int add(int x,int y) {
		return x^y;
	}
	
	public int sub(int x,int y) {
		return x-y;
	}
}

public class OperationTest {

	@Test
	public void testAdd() {
		Operation operation=new Operation();
		int result=operation.add(20, 30);
		System.out.println(result);
		Assert.assertEquals(50, result);
	}
	@Test
	public void testSub() {
		Operation operation=new Operation();
		int result=operation.sub(10,5);
		System.out.println(result);
	}
}

演示测试2

有一个学生类StudentDao,添加测试类

public class StudentDao {
	public void add() {
		System.out.println("添加学生");
	}
	public void update() {
		System.out.println("更新学生");
	}

}	

public class StudentDaoTest {

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		System.out.println("测试类执行之前。。。。。。。。。");
	}

	@AfterClass
	public static void tearDownAfterClass() throws Exception {
		System.out.println("测试类执行之后。。。。。。。。。");
	}

	@Before
	public void setUp() throws Exception {
		System.out.println("方法之前执行........");
	}

	@After
	public void tearDown() throws Exception {
		System.out.println("方法之后执行........");
	}

	@Test
	public void test() {
		System.out.println("测试方法");
	}
	@Test
	public void test2() {
		System.out.println("测试方法2");
	}
  
  	@Test
	public void testAdd() {
		StudentDao studentDao=new StudentDao();
		studentDao.add();
	}
	@Test
	public void testUpdate() {
		StudentDao studentDao=new StudentDao();
		studentDao.update();
	}

}

注意:

​ ①测试方法上必须使用@Test进行修饰

​ ②测试方法必须使用public void 进行修饰,不能带任何的参数

​ ③新建一个源代码目录来存放我们的测试代码,即将测试代码和项目业务代码分开

​ ④测试类所在的包名应该和被测试类所在的包名保持一致

​ ⑤测试单元中的每个方法必须可以独立测试,测试方法间不能有任何的依赖

​ ⑥测试类使用Test作为类名的后缀(不是必须)

​ ⑦测试方法使用test作为方法名的前缀(不是必须)

第二节:注解

注释:给代码添加说明和解释,注释帮助开发人员理解程序。(Comment)

注解:给代码添加说明,这个说明给程序使用。

从 JDK 5.0 开始,Java 增加了对元数据(MetaData) 的支持, 也就是Annotation(注解)。

三个基本的 Annotation:

​ @Override:限定重写父类方法, 该注解只能用于方法

​ @Deprecated:用于表示某个程序元素(类, 方法等)已过时

​ @SuppressWarnings: 抑制编译器警告.

什么是注解

​ Annotation其实就是代码里的特殊标记, 它用于替代配置文件,也就是说,传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行。在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。

注解技术的要点:

​ 如何定义注解

​ 如何反射注解,并根据反射的注解信息,决定如何去运行类

2.1自定义注解:

​ 定义新的 Annotation 类型使用@interface关键字

​ 声明注解的属性

注解属性的作用:原来写在配置文件中的信息,可以通过注解的属性进行描述。
Annotation的属性声明方式:Stringname();
属性默认值声明方式:Stringname() default “xxx”;
特殊属性value:如果注解中有一个名称value的属性,那么使用注解时可以省略value=部分,如@MyAnnotation(“xxx")
特殊属性value[];
注解属性的类型可以是:
    String类型
    基本数据类型
    Class类型
    枚举类型
    注解类型
    以上类型的一维数组

案例演示1 创建和使用注解

public @interface MyAnnocation {
	String name();
	int num() default 10;
	MyAnnocation2 anno();
}
public @interface MyAnnocation2 {
	String value();
}

public class Demo1 {
	@MyAnnocation(name="哈哈",num=50,anno=@MyAnnocation2(value = "xxx"))
	public void show() {
		System.out.println("xxxxxxx");
	}
}

2.2 JDK的元 Annotation

元 Annotation指修饰Annotation的Annotation。

@Retention: 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 可以保留的域, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 通过这个变量指定域。

RetentionPolicy.CLASS: 编译器将把注解记录在 class文件中. 当运行 Java 程序时, JVM 不会保留注解. 这是默认值
RetentionPolicy.RUNTIME:编译器将把注解记录在 class文件中. 当运行 Java 程序时, JVM 会保留注解. 程序可以通过反射获取该注释
RetentionPolicy.SOURCE: 编译器直接丢弃这种策略的注释

@Target:指定注解用于修饰类的哪个成员.@Target 包含了一个名为value,类型为ElementType的成员变量。

@Documented:用于指定被该元 Annotation 修饰的Annotation类将被 javadoc 工具提取成文档。

@Inherited:被它修饰的 Annotation 将具有继承性.如果某个类使用了被 @Inherited 修饰的Annotation,则其子类将自动具有该注解。

案例演示2 使用反射获取注解信息

@Retention(RetentionPolicy.RUNTIME)
public @interface PersonInfo {
	String name();
	int age() default 20;
	String gender();
}

public class PersonOpe {
	@PersonInfo(name="李四",age=20,gender="男")
	public void show(String name,int age,String gen) {
		System.out.println(name);
		System.out.println(age);
		System.out.println(gen);
	}
}
public class Demo2 {
	public static void main(String[] args) throws Exception{
		PersonOpe ope=new PersonOpe();
		Class<?> class1=PersonOpe.class;
		Method method = class1.getMethod("show", String.class,int.class,String.class);
		PersonInfo annotation = method.getAnnotation(PersonInfo.class);
		String name=annotation.name();
		int age=annotation.age();
		String gender=annotation.gender();
		method.invoke(ope, name,age,gender);
		
		
	}
}

第三节:类加载器

​ Java类加载器(Java Classloader)是Java运行时环境(Java Runtime Environment)的一部分,负责动态加载Java类到Java虚拟机的内存空间中。

​ 类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。

​ JVM中有3个默认的类加载器:

  1. 引导(Bootstrap)类加载器。由原生代码(如C语言)编写,不继承自java.lang.ClassLoader。负责加载核心Java库,存储在<JAVA_HOME>/jre/lib目录中。

  2. 扩展(Extensions)类加载器。用来在<JAVA_HOME>/jre/lib/ext,或java.ext.dirs中指明的目录中加载 Java的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。该类由sun.misc.Launcher$ExtClassLoader实现。

  3. Apps类加载器(也称系统类加载器)。根据 Java应用程序的类路径(java.class.path或CLASSPATH环境变量)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。该类由sun.misc.Launcher$AppClassLoader实现。

    ​ 这三个类装载器存在父子层级关系,即根装载器是ExtClassLoader的父装载器,ExtClassLoader是AppClassLoader的父装载器。默认情况下使用AppClassLoader装载应用程序的类。

public class Test{
    Public static void main(String[] arg){
      	ClassLoader c  = Test.class.getClassLoader();  //获取Test类的类加载器
        System.out.println(c); 
      	ClassLoader c1 = c.getParent();  //获取c这个类加载器的父类加载器
        System.out.println(c1);
      	ClassLoader c2 = c1.getParent();//获取c1这个类加载器的父类加载器
        System.out.println(c2);
    }
}
3.1类加载过程

​ 每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的”.class”文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载,这里我们需要了解一下类加载的过程,如下:

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SoNgkVA9-1575685440937)(图片\02.png)]

  • 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象
  • 验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
  • 准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
  • 解析:主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析(这里涉及到字节码变量的引用,如需更详细了解,可参考《深入Java虚拟机》)。
  • 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。
3.2 双亲委托模式

​ Java装载类使用“全盘负责委托机制”。“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入;“委托机制”是指先委托父类装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类。这一点是从安全方面考虑的,试想如果一个人写了一个恶意的基础类(如java.lang.String)并加载到JVM将会引起严重的后果,但有了全盘负责制,java.lang.String永远是由根装载器来装载,避免以上情况发生 除了JVM默认的三个ClassLoder以外,第三方可以编写自己的类装载器,以实现一些特殊的需求。

为什么要使用这种双亲委托模式呢?

​ 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。

​ 考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法被加载。

演示错误

创建java.lang包并创建String类。

双亲委派模式工作原理

​ 双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,请注意双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码,类加载器间的关系如下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TBburOWz-1575685440938)(图片\01.png)]

​ 双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的实力坑爹啊。

3.3 使用类加载器加载属性文件

使用类加载器加载属性文件。

案例1:Properties加载属性文件

InputStream is = Test.class.getClassLoader().getResourceAsStream("user.properties");
Properties properties=new Properties();
properties.load(new InputStreamReader(is, "utf-8"));
properties.list(System.out);

案例2:使用ResourceBundle加载属性文件

//不用带扩展名
ResourceBundle bundle = ResourceBundle.getBundle("com.qf.day19.user");
//解决中文乱码问题
System.out.println(new String(bundle.getString("name").getBytes("iso-8859-1"),"utf-8"));
System.out.println(bundle.getString("age"));
System.out.println(new String(bundle.getString("gender").getBytes("iso-8859-1"),"utf-8"));

注意:属性文件默认编码为ISO-88590-1,如果修改为utf-8需要代码中处理乱码问题。

发布了72 篇原创文章 · 获赞 21 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_39513430/article/details/103432834