一 Junit单元测试:
1. 测试分类:
- 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
- 白盒测试:需要写代码的。关注程序具体的执行流程。
⑴ Junit使用:白盒测试
① 步骤:
-
定义一个测试类(测试用例)
建议:
- 测试类名:被测试的类名+Test 如:CalculatorTest
- 包名:xxx.xxx.xx.test 如:com.kejizheantan.test -
定义测试方法:可以独立运行
建议:
- 方法名:test测试的方法名 如:testAdd()
- 返回值:void
- 参数列表:空参 -
给方法加@Test
-
导入junit依赖环境
代码如下:
Calculator计算器类:
/**
* 计算器类
*/
public class Calculator {
/**
* 加法
*/
public int add (int a , int b){
//int i = 3/0;
return a + b;
}
/**
* 减法
*/
public int sub (int a , int b){
return a - b;
}
}
测试类:
public class CalculatorTest {
@Test
public void addTest(){
Calculator calc = new Calculator();
int num = calc.add(2, 5);
System.out.println(num);
}
}
执行结果:
注意:
1.要加注解@Test
并导入import org.junit.Test;
依赖
2.执行的方式如下图
② 判定结果:
-
红色:失败
-
绿色:成功
一般我们会使用断言操作来处理结果
Assert.assertEquals
(期望的结果,运算的结果);
代码如下:
结果如下:
③ 补充:
- @Before:
修饰的方法会在测试方法之前被自动执行 - @After:
修饰的方法会在测试方法执行之后自动被执行
代码如下:
JunitTest类:
public class JunitTest {
public void show(){
System.out.println("junit测试.....");
}
}
测试代码TestDemo类:
public class TestDemo {
/**
* 初始化方法:
* 用于资源申请,所有测试方法在执行之前都会先执行该方法
*/
@Before
public void init(){
System.out.println("用于初始化资源的方法init....");
}
/**
*测试方法
*/
@Test
public void showTest(){
JunitTest jt = new JunitTest();
jt.show();
}
/**
* 释放资源方法:
* 在所有测试方法执行完后,都会自动执行该方法
*/
@After
public void close(){
System.out.println("用于关闭资源的方close...");
}
}
结果如下:
二 反射:框架设计的灵魂
⑴ 框架:
半成品软件。可以在框架的基础上进行软件开发,简化编码
⑵ 反射
将类的各个组成部分封装为其他对象,这就是反射机制
- java代码的三个阶段:
好处:
- 可以在程序运行过程中,操作这些对象。
- 可以解耦,提高程序的可扩展性。
① 获取Class对象的方式
Class.forName("全类名")
:将字节码文件加载进内存,返回Class对象(第一阶段的获取方式)
多用于配置文件,将类名定义在配置文件中。读取文件,加载类类名.class
:通过类名的属性class获取*(第二阶段获取方式)
多用于参数的传递对象.getClass()
:getClass()方法在Object类中定义着。(第三阶段的获取方式)
多用于对象的获取字节码的方式
代码如下:
-
Person类:
public class Person { private String name; private int age; public String a; protected String b; String c; private String d; 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 + ", a='" + a + '\'' + ", b='" + b + '\'' + ", c='" + c + '\'' + ", d='" + d + '\'' + '}'; } public void eat(){ System.out.println("eat..."); } public void eat(String food){ System.out.println("eat..."+food); } }
-
ReflectDemo类
public class ReflectDemo { /** 获取Class对象的方式: 1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象 2. 类名.class:通过类名的属性class获取 3. 对象.getClass():getClass()方法在Object类中定义着。 */ public static void main(String[] args) throws Exception { //1.Class.forName("全类名") Class person1 = Class.forName("com.kejizhentan.demo8.Person"); System.out.println(person1); //2.类名.class Class person2 = Person.class; System.out.println(person2); //3.对象.getClass() Class person3 = new Person().getClass(); System.out.println(person3); //== 比较三个对象 System.out.println(person1==person2);//true System.out.println(person2 == person3);//true } }
结论:
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
② Class对象功能:
获取功能:
-
获取成员变量们
Field[] getFields()
:获取所有public修饰的成员变量
Field getField(String name)
: 获取指定名称的 public修饰的成员变量Field[] getDeclaredFields()
获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name)
获指定名称的成员变量,不考虑修饰代码如下:
Person类:public class Person { private String name; private int age; //定义四种不同修饰符下的成员变量 public String a; protected String b; String c; private String d; 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 + ", a='" + a + '\'' + ", b='" + b + '\'' + ", c='" + c + '\'' + ", d='" + d + '\'' + '}'; } public void eat(){ System.out.println("eat..."); } public void eat(String food){ System.out.println("eat..."+food); } }
ReflectDemo类:
/** Class对象功能: * 获取功能: 1. 获取成员变量们 * Field[] getFields() * Field getField(String name) * Field[] getDeclaredFields() * Field getDeclaredField(String name) */ public class ReflectDemo { public static void main(String[] args) throws Exception { //0.获取Person的Class对象 Class p = Person.class; //通过Field[] getFields()获取所有被public修饰的成员属性 Field[] fields = p.getFields(); for (Field field : fields) { System.out.println("通过Field[] getFields()获取所有被public修饰的成员属性:"+field); } //通过Field getField(String name)获取指定名称的被public修饰的成员属性 Field a = p.getField("a"); System.out.println("通过Field getField(String name)获取指定名称的被public修饰的成员属性:"+a); //通过Field[] getDeclaredFields()获取所有的成员属性(包括public、省略、protected、private修饰的成员属性) Field[] fs = p.getDeclaredFields(); for (Field f : fs) { System.out.println("通过Field[] getDeclaredFields()获取所有的成员属性(包括public、省略、protected、private修饰的成员属性):"+f); } //通过Field getDeclaredField(String name)获取指定名称的成员属性(包括public、省略、protected、private修饰的成员属性) Field d = p.getDeclaredField("d"); System.out.println("通过Field getDeclaredField(String name)获取指定名称的成员属性(包括public、省略、protected、private修饰的成员属性):"+d); } }
结果如下:
-
获取构造方法们
Constructor<?>[] getConstructors()
获取所有public修饰的构造方法(包括有参和无参)
Constructor<T> getConstructor(类<?>... parameterTypes)
获取指定参数类型并用public修饰的构造方法
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
获取指定参数类型的构造方法,不考虑修饰符
Constructor<?>[] getDeclaredConstructors()
获取所有构造方法,不考虑修饰符(包括有参和无参)代码如下:
Person类:
public class Person { private String name; private int age; //定义四种不同修饰符下的成员变量 public String a; protected String b; String c; private String d; //定义四种不同修饰符下的构造方法 public Person(String a, String b) { } protected Person(String a, String b,int age) { } Person(String a,int age,String b) { } private Person(int age,String b) { } 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 + ", a='" + a + '\'' + ", b='" + b + '\'' + ", c='" + c + '\'' + ", d='" + d + '\'' + '}'; } public void eat(){ System.out.println("eat..."); } public void eat(String food){ System.out.println("eat..."+food); } }
ReflectDemo类:
public class ReflectDemo { public static void main(String[] args) throws Exception { Class p = Person.class; //通过Constructor<?>[] getConstructors()获取所有的构造方法(包括有参和无参) Constructor[] constructors = p.getConstructors(); for (Constructor constructor : constructors) { System.out.println("通过Constructor<?>[] getConstructors()获取所有的构造方法(包括有参和无参):"+constructor); } //通过Constructor<T> getConstructor(类<?>... parameterTypes)获取指定参数类型并由public修饰的的构造方法 Constructor constructor = p.getConstructor(String.class,int.class); System.out.println("通过Constructor<T> getConstructor(类<?>... parameterTypes)获取指定参数类型并由public修饰的的构造方法:"+constructor); //Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) 获取指定参数类型的构造方法,不考虑修饰符 Constructor con = p.getDeclaredConstructor(int.class, String.class); System.out.println("Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) 获取指定参数类型的构造方法,不考虑修饰符"+con); //Constructor<?>[] getDeclaredConstructors() 获取所有构造方法,不考虑修饰符(包括有参和无参) Constructor[] cons = p.getDeclaredConstructors(); for (Constructor con1 : cons) { System.out.println("Constructor<?>[] getDeclaredConstructors() 获取所有构造方法,不考虑修饰符(包括有参和无参)"+con1); } } }
结果如下:
-
获取成员方法们:
Method[] getMethods()
获取所有public修饰的方法(包含了Object类和父类中的方法)
Method getMethod(String name, 类<?>... parameterTypes)
获取指定方法名和参数,并被public修饰的方法
Method[] getDeclaredMethods()
获取所有的方法,不考虑修饰符(不包含Object类和父类中的方法)
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
获取指定方法名和参数,不考虑修饰符代码如下:
Person类:
public class Person { private String name; private int age; //定义四种不同修饰符下的成员变量 public String a; protected String b; String c; private String d; //定义四种不同修饰符下的构造方法 public Person(String a, String b) { } protected Person(String a, String b,int age) { } Person(String a,int age,String b) { } private Person(int age,String b) { } 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 + ", a='" + a + '\'' + ", b='" + b + '\'' + ", c='" + c + '\'' + ", d='" + d + '\'' + '}'; } public void eat(){ System.out.println("eat..."); } public void eat(String food){ System.out.println("eat..."+food); } private void eat(int num){ System.out.println("eat了..."+num+"个"); } }
ReflectDemo类:
/** Method[] getMethods() 获取所有public修饰的方法(包含了Object类和父类中的方法) Method getMethod(String name, 类<?>... parameterTypes) 获取指定方法名和参数,并被public修饰的方法 Method[] getDeclaredMethods() 获取所有的方法,不考虑修饰符(包含了Object类和父类中的方法) Method getDeclaredMethod(String name, 类<?>... parameterTypes) 获取指定方法名和参数,不考虑修饰符 */ public class ReflectDemo { public static void main(String[] args) throws Exception { Class p = Person.class; // Method[] getMethods() 获取所有public修饰的方法(包含了Object类和父类中的方法) Method[] methods = p.getMethods(); for (Method method : methods) { System.out.println(" Method[] getMethods() 获取所有public修饰的方法(包含了Object类和父类中的方法)" + method); } //Method getMethod(String name, 类<?>... parameterTypes) 获取指定方法名和参数,并被public修饰的方法 Method eat = p.getMethod("eat",String.class); System.out.println("Method getMethod(String name, 类<?>... parameterTypes) 获取指定方法名和参数,并被public修饰的方法:"+eat); //Method[] getDeclaredMethods() 获取所有的方法,不考虑修饰符(不包含Object类和父类中的方法) Method[] ms = p.getDeclaredMethods(); for (Method method : ms) { System.out.println("Method[] getDeclaredMethods() 获取所有的方法,不考虑修饰符(不包含Object类和父类中的方法):"+method); } // Method getDeclaredMethod(String name, 类<?>... parameterTypes) 获取指定方法名和参数,不考虑修饰符 Method eatMethod = p.getDeclaredMethod("eat", int.class); System.out.println(" Method getDeclaredMethod(String name, 类<?>... parameterTypes) 获取指定方法名和参数,不考虑修饰符:" +eatMethod); } }
结果如下:
-
获取全类名
String getName()
代码如下:
Student类:public class Student { }
ReflectDemo类
public class ReflectDemo { public static void main(String[] args) throws Exception { Class studentClass = Student.class; System.out.println("类的全名称是:"+studentClass.getName()); } }
结果如下:
③ Field:成员变量
操作:
- 设置值
void set(Object obj, Object value)
- 获取值
get(Object obj)
- 忽略访问权限修饰符的安全检查
setAccessible(true)
:暴力反射
代码如下:
Student类:
public class Student {
private String name;
private int age;
public String sex;
public Student() {
}
public Student(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 boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
ReflectDemo类:
public class ReflectDemo {
public static void main(String[] args) throws Exception {
Class studentClass = Student.class;
//一般成员变量值的获取和设置
Field sex = studentClass.getField("sex");
//获取成员变量sex的值
Student student = new Student();
Object s = sex.get(student);
System.out.println("Student中sex变量的值为:"+s);
//设置成员变量sex的值
sex.set(student,"男");
System.out.println("赋值完后Student对象为:"+student);
//private修饰的成员变量值的获取和设置
Class stu = Student.class;
Field field = stu.getDeclaredField("name");
/*获取成员变量name
Student st = new Student();
Object n = name.get(st);
System.out.println("private修饰的成员变量为:"+n);//抛:"Exception in thread "main" java.lang.IllegalAccessException: class com.kejizhentan.demo8.ReflectDemo cannot access a member of class com.kejizhentan.demo8.Student with modifiers "private"
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)"异常*/
//获取成员变量name
Student st = new Student();
//忽略访问权限修饰符的安全检查
field.setAccessible(true);//暴力反射
Object n = field.get(st);
System.out.println("private修饰的成员变量为:"+n);
}
}
④ Constructor:构造方法
T newInstance(Object... initargs)
:创建对象
如果使用空参数构造方法创建对象,操作可以简化:Class
对象的newInstance
方法
代码如下:
student类:
public class Student {
private String name;
private int age;
public String sex;
public Student() {
}
public Student(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 boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
ReflectDemo类:
public class ReflectDemo {
public static void main(String[] args) throws Exception {
Class studentClass = Student.class;
//获取有参构造方法
Constructor constructor = studentClass.getConstructor(String.class, int.class);
//创建对象
Object student = constructor.newInstance("张三",18);
System.out.println("通过有参构建方法创建的对象为:"+student);
//获取空参构造方法
Constructor con = studentClass.getConstructor();
//创建对象
Object o = con.newInstance();
System.out.println("通过空参构建方法创建的对象为:" + o);
//使用空参构造方法创建对象的简写版
Object o1 = studentClass.newInstance();
System.out.println("使用空参构造方法创建对象的简写版:"+o1);
}
}
⑤ Method:方法对象
Object invoke(Object obj, Object... args)
:执行方法String getName
:获取方法名
代码如下:
Student类
public class Student {
public void eat(){
System.out.println("吃饭的方法!!!");
}
}
ReflectDemo类
public class ReflectDemo {
public static void main(String[] args) throws Exception {
Class studentClass = Student.class;
//获取指定名称的方法
Method eat = studentClass.getMethod("eat");
//获取方法名
String methodName = eat.getName();
System.out.println("方法名为:"+methodName);
Student student = new Student();
//执行方法
eat.invoke(student);
}
}
结果如下:
注意:
上述的成员变量、成员方法、构造器都能通过setAccessible(true)
暴力反射获取private修饰的类成员
⑶案例:
需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
- 实现:
1. 配置文件
2. 反射- 步骤:
1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
2. 在程序中加载读取配置文件
3. 使用反射技术来加载类文件进内存
4. 创建对象
5. 执行方法
代码如下:
项目结构:
配置文件:
className=com.kejizhentan.demo9.Student
methodName=eat
Student类:
public class Student {
public void eat(){
System.out.println("吃饭的方法!!!");
}
}
ReflectTest类:
/**
* 框架类
*/
public class ReflectTest {
public static void main(String[] args) throws Exception {
//可以创建任意类的对象,可以执行任意方法
/*
前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
*/
//1.加载配置文件
//1.1创建Properties对象
Properties properties = new Properties();
//1.2加载配置文件,转换为一个集合
//1.2.1获取class目录下的配置文件
ClassLoader classLoader = ReflectTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("pro.properties");
properties.load(is);
//2.获取配置文件中定义的数据
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
//3.加载该类进内存
Class cls = Class.forName(className);
//4.创建对象
Object obj = cls.newInstance();
//5.获取方法对象
Method method = cls.getMethod(methodName);
//6.执行方法
method.invoke(obj);
}
}
结果如下:
三 注解
⑴ 注解与注释的区别:
- 注解:说明程序的。给计算机看的
- 注释:用文字描述程序的。给程序员看的
⑵ 定义:
注解(Annotation
),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
概念描述:
- JDK1.5之后的新特性
- 说明程序的
- 使用注解:@注解名称
⑶ 作用分类
①编写文档:
通过代码里标识的注解生成文档【生成文档doc文档】
-
编写代码加上相应的注解说明(如:
@Description
、@param
、@return
等)
文件名称如下:
代码如下:/** * @Description: 通过代码里标识的注解生成文档 * @Author: kejizhentan * @CreateDate: 2021/08/28 * @UpdateUser: kejizhentan * @Version: 1.0 * @since java1.5之后 */ public class AnnotationDemo { /** * @Description:计算两数的和 * @param a 整数 * @param b 整数 * @return 返回两数的和 */ public int add(int a,int b){ return a+b; } }
-
将编写好的.AnnotationDemo.java文件放到想生成doc文档的文件夹中,并编辑AnnotationDemo.java文件,将上方导包的语句删掉。
-
执行javadoc AnnotationDemo.java命令,生成api文文档
-
生成的html如下
②代码分析:
通过代码里标识的注解对代码进行分析【使用反射】
③编译检查:
通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
⑷ JDK中预定义的一些注解
① @Override :
检测被该注解标注的方法是否是继承自父类(接口)的
② @Deprecated:
该注解标注的内容,表示已过时
③ @SuppressWarnings:
压制警告
一般传递参数all @SuppressWarnings("all")
⑸ 自定义注解
① 格式:
public @interface 注解名称{
属性列表;
}本质:注解本质上就是一个接口,该接口默认继承Annotation接口
public interface MyAnno extends java.lang.annotation.Annotation {}
② 属性(指注解中的抽象方法)
要求:
-
属性的返回值类型有下列取值
* 基本数据类型
* String
* 枚举
* 注解
* 以上类型的数组
如:
-
定义了属性,在使用时需要给属性赋值
* 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
如:
* 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
* 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
如:
⑹ 元注解:用于描述注解的注解
-
@Target:描述注解能够作用的位置
ElementType
取值:
*TYPE
:可以作用于类上
*METHOD
:可以作用于方法上
*FIELD
:可以作用于成员变量上
-
@Retention:描述注解被保留的阶段
@Retention(RetentionPolicy.RUNTIME)
:当前被描述的注解,会保留到class字节码文件中,并被JVM读取到 -
@Documented:描述注解是否被抽取到api文档中
-
@Inherited:描述注解是否被子类继承
⑺ 综合案例
① 使用注解的方式解决反射案例中的问题
需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
项目结构
Student类:
public class Student {
public void eat(){
System.out.println("吃饭的方法!!!");
}
}
Pro自定义注解
/**
* 描述需要执行的类名,和方法名
*/
@Target({ElementType.TYPE})//表示该注解只能标注在类上
@Retention(RetentionPolicy.RUNTIME)//当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
public @interface Pro {
String className();
String methodName();
}
ReflectTest类
/**
* 框架类
*/
@Pro(className = "com.kejizhentan.demo10.Student",methodName = "eat")
public class ReflectTest {
public static void main(String[] args) throws Exception {
/*
前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
*/
//1.解析注解
//1.1获取该类的字节码文件对象
Class<ReflectTest> reflectTestClass = ReflectTest.class;
//2.获取上边的注解对象
//其实就是在内存中生成了一个该注解接口的子类实现对象
/* public class ProImpl implements Pro{
public String className(){
return "com.kejizhentan.demo10.Student";
}
public String methodName(){
return "eat";
}
}*/
Pro an = reflectTestClass.getAnnotation(Pro.class);
//3.调用注解对象中定义的抽象方法,获取返回值
String className = an.className();
String methodName = an.methodName();
//System.out.println(className);
//System.out.println(methodName);
//3.加载该类进内存
Class cls = Class.forName(className);
//4.创建对象
Object obj = cls.newInstance();
//5.获取方法对象
Method method = cls.getMethod(methodName);
//6.执行方法
method.invoke(obj);
}
}
结果如下:
② 简单的测试框架
项目结构
自定义注解Check
@Retention(RetentionPolicy.RUNTIME)//当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
@Target(ElementType.METHOD)//当前被描述的注解只能标注在方法上
public @interface Check {
}
计算器类Calculator
/**
* 小明定义的计算器类
*/
public class Calculator {
//加法
@Check
public void add(){
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...");
}
}
TestCheck类
/**
* 简单的测试框架
* 当主方法执行后,会自动自行被检测的所有方法(加了Check注解的方法),判断方法是否有异常,记录到文件中
*/
public class TestCheck {
public static void main(String[] args) throws IOException {
//1.创建计算器对象
Calculator c = new Calculator();
//2.获取字节码文件对象
Class cls = c.getClass();
//3.获取所有方法
Method[] methods = cls.getMethods();
int number = 0;//出现异常的次数
BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
for (Method method : methods) {
//4.判断方法上是否有Check注解
if(method.isAnnotationPresent(Check.class)){
//5.有,执行
try {
method.invoke(c);
} catch (Exception e) {
//6.捕获异常
//记录到文件中
number ++;
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();
}
}
}
bw.write("本次测试一共出现 "+number+" 次异常");
bw.flush();
bw.close();
}
}
执行结果
1.生成测试报告文件
2.控制台打印结果如下:
- 小结:
1. 以后大多数时候,我们会使用注解,而不是自定义注解
2. 注解给谁用?
* 编译器
*给解析程序用
3. 注解不是程序的一部分,可以理解为注解就是一个标签
四、抽象类
⑴ 概述
父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有 意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法 的类就是抽象类。
⑵ 定义抽象
- 抽象方法 : 没有方法体的方法。
- 抽象类:包含抽象方法的类。
⑶ abstract使用格式
①抽象方法
使用 abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。
定义格式:
修饰符 abstract 返回值类型 方法名 (参数列表);
代码举例:
public abstract void run();
② 抽象类
如果一个类包含抽象方法,那么该类必须是抽象类。
定义格式:
abstract class 类名字 {
}
代码举例:
public abstract class Abstractdemo {
public abstract void run();
}
③ 抽象的使用
继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父 类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。
Animal抽象类:
public abstract class Animal {
public abstract void run();
}
Cat子类
public class Cat extends Animal {
@Override
public void run() {
System.out.println("小猫在墙头走~~~");
}
}
测试类:
public class CatTest {
public static void main(String[] args) {
// 创建子类对象
Cat c = new Cat();
// 调用run方法
c.run();
}
}
结果如下:
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法。
⑷ 注意事项
关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
-
抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
-
抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
-
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设 计。
-
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象 类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有 意义。
五、接口
⑴ 概述
接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么 接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法 (JDK 9)。
接口的定义,它与定义类方式相似,但是使用interface
关键字。它也会被编译成.class
文件,但一定要明确它并 不是类,而是另外一种引用数据类型。
引用数据类型:数组,类,接口。
接口的使用,它不能创建对象,但是可以被实现(implements
,类似于被继承)。一个实现接口的类(可以看做 是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象 类。
⑵定义格式
public interface 接口名称 {
// 抽象方法
// 默认方法
// 静态方法
// 私有方法
}
① 含有抽象方法
抽象方法:使用abstract
关键字修饰,可以省略,没有方法体。该方法供子类实现使用。
代码如下:
public interface InterFaceName {
public abstract void method();
}
② 含有默认方法和静态方法
默认方法:使用default
修饰,不可省略,供子类调用或者子类重写。
静态方法:使用 static
修饰,供接口直接调用。
代码如下:
public interface InterFaceName {
public default void method() {
// 执行语句
}
public static void method2() {
// 执行语句
}
}
③ 含有私有方法和私有静态方法
私有方法:使用 private
修饰,供接口中的默认方法或者静态方法调用。
public interface InterFaceName {
private void method() {
// 执行语句
}
private static void method2(){
// 执行语句
}
}
⑶ 基本的实现
实现的概述
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类 似继承,格式相仿,只是关键字不同,实现使用 implements
关键字。
非抽象子类实现接口:
- 必须重写接口中所有抽象方法。
- 继承了接口的默认方法,即可以直接调用,也可以重写。
实现格式:
class 类名 implements 接口名 {
// 重写接口中抽象方法【必须】
// 重写接口中默认方法【可选】
}
① 抽象方法的使用
必须全部实现,代码如下:
定义接口:
public interface InterFaceName {
public abstract void eat();
public abstract void work();
}
定义实现类:
public class ImplementsClass implements InterFaceName {
@Override
public void eat() {
System.out.println("饿了,就得吃饭啊!!!");
}
@Override
public void work() {
System.out.println("上班真特么类!!!");
}
定义测试类:
public class TestDemo {
public static void main(String[] args) {
ImplementsClass ic = new ImplementsClass();
ic.eat();
ic.work();
}
}
结果如下:
② 默认方法的使用
可以继承,可以重写,二选一,但是只能通过实现类的对象来调用。代码如下:
定义接口:
public interface InterFaceName {
//默认方法可以继承,可以重写,二选一,但是只能通过实现类的对象来调用。
public default void sleep() {
System.out.println("真想天天睡到自然醒啊!!!");
}
public default void copyDemo() {
System.out.println("复制粘贴代码!!!");
}
}
定义实现类:
public class ImplementsClass implements InterFaceName {
@Override
public void sleep(){
System.out.println("默认方法可以不重写!!!");
}
}
定义测试类:
public class TestDemo {
public static void main(String[] args) {
ImplementsClass ic = new ImplementsClass();
ic.copyDemo();
ic.sleep();
}
}
结果如下:
③ 静态方法的使用
静态与.class
文件相关,只能使用接口名调用,不可以通过实现类的类名或者实现类的对象调用,代码如下:
定义接口:
public interface InterFaceName {
public static void run(){
System.out.println("跑起来~~~");
}
}
定义实现类:
public class ImplementsClass implements InterFaceName {
// 无法重写静态方法
}
定义测试类:
public class TestDemo {
public static void main(String[] args) {
// ImplementsClass.run(); // 【错误】无法继承方法,也无法调用
InterFaceName.run();
}
}
结果如下:
④ 私有方法的使用
- 私有方法:只有默认方法可以调用。
- 私有静态方法:默认方法和静态方法可以调用。
如果一个接口中有多个默认方法,并且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法 去调用。从设计的角度讲,私有的方法是对默认方法和静态方法的辅助。同学们在已学技术的基础上,可以自行测 试。
定义接口:
public interface InterFaceName {
default void func() {
//私有方法只有默认方法可以调用
func1();
//私有静态方法,默认方法和静态方法都可以调用
func2();
}
public static void method(){
//私有静态方法,默认方法和静态方法可以调用
func2();
}
private void func1() {
System.out.println("私有方法跑起来~~~");
}
private static void func2() {
System.out.println("私有静态方法跑起来~~~");
}
}
定义实现类:
public class ImplementsClass implements InterFaceName {
// 无法重写静态方法
}
定义测试类:
public class TestDemo {
public static void main(String[] args) {
ImplementsClass ic = new ImplementsClass();
ic.func();//通过实现类,调用接口的默认方法
// ImplementsClass.method(); // 【错误】无法继承方法,也无法调用
InterFaceName.method();
}
}
执行结果如下:
⑷ 接口的多实现
之前学过,在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接 口的多实现。并且,一个类能继承一个父类,同时实现多个接口。
实现格式:
class 类名 [
extends
父类名]implements
接口名1,接口名2,接口名3… {
// 重写接口中抽象方法【必须】
// 重写接口中默认方法【不重名时可选】
}
[ ]: 表示可选操作。
① 抽象方法
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。代码如 下:
定义多个接口:
public interface A {
public abstract void showA();
public abstract void show();
}
public interface B {
public abstract void showB();
public abstract void show();
}
实现类C
public class C implements A,B {
@Override
public void showA() {
System.out.println("方法showA被调用了!");
}
@Override
public void showB() {
System.out.println("方法showB被调用了!");
}
@Override
public void show() {
System.out.println("A和B重名方法被调用了!");
}
}
测试类:
public class TestDemo {
public static void main(String[] args) {
C c = new C();
c.showA();
c.showB();
c.show();
}
}
② 默认方法
接口中,有多个默认方法时,实现类都可继承使用。**如果默认方法有重名的,必须重写一次。**代码如下:
定义多个接口:
public interface A {
public default void methodA(){
System.out.println("A接口中的默认方法methodA被执行了!");
}
public default void method(){
System.out.println("A接口中的默认方法method被执行了!");
}
}
public interface B {
public default void methodB(){
System.out.println("B接口中的默认方法methodB被执行了!");
}
public default void method(){
System.out.println("B中的默认方法method被执行了!");
}
}
实现类C
public class C implements A,B {
@Override
public void method() {
//如果默认方法有重名的,必须重写一次
System.out.println("被重写的method方法被调用了!");
}
}
测试类:
public class TestDemo {
public static void main(String[] args) {
C c = new C();
c.methodA();
c.methodB();
c.method();
}
}
执行结果如下:
③ 静态方法
接口中,存在同名的静态方法并不会冲突,原因是只能通过各自接口名访问静态方法。
定义多个接口:
public interface A {
public static void methodA(){
System.out.println("A接口中的静态方法methodA被执行了!");
}
public static void method(){
System.out.println("A接口中的静态方法method被执行了!");
}
}
public interface B {
public static void methodB(){
System.out.println("B接口中的静态方法methodB被执行了!");
}
public static void method(){
System.out.println("B中的静态方法method被执行了!");
}
}
实现类C
public class C implements A,B {
// 无法重写静态方法
}
测试类:
public class TestDemo {
public static void main(String[] args) {
// C.method(); // 【错误】无法继承方法,也无法调用
A.method();
A.methodA();
B.method();
B.methodB();
}
}
执行结果如下:
⑸ 优先级的问题
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的默认方法重名,子类就近选择执 行父类的成员方法。代码如下:
定义接口:
public interface A {
public default void methodA(){
System.out.println("接口A中的methodA方法被调用了!!!");
}
}
定义父类:
public class B {
public void methodA(){
System.out.println("父类B中的methodA方法被调用了!!!");
}
}
定义子类:
public class C extends B implements A {
// 未重写methodA方法
}
测试类:
public class TestDemo {
public static void main(String[] args) {
C c = new C();
c.methodA();
}
}
⑹ 接口的多继承(了解内容)
一个接口能继承另一个或者多个接口,这和类之间的继承比较相似。接口的继承使用 extends
关键字,子接口继 承父接口的方法。如果父接口中的默认方法有重名的,那么子接口需要重写一次。代码如下:
定义父接口:
public interface A {
public default void method(){
System.out.println("父接口A");
}
}
public interface B {
public default void method(){
System.out.println("父接口B");
}
}
定义子接口:
public interface C extends B , A {
@Override
public default void method() {
System.out.println("子接口重写的方法method");
}
}
定义实现类:
public class D implements C {
@Override
public void method() {
System.out.println("实现类重写了接口C中的method方法");
}
}
定义测试类:
public class TestDemo {
public static void main(String[] args) {
D d = new D();
d.method();
}
}
执行结果如下:
小贴士:
子接口重写默认方法时,default关键字可以保留。
子类重写默认方法时,default关键字不可以保留。
⑺ 其他成员特点
- 接口中,无法定义成员变量,但是可以定义常量,其值不可以改变,默认使用public static final修饰。
- 接口中,没有构造方法,不能创建对象。
- 接口中,没有静态代码块。
public interface A {
// public String name;无法定义成员变量
public static final String NAME = "zhangsan";//可以定义常量,其值不可以改变,默认使用public static final修饰。
//public A(){}接口中,没有构造方法,不能创建对象。
/*static {
接口中,没有静态代码块
}*/
}