2.4 Java类型
2.4.1 enum
enum实际上是一个final类型(继承于Enum)以及一些给类型的实例,而且可以为enum实例编写方法,从而每个enum实例赋予各自不同的行为。
Java中enum是静态常量,隐式地用static final修饰过,这里面包含了两层意思:
- 枚举型中的实例隐式地用static final修饰过;
- 枚举型作为某个类中的成员字段也隐式地用static final修饰过。
我们看一个代码事例:
public enum Light {Green, Yellow, Red}
编译后再反编译一下,我们看一下结果
public static final class Light extends Enum<Light> {
public static final Light Green = new Light();
public static final Light Yellow = new Light();
public static final Light Red = new Light();
private static final Light[] $VALUES;
public static Light[] values() {
reutrn (Ligth[]) $VALUES.clone();
}
public static Light valueOf(String string) {
return Enum.valueOf(Light.class, string);
}
private Ligth() {
super(string, n);
}
static {
$VALUES = new Light[] {Green, Yellow, Red};
}
}
首先,枚举型Light是个实实在在的类,继承自基类Enum<Light>,编译器偷偷加了static final修饰词。然后三个枚举实例GREEN,YELLOW,RED也确确实实是Light的实例,然而前面也加上了static final。构造方法也被修改成private。这种实例控制手段,在单例器模式中比较常见,所以枚举也是实现单例器的一种方法。然后编译器还偷偷合成了Light[]数组,一个values()方法,一个valueOf()方法。如果在Enum里定义一个相关方法,你还会看到一个匿名内部类:
public enum Light {
Green{public void show() {...}},
Yellow{public void show() {...}},
Red{public void show() {...}};
}
编译后反编译结果如下:
public static final class Light extends Enum<Light> {
public static final Light Green = new Light("Green", 0) {
public void show() {...}
};
public static final Light Yellow = new Light("Yellow", 0) {
public void show() {...}
};
public static final Light Red = new Light("Red", 0) {
public void show() {...}
};
...
}
总结
Enum中的常用方法:
- toString(等价于name)
- 静态方法valueOf(String),需要注意的是valueOf的参数是区分大小写的(会抛出RuntimeException)
- ordinal,返回该枚举元素在元素列表中的位置
- 静态方法values(),返回一个该枚举类的元素数组。
2.4.2 数组
数组本身也是对象(其是由JVM直接实现的,没有对应的Java源码文件),数组标识符是一个引用,其指向heap上的数组对象,而这个对象上保存着指向具体对象的引用(如果是基本类型,则为具体的值)。
数组是协变的,即如果类Base是类Sub的父类,那么Base[]就是Sub[]的父类。Arrays中有很多方法参数为Object[],可以接受String[],Integer[]类型的实参。而Java最开始没有泛型,只好使用多态来解决这个问题。
为什么数组可以设计成协变的,这是因为数组记得它内部元素的具体类型,并且会在运行时做类型检查(这也是不能创建泛型数组的原因,数组创建时必须知道确定类型)。注意和泛型的区别:泛型是编译期做类型检查。
Number[] nums1 = new Integer[10];
nums1[0] = 2.1; //runtime error
List<Integer> nums2 = new ArrayList<> ();
nums2.add(new Double(2.1)); //compile error
前者编译没有问题,但运行时会crash;而后者则无法编译通过。本着fail-fast原则,后者更好。
2.4.3 内部类
内部类分为三种:static成员类,普通成员类以及匿名类。
- static成员类,static成员类其实就是一个普通类,一般用来加强代码的内聚性。
- 普通成员类,普通成员类的每个实例都隐含着一个外围类的实例,所以在该类实例中可以直接调用外围类的方法与成员,同样在没有外围类实例的情况下,不可能创建该类实例。
- 匿名类,匿名类语法比较特殊,它不是外围类的一个成员,并不与其他成员一起声明,而是在使用时被声明和实例化。匿名类可以出现在代码中任何容许表达式存在的地方。Java8中当该匿名类只有一个方法时可以直接使用Lambda表达式。
需要注意的是:Java中的闭包只实现了capture-by-value,也就是说它捕获的局部变量都会拷贝一份到Lambda表达式的实体里,然后在Lambda表达式里要变也只能变自己的那份拷贝而无法影响外部原本的变量。所以Java不允许向捕获的变量赋值,而且可以捕获的也只有“效果上不可变”(effectively final)的参数/局部变量。但是Java只是不允许改变被Lambda表达式捕获的变量,并没有限制这些变量所指向的对象的状态能不能变。要从lambda表达式向外传值的常见方法就是用长度为1的数组:
String[] a = new String[1];
... ( () -> a[0] = "a" );
return a[0];
JDK中有些代码也是这么做的,这种做法可以叫做“手动boxing”。