本帖最后由 13333114253 于 2018-12-2 16:55 编辑
[石家庄校区] JAVA_网络编程
[石家庄校区]JAVA_函数式接口
[石家庄校区] JAVA_Stream方法引用
JUnit单元测试JUnit使用步骤:
1. 定义一个测试类(也叫测试用例)
包名:xxx.xxx.xx.test
被测试的类名: Calculator
对应的测试类名: CalculatorTest
2. 定义测试方法:可以独立运行
被测试的方法名: add()
对应的测试方法名: testAdd()
建议测试方法的返回值是void, 参数列表是空参
3. 在方法上加 @Test 注解
4. 在 @Test 注解上按 Alt+Enter, 选择 "Add 'JUnit4' to Classpath" 导入JUnit依赖环境
5. 在方法名上右键, 选择 "Run '方法名()'"
判定结果:
红色:失败
绿色:成功.(测试通过)
断言: Assert
使用断言操作来判断结果是否符合预期:
Assert.assertEquals(期望的结果, 运算的结果);
如果 期望的结果 和 运算的结果 相等, 则认为测试通过, 否则测试失败
测试失败的原因提示:
java.lang.AssertionError:
Expected :1 (表示我期望得到的是1)
Actual :-1 (但是实际得到的是-1)
需求:例:
[Java] 纯文本查看 复制代码
public class Calculator {
// 计算两个int相加
public int add(int a, int b) {
return a + b;
}
// 计算两个int相减
public int sub(int a, int b) {
return a - b;
}
}
// 专门用于测试Calculator类的测试类
public class CalculatorTest {
// 不要写main方法, 要用JUnit提供的测试方式
// 每个方法对应一个测试方法
@Test // 红色, Alt + 回车, 选 Add 'JUnit4' to classpath
public void testAdd() { // 在测试方法中, 编写测试代码
// 测试Calculator的add方法
Calculator calculator = new Calculator();
int result = calculator.add(1, 2);
// 不要打印, 而是使用JUnit提供的Assert类的方法, 来断言结果
// Assert.assertEquals(我们期望的结果, 实际得到的结果);
Assert.assertEquals(3, result);
}
@Test // 每个测试方法上都要加上 @Test 注解, 这样才能将该方法独立运行
public void testSub() {
// 测试Calculator的sub方法
Calculator calculator = new Calculator();
int result = calculator.sub(2, 1);
Assert.assertEquals(1, result);
}
}
JUnit: @Before, @After@Before: 修饰的方法会"在每个测试方法执行 之前"被执行
@After: 修饰的方法会"在每个测试方法执行 之后"被执行
在刚才的CalculatorTest测试类中, 再定义两个方法:
@Before
public void start() {
System.out.println("开始测试方法");
}
@After
public void end() {
System.out.println("测试方法结束");
}
02-反射
获取一个类的字节码对象的3种方式:
1. Class.forName("全类名")
将字节码文件加载进内存,返回Class对象
适用场景: 多用于配置文件,将类名定义在配置文件中. 读取文件, 加载类
2. 类名.class .
通过类名的属性class获取
适用场景: 多用于参数的传递 getConstructor(String.class, int.class)
3. 对象.getClass()
getClass()方法在Object类中定义
适用场景: 多用于对象的获取字节码的方式 p.getClass()
同一个类的字节码对象, 只有"唯一的一个"
反射:Class的方法概述
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容
// 获取功能
1. 获取成员变量们
Field[] getFields(): 获取所有 public 的成员变量
Field getField(String name): 获取指定名称的 public 的成员变量
Field[] getDeclaredFields(): 获取所有的成员变量, 不考虑权限修饰符
Field getDeclaredField(String name): 获取指定名称的成员变量, 不考虑权限修饰符
java.lang.reflect.Field: 表示一个成员变量
// 成员方法 String name; Person p = new Person(); p2.name = "abc";
void set(Object obj, Object value): 设置指定对象的成员变量的值 field.set(p1, "abc")
Object get(Object obj): 获取指定对象的成员变量的值 field.get(p1)
void setAccessible(boolean flag): 传true时忽略访问权限修饰符的安全检查. 暴力反射 field.set
例:
[Java] 纯文本查看 复制代码 public class Person {
public String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) throws Exception {
// 通过Person类名先获取Person类的Class对象
Class cls = Person.class;
// 创建Person对象p, 用来修改它的属性值
Person p = new Person();
// 通过反射获取p对象的name属性的Field对象, 将姓名修改为 "张三"
Field nameField = cls.getField("name");
nameField.set(p, "张三");
// 通过反射获取p对象的age属性的Field对象, 将年龄修改为 18
// age是private的, 获取的时候要调用带有 Declared 的方法
Field ageField = cls.getDeclaredField("age");
// age是private的, 所以操作前, 需要先暴力反射
ageField.setAccessible(true);
ageField.set(p, 18);
// 打印p对象, 查看是否修改
System.out.println(p); // Person{name='张三', age=18}
}
}
反射: 获取构造方法Constructorjava.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容
// 获取构造方法们
Constructor<?>[] getConstructors(): 获取所有 public 的构造方法
Constructor<T> getConstructor(Class<?>... parameterTypes): 获取指定的 public 构造方法
Constructor<?>[] getDeclaredConstructors(): 获取所有的构造方法, 不考虑权限修饰符
Constructor<T> getDeclaredConstructor(Class... parameterTypes): 获取指定的构造方法, 不考虑权限修饰符
T newInstance(): 使用当前类的空参构造创建一个对象
java.lang.reflect.Constructor<T>: 表示一个构造方法
// 成员方法
T newInstance(Object... initargs): 使用当前构造方法传入参数, 创建对象
void setAccessible(boolean flag): 注意: 构造方法不能利用此方法忽略权限, 会抛异常
[Java] 纯文本查看 复制代码 /*
1. 通过Person类的Class对象
调用Class对象的无参 newInstance()方法
创建一个Person对象p1, 打印p1对象查看属性值
*/
Object p1 = cls.newInstance(); // 这是利用Class类中的newInstance方法创建的对象, 底层利用的是Person类的无参构造
System.out.println(p1);
/*
2. 通过Person类的Class对象, 获取Person类的有参构造Constructor对象
调用该构造方法Constructor对象的 newInstance(Object... o), 传入参数"张三", 18,
创建一个Person对象p2, 打印p2对象查看属性值
*/
Constructor con = cls.getConstructor(String.class, int.class);
Object p2 = con.newInstance("张三", 18);
System.out.println(p2);
反射: 获取成员方法Methodjava.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容
// 获取成员方法们:
Method[] getMethods(): 获取所有 public 的成员方法
Method getMethod(String name, Class<?>... parameterTypes) : 获取指定的 public 成员方法
Method[] getDeclaredMethods(): 获取所有的成员方法, 不考虑权限修饰符
Method getDeclaredMethod(String name, Class<?>... parameterTypes): 获取指定的成员方法, 不考虑权限修饰符
java.lang.reflect.Method类: 表示一个成员方法
// 成员方法 Person p = new Person(); p.eat("adf", 123);
Object invoke(Object obj, Object... args): 使用指定对象和指定参数值调用此方法
String getName(): 获取方法名
void setAccessible(boolean flag): 传true时忽略访问权限修饰符的安全检查. 暴力反射
例:
[Java] 纯文本查看 复制代码 //继续在之前定义的Person类中, 增加2个成员方法:
// 无参方法
public void eat() {
System.out.println("无参方法: 吃");
}
// 有参方法
public void eat(String food) {
System.out.println("有参方法: 吃" + food);
}
// 通过Class对象获取Person类的无参方法 eat() 的Method对象, 传入p对象来调用
Method noParamEatMethod = cls.getDeclaredMethod("eat"); // 无参, 就只需要传方法名
Object result1 = noParamEatMethod.invoke(p1); // 调用无参方法, 只需要传对象, 参数值不用传
System.out.println("调用无参方法的返回值:" + result1); // null
// 通过Class对象获取Person类的有参方法 eat(String food) 的Method对象, 传入p对象和"KFC"来调用
Method oneParamEatMethod = cls.getDeclaredMethod("eat", String.class); // 有参, 传递方法名和参数类型的字节码对象
oneParamEatMethod.invoke(p1, "KFC"); // 调用有参方法, 需要传递对象和参数值
反射: 利用反射实现可以运行任意类的任意方法的框架案例[Java] 纯文本查看 复制代码 # 配置文件 src目录下的pro.properties
className=com.itheima.test03.Student
methodName=sleep
public class Person {
public void eat() {
System.out.println("人吃饭");
}
}
public class Student {
public void sleep() {
System.out.println("学生睡觉");
}
}
/*
模拟实现框架
目的: 加载配置文件中指定的类, 并调用指定的方法
*/
public class Test {
public static void main(String[] args) throws Exception {
/*
加载配置文件:
通过类加载器加载配置文件变成字节输入流
然后让Properties加载流中的数据
*/
ClassLoader classLoader = Test.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("pro.properties");
Properties properties = new Properties();
properties.load(is);
/*
获取配置信息:
通过Properties的键, 获取值
*/
// 通过配置文件中的类名key获取真正的类名的value
String className = properties.getProperty("className");
// 通过配置文件中的方法名key获取真正的方法名的value
String methodName = properties.getProperty("methodName");
/*
通过反射技术创建指定类的对象, 并调用方法:
*/
// 先通过读取到的全类名, 获取字节码对象.
Class cls = Class.forName(className);
// 通过字节码对象创建该类的对象
Object obj = cls.newInstance();
// 通过字节码对象获取指定的方法对象
Method method = cls.getDeclaredMethod(methodName);
// 调用方法
method.invoke(obj);
}
}
03-注解
注解: Annotation
JDK 1.5 引入. 也叫元数据, 是一种代码级别的说明
它可以声明在包, 类, 字段(成员变量), 方法, 局部变量, 方法参数等的前面, 用来对这些元素进行说明
注解: 说明程序的。给计算机看的
注释: 用文字描述程序的。给程序员看的
使用注解: @注解名称
作用分类:
1. 编写文档: 通过代码里标识的注解生成文档
(生成API文档 @author @version @since @param @return)
2. 代码分析: 通过代码里标识的注解对代码进行分析 (使用反射)
(JUnit提供的 @Test @Before @After)
3. 编译检查: 通过代码里标识的注解让编译器能够实现基本的编译检查
(@Override @FunctionalInterface)
JDK中预定义的一些注解:
@Override: 检测被该注解标注的方法是否是"重写"父类(接口)的
@Deprecated: 该注解标注的内容,表示已过时
@SuppressWarnings: 压制警告. 一般传递参数all @SuppressWarnings("all")
自定义注解: 格式和本质知识点:public interface 接口名 {}
自定义注解格式:关键字 @interface
元注解
public @interface 注解名称 {
属性; (接口中的抽象方法)
属性;
属性;
...
}
@注解名称
注解的本质:
注解本质上就是一个接口,该接口默认继承Annotation接口
public interface MyAnno extends java.lang.annotation.Annotation {}
属性:
接口中的"抽象方法"
属性的要求:
1. 属性的"返回值类型"可以是以下类型:
基本数据类型(8种)
String
枚举
注解
以上类型的数组
2. 定义了属性,在使用注解时, 需要"给属性赋值" (其实是抽象方法的返回值)
1. 属性使用 default 关键字指定默认值, 则可以不赋值
2. 如果只有一个名为"value"的属性需要赋值, 则 value 可以省略, 直接写值即可
3. 给数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
[Java] 纯文本查看 复制代码 public @interface MyAnno {
String name() default "张三"; // 有默认值
int value(); // 属性名为value
String[] strs(); // 字符串数组
}
// 使用注解
@MyAnno(name = "李四", value = 10, strs = {"a", "b"})
public class Worker {
}
自定义注解: 元注解元注解:
用于描述注解的注解
常用元注解:
@Target: 描述注解能够作用的位置
ElementType枚举的常用取值:
TYPE:可以作用于类上
METHOD:可以作用于方法上
FIELD:可以作用于成员变量上
示例: @Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention: 描述注解被保留的阶段
RetentionPolicy枚举的取值:
SOURCE: 保留到源代码阶段
CLASS: 保留到类对象阶段
RUNTIME: 保留到运行时阶段
示例: @Retention(RetentionPolicy.RUNTIME):保留注解到class字节码文件中并被JVM读取到
@Documented: 加上后, 当前注解会被抽取到api文档中
@Inherited: 加上后, 当前注解会被子类继承
自定义注释解析属性值
[Java] 纯文本查看 复制代码 public class Person {
public void eat() {
System.out.println("吃东西");
}
}
@Target(ElementType.TYPE) // 只能用在类上
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时期, 一般固定写成这样
public @interface ProAnno {
String className(); // 类名属性
String methodName(); // 方法名属性
}
// 使用注解方式配置要调用的类和方法
@ProAnno(className = "com.itheima.test04.Person", methodName = "eat")
public class Test {
public static void main(String[] args) throws Exception {
// 获取当前类的字节码对象
Class<Test> cls = Test.class; // 这里写上泛型, 下面获取注解对象就可以直接获取到对应的类型
// 通过字节码对象获取使用在类上的注解
ProAnno pro = cls.getAnnotation(ProAnno.class);
// 通过注解对象, 调用抽象方法, 获取属性值
String className = pro.className();
String methodName = pro.methodName();
// 剩下的步骤都和反射一样, 获取方法, 调用方法
Class c = Class.forName(className);
// 创建该类对象
Object obj = c.newInstance();
// 获取方法对象
Method method = c.getDeclaredMethod(methodName);
// 调用方法
method.invoke(obj);
}
}
自定义测试框架[Java] 纯文本查看 复制代码 自定义的测试框架如要实现方式如下:
1. 获取Calculator的字节码对象
2. 通过Calculator的字节码对象获取所有的Method对象
3. 遍历出每个Method对象, 判断是否添加了 @Check 注解, 是则调用该方法
4. 在捕获的异常中, 使用IO流将发生异常的方法名, 异常类名和原因, 写入文件
add 方法出异常了
异常的名称: NullPointerException
异常的原因:
-------------------
div 方法出异常了
异常的名称: XxxException
异常的原因: / by zero
-------------------
总共发生了 2 个异常
需求:
已知 Check注解 和 小明写的计算器类:
/* 用于测试方法的自定义注解 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
}
/* 小明定义的计算器类 */
public class Calculator {
//加法
@Check
public void add(){
String str = null;
str.toString();
System.out.println("1 + 0 =" + (1 + 0));
}
//减法
@Check
public void sub(){
System.out.println("1 - 0 =" + (1 - 0));
}
//乘法
@Check
public void mul(){
System.out.println("1 * 0 =" + (1 * 0));
}
//除法
@Check
public void div(){
System.out.println("1 / 0 =" + (1 / 0));
}
public void show(){
System.out.println("永无bug...");
}
}
请定义测试类Test, 在main方法中实现测试框架:
1. 创建Calculator类的对象, 通过Calculator对象获取该类的Class对象
2. 通过Class对象的 getMethods() 方法, 获取包含所有public权限的Method对象的数组
3. 定义异常计数器变量 int number = 0;
4. 创建字节缓冲输出流对象指向bug.txt文件:
BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
5. 遍历Method对象数组, 取出每个Method对象
6. if判断当前Method方法上是否使用了@Check注解: if(method.isAnnotationPresent(Check.class)){}
7. 如果有, 则调用该方法: method.invoke(计算器对象); 并使用try...catch捕获Exception
8. 在catch中, 一旦发生异常, 则异常计数器number++, 并利用BufferedWriter将异常信息写入文件:
bw.write(method.getName()+ " 方法出异常了");
bw.newLine();
bw.write("异常的名称:" + e.getCause().getClass().getSimpleName());
bw.newLine();
bw.write("异常的原因:"+e.getCause().getMessage());
bw.newLine();
bw.write("--------------------------");
bw.newLine();
|
|