Java第十三章枚举类型与泛型

  枚举类型可以取代以往常量的定义方式,即将常量封装在类或接口中。此外,它还提供了安全检查功能。枚举类型本质上还是以类的形式存在的。泛型的出现不仅可以让程序员少写一些代码,更重要的是它可以解决类型安全问题。泛型提供了编译时的安全检查,不会因为将对象置于某个容器中而失去其类型。本章将着重讲解枚举类型与泛型。

一、枚举类型

使用枚举类型,可以取代前面学习过的定义常量的方式,同时枚举类型还赋予程序在编译时进行检查的功能。

1、使用枚举类型设置常量

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

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

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

public enum Constants{
    Constants_A,
    Constants_B,
}

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

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

分别创建SeasonInterface接口和SeasonEnum枚举来定义四季常量,在SeasonDemo类创建两个printSeason()方法,分别以SeasonInterface接口常量和SeasonEnum枚举作为参数,打印传入的月份名称。尝试在调用printSeason()方法时使用接口常量值以外的数字“冒充”常量值。

代码如下:

2、深入了解枚举类型

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

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

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

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

接下来,具体讲解枚举类型的常用方法于构造方法。

1、value()方法

枚举类型实例包含一个values()方法,该方法将枚举中所有的枚举值以数组的形式返回。

例题:打印四季枚举中的所有枚举值

在项目中创建ShowEnum类,在该类中使用枚举类型中的values()方法获取四季枚举中的所有枚举值并打印出来。

代码如下:Java第十三章枚举类型与泛型_第1张图片

Java第十三章枚举类型与泛型_第2张图片

Java第十三章枚举类型与泛型_第3张图片

Java第十三章枚举类型与泛型_第4张图片

Java第十三章枚举类型与泛型_第5张图片

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

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

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

创建EnumMethodTest类,在主方法中创建字面为“SUMMER”的季节枚举,让创建的枚举与四季枚举的每一个值多对比,判断“SUMMER”所在的位置。

代码如下:

3、ordinal()方法

枚举类型中的ordinal()方法用于获取某个枚举对象的位置索引值。

例题:输出每一个季节的索引位置

在项目中创建EnumIndexTest类,在该类中使用枚举类型中的ordinal()方法获取枚举类型成员的位置索引。

代码如下:

4、枚举类型中的构造方法

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

从枚举类型构造方法的语法中可以看出,无论是无参构造方法还是有参构造方法,修饰权限都为private。定义一个有参构造方法后,需要对枚举类型成员相应地使用该构造方法,如Constants_A("我是枚举成员A")和Constants_D(3)语句,相应地使用了参数为int型的构造方法。然后可以在枚举类型中定义两个成员变量,在构造方法中为这两个成员变量赋值,这样就可以在枚举类型中定义该成员变量的getXXX()方法了。

例题:为四季枚举创建构造方法,记录每一个季节的特征

在四季枚举中创建一个字符串类型的备注属性,并创建该属性的Getter方法,在枚举构造方法中为备注属性赋值,最后输出每一个季节枚举的备注值。

3、使用枚举类型的优势

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

类型安全。

紧凑有效的数据定义。

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

运行效率高

二、泛型

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

1、回顾向上转型与向下转型

在介绍泛型之前,先来看一个例子。在项目中创建Test类,在该类中使基本类型向上转型Object类型,具体代码如下:

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(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类型,同时为其定义了相应的setXXX()与getXXX()方法。在类的主方法中,将Boolean.valueOf(true)作为setB()方法的参数,由于setB()方法的参数类型为Object类型,这样就实现了向上转型操作。同时,在调用getB()方法的参数,将setB()方法返回的object对象以相应的类型返回,这个就是向下转型操作,问题通常就会出现在这里。因为向上转型是安全的,而如果向下转型操作时使用错了类型,或者没有执行该操作,就会出现异常例如以下代码:

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

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

2、定义泛型类

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

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

类名

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

例题:创建带泛型的图书类

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

代码如下:Java第十三章枚举类型与泛型_第6张图片

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

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

如果不按照泛型指定的类型进行赋值,就会发生编译错误。例如,将泛型指定为Double类型的值赋值给Integer类型时,就会出现错误。

3、泛型的常规用法

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

在定义泛型类时,可以声明多个类型。语法如下:

class MyClass{        }

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

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

MyClass m = new MyClass();

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

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

例题:定义泛型数组

在项目中创建ArrayClass类,在该类中定义泛型类声明数组类型。

Java第十三章枚举类型与泛型_第7张图片

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

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

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

常用的被泛型化的集合类
集合类 泛型定义
ArrayList ArrayList
HashMap HashMap
HashSet HashSet

下面的实例演示了这些集合的使用方式。

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

在项目中创建AnyClass类,在该类中使用泛型实例化常用集合类。

Java第十三章枚举类型与泛型_第8张图片

4、泛型的高级用法

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

1.限制泛型可用类型

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

class 类名称

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

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

例题:限制泛型的类型必须为List的子类

在项目中创建LimitClass类,在该类中限制泛型类型。

代码如下:

Java第十三章枚举类型与泛型_第9张图片

在上面这个实例中,设置泛型类型必须实现List接口。例如,ArrayList类和LinkedList类都实现了List接口,而HashMap类没有实现List接口,所以在这里不能实例化HashMap类型时,默认Object类下的所有子类都可以实例化泛型类对象。所以这两个语句是等价的。

2.使用类型通配符

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

泛型类名称 a=null;

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

A a = null;

a = new A();

a = new A();

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

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

public void doSomething(Aa){}

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

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

Listl1 = new ArrayList();//实例化一个ArrayList对象
l1.add("成员");//在集合中添加内容
Listl2 = l1;//使用通配符
Listl3 = new LikedList();
System.out.println(l2.get(0));//获取集合中第一个值

在上面的例子中,List类型的对象可以接受String类型的ArrayList集合,也可以接受Integer类型的LinkedList集合。也许有的读者会有疑问,“List l2 = 11”语句存在何种本质区别?这里需要注意的是,使用通配符声明的名称实例化的对象不能对其加入新的信息,只能获取或删除。例如:

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

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

技巧:

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

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

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

class ExtendClass{}

class SubClassextends ExtendClass{}

如果在SubClass类继承ExtendsClass类时保留父类的泛型类型,需要在继承时指明,如果没有指明,直接使用“extends Extends”语句进行继承操作,则SubClass类中的T1、T2和T3都会自动变为Object类型,所以在一般情况下都将父类的泛型类型保留。

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

interface SomeInterface{}

class SubClassimplements SomeInterface{}

5、泛型的总结

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

泛型的类型参数只能是类类型,不可以是简单类型,如A这种泛型定义就是错误的。

泛型的类型个数可以是多个。

可以使用extends关键字限制泛型的类型。

可以使用通配符限制泛型的类型。

你可能感兴趣的:(java,开发语言)