枚举类型与泛型

枚举类型与泛型

枚举类型

使用枚举类型设置常量

设置常量是,我们通常将常量放置在接口中,这样在程序中就可以直接使用。该常量不能被修改,因为在接口中定义常量时,该常量的修饰符为 final 与 static。常规定义常量的代码如下:

public interface Constants {
    public static final Constants_A = 1;
    public static final Constants_B = 2;
}

枚举出现后,逐渐取代了上述常量定义方式。使用枚举类型定义常量的语法如下:

public enum Constants {
    CONSTANTS_A,CONSTANTS_B;
}

其中, enum 是定义枚举类型的关键字。当需要在程序中使用该常量是,可以使用 Constants.CONSTANTS_A 来表示。

分别创建四季的接口常量和枚举,比较两者的使用场景

public interface SeasonInterface {
    int SPRING = 1;
    int SUMMER = 2;
    int AUTUMN = 3;
    int WINTER = 4;
}
public enum SeasonEnum {
    // 春季 夏季 秋季 冬季
    AUTUMN, SPRING, SUMMER, WINTER
}
public class SeasonDemo {
    public static void printSeason1(int season) {
        switch (season) {
            case SeasonInterface.SPRING:
                System.out.println("这是春季");break;
            case SeasonInterface.SUMMER:
                System.out.println("这是夏季");break;
            case SeasonInterface.AUTUMN:
                System.out.println("这是秋季");break;
            case SeasonInterface.WINTER:
                System.out.println("这是冬季");break;
            default:
                System.out.println("这不是四季的常量值");
        }
    }

    public static void printSeason2(SeasonEnum season) {
        switch (season) {
            case SPRING:
                System.out.println("这是春季");break;
            case SUMMER:
                System.out.println("这是夏季");break;
            case AUTUMN:
                System.out.println("这是秋季");break;
            case WINTER:
                System.out.println("这是冬季");break;
            default:
                System.out.println("错误");
        }
    }

    public static void main(String[] args) {
        // 使用接口常量作为参数
        printSeason1(SeasonInterface.SPRING);
        // 可以使用数字做出参数
        printSeason1(3);
        // 使用接口常量值以外的值“冒充”常量
        printSeason1(-1);
        // 使用枚举做参数,只能用枚举中有的值,无法"冒充"
        printSeason2(SeasonEnum.WINTER);
    }
}

运行结果:

这是春季
这是秋季
这不是四季的常量值
这是冬季

深入了解枚举类型

枚举类型比较传统定义常量的方式,除具有参数类型检测的优势外,还具有其他方面的优势。

用户可以将一个枚举类型看作一个类,它继承与 java.lang.Enum 类,当定义一个枚举类型时,每一个枚举类型成员都可以看作是枚举类型的一个实例,这些枚举类型成员都默认被 final 、 public 、 static 修饰,所以当使用枚举类型成员时,直接使用枚举类型名称调用枚举类型成员即可。

由于枚举类型对象继承与 java.lang.Enum 类,所以该类中一些操作枚举类型的方法都可以应用到枚举类型中。

方法 具体含义 使用方法 举例
values() 该方法可以将枚举类型成员以数组的形式返回 枚举类型名称.value() Constants2.values()
valueOf() 该方法可以实现将普通字符串转换为枚举实例 枚举类型名称.valueOf() Constants2.valueOf()
compareTo() 该方法用于比较两个枚举对象在定义时的顺序 枚举对象.compareTo() Constants_A.compareTo(Constants_B)
ordinal() 该方法用于得到枚举成员的位置索引 枚举对象.ordinal() Constants_A.ordinal()

1.values() 方法

枚举类型实例包含一个 values() 方法,该方法将枚举中所有的枚举值以数组的形式返回。接着用到上面定义好的枚举

public class ShowEnum {
    public static void main(String[] args) {
        SeasonEnum[] es = SeasonEnum.values();
        for (SeasonEnum e : es) {
            System.out.println("枚举常量:" + e);
        }
    }
}

运行结果:

枚举常量:AUTUMN
枚举常量:SPRING
枚举常量:SUMMER
枚举常量:WINTER

2.valueOf() 方法与 compareTo() 方法

枚举类型中静态方法 valueOf() 可以将普通字符串装换为枚举类型,而 compareTo() 方法用于比较两个枚举对象定义时的顺序。继续用到上面定义好的枚举

使用字符串创建一个季节的枚举值,并判断季节的位置

public class EnumMethodTest {
    public static void main(String[] args) {
        // 根据字符串创建一个枚举值
        SeasonEnum tmp = SeasonEnum.valueOf("SUMMER");
        // 获取所有枚举值
        SeasonEnum[] es = SeasonEnum.values();
        for (int i = 0; i < es.length; i++) {
            // 待输出的消息
            String message = "";
            // 记录两个枚举的比较结果
            int result = tmp.compareTo(es[i]);
            if (result < 0) {
                message = tmp + "在" + es[i] + "的前" + (-result) + "个位置";
            } else if (result > 0) {
                message = tmp + "在" + es[i] + "的后" + (-result) + "个位置";
            } else if (result == 0) {
                message = tmp + "与" + es[i] + "是同一个值";
            }
            System.out.println(message);
        }
    }
}

运行结果:

SUMMER在AUTUMN的后-2个位置
SUMMER在SPRING的后-1个位置
SUMMER与SUMMER是同一个值
SUMMER在WINTER的前1个位置

3.ordinal() 方法

输出每个季节的索引位置,继续使用上定义好的枚举

public class EnumIndexText {
    public static void main(String[] args) {
        SeasonEnum[] values = SeasonEnum.values();
        for (SeasonEnum value : values) {
            System.out.println(value + "在枚举类型中位置索引值" + value.ordinal());
        }
    }
}

运行结果:

AUTUMN在枚举类型中位置索引值0
SPRING在枚举类型中位置索引值1
SUMMER在枚举类型中位置索引值2
WINTER在枚举类型中位置索引值3

4.枚举中的构造方法

在枚举类型中,可以添加构造方法,但是规定这个构造方法必须被 private 修饰符所修饰。

enum Constants {
    Constants_A("我是枚举成员A"),
    Constants_B("我是枚举成员B"),
    Constants_C("我是枚举成员C"),
    Constants_D(3);
    private String description;

    /**
     * 定义默认的构造方法
     */
    private Constants() {
    }

    /**
     * 定义带参数的构造方法,参数类型为字符串型
     */
    private Constants(String description) {
        this.description = description;
    }

    /**
     * 定义带参数的构造方法,参数类型为整型
     */
    private Constants(int i) {
        this.i = this.i + i
    }
}

为四季枚举创建构造方法

public enum SeasonEnumTest {
    // 四季枚举
    SPRING("万物复苏"),
    SUMMER("烈日炎炎"),
    AUTUMN("秋草枯黄"),
    SINTER("白雪皑皑");

    private final String remarks;
    private SeasonEnumTest(String remarks) {
        this.remarks = "我是" + this.toString() + ",我来之后" + remarks + ".";
    }

    public String getRemarks() {
        return remarks;
    }
}

public class EnumConstructTest {
    public static void main(String[] args) {
        SeasonEnumTest[] values = SeasonEnumTest.values();
        for (SeasonEnumTest value : values) {
            System.out.println(value.getRemarks());
        }
    }
}

运行结果:

我是SPRING,我来之后万物复苏.
我是SUMMER,我来之后烈日炎炎.
我是AUTUMN,我来之后秋草枯黄.
我是SINTER,我来之后白雪皑皑.

使用枚举类型的优势

枚举类型声明提供了一种对用户友好的变量定义方法,枚举了某种数据类型所有可能出现的值。总结枚举类型,他具有一下特点:

  • 类型安全

  • 经凑有效的数据定义

  • 可以和程序其他部分完美交互

  • 运行效率高

泛型

泛型实质上是使程序员定义安全的类型。在没有出现泛型之前,java 也提供了对 Object 类型的引用“任意化”操作,这种“任意化”操作就是对 Object 类型引用进行向下转型及向上转型操作,但某些强制类型装换的错误也许不会被编译器捕捉,而在运行后出现 异常,可见强制类型转换存在安全隐患,所以在此提供了泛型机制。

回顾向上转型和向下转型

在项目中创建 Test 类,在该类中使用基本类型向上转型为 Object 类型。

public class Test {

    // 定义 Object 类型成员变量

    private Object b;

    /**
     * 设置相应的 get 方法
     */
    public Object getB() {
        return b;
    }

    /**
     * 设置相应的 set 方法
     */
    public void setB(Object b) {
        this.b = b;
    }

    public static void main(String[] args) {
        Test t = new Test();
        // 向上转型操作
        t.setB(Boolean.valueOf(true));
        System.out.println(t.getB());
        // 向下转型操作
        t.setB(Float.valueOf("12.3"));
        Float f = (Float) t.getB();
        System.out.println(f);
    }
}

运行结果:

true
12.3

在本实例中,Test 类中定义了私有的成员变量 b,他的类型为 Object 类型,同时为其定义了相应的 set 和 get 方法。在类的主方法中,将 Boolean.valueOf(true) 作为 setB() 方法的参数,由于 setB() 方法的参数类型为 Object 类型,这样就实现了向上转型操作,同时,在调用 getB() 方法时,将 getB() 方法返回的 Object 对象以相应的类型返回,这个就是向下转型操作,问题通常就会出现在这里,因为向上转型是安全的,而如果进行向下转型操作时用错了类型,或者并没有执行该操作,就会出现异常。例如以下代码:

t.setB(Float.valueOf("12.3"));
Integer f = (Integer) t.getB();
System.out.println(f);

该段代码并不存在语法错误,所以可以被编译器接收,但在执行时会出现 ClassCastException 异常,这样看来,向下转型操作通常会出现问题,而泛型机制有效地解决了这一问题。

定义泛型类

Object 类为最上层的父类,很多程序员为了使程序更为通用,设计程序时,通常使传入的值与返回的值都以 Object 为主。当需要使用这些实例时,必须正确地将该实例转换为原来的类型,否则在运行时将会发生 ClassCastException 异常。

为了提前预防这种问题,java 提供了泛型机制。其语法如下:

类名<T>

其中,T 是泛型的名称,代表某一种类型。开发者在创建该类对象时需要指定 T 所代表哪种具体的类型。如果不指定具体类型,T 则采用 Object 类型。

创建带泛型的的图书类

为 Book 图书类创建带泛型 T,用 T 声明一个成员变量 bookInfo。创建不同的图书对象,分别将 bookInfo 的类型指定为字符串、浮点数和布尔值类型。

public class Book<T> {

    // 类型形参:书籍信息

    private T bookInfo;

    /**
     * 参数为类型形参的构造方法
     */
    public Book(T bookInfo) {
        this.bookInfo = bookInfo;
    }

    /**
     * 获取书籍信息的值
     */
    public T getBookInfo() {
        return bookInfo;
    }

    public static void main(String[] args) {
        // 创建参数为 String 类型的书名对象
        Book<String> bookName = new Book<>("《牛马日记》");
        // 创建参数为 String 类型的作者对象
        Book<String> bookAuthor = new Book<>("niuma");
        // 创建参数为 Double 类型的价格对象
        Book<Double> bookPrice = new Book<>(99.99);
        // 创建参数为 Boolean 类型的附赠源码
        Book<Boolean> hasSource = new Book<>(true);
        // 控制台输出书名、作者、价格和是否附赠光盘
        System.out.println("书名:" + bookName.getBookInfo());
        System.out.println("作者:" + bookAuthor.getBookInfo());
        System.out.println("价格:" + bookPrice.getBookInfo());
        System.out.println("是否附赠源码:" + hasSource.getBookInfo());
    }
}

运行结果:

书名:《牛马日记》
作者:niuma
价格:99.99
是否附赠源码:true

从这个实例可以看出,使用泛型定义的类在声明对象时可以根据不同的需求指定 真正的类型,而在使用类中的方法传递或返回数据类型时将不再需要进行类型转换操作,而是使用在泛型类对象时 "< >" 符号中设置数据类型。

使用泛型这种形式将不会发生 ClassCastException 异常,因为在编译器中就可以检查类型匹配是否正确。

泛型的常规用法

1.定义泛型时声明多个类型

class MyClass<T1, T2>{ }

其中,T1 和 T2 为可能被定义的类型

这样,在实例化指定类型的对象时就可以指定多个类型。例如:

MyClass<Boolean, Float> m = new MyClass<Boolean, Float> ()

2. 定义泛型类型是声明数组类型

定义泛型类是也可以声明数组类型,下面的实例中定义泛型时便声明了数组类型。

定义泛型数组

public class ArrayClass<T> {

    private T[] array;

    private T[] getArray() {
        return array;
    }

    private void setArray(T[] array) {
        this.array = array;
    }

    public static void main(String[] args) {
        ArrayClass<String> demo = new ArrayClass<>();
        String[] value = {"成员1","成员2","成员3","成员4","成员5","成员6","成员7",};
        demo.setArray(value);
        String[] array = demo.getArray();
        for (String s : array) {
            System.out.println(s);
        }
    }
}

可见,可以在使用泛型机制时声明一个数组,但是不可以使用泛型来建立数组的实例。

3.集合类声明容器的元素

JDK 中的集合接口、集合类都被定义了泛型,其中 List 的泛型 E 实际上就是 element 元素的首字母, Map<K, V> 的泛型 K 和 V 就是 key 键和 value 值的首字母。 常用的被泛型化的集合类如下表:

集合类 泛型定义
ArrayList ArrayList
HashMap HashMap<K, V>
HashSet HashSet

使用泛型约束集合的元素类型

public class AnyClass {
    public static void main(String[] args) {
        // 定义 ArrayList 容器,设置容器内的值类型为 Integer
        ArrayList<Integer> a = new ArrayList<>();
        a.add(1);
        for (int i = 0; i < a.size(); i++) {
            // 根据容器的长度循环显示容器内的值
            System.out.println("获取 ArrayList 容器的成员值:" + a.get(i));
        }

        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));
        }
    }
}

运行结果:

获取 ArrayList 容器的成员值:1
获取 Map 容器的成员值成员0
获取 Map 容器的成员值成员1
获取 Map 容器的成员值成员2
获取 Map 容器的成员值成员3
获取 Map 容器的成员值成员4

泛型的高级用法

泛型的高级用法包括限制泛型可用类型和使用类型通配符等。

1.限制泛型可用类型

默认可以使用任何类型来实例化一个泛型类对象,但 java 中也对泛型类实例的类型做了限制。语法如下:

class 类名称<T extends AnyClass>

其中, AnyClass 指某个接口或类。

使用泛型限制后,泛型类的类型必须实现或继承 AnyClass 这个接口或类。无论 AnyClass 是接口还是类,在进行泛型限制时都必须使用 extends 关键字。

限制泛型的类型必须为 List 的子类

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>();
    }
}

在上面这个实例中,设置泛型类型必须实现 LIst 接口。例如 ArrayList 类和 LinkedList 类都实现了 List 接口,而 HashMap 类没有实现 List 接口,所以在这里不能实例化 HashMap 类型的泛型对象。

当没有使用 extends 关键字限制泛型类型时,默认 Object 类下的所有子类都可以实例化泛型类对象。

2.使用类型通配符

在泛型机制中,提供了类型通配符,其主要作用是在创建一个泛型类对象时限制这个泛型类的类型实现或继承某个接口或类的子类。要声明这样一个对象可以使用“?”通配符来表示,同时使用 extends 关键字来对泛型类加以限制。使用泛型通配符的语法如下:

泛型类名称<? extends List> a = null;

其中,<? extends List> 表示类型未知,但需要使用该泛型对象时,可以单独实例化。例如:

A<? extends LIst> a = null;
a = new A<ArrayList>();
a = new A<LinkedList>();

如果实例化没有实现 LIst 接口的泛型对象,编译器将会报错。例如,实例化 HashMap 对象时,编译器将会报错,因为 HashMap 类没有实现 LIst 接口。

除了可以实例化一个限制泛型类型的实例,还可以将实例放置在方法的参数中。例如:

public void doSomething(A<? extends List> a){ }

在上述代码中,定义方式有效地限制了传入 doSomething() 方法的参数类型。

如果使用 A<?> 这种形式实例化泛型类对象,则默认表示可以将 A 指定为实例化 Object 及一下的子类类型。例如:

// 实例化一个 ArrayList 对象
List<String> l1 = new ArrayList<String>();
// 再结合中添加内容
l1.add(0, "成员");
// 在集合中添加内容
List<?> l2 = l1;
List<?> l3 = new LinkedList<Integer>();
// 获取集合中的第一个值
System.out.println(l2.get(0));

在上面例子中,List<?>类型的对象可以接受 String 类型的 ArrayList 集合,也可以接受 Integer 类型的 LinkedList 集合。

List<?> l2 = l1 语句和 List l2 = l1 语句存在何种本质区别?这里需要注意的是,使用通配符声明的名称实例化的对象不能对其加入新的信息,只能获取或删除。例如:

// 没有使用通配符的对象调用 set() 方法
l1.set(0, "成员改变");
// 使用通配符的对象调用 set() 方法,不能被调用
l2.set(0, "成员改变");
l2.set(0, 1);
// 可以使用 l2 的实例获取集合中的值
l2.get(0);
// 根据键名删除集合中的值
l2.remove(0);

从上述代码中可以看出,由于对象 l1 是没有使用 A<?> 这种形式初始化出来的对象,所以它可以调用 set() 方法改变集合中的值,但 l2 与 l3 则是通过使用通配符的方式创建出来的,所以不能改变集合中的值。

泛型类型限制除了可以向下限制,还可以进行向上限制,只要在定义时使用 super 关键字即可。例如, 
A<? super List> a = null; 这样定义后,对象 a 只接收 LIst 接口或上层父类类型, 
如 a = new A<Object>();

3.继承继承泛型类与实现泛型接口

定义为泛型的类和接口也可以被继承与实现。例如,让 SubClass 类继承 ExtendClass 的泛型,代码如下:

class ExtendClass<T1>{ }
class SubClass<T1, T2, T3> implements SomeInterface<T1>{ }

泛型总结:

下面总结一下泛型的使用方法:

  • 泛型的类型参数只能是类类型,不可以是简单类型,如 A 这种泛型定义就是错误的。
  • 泛型的类型个数可以是多个。
  • 可以使用 extends 关键字限制泛型的类型。
  • 可以使用通配符限制泛型的类型。

posted on 2022-03-24 19:35  一颗蛋50斤  阅读(255)  评论(0)    收藏  举报

导航