zl程序教程

您现在的位置是:首页 >  后端

当前栏目

java学习笔记10--泛型总结

JAVA笔记学习泛型 -- 总结 10
2023-09-14 08:58:00 时间
集合类中的数据类型

集合类中可以存储各种数据,数据一旦存入,其类型均会转化为Object类型。从集合类中取出数据时,一般均需要将Object类型转换回存入之前的实际类型

Vector v=new Vector();

v.add("张三"); //存入字符串

String name=(String)v.get(0); //强制类型转换,OK

v.add(new Date()); //存入当前时间对象,OK

 // 由于Date类型不能转换为String,下面语句会在运行时发生错误,但这种错误在编译时不会被检查出来

String date=(String)v.get(1); //编译器不会发现这里有问题

强类型集合

传统的集合类的实例中可以存储任意类型数据,这种集合类称为弱类型集合类。JDK1.5以后,引入了强类型集合类:


v.add("hello"); //加入的是字符串,OK String name=v.get(0); //取出时,无需做类型转换,如果想在这种强类型集合中加入日期数据,在编译时就会报告错误 v.add(new Date()); //编译器会直接报告类型不匹配错误

定义泛型(Generics)类

强类型集合采用了JDK1.5引入的泛型语法。泛型相当于类中一种特殊的类型,这种类型的特点是在实例化该类时可指定为某个具体的实际类型。

声明包含泛型的类的格式如下:

 [访问修饰符] class 类名 泛型1,泛型2,… {

 泛型1 泛型成员1;

 泛型2 泛型成员2;

 //....

 }

声明中的泛型1、泛型2等等泛型符号可以是任意合法的Java标识符。

泛型类的声明示例

//此处声明了一个包含泛型T的泛型类,T代表所有可能的类型,而T的实际类型在Generic类实例化时指定。

class Generic T {

 private T f; //f为泛型成员

 public void setF(T f) {//setF方法的参数类型为泛型T

 this.f = f;

 public T getF() {//getF方法的返回类型为泛型T

 return f;

}

泛型类的实例化

创建泛型类的实例时,可以使用一对尖括号指定泛型的真正类型


实例化时的泛型的默认类型

泛型类实例化时,并不一定要指明泛型对应的实际类型,此时会使用Object作为泛型的默认类型

Generic f3 = new Generic();

f3.setF(new Boolean(false));

编译时编译器会发出警告:

Note: Generic.java uses unchecked or unsafe operations.

Note: Recompile with -Xlint:unchecked for details.

建立类型为泛型类的数组

如果要建立泛型类的数组,需要注意new关键字后面不要加入泛型的实际类型名,如下所示:

Generic String [] gs; //声明泛型类的数组

//先对泛型数组进行初始化

gs = new Generic[5]; //不要写成new Generic String [5]

//再分别为每一个数组元素进行初始化

gs[0] = new Generic String //为第一个数组元素赋值

//....

包含多个泛型的类定义示例

包含有两个泛型定义的类声明和实例化:

class Generic2 T1, T2 {

 private T1 f1;

 private T2 f2;

 //...

//给出泛型T1, T2的实际类型

Generic Integer, Boolean f = new Generic Integer, Boolean 

//没有给出泛型T1, T2的实际类型

Generic f1 = new Generic(); //T1, T2将默认为是Object类型

泛型成员的使用

在泛型类中的泛型成员不能直接实例化,其实例必须要通过方法的参数传递给泛型成员:

class Generic T {

 private T[] array; //此处不能用new T[]实例化array

 public void setArray(T[] array) {

 this.array = array;

 public T[] getArray() {

 return array;

}

测试程序:

public class test { 

 public static void main(String args[ ]) { 

 String[] strs = {"red", "black", "green"};

 Generic String f = new Generic String 

 //向泛型成员array传递实际的字符串数组

 f.setArray(strs);

 //读取泛型成员array的值,将其赋给字符串数组变量strs

 strs = f.getArray();

}

泛型成员的可用方法

由于泛型类型只有在类实例化后才能确定,类中的泛型成员只能使用Object类型中的方法:

class Generic T {

 T f;

 void setF(T f){ this.f = f; }

 //....

 void doSome(){

 //getClass和toString都是Object中的方法

 System.out.println(f.getClass().getName());

 System.out.println(f.toString());

}

测试程序:

public class javatest { 

 public static void main(String args[ ]) { 

 String strs = "hello";

 Generic String f = new Generic String 

 f.setF(strs);

 f.doSome();

}
限制泛型上限类型

extends关键字用来指定泛型的上限,在实例化泛型类时,为该泛型指定的实际类型必须是指定类的子类或指定接口的子接口

import java.util.List;

public class ListGeneric T extends List {

 private T list;

 public void setList(T list) {

 this.list = list;

 public T getList() {

 return list;

}

在限定泛型的类型时,无论要限定的是接口或是类,都要使用extends关键词

测试例子:

ListGeneric Vector f1 = new ListGeneric Vector 

ListGeneric ArrayList f2 = new ListGeneric ArrayList 

如果不是List的类型,编译时就会发生错误:

ListGeneric HashMap f3 = new ListGeneric HashMap 

type parameter java.util.HashMap is not within its bound

ListGeneric HashMap f3 = new ListGeneric HashMap 

默认的泛型限制类型

定义泛型类别时,如果只写以下代码:

class Generic T {

 //...

}

相当于下面的定义方式:

class Generic T extends Object {

 //...

}

限定泛型上限后的成员可用方法:

泛型类型的上限一经限定,类中的泛型成员就可使用上限类型中的方法和其他可用成员:
class ListGeneric T extends List {

 private T list;

 public void setList(T list) {

 this.list = list;

 public void doSome() {

 //ad、get方法都是List接口中定义的方法

 list.add(new Integer(0));

 System.out.println(list.get(0));

}

Object是所有类的父类,因此,所有的类型的实例都可赋值给声明为Object类型的变量

Boolean f1 = new Boolean(true);

Integer f2 = new Integer(1);

Object f = f1; //ok

f = f2; //ok

在实例化泛型类时,将泛型指定为Object类型却不存在着和其他类型之间的兼容性:

Generic Boolean f1 = new Generic Boolean 

Generic Integer f2 = new Generic Integer 

Generic Object f=f1; //f1和f类型并不兼容,发生编译错误

f=f2; //f2和f类型同样不兼容,也会发生编译错误 
泛型通配字符(Wildcard)

泛型类实例之间的不兼容性会带来使用的不便。使用泛型通配符(?)声明泛型类的变量可以解决这个问题

Generic Boolean f1 = new Generic Boolean 

Generic Integer f2 = new Generic Integer 

Generic Object f3 = new Generic Object 

Generic ? 

f = f1; //ok

f = f2; //ok

f = f3; //ok

通配符也可以用于方法的参数类型的声明,表示该参数可接受对应泛型类型的任意实例。

以下类定义中的printCollection方法可以打印任意强类型集合中的内容

class test {

 //Collection ? 可以匹配任意强类型的集合

 static void printCollection(Collection ? c) {

 for(Object o : c)

 System.out.println(o);

}

和限制泛型的上限相似,同样可以使用extends关键字限定通配符匹配类型的上限:

Generic ? extends List f = null;

f = new Generic ArrayList //ok

//...

f = new Generic Vector //ok

//...

//以下语句会发生编译错误,因为HashMap没有实现List接口

f = new Generic HashMap 

incompatible types

found : Generic java.util.HashMap 

required: Generic ? extends java.util.List 

f = new Generic HashMap 

限定通配符匹配类型的下限

还可以使用super关键词将通配符匹配类型限定为某个类型及其父类型:

//将f限定为只能代表采用java.sql.Date的父类实例化的

Generic ? super java.sql.Date f = null;

f = new Generic java.sql.Date //ok

//OK,java.util.Date是java.sql.Date的父类

f = new Generic java.util.Date 

//错误,因为String不是java.sql.Date的父类

f = new Generic String 

不仅类可以声明泛型,类中的方法也可以声明仅用于自身的泛型,这种方法叫做泛型方法。其定义格式为:

访问修饰符 泛型列表 返回类型 方法名(参数列表){

    实现代码

}

其中泛型列表为用逗号分隔的合法Java标识符。

在泛型列表中声明的泛型,可用于该方法的返回类型声明、参数类型声明和方法代码中的局部变量的类型声明。类中其他方法不能使用当前方法声明的泛型。使用泛型方法可以解决上述的泛型通配符造成的问题

泛型方法声明示例:

class cc{

 方法fun声明了一个泛型T,该方法将任意类型的数组a中的所有

 元素复制到相应的强类型集合c当中而不会导致编译错误。

 此处的泛型声明T仅作用于fun方法的声明部分和实现代码部分。

 public static T void fun(T[] a, Collection T c){

 for(T o : a)

 c.add(o); //不会出现类似通配符的编译错误

}

调用泛型方法和调用普通方法没有任何不同,只需要传递含有具体类型的实参即可:

泛型方法的调用示例:

//对cc中定义的泛型方法fun进行调用测试

public class javatest { 

 public static void main(String args[ ]) { 

 String[] sa = new String[100];

 Collection String cs = new Vector String 

 Collection Object co = new Vector Object 

 cc.fun(sa, cs); //fun中的泛型T此时匹配类型String

 cc.fun(sa, co); //fun中的泛型T此时匹配类型Object

}

限定泛型方法中泛型类型

泛型方法中的声明的泛型,同样可以使用extends关键字限定其类型的下限:

class cc{

 //限定aToC方法中的泛型T必须是实现了序列化接口的类型

 public static T extends java.io.Serializable void fun(T[] a,Collection T c){

 for(T o : a)

 c.add(o);

}
原始类型和向后兼容

先看一个泛型类定义

public class GenericStack E {

 ArrayList E list = new ArrayList E 

 public int getSize() {

 return list.size();

 public E peek() {

 return list.get(getSize() - 1);

 public void push(E o) {

 list.add(o);

 public E pop() {

 E o = list.get(getSize() - 1);

 list.remove(getSize() - 1);

 return o;

 public boolean isEmpty() {

 return list.isEmpty();

}

可以使用泛型类而无需指定具体类型:

GenericStack stack = new GenericStack();

//大体等价于于下面的语句

GenericStack Object stack = new GenericStack Object 

再看下面的一个例子:

class Max {

 public static Comparable max(Comparable o1, Comparable o2) {

 if(o1.compareTo(o2) 0)

 return o1;

 return o2;

public class javatest { 

 public static void main(String args[]) { 

 Max.max("welcome", 12);

}

得到一个运行错误:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

结论:原始类型是不安全的,尽量使用泛型类型

一个更好的编写max方法的方式是使用泛型:

class Max {

 public static E extends Comparable E E max(E o1, E o2) {

 if(o1.compareTo(o2) 0)

 return o1;

 return o2;

}

再次调用上面的命令就会显示一个编译错误,由于max方法的两个参数必须是相同的类型

继承中的泛型

继承时如需保留父类泛型,需要在声明时加入父类泛型

class subGeneric T1, T2, T3 extends Generic T1, T2 {

 private T3 f3;

 public void setF3(T3 f3) {

 this.f3 = f3;

 public T3 getF3() {

 return f3;

}

如果不保留父类中的泛型声明,则继承下来的T1与T2自动变为Object类型。建议父类中的泛型声明在子类中都要保留

继承时指定父类的泛型类型

public class SubGeneric T3 extends Generic String, Object {

 private T3 f3;

 public void setF3(T3 f3) {

 this.f3 = f3;

 public T3 getF3() {

 return f3;

}

接口也可包含泛型的声明:

interface I T1, T2 {

 T1 getT1();

 T2 getT2();

 //...

}

实现泛型接口时,类在定义时可以不声明泛型接口中的泛型,此时接口中的泛型也会自动变为Object类型:

class IC implements I {

 public Object getT1() { }

 public Object getT2() { }

 //...

}

泛型接口的实现

class IC T1, T2 implements I T1, T2 {

 public T1 getT1() { }

 public T2 getT2() { }

 //...

I String, Integer i = new IC String, Integer 

实现泛型接口时指定泛型类型

在实现泛型接口时,也可直接指定接口中的泛型的实际类型:

interface I T1, T2 {

 T1 getT1();

 T2 getT2();

 //...

//实现接口I时,直接指定泛型T1、T2的类型

class IC implements I String, Integer {

 //由于指定接口I中T1类型为String,getT1返回类型必须为String

 public String getT1() { }

 //由于指定接口I中T2类型为Integer,getT2返回类型必须为Integer

 public Integer getT2() { }

 //...

}
泛型和枚举

由于枚举类型不能直接实例化,所以枚举的定义中不能含有泛型的声明,但枚举中可包含泛型方法的定义。

public enum TrafficLight{

 Red,Amber,Green;

 private int duration;

 public static T void avgDuration(Collection T carType){

 //....

 //....

}

「 Java基础-泛型 」一篇文章说清楚Java泛型中的通配符T、E、K、V、N、?和Object的区别与含义 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型。 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。 可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。
Java中的泛型 泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数 可以声明泛型类、泛型方法和泛型接口(下一章介绍接口)
Java基础之泛型程序设计 类型变量使用大写形式,且比较短,在Java库中,使用变量E表示集合的元素类型,K和V分别表示表的关键字与值得类型。Object 表示”任意类型”