枚举、注解、反射是三个Java中最重要,也是最容易被忽视的三个技术。很多人只知道利用框架机械性地使用它们,但是对原理掌握不扎实,下面一起从根本来理解它们。
JDK1.5引入了一个新的类型——枚举
在JDK1.5之前,我们定义常量都是:
public static final xxx
很难去管理。
而枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法。
枚举:用于定义有限数量的一组同类常量,例如:
错误级别:
低、中、高、紧急
一年四季:
春、夏、秋、冬
商品的类型:
美妆、手机、电脑、男装、女装…
在枚举类型中定义的常量是该枚举类型的实例
在JDK1.5之前, 我们定义一个类型的常量的方式:
//JDK1.5之前描述级别的方式:
public class Level {
private int levelValue; //私有属性值
private Level(int levelValue){ //封装构造方法,让外部无法直接利用构造方法创建对象
this.levelValue = levelValue;
}
//接下来使用公开静态常量属性的方式,来创建对象
//用户可以通过类名.常量名获取这些常量,获取的常量就是一个Level类型
//然后通过.getLevelValue()方法获取它的值
public static final Level LOW = new Level(1);
public static final Level MIDDLE = new Level(2);
public static final Level HIGH = new Level(3);
public int getLevelValue() {
return levelValue;
}
public void setLevelValue(int levelValue) {
this.levelValue = levelValue;
}
}
以上的方式,显然代码量有点冗余,而且并不好提供一些常量对比方法。
枚举定义方式:
权限修饰符 enum 枚举类型名称{
实例1,实例2,实例3....;
}
例如:
public enum Level {
//描述枚举类型(创建对象)
LOW(1),
MIDDLE(2),
HIGH(3);
private int levelValue; //正常私有属性
private Level2(int levelValue){ //正常私有构造方法
this.levelValue = levelValue;
}
public int getLevelValue() {
return levelValue;
}
public void setLevelValue(int levelValue) {
this.levelValue = levelValue;
}
}
此时的代码量就比较简洁了,甚至还可以这样:
//不赋值,直接通过名字从字面意义上判断大小
public enum Level {
//描述类型的对象(后面可以跟括号,也可以不跟)
LOW,MIDDLE,HIGH;
//无私有属性
//私有无参构造(可省略)
//private Level3(){}
}
可以通过字面意思直接判断Level类型的状态,也可以通过自定义的方法来实现比较规则等。
Enum抽象类常见方法
Enum是所有Java枚举类型的公共基本类(注意Enum是抽象类),以下是它的常见方法:
返回类型 | 方法名称 | 方法说明 |
---|---|---|
int |
compareTo(E e) |
比较此枚举与指定对象的顺序 |
boolean |
equals(Object o) |
当指定对象等于此枚举类型时,返回true |
Class> |
getDeclaringClass() |
返回此枚举常量的枚举类型对应的Class对象 |
String |
name() |
返回此枚举常量的名称,在其枚举声明中对其进行声明 |
int |
ordinal() |
返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零) |
String |
toString() |
返回枚举常量的名称,它包含在声明中 |
static |
static valueOf(Class |
返回带有指定名称的指定枚举类型的枚举常量 |
以下方枚举为例:
public enum Level3 {
LOW, MIDDLE, HIGH;
}
此方法按照枚举对象定义的顺序来作为比较的依据,与值无关
int a = Level3.LOW.compareTo(Level3.MIDDLE); //a = -1
int b = Level3.LOW.compareTo(Level3.HIGH); //b = -2
int c = Level3.HIGH.compareTo(Level3.LOW); //c = 2
Level3 level = Enum.valueOf(Level3.class,"LOW");
此时的 level 就是 Level3 的 LOW 对象
所有的枚举都是继承自java.lang.Enum
类,由于Java不支持多继承,所以枚举对象不能再继承其他类。
每一个枚举对象,都可以实现自己的抽象方法
示例接口:
public interface LShow{
void show();
}
public enum Level3 implements LShow {
LOW(){
@Override
public void show() {
System.out.println("我是LOW");
}
}, MIDDLE, HIGH;
@Override
public void show() {
System.out.println("我是公共方法");
}
}
当LOW对象调用show时输出“我是LOW”,而其他没有实现自己的抽象方法的对象调用show方法,输出的则是“我是公共方法”。
注:对象后的小括号指的是调用构造方法,如果对象调用的是无参构造方法,那么可以省略小括号,如果对象调用有参构造方法,那么括号和参数都必须要有。
Java 注解 (Annotation)又称Java标注,是JDK5.0引入的一种注释机制。
Java语言中的类、方法、属性、参数和包等都可以被标注,和注释不同,Java标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java虚拟机可以保留标注内容,在运行时可以获取到标准内容。当然它也支持自定义Java注解。
注解主要用于:
理解 Annotation 的关键,是理解 Annotation 的语法和用法。
学习步骤:
@Override
: 重写
用于标注重写了父类的方法
@Deprecated
: 废弃
用于标注该类/接口/方法已被废弃,不建议使用,一般用于项目中代码的扩展
@SafeVarargs
(一般不怎么使用)
Java 7 开始支持,忽视任何使用参数为泛变量的方法或构造函数调用产生的警告
@FunctionalInterface
: 函数式接口
Java8开始支持,标注一个匿名函数或函数式接口
@Repeatable
: 标注某注解可以在同一个声明上使用多次
@SuppressWarning
: 抑制编译时的警告信息
作用于其他注解(自定义注解)的注解
@Retention
:表示这个注解怎么保存,是只在代码中,还是编入.class文件中,还是在运行时可以通过反射访问@Documented
:表示该注解可以包含在用户文档中@Target
:表示这个注解可以作用的范围,类/变量/方法等@Inherited
:表示这个注解可以被子类自动继承注意:
@Inherited
标记的注解@Inherited
标记每一个 Annotation 对象,都会有唯一的 RetentionPolicy 属性;至于ElementType,则有1-n个。
每一个 Annotation 都与 1-n 个 ElementType 关联。当 Annotation 与某个 ElementType 关联时,就意味着:Annotation有了某种和用途。例如,若一个 Annotation 对象的 ElementType是METHOD,那么该Annotation只能用来修饰方法。
public enum ElementType {
TYPE, //类、接口(包括注解)或枚举的声明
FIELD, //字段声明(包括枚举常量)
METHOD, //方法声明
PARAMETER, //参数声明
CONSTRUCTOR, //构造方法声明
LOCAL_VARIABLE, //局部变量声明
ANNOTATION_TYPE, //注解类型声明
PACKAGE, //包声
}
“每一个 Annotation 都与一个 RententionPolicy 关联”
public enum RetentionPolicy{
SOURCE, //Annotation信息仅存在于编译器处理期间,编译器处理完之后就没用了
CLASS, //编译器将Annotation储存于类对应的.class文件,默认行为
RUNTIME //编译器将Annotation储存于类对应的.class文件中,并且可以由JVM读取
}
public @Interface 注解名{}
//元注解
@Documented //表示此注解可以在文档中生成
@Target({ElementType.TYPE,ElementType.METHOD}) //表示此注解的作用范围:可以用在类、接口、枚举、注解、方法上
@Retention(RetentionPolicy.RUNTIME) //持久策略(JVM可读取)
@Inherited //表示该注解可以被子类继承
@interface MyAnnotation{
String value() default "";
int num() default 1;
}
上面是自定义的一个 Annotation ,它上方的四个元注解以及@interface含义分别是:
@interface
使用@interface定义注解时,意味着它实现了Annotation接口,即该注解就是一个Annotation
自定义Annotation 时,@interface是必须的
注意:它和我们通常的implemented实现接口的方法不同。Annotation接口的实现细节全部有编译器完。通过@interface定义注解后,该注解不能继承其他的注解或接口。
@Documented
类和方法的Annotation在缺省的情况下是不不出现在javadoc中的。如果使用@Documented修饰该Annotation,则表示它可以出现在javadoc中。
定义Annotation时,@Document可有可无
@Target(ElementType.TYPE)
之前说过,ElementType 是Annotation 的类型属性。而@Target的作用,就是来指定Annotation的类型属性
@Target(ElementType.TYPE) 的意思就是指定该 Annotation 的类型是 ElementType.TYPE。这就意味着,MyAnnotation1 是来修饰"类、接口(包括注释类型)或枚举声明"的注解
定义 Annotation 时,@Target 可有可无。若有 @Target,则该 Annotation 只能用于它所指定的地方;若没有 @Target,则该 Annotation 可以用于任何地方。
@Retention(RetentionPolicy.RUNTIME)
之前说过,RententionPolicy是 Annotation 的保存策略属性,而@Retention的作用,就是指定Annotation的保存策略。
@Retention(RetentionPolicy.RUNTIME) 的意思就是指定该 Annotation 的策略是RetentionPolicy.RUNTIME。这就意味着,编译器会将该 Annotation 信息保留在 .class 文件中,并且能被JVM读取。
定义 Annotation 时,@Retention 可有可无。若没有 @Retention,则默认是RetentionPolicy.CLASS。
Java反射机制是在运行状态中(Runtime),可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。
这种运行状态动态获取信息以及动态调用对象的功能被称为Java语言的反射机制。
Java类加载器(Java ClassLoader) 是Java运行时环境JRE(Java Runtime Environment)的一部分,负责动态加载Java类到Java虚拟机的内存空间中。
Java默认有三种类加载器:BootStrapClassLoader、ExtensionClassLoader、AppClassLoader。
BootStrapClassLoader(引导启动类加载器):
嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负责加载JAVA_HOME/lib下的类库,引导启动类加载器无法被应用程序直接使用。
ExtensionClassLoader(扩展类加载器):
是用Java语言编写的,主要加载JAVA_HOME/lib/ext目录下的类库,它的父类加载器是BootStrapClassLoader
AppClassLoader(应用类加载器):
应用程序类加载器,负责加载应用程序classpath目录下的所有jar和class文件,它的父类加载器为ExtensionClassLoader
类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。学习类加载器时,掌握Java类派很重要。
双亲委派模型:如果一个类加载器收到了一个类加载请求,它不会自己去尝试加载这个类,而是把这个请求转交给父类加载器去完成。每一个层次的类加载器都是如此。因此所有的类加载请求都应该传递到最顶层的启动类加载器中,只有父类加载器反馈自己无法完成这个加载请求(在他的搜索范围内没有找到这个类)时,子类加载器才会尝试自己去加载,委派的好处就是避免有些类被重复加载
想要了解一个类,必须先要获取到该类的字节码文件对象,在Java中,每一个.class字节码文件被加载到内存后,其在内存中的表现形式就是Class类的一个对象
包名.类名.class
来得到该类的字节码文件对象对象名.getClass()
来得到该类的字节码文件对象Class.forName("包名+类名")
在运行时得到该类的字节码文件对象上述三种方式在调用时,如果类在内存中不存在,则会先通过类加载器的双清委派模型将类加载进内存。如果在内存中已经存在,则直接获取该类的字节码文件对象,不会重复加载,只会重复利用
特殊类的对象
int.class
Integer.TYPE
Integer.class
//第一种方式:通过包名.类名.class加载类(获取类的字节码文件的对象)
Class<Person> c1 = Person.class;
System.out.println(c1);
//第二种方式:通过类的对象获取类的信息,此时类信息肯定已经加载进内存了,所以不存在加载类,只是通过对象获取类的信息
Person person = new Person();
Class<Person> c2 = (Class<Person>)person.getClass();
System.out.println(c2);
//第三种方式:通过类的位置字符串来获取类的对象(重点)
Class<?> c3 = Class.forName("Demo.Person");
System.out.println(c3);
System.out.println(c1 == c2 && c1 ==c3); //true 证明类只加载了一次。三者指向的都是同一个对象
通过指定的参数类型,获取指定的单个构造方法
getConstructor(参数类型的class或数组)
获取构造方法数组
getContructors()
获取所有权限的单个构造方法
getDeclaredConstructor(参数类型的class或数组)
、
获取所有权限的构造方法数组
getDeclaredConstructors()
常用方法:
newInstance(Object param)
作用:调用这个构造方法,传入对应的参数,把对象创建处理
参数:是一个Object类型的可变参数,传递的参数顺序必须匹配构造方法中形参列表的顺序!!!
setAccessible(boolean flag)
作用:设置是否忽视权限检查,true代表忽视权限检查。
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//1.通过类名获取到该类的字节码文件对象
Class<?> c = Class.forName("Demo.Person");
//2.从该类的字节码对象中获取该类的无参构造方法
Constructor<?> c1 = c.getConstructor();
//3.通过无参构造方法创建对象
Object o = c1.newInstance();
//找到全参构造方法
Constructor<?> c2 = c.getConstructor(String.class, int.class);
//传参,并创建对象
Object o1 = c2.newInstance("李四", 1);
//获取私有的构造方法
Constructor<?> c3 = c.getDeclaredConstructor(String.class);
//设置忽略权限检查
c3.setAccessible(true);
//传参并创建对象
Object o2 = c3.newInstance("王五");
}
getMethod(String methodName, 参数类型.class)
根据方法名和参数列表的类型,得到一个方法对象(public 修饰)
getMethods()
得到一个类的所有方法
getDeclaredMethod(String methodName, 参数类型.class)
根据方法名和参数列表的类型,得到一个方法对象(除继承以外的所有权限的方法)
getDeclaredMethods()
得到一个类的所有方法(除了继承以外的所有权限的方法)
invoke(Object o,Object param)
执行方法的方法
参数1:要调用该方法的对象
参数2:要传递的参数列表
getName()
获取方法的方法名称
setAccessible(boolean flag)
如果flag为true 则表示忽略权限检查
public static void main(String[] args) throws Exception{
//加载类
Class<?> cla = Class.forName("Demo.Person");
//获取类的构造方法
Constructor<?> c = cla.getConstructor();
//创建对象
Object o = c.newInstance();
//获取类的方法(public修饰的方法)
Method setName = cla.getMethod("setName", String.class);
//private修饰的方法
Method setAge = cla.getDeclaredMethod("setAge", int.class);
setAge.setAccessible(true); //忽略权限检查
//参数1:哪个对象要执行setName方法,
//参数2:调用方法时传递的参数 0-n个
setName.invoke(o,"张三");
setAge.invoke(o,18);
}
getField(String filedName)
getFields()
getDeclaredField(String filedName)
getDeclaredFields()
常用方法:
get(Object o)
参数:要获取属性的对象
获取指定对象的此属性
set(Object o, Object value)
参数1:要设置属性的对象
参数2:要设置的值
设置指定对象的属性的值
getName()
获取属性的名称
setAccessible(boolean flag)
如果flag为true 则表示忽略访问权限检查!
public static void main(String[] args) throws Exception{
//加载类
Class<?> cla = Class.forName("Demo.Person");
//获取无参构造方法
Constructor<?> c = cla.getConstructor();
//创建对象
Object o = c.newInstance();
//通过类获取方法(public)
Field phone = cla.getField("phone");
phone.set(o,"123456");
//通过类获取方法(private)
Field name = cla.getDeclaredField("name");
name.setAccessible(true);
name.set(o,"李四");
}
Annotation[] annotations = Class/Field/Method.getAnnotations();
for (Annotation annotation : annotations01){
System.out.println(annotation);
}
注解类型 对象名 = c.getAnnotation(注解类型.class);
自定义注解:表注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Inherited
public @interface TableAnnotation {
/**
* 标注类对应的表格名称
* @return
*/
String value();
}
自定义注解:字段注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface ColumnAnnotation {
/**
* 描述列名
* @return
*/
String columnName();
/**
* 描述类型
* @return
*/
String type();
/**
* 描述长度
* @return
*/
String length();
}
JavaBean:Book类
@TableAnnotation("test_Book")
public class Book {
@ColumnAnnotation(columnName = "name",type = "varchar",length = "50")
private String name;
@ColumnAnnotation(columnName = "info",type = "varchar",length = "1000`")
private String info;
@ColumnAnnotation(columnName = "id",type = "int",length = "11")
private int id;
public Book() {
}
public Book(String name, String info, int id) {
this.name = name;
this.info = info;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", info='" + info + '\'' +
", id=" + id +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return id == book.id && Objects.equals(name, book.name) && Objects.equals(info, book.info);
}
@Override
public int hashCode() {
return Objects.hash(name, info, id);
}
}
测试类
public class Demo {
public static void main(String[] args) throws Exception{
//加载类
Class<?> c = Class.forName("Demo2.Book");
//通过Book类的字节码文件对象获取类上的注解对象
TableAnnotation ta = c.getAnnotation(TableAnnotation.class);
//通过注解对象获取其注解中设置的属性值
String value = ta.value();
//根据Book类的字节码文件对象获取Book类的所有权限的属性
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
//忽略权限检查
field.setAccessible(true);
//通过属性对象获取其注解对象
ColumnAnnotation ca = field.getAnnotation(ColumnAnnotation.class);
//通过注解对象获取对应的属性值
String length = ca.length();
String type = ca.type();
System.out.println(field.getName()+"属性,对应数据库中的字段:"+ca.columnName()+"数据类型:"+type+"长度:"+length);
}
}
}