目录
1、泛型(Generic)
1.1 泛型方法
1.2 泛型类
1.3 泛型接口
1.4 泛型通配符
1、泛型(Generic)
当集合中存储的对象类型不同时,那么会导致程序在运行的时候的转型异常
import java.util.ArrayList; import java.util.Iterator; public class Demo5 { public static void main(String[] args) { ArrayList arr = new ArrayList(); arr.add(new Tiger("华南虎")); arr.add(new Tiger("东北虎")); arr.add(new Sheep("喜羊羊")); System.out.println(arr); Iterator it = arr.iterator(); while (it.hasNext()) { Object next = it.next(); Tiger t = (Tiger) next; t.eat(); } } } class Tiger { String name; public Tiger() { } public Tiger(String name) { this.name = name; } @Override public String toString() { return "Tiger@name:" + this.name; } public void eat() { System.out.println(this.name + "吃羊"); } } class Sheep { String name; public Sheep() { } public Sheep(String name) { this.name = name; } @Override public String toString() { return "Sheep@name:" + this.name; } public void eat() { System.out.println(this.name + "吃青草"); } } |
原因 :发现虽然集合可以存储任意对象,但是如果需要使用对象的特有方法,那么就需要类型转换,如果集合中存入的对象不同,可能引发类型转换异常.
[Tiger@name:华南虎, Tiger@name:东北虎, Sheep@name:喜羊羊] 华南虎吃羊 东北虎吃羊 Exception in thread "main" java.lang.ClassCastException: cn.ittest.gz.map.Sheep cannot be cast to cn.ittest.gz.map.Tiger at cn.ittest.gz.map.Demo5.main(Demo5.java:17) |
出现问题:
存入的是特定的对象,取出的时候是Object对象,需要强制类型转换,可能诱发类型转换异常.
无法控制存入的是什么类型的对象,取出对象的时候进行强转时可能诱发异常.而且在编译时期无法发现问题.
虽然可以再类型转换的时候通过if语句进行类型检查(instanceof),但是效率较低.(例如吃饭的时候,还需要判断米饭里有没有沙子,吃饭效率低).可以通过给容器加限定的形式规定容器只能存储一种类型的对象.
就像给容器贴标签说明该容器中只能存储什么样类型的对象。
所以在jdk5.0后出现了泛型
泛型应用:
格式
- 集合类<类类型> 变量名 = new 集合类<类类型>();
public class Demo5 { public static void main(String[] args) { // 使用泛型后,规定该集合只能放羊,老虎就进不来了. ArrayList<Sheep> arr = new ArrayList<Sheep>(); arr.add(new Sheep("美羊羊")); arr.add(new Sheep("懒洋洋")); arr.add(new Sheep("喜羊羊")); // 编译失败 // arr.add(new Tiger("东北虎")); System.out.println(arr); Iterator<Sheep> it = arr.iterator(); while (it.hasNext()) { // 使用泛型后,不需要强制类型转换了 Sheep next = it.next(); next.eat(); } } } |
1. 将运行时的异常提前至编译时发生。
2. 获取元素的时候无需强转类型,就避免了类型转换的异常问题
格式 通过<> 来指定容器中元素的类型.
什么时候使用泛型:当类中操作的引用数据类型不确定的时候,就可以使用泛型类.
JDK5.0之前的Comparable
package java.lang; public interface Comparable { public int compareTo(Object o); } |
JDK5.0之后的Comparable
package java.lang; public interface Comparable<T> { public int compareTo(T o); } |
这里的<T>表示泛型类型,随后可以传入具体的类型来替换它.
细节一
声明好泛型类型之后,集合中只能存放特定类型元素
public class Demo6 { public static void main(String[] args) { //创建一个存储字符串的list ArrayList<String> arr=new ArrayList<String>(); arr.add("gz"); arr.add("ittest"); //存储非字符串编译报错. arr.add(1); } } |
细节二:
泛型类型必须是引用类型
public class Demo6 { public static void main(String[] args) { // 泛型类型必须是引用类型,也就是说集合不能存储基本数据类型 // ArrayList<int> arr2=new ArrayList<int>(); // 使用基本数据类型的包装类 ArrayList<Integer> arr2 = new ArrayList<Integer>(); } } |
细节三: 使用泛型后取出元素不需要类型转换.
public class Demo6 { public static void main(String[] args) { ArrayList<String> arr = new ArrayList<String>(); arr.add("gzittest"); arr.add("cdittest"); arr.add("bjittest"); //使用泛型后取出元素不需要类型转换. String str=arr.get(0); System.out.println(); } } |
1.1 泛型方法
需求:写一个函数,调用者传递什么类型的变量,该函数就返回什么类型的变量?
实现一:
由于无法确定具体传递什么类型的数据.那么方法的形参就定义为Object类型.返回值也就是Object类型.但是使用该函数时需要强制类型转换.
private Object getDate(Object obj) { return obj; } |
当不进行强制类型转换能否写出该功能?
目前所学的知识无法解决该问题
就需要使用泛型类解决
使用的泛型的自定义来解决以上问题。
泛型: 就是将类型当作变量处理。规范泛型的定义一般是一个大写的任意字母。
1. 函数上的泛型定义 当函数中使用了一个不明确的数据类型,那么在函数上就可以进行泛型的定义。 public <泛型的声明> 返回值类型 函数名( 泛型 变量名 ){ } |
public static void main(String[] args) { int[] arr = { 1, 2, 3, 4, 5 }; new Demo6().getData(5); } public <T> T getData(T data) { return data; } |
细节:
使用泛型方法前需要进行泛型声明,使用一对尖括号 <泛型>,声明的位置在static后返回值类型前。
当一个类中有多个函数声明了泛型,那么该泛型的声明可以声明在类上。
1.2 泛型类
格式
2. 类上的泛型声明 修饰符 class 类名<泛型>{ } |
import java.util.Arrays; public class Demo6<T> { public static void main(String[] args) { // 使用泛型类,创建对象的时候需要指定具体的类型 new Demo6<Integer>().getData(5); } public T getData(T data) { return data; } // 反序任意类型数组 public void reverse(T[] arr) { int start = 0; int end = arr.length - 1; for (int i = 0; i < arr.length; i++) { if (start < end) { T temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; } } } |
在泛型类中定义一个静态方法
public class Demo6<T> { public static void main(String[] args) { System.out.println(getData2(100)); } public T getData(T data) { return data; } //静态方法 public static T getData2(T data) { return data; } } |
注意:静态方法不可以使用类中定义的泛型
因为类中的泛型需要在对象初始化时指定具体的类型,而静态优先于对象存在。那么类中的静态方法就需要单独进行泛型声明,声明泛型一定要写在static后,返回值类型之前
泛型类细节:
1、创建对象的时候要指定泛型的具体类型 2、创建对象时可以不指定泛型的具体类型(和创建集合对象一眼)。默认是Object,例如我们使用集合存储元素的时候没有使用泛型就是那么参数的类型就是Object 3、类上面声明的泛型只能应用于非静态成员函数,如果静态函数需要使用泛型,那么 需要在函数上独立声明。 4、如果建立对象后指定了泛型的具体类型,那么该对象操作方法时,这些方法只能操作一种数据类型。 5、所以既可以在类上的泛型声明,也可以在同时在该类的方法中声明泛型。 |
泛型练习:
定义泛型成员
public class Demo7 { public static void main(String[] args) { Father<String> f = new Father<String>("jack"); System.out.println(f.getT()); Father<Integer> f2 = new Father<Integer>(20); System.out.println(f2.getT()); } } class Father<T> { private T t; public Father() { } public Father(T t) { super(); this.t = t; } public T getT() { return t; } public void setT(T t) { this.t = t; } } |
如果Father类有子类,子类该如何实现
public class Demo7 { public static void main(String[] args) { Father<String> f = new Father<String>("jack"); System.out.println(f.getT()); Father<Integer> f2 = new Father<Integer>(20); System.out.println(f2.getT()); } } class Father<T> { private T t; public Father() { } public Father(T t) { super(); this.t = t; } public T getT() { return t; } public void setT(T t) { this.t = t; } } //子类指定了具体的类型 class Son extends Father<String>{ } //子类也需要使用泛型 class Son3<T> extends Father<T>{ } //错误写法,父类上定义有泛型需要进行处理 class Son2 extends Father<T>{ } |
1.3 泛型接口
public class Demo8 { public static void main(String[] args) { MyInter<String> my = new MyInter<String>(); my.print("泛型"); MyInter2 my2 = new MyInter2(); my.print("只能传字符串"); } } interface Inter<T> { void print(T t); } // 实现不知为何类型时可以这样定义 class MyInter<T> implements Inter<T> { public void print(T t) { System.out.println("myprint:" + t); } } //使用接口时明确具体类型。 class MyInter2 implements Inter<String> { @Override public void print(String t) { System.out.println("myprint:" + t); } } |
1.4 泛型通配符
需求:
定义一个方法,接收一个集合对象(该集合有泛型),并打印出集合中的所有元素。
例如集合对象如下格式:
Collection<Person> coll = new ArrayList<Person>(); coll.add(new Person("jack", 20)); coll.add(new Person("rose", 18)); Collection<Object> coll2 = new ArrayList<Object>(); coll2.add(new Object()); coll2.add(new Object()); coll2.add(new Object()); Collection<String> coll3 = new ArrayList<String>(); coll3.add("abc"); coll3.add("ddd"); coll3.add("eee"); |
分析,集合对象中的元素的类型是变化的,方法的形参的那么泛型类型就只能定义为Object类型.
import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; public class Demo9 { public static void main(String[] args) { ArrayList<Object> arr = new ArrayList<Object>(); arr.add(new Object()); arr.add("String"); print(arr);
//将集合的泛型设置类String类型,是Object子类 HashSet<String> hs = new HashSet<String>(); hs.add("hello"); hs.add("jack"); //由于print方法接收的集合进行了元素限定,只接受限定为Object类型的集合,编译不通过 //print(hs); } public static void print(Collection<Object> coll) { Iterator<Object> it = coll.iterator(); while (it.hasNext()) { Object next = it.next(); System.out.println(next); } } } |
但是,由于print方法接收的集合进行了元素限定,只接受限定为Object类型的集合,编译不通过该问题如何解决?
可以把方法的形参的泛型去掉,那么方法中就把集合中的元素当做Object类型处理.
也可以使用使用泛型通配符
public class Demo9 { public static void main(String[] args) { ArrayList<Object> arr = new ArrayList<Object>(); arr.add(new Object()); arr.add("String"); print(arr); // 将集合的泛型设置类String类型,是Object子类 HashSet<String> hs = new HashSet<String>(); hs.add("hello"); hs.add("jack"); // 使用泛型通配符,编译通过。 print(hs); } public static void print(Collection<?> coll) { Iterator<?> it = coll.iterator(); while (it.hasNext()) { Object next = it.next(); System.out.println(next); } } } |
上述就使用了泛型通配符
通配符:? public void show(List<?> list){ } 可以对类型进行限定范围。 ?extends E: 接收E类型或者E的子类型。 ? super E: 接收E类型或者E的父类型。 |
限定泛型通配符的边界
限定通配符的上边界:
extends
接收Number 类型或者Number的子类型
正确:Vector<? extends Number> x = new Vector<Integer>(); 错误:Vector<? extends Number> x = new Vector<String>(); |
限定通配符的下边界
super
接收Integer 或者Integer的父类型
正确:Vector<? super Integer> x = new Vector<Number>(); 错误:Vector<? super Integer> x = new Vector<Byte>(); |
总结:
JDK5中的泛型允许程序员在编写集合代码时,就限制集合的处理类型,从而把原来程序运行时可能发生问题,转变为编译时的问题,以此提高程序的可读性和稳定
注意:泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。
泛型的基本术语,以ArrayList<E>为例:<>念着typeof
ArrayList<E>中的E称为类型参数变量
ArrayList<Integer>中的Integer称为实际类型参数
整个称为ArrayList<E>泛型类型
整个ArrayList<Integer>称为参数化的类型ParameterizedType