生命周期: 从立项到软件停用的过程
为了提高软件的开发效率,降低软件开发成本,一个优良的软件系统应该具有以下特点:
基本命名规范:使用有意义的英文单词,多个单词用驼峰表示法
包名
全小写 , 域名倒写.模块名.组件名 ,例如: com.util
接口名
首字母大写,形容词,副词。习惯性的以 I 开头。此时的I表示 interface,见名知意.(不强制,要结合其他规范综合考虑),例如: IUserService , IEmployeeService
接口实现类
习惯性使用 Impl 结尾.(不强制,要结合其他规范综合考虑),例如: UserServiceImpl , EmployeeServiceImpl
类名
首字母大写,名词。遵循驼峰表示法。例如: User , Employ
方法名
首字母小写。力求语义清晰,使用多个单词。遵循驼峰表示法。例如: getUserInfoByName()
变量名
首字母小写。遵循驼峰表示法。例如: userInfo
常量名
全大写,力求语义清晰,使用多个单词。使用下划线分割。例如: MAX_STOCK_COUNT.
软件测试经常分为两类:黑盒测试和白盒测试。
黑盒测试也称功能测试,是通过测试来检测每个功能是否能正常使用,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,在程序的接口上进行测试,检查程序功能是否 按照需求规格说明书 的规定正常使用。
他主要发现以下几个错误:
由开发人员来测试. 又称结构测试、透明盒测试、逻辑驱动测试或基于代码的测试。它是按照程序内部的结构测试程序,通过测试来检测产品内部动作是否按照设计规格说明书的规定正常执行。测试者必须检查程序的内部结构,从检查程序的逻辑着手,得出测试数据。
JUnit 是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework)。JUnit 测试是程序员测试, 即白盒测试 ,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。
由于 JUnit 4 回归测试框架是三方提供的,不是 JDK 自带的,所有要使用需导入人家的 jar 包以及安装对应的插件,这里以maven为例,导入maven坐标:
<!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> 复制代码
JUnit配上一些常用注解可以解决代码重复的问题
修饰的方法会在测试方法之前被自动执行
修饰的方法会在测试方法执行之后自动被执行,如果有异常的话,@After后面的代码也会执行
按理说只要能保存一些配置信息,供程序动态读取数据就OK,但是为了提高效率,在 IT 行业中,习惯使用两种具有特殊特点的文件来作为配置文件
该文件称属性文件 / 资源文件 / 配置文件, 以 properties 作为文件后缀名,存取特点是KV键值对的格式:key=value, 多对数据使用换行分开 。
如果要读取 properties 中的数据,我们使用 IO 操作,一行一行的读取,再通过 = 来切分字符串也可以完成.但是还是比较麻烦的,如果有注释更麻烦,此时我们可以意识到这么复杂的步骤,SUN公司肯定帮我们写好了工具方法,这就是是 Properties 。
Properties 是 Map 的实现类.可以继承过来map的常见的操作方法 (get,put....) , map 中的方法,我们一般都不用.因为 properties 文件比较特殊,我们一般使用 Properties 类的新增的方法。
public void load(InputStream inStream); public String getProperty(String key);
package com.test; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * @author Xiao_Lin * @date 2020/12/28 11:55 */ public class ProTest { public static void main(String[] args) throws IOException { //读取配置文件中的数据 Properties properties = new Properties(); //为了获取ClassLoder对象,跟Thread无关 ClassLoader loader = Thread.currentThread().getContextClassLoader(); //通过类加载器去读取配置文件发,返回一个输入流 InputStream stream = loader.getResourceAsStream("user.properties"); //加载配置文件 properties.load(stream); System.out.println(properties.getProperty("user")); System.out.println(properties.getProperty("password")); } } 复制代码
XML(Extensible Markup Language),是一种可扩展的标记语言.(使用◇>括起来)XML技术是W3C组织(World Wide Web Consortium万维网联盟)发布的,目前遵循的是W3C组织于1998年发布的。
XML1.0规范.它的设计宗旨是传输数据,而不是显示数据(HTML).它的标签没有被预定义需要自行定义标签.它是W3C的推荐标准。
在程序的运行过程中,通过 字节码文件 动态的获取类中的成员信息(构造器、方法、字段),这种就叫做反射。目的是为了通过程序自动获取构造器来创建对象、获取方法并调用。
Java代码会经历三个阶段:
我们可以通过多个实物,发现他们的共性,来抽象成一个类,类就是对象的模板,而一个个的实体就是对象
字节码也是真实存在的文件,每一个字节码都是一个实例,而JVM要来存放这些字节码就需要抽象成模板,再通过模板来创建对象,存放每份字节码的信息。当要使用某份字节码时(比方说创建Person对象),就从JVM中调出存了Person.class内容的Class对象,然后再去实例化Person对象。3.
JDK中定义好了Class类: java.lang.Class ,该类中有大量gte开头的方法,表示可以使用字节码对象来获取信息,所以我们当我们拿到了字节码对象就可以直接操作当前字节码中的构造器、方法、字段等等。
通过API,我们可以得知Class没有公共的构造器,原因是因为Class对象在加载类时由Java虚拟机自动构建。
方式一
通过Class类的 forName() 方法来获取字节码对象**(常用)**,多用于配置文件,将类名定义在配置文件中。读取文件,加载类以及各种流行框架中。
Class.forName(String className); //通过类的全限定名来获取字节码对象,全限定类名是包名+类型 Class.forName("java.lang.String"); //JVM中存在则返回,不存在就抛出异常 复制代码
方式二
通过对象的getClass()方法来获取字节码对象,多用于对象的获取字节码的方式。
new User().getClass(); //通过父类Object中的getClass方法 复制代码
方式三
通过类型(基本类型)的class字段来获取字节码对象,多用于参数的传递。
int.class; 复制代码
总结
@Test public void testGetClass() throws Exception { // 1 通过类的全限定名 Class.forName(); Class clz1 = Class.forName("cn.linstudy.domain.Person"); // 2 通过对象的getClass() 方法 Person p = new Person(); Class clz2 = p.getClass(); // 3 通过class 字段去获取 Class clz3 = Person.class; // 字节码只会加载一次,所有不管用的哪种方式去获取字节码,都是同一个 System.out.println(clz1 == clz2); //true System.out.println(clz2 == clz3); //true System.out.println(clz1 == clz3); //true // int 类型和int数据类型不是同一个 System.out.println(int.class); System.out.println(int[].class); } 复制代码
使用反射的目的无外乎是使用程序动态操作类的成员,比如说方法,而且操作方法首先得有对象,而对象是通过构造器来创建的,所以必须先获取构造器。
public Constructor<?>[] getConstructors(); :获取 所有public修饰的构造器 。
public Constructor<?>[] getDeclaredConstructors(); :获取所有的构造器(包括非public)
public Constructor getConstructor(Class... parameterTypes);
public Constructor getDeclaredConstructor(Class...parameterTypes)
parameterTypes : 参数的类型(构造方法的参数列表的类型).
结论
带着 s 表示获取多个.带着 Declared 表示忽略权限,包括私有的也可以获取到。
@Test public void testGetConstructor() throws NoSuchMethodException { // 获取字节码对象 Class clz = Person.class; // 1 获取所有 public 构造器 Constructor[] cons1 = clz.getConstructors(); for(Constructor con : cons1){ System.out.println(con); } System.out.println("--------"); // 2 获取所有构造器,包括 private Constructor[] cons2 = clz.getDeclaredConstructors(); for(Constructor con : cons2){ System.out.println(con); } // 3 获取无参构造器 Constructor con1 = clz.getConstructor(); System.out.println(con1); // 4 获取带参构造器 Constructor con2 = clz.getConstructor(Long.class, String.class); System.out.println(con2); // 5 获取指定 private 构造器 Constructor con3 = clz.getDeclaredConstructor(String.class); System.out.println(con3); } 复制代码
常见错误
参数不匹配,报错.找不到指定的构造器
public Object newInstance(Object... initargs);// initargs: 调用该构造器传递的实际参数.参数列表一定要匹配(类型,个数,顺序). 复制代码
@Test public void testCreateObject() throws Exception { // 获取字节码对象 Class clz = Class.forName("cn.linstudy.domain.Person"); // 获取带参数构造器,参数为参数类型 Constructor con1 = clz.getConstructor(Long.class, String.class); //调用构造器 Object obj = con1.newInstance(1L, "小狼"); System.out.println(obj); // 获取带有参数的 private 构造器 Constructor con2 = clz.getDeclaredConstructor(String.class); // 调用私有构造器,必须先设置为可访问 con2.setAccessible(true); Object obj2 = con2.newInstance("小码"); System.out.println(obj2); } 复制代码
我们尝试私有化构造器来创建对象,结果被告知权限不够
解决办法如下:
public void setAccessible(boolean flag) : 传递一个true,表示可以访问,表示不管权限.
从 API 中我们可以发现, Constructor,Field,Method 是 AccessibleObject 的子类,因为这三种成员都是可以被访问private 修饰符修饰的。
package com.test.reflect; import java.lang.reflect.Constructor; /** * @author Xiao_Lin * @date 2020/12/28 20:17 */ public class TestReflect { public static void main(String[] args) throws Exception { Class<?> student = Class.forName("com.test.reflect.Student"); System.out.println(student); Constructor<?> constructor = student.getDeclaredConstructor(String.class); constructor.setAccessible(true); Object zs = constructor.newInstance("张三"); System.out.println(zs); } } 复制代码
只要看到传入全限定名,基本上都是要使用反射,通过全限定名来获取字节码对象. 只要看到无指定构造器但是能创建对象,基本上都是要通过字节码对象的 newInstance 去创建对象.
public Method[] getMethods(); public Method[] getDeclaredMethods();
@Test public void testGetMethod() throws Exception { /** 1 获取所有 public 方法,包括父类的 2 获取所有方法,包括 private 不包括父类的 3 获取指定参数的public 的方法,包括父类的 4 获取指定参数的private 方法,不包括父类的 **/ // 1 获取字节码对象 Class clz = Class.forName("cn.linstudy.domain.Person"); // 2 获取构造器来创建对象 // 3 获取方法 //1 获取所有 public 方法,包括父类的 Method[] methods = clz.getMethods(); for(Method m : methods){ System.out.println(m); } System.out.println("---------"); //2 获取所有方法,包括 private 不包括父类的 Method[] methods2 = clz.getDeclaredMethods(); for(Method m : methods2){ System.out.println(m); } System.out.println("---------"); //3 获取指定参数的 public 的方法,包括父类的 Method sayHelloMethod = clz.getMethod("sayHello", String.class); System.out.println(sayHelloMethod); //4 获取指定参数的private 方法,不包括父类的 Method doWorkMethod = clz.getDeclaredMethod("doWork", String.class); System.out.println(doWorkMethod); } 复制代码
public Object invoke(Object obj, Object... args); :
obj: 表示调用该方法要作用到那个对象上..
args:调用方法的实际参数方法的返回值表示,调用该方法是否有返回值,如果有就返回,如果没有返回null。
传统的调用方法
Student t = new Student(1, "张三"); t.sleep(5);// 张三,睡5个小时。 复制代码
使用反射创建对象调用方法
Method m = clz.getMethod(“sleep”, int.class);// 找到sleep方法。 m.invoke(obj, 5);// 睡,5个小时。 复制代码
public class Person { private Long id; private String name; public Person() { } public Person(Long id, String name) { this.id = id; this.name = name; } private Person(String name) { this.name = name; } public void sayHello(){ System.out.println("hello"); } public String sayHello(String name){ System.out.println(name + ": hello"); return "您好"; } public static void sayHello(String name,Long id){ System.out.println("调用静态方法"); } private void doWork(){ System.out.println("doWork"); } private void doWork(String name){ System.out.println(name + ": doWork"); } // getter方法 setter 方法 public String toString() { return "Person{" + "id=" + id + ", name='" + name + '\'' + '}'; } } 复制代码
@Test public void testGetMethod() throws Exception { // 1 获取字节码对象 Class clz = Class.forName("com.reflect.Person"); // 2 获取构造器来创建对象 Object obj = clz.newInstance(); // 使用公共的无参数的构造器 // 3 获取方法 //1 获取所有 public 方法,包括父类的 Method[] methods = clz.getMethods(); for(Method m : methods){ System.out.println(m); } System.out.println("---------"); //2 获取所有方法,包括 private 不包括父类的 Method[] methods2 = clz.getDeclaredMethods(); for(Method m : methods2){ System.out.println(m); } System.out.println("---------"); //3 获取指定参数的 public 的方法,包括父类的 Method sayHelloMethod = clz.getMethod("sayHello", String.class); System.out.println(sayHelloMethod); // 调用方法 Object val1 = sayHelloMethod.invoke(obj, "张三"); System.out.println("值1:" + val1); //4 获取指定参数的private 方法,不包括父类的 Method doWorkMethod = clz.getDeclaredMethod("doWork", String.class); System.out.println(doWorkMethod); // 设置可访问 doWorkMethod.setAccessible(true); // 调用私有的方法 doWorkMethod.invoke(obj,"李四"); // 调用静态方法 Method staticSayHelloMethod = clz.getDeclaredMethod("sayHello", String.class, Long.class); // 不需要对象去调用,但是参数必须加上null,不然会把后面的参数作为调用方法的对象了. staticSayHelloMethod.invoke(null,"小明",1L); } 复制代码
public Field getField(String name); :
public Field getDeclaredField(String name);
name 要获取的字段的名称
public Field[];
getFields() ;
public Field[] getDeclaredFields();
@Test public void testField() throws Exception { // 1 获取字节码对象 Class clz = Person.class; Object obj = clz.newInstance(); // 2 获取字段 Field[] fs = clz.getFields(); for(Field f: fs){ System.out.println(f); } Field[] fs2 = clz.getDeclaredFields(); for(Field f: fs2){ System.out.println(f); } // 获取单个字段 Field nameField = clz.getDeclaredField("name"); System.out.println(nameField); } 复制代码
get(Object obj);
set(Object obj,Object value);
// 设置私有字段可访问 nameField.setAccessible(true); // 操作name字段 // 设置那么字段的数据 nameField.set(obj,"小狼"); // 获取name字段的数据 Object nameValue = nameField.get(obj); System.out.println(nameValue); 复制代码
JavaBean 是 Java 中最重要的一个可重用的组件(减少代码重复,可重用,封装业务逻辑,封装数据)。
JavaBean 可以封装数据,就是将数据保存到一个 bean 对象的属性中的。
属性不是字段,属性是通过get/set方法推导出来的。
**规范的get方法/获取方法/读方法:**public修饰、无参数、有返回、get开头。
**规范的set方法/设置方法/写方法:**public修饰、有参数、无返回、set开头。
注意:
JavaBean是一个非常常用的组件,无外乎就是操作里面的属性。而之前我们要获取JavaBean中的方法,如果使用反射非常麻烦,于是SUN公司专门提供了一套操作 JavaBean 属性的API: 内省 ( Introspector )。
通过字节码对象来获取BeanInfo对象的时候,默认会内省当前字节码对象以及其所有的父类的信息。比如: getBeanInfo(A.class) ,其实它也会内省A的父类,如Object的信息。一般来说,我们不关心父类的属性相关信息,此时可以调用getBeanInfo的重载方法: getBeanInfo(beanClass,stopClass) 。
示范: BeanInfo beanInfo = Introspector.getBeanInfo(Person.class,Object.class);
package com.day03.IntrospectorDemo; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; /** * @author Xiao_Lin * @date 2020/12/29 13:37 */ public class TestIntrospector { public static void main(String[] args) throws Exception { //创建对象 Student student = Student.class.newInstance(); //把 JavaBean 转成 beanInfo BeanInfo beanInfo = Introspector.getBeanInfo(student.getClass(),Object.class); //通过通过beanInfo获取所有属性 PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); //遍历属性描述器数组,获取到每个属性描述器 for (PropertyDescriptor pd : propertyDescriptors) { //获取属性名 System.out.println("属性名 = " + pd.getName()); //获取属性类型 System.out.println("属性类型 = " + pd.getPropertyType()); //获取属性的getter/setter方法 Method getMethod = pd.getReadMethod(); System.out.println("get方法 = " +getMethod); Method setMethod = pd.getWriteMethod(); System.out.println("set方法 = " +setMethod); //调用age属性的set方法 if ("age".equals(pd.getName())){ //执行age的set方法,invoke参数含义是给哪个对象赋予哪个值 setMethod.invoke(student,22); } //再次执行get方法 System.out.println(student.getAge()); } } } 复制代码
map和JavaBean的结构很类似,我们可以将 map 和 JavaBean 相互转换.将key和属性名一 一对应起来
// Javabean 转 map public static Map<String, Object> BeanToMap(Object obj) throws Exception{ Map<String, Object> map = new HashMap<>(); //通过内省获得所有属性 BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass(), Object.class); PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor pd : pds) { //获取属性名作为key String key = pd.getName(); //获取属性的getter方法并且调用 Object value = pd.getReadMethod().invoke(obj); map.put(key, value); } return map; } 复制代码
public class BeanMap { public static void main(String[] args) throws Exception{ Map<String, Object> map = BeanToMap(new Student("张三", 20)); map.forEach((k,v)-> System.out.println(k+"->"+v)); } } 复制代码
//map转JaveBean,这里使用泛型 public static <T> T MapToBean(Map<String, Object> map ,Class<T> clz) throws Exception{ //创建JavaBean对象 T t = clz.newInstance(); //遍历属性,获取属性名作为mao的key 去获取value值,再设置给setter方法 //获取所有属性 BeanInfo beanInfo = Introspector.getBeanInfo(clz, Object.class); PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor pd : pds) { String key = pd.getName(); Object value = map.get(key); pd.getWriteMethod().invoke(t,value); } return t; } 复制代码
我们可以使用注解来修饰类中的成员信息,注解其实就是Annotation。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface 注解名 { } 复制代码
定义格式: @interface 注解名
使用格式: @注解名(属性名=属性值, 属性名=属性值)
注解贴在程序元素上,想要拥有某一些功能, 必须有三个角色去参与 :
@Override @Deprecated @SuppressWarings @Functionallnterface
注解 :用来贴在类/方法/变量等之上的一个标记,第三方程序可以通过这个标记赋予一定功能。
元注解 :在定义注解的时候用来贴在注解上的注解,用来限定注解的用法。他囊括了三个注解
表示注解可以贴在哪些位置(类,方法上,构造器上等等).位置的常量封装在 ElementType 枚举类 中。
ElementType.ANNOTATION_TYPE ElementType.CONSTRUCTOR ElementType.FIELD ElementType.LOCAL_VARIABLE ElementType.METHOD ElementType.PACKAGE ElementType.PARAMETER ElementType.TYPE
表示注解可以保存在哪一个时期,表示时期的值,封装在RetentionPolicy枚举类中。
使用 @Documented 标注的标签会保存到API文档中。
@Inherited 标注的标签可以被子类所继承。
标记已过时,不推荐使用.在JDK5之前,使用文档注释来标记过时
@SuppressWarings @Functionallnterface
注解 :用来贴在类/方法/变量等之上的一个标记,第三方程序可以通过这个标记赋予一定功能。
元注解 :在定义注解的时候用来贴在注解上的注解,用来限定注解的用法。他囊括了三个注解
表示注解可以贴在哪些位置(类,方法上,构造器上等等).位置的常量封装在 ElementType 枚举类 中。
ElementType.ANNOTATION_TYPE ElementType.CONSTRUCTOR ElementType.FIELD ElementType.LOCAL_VARIABLE ElementType.METHOD ElementType.PACKAGE ElementType.PARAMETER ElementType.TYPE
表示注解可以保存在哪一个时期,表示时期的值,封装在RetentionPolicy枚举类中。
使用 @Documented 标注的标签会保存到API文档中。
@Inherited 标注的标签可以被子类所继承。