导图社区 枚举类型与泛型
操作枚举类型成员的方法:枚举类型较传统定义常量的方式,除了具有参数类型检测的优势之外,还具有其他方面的优势。 用户可以将一个枚举类型看作是一个类,它继承于java.lang.Enum类,当定义一个枚举类型时,每一个枚举类型成员都可以看作是枚举类型的一个实例,这些枚举类型成员都默认被final、public、static修饰,所以当使用枚举类型成员时直接使用枚举类型名称调用枚举类型成员即可。
编辑于2023-02-15 16:22:49 贵州微信小程序开发知识整理,系统地介绍了微信小程序开发的基础知识,帮助开发者更好地理解和掌握小程序开发的要点和流程。
SpringFramework技术的介绍,SpringFramework,通常简称为Spring,是一个开源的企业级Java应用程序框架,由Rod Johnson创建,并于2004年首次发布。Spring Framework的主要目标是简化企业级Java开发,提高开发效率和应用程序的可维护性。
Maven的特性以及使用部署方法,Maven介绍:Maven是一款为Java项目构建管理、依赖管理的工具(软件),使用Maven可以自动化构建、测试、打包和发布项目,大大提高了开发效率和质量。
社区模板帮助中心,点此进入>>
微信小程序开发知识整理,系统地介绍了微信小程序开发的基础知识,帮助开发者更好地理解和掌握小程序开发的要点和流程。
SpringFramework技术的介绍,SpringFramework,通常简称为Spring,是一个开源的企业级Java应用程序框架,由Rod Johnson创建,并于2004年首次发布。Spring Framework的主要目标是简化企业级Java开发,提高开发效率和应用程序的可维护性。
Maven的特性以及使用部署方法,Maven介绍:Maven是一款为Java项目构建管理、依赖管理的工具(软件),使用Maven可以自动化构建、测试、打包和发布项目,大大提高了开发效率和质量。
枚举类型与泛型
枚举类型可以取代以往常量的定义方式,即将常量封装在类或接口中。此外,它还提供了安全检查功能。枚举类型本质上还是以类的形式存在的。泛型的出现不仅可以让程序员少写一些代码,更重要的是它可以解决类型安全问题。泛型提供了编译时的安全检查,不会因为将对象置于某个容器中而失去其类型。
一、 枚举类型
使用枚举类型,可以取代前面学习过的定义常量的方式,同时枚举类型还赋予程序在编译时进行检查的功能。
1. 适用枚举类型设置常量:设置常量时,我们通常将常量放置在接口中,这样在程序中就可以直接使用。该常量不能被修改,因为在接口中定义常量时,该常量的修饰符为final与static。常规定义常量的代码如下所示。枚举类型定义常量如右所示
public interface Constants{ public static final int Constants_A=1; public static final int Constants_B=1; }
public enum Constants{ Constants_A, Constants_B, Constants_C }
其中,enum是定义枚举类型的关键字。当需要在程序中使用该常量时,可以使用Constants.Constants_A来表示
实例代码: package com.ENUM; 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) { //定义一个方法参数为int型 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); ConstantsTest.doit(3); //ConstantsTest.doit2(3); } }
运行结果: doit() Constants_A doit2() Constants_A doit2() Constants_B
在上述代码中,当用户调用doit()方法时,即使编译器不接受在接口中定义的常量参数,也不会报错;但调用doit2()方法,任意传递参数,编译器就会报错,因为这个方法只接受枚举类型的常量作为其参数。
2. 枚举类型也可以在类的内部进行定义,下面将介绍如何在类的内部进行枚举类型的定义。
public class ConstantsTest { enum Constants2 { Constants_A, Constants_B } }
这种形式类似于内部类形式,当编译该类时,除了ConstantsTest.class外,还存在Constants-Test$1.class与ConstantsTest$Constants2.class文件。
二、 深入了解枚举类型
1. 操作枚举类型成员的方法:枚举类型较传统定义常量的方式,除了具有参数类型检测的优势之外,还具有其他方面的优势。 用户可以将一个枚举类型看作是一个类,它继承于java.lang.Enum类,当定义一个枚举类型时,每一个枚举类型成员都可以看作是枚举类型的一个实例,这些枚举类型成员都默认被final、public、static修饰,所以当使用枚举类型成员时直接使用枚举类型名称调用枚举类型成员即可。
枚举类型常用的方法如右图所示:
1||| values():枚举类型实例包含一个values()方法,该方法将枚举类型的成员变量实例以数组的形式返回,也可以通过该方法获取枚举类型的成员。
实例代码: package com.ENUM; import static java.lang.System.out; public class ShowEnum { enum Constants{ Constants_A,Constants_B //将常量放入枚举类型中 } public static void main(String[] args) { for (int i=0;i<Constants.values().length;i++) { //循环由values()方法返回的数组 out.println("枚举类型成员变量:"+Constants.values()[i]); //将枚举成员变量打印 } } }
运行结果: 枚举类型成员变量:Constants_A 枚举类型成员变量:Constants_B
2||| valueOf()与compareTo():枚举类型中静态方法valueOf()可以将普通字符串转换为枚举类型,而compareTo()方法用于比较两个枚举类型对象定义时的顺序。
实例代码: package com.ENUM; import static java.lang.System.out; public class EnumMethodTest { enum Constants2{ Constants_A,Constants_B //将常量放入枚举类型中 } 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) { compare(Constants2.valueOf("Constants_B")); //在主方法中调用compare()方法 } }
运行结果: Constants_B与Constants_A的比较结果为:1 Constants_B与Constants_B的比较结果为:0
调用compareTo()方法返回的结果,正值代表方法中参数在调用该方法的枚举对象位置之前;0代表两个互相比较的枚举成员的位置相同;负值代表方法中参数在调用该方法的枚举对象位置之后
3||| ordinal():枚举类型中的ordinal()方法用于获取某个枚举对象的位置索引值。
实例代码: package com.ENUM; import static java.lang.System.out; public class EnumIndexTest { enum Constants1{ //将常量放置在枚举类中 Constants_A,Constants_B,Constants_C } public static void main(String[] args) { for (int i=0;i<Constants1.values().length;i++) { //在循环中获取枚举类型成员的索引位置 out.println(Constants1.values()[i]+"在枚举类型中位置索引值:" +Constants1.values()[i].ordinal()); } } }
运行结果: Constants_A在枚举类型中位置索引值:0 Constants_B在枚举类型中位置索引值:1 Constants_C在枚举类型中位置索引值:2
三、 枚举类型中的构造方法
在枚举类型中,可以添加构造方法,但是规定这个构造方法必须为private修饰符所修饰。枚举类型定义的构造方法语法如下: enmu 枚举类型名称{ 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; }
从枚举类型构造方法的语法中可以看出,无论是无参构造方法还是有参构造方法,修饰权限都为private。定义一个有参构造方法后,需要对枚举类型成员相应地使用该构造方法,如Constants_A("我是枚举成员A")和Constants_D(3)语句,相应地使用了参数为String型和参数为int型的构造方法。然后可以在枚举类型中定义两个成员变量,在构造方法中为这两个成员变量赋值,这样就可以在枚举类型中定义该成员变量的getXXX()方法了。
实例代码: package com.ENUM; public class EnumIndexTest1 { 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 getInt() { //获取i的值 return i; } } public static void main(String[] args) { for (int i=0;i<Constants2.values().length;i++) { System.out.println(Constants2.values()[i]+"调用getDescription()方法为:" +Constants2.values()[i].getDescription()); } System.out.println(Constants2.valueOf("Constants_D")+"调用getInt()方法为:" +Constants2.valueOf("Constants_D").getInt()); } }
运行结果: Constants_A调用getDescription()方法为:枚举类型A Constants_B调用getDescription()方法为:枚举类型B Constants_C调用getDescription()方法为:枚举类型C Constants_D调用getDescription()方法为:null Constants_D调用getInt()方法为:8
在本实例中,调用getDescription()和getI()方法,返回在枚举类型定义的构造方法中设置的操作。这里将枚举类型中的构造方法设置为private修饰,以防止客户代码实例化一个枚举对象。 除了可以使用定义getDescription()方法获取枚举类型成员定义时的描述之外,还可以将这个getDescription()方法放置在接口中,使枚举类型实现该接口,然后使每个枚举类型实现接口中的方法。
实例代码: package com.ENUM; interface face{ public String getDescription(); public int getInt(); } public enum AnyEnum implements face{ Constants_A{ public String getDescription(){ return("枚举成员A"); } public int getInt() { return i; } }, Constants_B{ public String getDescription(){ return("枚举成员B"); } public int getInt() { return i; } }, Constants_C{ public String getDescription(){ return("枚举成员C"); } public int getInt() { return i; } }, Constants_D{ public String getDescription(){ return("枚举成员D"); } public int getInt() { return i; } }; private static int i=5; public static void main(String[] args) { for (int i=0;i<AnyEnum.values().length;i++) { System.out.println(AnyEnum.values()[i]+"调用getDescription()方法为:" +AnyEnum.values()[i].getDescription()); System.out.println(AnyEnum.values()[i]+"调用getInt()方法为:" +AnyEnum.values()[i].getInt()); } } }
运行结果: Constants_A调用getDescription()方法为:枚举成员A Constants_A调用getInt()方法为:5 Constants_B调用getDescription()方法为:枚举成员B Constants_B调用getInt()方法为:5 Constants_C调用getDescription()方法为:枚举成员C Constants_C调用getInt()方法为:5 Constants_D调用getDescription()方法为:枚举成员D Constants_D调用getInt()方法为:5
四、 使用枚举类型的优势
枚举类型声明提供了一种用户友好的变量定义方法,枚举了某种数据类型所有可能出现的值。总结枚举类型,它具有以下特点:
1||| 类型安全
2||| 紧凑有效的数据定义
3||| 可以和程序其他部分完美交互
4||| 运行效率高
五、 泛型
1. 回顾向上转型与向下转型
实例代码: package com.ENUM; public class Test { private Object b; //定义Object类型成员变量 public Object getB() { return b; } public void setB(Object b) { 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.68)); Float f=(float)(t.getB()); //向下转型操作 System.out.println(f); } }
运行结果: true 12.68
在本实例中,Test类中定义了私有的成员变量b,它的类型为Object类型,同时为其定义了相应的setXXX()与getXXX()方法。在类主方法中,将new Boolean(true)对象作为setB()方法的参数,由于setB()方法的参数类型为Object,这样就实现了向上转型操作。同时在调用getB()方法时,将getB()方法返回的Object对象以相应的类型返回,这个就是向下转型操作,问题通常就会出现在这里。因为向上转型是安全的,而如果进行向下转型操作时用错了类型,或者并没有执行该操作,就会出现异常,例如以下代码: t.setB(new Float(12.68)); Integer f=(Integer)(t.getB()); System.out.println(f); 以上代码并不存在语法错误,所以可以被编译器接受,但在执行时会出现ClassCastException异常。这样看来,向下转型操作通常会出现问题,而泛型机制有效地解决了这一问题。
2. 泛型实质上就是使程序员定义安全的类型。在没有出现泛型之前,Java也提供了对Object的引用“任意化”操作,这种“任意化”操作就是对Object引用进行向下转型及向上转型操作,但某些强制类型转换的错误也许不会被编译器捕捉,而在运行后出现异常,可见强制类型转换存在安全隐患,所以在此提供了泛型机制。
3. 定义泛型类:Object类为最上层的父类,很多程序员为了使程序更为通用,设计程序时通常使传入的值与返回的值都以Object类型为主。当需要使用这些实例时,必须正确地将该实例转换为原来的类型,否则在运行时将会发生ClassCastException异常。
在JDK 1.5版本以后,提出了泛型机制。其语法如下: 类名<T> T代表一个类型的名称
实例代码: package com.ENUM; 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>(); //实例化一个Boolean型的对象 OverClass<Float> over2=new OverClass<Float>(); over1.setOver(true); over2.setOver(12.68f); Boolean b=over1.getOver(); Float f=over2.getOver(); System.out.println(b); System.out.println(f); } }
运行结果: true 12.68
定义类时,在类名后添加了一个<T>语句,这里便使用了泛型机制。可以将OverClass类称为泛型类,同时返回和接受的参数使用T这个类型。最后在主方法中可以使用Over<Boolean>形式返回一个Boolean型的对象,使用OverClass<Float>形式返回一个Float型的对象,使这两个对象分别调用setOver()方法不需要进行显式向上转型操作,setOver()方法直接接受相应类型的参数,而调用getOver()方法时,不需要进行向下转型操作,直接将getOver()方法返回的值赋予相应的类型变量即可。 使用泛型定义的类在声明该类对象时可以根据不同的需求指定<T>真正的类型,而在使用类中的方法传递或返回数据类型时将不再需要进行类型转换操作,而是使用在声明泛型类对象时“< >”符号中设置的数据类型。 使用泛型这种形式将不会发生ClassCastException异常,因为在编译器中就可以检查类型匹配是否正确。
在定义泛型类时,一般类型名称使用T来表达,而容器的元素使用E来表达
4. 泛型的常规用法
I. 定义泛型时声明多个类型
在定义泛型时,可以声明多个类型。语法如下: MutiOverClass<T1,T2> MutiOverClass:泛型类名称 其中T1,T2为可能被定义的类型。这样在实例化指定类型的对象时就可以指定多个类型,例如: MutiOverClass<Boolean,Float>=new MutiOverClass<Boolean,Float>();
II. 定义泛型时声明数组类型
在定义泛型时可以声明数组类型。实例代码: package com.ENUM; public class ArraryClass<T>{ private T[] arrary; //定义泛型数组 public void setT(T[] arrary) { //设置Set()方法为成员数组赋值 this.arrary=arrary; } public T[] getT() { //获取成员数组 return arrary; } public static void main(String[] args) { ArraryClass<String> a=new ArraryClass<String>(); String[] arrary ={"成员1","成员2","成员3","成员4","成员5"}; a.setT(arrary); //调用SetT()方法 for (int i=0;i<a.getT().length;i++) { System.out.println(a.getT()[i]); } } }
运行结果: 成员1 成员2 成员3 成员4 成员5
本实例在定义泛型类时声明一个成员数组,数组的类型为泛型,然后在泛型类中相应设置setXXX()与getXXX()方法。 可见,可以在使用泛型机制时声明一个数组,但是不可以使用泛型来建立数组的实例。例如,下面的代码就是错误的: public class ArraryClass<T>{ private T[ ] arrary=new T[10]; //不能使用泛型来建立数组的实例 ...... }
III. 集合类声明容器的元素
可以使用K和V两个字符代表容器中的键值和与键值相对应的具体值。实例代码: package com.ENUM; import java.util.HashMap; import java.util.Map; public class MutiOverClass<K,V>{ public Map<K,V>m=new HashMap<K,V>(); //定义一个集合HashMap实例 public void put(K k,V v){ //设置put()方法,将对应的键值与键名字存入集合对象中 m.put(k,v); } public V get(K k){ return m.get(k); //根据键名获取键值 } public static void main(String[] args) { MutiOverClass<Integer,String>mu=new MutiOverClass<Integer,String>(); //实例化泛型类对象 for (int i=0;i<5;i++) { mu.put(i,"我是第"+i+"号ikun"); //根据集合长度循环将键名与具体键值放入集合中 } for (int i=0;i<mu.m.size();i++) { System.out.println(mu.get(i)); //调用get()方法根据给定的键名返回对应的键值 } } }
运行结果: 我是第0号ikun 我是第1号ikun 我是第2号ikun 我是第3号ikun 我是第4号ikun
其实在本实例中定义的泛型类MutiOverClass纯属多余,因为在Java中这些集合框架已经都被泛型化了,可以在主方法中直接使用“public Map<K,V> m=new HashMap<K,V>();”语句创建实例,然后相应调用Map接口中的put()与get()方法完成填充容器或根据键名获取集合中具体值的功能。集合中除了HashMap这种集合类型之外,还包括ArrayList、Vector等。
常用的被泛化的集合类
1||| ArrayList
泛型定义:ArrayList<E>
2||| HashMap
泛型定义:HashMap<K,V>
3||| HashSet
泛型定义:HashSet<E>
4||| Vector
泛型定义:Vector<E>
实例代码: package com.ENUM; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Vector; public class AnyClass { public static void main(String[] args) { ArrayList<Integer> a=new ArrayList<Integer>(); //定义ArrayList容器,设置容器内的值类型为Integer 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>(); //定义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(m.get(i)); } Vector<String> v=new Vector<String>(); //定义Vector容器 for (int i=0;i<5;i++) { v.addElement("集合成员"+i); //为Vector容器添加内容 } for (int i=0;i<v.size();i++){ System.out.println(v.get(i)); } } }
运行结果: 获取ArrayList容器的值:1 成员0 成员1 成员2 成员3 成员4 集合成员0 集合成员1 集合成员2 集合成员3 集合成员4
5. 泛型的高级用法:泛型的高级用法包括限制泛型可用类型和使用类型通配符等
I. 限制泛型可用类型:默认可以使用任何类型来实例化一个泛型类对象,但Java中也对泛型类实例的类型作了限制。语法如下: class 类名称<T extends anyClass> 其中,anyClass指某个接口或类。 使用泛型限制后,泛型类的类型必须实现或继承了anyClass这个接口或类。无论anyClass是接口还是类,在进行泛型限制时都必须使用extends关键字。
实例代码: package com.ENUM; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; public class LimitClass <T extends List> { //限制泛型的类型 public static void main(String[] args) { LimitClass<ArrayList> l1=new LimitClass<ArrayList>(); LimitClass<LinkedList> l2=new LimitClass<LinkedList>(); //可以实例化已经实现List接口的类 //LimitClass<HashMap> l3= new LimitClass<HashMap>(); // 这句代码是错误的因为HashMap没有实现List()接口 } }
在本例中,将泛型作了限制,设置泛型类型必须实现List接口。例如,ArrayList和LinkedList都实现了List接口,而HashMap没有实现List接口,所以在这里不能实例化HashMap类型的泛型对象。 当没有使用extends关键字限制泛型类型时,默认Object类下的所有子类都可以实例化泛型类对象。如下所示的两个语句是等价的。 public class a<T>{ } public class a<T extends Object>{ }
II. 使用类型通配符:在泛型机制中,提供了类型通配符,其主要作用是在创建一个泛型类对象时限制这个泛型类的类型实现或继承某个接口或类的子类。要声明这样一个对象可以使用“?”通配符来表示,同时使用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及以下的子类类型。 例如: List<String> l1=new ArrayList<String>( ); l1.add("成员"); //在集合中添加内容 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存在本质区别,这里需要注意的是,使用通配符声明的名称实例化的对象不能对其加入新的信息,只能获取或删除。例如: l1.set(0,"成员"); //l2.set(0,"a"); //使用了通配符的对象调用set()方法,不能被调用 //l3.set(0,1); l2.get(0); l2.remove(0); 从上述代码中可以看出,由于对象l1是没有使用A<?>这种形式初始化出来的对象,所以它可以调用set()方法改变集合中的值,但l2与l3则是通过使用通配符的方式创建出来的,所以不能改变集合中的值。
泛型类型限制除了可以向下限制之外,还可以进行向上限制,只要在定义时使用super关键字即可。例如:“A<?super List>a=null;"这样定义后,对象a只接受List接口或上层父类类型。如:“a=new A<Object>();"。
6. 继承泛型类与实现泛型接口:定义为泛型的类和接口也可以被继承与实现
例: public class ExtendsClass<T1>{ } class SubClass<T1,T2,T3>extends ExtendClass<T1>{ }
如果在SubClass类继承ExtendClass类时保留父类的泛型类型,需要在继承时指明,如果没有指明,直接使用extends ExtendsClass语句进行继承操作,则SubClass类中的T1、T2和T3都会自动变为Object,所以在一般情况下都将父类的泛型类型保留。
定义的泛型接口也可以被实现,例: interface i<T1>{ } class SubClass2<T1,T2,T3>implements i<T1>{ }
7. 总结
I. 泛型的类型参数只能是类类型,不可以是简单类型,如A<int>这种泛型定义就是错误的。
II. 泛型的类型个数可以是多个。
III. 可以使用extends关键字限制泛型的类型。
IV. 可以使用通配符限制泛型的类型。