Java进阶

Java 进阶

1. 反射 Reflect

java.lang.reflect

1. Java 反射机制概述

反射:就是 Java 程序在运行时,可以加载、使用编译期完全未知的类

反射可以加载一个运行时才得知类名的类,获得类的完整构造方法,并实例化出对象,给对象属性设定值或者调用对象的方法。

  • 反射机制的功能:

    1. 在运行时判断任意一个对象所属的类;
    2. 在运行时构造任意一个类的对象;
    3. 在运行时获取任意一个类所具有的成员变量和方法;
    4. 在运行时调用任意一个对象的方法
  • 反射用到的类和接口:

    反射用到的类和接口作用
    Class 类表示正在运行的 Java 应用程序中的类和接口
    java.lang.Class
    Constructor 类提供一个类的构造函数的信息以及访问类的构造函数的接口
    java.lang.reflect.Constructor
    Field 类提供一个类的域的信息以及访问类的域的接口
    java.lang.reflect.Field
    Method 类提供一个类的方法的信息以及访问类的方法的接口
    java.lang.reflect.Method
    Modifier提供了 static 方法和常量,对类和成员访问修饰符进行解码
    java.lang.reflect.Modifier
    Proxy 类提供动态地生成代理类类实例的静态方法
    java.lang.reflect.Proxy
    Array 类该类提供动态地生成和访问 JAVA 数组的方法
    java.lang.reflect.Array
    AccessibleObject类该类是域对象、方法对象、构造函数对象的基础类
    它提供将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力
    java.lang.reflect.AccessibleObject
    Member 接口可以获取有关类成员(域或方法)后者构造函数的信息
    java.lang.reflect 下的接口

2. Class 对象

java.lang.Class

Class 类是 Java 反射机制的入口,封装了一个类或接口的运行时信息,通过调用 Class 类的方法可获取类的信息

  1. Class 类的源码

    public final class Class implements Serializable {
        private Class()
    }
    

    注意:

    1. final 修饰类,不可以被子类继承,同时实现了 Serializable 接口。
    2. private 修饰构造器用,不能被实例化(即不能用 new 创建 Class对象)
  2. 获得 Class 类对象

    Class cls = Class.forName("com.mysql.jdbc.Driver"); //1.通过 Class 类的forName方法
    Class cls = Driver.class; 							//2.通过类名.class 获取反射对象
    Class cls = new Driver().getClass(); 				//3.通过对象.getClass()创建反射对象
    
  3. Class 类对象重要方法

    Class forName("path");			// 返回类对象
    Class getComponentType();		// 是针对数组对象,得到该数组的组成元素所对应对象的 Class 对象
    Class getSuperClass();			// 返回某子类所对应的直接父类所对应的 Class 对象
    String getName();				// 返回 String 形式的该类的简要描述
    T newInstance(); 				// 创建 class 实例,根据某个 Class 对象产生其对应类的实例
    ClassLoader getClassLoader();	// 返回该 Class 对象对应的类的类加载器
    Boolean isArray();				// 判定此 Class 对象所对应的是否是一个数组对象
    
  4. Class 类对象功能方法

    反射可以加载一个运行时才得知类名的类,获得类的完整构造方法,并实例化出对象,给对象属性设定值或者调用对象的方法。

    1. 获取类中的构造器 Constructor

      Constructor[] getDeclaredConstructors(); 			// 获取所有的构造器
      Constructor getDeclaredConstructor(parameterTypes); // 获取任意一个构造器
      Constructor[] getConstructors(); 					// 获取所有 public修饰的构造器
      Constructor getConstructor(parameterTypes));		// 获取单个public修饰的构造器
      
      • Constructor 类的方法:

        Class[] getParameterTypes();	//	获取构造器的参数类型
        int getModifiers();				// 获得构造器的修饰符
        T newInstance(initargs);		// 通过构造器对象创建 class 类的实例
        
    2. 获取类中的变量 field

      Field[] getDeclaredFields(); 		// 获取所有的成员变量
      Field getDeclaredField("property");	// 获取单个成员变量
      Field[] getFields(); 				// 获取所有 public 修饰的成员变量
      Field getField("property");			// 获取单个 public 修饰的成员变量
      
      • Field类的方法和属性:

        getType().getName(); 			// 获得单个成员变量 class 类的名字
        
      • getFieldsgetDeclaredFields 区别:

      区别getFieldsgetDeclaredFields
      获得变量的修饰符返回的是申明为 public 的属性返回的是指定类定义的所有属性
      是否包括父类字段包括父类中定义的不包括父类的
      Field intField = classType.getField("age");
      intField.setAccessible(true);	// 设置私有的(priavte)属性可访问
      intField.setInt(inst, 100);
      int value = intField.getInt(inst);
      
    3. 获取类中的方法 method

      Method[] getDeclaredMethods(); 				// 获取所有方法
      Method[] getMethods(); 						// 获取 public 修饰的方法
      Method getDeclaredMethod((parameterTypes); 	// 获取类中的单个方法
      Method getMethod(parameterTypes); 			// 获取单个 public 修饰方法
      Method getMethod("方法名",parameterTypes);   // 根据方法名获得有参的public方法
      Method getMethod("方法名");   				  // 根据方法名获得无参的public方法
      void setAccessible(true);   		// 设置私有的方法可访问,是AccessibleObject类的方法
      
      • Method类的方法:

        Object invoke(对象名,"参数"); 				 // 通过方法对象执行对应方法
        
      Method logMethod = classType.getDeclaredMethod("Log", String.class);
      logMethod.setAccessible(true);	// 如果是私有的(priavte)方法,需要先设置方法可访问
      logMethod.invoke(inst, "test");
      
  5. 创建对象的方法

    1. 调用类的 Class 对象newInstance 方法

      Class classType = Student.class;
      Student obj = (Student)classType.newInstance();
      
    2. 调用默认 Constructor 对象newInstance 方法

      Class classType = Student.class;
      Constructor constructor = classType.getConstructor();
      Student obj = (Student)constructor.newInstance();
      
    3. 调用带参数 Constructor 对象newInstance 方法

      Class classType = Student.class;
      Constructor cons =classType.getDeclaredConstructor(int.class, String.class);
      Student inst = (Student)constructor.newInstance(1, "123");
      

3. Array 类

Array 类:动态操作数组

Java 在创建数组的时候,需要指定数组长度,且数组长度不可变。

java.lang.reflect 包下提供了一个 Array 类,通过这些方法可以创建动态数组,对数组元素进行赋值、取值操作。 Array 类提供的主要方法(均为静态方法)如下:

									// 创建一个具有指定的元素类型和长度的新数组
Object newInstance(Class componentType, int length);
									// 创建一个具有指定的元素类型和维度的多维数组
Object newInstance(Class componentType, int dimensions);
									// 将指定数组对象中索引元素的值设置为指定的 xxx 类型的 val 值
void setXxx(Object array, int index, xxx val);
xxx getXxx(Object array, int index);// 获取数组对象中指定索引元素的 xxx 类型的值

4. 反射机制应用

反射可以加载一个运行时才得知类名的类,获得类的完整构造方法,并实例化出对象,给对象属性设定值或者调用对象的方法。

  1. 利用反射机制读取文件的内容

    classname=cn.com.xxx.entity.Student
    propertyName1=sname
    propertyValue1=lisi
    propertyName2=stid
    propertyValue2=1
    
    public class ReflectDemo{
        public static void main(String[] args){
            Student stu =(Student)new ReflectDemo().getObject("文件 url");
            System.out.println(stu.getSname()+":"+stu.getStid());
        }
        public Object getObject(String path){
            Properties property = new Properties();
            Object obj = null;
            try{
                String propertyName1 = property.getProperty("propertyName1");
                String propertyValue1 = property.getProperty("propertyValue1");
                String propertyName2 = property.getProperty("propertyName2");
                String propertyValue2 = property.getProperty("propertyValue2");
                String classname = property.getProperty("classname");
                
                Class cls = Class.forName(classname);
                Constructor constructor = cls.getConstructor();
                Object obj = constructor.newInstance();
                Method method1 = cls.getMethod("set"+
                    propertyName1.substring(0,1).toUpperCase()+propertyName1.substring(1),
               		cls.getDeclaredField(propertyName1).getType());
                StudentAnnotation stu = method.getAnnotation(StudentAnnotation.class);
                if(cls.getDeclaredField(propertyName1).getType().equals("int")){
                    method1.invoke(obj,Integer.ValueOf(propertyValue1));
                }else{
                    method1.invoke(obj,propertyValue1);
                }
                
                 Method method2 = cls.getMethod("set"+
                    propertyName2.substring(0,1).toUpperCase()+propertyName2.substring(1),
               		cls.getDeclaredField(propertyName2).getType());
                if(cls.getDeclaredField(propertyName2).getType().equals("int")){
                    method2.invoke(obj,Integer.ValueOf(propertyValue2));
                }else{
                    method2.invoke(obj,propertyValue2);
                }
            }catch (Exception e){
                System.out.println("有异常");
            }
            return obj;
        }
    }
    

2. 代理 Proxy

java.lang.reflect.Proxy

Proxy 代理模式是一种常用的结构型设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问

代理类负责为委托类预处理消息过滤消息转发消息,以及进行消息被委托类执行后的后续处理

代理类与委托类之间通常会存在关联关系,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法来提供特定的服务。
代理类是在执行被代理类的功能方法前后,加入一些业务逻辑:权限的验证,事务的处理,日志的记录

1. 静态代理

静态代理类只能为特定的接口服务。如想要为多个接口服务则需要建立很多个代理类

  • 缺点:

    1. 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度
    2. 代理对象只服务于一种类型(即一个接口)的对象,如果要服务多类型的对象。势必要为每一种对象都进行代, 静态代理在程序规模稍大时就无法胜任了
    3. 静态代理只能代理一种类型,而且是在编译期就已经确定被代理的对象,不够灵活
  • 静态代理的代码实现

    1. 接口

      public interface HelloWorld{
          public void print();
      }
      
    2. 委托类

      public class HelloWorldImpl implements HelloWorld{
          public void print(){
              System.out.println("HelloWorld");
          }
      }
      
    3. 代理类

      public class HelloWorlProxy implements HelloWorld{
          HelloWorld helloWorld;
          public HelloWorldProxy(HelloWorld helloWorld){
              this.HelloWorld = helloWorld;
          }
          @Override
          public void print(){
              System.out.println("《《《before");
              helloWorld.print();
              System.out.println("《《《after");
          }
      }
      
    4. 测试类

      public class MyTest{
          public static void main(String[] args){
              HelloWorld hello = new HelloWorldProxy(new HelloWorldImpl());
              hello.print();
              HelloWorld hello2 = new HelloWorldImpl();
              hello2.print();
          }
      }
      

2. 动态代理

静态代理只能代理一种类型,而且是在编译期就已经确定被代理的对象。

动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象

  • 动态代理的实际使用
    1. Spring 中的 aop
    2. Struts2 中的拦截器
    3. Hibernate 中的代理

实现方法:

  1. 如果被代理类实现了接口,可以使用 jdk 提供的 ProxyInvocationHandler 实现

  2. 如果被代理类没有实现接口,可以使用 cglib,通过继承来实现动态代理,使用时必须导入 cglib 的包

2.1 jdk 动态代理

在 Java 中要实现动态代理机制,需 InvocationHandler 接口和 Proxy 类的支持。

  1. java.lang.reflect.InvocationHandler 接口的定义如下:

    public interface InvocationHandler {
        public Object invoke(Object proxy, //被代理的对象
                            Method method, //要调用的方法
                            Object[] args //方法调用时所需要参数
        				) throws Throwable {};
    }
    
  2. java.lang.reflect.Proxy 类的定义如下:

    public static Object newProxyInstance(ClassLoader loader, //类的加载器
                                 Class< >[] interfaces, //得到全部的接口
                                 InvocationHandler h //得到 InvocationHandler接口的子类实例
                            )throws IllegalArgumentException {}
    
    HelloWorld hello = new HelloWorldImpl();					 //创建委托对象
    MyInvocationHandler handler = new MyInvocationHandler(hello);//创建 InvocationHandler
    HelloWorld proxy = (HelloWorld)Proxy.newProxyInstance(		 //生成动态代理 Proxy
                                    hello.getClass().getClassLoader(), 
                                    hello.getClass().getInterfaces(), 
                                    handler);
    proxy.print();												//调用代理的方法
    
  • 处理器类的代码实现

    1. 处理器类一

      public LogHandler implements InvocationHandler{
          private Object targetObject;
          public Object newProxyInstance(Object targetObject){
              this.targetObject = targetObject;
              return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
                                           targetObject.getClass().getInterfaces,
                                           this);
          }
          @Override
          public Object invoke(Object proxy, //被代理的对象
                              Method method, //要调用的方法
                              Object[] args //方法调用时所需要参数
          				) throws Throwable{
              for(int i = 0;i < args.length;i++){
                  System.out.println(aegs[i]);
              }
              Object ret = null;
              try{
                  System.out.println("logging before");
      			ret = method.invoke(targetObject,args);
                  System.out.println("logging after");
              }catch(NoSuchFieldException e){
                  e.printStackTrace();
              }
              return ret;
          }
      }
      
    2. 处理器类二

      public MyInvocationHandler implements InvocationHandler{
          private Object target;
          public MyInvocationHandler(Object target){
              this.target = target;
          }
          @Override
          public Object invoke(Object proxy, //被代理的对象
                              Method method, //要调用的方法
                              Object[] args //方法调用时所需要参数
          				) throws Throwable{
              System.out.println("logging before");
              Object ret = method.invoke(targetObject,args);
              System.out.println("logging after");
              return ret;
          }
      }
      

2.2 cglib 动态代理

使用 cglib,通过继承来实现动态代理

  1. 委托类

    public class RealSubject{
    	public void buyBook() {
    		System.out.println("************买书************");
    	}
    }
    
  2. 拦截器

    import java.lang.reflect.Method;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class MyInterceptor implements MethodInterceptor{
    	private RealSubject subject;				// 真实对象
    	public MyInterceptor(RealSubject subject) {
    		this.subject = subject;
    	}
    	@Override
    	public Object intercept(Object arg0, Method method, Object[] args, 
                                MethodProxy methodProxy) throws Throwable {
    		Object obj = null;
    		System.out.println("打折...");		  // 之前加入一些业务逻辑
    		obj = method.invoke(subject, args);	   // 真正的动作
    		System.out.println("赠送优惠券...");		// 之后加入一些业务逻辑
    		return obj;
    	}
    }
    
  3. 动态代理

    import net.sf.cglib.proxy.Enhancer;
    
    public class ProxyDemo {
    	@Test
    	public void test1(){
    		RealSubject subject = new RealSubject();
    		MyInterceptor myInterceptor = new MyInterceptor(subject);
    		RealSubject proxy = (RealSubject) Enhancer.create(
                					RealSubject.class, myInterceptor); // 生成代理对象
    		System.out.println(proxy.getClass());
    		proxy.buyBook();
    	}
    }
    

3. 注解 Annotation

注解(Annotation),也叫元数据,一种代码级别的说明。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

Annotation不直接影响代码语义,但是它能够被看作类似程序的工具或类库,它会反过来对正在运行的程序语义有所影响

Annotation 可以从源文件class 文件、或以在运行时反射的多种方式被读取。

根据注解使用方法和用途,可将 Annotation 分为三类:

  1. JDK 内置系统注解
  2. 元注解 (注解的注解,对 “注解” 进行注解 的 “注解”)
  3. 自定义注解

1. 自定义注解

注解定义的形式与 interface 类似,不同的是在 interface 关键字前面加上**@**符号,注解是一种特殊的接口

  1. 自定义注解

    public @interface 注解名 {
        数据类型 属性名() default "默认值";
    }
    

    说明:

    1. 一定要用 public 或默认(default) 修饰符修饰注解名

    2. 属性名字叫作 value 时可以不加名字直接赋值,属性名字不叫作 value 时给属性赋值必须显式指定名字

    3. 属性名后要加括号**()**,可以用 default 关键字设置默认值

    4. 如果注解是数组类型,并且只赋值一个值,{}可以省略

    5. 注解参数可支持数据类型

      1. 所有基本数据类型byte,int,short,long,float,double,boolean,char)
      2. String 类型、Class 类型、Enum 类型、Annotation 类型
      3. 以上所有类型的数组
  2. 常用注解

    注解作用
    @Override表示子类要重写父类的对应方法
    @Deprecated表示方法是不建议被使用的
    @Param表示参数的值
    @SuppressWarnings表示抑制警告,如:@SuppressWarnings("unchecked")
    • @Documented 导出文档,如果乱码:

      javadoc -encoding UTF-8 -charset UTF-8
      
    • **@SuppressWarnings(“要压制的警告类型”) **

      1. 单个字符串时可以用花括号也可以不用,但多个字符串时需要用花括号包起来表示

        @SuppressWarnings({"unchecked","deprecation"})
        
      2. 如在类上注解压制一种警告,再类中的方法注解压制另一种警告,则方法中会同时压制这两种警告

    • SuppressWarnings 注解的常见参数值

      参数值说明
      deprecation使用了不赞成使用的类或方法时的警告
      unchecked执行了未检查的转换时警告,如当使用集合时没有用泛型来指定集合保存的类型
      fallthrough当 Switch 程序块直接通往下一种情况而没有 break 时的警告
      path在类路径、源文件路径中有不存在的路径时的警告
      serial当在可序列化的类上缺少 serialVersionUID 定义时的警告
      finally任何 finally 子句不能正常完成时的警告
      all关于以上所有情况的警告

2. 元注解

元注解:注解的注解,对 “注解” 进行注解 的 “注解”

元注解作用
@Retention表示注解的使用时期
@Target表示注解的使用位置
@Documented表示将会生成到文档里面去
@Param表示参数的解释
@Inherited表示是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的
如果一个使用了@Inherited 修饰的注解类型被用于一个 class,则这个 annotation 将被用于该 class 的子类
  1. 定义注解的使用位置

    @Target(ElementType.)
    
    说明
    TYPE适用 class、interface、enum
    FIELD适用 field
    METHOD适用 method
    CONSTRUCTOR适用 constructor
    PARAMETER适用方法上的参数 parameter
    LOCAL_VARIABLE适用局部变量
    ANNOTATION_TYPE适用注解形态 annotation
    PACKAGE适用包 package
  2. 定义注解的使用时期

    @Retention(RetentionPolicy.类型)
    
    类型说明
    SOURCE源文件(即.java 文件中)有效
    注解只会存在于源文件当中,编译器将其丢弃,不会把注解编译到 class 文件当中
    RUNTIME运行程序时有效
    编译程序时将注解存储于 class 档中,可由 VM 读入,可以通过反射的方式读取到
    CLASS生成.class 文件时起作用
    编译程序时将注解存储于 class 档中,默认是这种行为

3. 注解信息获取

获取方法:

T getAnnotation(annotationClass);				// 获取该元素的注解类
Annotation[] getAnnotations();					// 获取该元素所有的注解
boolean isAnnotationPresent(annotationClass);	// 判断该程序元素上是否包含指定类型的注解
Annotation[] getDeclaredAnnotations();			// 返回直接存在于此元素上的所有注释
public void testAnnotation(){
    Class c = MyAnnotation.class;
    Annotation[] annotations = c.getDeclareMethod("hello",null).getAnnotations();
    for(Annotation ann : annotations){
        if(ann instanceof OtherAnnotation){
            OtherAnnotation other = (OtherAnnotation)ann;
            System.out.println(other.value());
        }
        System.out.println(ann);
    }
    try{
        BothAnnotation both = c.getDeclaredField("num")
            				   .getAnnotation(BothAnnotation.class);
        System.out.println(both.value()+":"+both.count());
    }catch(NoSuchFieldException e){
        e.printStackTrace();
    }
}

4. 注解的使用

  1. 定义注解类

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface StudentAnnotation{
        public String sname() default "张三";
        public int stid() default 1;
        public int value() default 1;
    }
    
  2. 将注解注入到参数中

    public class StudentService{
        @StudentAnnotation(sname="李四",stid=2)  // 其中,value为默认值1
        public void say(){
            System.out.println(sname+":"+stid);
        }
    }
    
  3. 通过反射来得到注解

    public void test(){
        try{
            Class cls = Class.forName("cn.com.xxx.service.StudentService");
            Constructor constructor = cls.getConstructor();
            Method method = cls.getMethod("say",String.class,int.class);
            StudentAnnotation stu = method.getAnnotation(StudentAnnotation.class);
            Object obj = constructor.newInstance();
            String stid = stu.sname();
            int stid = stu.stid();
            method.invoke(obj,sname,stid);
        }catch(ClassNotFoundException e){
            System.out.println("有异常");
        }
    }
    

4. 枚举 Enum

java.lang.Enum

JDK 5 新增了一个关键字 Enum它与 class,interface 的地位相同,用来定义枚举类。枚举类其实是一个特殊的类,它可以有自己的 Field方法构造函数可以实现一个或多个接口

  • JDK 5 增加枚举后对 switch 的扩展

    switch 的控制表达式可以是任何枚举类型。不仅如此,当 switch 控制表达式使用枚举类型时,后面 case 表达式中的值直接使用枚举值名字,无须添加枚举类作为限定

    switch (s) {
        case SPRING:	System.out.println("春天,趁春踏青"); break;
        case SUMMER:	System.out.println("夏天,夏日炎炎"); break;
        case FALL: 		System.out.println("秋天,秋高气爽"); break;
        case WINTER:	System.out.println("冬天,围炉观雪"); break;
    }
    

1. 枚举类

  1. 枚举类

    枚举类是一种特殊类,它也可以有自己的 Field方法构造函数

    但是从枚举”对象是有限而且固定的“含义层面上,枚举对象应该是状态不可变的对象,即枚举类的所有 Field 都应该用 final 修饰。因为 Field 都使用了 final 修饰符来修饰,所以必须在构造函数里为这些 Field 指定初始化值(或者在定义 Field 时指定默认值,或者在初始化代码块中指定初始值,但这两种情况并不常见),因此应该为枚举类显式定义带有参数的构造函数。 一旦为枚举类显式定义了带参数的构造函数,列出枚举值时,就必须对应地传入参数

    public enum SeasonEnum {
        SPRING("趁春踏青"), SUMMER("夏日炎炎"), FALL("秋高气爽"), WINTER("围炉观雪");
        private final String desc;			// 字段 field,final修饰
        private SeasonEnum(String desc) {	// 构造函数 注意:是 private 类型
            this.desc = desc; 
        }
        public String getDesc() {			// 方法
            return desc; 
        }
    }
    

    简单应用:

    1. 枚举类

      public enum Direction {("east"),("south"),西("west"),("north");
      	private String s;
      	public String getS() {
      		return s;
      	}
      	private Direction(String s){ 注意:是 private 类型 s="east"
      		this.s = s;
      	}
      }
      
    2. 使用枚举类

      private void method() {
          Direction d = Direction.;
          System.out.println(d.getS()); // east
      }
      

    枚举类的简单定义:

    public enum SeasonEnum{ 
        SPRING, SUMMER, FALL, WINTER; // 枚举属性
    }
    
  2. 枚举对象

    枚举对象组成成分:

    1. 包含两个属性:“ ordinal ”和“ name

      ordinal # 是索引值,从 0 开始
      name	# 是枚举对象的名称
      
    2. 一系列实例方法:如 toStringcompareTo

      String toString();	// 将枚举转化为字符串
      compareTo();
      enum valueOf(enum.class,string);	// 将字符串转化为枚举
      enum[] values();					// 获得枚举的所有属性值
      int ordinal();						// 获得枚举对象的索引值
      String name();						// 获得枚举对象的名称
      
      String s = "男";
      SexEnum sex= Enum.valueOf(SexEnum.class, s);// 将字符串转化为枚举
      SexEnum sex = SexEnum.;
      String s1 = sex.toString();					// 将枚举转化为字符串
      System.out.println(sex+":"+ s1);
      SexEnum[] sexEnums = SexEnum.values();		// 获得枚举的所有属性值
      for(SexEnum s:sexEnums){
          int ordinal  = s.ordinal();				// 获得枚举对象的索引值
          String name = s.name();					// 获得枚举对象的名称
          System.out.println(ordinal+":"+name);
      }
      

2. 枚举类与接口

枚举类也可以实现一个或多个接口。

与普通类实现一个或多个接口完全一样,枚举类实现一个或多个接口时,也需要实现该接口所包含的方法。

public enum SeasonEnum implements SeasonDesc {
    SPRING, SUMMER, FALL, WINTER;
    @Override
    public void info() { 
        System.out.println("这是一个定义春夏秋冬的枚举类"); 
    }
}

3. 枚举类与抽象方法

如果不使用接口,而是直接在枚举类中定义一个抽象方法,然后再让每个枚举对象提供不同的实现方式

public enum SeasonEnum {
    SPRING {
        @Override
        public void info() { System.out.println("趁春踏青"); }  // 实现方式
    },
    SUMMER {
        @Override
        public void info() { System.out.println("夏日炎炎"); }  // 实现方式
    },
    FALL {
        @Override
        public void info() { System.out.println("秋高气爽"); }  // 实现方式
    },
    WINTER {
        @Override
        public void info() { System.out.println("围炉观雪"); }  // 实现方式 
    };
    abstract void info(); // 定义抽象方法
}

4. Enum 常见种用法

  1. 常量

    JDK1.5 之前,我们定义常量都是: public static final....

    有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法。

    public enum Color { 
        RED, GREEN, BLANK, YELLOW 
    }
    
  2. switch

    JDK1.6 之前的 switch 语句只支持 int,char,enum 类型,使用枚举,能让我们的代码可读性更强

    enum Signal { 
        GREEN, YELLOW, RED 
    }
    public class TrafficLight{
        Signal color = Signal.RED;
        public void change(){
            switch(color){
                case RED: color=Signal.GREEN;break;
                case YELLOW: color=Signal.RED;break;
                case GREEN: color=Signal.YELLOW;break;
            }
        }
    }
    
  3. 向枚举中添加新方法

    如果打算自定义自己的方法,那么必须在 enum 实例序列的最后添加一个分号。而且 Java 要求必须先定义 enum 实例

    public enum Color{
        GREEN("绿色",1), YELLOW("黄色",2), RED("红色",3); // 添加一个分号
        private String name;// 成员变量
        private int index;
        private Color(String name,int index){
            this.name = name;
            this.index = index;
        }
        public static String getName(int index){ // 添加自己的新方法
            for(Color c : Color.values()){
                ifc.getIndex == index){
                    return c.name;
                }
            }
            return null;
        }
        // name,index 的 getter/setter方法
    }
    
  4. 覆盖枚举的方法

    public enum Color{
        GREEN("绿色",1), YELLOW("黄色",2), RED("红色",3); // 添加一个分号
        
        private String name;// 成员变量
        private int index;
        private Color(String name,int index){
            this.name = name;
            this.index = index;
        }
        @Override  // 覆盖枚举的 toString 方法
        public String toString(){
            return this.index+"_"+this.name;
        }
        // name,index 的 getter/setter方法
    }
    
  5. 实现接口

    所有的枚举都继承自 java.lang.Enum 类。由于 Java 不支持多继承,所以枚举对象不能再继承其他类

    public interface Behaviour{
        void print();
        String getInfo();
    }
    public enum Color implements Behaviour{
        GREEN("绿色",1), YELLOW("黄色",2), RED("红色",3); // 添加一个分号
        
        private String name;// 成员变量
        private int index;
        private Color(String name,int index){
            this.name = name;
            this.index = index;
        }
        @Override  // 覆盖接口方法
        public void print(){
            return this.name;
        }
        @Override  // 覆盖接口方法
        public void getInfo(){
            return this.index+"_"+this.name;
        }
        // name,index 的 getter/setter方法
    }
    
  6. 使用接口组织枚举

    public interface Food{
        enum Coffee implements Food{
            BLACK_COFFEE,DECAF_COFFEE,DEFAULT_COFFEE
        }
        enum Desert implements Food{
            FRUIT,CAKE,GELATO
        }
    }
    
  7. 关于枚举集合的使用

    java.util.EnumSetjava.util.EnumMap 是两个枚举集合。

    1. EnumSet 保证集合中的元素不重复EnumSet是抽象类,可通过 allof 方法获取枚举类型内容
    2. EnumMap 中的 key 是 enum 类型,而 value 则可以是任意类型。用法与HashMap 的用法差不多
    public class LightTest{
        public enum Light{
            GREEN(1), YELLOW(2), RED(3); // 添加一个分号
            private int index;			 // 成员变量
            private Light(int index){
                this.index = index;
            }
            @Override  // 覆盖枚举的 toString 方法
            public String toString(){
                return String.ValueOf(this.index);
            }
            // index 的 getter/setter方法
        }
        public static void main(String[] args){
            return this.name;
        }
        public static void testTraversalEnum(){
            Light[] allLight = Light.values();
            for(Light alight : allLight){
                System.out.println("当前灯name:"+ alight.name());
                System.out.println("当前灯ordinal:"+ alight.ordinal());
                System.out.println("当前灯:"+ alight;
            }
        }
        // EnumMap 的用法与 HashMap 的用法差不多,只不过 key 的类型是枚举
        public static void testEnumMap(){
            // 定义时传入参数,默认是 key 的类型
            EnumMap<Light,String> currEnumMap = new EnumMap<Light,String>(Light.class);
            currEnumMap.put(Light.RED,"红灯");
            currEnumMap.put(Light.GREEN,"绿灯");
            currEnumMap.put(Light.YELLOW,"黄灯");
            for(Light alight : Light.values()){
                System.out.println(
                    "[key="+ alight.name()+",value="+currEnumMap.get(aLight)+"]");
            }
        }
        // EnumSet是一个抽象类,获取一个类型的枚举类型内容,可以使用allof方法
        public static void testEnumSet(){
            EnumSet<Lightg> currEnumSet = EnumSet.allof(Light.class);
            for(Light alight : currEnumSet){
                System.out.println("当前EnumSet中数据为:"+ alight);
            }
        }
    }
    

5. 日志 Log4j

log4j 是一个用 Java 编写的可靠,快速和灵活的日志框架(API),它在Apache软件许可下发布。

  • log4j 功能:

    1. 可以控制日志信息输送的目的地

      目的地可以是控制台文件GUI组件套接口服务器NT 的事件记录器UNIX Syslog 守护进程等;

    2. 可以控制每一条日志的输出格式;

    3. 通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程。

  • 日志优缺点:

    1. 优点:维护方便
    2. 缺点:以减缓的应用程序。如果太详细,它可能会导致滚动失明
  • log4j 特性:

    1. log4j 的是线程安全的,是经过优化速度的,支持国际化
    2. log4j 是基于一个名为记录器的层次结构
    3. log4j 的支持每个记录器多输出追加器(appender)
    4. log4j 并不限于一组预定义的设备
    5. 日志行为可以使用配置文件在运行时设置
    6. log4j 设计从一开始就是处理 Java 异常
    7. log4j 使用多个层次,即 ALL,TRACE,DEBUG,INFO,WARN,ERROR 和 FATAL
    8. 日志输出的格式可以通过扩展 Layout 类容易地改变
    9. 日志输出的目标,以及再写入策略可通过实现 Appender 接口改变
    10. log4j 会故障停止。然而,尽管它肯定努力确保传递,log4j 不保证每个日志语句将被传递到目的地

1. log4j 组成部分

log4j 组成作用
logger负责捕获记录信息
appender负责发布日志信息,以不同的首选目的地
layout负责格式化不同风格的日志信息
  • appender的选项:layoutFile( logs/log.log)、Append(true)、Target(System.out)、Threshold (INFO)
  • layout的选项:ConversionPattern

2. 基本配置

  1. 日志输出级别level

    TRACE、DEBUG(默认)、INFO、WARN、ERROR、FATAL 		// 由低到高
    
  2. 日志输出目的地appender

    org.apache.log4j.ConsoleAppender			// 控制台
    org.apache.log4j.FileAppender				// 文件
    org.apache.log4j.DailyRollingFileAppender	// 每天产生一个日志文件
    org.apache.log4j.RollingFileAppender		// 文件大小到达指定尺寸的时候产生一个新的文件
    org.apache.log4j.WriterAppender				// 将日志信息以流格式发送到任意指定的地方
    
  3. 日志输出格式Layout

    org.apache.log4j.HTMLLayout			// 以HTML表格形式布局
    org.apache.log4j.PatternLayout		// 可以灵活地指定布局模式
    org.apache.log4j.SimpleLayout		// 包含日志信息的级别和信息字符串
    org.apache.log4j.TTCCLayout			// 包含日志产生的时间、线程、类别等等信息
    
  4. 打印参数

    %m	// 输出代码中指定的消息
    %p	// 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL 
    %r	// 输出自应用启动到输出该log信息耗费的毫秒数 
    %c	// 输出所属的类目,通常就是所在类的全名 
    %t	// 输出产生该日志事件的线程名 
    %n	// 输出一个回车换行符,Windows平台为“/r/n”,Unix平台为“/n” 
    %d	// 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,
        比如:%d{yyy MMM dd HH:mm:ss , SSS},输出类似:20021018221028921  
    %l	// 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。
        举例:Testlog4.main(TestLog4.java: 10 ) 
    
  5. 配置文件:目的地 appender、日志信息的格式 layout

    log4j.rootLogger=[level],appenderName,appenderName1 # 配置根Logger
    # 配置输出目的地Appender
    log4j.appender.appenderName=appender.class # Appender的全路径
    log4j.appender.appenderName.option=value   # Appender的选项和选项值
    
    #配置日志信息的格式(布局)
    log4j.appender.appenderName.layout=layout.class # layout的全路径
    log4j.appender.appenderName.layout.option=value # layout的选项和选项值
    

3. 代码中初始化 Logger

BasicConfigurator.configure();				// 配置使用控制台输出日志 ConsoleAppender
PropertyConfigurator.configure("src");	// 输出日志到问文件

4. 日志使用

  1. 配置文件

    log4j.rootLogger=DEBUG,A1,A2
    
    log4j.appender.A1=org.apache.log4j.ConsoleAppender
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
     
    log4j.appender.A2=org.apache.log4j.DailyRollingFileAppender
    log4j.appender.A2.File=logs/log.log
    log4j.appender.A2.Append = true
    log4j.appender.A2.layout=org.apache.log4j.PatternLayout
    log4j.appender.A2.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss}[%t:%r]-[%p]%m%n
    
  2. 日志使用

    import org.apache.log4j.BasicConfigurator;
    import org.apache.log4j.Level;
    import org.apache.log4j.Logger;
    import org.junit.Test;
    
    public class Log4JDemo {
    	@Test
    	public void test1(){
    		Logger  logger = Logger.getLogger(Log4JDemo.class);// 实例化日志对象
    		BasicConfigurator.configure();// 基本配置 ConsoleAppender使用控制台输出日志
    		logger.setLevel(Level.ERROR);// 设置日志的等级
    		logger.trace("这是trace...");// 写日志信息
    		logger.debug("这是debug...");
    		logger.info("这是info...");
    		logger.warn("这是warn...");
    		logger.error("这是error...");
    		logger.fatal("这是fatal...");
    	}
    }
    

6. 单元测试 Junit

单元测试:是指对软件中的最小可测试单元进行检查和验证。只有在java项目中使用,不能直接测试web对象

  • 常用的测试:
    1. 黑盒测试
    2. 白盒测试
    3. 冒烟测试/压力测试

1. 常用注解

  1. @Test:放在方法的头部,表示这个方法是一个测试方法,不是一个普通的方法了
  2. @Before,@After:在每个测试方法前后都会进行操作
  3. @BeforeClass,@AfterClass:在各测试方法前后都会进行操作,只运行一次,且方法必须是静态的

2. assertThat 的用法

  1. 一般匹配符

    		// allOf 表明如果接下来的所有条件必须都成立测试才通过,相当于“与”(&&)
    assertThat( testedNumber, allOf( greaterThan(8), lessThan(16) ) );
    		// anyOf 表明如果接下来的所有条件只要有一个成立则测试通过,相当于“或”(||)
    assertThat( testedNumber, anyOf( greaterThan(16), lessThan(8) ) );
    		// anything 表明无论什么条件,永远为 true
    assertThat( testedNumber, anything() );
    		// is 表明如果前面待测的 object 等于后面给出的 object,则测试通过
    assertThat( testedString, is( "developerWorks" ) ); 
    		// not 表明如果前面待测的 object 不等于后面给出的 object,则测试通过
    assertThat( testedString, not( "developerWorks" ) );
    
  2. 字符串相关匹配符

    // containsString 表明如果testedString 包含"developerWorks"则测试通过
    assertThat( testedString, containsString( "developerWorks" ) );
    // endsWith 表明如果testedString 以"developerWorks"结尾则测试通过
    assertThat( testedString, endsWith( "developerWorks" ) );
    // startsWith 表明如果testedString 以"developerWorks"开始则测试通过
    assertThat( testedString, startsWith( "developerWorks" ) );
    // equalTo 表明如果testedValue 等于 expectedValue 则测试通过
    assertThat( testedValue, equalTo( expectedValue ) ); 
    // equalToIgnoringCase 表明如果testedString在忽略大小写等于"developerWorks"则测试通过
    assertThat( testedString, equalToIgnoringCase( "developerWorks" ) );
    // equalToIgnoringWhiteSpace表明如testedString在忽略头尾空格等于"developerWorks"则测试通过
    assertThat( testedString, equalToIgnoringWhiteSpace( "developerWorks" ) );
    
  3. 数值相关匹配符

// closeTo 表明如果testedDouble 在 20.0±0.5 范围之内则测试通过
assertThat( testedDouble, closeTo( 20.0, 0.5 ) );
// greaterThan 表明如果testedNumber 大于 16.0 则测试通过
assertThat( testedNumber, greaterThan(16.0) );
// lessThan 表明如果testedNumber 小于 16.0 则测试通过
assertThat( testedNumber, lessThan (16.0) );
// greaterThanOrEqualTo 表明如果testedNumber 大于等于 16.0 则测试通过
assertThat( testedNumber, greaterThanOrEqualTo (16.0) );
// lessThanOrEqualTo 表明如果testedNumber 小于等于 16.0 则测试通过
assertThat( testedNumber, lessThanOrEqualTo (16.0) );
  1. collection 相关匹配符

    // hasEntry 表明如果mapObject 含有一个键值为"key"对应元素值为"value"的 Entry 项则测试通过
    assertThat( mapObject, hasEntry( "key", "value" ) );
    // hasItem 表明如果iterableObject 含有元素“element”项则测试通过
    assertThat( iterableObject, hasItem ( "element" ) );
    // hasKey 表明如果mapObject 含有键值“key”则测试通过
    assertThat( mapObject, hasKey ( "key" ) );
    // hasValue 表明如果mapObject 含有元素值“value”则测试通过
    assertThat( mapObject, hasValue ( "key" ) );
    

3. 简单应用

import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import org.junit.Test;

public class CalcServiceTest {
	@Before
	public void before(){
		System.out.println("before...在开始的时候,可以初始化一些内容");
	}
	@After
	public void after(){
		System.out.println("after...在结束的的时候,可以释放一些资源");
	}
	
	@BeforeClass
	public static void beforeClass(){
		System.out.println("beforeClass...在开始的时候,可以初始化一些内容");
	}
	@AfterClass
	public static void afterClass(){
		System.out.println("afterClass...在结束的的时候,可以释放一些资源");
	}
	@Test
	public void testAdd() {
		CalcService calcService = new CalcService();
		int a=1;
		int b = 2;
		int ret  = calcService.add(a, b);
		if(ret!=(a+b)){		//判断非正确的业务逻辑
			fail("cn.com.bochy.service.CalcService.add(..)测试失败");
		}
        assertThat("cn...service.CalcService.add(..)测试失败",(a+b),equalTo(ret));
	}
}

7. MD5 加密

1. JDK 的 MD5 加密

MD5Util 工具类

import java.security.MessageDigest;

public class MD5Util {
    public final static String MD5(String s) {
    char hexDigits[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};   
        try {
            byte[] btInput = s.getBytes();
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            mdInst.update(btInput);			// 使用指定的字节更新摘要
            byte[] md = mdInst.digest();	// 获得密文
            int j = md.length;				// 把密文转换成十六进制的字符串形式
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    public static void main(String[] args) {
        System.out.println(MD5Util.MD5("20121221"));//1F69B3D54C2F95A014EA3CC131A34D5B
        System.out.println(MD5Util.MD5("20121221"));
        System.out.println(MD5Util.MD5("加密"));// 2C8DA3BF709F09E73EC143BA0F9AFC82
        System.out.println(MD5Util.MD5("加密"));// 2C8DA3BF709F09E73EC143BA0F9AFC82
    }
}

2. 加载包的 MD5 加密

import org.apache.commons.codec.digest.DigestUtils;
import org.junit.Test;

public class MyTest {
	@Test
	public void test1(){
		String pwd = "123456";
		String md5Pwd = DigestUtils.md5Hex(pwd);
		System.out.println(md5Pwd);
		System.out.println(md5Pwd.length());	// e10adc3949ba59abbe56e057f20f883e
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值