JUnit、反射与Properties类的使用
目标
n JUnit单元测试
n 反射
n Properties
n 反射和Properties的综合案例
1 JUnit单元测试
1.1 JUnit的概述
JUnit是一个Java语言的单元测试框架,简单理解为可以用于取代java的main方法。JUnit属于第三方工具,一般情况下需要导入jar包。不过,多数Java开发环境已经集成了JUnit作为单元测试工具。
1.2 JUnit使用
1. JUnit的下载
官网:http://www.junit.org
2. 在eclipse中的使用步骤
1) 创建项目
2) 把JUnit的jar包导入到项目的类环境中,eclipse中已经自带了JUnit工具包,直接导入即可使用。
3) 编写单元测试方法。加上@Test注解。编写测试逻辑
4) 点工具条上的绿色运行箭头,运行单元测试方法
案例代码:
测试类:
public class Demo01 {
@Test
public void testGetUp() {
Student s1 = new Student();
s1.getUp();
}
}
学生类:
public class Student {
public void getUp() {
System.out.println("起床");
}
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
1.3 测试方法
1. 测试方法的特点:
1) 在方法的顶部加上@Test注解,建议方法名称: test+需要测试的方法名,方法遵守驼峰式命名法。方法首单词小写,以后的单词首字母大写。
2) 修饰符必须是public,可以用final修饰,不能有返回值,方法不能含有参数,可以抛出异常。
2. 如何运行测试方法
在package Explorer中
1) 右击方法,右键单击Run as - JUnit Test。运行一个单元测试方法。
2) 右击类,右键单击Run as - JUnit Test。运行该类上所有的单元测试方法
3) 右击项目,右键单击Run as - JUnit Test。运行该项目下所有的单元测试方法。
3. 查看结果
看到绿条,证明测试通过;看到红条,证明测试不通过。
1.4 初始化环境和清理环境方法
1. 注解的介绍:
n @Test:测试方法
n @Before: 在每个单元测试方法执行之前执行
n @After: 在每个单元测试方法执行完毕之后执行
n @BeforeClass: 在所有单元测试方法执行之前执行1次。注意方法必须是static的
n @AfterClass:在所有单元测试方法执行完毕之后执行1次。注意方法必须是static的
2. 案例代码:
public class Demo01 {
Student s1;
@BeforeClass
public static void init() {
System.out.println("@BeforeClass 所有测试方法之前,执行一次");
}
@AfterClass
public static void destory() {
System.out.println("@AfterClass 所有测试方法之后,执行一次");
}
@Before
public void before() {
System.out.println("@Before, 在每个测试方法之前");
s1 = new Student();
}
@After
public void after() {
System.out.println("@After, 在每个测试方法之后");
}
@Test
public void testGetUp() {
s1.getUp();
}
@Test
public void testEat() {
s1.eat();
}
@Test
public void testSleep() {
s1.sleep();
}
}
2 Java中的反射
2.1 类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
l 加载
就是指将class文件读入内存,并为之创建一个Class对象。
任何类被使用时系统都会建立一个Class对象
l 连接
验证 是否有正确的内部结构,并和其他类协调一致
准备 负责为类的静态成员分配内存,并设置默认初始化值
解析 将类的二进制数据中的符号引用替换为直接引用
l 初始化
就是我们以前讲过的初始化步骤
2.1 反射
● 提问:eclipse中一个对象,是怎么样知道这个对象有哪些方法、属性?
1. 什么是反射:
反射就是在运行的时候通过类对象Class,得到类中有哪些构造方法Constructor、方法 Method、属性Field等对象,并对它们进行操作。
2. 反射在实际开发中的应用:
1) 各种框架的设计
、 、
2) IDE等系统软件的开发
2.2 得到Class对象的三种方式
1) 直接通过类的静态属性.class得到。如:Date.class
2) 通过对象的方法:getClass()得到,在Object中有这个方法。
3) 通过类的字符串全名,得到类对象。调用Class.forName("完全限定类名") ,会有ClassNotFoundException异常。注:三种方式得到的类对象,是同一个对象
2.3 Class类中的方法:
1) getName() 得到类的完全限定类名。如:java.util.Date
2) getSimpleName() 得到类名。 如:Date
2.4 Class类介绍
一个类包含:构造方法,成员变量,成员方法
Class中使用:Constructor表示构造方法,Field表示成员变量,Method表示成员方法
2.5 Constructor构造方法类
1. Constructor作用:代表一个构造方法,通过构造方法实例化对象。
2.返回一个构造方法:具体返回哪个构造方法看参数传递的是什么类型
getConstructor |
|
getDeclaredConstructor |
3.返回多个构造方法:
|
getConstructors |
|
getDeclaredConstructors |
4. 得到反射中对象的规律:
有Declared的可以得到所有声明的方法,没有Declared的只能得到公有的方法
私有的构造方法在调用前设置: setAccessible(true);
案例代码:
Emoplyee类:
public class Emoplyee {
private String id;
private String name;
private int age;
public Emoplyee() {
}
private Emoplyee(String id) {
this.id = id;
}
Emoplyee(String id, String name) {
this.id = id;
this.name = name;
}
public Emoplyee(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Emoplyee [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
测试类:
public class Demo03 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class<?> clazz = Class.forName("com.itheima.demo03Calss获取构造方法.Emoplyee");
// 获取到无参构造
Constructor<?> c1 = clazz.getConstructor();
// System.out.println(c1);
Emoplyee e1 = (Emoplyee)c1.newInstance();
System.out.println(e1);
// 获取到对应参数的构造
Constructor<?> c2 = clazz.getConstructor(String.class, String.class, int.class);
// System.out.println(c2);
Emoplyee e2 = (Emoplyee)c2.newInstance("001", "马云", 45);
System.out.println(e2);
// 获取到非public的构造方法
Constructor<?> c3 = clazz.getDeclaredConstructor(String.class);
// System.out.println(c3);
c3.setAccessible(true); // 这个构造方法本来是私有的,将他设置为可以被别人访问的
Emoplyee e3 = (Emoplyee)c3.newInstance("002");
System.out.println(e3);
// System.out.println("===================");
// 获取到所有公开的构造方法
// Constructor<?>[] constructors = clazz.getConstructors();
// for (Constructor<?> constructor : constructors) {
// System.out.println(constructor);
// }
// System.out.println("===================");
// 获取到所有的构造方法
// Constructor<?>[] constructors2 = clazz.getDeclaredConstructors();
// for (Constructor<?> constructor : constructors2) {
// System.out.println(constructor);
// }
}
}
2.6 Method方法类
1.在反射机制中,把类中的成员方法使用类Method表示。
2.返回获取一个方法:
getMethod |
|
getDeclaredMethod |
3.返回获取多个方法:
|
getMethods |
|
getDeclaredMethods |
2. Method类的方法:
1) .调用实例方法
method对象.invoke(对象, 方法的参数值);
2) .调用静态方法
method对象.invoke(null, 参数); //静态方法的调用
案例代码:
Emoplyee类:
public class Emoplyee {
private String id;
private String name;
private int age;
public Emoplyee() {
}
private Emoplyee(String id) {
this.id = id;
}
Emoplyee(String id, String name) {
this.id = id;
this.name = name;
}
public Emoplyee(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Emoplyee [id=" + id + ", name=" + name + ", age=" + age + "]";
}
public void work(int hour) {
System.out.println(name + "工作" + hour + "个小时");
}
// 私有的方法
private int eat(String food) {
System.out.println(name + "吃" + food);
return 100;
}
// 静态方法
public static void sleep(int hour) {
System.out.println("睡觉zZZ");
}
}
测试类:
public class Demo04 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class<?> clazz = Class.forName("com.itheima.demo03Calss获取构造方法.Emoplyee");
// 根据构造方法创建对象
Constructor<?> c = clazz.getConstructor(String.class, String.class, int.class);
Emoplyee emoplyee = (Emoplyee)c.newInstance("001", "马云", 45);
// 获取单个方法
Method m1 = clazz.getMethod("work", int.class);
// System.out.println(m1);
m1.invoke(emoplyee, 12);
// 获取单个私有方法
Method m2 = clazz.getDeclaredMethod("eat", String.class);
// System.out.println(m2);
m2.setAccessible(true);
m2.invoke(emoplyee, "面包");
// 获取单个方法
Method m3 = clazz.getMethod("sleep", int.class);
// System.out.println(m1);
m3.invoke(null, 6);
System.out.println("==================");
// 获取多个方法,包括父类
Method[] m4 = clazz.getMethods();
for (Method method : m4) {
System.out.println(method);
}
System.out.println("==================");
// 获取多个方法,本类所有方法
Method[] m5 = clazz.getDeclaredMethods();
for (Method method : m5) {
System.out.println(method);
}
}
}
2.7 Field属性类
- 在反射机制中,把类中的成员变量使用类Field表示
- 返回一个成员变量
getField |
|
getDeclaredField |
- 返回多个成员变量
|
getFields |
|
getDeclaredFields |
- 给属性赋值
1) 给基本类型赋值
setDouble(对象, 值)8种基本类型都有相应的方法:set基本类型(对象,值)
2) 给引用类型赋值
set(对象,值)将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
- 得到变量的名字和类型:
getName() 得到变量的名字
getType() 得到变量的类型,返回Class对象
注:基本类型和包装类型不是同一种类型
案例代码:
public class Emoplyee {
private String id;
private String name;
private int age;
public double salary;
public String gender;
public Emoplyee() {
}
private Emoplyee(String id) {
this.id = id;
}
Emoplyee(String id, String name) {
this.id = id;
this.name = name;
}
public Emoplyee(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Emoplyee [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + ", gender=" + gender
+ "]";
}
public void work(int hour) {
System.out.println(name + "工作" + hour + "个小时");
}
// 私有的方法
private int eat(String food) {
System.out.println(name + "吃" + food);
return 100;
}
// 静态方法
public static void sleep(int hour) {
System.out.println("静态方法.睡觉zZZ");
}
}
测试类:
public class Demo05 {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.itheima.demo03Calss获取构造方法.Emoplyee");
// 根据构造方法创建对象
Constructor<?> c = clazz.getConstructor(String.class, String.class, int.class);
Emoplyee emoplyee = (Emoplyee)c.newInstance("001", "马云", 45);
// 获取一个成员变量
Field f1 = clazz.getField("salary");
System.out.println(f1.getName() + ",," + f1.getType());
f1.setDouble(emoplyee, 8888);
Field f2 = clazz.getField("gender");
// System.out.println(f2);
f2.set(emoplyee, "男");
// 获取非公开一个成员变量
Field f3 = clazz.getDeclaredField("name");
f3.setAccessible(true);
f3.set(emoplyee, "马爸爸");
System.out.println(emoplyee);
System.out.println("============");
// 获取多个成员变量
Field[] f4 = clazz.getFields();
for (Field field : f4) {
System.out.println(field);
}
System.out.println("============");
// 获取多个成员变量
Field[] f5 = clazz.getDeclaredFields();
for (Field field : f5) {
System.out.println(field);
}
}
}
3 Properties类的使用
3.1 Properties类的概述:
1. Map的简单回顾:
有一个键和值,键和值都是Object。
Map<K,V> 集合
|-- Hashtable 类,线程安全,效率低。不可以有null键和null值
|-- Properties 有键和值的一个双列集合
2. Properties类的特点:
键和值必须是String类型,不支持泛型, Properties与IO流有关系,可以直接操作属性文件,扩展名为.properties的文件
3.2 常用方法:设置/得到属性值
1. 构造方法:
Properties() 无参的构造方法,创建一个空的属性对象。
2. 普通方法:
注:不推荐使用从父类继承下来的设置/得到属性值的方法,因为这些方法可以使用非字符串的键和值。如:put(K,V)、V get(K)
方法名 |
作用 |
Object setProperty(String key, String value) |
设置属性名和值,返回被覆盖的值。 |
String getProperty(String key) |
通过属性名得到属性值 |
String getProperty(String key, String defaultValue) |
通过属性名得到属性值,如果属性值不存在,则返回defaultValue,从而保证一定可以得到一个属性值。 |
Set<String> stringPropertyNames() |
得到属性中的所有属性名(键) |
案例代码:
public class Demo06 {
public static void main(String[] args) {
Properties pp = new Properties();
// pp.put("name", "张三");
pp.setProperty("name", "张飞");
pp.setProperty("age", "30");
pp.setProperty("gender", "男");
pp.setProperty("height", "170");
System.out.println(pp);
// 根据key获取value
System.out.println("姓名: " + pp.getProperty("name"));
System.out.println("年龄: " + pp.getProperty("age"));
// 根据key获取value,如果没有这个key返回默认值
String height = pp.getProperty("height", "0.1");
System.out.println(height);
System.out.println("================");
// 得到所有的属性名
Set<String> propertyNames = pp.stringPropertyNames();
for (String key : propertyNames) {
String value = pp.getProperty(key);
System.out.println(key + " == " + value);
}
}
}
3.3 将集合中内容存储到文件:
- 属性文件的格式要求:
1) 内容是:键=值,如:name=password
2) 每一行有一个属性名和一个属性值
3) 可以使用注释 #,以#开头的行,则是注释行。
4) 如果有空行,则被忽略。
5) 属性文件的扩展名是:properties
- 保存到IO流:
方法名 |
作用 |
void store(OutputStream out, String comments) |
保存属性到字节流中,并加上注释。还会加上保存的时间,汉字使用的是Unicode编码。 |
void store(Writer writer, String comments) |
保存属性到字符流中,并加上注释。还会加上保存的时间。字符流汉字直接写入。 |
- 需求:使用Properties集合,完成把集合内容存储到IO流所对应文件中的操作
// 将Properties的内容保存到文件中
private static void method01() throws IOException, FileNotFoundException {
Properties pp = new Properties();
pp.setProperty("name", "张飞");
pp.setProperty("age", "30");
pp.setProperty("gender", "男");
pp.setProperty("height", "170");
// 将Properties的内容保存到文件中
pp.store(new FileOutputStream("stu.properties"), "Hello Commonts");
}
3.4 读取文件中的数据,并保存到属性集合
- 从流中加载:
方法 |
作用 |
void load(InputStream inStream) |
从字节输入流中读取属性列表(属性名和属性值) |
void load(Reader reader) |
从字符输入流中读取属性列表(属性名和属性值) |
- 需求:从属性集文件prop.properties 中取出数据,保存到集合中
// 将文件中的内容加载到Properties中
private static void method02() throws IOException, FileNotFoundException {
Properties pp = new Properties();
// 将文件中的内容加载到Properties中
pp.load(new FileInputStream("stu.properties"));
Set<String> propertyNames = pp.stringPropertyNames();
for (String key : propertyNames) {
String value = pp.getProperty(key);
System.out.println(key + " === " + value);
}
}
4 反射和Properties的综合案例
4.1 需求
1) 有属性内容如下:
● 注意:文件放在项目根目录下
student.properties
id=1
name=Sandy
gender=u7537
score=100
2) 有一个Student类的属性:Student(String id, String name, String gender, String score),这里将所有的属性设置成了String类型。
3) 通过Properties类读取student.properties文件
4) 使用反射的方式把属性文件中读取的数据赋值给一个实例化好的Student对象,Student类中的属性名与student.properties要对应。
5) 重写Student的toString()方法,输出对象的属性值 。
4.2 步骤
1) 创建方法:public static Object createObject(String className, Properties props) 通过反射实例化对象,并且返回封装好数据的对象
1.1) 得到类对象
1.2) 通过反射实例化对象
1.3) 通过反射得到类所有声明的属性
1.4) 循环得到属性文件中的属性名和属性值
1.5) 使用暴力反射对每个属性赋值
2) 在main函数中调用方法
2.1) 创建Properties类
2.2) 创建文件输入字节流
2.3) 加载文件到属性文件中
2.4) 调用反射方法实例化对象并且输出属性
4.3 代码
人类:
public class Person {
private String name;
private String gender;
private int age;
public void eat() {
System.out.println(name + "在吃饭");
}
//...
}
狗类:
public class Dog {
private String name;
private int age;
private String color;
//...
}
测试类:
public class Demo08 {
public static void main(String[] args) throws Exception {
Person obj = createObject();
System.out.println(obj);
}
// 根据配置文件的内容创建对象
public static <T> T createObject() throws Exception {
Properties pp = new Properties();
// 加载配置文件
pp.load(new FileInputStream("person.properties"));
// 得到类名
String className = pp.getProperty("className");
// 得到类对象
Class<?> clzz = Class.forName(className);
// 实例化
T obj = (T) clzz.newInstance();
// 通过反射得到类所有的属性
Field[] fields = clzz.getDeclaredFields();
// 遍历获取每个属性
for (Field field : fields) {
// 获取到属性的名称
String name = field.getName();
Class<?> type = field.getType();
// 根据名称去Property中获取属性值
String value = pp.getProperty(name);
// 暴力反射
field.setAccessible(true);
// 根据不同的类型设置不同的属性
if (type == int.class) {
field.setInt(obj, Integer.parseInt(value));
} else {
field.set(obj, value);
}
}
return obj;
}
}