作者:钟良堂
一:Java反射
Java的反射机制是指在程序运行过程中动态地获取一个类或对象的成员属性及方法。在程序运行中,通过反射机制还可以动态地修改对象信息。
Java中的java.lang.reflect包提供了对反射的支持。该包提供的Constructor类和Field类以及Method类分别用来访问和储存类的构造方法、成员变量和成员方法。在Java中,反射主要是通过java.lang.Class类的方法实现的。
1:java.lang.Class类的介绍:
Java编程中的每一个类都是一个对象,是java.lang.Class类的对象。也就是说,每一个类既有自己的对象,同时也是Class类的对象。下面详解如何表示Class类的对象。
由于Class类的构造方法是私有的,因此我们无法通过new关键词创建对象的引用。但是,Java提供了几种获取Class对象的方法。
方法一:使用对象的getClass()方法获取Class对象的引用。
package com.zhangliangtang;
public class Test {
public static void main(String[] args) {
//创建一个Student类的对象
Student student = new Student();
//通过getClass()方法获取Class对象
Class clazz = student.getClass();
}
}
/*
自定义学生类
*/
class Student{
}
方法二:任何一个类都有隐含的静态变量class,通过该静态成员获取Class对象的引用。
package com.zhangliangtang;
public class Test {
public static void main(String[] args) {
//通过Student.class获取Class对象
Class clazz = Student.class
}
}
/*
自定义学生类
*/
class Student{
}
方法三:使用Class类的静态方法forName(),它使用一个包含类的字符串作为输入,返回一个Class对象的引用。因为这个方法传入的是一个字符串形式的类路径,所以通过该方法获取Class对象时需要处理ClassNotFoundException异常,该异常代表找不到类或者类无法加载。
package com.zhangliangtang;
public class Test {
public static void main(String[] args) {
//通过Class类提供的静态方法forName()获取Class对象
try {
Class clazz = Class.forName("com.zhangliangtang.Student");
}catch(ClassNotFoundException e) {
e.printStackTrace();
}
}
}
/*
自定义学生类
*/
class Student{
}
上述三种方法都可以创建Class类的对象,但要注意的是,一个类只能有一个反射对象,即上面三种方法创建的Class类的对象是完全相同的。
2:获取构造方法:
①Constructor[] getConstrutors():返回该Class对象表示类包含的所有public构造方法(不含继承)所对应Constructor对象数组。
②Constructor getConstrutor(Class<?>… parameterTypes):返回与该Class对象表示类中参数列表相匹配的public构造函数(不含继承)对应的Constructor对象。
public class Test{
public static void main(String[] args) {
try{
Class clazz;
//获取类
clazz=Student.class;
//获取类中的构造方法
Constructor constructor=clazz.getConstructor();
System.out.println(constructor.getName());
//获取类中第二种构造方法
constructor=clazz.getConstructor(String.class);
System.out.println(constructor.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
③Constructor<?>[] getDeclaredConstructors():返回一个该Class对象表示类中声明的所有构造方法(不区分访问权限)对应的Constructor对象数组。
④Constructor getDeclaredConstructor(Class<?>… parameterTypes):返回与该Class对象表示类中定义的形参类型相匹配的构造方法(不区分访问权限:如果把Student类中的构造方法访问权限改为非public的权限也不影响)的Constructor对象。
public class Test{
public static void main(String[] args) {
try{
Class clazz;
//获取类
clazz=Student.class;
//获取类中的构造方法
Constructor constructor=clazz.getDeclaredConstructor();
System.out.println(constructor.getName());
//获取类中第二种构造方法
constructor=clazz.getDeclaredConstructor(String.class);
System.out.println(constructor.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
获取构造方法信息:
①Class getDeclaringClass():返回声明Constructor对象对应构造方法的类的Class对象。
public class Test{
public static void main(String[] args) {
try{
Class clazz;
clazz=Student.class;
Constructor constructor=clazz.getDeclaredConstructor();
//获取构造方法所属的类信息
clazz=constructor.getDeclaringClass();
System.out.println(clazz.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
②int getModifiers():以整数形式返回Constructor对象表示的构造函数的修饰符。
public class Test{
public static void main(String[] args) {
try{
Class clazz;
clazz=Student.class;
Constructor constructor=clazz.getDeclaredConstructor();
//获取访问权限修饰符,并接收返回值
int a=constructor.getModifiers();
//比对返回值并接收返回的访问权限字符串
String result=Modifier.toString(a);
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
③String getName() :以字符串形式返回Constructor对象所表示得构造方法的名称。
public class Test{
public static void main(String[] args) {
try{
Class clazz;
clazz=Student.class;
Constructor constructor=clazz.getDeclaredConstructor();
System.out.println(constructor.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
④Class<?>[] getParameterTypes():返回由Constructor对象所表示的构造方法的形参类型对应Class对象组成的数组此 。如果构造方法没有参数,则数组长度为0。
public class Test{
public static void main(String[] args) {
try{
Class clazz;
clazz=Student.class;
Constructor constructor=clazz.getDeclaredConstructor();
//得到参数列表存入数组中
Class[] clazzs =constructor.getParameterTypes();
//加强循环遍历该数组,输出参数列表所涉及的参数类
for (Class s : clazzs) {
System.out.println(s.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3:操作构造方法 —— 创建对象:
①void setAccessible(boolean flag):调用构造函数时是否忽略访问权限的影响,true表示忽略,false表示不忽略。
②newInstance(Object… initargs):使用此Constructor对象表示的构造方法来创建声明该构造方法类的新对象。initargs为传入该构造方法中的参数,如果该构造方法没有参数,则可设定为null或一个长度为0的数组。
public class Test{
public static void main(String[] args) {
try{
Class clazz;
clazz=Student.class;
Constructor constructor=clazz.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Object obj=constructor.newInstance("fsr");
Student stu=(Student)obj;
} catch (Exception e) {
e.printStackTrace();
}
}
}
4:获取属性:
①Field[] getFields():返回一个该Class对象表示类或接口中所有public属性(含继承的)对应的Field对象数组。
②Field getField(String fieldName):返回该Class对象表示类或接口中与指定属性名(含继承的)相同的public 属性对应的Field对象。
public class Test{
public static void main(String[] args) {
try{
Class clazz;
clazz=Student.class;
//获取成员变量
clazz.getField("name");
} catch (Exception e) {
e.printStackTrace();
}
}
}
③Field[] getDeclaredFields():返回一个该Class对象表示类或接口内定义的所有属性(不含继承的)对应的Field对象数组。
④Field getDeclaredField(String fieldName) :返回一个与该Class对象表示类或接口内定义的属性名(不含继承的)相匹配的属性相对应的Field对象。
public class Test{
public static void main(String[] args) {
try{
Class clazz;
clazz=Student.class;
//获取成员变量(无论访问权限是什么,都可以获取到)
clazz.getDeclaredField("name");
} catch (Exception e) {
e.printStackTrace();
}
}
}
5:获取普通方法:
①Method[] getMethods():返回一个该Class对象表示类或接口中所有public方法(含继承的)对应的Method对象数组。
②Method getMethod(String methodName, Class<?>… parameterTypes):返回与该Class对象表示类或接口中方法名和方法形参类型相匹配的public方法(含继承的)的Method对象。
public class Test{
public static void main(String[] args) {
try{
Class clazz;
clazz=Student.class;
Method met=clazz.getMethod("doHomework",Integer.TYPE,String.class);
} catch (Exception e) {
e.printStackTrace();
}
}
}
③Method[] getDeclaredMethods():返回一个该Class对象表示类或接口内声明定义的所有访问权限的方法(不含继承的)对应的Method对象数组。
④Method getDeclaredMethod(String methodName,Class<?>… parameterTypes) :返回与该Class对象表示类或接口中方法名和方法形参类型相匹配方法(不含继承的)对应的Method对象。
public class Test{
public static void main(String[] args) {
try{
Class clazz;
clazz=Student.class;
Method met=clazz.getDeclaredMethod("doHomework",Integer.TYPE,String.class);
} catch (Exception e) {
e.printStackTrace();
}
}
}
6:获取普通方法信息:
①Class<?> getDeclaringClass():返回声明Method对象表示方法的类或接口的 Class 对象。
②int getModifiers():以整数形式返回此Method对象所表示方法的修饰符。应该使用Modifier类中的toString方法对所返回的整数进行解码。
③Class<?> getReturnType():返回Method对象所表示的方法的返回值类型所对应的Class对象。
④String getName():返回方法名。
⑤Class<?>[] getParameterTypes():返回由Method对象代表方法的形参类型对应Class对象组成的数组。如果方法没有参数,则数组长度为 0。
⑥Class<?>[] getExceptionTypes():返回由Method对象表示方法抛出异常类型对应Class对象组成的数组。如果此方法没有在其 throws子句中声明异常,则返回长度为 0 的数组。
7:操作普通方法:
①void setAccessible(boolean flag):调用方法时是否忽略访问权限的影响,true表示忽略,false表示不忽略。
②Object invoke(Object obj, Object… args):调用Method对象指代的方法并返回Object类型结果。obj表示该方法所在类实例,如果方法时静态的则obj可以指定为null; args表示传入该方法的参数,如果方法没有参数,则args数组长度可以为 0 或 null。
public class Test{
public static void main(String[] args) {
try{
Class clazz;
clazz=Student.class;
Method met=clazz.getDeclaredMethod("doHomework",Integer.TYPE,String.class);
//忽略访问权限的影响
met.setAccessible(true);
Object obj=met.invoke(4,"qiang")
System.out.println(obj)
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
1
二:注解:
注解目前非常的流行,很多主流框架都支持注解。
注解的语法比较简单,除了@符号的使用之外,它基本与Java固有语法一致。Java SE 5内置了三种标准注解:
@Override,表示当前的方法定义将覆盖超类中的方法。
@Deprecated,使用了注解为它的元素编译器将发出警告,因为注解@Deprecated是不赞成使用的代码,被弃用的代码。
@SuppressWarnings,关闭不当编译器警告信息。
上面这三个注解多少我们都会在写代码的时候遇到。Java还提供了4中注解,专门负责新注解的创建。
1:@Target:表示该注解可以用于什么地方,可能的ElementType参数有:
CONSTRUCTOR:构造器的声明
FIELD:域声明(包括enum实例)
LOCAL_VARIABLE:局部变量声明
METHOD:方法声明
PACKAGE:包声明
PARAMETER:参数声明
TYPE:类、接口(包括注解类型)或enum声明
2:@Retention:表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:
SOURCE:注解将被编译器丢弃
CLASS:注解在class文件中可用,但会被VM丢弃
RUNTIME:VM将在运行期间保留注解,因此可以通过反射机制读取注解的信息。
3:@Document:将注解包含在Javadoc中。
4:@Inherited:允许子类继承父类中的注解。
定义一个注解的方式:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
除了@符号,注解很像是一个接口。定义注解的时候需要用到元注解,上面用到了@Target和@RetentionPolicy,它们的含义在上面的表格中已近给出。
在注解中一般会有一些元素以表示某些值。注解的元素看起来就像接口的方法,唯一的区别在于可以为其制定默认值。没有元素的注解称为标记注解,上面的@Test就是一个标记注解。
注解的可用的类型包括以下几种:所有基本类型、String、Class、enum、Annotation、以上类型的数组形式。元素不能有不确定的值,即要么有默认值,要么在使用注解的时候提供元素的值。而且元素不能使用null作为默认值。注解在只有一个元素且该元素的名称是value的情况下,在使用注解的时候可以省略“value=”,直接写需要的值即可。
下面看一个定义了元素的注解。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
public String id();
public String description() default "no description";
}
定义了注解,必然要去使用注解。
public class PasswordUtils {
@UseCase(id = 47, description = "Passwords must contain at least one numeric")
public boolean validatePassword(String password) {
return (password.matches("\\w*\\d\\w*"));
}
@UseCase(id = 48)
public String encryptPassword(String password) {
return new StringBuilder(password).reverse().toString();
}
}
使用注解最主要的部分在于对注解的处理,那么就会涉及到注解处理器。
从原理上讲,注解处理器就是通过反射机制获取被检查方法上的注解信息,然后根据注解元素的值进行特定的处理。
public static void main(String[] args) {
List<Integer> useCases = new ArrayList<Integer>();
Collections.addAll(useCases, 47, 48, 49, 50);
trackUseCases(useCases, PasswordUtils.class);
}
public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
for (Method m : cl.getDeclaredMethods()) {
UseCase uc = m.getAnnotation(UseCase.class);
if (uc != null) {
System.out.println("Found Use Case:" + uc.id() + " "
+ uc.description());
useCases.remove(new Integer(uc.id()));
}
}
for (int i : useCases) {
System.out.println("Warning: Missing use case-" + i);
}
}
三:Java处理日期和时间:
1、获取今天的日期
Java中的 LocalDate 用于表示当天日期。和 java.util.Date 不同,它只有日期,不包含时间。当你仅需要表示日期时就用这个类。
public static void main(String[] args) {
LocalDate now = LocalDate.now();
System.out.println(now);
}
运行结果:
2020-02-27
上面的代码创建了当天的日期,不含时间信息。打印出的日期格式非常友好,不像老的 Date 类打印出一堆没有格式化的信息。
2、在Java中获取年、月、日信息
LocalDate 类提供了获取年、月、日的快捷方法,其实例还包含很多其它的日期属性。通过调用这些方法就可以很方便的得到需要的日期信息,不用像以前一样需要依赖 java.util.Calendar 类了。
public static void main(String[] args) {
LocalDate now = LocalDate.now();
int year = now.getYear();
int monthValue = now.getMonthValue();
int dayOfMonth = now.getDayOfMonth();
System.out.printf("year = %d, month = %d, day = %d", year, monthValue, dayOfMonth);
}
}
运行结果:
year = 2020, month = 2, day = 27
3、在Java中处理特定日期
在第一个例子里,我们通过静态工厂方法 now() 非常容易地创建了当天日期,你还可以调用另一个有用的工厂方法LocalDate.of() 创建任意日期, 该方法需要传入年、月、日做参数,返回对应的 LocalDate 实例。这个方法的好处是没再犯老 API 的设计错误,比如年度起始于 1900,月份是从 0 开始等等。日期所见即所得,就像下面这个例子表示了2 月 27日,没有任何隐藏机关。
public static void main(String[] args) {
LocalDate date = LocalDate.of(2020, 02, 27);
System.out.println(date);
}
4:在Java中判断两个日期是否相等
LocalDate 重载了 equal 方法:
public static void main(String[] args) {
LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2020, 02, 27);
if (date.equals(now)) {
System.out.println("在同一天");
}
}
运行结果:
在同一天
5:在 Java中获取当前时间
与 Java获取日期的例子很像,获取时间使用的是 LocalTime 类,一个只有时间没有日期的 LocalDate 近亲。可以调用静态工厂方法 now() 来获取当前时间。默认的格式是 hh:mm:ss:nnn。
public static void main(String[] args) {
LocalTime localTime = LocalTime.now();
System.out.println(localTime);
}
运行结果:
10:30:35.093
6:在现有的时间上增加小时
通过增加小时、分、秒来计算将来的时间很常见。Java 8 除了不变类型和线程安全的好处之外,还提供了更好的plusHours() 方法替换 add(),并且是兼容的。注意,这些方法返回一个全新的 LocalTime 实例,由于其不可变性,返回后一定要用变量赋值。
public static void main(String[] args) {
LocalTime localTime = LocalTime.now();
System.out.println(localTime);
//增加2小时
LocalTime localTime1 = localTime.plusHours(2);
System.out.println(localTime1);
}
运行结果:
10:33:12.904
12:33:12.904
7:计算一周后的日期
LocalDate 日期不包含时间信息,它的 plus()方法用来增加天、周、月,ChronoUnit 类声明了这些时间单位。由于 LocalDate 也是不变类型,返回后一定要用变量赋值。
public static void main(String[] args) {
LocalDate now = LocalDate.now();
LocalDate plusDate = now.plus(1, ChronoUnit.WEEKS);
System.out.println(now);
System.out.println(plusDate);
}
运行结果:
2020-02-27
2020-03-05
可以看到新日期离当天日期是 7 天,也就是一周。你可以用同样的方法增加 1 个月、1 年、1 小时、1 分钟甚至一个世纪,更多选项可以查看 Java 8 API 中的 ChronoUnit 类。