JavaSE进阶篇【6】
2022年7月2日
第六部分 枚举类型与泛型
6.1 枚举类型
使用枚举类型,可以替代定义常量的方式,同时枚举类型还赋予程序在编译时的核查功能。
1. 使用枚举类型设置常量
设置常量时,我们通常将常量放置在接口中,这样在程序中就可以直接使用。该常量不能被修改,因为在接口中定义常量时,该常量的修饰符为 final 与 static。
枚举类型出现后,逐渐替代了这种常量定义方式。使用枚举类型定义常量的语法如下:
public enum Constants{
Constants_A,
Constants_B.
Constants_C
}
其中,enum 是定义枚举类型的关键字。当在程序中需要使用该常量的时候,可以用 Constants.Constants_A 来表示。如下面的例子:
package part6;
interface Constants{ //将常量放置在接口中
public static final int Constants_A = 1;
public static final int Constants_B = 12;
}
public class ConstantsTest {
enum Constants2{ //将常量放置在枚举类型中
Constants_A,Constants_B
}
//使用接口定义常量,根据常量的值做不同操作
public static void doit(int c) {
switch(c) {
case Constants.Constants_A:
System.out.println("doit() Constants_A");
break;
case Constants.Constants_B:
System.out.println("doit() Constants_B");
break;
}
}
//使用枚举类型定义常量,根据枚举对象做不同的操作
public static void doit2(Constants2 c) {
switch(c) {
case Constants_A:
System.out.println("doit2() Constants_A");
break;
case Constants_B:
System.out.println("doit2() Constants_B");
break;
}
}
public static void main(String[] args) {
ConstantsTest.doit(Constants.Constants_A); //使用接口中定义的常量
ConstantsTest.doit2(Constants2.Constants_A); //使用枚举类型中的常量
ConstantsTest.doit2(Constants2.Constants_B); //使用枚举类型中的常量
}
}
此外,枚举类型也可以在类的内部进行定义,如下述代码所示:
public class ConstantsTest2{
enum Constants2{
Constants_A,
Constants_B
}
}
这种形式类似于内部类形式,当编译该类时,除了 ConstantsTest,Class 外,还存在 ConstantsTest$1.class 与 ConstantsTest$2.class 文件。
2. 操作枚举类型的方法
- 操作枚举类型成员的方法
枚举类型较传统定义常量的方式,除了具有参数类型检测的优势以外,还具有其他方面的优势。
可以将一个枚举类型看作是一个类,它继承于 java.lang.Enum 类,当定义一个枚举类型时,每一个枚举类型成员都可以看作是枚举类型的一个实例,这些类型都默认被 final、public、static 修饰,所以当使用枚举类型成员时直接使用枚举类型名称调用枚举类型成员即可。枚举类型常用的方法有:
- values() 方法:例如 Constants2.values(),该方法可以将枚举类型成员以数组的形式返回,也可以通过该方法获取枚举类型的成员。其中 Constants2 为枚举类型名称
- valueOF() 方法:例如 Constants2.valueOf(“abc”),该方法可以实现将普通类型字符串转换为枚举实例,其中 Constants2 为枚举类型名称
- compareTo() 方法:例如 Constants_A.compareTo(Constants_B),该方法用于比较两个枚举对象在定义时的顺序,其返回结果若为正值,则代表方法中的参数在调用该方法的枚举对象之前;0 代表两个相互比较的枚举成员的位置相同;负值代表方法中的参数在调用该方法的枚举对象位置之后
- ordina;() 方法:例如 Constants_A.ordinal(),该方法用于得到枚举类型的位置索引,返回值为其索引位置
具体用法如下面的例子所示:
package part6;
import static java.lang.System.out;
public class EnumMethodTest {
enum Constants2{
Constants_A,Constants_B,Constants_C
}
public static void compare(Constants2 c) {
for(int i = 0;i<Constants2.values().length;i++) {
out.println(c+"于"+Constants2.values()[i]+"的比较结果为:"+c.compareTo(Constants2.values()[i]));
}
}
public static void main(String[] args) {
//用 values()方法获取枚举类型中的成员变量
for(int j=0;j<Constants2.values().length;j++) {
out.println("枚举了成员变量:"+Constants2.values()[j]);
}
//调用 compare()方法并使用 valuesOf()方法
compare(Constants2.valueOf("Constants_B"));
//用 ordinal()方法获取枚举对象的索引位置
for(int k=0;k<Constants2.values().length;k++) {
out.println(Constants2.values()[k]+"在枚举类型中的索引值为:"+Constants2.values()[k].ordinal());
}
}
}
运行本例子得到的结果如下图所示:
- 枚举类型中的构造方法
在枚举类型中,可以添加构造方法,但是规定这个构造方法必须为 private 修饰符所修饰。枚举类型定义的构造方法具体用法可以从下面例子看出:
package part6;
import static java.lang.System.out;
public class EnumIndexTest {
enum Constants2{
Constants_A("枚举类型成员A"),
Constants_B("枚举类型成员B"),
Constants_C("枚举类型成员C"),
Constants_D(3);
private String description;
private int i = 5;
private Constants2() {
//定义默认构造方法
}
private Constants2(String description) {
//定义带参数的构造方法,参数类型为 String
this.description = description;
}
private Constants2(int i) {
//定义带参数的构造方法,参数类型为 int
this.i =this.i+i;
}
public String getDescription() {
//获取 description的值
return description;
}
public int getI() {
//获得 i的值
return i;
}
}
public static void main(String[] args) {
for(int j=0;j<Constants2.values().length;j++) {
out.println(Constants2.values()[j]+"调用 getDescription() 方法为:"+Constants2.values()[j].getDescription());
}
out.println(Constants2.valueOf("Constants_D")+"调用 getI() 方法为:"+Constants2.valueOf("Constants_D").getI());
}
}
- 使用枚举类型的优势
枚举类型声明提供了一种用户友好的变量定义方法,枚举了某种数据类型所有可能出现的值。总结枚举类型,有如下特点:
- 类型安全
- 紧凑有效的数据定义
- 可以和程序其他部分完美交互
- 运行效率高
6.2 泛型
泛型实质上就是使程序员定义安全的类型,在没有出现泛型之前,Java 也提供了对 Object 的引用 “任意化” 操作,这种 “任意化” 操作就是对 Object 引用进行向下转型及向上转型操作,但在某些强制类型转换的错误也许不会被编译器捕捉,而在运行后出现异常,可见强制类型转换存在安全隐患,所以在此提供了泛型机制。
Object 类为最上层的父类,设计程序时通常使传入的值与返回的值都以 Object 类型为主。当需要使用这些实例时,必须正确地将该实例转化为原来的类型,否则在运行时将会出现 ClassCaseException 异常。
泛型机制的语法如下:
类名<T>
T代表一个类型的名称
如下面的定义泛型的例子
package part6;
public class OverClass<T> {
private T over;
public T getOver() {
return over;
}
public void setOver(T over) {
this.over = over;
}
public static void main(String[] args) {
OverClass<Boolean>over1 = new OverClass<Boolean>();
OverClass<Float>over2 = new OverClass<Float>();
over1.setOver(true);//不需要类型转换
over2.setOver(12.3f);
Boolean b = over1.getOver();
Float f = over2.getOver();
System.out.println(b);
System.out.println(f);
}
}
使用泛型这种形式将不会发生 ClassCaseException 异常,因为在编译器中就可以检查类型匹配是否正确。在定义泛型类型时。一般类型名称使用 T 来表达,而容器类的元素使用 E 来表达。
1. 泛型的常规用法
① 定义泛型类时声明多个类型
在定义泛型时,可以声明多个类型。语法如下:
MultiOverClass<T1,T2>
MutiOverClass:泛型类名称
其中 T1, T2 为可能被定义的类型。这样在实例化指定类型的对象时就可以指定多个类型。例如:
MutiOverClass<Boolean,Float>=new MutiOverClass<Boolean,Float>();
② 定义泛型类时声明数组类型
定义泛型时也可以声明数组类型,如下面的实例:
package part6;
public class ArrayClass<T> {
private T[] array;
public void setT(T[] array) {
this.array = array;
}
public T[] getT() {
return array;
}
public static void main(String[] args) {
ArrayClass<String>a = new ArrayClass<String>();
String[] array = {"Member1","Member2","Member3"};
a.setT(array);
for(int i=0;i<a.getT().length;i++) {
System.out.println(a.getT()[i]);
}
}
}
**注意:可以在使用泛型机制时声明一个数组,但是不可以使用泛型来建立数组的实例。**例如以下语法是被禁止的: private T[] array=new T[10];
③ 集合类声明容器的元素
可以使用 K(或 E) 和 V 两个字符代表容器中的键值和与键值相对应的具体的值。常用的被泛型化的集合类有:
集合类 | 泛型定义 |
---|---|
ArrayList | ArrayList<E> |
HashMap | HashMap<K,V> |
HashSet | HashSet<E> |
Vector | Vector<E> |
下面用一个例子来展示这些集合的用法。
package part6;
import java.util.*;
public class AnyClass {
public static void main(String[] args) {
//定义 ArrayList容器,设置容器内的值类型为 Integer
ArrayList<Integer>a=new ArrayList<Integer>();
a.add(1);
for(int i=0;i<a.size();i++) {
//根据容器的长度,循环显示容器内的值
System.out.println("获取 ArrayList 容器的值:"+a.get(i));
}
//定义 HashMap容器,设置容器内的值类型为 Integer与 String型
Map<Integer,String>m=new HashMap<Integer,String>();
for(int i=0;i<5;i++) {
m.put(i, "成员"+i);
}
for(int i=0;i<m.size();i++) {
//根据键名获取键值
System.out.println("获取 Map 容器的值:"+m.get(i));
}
//定义 Vector容器,设置容器内的值类型为 String型
Vector<String>v =new Vector<String>();
for(int i=0;i<5;i++) {
v.addElement("成员"+i);
}
for(int i=0;i<v.size();i++) {
//根据键名获取键值
System.out.println("获取 Vector 容器的值:"+v.get(i));
}
}
}
运行结果如下图所示:
2. 泛型的高级用法
泛型的高级用法包括限制泛型可用类型和使用类型通配符等。
① 限制泛型可用类型
默认可以使用任何类型来实例化一个泛型类对象,但 Java 中也对泛型类实例作了限制。语法如下:
class 类名称<T extends ang Class>
其中,anyClass 是指某个接口或类。
使用泛型限制类后,泛型类的类型必须实现或继承了 anyClass 这个接口或类。无论 anyClass 是接口还是类,在进行泛型限制时都必须使用 extends 关键字。
当没有使用 extends 关键字时,默认 Object 类下的所有子类都可以实例化泛型类型对象。
例如下列语句:
public class LimitClass<T extends List>{
//…
}
在该语句下,对泛型进行了限制,设置泛型类型实现必须实现 List 接口。例如,ArrayList 和 LinkedList 都实现了 List 接口,而 HashMap 没有实现 List 接口,所以在这里不能实例化 HashMap 类型的泛型对象。
② 使用类型通配符
在泛型机制中,提供了类型通配符,其主要作用实在创建一个泛型类对象时限制这个泛型类的类型实现或继承某个接口或类的子类。要声明这样一个对象可用使用 “?” 通配符来表示,同时使用 extends 关键字来对泛型加以限制。
使用泛型类型通配符的语法如下:
泛型类名称<? extends LKist> a=null;
其中,<? extends List> 表示类型未知,当需要使用该泛型对象时,可用单独实例化。此外,这个语句还要求设置泛型类型实现必须实现 List 接口。
除了实例化一个限制泛型类型的实例之外,还可以将该实例放置在方法的参数中。如:
public void doSomething(A<? extends List> a){
}
在上述代码中,定义方式有效地限制传入 doSomething() 方法的参数类型。如果使用 A<?> 这种形式实例化泛型类对象,则默认表示可以将 A 指定为实例化 Object 及以下的子类类型,
注:泛型类型限制除了可以向下限制之外,还可以进行向上限制,只要在定义时使用 super 关键字替换 extends 关键字即可。
③ 继承泛型类与实现泛型接口
定义为泛型的类和接口也可以被继承与实现。继承泛型类同样也遵循 extends 关键字,而实现泛型接口,同样也采取 implements 关键字。
- 泛型的类型参数只能是类类型,不可以时简单类型
- 泛型的类型个数可以是多个
- 可以使用 extends 关键字限制泛型的类型
- 可以使用通配符限制泛型的类型