- 反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,
都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
在编译后产生字节码文件的时候,类加载器子系统通过二进制字节流,负责从文件系统加载class文件。
在执行程序(java.exe)时候,将字节码文件读入JVM中--->这个过程叫做类的加载。然后在内存中对应创建一个java.lang.Class对象-->这个对象会被放入字节码信息中,这个Class对象,就对应加载那个字节码信息,这个对象将被作为程序访问方法区中的这个类的各种数据的外部接口。
所以:我们可以通过这个对象看到类的结构,这个对象就好像是一面镜子,透过镜子看到类的各种信息,我们形象的称之为反射
这种“看透”class的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。
说明:在运行期间,如果我们要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。
如果没有被加载,JVM会根据类的名称找到.class文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象。
补充:
动态语膏vs静态语言
1、动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以
被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运
行时代码可以根据某些条件改变自身结构。
主要动态语言: Object-C、 C#、JavaScript、 PHP、 Python、 Erlang 。
2、静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、
C++。
所以Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动
态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
Java的动态性让编程的时候更加灵活!
- 获取字节码信息:
package com.zhaoss.test02; public class Test { public static void main(String[] args) throws ClassNotFoundException { //案例:以Person的字节码信息为案例 //方式1:通过getClass()方法获取 Person p = new Person(); Class c1 = p.getClass(); System.out.println(c1); //方式2:通过内置class属性: Class c2 = Person.class; System.out.println(c2); System.out.println(c1==c2); //注意:方式1和方式2 不常用 //方式3:--》用的最多:调用Class类提供的静态方法forName Class c3 = Class.forName("com.zhaoss.test02.Person"); //方式4:利用类的加载器(了解技能点) ClassLoader loader = Test.class.getClassLoader(); Class c4 = loader.loadClass("com.zhaoss.test02.Person");
}
Class的具体实例:
类:外部类,内部类
接口
注解
数组
基本数据类型
void
public class Demo { public static void main(String[] args) { /* Class类的具体的实例: (1)类:外部类,内部类 (2)接口 (3)注解 (4)数组 (5)基本数据类型 (6)void */ Class c1 = Person.class; Class c2 = Comparable.class; Class c3 = Override.class; int[] arr1 = {1,2,3}; Class c4 = arr1.getClass(); int[] arr2 = {5,6,7}; Class c5 = arr2.getClass(); System.out.println(c4==c5);//结果:true .同一个维度,同一个元素类型,得到的字节码就是同一个 Class c6 = int.class; Class c7 = void.class; } }
提供一个丰富的类备用:
//作为一个父类 public class Person implements Serializable { //属性 private int age; public String name; //方法 private void eat(){ System.out.println("Person---eat"); } public void sleep(){ System.out.println("Person---sleep"); } }
//Student作为子类 @MyAnnotation(value="hello") public class Student extends Person implements MyInterface{ //属性: private int sno;//学号 double height;//身高 protected double weight;//体重 public double score;//成绩 //方法: @MyAnnotation(value="himethod") public String showInfo(){ return "我是一名三好学生"; } public String showInfo(int a,int b){ return "重载方法====我是一名三好学生"; } private void work(){ System.out.println("我以后会找工作--》成为码农 程序员 程序猿 程序媛"); } void happy(){ System.out.println("做人最重要的就是开心每一天"); } protected int getSno(){ return sno; } //构造器 public Student(){ System.out.println("空参构造器"); } private Student(int sno){ this.sno = sno; } Student(int sno,double weight){ this.sno = sno; this.weight = weight; } protected Student(int sno,double height,double weight){ this.sno = sno; } @Override @MyAnnotation(value="hellomyMethod") public void myMethod() { System.out.println("我重写了myMethod方法。。"); } @Override public String toString() { return "Student{" + "sno=" + sno + ", height=" + height + ", weight=" + weight + ", score=" + score + '}'; } }
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.ElementType.LOCAL_VARIABLE; /* @Target:定义当前注解能够修饰程序中的哪些元素 @Retention:定义注解的声明周期 */ @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String value();//属性 }
public interface MyInterface {//自定义的接口 //随便定义一个抽象方法: void myMethod(); }
2.获取构造器创建对象
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class Test01 { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //获取字节码信息: Class cls = Student.class; //通过字节码信息可以获取构造器: //getConstructors只能获取当前运行时类的被public修饰的构造器 Constructor[] c1 = cls.getConstructors(); for(Constructor c:c1){ System.out.println(c); } System.out.println("-------------------"); //getDeclaredConstructors:获取运行时类的全部修饰符的构造器 Constructor[] c2 = cls.getDeclaredConstructors(); for(Constructor c:c2){ System.out.println(c); } System.out.println("-------------------"); //获取指定的构造器: //得到空构造器 Constructor con1 = cls.getConstructor(); System.out.println(con1); //得到两个参数的有参构造器: Constructor con2 = cls.getConstructor(double.class, double.class); System.out.println(con2); //得到一个参数的有参构造器:并且是private修饰的 Constructor con3 = cls.getDeclaredConstructor(int.class); System.out.println(con3); //有了构造器以后我就可以创建对象: Object o1 = con1.newInstance(); System.out.println(o1); Object o2 = con2.newInstance(180.5, 170.6); System.out.println(o2); } }
3.获取属性和给属性赋值
public class Test02 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException { //获取运行时类的字节码信息: Class cls = Student.class; //获取属性: //getFields:获取运行时类和父类中被public修饰的属性 Field[] fields = cls.getFields(); for(Field f:fields){ System.out.println(f); } System.out.println("---------------------"); //getDeclaredFields:获取运行时类中的所有属性 Field[] declaredFields = cls.getDeclaredFields(); for(Field f:declaredFields){ System.out.println(f); } System.out.println("---------------------"); //获取指定的属性: Field score = cls.getField("score"); System.out.println(score); Field sno = cls.getDeclaredField("sno"); System.out.println(sno); System.out.println("---------------------"); //属性的具体结构: //获取修饰符 /*int modifiers = sno.getModifiers(); System.out.println(modifiers); System.out.println(Modifier.toString(modifiers));*/ System.out.println(Modifier.toString(sno.getModifiers())); //获取属性的数据类型: Class clazz = sno.getType(); System.out.println(clazz.getName()); //获取属性的名字: String name = sno.getName(); System.out.println(name); System.out.println("-------------------------------"); //给属性赋值:(给属性设置值,必须要有对象) Field sco = cls.getField("score"); Object obj = cls.newInstance(); sco.set(obj,98);//给obj这个对象的score属性设置具体的值,这个值为98 System.out.println(obj); } }
4.获取方法和调用方法
public class Test03 { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { //获取字节码信息: Class cls = Student.class; //获取方法: //getMethods:获取运行时类的方法还有所有父类中的方法(被public修饰) Method[] methods = cls.getMethods(); for(Method m:methods){ System.out.println(m); } System.out.println("-----------------------"); //getDeclaredMethods:获取运行时类中的所有方法: Method[] declaredMethods = cls.getDeclaredMethods(); for(Method m:declaredMethods){ System.out.println(m); } System.out.println("-----------------------"); //获取指定的方法: Method showInfo1 = cls.getMethod("showInfo"); System.out.println(showInfo1); Method showInfo2 = cls.getMethod("showInfo", int.class, int.class); System.out.println(showInfo2); Method work = cls.getDeclaredMethod("work",int.class); System.out.println(work); System.out.println("-----------------------"); //获取方法的具体结构: /* @注解 修饰符 返回值类型 方法名(参数列表) throws XXXXX{} */ //名字: System.out.println(work.getName()); //修饰符: int modifiers = work.getModifiers(); System.out.println(Modifier.toString(modifiers)); //返回值: System.out.println(work.getReturnType()); //参数列表: Class[] parameterTypes = work.getParameterTypes(); for(Class c:parameterTypes){ System.out.println(c); } //获取注解: Method myMethod = cls.getMethod("myMethod"); Annotation[] annotations = myMethod.getAnnotations(); for(Annotation a:annotations){ System.out.println(a); } //获取异常: Class[] exceptionTypes = myMethod.getExceptionTypes(); for(Class c:exceptionTypes){ System.out.println(c); } //调用方法: Object o = cls.newInstance(); myMethod.invoke(o);//调用o对象的mymethod方法 System.out.println(showInfo2.invoke(o,12,45));; } }
5.获取类的接口,所在包,注解
public class Test04 { public static void main(String[] args) { //获取字节码信息: Class cls = Student.class; //获取运行时类的接口: Class[] interfaces = cls.getInterfaces(); for(Class c:interfaces){ System.out.println(c); } //得到父类的接口: //先得到父类的字节码信息: Class superclass = cls.getSuperclass(); //得到接口: Class[] interfaces1 = superclass.getInterfaces(); for(Class c:interfaces1){ System.out.println(c); } //获取运行时类所在的包: Package aPackage = cls.getPackage(); System.out.println(aPackage); System.out.println(aPackage.getName()); //获取运行类的注解: Annotation[] annotations = cls.getAnnotations(); for(Annotation a:annotations){ System.out.println(a); } } }
【1】问题1:创建Person的对象,以后用new Person()创建,还是用反射创建?
【2】问题2:反射是否破坏了面向对象的封装性?
- Junit
【1】软件测试的目的:
软件测试的目的是在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。
【2】测试分类:
(1)黑盒测试:
软件的黑盒测试意味着测试要在软件的接口处进行。这种方法是把测试对象看做一个黑盒子,测试人员完全不考虑程序内部的逻辑结构和内部特性,只依据程序的需求规格说明书,检查程序的功能是否符合它的功能说明。因此黑盒测试又叫功能测试。
(2)白盒测试:---》Junit属于白盒测试。
软件的白盒测试是对软件的过程性细节做细致的检查。这种方法是把测试对象看做一个打开的盒子,它允许测试人员利用程序内部的逻辑结构及有关信息,设计或选择测试用例,对程序的所有逻辑路径进行测试,通过在不同点检查程序状态,确定实际状态是否与预期的状态一致。因此白盒测试又称为结构测试。
在没有使用Junit的时候,缺点:
(1)测试一定走main方法,是程序的入口,main方法的格式必须不能写错。
(2)要是在同一个main方法中测试的话,那么不需要测试的东西必须注释掉。
(3)测试逻辑如果分开的话,需要定义多个测试类,麻烦。
(4)业务逻辑和测试代码,都混淆了
junit使用
1】一般测试和业务做一个分离,分离为不同的包:
建议起名:公司域名倒着写+test
以后测试类就单独放在这个包下
【2】测试类的名字:****Test --->见名知意
【3】测试方法的定义--》这个方法可以独立运行,不依托于main方法
建议:
名字:testAdd() testSub() 见名知意
参数:无参
返回值:void
【4】测试方法定义完以后,不能直接就独立运行了,必须要在方法前加入一个注解: @Test
【5】导入Junit的依赖的环境:
public class CalculatorTest { //测试add方法 @Test public void testAdd(){ System.out.println("测试add方法"); Calculator cal = new Calculator(); int result = cal.add(10, 30); System.out.println(result); } //测试sub方法 @Test public void testSub(){ System.out.println("测试sub方法"); Calculator cal = new Calculator(); int result = cal.sub(10, 30); System.out.println(result); } }
【7】判定结果:
绿色:正常结果
红色:出现异常
【8】即使出现绿色效果,也不意味着你的测试就通过了,因为代码中逻辑也可能出现问题,这种情况怎么解决呢?
加入断言
public class CalculatorTest { //测试add方法 @Test public void testAdd(){ System.out.println("测试add方法"); Calculator cal = new Calculator(); int result = cal.add(10, 30); //System.out.println(result);--》程序的运行结果可以不关注 //加入断言:预测一下结果,判断一下我预测的结果和 实际的结果是否一致: Assert.assertEquals(40,result);//第一个参数:预测结果 第二个参数:实际结果 } //测试sub方法 @Test public void testSub(){ System.out.println("测试sub方法"); Calculator cal = new Calculator(); int result = cal.sub(10, 30); System.out.println(result); } }
@Before:
某一个方法中,加入了@Before注解以后,那么这个方法中的功能会在测试方法执行前先执行
一般会在@Beforer修饰的那个方法中加入:加入一些申请资源的代码:申请数据库资源,申请IO资源,申请网络资源。。。
@After:
某一个方法中,加入了@After注解以后,那么这个方法中的功能会在测试方法执行后先执行
一般会在@After修饰的那个方法中加入:加入释放资源的代码:释放数据库资源,释放IO资源,释放网络资源。。。
public class CalculatorTest { @Before public void init(){ System.out.println("方法执行开始了。。。"); } @After public void close(){ System.out.println("方法执行结束了。。。"); } //测试add方法 @Test public void testAdd(){ System.out.println("测试add方法"); Calculator cal = new Calculator(); int result = cal.add(10, 30); //System.out.println(result);--》程序的运行结果可以不关注 //加入断言:预测一下结果,判断一下我预测的结果和 实际的结果是否一致: Assert.assertEquals(40,result);//第一个参数:预测结果 第二个参数:实际结果 } //测试sub方法 @Test public void testSub(){ System.out.println("测试sub方法"); Calculator cal = new Calculator(); int result = cal.sub(10, 30); System.out.println(result); } }
- 注解
【1】历史:
JDK5.0 新增 --- 注解(Annotation),也叫元数据
【2】什么是注解?
注解其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用注解,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
使用注解时要在其前面增加@符号,并把该注解当成一个修饰符使用。用于修饰它支持的程序元素。
【3】注解的重要性:
Annotation 可以像修饰符一样被使用,可用于修饰包,类,构造器,方法,成员变量,参数,局部变量的声明,这些信息被保存在Annotation的"name=value"对中。在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/ArIdroid中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。未来的开发模式都是基于注解的,JPA(java的持久化API)是基于注解的,Spring2.5以. E都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上可以说 :框架=注解+反射+设计模式。
1.实例
(1)Junit注解 @test @Before @After
(2)文档注解
文档注解我们一般使用在文档注释中,配合javadoc工具
javadoc 工具软件识别以下标签:
其中注意:
Ø @param @return和@exception这三个标记都是只用于方法的。
Ø @param的格式要求: @param 形参名 形参类型 形参说明
Ø @return的格式要求: @return 返回值类型返回值说明,如果方法的返回值类型是void就不能写
Ø @exception的格式要求: @exception 异常类型异常说明
Ø @param和@exception可以并列多个
public class Person { /** * 下面是eat方法,实现了XXX功能。 * @param num1 就餐人数 * @param num2 点了几个菜 */ public void eat(int num1,int num2){ } /** * @param age 年龄 * @return int * @exception RuntimeException 当年龄过大的时候 * @exception IndexOutOfBoundsException 当年龄过小的时候 * @see Student */ public int sleep(int age){ new Student(); if(age>100){ throw new RuntimeException(); } if(age<0){ throw new IndexOutOfBoundsException(); } return 10; } }
IDEA中的javadoc使用
(3)JDK内置的3个注解
@Override:限定重写父类方法,该注解只能用于方法
public class Person { public void eat(){ System.out.println("父类eat.."); } }
public class Student extends Person { /* @Override的作用:限定重写的方法,只要重写方法有问题,就有错误提示。 */ @Override public void eat(){ System.out.println("子类eat.."); } }
@Deprecated:用于表示所修饰的元素(类,方法,构造器,属性等)已过时。通常是因为所修饰的结构危险或存在更好的选择
public class Student extends Person { /* @Override的作用:限定重写的方法,只要重写方法有问题,就有错误提示。 */ @Override public void eat(){ System.out.println("子类eat.."); } /* 在方法前加入@Deprecated,这个方法就会变成一个废弃方法/过期方法/过时方法 */ @Deprecated public void study(){ System.out.println("学习。。"); } }
@SuppressWarnings:抑制编译器警告
public class Test02 { //这是一个main方法,是程序的入口: public static void main(String[] args) { @SuppressWarnings("unused") int age = 10; int num = 10; System.out.println(num); @SuppressWarnings({"unused","rwatypes"}) ArrayList al = new ArrayList(); } }
注解代替配置文件:
在servlet3.0之前的配置:
public class HelloServlet implements Servlet { @Override public void init(ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("service方法被调用了..."); } @Override public String getServletInfo() { return null; } @Override public void destroy() { } }
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--配置Servlet--> <!--配置Servlet的信息--> <servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>com.bjsxt.servlet.HelloServlet</servlet-class> </servlet> <!--配置Servlet的映射路径--> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <!--http://localhost:8080/01-hello-servlet/hello--> <url-pattern>/hello</url-pattern> </servlet-mapping> </web-app>
在servlet3.0之后使用注解:替代配置文件。
@WebServlet("/hello") public class HelloServlet implements Servlet { @Override public void init(ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } /** * 用于提供服务, 接收请求, 处理响应 * * @param servletRequest * @param servletResponse * @throws ServletException * @throws IOException */ @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("service方法被调用了..."); } @Override public String getServletInfo() { return null; } @Override public void destroy() { } }
自定义注解
【1】自定义注解使用很少,一般情况下都是用现成的注解。
【2】如何自定义注解:
发现定义的注解的声明使用的关键字:@interface,跟接口没有一点关系。
【3】注解的内部:
这value是属性还是方法?
答案:看上去是无参数方法,实际上理解为一个成员变量,一个属性
无参数方法名字--》成员变量的名字
无参数方法的返回值--》成员变量的类型
这个参数叫 配置参数
无参数方法的类型:基本数据类型(八种),String,枚举,注解类型,还可以是以上类型对应的数组。
PS:注意:如果只有一个成员变量的话,名字尽量叫value。
4】使用注解:
(1)使用注解的话,如果你定义了配置参数,就必须给配置参数进行赋值操作:
@MyAnnotation(value={"abc","def","hij"}) public class Person { }
2)如果只有一个参数,并且这个参数的名字为value的话,那么value=可以省略不写。
@MyAnnotation({"abc","def","hij"}) public class Person { }
(3)如果你给配置参数设置默认的值了,那么使用的时候可以无需传值:
public @interface MyAnnotation2 { String value() default "abc"; }
@MyAnnotation2 @MyAnnotation({"abc","def","hij"}) public class Person { }
(4)一个注解的内部是可以不定义配置参数的:
public @interface MyAnnotation3 { }
内部没有定义配置参数的注解--》可以叫做标记
内部定义配置参数的注解--》元数据
元注解
(1)Retention
@Retention:用于修饰注解,用于指定修饰的那个注解的生命周期,@Rentention包含一个RetentionPolicy枚举类型的成员变量,使用@Rentention时必须为该value成员变量指定值:
➢RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释,在.class文件中不会保留注解信息
➢RetentionPolicy.CLASS:在class文件中有效(即class保留),保留在.class文件中,但是当运行Java程序时,他就不会继续加载了,不会保留在内存中,JVM不会保留注解。如果注解没有加Retention元注解,那么相当于默认的注解就是这种状态。
➢RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java程序时,JVM会保留注释,加载在内存中了,那么程序可以通过反射获取该注释。
(2)Target
用于修饰注解的注解,用于指定被修饰的注解能用于修饰哪些程序元素。@Target也包含一个名为value的成员变量。
@Target({TYPE,CONSTRUCTOR,METHOD}) public @interface MyAnnotation4 { }
(3)Documented
用于指定被该元注解修饰的注解类将被javadoc工具提取成文档。默认情况下,javadoc是 不包括注解的,但是加上了这个注解生成的文档中就会带着注解了
如果:Documented注解修饰了Deprecated注解,
那么Deprecated注解就会在javadoc提取的时候,提取到API中:
(4)Inherited
被它修饰的Annotation将具有继承性。如果某个类使用了被
@Inherited修饰的Annotation,则其子类将自动具有该注解。
案例:
注解:如果MyAnno注解使用了@Inherited之后,就具备了继承性,那么相当于子类Student也使用了这个MyAnno