类加载机制、反射和单元测试

1.类加载机制

2.反射

3.单元测试

 

一、类加载机制

1.类加载原理和时机

    (1)原理

      当一个class文件被加载进内存时,在JVM中将形成一份描述该class文件结构的元信息对象Class,通过该对象可以获知class文件的结构信息:如构造器,字段,方法等。

      虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

      简而言之:class文件被虚拟机加载进内存生产Class对象的过程就是类加载机制。class文件-->Class对象的过程。

      类加载机制的流程:

      当程序要使用到某个类时,如果该类还未被加载进内存中,则系统会通过加载、连接、初始化三个步骤来对该类进行初始化操作。

      1.加载:查找和导入class文件

      类加载是指将类的class文件(字节码文件)载入到内存中,并为之创建一个java.lang.Class对象,我们称之为字节码对象.

      2.连接:把类的二进制数据合并到Java运行环境JRE(Java Runtime Environment))中

        1>:验证:检测被加载的类是否有正确的内部结构.

        2>:准备:负责为类的static变量分配内存,并设置默认值.

        3>:解析:把类的二进制数据中的符号引用替换为直接引用(深入分析JVM).

      (需要说明的是:这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中;并且这里所说的初始值“通常情况”是数据类型的零值,例如:public static int value = 123;value在准备阶段过后的初始值为0而不是123,而给value赋值的指令将在初始化阶段才会被执行)

      3.初始化:将符号引用转成直接引用。在此阶段,JVM负责对类进行初始化,主要就是对static变量进行初始化。

      类的初始化一个类包含以下几个步骤:

       1>:如果该类还未被加载和连接,则程序先加载并连接该类。

       2>:如果该类的直接父类还未被初始化,则先初始化其父类。

       3>:如果类中有初始化语句(静态代码块),则系统依次执行这些初始化语句。

      扩展:符号引用:符号引用是一个字符串,它给出了被引用的内容的名字并且可能会包含一些其他关于这个被引用项的信息——这些信息必须足以唯一的识别一个类、字段、方法。这样,对于其他类的符号引用必须给出类的全名。

    (2)时机

      下列几种情况能够触发类的初始化:

      1、使用new关键字实例化对象的时候

      2、操作类的静态字段的时候(被final修饰、已在编译期把结果放入常量池的静态字段除外)

      3、调用类的静态方法的时候。

      4、初始化子类的时候,先初始化其父类。

      5、用户指定运行一个主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

      6、直接使用java.exe命令来运行某个主类

      7、使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

2.类加载器ClassLoader

      (1)什么是类加载器

      类的加载过程(也就是加载.class文件)由类加载器(ClassLoader)完成;也就是说ClassLoader是用来动态的加载class文件到JVM中并转换成java.lang.class类的一个实例。

      注意:

      class文件可以是本地class文件,jar包中的class文件,来自网络的class文件等

      程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制来动态加载某个class文件到内存中。

      类加载器通常有JVM提供,当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构。当然我们也通过自定义类加载器,来指定一个类的加载过程。

protected 类<?>

findClass(String name) 

找到具有指定的课程 binary name 。

protected String

findLibrary(String libname) 

返回本机库的绝对路径名。

      (2)类加载器的分类

      1. Bootstrap ClassLoader 根类加载器/引导类加载器

      负责Java核心类的加载

      jdk安装目录\jre\lib目录下rt.jar文件中

      比如System,String等。

      注:这个加载器的是非常特殊的,它实际上不是ClassLoader的子类,而是由C/C++实现的。

      2. Extension ClassLoader 扩展类加载器

      负责JRE的扩展目录中jar包的加载。

      jdk安装目录\jre\lib\ext目录下

      3. System ClassLoader 系统类加载器/应用类加载器

      负责在JVM启动时加载我们自己写的Java类编译成的class文件和导入的jar包中的class文件。

      可以通过静态方法ClassLoader.getSystemClassLoader()找到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。

     代码示例

import java.lang.ClassLoader;
import sun.net.spi.nameservice.dns.DNSNameService;
import java.net.URL;
class Test {
	public static void main(String[] args) {
		
		//加载自己创建的类
		ClassLoader loader1 = ClassLoaderDemo.class.getClassLoader();
		System.out.println(loader1);//sun.misc.Launcher$AppClassLoader@73d16e93

		//加载ext里面内容
		ClassLoader loader2 = DNSNameService.class.getClassLoader();
		System.out.println(loader2);//sun.misc.Launcher$ExtClassLoader@70dea4e

		//加载rt.jar里面内容
		ClassLoader loader3 = String.class.getClassLoader();
		System.out.println(loader3);//null

		//查看bootstrap classloader加载了哪些核心类库
		URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
		for(int i=0;i<urls.length;i++){
			System.out.println(urls[i].toExternalForm());
		}
		/*
		file:/C:/Program%20Files/Java/jdk1.8.0_191/jre/lib/resources.jar
		file:/C:/Program%20Files/Java/jdk1.8.0_191/jre/lib/rt.jar
		file:/C:/Program%20Files/Java/jdk1.8.0_191/jre/lib/sunrsasign.jar
		file:/C:/Program%20Files/Java/jdk1.8.0_191/jre/lib/jsse.jar
		file:/C:/Program%20Files/Java/jdk1.8.0_191/jre/lib/jce.jar
		file:/C:/Program%20Files/Java/jdk1.8.0_191/jre/lib/charsets.jar
		file:/C:/Program%20Files/Java/jdk1.8.0_191/jre/lib/jfr.jar
		file:/C:/Program%20Files/Java/jdk1.8.0_191/jre/classes
		*/

	}
}
class ClassLoaderDemo{
}

        JRE中并不是所有类,在自己定义的类中都可以直接加载。例如在加载ext目录下的jar包中的类就不能直接使用。可以按照下图步骤解决该问题:

      (3)全盘负责委托机制

       上面学习的三个类加载器没有继承关系,只有加载顺序的关系:

       引导类加载器 > 扩展类加载器 > 应用类加载器

       那么我们自己创建的一个类是如何加载的?是不是就是按照顺序加载的呢?

       首先使用引导类加载器,但是看到不是自己做的事情,向下给扩展类加载器,但是扩展类加载器一看,也不是自己做的事情,继续向下给应用类加载器。

       这个过程其实就是:全盘委托机制

       全盘负责:即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的其它Class通常也由这个classloader负责载入。

       比如:A类如果要使用B类(不存在),A类加载器必须负责加载B类。

       委托机制:需要查找类或资源时,ClassLoader实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。先让parent(父)类加载器 寻找,只有在parent找不到的时候才从自己的类路径中去寻找。也就是说一个类如果加载了,将直接使用。如果没有加载就先去加载再使用。

       比如:A类加载器如果要加载资源B,必须询问父类加载是否加载。

       cache(缓存):类加载还采用了cache机制:如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache。这就是为什么修改了Class但是必须重新启动JVM才能生效,并且类只加载一次的原因。

       

       注意:

       全盘负责委托机制保证了一个class文件只会被加载一次,形成一个Class对象。

       如果一个class文件,被两个类加载器加载,将是两个对象。

       (自定义类加载,可以将一个class文件加载多次)

        

        

      (4)自定义类加载器(了解)

      通过自定义类加载器,我们可以自定义一个类的加载过程。

       

      步骤:

      1、编写一个类继承ClassLoader

      2、重写findClass方法

      3、在findClass方法中调用父类的defineClass方法

      原理:

      将.class文件通过流读取到,得到一个byte[]数组。通过defineClass方法直接生成Class对象。

     代码示例

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader {
	private String rootDir;
	public MyClassLoader(String rootDir) { 
		this.rootDir = rootDir;
	}
	//参数是类的全名
	@Override
	public Class<?> findClass(String name) throws ClassNotFoundException {
		String extname = name.replace(".", "\\");  
		String filename = rootDir + "\\" + extname + ".class"; 
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		try {
			InputStream is = new FileInputStream(filename);
			int len = -1;
			byte[] b = new byte[1024];
			while ((len = is.read(b)) != -1) {
				baos.write(b, 0, len);
			}
			baos.flush();
			baos.close();
			is.close();
			byte[] data = baos.toByteArray();
			return super.defineClass(name, data, 0, data.length);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}

      (5)实现类的热部署(了解)

       什么是类的热部署:

       所谓热部署,就是在应用正在运行的时候升级软件,不需要重新启用应用。

       对于Java应用程序来说,热部署就是运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程中,类加载器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持热部署。

       类加载器不能重新载入一个已经载入的类,但只要使用一个新的类加载器实例,就可以将类再次装入一个正在运行的应用程序。

       如何实现Java类的热部署

  前面的分析,我们已经知道,JVM在加载类之前会检查请求的类是否已经被加载过来,也就是要调用findLoadedClass方法查看是否能够返回类实例。如果类已经加载过来,再调用loadClass会导致类冲突。

       但是,JVM判断一个类是否是同一个类有两个条件:一是看这个类的完整类名是否一样(包括包名),二是看加载这个类的ClassLoader加载器对象是否是同一个。       

       所以,要实现类的热部署可以创建不同的ClassLoader的实例对象,然后通过这个不同的实例对象来加载同名的类。

二、反射

1.引入

       到目前为止我们已经知道了当一个class文件被类加载器加载进内存时,会在JVM中形成一份描述该class文件结构的元信息对象Class,通过该对象JVM就可以获知class文件的结构信息:如构造器,字段,方法等。

       那么,问题来了

       Class类到底是什么?

       我们该如何通过API去创建Class类的实例对象?

       又该如何去获取Class对象中的构造器、字段、方法这些信息呢?

       这就是接下来的反射要研究的内容。

2.Class类理解

       由面向对象引发的思考:

       在学习面向对象阶段我们了解到java是一门面向对象的编程语言,并且在java中,万物皆对象。那么问题来了:“类”这类事物是不是对象呢?又该如何表示呢?

       既然万物皆对象,那么类名、构造器、字段、方法等这些信息当然也需要封装成一个对象,,这就是Class类、Constructor类、Field类、Method类。

       而通过Class类、Constructor类、Field类、Method类等类的实例对象就可以得到相应的信息,甚至可以不用new关键字就创建一个实例,并设置获取字段的值,执行对象中的方法,这就是反射技术。

        

       通过上面的图示我们可以用自己的话总结一下什么是反射:反射就是在运行期间,动态的获取类中成员信息(构造器、字段、方法)的过程。

3.获取类的字节码对象(Class类型对象)的三种方式

      要想获取和操作类中的内容,首先要获取类的字节码对象。这些对象(类的字节码对象,也称为.class对象),都是Class类型的对象。

      获取字节码对象的方式:

      (1)对象名.getClass():返回的是某个引用指向的具体对象所属的运行是类的字节码对象。获取的是那个真正用来创建对象的子类的字节码对象。

      (2)类名.class:如果已经有了类名,可以通过.class的方式获取这个类的字节码对象。

      (3)Class.forName(String className):Class类中的一个静态方法,可以根据一个类的全类名,动态的加载某个类型。传入一个类的全类名,将类描述的字节码文件,加载到内存中,形成一个字节码对象,并且把这个对象作为该方法的返回值。(在调用方法之前,内存中是没有这个字节码对象的)。字符串的来源非常广泛,来源于源代码,可以来源于键盘录入、网络传输、文件读取、数据库等。

      代码示例

package com.xj.Test;

class Test {
	public static void main(String[] args) throws Exception {
		
		//getClass的方法获取Person类型的字节码对象
		Person p1 = new Person();
		Class clazz1 = p1.getClass();
		System.out.println(clazz1);//class com.xj.Test.Person
		
		//类名.class的方式获取person的字节码对象
		Class clazz2 = Person.class;
		System.out.println(clazz2);//class com.xj.Test.Person
		System.out.println(clazz1==clazz2);//true
		
		//Class.forName的方式获取person的字节码对象
		Class clazz3 = Class.forName("com.xj.Test.Person");
		System.out.println(clazz3);//class com.xj.Test.Person

	}
}
package com.xj.Test;

public class Person {

}

4.Class的newInstance()方法创建对象

T

newInstance(Object... initargs) 

使用此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。

     代码示例

package com.xj.Test;

import java.util.Date;

class Test {
	public static void main(String[] args) throws Exception {
		
		Date date = new Date();
		Class c1 = date.getClass();
		Object obj1 = c1.newInstance();
		Date d1 = (Date)obj1;
		System.out.println(d1);//Wed Nov 20 17:16:15 CST 2019
		
		Class c2 = Date.class;
		Object obj2 = c2.newInstance();
		Date d2 = (Date)obj2;
		System.out.println(d2);//Wed Nov 20 17:16:15 CST 2019
		
		Class c3 = Class.forName("java.util.Date");
		Object obj3 = c3.newInstance();
		Date d3 = (Date)obj3;
		System.out.println(d3);//Wed Nov 20 17:16:15 CST 2019
			

	}
}

5.反射构造方法

Constructor<T>

getConstructor(<?>... parameterTypes) 

返回一个 Constructor对象,该对象反映 Constructor对象表示的类的指定的公共 函数。

Constructor<?>[]

getConstructors() 

返回包含一个数组 Constructor对象反射由此表示的类的所有公共构造 对象。

Constructor<T>

getDeclaredConstructor(<?>... parameterTypes) 

返回一个 Constructor对象,该对象反映 Constructor对象表示的类或接口的指定 函数。

Constructor<?>[]

getDeclaredConstructors() 

返回一个反映 Constructor对象表示的类声明的所有 Constructor对象的数组  。

      使用反射来获取类的构造器的步骤:

      (1)获取该类的字节码对象

      (2)从该字节码对象中获取需要的构造器

       需求:

       1.获取所有public构造器

       2.获取所有构造器包括private

       3.获取无参数public构造器

       4.获取带有指定参数的public构造器

       5.获取带有指定参数的private构造器

       注:不使用Declared获取带有指定参数的private构造器会报错

       代码示例:反射共有构造方法

package com.xj.Test;

public class Person {
	
		// 成员变量
		private Long id;
		public String name;
		public int age;

		// 构造方法
		public Person() {
			System.out.println("无参数public构造器被执行");
		}
		public Person(String name) {
			this.name = name;
			System.out.println("带有String的public构造器被执行");
		}
		// 私有的构造方法
		private Person(String name, int age) {
			this.name = name;
			this.age = age;
			System.out.println("带有String, int的private构造器被执行");
		}
		public Person(Long id, String name, int age) {
			super();
			this.id = id;
			this.name = name;
			this.age = age;
			System.out.println("带有Long, String, int的public构造器被执行");
		}

		@Override
		public String toString() {
			return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
		}

	}
package com.xj.Test;

import java.lang.reflect.Constructor;

class Test {
	public static void main(String[] args) throws Exception {
		
		Class clazz = Person.class;
		//获取全部被public修饰的构造方法
		Constructor[] cs = clazz.getConstructors();   
		for(Constructor con:cs){
			System.out.println(con);
		}
		
		Constructor con1 = clazz.getConstructor();
		Constructor con2 = clazz.getConstructor(String.class);
		Constructor con3 = clazz.getConstructor(String.class,int.class);
		Constructor con4 = clazz.getConstructor(Long.class,String.class,int.class);
		   
		
		System.out.println(con1);
		System.out.println(con2);
		System.out.println(con3);
		System.out.println(con4);
	}
}

  

       代码示例:反射私有构造方法

package com.xj.Test;

import java.lang.reflect.Constructor;

class Test {
	public static void main(String[] args) throws Exception {
		
		Class clazz = Person.class;
		//反射到所有构造方法,包括private
		
		Constructor[] cons = clazz.getDeclaredConstructors();
		
		for(Constructor con:cons) {
			System.out.println(con);
		}
		
		System.out.println("------");
		
		Constructor con = clazz.getDeclaredConstructor(String.class,int.class);
		System.out.println(con);
	}
}

     代码示例:调用反射到的构造方法创建对象:

     常用API:

     public T newInstance(Object... initargs)

     参数:initargs:表示调用构造器的实际参数

     返回:返回创建的实例,T表示Class所表示类的类型

     注意:

     如果使用public空参构造创建对象,那么可以直接使用Class类中的newInstance方法创建对象。

     调用私有的构造器前需要设置开启暴力访问:constructor.setAccessible(true);

package com.xj.Test;

import java.lang.reflect.Constructor;

class Test {
	public static void main(String[] args) throws Exception {
		
		Class clazz = Person.class;
		
		Constructor con1 = clazz.getConstructor();
		Object obj1 = con1.newInstance();
		Person person1 = (Person)obj1;
		System.out.println(person1);
		
		System.out.println("-------");
		
		Constructor con2 = clazz.getConstructor(Long.class,String.class,int.class);
		Object obj2 = con2.newInstance(1000L,"zhangsan",20);
		Person person2 = (Person)obj2;
		System.out.println(person2);
		
		System.out.println("=======");
		
		Constructor con3 = clazz.getDeclaredConstructor(String.class,int.class);
		//私有的构造方法不能直接使用
		con3.setAccessible(true);//暴力反射
		Object obj3 = con3.newInstance("lisi",21);
		Person person3 = (Person)obj3;
		System.out.println(person3);
		
	}
}

      练习

package com.xj.Test;

import java.util.Scanner;

class Test {
	public static void main(String[] args) throws Exception {
		
		Scanner sc = new Scanner(System.in);
		String str = sc.nextLine();
		Class clazz = Class.forName("com.xj.Test."+str);
		Fruit fruit = (Fruit)clazz.newInstance();
		fruit.flow();
		
	}
}

interface Fruit{
	void flow();
}
class Apple implements Fruit{

	@Override
	public void flow() {
		System.out.println("苹果汁");
	}	
}

class Orange implements Fruit{

	@Override
	public void flow() {
		System.out.println("橙子汁");	
	}
}

6.反射方法

方法

getMethod(String name, 类<?>... parameterTypes) 

返回一个 方法对象,它反映此表示的类或接口的指定公共成员方法 对象。

方法[]

getMethods() 

返回包含一个数组 方法对象反射由此表示的类或接口的所有公共方法 对象,包括那些由类或接口和那些从超类和超接口继承的声明。

方法

getDeclaredMethod(String name, 类<?>... parameterTypes) 

返回一个 方法对象,它反映此表示的类或接口的指定声明的方法 对象。

方法[]

getDeclaredMethods() 

返回包含一个数组 方法对象反射的类或接口的所有声明的方法,通过此表示 对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法

      Method类下的方法:

      getModifiers()         获得访问控制符

      getReturnType()        获得返回值类型

      getName()            获得方法名

      getParameterTypes()    获得参数列表

      需求:

     1、获取所有public方法,包括继承的

     2、获取所有方法,包括private,不包括继承的

     3、获取指定方法,包括继承的

     4、获取指定方法,包括private,不包括继承的

package com.xj.Test;

import java.util.Arrays;

public class Person {
	
		//无参无返回的方法
		public void method1() {
			System.out.println("无参无返回的public方法被执行");
		}
		//有参无返回的方法
		public void method2(String name) {
			System.out.println("有参无返回的public方法被执行 name="+name);
		}
		//无参有返回的方法
		public int method3() {
			System.out.println("无参有返回的public方法被执行");
			return 123;
		}
		//有参有返回
		public String method4(String name) {
			System.out.println("有参有返回的public方法被执行");
			return "有参有返回"+name;
		}
		
		//私有方法
		private void method5() {
			System.out.println("private私有方法被执行");
		}
		
		//静态方法
		public static void method6(String name) {
			System.out.println("静态方法被执行 name="+name);
		}
		
		//参数是基本数据类型的数组
		public void method7(int...arr) {
			System.out.println("参数是基本数据类型的数组的方法被执行arr= "+ Arrays.toString(arr));
		}
		
		//参数是引用数据类型的数组
		public void method8(String...arr) {
			System.out.println("参数是引用数据类型的数组的方法被执行arr= "+ Arrays.toString(arr));
		}
		
		

	}
package com.xj.Test;

import java.lang.reflect.Method;

class Test {
	public static void main(String[] args) throws Exception {
		
		Class clazz = Person.class;
		
		//getMethods() 获得public的方法和父类的方法
		Method[] ms = clazz.getMethods();
		for(Method m:ms) {
			//System.out.println(m);
			System.out.println(m.getModifiers());//获得访问控制符
			System.out.println(m.getReturnType());//获得返回值类型
			System.out.println(m.getName());//获得方法名
			
			Class[] rs = m.getParameterTypes();//获得参数列表
			for(Class c:rs) {
				System.out.println(c.getName());
			}
			System.out.println("---------");
		}
		
		//getDeclaredMethods() 获得本类中的所有方法(包括private的)
		Method[] ms1 = clazz.getDeclaredMethods();
		for(Method m:ms) {
			System.out.println(m);
		}
		
		//getMethod() 获取具体某个方法
		Method method = clazz.getMethod("method4", String.class);
		System.out.println(method);
		
		//getDeclaredMethod获取私有方法
		Method method2 = clazz.getDeclaredMethod("method5", null);
		System.out.println(method2);
	}
}

      执行反射的方法

Object

invoke(Object obj, Object... args) 

在具有指定参数的 方法对象上调用此 方法对象表示的底层方法。

      参数:

      obj: 表示被调用方法所属对象

      args:表示调用方法是传递的实际参数

      返回:方法的返回结果

      注意:在调用私有方法之前,开启暴力访问:Method.setAccessible(true);

      需求

      1、执行无参无返回的方法

      2、执行有参无返回的方法

      3、执行无参有返回的方法

      4、执行有参有返回的方法

      5、执行私有方法

      代码示例

package com.xj.Test;

import java.lang.reflect.Method;

class Test {
	public static void main(String[] args) throws Exception {
		
		Class clazz = Person.class;
		Person p = (Person)clazz.newInstance();
		
		Method method1 = clazz.getMethod("method1",null);
		method1.invoke(p, null);
		
		Method method2 = clazz.getMethod("method2",String.class);
		method2.invoke(p,"Tom");
		
		Method method3 = clazz.getMethod("method3",null);
		Object obj3 = method3.invoke(p, null);
		System.out.println(obj3);
		
		Method method4 = clazz.getMethod("method4",String.class);
		Object obj4 = method4.invoke(p,"Jerry");
		System.out.println(obj4);
		
		Method method5 = clazz.getDeclaredMethod("method5",null);
		method5.setAccessible(true);  //暴力反射
		method5.invoke(p,null);
	}
}

7.反射调用静态方法和数组参数方法

       使用反射调用静态方法:

       静态方法不属于任何对象,静态方法属于类本身。

       此时把invoke方法的第一个参数设置为null即可。

       使用反射调用数组参数方法(可变参数):

       调用方法的时候把实际参数统统作为Object数组的元素即可。

       Method对象.invoke(方法所属对象,new Object[]{所有实参 });

       代码示例

package com.xj.Test;

import java.lang.reflect.Method;

class Test {
	public static void main(String[] args) throws Exception {
		
		Class clazz = Person.class;
		Person p = (Person)clazz.newInstance();
		
		Method method6 = clazz.getMethod("method6", String.class);
		method6.invoke(p, "张三");
		method6.invoke(null,"李四"); //静态方法不属于任何对象,所以第一个参数给null也可以
		
		Method method7 = clazz.getMethod("method7",int[].class);
		method7.invoke(p, new Object[] {new int[] {1,2,3,4}});
		
		Method method8 = clazz.getMethod("method8",String[].class);
	    method8.invoke(p,new Object[]{new String[]{"a","b","c"}});
	}
}

8.反射属性

Field

getField(String name) 

返回一个 Field对象,它反映此表示的类或接口的指定公共成员字段 对象。

Field[]

getFields() 

返回包含一个数组 Field对象反射由此表示的类或接口的所有可访问的公共字段 对象。

Field

getDeclaredField(String name) 

返回一个 Field对象,它反映此表示的类或接口的指定已声明字段 对象。 暴力反射

Field[]

getDeclaredFields() 

返回的数组 Field对象反映此表示的类或接口声明的所有字段 对象。

void

set(Object obj, Object value) 

将指定对象参数上的此 Field对象表示的字段设置为指定的新值。

Object

get(Object obj) 

返回该所表示的字段的值Field,指定的对象上

       Field类的方法

       getModifiers();  //修饰符

       getType();       //类型

       getName();       //变量名

       get(Object obj);     // 获取值

       注意私有成员变量,通过setAccessible(boolean flag)方法暴力访问

       需求:

       获取所有public字段

       获取所有字段包括private

       获取指定public字段并设置值

       获取指定private字段并设置值

package com.xj.Test;

public class Person {
	
		private Long id;
		public String name;
		public int age;

	}
package com.xj.Test;

import java.lang.reflect.Field;

class Test {
	public static void main(String[] args) throws Exception {
		
		Class clazz = Person.class;
		Object instance = clazz.newInstance();
		
		System.out.println("===获取所有public字段===");
		Field[] fields = clazz.getFields();
		for(Field field:fields) {
			System.out.println(field);
		}
		
		System.out.println("==获取所有字段包括private==");
		Field[] declaredFields = clazz.getDeclaredFields();
		for(Field field:declaredFields) {
			System.out.println(field);
		}
		
		System.out.println("==获取指定public字段并设置值==");
		Field field = clazz.getField("name");
		field.set(instance, "Andy");
		System.out.println(field.get(instance));
		
		System.out.println("==获取自定private字段并设置值==");
		Field declaredField = clazz.getDeclaredField("id");
		declaredField.setAccessible(true);
		declaredField.set(instance, 1L);
		System.out.println(declaredField.get(instance));
	}
}

9.反射其他

          String getName():获取全限定名

          String getSimpleName():获取简单类名,不包含包名

          Package getPackage():获取该类的包

          Class getSuperclass():获取父类的Class

          getGenericSuperclass():获取父类

          boolean isArray():是否为数组类型

          boolean isEnum():是否为枚举类型

          boolean isInterface():是否为接口类型

          boolean isPrimitive():是否为基本类型

          boolean isSynthetic():是否为引用类型

          boolean isAnnotation():是否为注解类型

          boolean isAnnotationPresent(Class annotationClass):当前类是否加了指定类型注解

          代码示例

package com.xj.Test;

import java.lang.reflect.Field;
import java.util.Arrays;

class Test {
	public static void main(String[] args) throws Exception {
		
		Class clazz = Person.class;
		System.out.println("getName():"+clazz.getName());
		System.out.println("getSimpleName():"+clazz.getSimpleName());
		System.out.println("getPackage():"+clazz.getPackage());
		System.out.println("getModifiers():"+clazz.getModifiers());
		System.out.println("getSuperclass():"+clazz.getSuperclass());
		System.out.println("getInterfaces():"+Arrays.toString(clazz.getInterfaces()));
		System.out.println("getGenericSuperclass():"+clazz.getGenericSuperclass());
		System.out.println("isArray():"+clazz.isArray());
		System.out.println("isEnum():"+clazz.isEnum());
		System.out.println("isInterface():"+clazz.isInterface());
	}
}

10.反射应用—泛型擦除

       //有如下集合

       ArrayList<Integer> list = new ArrayList<>();

      list.add(666);

      //设计代码,将字符串类型的"abc",添加到上述带有Integer泛型的集合list中

package com.xj.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;

class Test {
	public static void main(String[] args) throws Exception {
		
		ArrayList<Integer> list = new ArrayList<Integer>();
		list.add(666);
		
		// 要把 "abc" 也加入到list中
		//list.add("abc");  // 编译期检查泛型——"abc" 不能加入到list中。可以在运行期加入
		/**
		 * 在编译阶段,检查add方法的实际参数,如果在编译阶段,不要调用add方法,
		 * 就会避免掉在编译阶段,对实际参数数据类型的检查
		 * 在运行阶段,调用add方法
		 * 使用反射的方式,调用某个方法,在写代码的阶段,根本不知道将来调用哪个方法
		 * 编译器也就没有办法在编译阶段对代码进行检查
		 * 
		 * 这种方式叫做“泛型擦除”
		 * 在java中,只会在编译阶段,对泛型进行检查,到了运行阶段,对泛型不检查
		 * 称这种泛型为:伪泛型
		 */
		Class clazz = list.getClass();
		
		Method method = clazz.getMethod("add",Object.class);
		method.invoke(list, "abc");		
		System.out.println(list);
	}
}

三、单元测试

1.测试的概念

       先认识到测试的重要性:优秀的软件不是开发出来的,而是测试出来的。

       软件测试分类:黑盒测试白盒测试

       黑盒测试:测试工程师

       黑盒测试又称功能测试,主要检测每个功能是否都能正常使用。

       在测试中,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,进行测试,主要针对软件界面和软件功能进行测试。

       白盒测试:开发工程师

       白盒测试又称结构测试、透明盒测试、逻辑驱动测试或基于代码的测试,主要检测程序内部逻辑是否正常

       在测试中测试者必须检查程序的内部结构,从检查程序的逻辑着手,得出测试数据。按照程序内部的结构进行测试

这一方法是把测试对象看作一个打开的盒子,测试人员清楚盒子内部的东西以及里面是如何运作的。

       测试先行的思想

       在你不知道如何测试代码前,就不要写代码去完成功能--------->测试先行。

       单元测试Junit就属于白盒测试

       Java的单元测试:Junit

       junit3.x   针对于Java5之前的版本,没有注解,得按照规范来写测试。

       junit4.x   针对于Java5以及之后的版本,使用注解,推荐.

       junit5.x

2.使用Junit4.x进行单元环境

    (1)环境搭建(Jar包添加图解

      

      

      

      

      (2)使用步骤

      1、把junit4.x的测试jar,添加到该项目中来;

      2、定义一个测试类(约定俗称的规则,非强制要求)

      测试类的名字: XxxTest,例如要测试MyMath类型,一般定义测试类的类名为MyMathTest

      在MyMathTest中编写测试方法

      例如要测试MyMath中的add方法

import org.junit.Test;

public class MyMathTest {
	 @Test
	 public void testAdd() throws Exception{ 
		 
	 }
}

       注意方法是public修饰的,无返回的,该方法上必须贴有@Test标签。

      1.选择某一个测试方法,鼠标右键选择 [run as junit],或则选中测试类,表示测试该类中所有的测试方法。

      

     2.运行结果查看

    

   表示测试正确

   

   表示测试错误,会给出错误的堆栈信息

3.常见注解

   Junit4.x基于Java5开始的版本,支持注解。

   常用注解:

   @Test:要执行的测试方法

   @Before 每次执行测试方法之前都会执行

   @After 每次执行测试方法之后都会执行

   @BeforeClass 在所有的Before方法之前执行,只在最初执行一次。只能修饰静态方法

   @AfterClass 在所有的After方法之后执行,只在最后执行一次。 只能修饰静态方法

   执行顺序: BeforeClass->(Before->Test-After多个测试方法)-->AfterClass

   示例代码

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class MyMathTest {
	
	@BeforeClass
	public static void myBeforeClass() {
		System.out.println("beforeClass....");
	}
	
	@Before
	public void init() {
		System.out.println("init....");
	}
	
	 @Test
	 public void testAdd() throws Exception{ 
		 System.out.println("测试方法");
	 }
	 
	 @After
	 public void myAfter() {
		 System.out.println("myAfter...");
	 }
	 
	 @AfterClass
	 public static void myAfterClass() {
		 System.out.println("afterClass...");
	 }
}

4.规范的Junit测试方式

     (1)已知要测试的类


public class MyMath {
	public int add(int x,int y){
		return x+y;
	}	
	public int minus(int x,int y){
		return x-y;
	}
	public int multiply(int x,int y){
		return x*y;
	}
	public int divide(int x,int y){
		return x/y;
	}
}

     (2)生成测试类

     (3)选择要测试的方法

     (4)生成测试类完成代码测试

import static org.junit.Assert.*;

import org.junit.Test;

import junit.framework.Assert;


public class MyMathTest {
	
	private MyMath math = new MyMath();

	@Test
	public void testAdd() {
		int result = math.add(1, 2);//执行MyMath中的minus方法获得结果
		//使用断言
		Assert.assertEquals(3, result);
	}

	@Test
	public void testMinus() {
		int result = math.minus(1, 2);
		Assert.assertEquals(-1, result);
	}

	@Test
	public void testMultiply() {
		int result = math.multiply(1,2);
		Assert.assertEquals(2, result);
	}

	@Test
	public void testDivide() {
		int result = math.divide(1,2);
		Assert.assertEquals(0, result);
	}

}

5.断言

     (1)为什么要使用断言

     【强制】单元测试应该是全自动执行的,并且非交互式的。测试框架通常是定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试并不是 一个好的单元测试。单元测试中不准使用System.out来进行人肉验证,必须使用assert来验证。

       (2)什么是断言

        期望值(猜测值):   断言时希望是多少。

        真实值(程序运行的结果):   程序内部运算之后实际是多少。

        断言成功(猜对了):   期望值和真实值相同,此时显示绿条。

        断言失败(猜错了):   期望值和真实值不同,此时显示红条。

       (3)常用API

       1、Assert.assertEquals(message, expected, actual):比较的值

       message:  断言失败的提示信息,断言成功不会显示

       expected: 期望值

       actual:   真实值

       若真实值和期望值想等,则断言成功.--->绿条

      以下断言作为了解即可

      2、Assert.assertSame(message, expected, actual):比较地址,是同一个对象

      Assert.assertNotSame(message, expected, actual):断言不是同一个对象

      3、Assert.assertTrue(message, condition):断言condition应该为TRUE

      Assert.assertFalse(message, condition):断言condition应该为FALSE

      4、Assert.assertNull(message, object):断言对象object为null

      Assert.assertNotNull(message, object):断言对象object不为null

      5、@Test(expected=ArithmeticException.class)

     断言该方法报错ArithmeticException

     6、@Test(timeout=400)

    期望该方法在400毫秒之内执行完成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值