Java 进阶
1. 反射 Reflect
java.lang.reflect
1. Java 反射机制概述
反射:就是 Java 程序在运行时,可以加载、使用编译期完全未知的类。
反射可以加载一个运行时才得知类名的类,获得类的完整构造方法,并实例化出对象,给对象属性设定值或者调用对象的方法。
-
反射机制的功能:
- 在运行时
判断
任意一个对象所属的类; - 在运行时
构造
任意一个类的对象; - 在运行时
获取
任意一个类所具有的成员变量和方法; - 在运行时
调用
任意一个对象的方法
- 在运行时
-
反射用到的类和接口:
反射用到的类和接口 作用 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 类的方法可获取类的信息
-
Class 类的源码
public final class Class implements Serializable { private Class() }
注意:
- final 修饰类,
不可以被子类继承
,同时实现了 Serializable 接口。 - private 修饰构造器用,
不能被实例化
(即不能用 new 创建 Class对象)
- final 修饰类,
-
获得 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()创建反射对象
-
Class 类对象中
重要方法
Class forName("path"); // 返回类对象 Class getComponentType(); // 是针对数组对象,得到该数组的组成元素所对应对象的 Class 对象 Class getSuperClass(); // 返回某子类所对应的直接父类所对应的 Class 对象 String getName(); // 返回 String 形式的该类的简要描述 T newInstance(); // 创建 class 实例,根据某个 Class 对象产生其对应类的实例 ClassLoader getClassLoader(); // 返回该 Class 对象对应的类的类加载器 Boolean isArray(); // 判定此 Class 对象所对应的是否是一个数组对象
-
Class 类对象的
功能方法
反射可以加载一个运行时才得知类名的类,获得类的完整构造方法,并实例化出对象,给对象属性设定值或者调用对象的方法。
-
获取类中的构造器 Constructor
Constructor[] getDeclaredConstructors(); // 获取所有的构造器 Constructor getDeclaredConstructor(parameterTypes); // 获取任意一个构造器 Constructor[] getConstructors(); // 获取所有 public修饰的构造器 Constructor getConstructor(parameterTypes)); // 获取单个public修饰的构造器
-
Constructor 类的方法:
Class[] getParameterTypes(); // 获取构造器的参数类型 int getModifiers(); // 获得构造器的修饰符 T newInstance(initargs); // 通过构造器对象创建 class 类的实例
-
-
获取类中的变量 field
Field[] getDeclaredFields(); // 获取所有的成员变量 Field getDeclaredField("property"); // 获取单个成员变量 Field[] getFields(); // 获取所有 public 修饰的成员变量 Field getField("property"); // 获取单个 public 修饰的成员变量
-
Field类的方法和属性:
getType().getName(); // 获得单个成员变量 class 类的名字
-
getFields 和 getDeclaredFields 区别:
区别 getFields getDeclaredFields 获得变量的修饰符 返回的是申明为 public 的属性 返回的是指定类定义的所有属性 是否包括父类字段 包括父类中定义的 不包括父类的 Field intField = classType.getField("age"); intField.setAccessible(true); // 设置私有的(priavte)属性可访问 intField.setInt(inst, 100); int value = intField.getInt(inst);
-
-
获取类中的方法 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");
-
-
-
创建对象的方法
-
调用类的 Class 对象的
newInstance
方法Class classType = Student.class; Student obj = (Student)classType.newInstance();
-
调用默认 Constructor 对象的
newInstance
方法Class classType = Student.class; Constructor constructor = classType.getConstructor(); Student obj = (Student)constructor.newInstance();
-
调用带参数 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. 反射机制应用
反射可以加载一个运行时才得知类名的类,获得类的完整构造方法,并实例化出对象,给对象属性设定值或者调用对象的方法。
-
利用反射机制读取文件的内容
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. 静态代理
静态代理类只能为特定的接口服务。如想要为多个接口服务则需要建立很多个代理类
-
缺点:
- 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
- 代理对象只服务于一种类型(即一个接口)的对象,如果要服务
多类型的对象
。势必要为每一种对象都进行代, 静态代理在程序规模稍大时就无法胜任了 - 静态代理只能代理一种类型,而且是在编译期就已经确定被代理的对象,不够灵活
-
静态代理的代码实现
-
接口
public interface HelloWorld{ public void print(); }
-
委托类
public class HelloWorldImpl implements HelloWorld{ public void print(){ System.out.println("HelloWorld"); } }
-
代理类
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"); } }
-
测试类
public class MyTest{ public static void main(String[] args){ HelloWorld hello = new HelloWorldProxy(new HelloWorldImpl()); hello.print(); HelloWorld hello2 = new HelloWorldImpl(); hello2.print(); } }
-
2. 动态代理
静态代理只能代理一种类型,而且是在编译期就已经确定被代理的对象。
动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象
- 动态代理的实际使用:
- Spring 中的
aop
- Struts2 中的
拦截器
- Hibernate 中的
代理
- Spring 中的
实现方法:
-
如果被代理类实现了接口,可以使用 jdk 提供的 Proxy,InvocationHandler 实现
-
如果被代理类没有实现接口,可以使用 cglib,通过继承来实现动态代理,使用时必须导入 cglib 的包
2.1 jdk 动态代理
在 Java 中要实现动态代理机制,需 InvocationHandler 接口和 Proxy 类的支持。
-
java.lang.reflect.InvocationHandler
接口的定义如下:public interface InvocationHandler { public Object invoke(Object proxy, //被代理的对象 Method method, //要调用的方法 Object[] args //方法调用时所需要参数 ) throws Throwable {}; }
-
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(); //调用代理的方法
-
处理器类的代码实现
-
处理器类一
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; } }
-
处理器类二
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,通过继承来实现动态代理
-
委托类
public class RealSubject{ public void buyBook() { System.out.println("************买书************"); } }
-
拦截器
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; } }
-
动态代理
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 分为三类:
- JDK 内置系统注解
- 元注解 (注解的注解,对 “注解” 进行注解 的 “注解”)
- 自定义注解
1. 自定义注解
注解定义的形式与 interface 类似,不同的是在 interface 关键字前面加上**@**符号,注解是一种特殊的接口
-
自定义注解:
public @interface 注解名 { 数据类型 属性名() default "默认值"; }
说明:
-
一定要用 public 或默认(default) 修饰符修饰注解名
-
属性名字叫作 value 时可以不加名字直接赋值,属性名字不叫作 value 时给属性赋值必须显式指定名字
-
属性名后要加括号**()**,可以用 default 关键字设置默认值
-
如果注解是
数组类型
,并且只赋值一个值,{}可以省略 -
注解参数可支持数据类型:
- 所有基本数据类型(
byte,int,short,long,float,double,boolean,char
) - String 类型、Class 类型、Enum 类型、Annotation 类型
- 以上所有类型的数组
- 所有基本数据类型(
-
-
常用注解
注解 作用 @ Override
表示子类要重写父类的对应方法 @Deprecated 表示方法是不建议被使用的 @ Param
表示参数的值 @SuppressWarnings 表示抑制警告,如: @SuppressWarnings("unchecked")
-
@Documented
导出文档,如果乱码:javadoc -encoding UTF-8 -charset UTF-8
-
**@SuppressWarnings(“要压制的警告类型”) **
-
单个字符串
时可以用花括号也可以不用,但多个字符串
时需要用花括号包起来表示@SuppressWarnings({"unchecked","deprecation"})
-
如在
类上注解压制
一种警告,再类中的方法注解压制
另一种警告,则方法中会同时压制这两种警告
-
-
SuppressWarnings 注解的常见参数值
参数值 说明 deprecation 使用了 不赞成使用
的类或方法时的警告unchecked 执行了 未检查的转换时
警告,如当使用集合时没有用泛型来指定集合保存的类型fallthrough 当 Switch 程序块直接通往下一种情况而没有 break 时的警告 path 在类路径、源文件路径中有 不存在的路径时
的警告serial 当在可序列化的类上 缺少 serialVersionUID 定义时
的警告finally 任何 finally 子句 不能正常完成时
的警告all 关于以上所有情况的警告
-
2. 元注解
元注解:注解的注解,对 “注解” 进行注解 的 “注解”
元注解 | 作用 |
---|---|
@Retention | 表示注解的使用时期 |
@Target | 表示注解的使用位置 |
@Documented | 表示将会生成到文档里面去 |
@Param | 表示参数的解释 |
@Inherited | 表示是一个标记注解 ,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的注解类型被用于一个 class,则这个 annotation 将被用于该 class 的子类 |
-
定义注解的使用位置
@Target(ElementType.值)
值 说明 TYPE
适用 class、interface、enum FIELD 适用 field METHOD 适用 method CONSTRUCTOR 适用 constructor PARAMETER 适用方法上的参数 parameter LOCAL_VARIABLE 适用局部变量 ANNOTATION_TYPE 适用注解形态 annotation PACKAGE 适用包 package -
定义注解的使用时期
@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. 注解的使用
-
定义注解类
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface StudentAnnotation{ public String sname() default "张三"; public int stid() default 1; public int value() default 1; }
-
将注解注入到参数中
public class StudentService{ @StudentAnnotation(sname="李四",stid=2) // 其中,value为默认值1 public void say(){ System.out.println(sname+":"+stid); } }
-
通过反射来得到注解
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. 枚举类
-
枚举类
枚举类是一种特殊类,它也可以有自己的
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; } }
简单应用:
-
枚举类
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; } }
-
使用枚举类
private void method() { Direction d = Direction.东; System.out.println(d.getS()); // east }
枚举类的简单定义:
public enum SeasonEnum{ SPRING, SUMMER, FALL, WINTER; // 枚举属性 }
-
-
枚举对象
枚举对象组成成分:
-
包含
两个属性
:“ ordinal ”和“ name ”ordinal # 是索引值,从 0 开始 name # 是枚举对象的名称
-
一系列实例方法
:如 toString、compareTo 等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 常见种用法
-
常量
在 JDK1.5 之前,我们定义常量都是:
public static final....
。有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法。
public enum Color { RED, GREEN, BLANK, YELLOW }
-
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; } } }
-
向枚举中添加新方法
如果打算自定义自己的方法,那么必须在 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方法 }
-
覆盖枚举的方法
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方法 }
-
实现接口
所有的枚举都继承自 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方法 }
-
使用接口组织枚举
public interface Food{ enum Coffee implements Food{ BLACK_COFFEE,DECAF_COFFEE,DEFAULT_COFFEE } enum Desert implements Food{ FRUIT,CAKE,GELATO } }
-
关于枚举集合的使用
java.util.EnumSet 和 java.util.EnumMap 是两个枚举集合。
EnumSet
保证集合中的元素不重复;EnumSet
是抽象类,可通过allof
方法获取枚举类型内容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 功能:
-
可以控制日志信息输送的目的地;
目的地可以是
控制台
、文件
、GUI组件
,套接口服务器
、NT 的事件记录器
、UNIX Syslog 守护进程
等; -
可以控制每一条日志的输出格式;
-
通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程。
-
-
日志优缺点:
- 优点:维护方便
- 缺点:以减缓的应用程序。如果太详细,它可能会导致滚动失明
-
log4j 特性:
- log4j 的是线程安全的,是经过优化速度的,支持国际化
- log4j 是基于一个名为记录器的层次结构
- log4j 的支持每个记录器多输出追加器(appender)
- log4j 并不限于一组预定义的设备
- 日志行为可以使用配置文件在运行时设置
- log4j 设计从一开始就是
处理 Java 异常
- log4j 使用多个层次,即 ALL,TRACE,DEBUG,INFO,WARN,ERROR 和 FATAL
- 日志输出的格式可以通过扩展 Layout 类容易地改变
- 日志输出的目标,以及再写入策略可通过实现 Appender 接口改变
- log4j 会故障停止。然而,尽管它肯定努力确保传递,log4j 不保证每个日志语句将被传递到目的地
1. log4j 组成部分
log4j 组成 | 作用 |
---|---|
logger | 负责捕获记录信息 |
appender | 负责发布日志信息,以不同的首选目的地 |
layout | 负责格式化不同风格的日志信息 |
- appender的选项:layout、File( logs/log.log)、Append(true)、Target(System.out)、Threshold (INFO)
- layout的选项:ConversionPattern
2. 基本配置
-
日志输出级别:
level
TRACE、DEBUG(默认)、INFO、WARN、ERROR、FATAL // 由低到高
-
日志输出目的地:
appender
org.apache.log4j.ConsoleAppender // 控制台 org.apache.log4j.FileAppender // 文件 org.apache.log4j.DailyRollingFileAppender // 每天产生一个日志文件 org.apache.log4j.RollingFileAppender // 文件大小到达指定尺寸的时候产生一个新的文件 org.apache.log4j.WriterAppender // 将日志信息以流格式发送到任意指定的地方
-
日志输出格式:
Layout
org.apache.log4j.HTMLLayout // 以HTML表格形式布局 org.apache.log4j.PatternLayout // 可以灵活地指定布局模式 org.apache.log4j.SimpleLayout // 包含日志信息的级别和信息字符串 org.apache.log4j.TTCCLayout // 包含日志产生的时间、线程、类别等等信息
-
打印参数
%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},输出类似:2002年10月18日 22 : 10 : 28 , 921 %l // 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。 举例:Testlog4.main(TestLog4.java: 10 )
-
配置文件:目的地 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. 日志使用
-
配置文件
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
-
日志使用
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. 常用注解
- @Test:放在方法的头部,表示这个方法是一个测试方法,不是一个普通的方法了
- @Before,@After:在每个测试方法前后都会进行操作
- @BeforeClass,@AfterClass:在各测试方法前后都会进行操作,只运行一次,且方法必须是静态的
2. assertThat 的用法
-
一般匹配符
// 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" ) );
-
字符串相关匹配符
// 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" ) );
-
数值相关匹配符
// 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) );
-
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
}
}