11.1 枚举
枚举是一种数据类型,它是一系列具有名称的常量的集合。比如在数学中所学的集合:A={123},当使用这个集合时, 只能使用集合中的1、2、3 这3个元素,不是这3个元素的值就无法使用。 Java中的枚举是同样的道理,比如在程序中定义了一个性别枚举,里面只有两个值:男、女,那么在使用该枚举时,只能使用男和女这两个值,其他的任何值都是无法使用的。
枚举是一个被命名的整型常数的集合,用于声明一组带标识符的常数。枚举在曰常生活中很常见,例如一个人的性别只能是“男”或者“女”,一周的星期只能是 7 天中的一个等。类似这种当一个变量有几种固定可能的取值时,就可以将它定义为枚举类型。
enum WeekDay {
Mon("Monday"),Tue("Tuesday"),Wed("Wednesday"),Thu("Thursday"),Fri("Friday"),Sat("Saturday"),Sun("Sunday");
// 以上是枚举的成员,必须先定义,而且使用分号结束
private final String day;
private WeekDay(String day) {
this.day = day;
}
public static void printDay(int i) {
switch(i) {
case 1:
System.out.println(WeekDay.Mon);
break;
case 2:
System.out.println(WeekDay.Tue);
break;
case 3:
System.out.println(WeekDay.Wed);
break;
case 4:
System.out.println(WeekDay.Thu);
break;
case 5:
System.out.println(WeekDay.Fri);
break;
case 6:
System.out.println(WeekDay.Sat);
break;
case 7:
System.out.println(WeekDay.Sun);
break;
default:
System.out.println("wrong number!");
}
}
public String getDay() {
return day;
}
}
public static void main(String[] args) {
for(WeekDay day : WeekDay.values()) {
System.out.println(day+"====>" + day.getDay());
}
WeekDay.printDay(5);
}
1.使用枚举类型设置常量
以往设置常量,通常将常量放置在接口中,这这样在程序中就可以直接使用,并且该常量不能被修改,因为在接口中定义常量时,该常量的修饰行符为final与static。
例如,在项目中创建 Constants接口,在接口中定义常量的常规规方式。
public interface Constants {
public static final int Constants_A=1;
public static final int Constants_B=12;
}
在JDK 1.5版本中新增枚举类型后就逐渐取代了这种常量定义方式,因为通过使用枚举类型,可以赋予程序在编译时进行检查的功能。使用枚举类型定义常量的语法如下:
public enum Constants{
Constants A,
Constants B,
Constants C;
}
enum-modifiers enum enumname:enum-base {
enum-body,
}
enum-modifiers 表示枚举的修饰符主要包括 public、private 和 internal;enumname 表示声明的枚举名称;enum-base 表示基础类型;enum-body 表示枚举的成员,它是枚举类型的命名常数。
2 深入了解枚举类型
Java 中的每一个枚举都继承自 java.lang.Enum 类。当定义一个枚举类型时,每一个枚举类型成员都可以看作是 Enum 类的实例,这些枚举成员默认都被 final、public, static 修饰,当使用枚举类型成员时,直接使用枚举名称调用成员即可。
enum Signal {
// 定义一个枚举类型
GREEN,YELLOW,RED
}
public class TrafficLight {
Signal color = Signal.RED;
public void change() {
switch(color) {
case RED:
color = Signal.GREEN;
break;
case YELLOW:
color = Signal.RED;
break;
case GREEN:
color = Signal.YELLOW;
break;
}
}
}
public class TestEnum {
public enum Sex {
// 定义一个枚举
male,female;
}
public static void main(String[] args) {
compare(Sex.valueOf("male")); // 比较
}
public static void compare(Sex s) {
for(int i = 0;i < Sex.values().length;i++) {
System.out.println(s + "与" + Sex.values()[i] + "的比较结果是:" + s.compareTo(Sex.values()[i]));
}
}
}
枚举类型中的构造方法
在枚举类型中,可以添加构造方法,但是规定这个构造方法必须为 private 修饰符或者默认修饰符所修饰。枚举类型定义的构造方法语法如下:
public enum Constants2{
Constants_A("我是枚举成员A"),
Constants_B("我是枚举成员B"),
Constants_C("我是枚举成员C"),
Constants_D(3);
String description;
int i;
private Constants2(){ //定义默认构造方法
}
//定义带参数的构造方法,参数类型为字符串型
private Constants2(String description){
this.description=description;
private Constants2(int i){//定义带参数的构造方法,参数类型为整型
this.i=this.i+i;
}
}
从枚举类型构造方法的语法中可以看出,无论是无参构造方法还是有参构造方法,修饰权限都为private。
定义一个有参构造方法后,需要对枚举类型成员相应地使用该构造方法,如Constants_A(我是枚举成员A")和 Constants_D(3)语句,相应地使用了参数为 String 型和参数为 int 型的构造方法。然后可以在枚举类型中定义两个成员变量,在构造方法中为这两个成员变量赋值,这样就可以在枚举类型中定义该成员变量的getXXX()方法了。
3.使用枚举类型的优势
枚举类型声明提供了一种用户友好的变量定义方法,枚举了某种数据类型所有可能出现的值。总结枚举类型,它具有以下特点:
(1)类型安全。
(2)紧凑有效的数据定义。
(3)可以和程序其他部分完美交互。(4)运行效率高
enum Signal {
// 定义一个枚举类型
GREEN,YELLOW,RED;
}
public static void main(String[] args) {
for(int i = 0;i < Signal.values().length;i++) {
System.out.println("枚举成员:"+Signal.values()[i]);
}
}
EnumMap 与 EnumSet
为了更好地支持枚举类型,java.util 中添加了两个新类:EnumMap 和 EnumSet。使用它们可以更高效地操作枚举类型。
EnumMap 类
EnumMap 是专门为枚举类型量身定做的 Map 实现。虽然使用其他的 Map(如 HashMap)实现也能完成枚举类型实例到值的映射,但是使用 EnumMap 会更加高效。
HashMap 只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,所以 EnumMap 使用数组来存放与枚举类型对应的值,使得 EnumMap 的效率非常高。
numSet 类
for(Operation op:EnumSet.range(Operation.PLUS,Operation.MULTIPLY)) {
doSomeThing(op);
}
11.2泛型
Java 集合之所以被设计成这样,是因为集合的设计者不知道我们会用集合来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对象,只要求具有很好的通用性,但这样做带来如下两个问题:
集合对元素类型没有任何限制,这样可能引发一些问题。例如,想创建一个只能保存 Dog 对象的集合,但程序也可以轻易地将 Cat 对象“丢”进去,所以可能引发异常。
由于把对象“丢进”集合时,集合丢失了对象的状态信息,集合只知道它盛装的是 Object,因此取出集合元素后通常还需要进行强制类型转换。这种强制类型转换既增加了编程的复杂度,也可能引发 ClassCastException 异常。
泛型本质上是提供类型的“类型参数”,也就是参数化类型。我们可以为类、接口或方法指定一个类型参数,通过这个参数限制操作的数据类型,从而保证类型转换的绝对安全。
1 回顾向上转型“与”向下转型“
package eleven07;
public class Test {
private Object b; // 定义Object类型成员变量
public Object getB() { // 设置相应的getXXX()方法
return b;
}
public void setB(Object b) { // 设置相应的setXXX()方法
this.b = b;
}
public static void main(String[] args) {
Test t = new Test();
t.setB(new Boolean(true)); // 向上转型操作
System.out.println(t.getB());
t.setB(new Float(12.3));
Float f = (Float) (t.getB()); // 向下转型操作
System.out.println(f);
}
}
2.定义泛型类
Object类为最上层的父类,很多程序员为了使程序更为通用,设计程序时通常使传入的值与返回的值都以Object类型为主。当需要使用这些实例时,必须正确地将该实例转换为原来的类型,否则在运行时将会发生ClassCastException异常。
public class class_name<data_type1,data_type2,…>{}
3.泛型的常规用法
定义泛型类时声明多个类型
在定义泛型类时,可以声明多个类型。语法如下:
MutiOverClass<T1,T2>
MutiOverClass:泛型类名称
其中,T1 和 T2 为可能被定义的类型。这样在实例化指定类型的对象时就可以指定多个类型。
MutiOverClass<Boolean,Float>=new Mutid OverClass<Boolean,Float>();
定义泛型类时声明数组类型
定义泛型类时也可以声明数组类型,下面的实例中定义泛型时便声明了数组类型。
public class Test14 {
public static void main(String[] args) {
// 创建3个Book对象
Book book1 = new Book(1, "唐诗三百首", 8);
Book book2 = new Book(2, "小星星", 12);
Book book3 = new Book(3, "成语大全", 22);
Map<Integer, Book> books = new HashMap<Integer, Book>(); // 定义泛型 Map 集合
books.put(1001, book1); // 将第一个 Book 对象存储到 Map 中
books.put(1002, book2); // 将第二个 Book 对象存储到 Map 中
books.put(1003, book3); // 将第三个 Book 对象存储到 Map 中
System.out.println("泛型Map存储的图书信息如下:");
for (Integer id : books.keySet()) {
// 遍历键
System.out.print(id + "——");
System.out.println(books.get(id)); // 不需要类型转换
}
List<Book> bookList = new ArrayList<Book>(); // 定义泛型的 List 集合
bookList.add(book1);
bookList.add(book2);
bookList.add(book3);
System.out.println("泛型List存储的图书信息如下:");
for (int i = 0; i < bookList.size(); i++) {
System.out.println(bookList.get(i)); // 这里不需要类型转换
}
}
}
到目前为止,我们所使用的泛型都是应用于整个类上。泛型同样可以在类中包含参数化的方法,而方法所在的类可以是泛型类,也可以不是泛型类。也就是说,是否拥有泛型方法,与其所在的类是不是泛型没有关系。
[访问权限修饰符] [static] [final] <类型参数列表> 返回值类型 方法名([形式参数列表]
public static <T> List find(Class<T> cs,int userId){}
一般来说编写 Java 泛型方法,其返回值类型至少有一个参数类型应该是泛型,而且类型应该是一致的,如果只有返回值类型或参数类型之一使用了泛型,那么这个泛型方法的使用就被限制了。
public class Test16 {
public static <T> void List(T book) { // 定义泛型方法
if (book != null) {
System.out.println(book);
}
}
public static void main(String[] args) {
Book stu = new Book(1, "细学 Java 编程", 28);
List(stu); // 调用泛型方法
}
}
4 泛型的高级用法
泛型的用法非常灵活,除在集合、类和方法中使用外,本节将从三个方面介绍泛型的高级用法,包括限制泛型可用类型、使用类型通配符、继承泛型类和实现泛型接口。
1. 限制泛型可用类型
在 Java 中默认可以使用任何类型来实例化一个泛型类对象。
class 类名称<T extends anyClass>
其中,anyClass 指某个接口或类。使用泛型限制后,泛型类的类型必须实现或继承 anyClass 这个接口或类。无论 anyClass 是接口还是类,在进行泛型限制时都必须使用 extends 关键字。
package eleven;
import java.util.*;
public class LimitClass<T extends List> { // 限制泛型的类型
public static void main(String[] args) {
// 可以实例化已经实现List接口的类
LimitClass<ArrayList> l1 = new LimitClass<ArrayList>();
LimitClass<LinkedList> l2 = new LimitClass<LinkedList>();
// 这句是错误的,因为HashMap没有实现List()接口
LimitClass<HashMap> l3 = new LimitClass<HashMap>();
}
}
通过类型通配符的继承限制泛型类型
在泛型机制中,提供了类型通配符,其主要作用是在创建一个泛型类对象时,限制这个泛型类的类型,或者限制这个泛型类型必须继承某个接口或某个类(或其子类)。要声明这样一个对象可以使用“?”通配符,同时使用 extends 关键字来对泛型加以限制。
注:
通过对类型参数T 过对类型通配符实现继承限制泛型类型时,则在实例化时才进行限制。
使用泛型类型通配符的语法如下:
泛型类名称<? extends list> a=null;
其中,<? extends List>表示类型未知,当需要使用该泛型对象时,可以单独实例化。
package eleven11;
import java.util.*;
public class WildClass {
public static void main(String[] args) {
List<String> l1 = new ArrayList<String>(); // 创建一个ArrayList对象
l1.add("成员"); // 在集合中添加内容
List<?> l2 = l1; // 使用通配符
List<?> l3 = new LinkedList<Integer>();
System.out.println("l1:" + l1.get(0)); // 获取l1集合中第一个值
System.out.println("l2:" + l2.get(0)); // 获取l2集合中第一个值
l1.set(0, "成员改变"); // 没有使用通配符的对象调用set()方法
// l2.add("添加");// 使用通配符的对象不能调用add方法
// l2.set(0, "成员改变"); // 使用通配符的对象不能调用set()方法
// l3.add(1);
// l3.set(0, 1);
System.out.println("l1:" + l1.get(0)); // 可以使用l1的实例获取集合中的值
}
}
继承泛型类和实现泛型接口
定义为泛型的类和接口也可以被继承和实现。
public class FatherClass<T1>{}
public class SonClass<T1,T2,T3> extents FatherClass<T1>{}
如果要在 SonClass 类继承 FatherClass 类时保留父类的泛型类型,需要在继承时指定,否则直接使用 extends FatherClass 语句进行继承操作,此时 T1、T2 和 T3 都会自动变为 Object,所以一般情况下都将父类的泛型类型保留。
interface interface1<T1>{}
interface SubClass<T1,T2,T3> implements
Interface1<T2>{}
泛型总结
泛型方法使得该方法能够独立于类而产生变化。如果使用泛型方法可以取代类泛型化,那么就应该只使用泛型方法。另外,对一个 static 的方法而言,无法访问泛型类的类型参数。因此,如果 static 方法需要使用泛型能力,就必须使其成为泛型方法。
使用泛型需遵循以下原则。
(1)泛型的类型参数只能是类类型,不可以是简单类型,如A<int>这种泛型定义就是错误的。
(2)泛型的类型个数可以是多个。
(3)可以使用extends关键字限制泛型的类类型。(4)可以使用通配符限制泛型的类型。