java web学习笔记第一天(Junit单元测试、反射、注解)

java web学习笔记目录

  这是第一篇!!(所以还没有目录)

前言

  今天开始规范地学习一下javaweb开发,平时记录一下学习笔记,方便之后的回顾和使用。第一天记录junit单元测试、反射和注解。

Junit单元测试

一、Junit单元测试是什么?

  Junit单元测试是白盒测试的一种

  JUnit 是一个简单的开源框架,用于编写和运行可重复的测试。它是用于单元测试框架的 xUnit 架构的一个实例。JUnit 特性包括:
1.测试预期结果的断言
2.用于共享公共测试数据的测试夹具
3.用于运行测试的测试运行器

二、为什么要使用Junit?

1. 为什么不直接使用System.out.println()?

  将调试语句插入代码是一种低技术的调试方法。通常需要在每次运行程序时手动扫描输出,以确保代码按预期运行。
  从长远来看,以自动化 JUnit 测试的形式编写期望值通常会花费更少的时间,该测试会随着时间的推移保持其价值。如果编写测试来断言期望很困难,那么测试可能会告诉您,更短、更有凝聚力的方法会改进您的设计。

2. 为什么不直接使用调试器??

  调试器通常用于单步调试代码并检查沿途的变量是否包含预期值。但是在调试器中单步调试程序是一个手动过程,需要繁琐的目视检查。本质上,调试会话只不过是对预期与实际结果的手动检查。而且,每次程序更改时,我们都必须在调试器中手动回退程序,以确保没有任何问题。
  以自动化 JUnit 测试的形式编写期望值通常会花费更少的时间,该测试会随着时间的推移保持其价值。如果编写测试来断言预期值很困难,测试可能会告诉您,更短、更有凝聚力的方法会改进您的设计。

三、Junit的使用

1. 测试文件应该放在哪里?

  官方文档给了我们两种方式的建议:

  • 将测试放在与被测类相同的包和目录中。

src
 com
  xyz
   SomeClass.java
   SomeClassTest.java

  虽然适用于小型项目,但许多开发人员认为这种方法会使源目录混乱,并且很难在不包含不需要的测试代码或编写不必要的复杂打包任务的情况下打包客户端可交付成果。

  • 将测试放在一个单独的并行目录结构中,并具有包对齐。

src
 com
  xyz
   SomeClass.java
test
 com
  xyz
   SomeClassTest.java

  这是官方文档认为更好的方法。

2. 测试的基本模板

  官方文档给了我们基本的模板可以直接套用

import org.junit.*;
import static org.junit.Assert.*;
 
public class SampleTest {
 
    private java.util.List emptyList;
 
    /**
     * 初始化测试状态
     * (在每个test方法之前被调用)
     */
    @Before
    public void setUp() {
        emptyList = new java.util.ArrayList();
    }
 
    /**
     * 清除测试状态
     * (在每个test方法之后被调用)
     */
    @After
    public void tearDown() {
        emptyList = null;
    }
    
    @Test
    public void testSomeBehavior() {
        assertEquals("Empty list should have 0 elements", 0, emptyList.size());
    }
 
    @Test(expected=IndexOutOfBoundsException.class)
    public void testForException() {
        Object o = emptyList.get(0);
    }
}

  1.当我们要测试不返回任何内容的方法时使用断言,有assertEquals()、assertArrayEquals()、assertTrue()、assertFalse()等方法,可以在API查看。

  2.当我们要编写在抛出预期异常时通过的测试时,将可选的expected属性添加到@Test注释中,例如:

@Test(expected=IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {
    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);
}

  3.当我们要编写在抛出意外异常时失败的测试时,在测试方法 的throws子句中声明异常,不要在测试方法中捕获异常。未捕获的异常将导致测试失败并出现错误。例如:

@Test
public void testIndexOutOfBoundsExceptionNotRaised() 
    throws IndexOutOfBoundsException {
 
    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);
}

  4.当我们要测试私有方法时,将测试与被测类放在同一个包中,或使用反射来进行测试。

  5.当我们想要为所有测试运行一次 setUp() 和 tearDown() 代码时,可以将@BeforeClass注释添加到要在类中的所有测试之前运行的方法,并在类中的所有测试之后要运行的方法中添加@AfterClass注释。如下:

package junitfaq;
 
import org.junit.*;
import static org.junit.Assert.*;
import java.util.*;
 
public class SimpleTest {
 
    private Collection collection;
    
    @BeforeClass
    public static void oneTimeSetUp() {
        // one-time initialization code        
    }
 
    @AfterClass
    public static void oneTimeTearDown() {
        // one-time cleanup code
    }
 
    @Before
    public void setUp() {
        collection = new ArrayList();
    }
    
    @After
    public void tearDown() {
        collection.clear();
    }
 
    @Test
    public void testEmptyCollection() {
        assertTrue(collection.isEmpty());
    }
    
    @Test
    public void testOneItemCollection() {
        collection.add("itemA");
        assertEquals(1, collection.size());
    }
}

Junit的官方文档链接

反射

一、反射是什么?

  反射 (Reflection) 是 Java 的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。附一篇很好的文章
  根据官方文档看到反射的用途与缺点如下:

1.反射的用途

  反射通常由需要能够检查或修改在 Java 虚拟机中运行的应用程序的运行时行为的程序使用。这是一个相对高级的特性,只应由对语言基础有很强掌握的开发人员使用。考虑到这一点,反射是一种强大的技术,可以使应用程序执行原本不可能执行的操作。
可扩展特性
  应用程序可以通过使用它们的完全限定名称创建可扩展性对象的实例来使用外部的、用户定义的类。
类浏览器和可视化开发环境
  类浏览器需要能够枚举类的成员。可视化开发环境可以受益于利用反射中可用的类型信息来帮助开发人员编写正确的代码。
调试器和测试工具
  调试器需要能够检查类的私有成员。测试工具可以利用反射来系统地调用定义在类上的可发现集 API,以确保测试套件中的高水平代码覆盖率。

2.反射的缺点

  反射是强大的,但不应随意使用。如果可以在不使用反射的情况下执行操作,那么最好避免使用它。通过反射访问代码时,应牢记以下问题。
性能开销
  由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能比非反射操作慢,应避免在对性能敏感的应用程序中频繁调用的代码段中使用。
安全限制
  反射需要运行时权限,在安全管理器下运行时可能不存在该权限。对于必须在受限安全上下文(例如 Applet)中运行的代码,这是一个重要的考虑因素。
内件暴露
  由于反射允许代码执行在非反射代码中非法的操作,例如访问private字段和方法,使用反射会导致意外的副作用,这可能导致代码功能失调并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为。

3.个人总结

  反射可以帮助我们拥有更高的权限,但是由于性能开销更大需要运行时权限防止代码功能失调等原因,我们应掌握但尽量不使用此技术。

二、反射的使用

1.获取Class对象

  1.若对象的实例可用,使用Object.getClass()方法。

Set<String> s = new HashSet<String>();
Class c = s.getClass();					//返回java.util.HashSet

  2.若类型可用但没有实例时,可以使用.class()来获得。

boolean b;
Class c = b.getClass();   				// 编译时会出错

Class c = boolean.class;  				// 正确写法
Class c = int[][][].class;				//检索 Class对应于给定类型的多维数组

  3.若类型的全限定名可用,类可使用Class.forName()方法(注意:该方法不能用于原始类型)

Class c = Class.forName("com.duke.MyLocaleServiceProvider");

  4.若想用于原始类型,可以使用.class()或者使用原始类型的包装类中的TYPE字段

Class c = Double.TYPE;

  5.数组类名称可使用Class.getName()获得(同时也可用于原始类型)

String.class.getName()					//返回"java.lang.String"
byte.class.getName()					//返回"byte"
(new Object[3]).getClass().getName()	//返回"[Ljava.lang.Object;"
(new int[3][4][5][6][7][8][9]).getClass().getName()
										//返回"[[[[[[[I"

2.Class对象的用途

  Class对象可以用于获取变量方法构造方法类名等。
  1.变量

函数方法是否返回变量数组继承变量私有变量
getDeclaredField()NoNoYes
getField()NoYesNo
getDeclaredFields()YesNoYes
getFields()YesYesNo

  2.方法

函数方法是否返回变量数组继承变量私有变量
getDeclaredMethod()NoNoYes
getMethod()NoYesNo
getDeclaredMethods()YesNoYes
getMethods()YesYesNo

  getMethod 方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。

public Method getMethod(String name, Class<?>... parameterTypes)
package com.nuc;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class test1 {
	public static void test() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
	        Class<?> c = methodClass.class;
	        Object object = c.newInstance();
	        Method[] methods = c.getMethods();
	        Method[] declaredMethods = c.getDeclaredMethods();
	        //获取methodClass类的add方法
	        Method method = c.getMethod("add", int.class, int.class);
	        //getMethods()方法获取的所有方法
	        System.out.println("getMethods获取的方法:");
	        for(Method m:methods)
	            System.out.println(m);
	        //getDeclaredMethods()方法获取的所有方法
	        System.out.println("getDeclaredMethods获取的方法:");
	        for(Method m:declaredMethods)
	            System.out.println(m);
	    }
    }
class methodClass {
    public final int fuck = 3;
    public int add(int a,int b) {
        return a+b;
    }
    public int sub(int a,int b) {
        return a+b;
    }
}

  使用invoke()方法调佣我们获取的方法,invoke方法的原型为

public Object invoke(Object obj,
                     Object... args)
              throws IllegalAccessException,
                     IllegalArgumentException,
                     InvocationTargetException
public class test1 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class<?> klass = methodClass.class;
        //创建methodClass的实例
        Object obj = klass.newInstance();
        //获取methodClass类的add方法
        Method method = klass.getMethod("add",int.class,int.class);
        //调用method对应的方法 => add(1,4)
        method.setAccessible(true);		
        //暴力反射
        Object result = method.invoke(obj,1,4);
        System.out.println(result);
    }
}
class methodClass {
    public final int fuck = 3;
    public int add(int a,int b) {
        return a+b;
    }
    public int sub(int a,int b) {
        return a+b;
    }
}

  对成员变量设置值和获取值可使用set()和get()方法

void set(Obeject obj, Obeject value)
get(Object obj)

  3.构造方法(构造方法不是继承的)

函数方法是否返回变量数组私有变量
getDeclaredConstructor()NoYes
getConstructor()NoNo
getDeclaredConstructors()YesYes
getConstructors()YesNo

  创建对象使用 java.lang.reflect.Constructor.newInstance()或 Class.newInstance().前者是首选
Class.newInstance()只能调用零参数构造函数,而 Constructor.newInstance()可以调用任何构造函数,而不管参数的数量。

  • Class.newInstance()抛出构造函数抛出的任何异常,无论它是检查还是未检查。
  • Constructor.newInstance()总是用InvocationTargetException.包装抛出的异常 。
  • Class.newInstance()要求构造函数可见; 在某些情况下Constructor.newInstance()可能会调用private构造函数。
public T newInstance(Object... initargs)
              throws InstantiationException,
                     IllegalAccessException,
                     IllegalArgumentException,
                     InvocationTargetException
Class personClass = Person.class
Consructor constructor = personClass.getConstructor(String.class, int.class);
Object person = constructor.newInstance("张三", 23);
Object person = personClass.newInstance();		//若使用空参构造则可简化
constructor.setAccessible(true);				//也可以暴力反射

反射有部分参考文章深入解析Java反射(1) - 基础

三、注解

一、注解是什么?

  Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
  Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
注解官方文档

二、注解作用?

1.Java 语言使用的注解类型(@Deprecated、@Override、@SuppressWarnings、@SafeVarargs)

1.@Deprecated

  @Deprecated注解表示被标记的元素已被弃用,不应再使用。每当程序使用带有@Deprecated注释的方法、类或字段时,编译器都会生成警告。

2.@Override

  @Override注释通知编译器该元素旨在覆盖超类中声明的元素。覆盖方法将在接口和继承中讨论 。

  虽然在覆盖方法时不需要使用此注释,但它有助于防止错误。如果标记为 的方法@Override未能正确覆盖其超类之一中的方法,则编译器会生成错误。

3.@SuppressWarnings

  @SuppressWarnings注释告诉编译器抑制它会生成的特定警告。

  每个编译器警告都属于一个类别。Java 语言规范列出了两个类别:deprecation和unchecked.

@SuppressWarnings({"unchecked", "deprecation"})
4.@SafeVarargs

  @SafeVarargs注释,当应用于方法或构造函数时,断言代码不会对其varargs参数执行潜在的不安全操作。使用此注释类型时,与varargs使用相关的未经检查的警告将被抑制。

2.元注解(@Retention、@Documented、@Target、@Inherited、@Repeatable)

1.@Retention

@Retention注解指定了标记注解的存储方式:
  RetentionPolicy.SOURCE – 标记的注解仅保留在源代码级别,编译器会忽略。
  RetentionPolicy.CLASS – 标记的注解在编译时由编译器保留,但被 Java 虚拟机 (JVM) 忽略。
  RetentionPolicy.RUNTIME – 标记的注解由 JVM 保留,以便运行时环境可以使用。

2.@Documented

  @Documented注释表明,无论何时使用指定的注释,都应使用 Javadoc 工具记录这些元素。(默认情况下,Javadoc 中不包含注释。)

3.@Target

  @Target注解标记了另一个注解,以限制该注解可以应用于哪种 Java 元素。

目标注释指定以下元素类型之一作为其值:
  ElementType.ANNOTATION_TYPE 可以应用于注释类型。
  ElementType.CONSTRUCTOR 可以应用于构造函数。
  ElementType.FIELD 可以应用于字段或属性。
  ElementType.LOCAL_VARIABLE 可以应用于局部变量。
  ElementType.METHOD 可以应用于方法级注释。
  ElementType.PACKAGE 可以应用于包声明。
  ElementType.PARAMETER 可以应用于方法的参数。
  ElementType.TYPE 可以应用于类的任何元素。

4.@Inherited

  @Inherited注解表示注解类型可以继承自超类。(默认情况下不是这样。)

5.@Repeatable

  @Repeatable注解在JDK8之后加入,表明标记的注解可以多次应用于同一个声明或类型使用。重复注解需要两个声明

  例如,您正在编写代码以使用计时器服务,该服务使您能够在给定时间或按特定计划运行方法,类似于 UNIX cron服务。现在您想设置一个计时器来运行一个方法doPeriodicCleanup,在这个月的最后一天和每个星期五晚上 11:00 要设置计时器运行,创建一个@Schedule注释并将它应用到doPeriodicCleanup方法两次。第一次使用指定月份的最后一天,第二次指定星期五晚上 11 点,如以下代码示例所示:

@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", 小时="23")
public void doPeriodicCleanup() { ... }

声明注释类型

import java.lang.annotation.Repeatable;

@Repeatable(Schedules.class)
public @interface Schedule {
  String dayOfMonth() default "first";
  String dayOfWeek() default "Mon";
  int hour() default 12;
}

声明包含的注释类型

public @interface Schedules {
    Schedule[] value();
}

3.注解小技巧

  1.可以使用default关键字给属性默认初始化值
  2.若只有一个属性需要赋值,且属性的名称为value时,value可以忽略,直接赋值
  3.数组赋值时,值使用{}包裹。若数组中只有一个值,则{}省略。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值