枚举和注解
枚举
- 定义解释
枚举是在java1.5之后加入进来的。
枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。本质就是,一个类里定义几个静态变量,每个变量都是这个类的实例。 - 怎么使用
简单的使用:使用关键字enum
public enum EnumFruit {
APPLE, ORANGE, BANANA, WATERMELON
}
然后使用
public class MainEnum {
private static String TAG = "MainEnum";
public static <T> void println(T msg) {
System.out.println("MainEnum===" + msg.toString());
}
public static void main(String[] args) {
println(EnumFruit.APPLE.name());
// EnumMap<EnumFruit, Integer> enumMap = new EnumMap<>(EnumFruit.class);
// enumMap.put(EnumFruit.ORANGE, 56);
// println(enumMap.get(EnumFruit.ORANGE));
}
}
运行结果:
这个就是最简单的用法了。
高级一点的用法就是增加字段如:
public enum EnumFruit {
APPLE("苹果", 10), ORANGE("橘子", 12), BANANA("香蕉", 15), WATERMELON("西瓜", 18);
private EnumFruit(String type, int price) {
this.type = type;
this.price = price;
}
private String type;
private int price;
public String getType() {
return type;
}
public int getPirce() {
return price;
}
}
给每个枚举变量都增加了描述和价格;然后增加了构造函数 和对应的获取方法。注意这里的构造函数为私有的,而且枚举变量增加了分号;结束,所以外界无法通过new来创建这个枚举对象。
测试:
public class MainEnum {
private static String TAG = "MainEnum";
public static <T> void println(T msg) {
System.out.println("MainEnum===" + msg.toString());
}
public static void main(String[] args) {
println(EnumFruit.APPLE.getPirce());
// EnumMap<EnumFruit, Integer> enumMap = new EnumMap<>(EnumFruit.class);
// enumMap.put(EnumFruit.ORANGE, 56);
// println(enumMap.get(EnumFruit.ORANGE));
}
}
这里我们获取苹果的价格,运行结果:
可以看到和我们定义的价格一样。
EnumMap的使用 这个其实和HashMap 类似。但是它是为了使用枚举 量身定做的。用法:
public static <T> void println(T msg) {
System.out.println("MainEnum===" + msg.toString());
}
public static void main(String[] args) {
// println(EnumFruit.APPLE.getPirce());
EnumMap<EnumFruit, Integer> enumMap = new EnumMap<>(EnumFruit.class);
enumMap.put(EnumFruit.ORANGE, 56);
println(enumMap.get(EnumFruit.ORANGE));
}
而如果我们使用普通的HashMap他是不能定义key为枚举的只能如下:
public static <T> void println(T msg) {
System.out.println("MainEnum===" + msg.toString());
}
public static void main(String[] args) {
// println(EnumFruit.APPLE.getPirce());
EnumMap<EnumFruit, Integer> enumMap = new EnumMap<>(EnumFruit.class);
enumMap.put(EnumFruit.ORANGE, 56);
println(enumMap.get(EnumFruit.ORANGE));
HashMap<String,Integer> hashMap = new HashMap<>();
hashMap.put(EnumFruit.BANANA.getType(), 13);
println(hashMap.get(EnumFruit.BANANA.getType()));
}
EnumMap 只能接收同一枚举类型的实例作为键值且不能为null,由于枚举类型实例的数量相对固定并且有限,所以EnumMap使用数组来存放与枚举类型对应的值,毕竟数组是一段连续的内存空间,根据程序局部性原理,效率会相当高。
HashMap我们知道是数组存储数据,动态扩容。但是EnumMap 的容量其实是固定的,因为枚举数量是固定的。
枚举与switch
java 1.7后switch也对字符串进行了支持。看一下switch与枚举类型的使用:
public static void main(String[] args) {
EnumFruit enumFruit = EnumFruit.APPLE;
switch (enumFruit) {
case APPLE:
break;
case ORANGE:
break;
case BANANA:
break;
case WATERMELON:
break;
default:
break;
}
}
枚举与单例
单例模式网上有6-7中写法,除了 枚举方式外, 都有两个致命的缺点, 不能完全保证单例在jvm中保持唯一性.
反射创建单例对象
解决方案 : 在构造上述中判断,当多于一个实例时,再调用构造函数,直接报错.
反序列化时创建对象
解决方案 : 使用readResolve()方法来避免此事发生.
这两种缺点虽然都有方式解决,但是不免有些繁琐.
枚举类天生有这些特性.而且实现单例相当简单.
public enum EnumSingle {
INSTANCE;
public void sayHello() {
System.out.println("Hello World");
}
}
使用:
public static void main(String[] args) {
EnumSingle.INSTANCE.sayHello();
}
所以,枚举实现的单例,可以说是最完美和简洁的单例了.推荐大家使用这种方式创建单例.
但是,枚举类的装载和初始化时会有时间和空间的成本. 它的实现比其他方式需要更多的内存空间。而且枚举类不能再去继承其他的类,而且也不能调用构造函数传递参数。
所以在Android这种受资源约束的设备中尽量避免使用枚举单例,而选择 双重检查锁(DCL)和静态内部类的方式实现单例.
enum类中定义抽象方法
与常规抽象类一样,enum类允许我们为其定义抽象方法,然后使每个枚举实例都实现该方法,以便产生不同的行为方式:
public enum EnumFruit {
APPLE {
@Override
public String getShape() {
return "椭圆";
}
},
ORANGE {
@Override
public String getShape() {
return "哈哈";
}
},
BANANA {
@Override
public String getShape() {
return "长条";
}
},
WATERMELON {
@Override
public String getShape() {
return "圆形";
}
};
public abstract String getShape();
}
测试:
public static <T> void println(T msg) {
System.out.println("MainEnum===" + msg.toString());
}
public static void main(String[] args) {
println(EnumFruit.BANANA.getShape());
}
结果:
其实这个东西意义不大,因为这个抽象方法并不能在外面实现。和传递
enum类与接口
由于Java单继承的原因,enum类并不能再继承其它类,但并不妨碍它实现接口,因此enum类同样是可以实现多接口的,如下就是管理不同类别:
public interface EnumFruit {
enum Appetizer implements EnumFruit {
SALAD, SOUP, SPRING_ROLLS;
}
enum MainCourse implements EnumFruit {
LASAGNE, BURRITO, PAD_THAI, LENTILS, HUMMOUS, VINDALOO;
}
enum Dessert implements EnumFruit {
TIRAMISU, GELATO, BLACK_FOREST_CAKE, FRUIT, CREME_CARAMEL;
}
enum Coffee implements EnumFruit {
BLACK_COFFEE, DECAF_COFFEE, ESPRESSO, LATTE, CAPPUCCINO, TEA, HERB_TEA;
}
}
使用:
public static void main(String[] args) {
EnumFruit food = Appetizer.SALAD;
EnumFruit food1 = MainCourse.LASAGNE;
EnumFruit food2 = Dessert.GELATO;
EnumFruit food3 = Coffee.CAPPUCCINO;
}
枚举基本就这些用法了。
- 优点缺点
优点:
1 增强代码可读性
2 去除equals两者判断 由于常量值地址唯一,使用枚举可以直接通过“==”进行两个值之间的对比,性能会有所提高。
3 枚举更加安全-因为类型数量已经确定了,不能反射。
4 Switch语句优势
缺点
1 占用内存多 比起 定义 静态常量
2 增加学习成本 - 原理源码
终于到了源码解析阶段。首先看一个最简单的枚举,就是开头写的:
public enum EnumFruit {
APPLE, ORANGE, BANANA, WATERMELON
}
我们使用xjad 反编译这个类:
package com.zh.t_enum;
public final class EnumFruit extends Enum
{
public static final EnumFruit APPLE;
public static final EnumFruit ORANGE;
public static final EnumFruit BANANA;
public static final EnumFruit WATERMELON;
private static final EnumFruit ENUM$VALUES[];
private EnumFruit(String s, int i)
{
super(s, i);
}
public static EnumFruit[] values()
{
EnumFruit aenumfruit[];
int i;
EnumFruit aenumfruit1[];
System.arraycopy(aenumfruit = ENUM$VALUES, 0, aenumfruit1 = new EnumFruit[i = aenumfruit.length], 0, i);
return aenumfruit1;
}
public static EnumFruit valueOf(String s)
{
return (EnumFruit)Enum.valueOf(com/zh/t_enum/EnumFruit, s);
}
static
{
APPLE = new EnumFruit("APPLE", 0);
ORANGE = new EnumFruit("ORANGE", 1);
BANANA = new EnumFruit("BANANA", 2);
WATERMELON = new EnumFruit("WATERMELON", 3);
ENUM$VALUES = (new EnumFruit[] {
APPLE, ORANGE, BANANA, WATERMELON
});
}
}
这下明白了吧
1 枚举类在编译的时候 就会变成一个正常的class类。
2 而且是final修饰不能被继承,同时继承了Enum抽象类。
3 我们定义的枚举变量其实是 类变量
看看Enum抽象类结构:
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
private final String name;
public final String name() {
return name;
}
private final int ordinal;
public final int ordinal() {
return ordinal;
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public String toString() {
return name;
}
public final boolean equals(Object other) {
return this==other;
}
public final int hashCode() {
return super.hashCode();
}
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
protected final void finalize() { }
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
}
Enum抽象类中:
1 定义了名称和顺序
2 比较大小 是通过 顺序关键字
3 禁止反序列化
- 使用情景
1 单例
2 有限的集合表示
3 看自己的取舍 - 总结
1.枚举并不难,编译后,它也只是个普通的final 类 继承了Enum。
2.定义的枚举变量 就是 类变量-所以它占用的内存会高
3.枚举单例是最好安全和简洁的。
注解
- 定义解释
1.5之后引入
注解可以理解为标签,就是对某个事物的解释。标准的定义如下:
Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。
元注解
可以理解为 注解的注解,是用来描述注解的。目前一共有5中:
@Retention
Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
它的取值如下:
RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
@Documented
顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。
@Target
Target 是目标的意思,@Target 指定了注解运用的地方。
ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
@Inherited
Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
@Repeatable
Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。
注解的属性
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
赋值的方式是在注解的括号内以 value="" 形式,多个属性之前用 ,隔开。
需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。
注解中属性可以有默认值,默认值需要用 default 关键值指定。
- 怎么使用
注解的声明用interface 和@ 如:
@Retention(RetentionPolicy.RUNTIME)
@Target(TYPE)
public @interface Describe {
public String type();
public int id() default -1;
}
这里我就定义了一个注解。这个注解保留到运行期间就是 虚拟机,目标是给类 接口 枚举使用。还定义了2个属性。String 类型, int id
使用:
@Describe(type = "Hello", id = 55)
public class Apple {
private int price;
private String name;
public Apple() {
}
public Apple(String name, int price) {
this.price = price;
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void call() {
System.out.println("苹果是:" + name + ",价格是:" + price);
}
@Override
public String toString() {
return "Apple [price=" + price + ", name=" + name + "]";
}
}
定义一个苹果类 使用了我们自定义的注解。
测试:
public static <T> void println(T msg) {
System.out.println("MainAnnotation====" + msg.toString());
}
public static void main(String[] args) {
Apple apple = new Apple("辣鸡", 78);
apple.call();
Describe describe = Apple.class.getAnnotation(Describe.class);
println("describe:" + describe.getClass().getName());
println("id:" + describe.id());
println("type:" + describe.type());
}
运行结果:
注解的使用必须要用到反射,所以需要提前掌握反射的知识。
可能会说这个还是没什么用啊。这给类的注解用处不大,但是我们可以标记给方法。可以实现 有了这个注解的方法就被调用。我们修改下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Describe {
}
给方法使用去掉了 变量。
使用:
public class Apple {
@Describe
public void call() {
System.out.println("我是苹果,价格是:10");
}
@Describe
public void say() {
System.out.println("我是香蕉,价格是:5");
}
}
简化了Apple。call方法添加了注解。
测试:
public static void main(String[] args) {
Apple apple = new Apple();
// 获取Class类对象
Class<?> appleClass = apple.getClass();
// 获取所有的方法
Method[] declaredMethods = appleClass.getDeclaredMethods();
for (int i = 0; i < declaredMethods.length; i++) {
// 获取方法
Method method = declaredMethods[i];
// 此方法有被Describe注解修饰
boolean isAnnotation = method.isAnnotationPresent(Describe.class);
if (isAnnotation) {
try {
// 调用方法,invoke方法有2个参数,1是调用这个方法的对象,因为方法不能自己调用自己,1是这个方法的参数
method.invoke(appleClass.newInstance());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
}
运行结果:
我们去掉一个注解:
public class Apple {
@Describe
public void call() {
System.out.println("我是苹果,价格是:10");
}
public void say() {
System.out.println("我是香蕉,价格是:5");
}
}
再运行:
注解的方法就差不多了,还是比较简单的,调用的都是系统提供好的API,就是使用的时候需要用到反射 去获取注解。
- 优点缺点
优点:
1 简洁
缺点
1 不好理解 - 原理源码
回到我们的注解Descriable
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Describe {
}
我们反编译一下看看:
看到编译后,我们定义的注解 其实就是个接口,继承了Annotation 接口
这就没了? 当然不是其实 编译器会生成一个代理类实现了整个接口,要想看到代理类需要先调用一句:
System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);
如:
它就会在根目录下生成com目录里面就有动态生成的代理类:
打开看看:
package com.sun.proxy;
import com.zh.annotation.Describe;
import java.lang.reflect.*;
public final class $Proxy1 extends Proxy
implements Describe
{
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m5;
private static Method m4;
private static Method m0;
public $Proxy1(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String name()
{
try
{
return (String)super.h.invoke(this, m3, null);
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final Class annotationType()
{
try
{
return (Class)super.h.invoke(this, m5, null);
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int id()
{
try
{
return ((Integer)super.h.invoke(this, m4, null)).intValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m3 = Class.forName("com.zh.annotation.Describe").getMethod("name", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m5 = Class.forName("com.zh.annotation.Describe").getMethod("annotationType", new Class[0]);
m4 = Class.forName("com.zh.annotation.Describe").getMethod("id", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
}
catch (NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch (ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
可以看到 这个代理类 继承了代理类, 实现了我们的注解接口。里面又是反射,调用了注解定义的方法。
跟踪一下 name()方法的调用流程.
构造函数需要传入
这就是为什么动态代理实现的时候 还是需要一个类去实现InvocationHandler接口。
第一个参数是动态代理的实例,第二个方法对象,第三是吃方法参数
重点看这一句
h是 系统自己实现的: AnnotationInvocationHandler
看下这个m3的实现:
负责创建代理对象AnnotationInvocationHandler, 其将变量从常量池中取出并创建map, 进而创建代理对象, 这个类就是 AnnotationParser。
我们的注解接口真在实现在哪 不得而知,后续再深入
- 使用情景
1 APT使用
2 框架使用-dagger2 bufferknife rxjava 等等
3 单元测试
4 你想用到的地方 - 总结
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。