详解Java中的 枚举与泛型

时间:2021-12-31 06:10:16

详解Java中的 枚举与泛型

一:首先从枚举开始说起

枚举类型是JDK5.0的新特征。Sun引进了一个全新的关键字enum来定义一个枚举类。下面就是一个典型枚举类型的定义:

?
1
2
3
public enum Color{
RED,BLUE,BLACK,YELLOW,GREEN
}

   显然,enum很像特殊的class,实际上enum声明定义的类型就是一个类。 而这些类都是类库中Enum类的子类(Java.lang.Enum)。它们继承了这个Enum中的许多有用的方法。我们对代码编译之后发现,编译器将 enum类型单独编译成了一个字节码文件:Color.class。

Color字节码代码

?
1
2
3
4
5
6
7
8
9
final enum hr.test.Color {
// 所有的枚举值都是类静态常量
public static final enum hr.test.Color RED;
public static final enum hr.test.Color BLUE;
public static final enum hr.test.Color BLACK;
public static final enum hr.test.Color YELLOW;
public static final enum hr.test.Color GREEN;
private static final synthetic hr.test.Color[] ENUM$VALUES;
}

下面我们就详细介绍enum定义的枚举类的特征及其用法。(后面均用Color举例)

1、Color枚举类就是class,而且是一个不可以被继承的final类。其枚举值(RED,BLUE.。.)都是Color类型的类静态常量, 我们可以通过下面的方式来得到Color枚举类的一个实例:

?
1
Color c=Color.RED;

注意:这些枚举值都是public static final的,也就是我们经常所定义的常量方式,因此枚举类中的枚举值最好全部大写。

2、即然枚举类是class,当然在枚举类型中有构造器,方法和数据域。但是,枚举类的构造器有很大的不同:

(1) 构造器只是在构造枚举值的时候被调用。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Color{
RED(25500),BLUE(00255),BLACK(000),YELLOW(2552550),GREEN(02550);
//构造枚举值,比如RED(255,0,0)
private Color(int rv,int gv,int bv){
this.redValue=rv;
this.greenValue=gv;
this.blueValue=bv;
}
public String toString(){ //覆盖了父类Enum的toString()
return super.toString()+“(”+redValue+“,”+greenValue+“,”+blueValue+“)”;
}
private int redValue; //自定义数据域,private为了封装。
private int greenValue;
private int blueValue;
}

  (2) 构造器只能私有private,绝对不允许有public构造器。 这样可以保证外部代码无法新构造枚举类的实例。这也是完全符合情理的,因为我们知道枚举值是public static final的常量而已。 但枚举类的方法和数据域可以允许外部访问。

?
1
2
3
4
5
6
public static void main(String args[])
{
// Color colors=new Color(100,200,300); //wrong
Color color=Color.RED;
System.out.println(color); // 调用了toString()方法
}

3、所有枚举类都继承了Enum的方法,下面我们详细介绍这些方法。

(1) ordinal()方法: 返回枚举值在枚举类种的顺序。这个顺序根据枚举值声明的顺序而定。

?
1
2
Color.RED.ordinal(); //返回结果:0
Color.BLUE.ordinal(); //返回结果:1

(2) compareTo()方法: Enum实现了java.lang.Comparable接口,因此可以比较象与指定对象的顺序。Enum中的compareTo返回的是两个枚举值的顺 序之差。当然,前提是两个枚举值必须属于同一个枚举类,否则会抛出ClassCastException()异常。(具体可见源代码)

?
1
Color.RED.compareTo(Color.BLUE); //返回结果 -1

(3) values()方法: 静态方法,返回一个包含全部枚举值的数组。

?
1
2
3
4
Color[] colors=Color.values();
for(Color c:colors){
System.out.print(c+“,”);
}//返回结果:RED,BLUE,BLACK YELLOW,GREEN,

(4) toString()方法: 返回枚举常量的名称。

?
1
2
Color c=Color.RED;
System.out.println(c);//返回结果: RED

(5) valueOf()方法: 这个方法和toString方法是相对应的,返回带指定名称的指定枚举类型的枚举常量。

?
1
Color.valueOf(“BLUE”); //返回结果: Color.BLUE

(6) equals()方法: 比较两个枚举类对象的引用。

?
1
2
3
4
//JDK源代码:
public final boolean equals(Object other) {
return this==other;
}

4、枚举类可以在switch语句中使用。

?
1
2
3
4
5
6
Color color=Color.RED;
switch(color){
case RED: System.out.println(“it‘s red”);break;
case BLUE: System.out.println(“it's blue”);break;
case BLACK: System.out.println(“it‘s blue”);break;
}

二:然后看泛型

泛型(Generic type 或者generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。

1.泛型的好处:

1)类型安全。泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
2)·消除强制类型转换。泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。 尽管减少强制类型转换可以降低使用泛型类的代码的罗嗦程度,但是声明泛型变量会带来相应的罗嗦
3)· 潜在的性能收益。泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的JVM 的优化带来可能。

2.类型参数:

在定义泛型类或声明泛型类的变量时,使用尖括号来指定形式类型参数。形式类型参数与实际类型参数之间的关系类似于形式方法参数与实际方法参数之间的关系,只是类型参数表示类型,而不是表示值。

泛型类中的类型参数几乎可以用于任何可以使用类名的地方。例如,下面是java.util.Map接口的定义的摘录:

?
1
2
3
4
public interface Map<K, V> {
public void put(K key, V value);
public V get(K key);
}

3.泛型不是协变的

关于泛型的混淆,一个常见的来源就是假设它们像数组一样是协变的。其实它们不是协变的。List<Object>不是List<String>的父类型。

如果 A 扩展 B,那么 A 的数组也是 B 的数组,并且完全可以在需要B[]的地方使用A[]:

?
1
2
Integer[] intArray = new Integer[10];
Number[] numberArray = intArray;

上面的代码是有效的,因为一个Integer是一个Number,因而一个Integer数组是一个Number数组。但是对于泛型来说则不然。

下面的代码是无效的

?
1
2
List<Integer> intList = new ArrayList<Integer>();
List<Number> numberList = intList; // invalid

4.泛型中的类型通配符

假设您具有该方法:

?
1
2
3
4
void printList(List l) {
for (Object o : l)
System.out.println(o);
}

上面的代码在 JDK 5.0 上编译通过,但是如果试图用List<Integer>调用它,则会得到警告。出现警告是因为,您将泛型 (List<Integer>)传递给一个只承诺将它当作List(所谓的原始类型)的方法,这将破坏使用泛型的类型安全。

如果试图编写像下面这样的方法,那么将会怎么样?

?
1
2
3
4
void printList(List<Object> l) {
for (Object o : l)
System.out.println(o);
}

它仍然不会通过编译,因为一个List<Integer>不是一个List<Object>(正如前一屏泛型不是协变的 中所学的)。这才真正烦人——现在您的泛型版本还没有普通的非泛型版本有用! 解决方案是使用类型通配符:

?
1
2
3
4
void printList(List<?> l) {
for (Object o : l)
System.out.println(o);
}

上面代码中的问号是一个类型通配符。它读作“问号”。List<?>是任何泛型List的父类型,所以您完全可以将 List<Object>、List<Integer>或 List<List<List<Flutzpah>>>传递给printList()。

5.泛型方法

(在类型参数 一节中)您已经看到,通过在类的定义中添加一个形式类型参数列表,可以将类泛型化。方法也可以被泛型化,不管它们定义在其中的类是不是泛型化的。

泛型类在多个方法签名间实施类型约束。在List<V>中,类型参数V出现在get()、add()、contains()等方法的签名中。 当创建一个Map<K, V>类型的变量时,您就在方法之间宣称一个类型约束。您传递给add()的值将与get()返回的值的类型相同。
类似地,之所以声明泛型方法,一般是因为您想要在该方法的多个参数之间宣称一个类型约束。例如,下面代码中的ifThenElse()方法,根据它的第一个参数的布尔值,它将返回第二个或第三个参数:

?
1
2
3
public <T> T ifThenElse(boolean b, T first, T second) {
return b ? first : second;
}

为什么您选择使用泛型方法,而不是将类型T添加到类定义呢?(至少)有两种情况应该这样做:

* 当泛型方法是静态的时,这种情况下不能使用类类型参数。

* 当 T 上的类型约束对于方法真正是局部的时,这意味着没有在相同类的另一个 方法签名中使用相同 类型 T 的约束。通过使得泛型方法的类型参数对于方法是局部的,可以简化封闭类型的签名。

有限制类型

在前一屏泛型方法 的例子中,类型参数V是无约束的或无限制的类型。有时在还没有完全指定类型参数时,需要对类型参数指定附加的约束。

考虑例子Matrix类,它使用类型参数V,该参数由Number类来限制:

?
1
public class Matrix<V extends Number> { ... }

编译器允许您创建Matrix<Integer>或Matrix<Float>类型的变量,但是如果您试图定义 Matrix<String>类型的变量,则会出现错误。类型参数V被判断为由Number限制。在没有类型限制时,假设类型参数由 Object限制。这就是为什么前一屏泛型方法 中的例子,允许List.get()在List<?>上调用时返回Object,即使编译器不知道类型参数V的类型。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!