泛型的本质便是类型参数化
泛型举例:
一个被举了无数次的例子:
List arrayList = new ArrayList();
arrayList.add(“aaaa”);
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
System.out.println(“泛型测试”+"item = " + item);
}
毫无疑问,程序的运行结果会以崩溃结束:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。
*我们将第一行声明初始化list的代码更改一下,*编译器会在编译阶段就能够帮我们发现类似这样的问题。
List arrayList = new ArrayList();
…
//arrayList.add(100); 在编译阶段,编译器就会报错
//泛型只在编译阶段有效
package generic;
import java.util.ArrayList;
import java.util.List;
public class genericType {
public static void main(String[] args) {
List<String> stringArrayList = new ArrayList<>();
List<Integer> integerArrayList = new ArrayList<>();
Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();
if(classStringArrayList.equals(classIntegerArrayList)){
System.out.println("泛型测试"+"类型相同");
}
}
}
4.泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法
4.1泛型类,(例如,ArrayList类就是一个泛型类)
一个最简单的泛型类
package generic;
public class Generic<E> {
private E key;
public Generic(E key) {
this.key=key;
}
public E geyKey() {
return key;
}
}
// 对上面类型的引用:Generic<Integer> genericInteger = new Generic<Integer>(123456);
注意:
泛型的类型参数只能是类类型(Integer、String、Number),不能是简单类型。
4.2 泛型接口
//定义一个泛型接口
public interface Generator<T> {
public T next();
}
//实现泛型类:分为是否传入泛型实参
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
*/
class FruitGenerator<T> implements Generator<T>{
......
}
/**
* 传入泛型实参时:
* 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
* 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
* 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
*/
public class FruitGenerator implements Generator<String> {
......
}
4.3泛型方法
1)定义一个泛型类,例如,GenericStack{}
2)定义一个泛型方法,例如, void max(E o1, E o2){}
//泛型只在编译阶段有效
package generic;
import java.util.ArrayList;
import java.util.List;
public class GenericType {
public static void main(String[] args) {
Integer[] integers = {1,2,3,4,5};
//方法的引用
genericType.<Integer>print(integers);
}
//定义一个泛型方法
public static <E> void print(E[] list) {
for(E item:list) {
System.out.println(item+" ");
}
}
}
4.4通配泛型
在一个演示中,Integer 是Number 的子类型,但是Generic并不是Generic的子类型。为了解决通配问题,引入了通配泛型类型。
通配泛型类型有三种形式:?、? extends T 或者 ? super T
1)? : 称为非受限通配,和 ? extends Object 是一样的;
2) ? extends T : 称为受限通配,表示T 或者 T 的一个未知子类;
3)? super T : 称为下限通配,表示T 或者 T 的一个未知父类;
//泛型只在编译阶段有效
package generic;
import java.util.ArrayList;
import java.util.List;
public class genericType {
public static void main(String[] args) {
GenericStack<String> stack1 = new GenericStack<>();
GenericStack<Object> stack2 = new GenericStack<>();
stack2.push("Java");
stack2.push(2);
stack1.push("Sun");
add(stack1,stack2);
print(stack2);
}
//第三种“? super T”的应用
public static <E> void add(GenericStack<E> stack1,GenericStack<? super E> stack2) {
while(!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
public static void print(GenericStack<?> stack) {
while(!stack.isEmpty()) {
System.out.println(stack.pop()+" ");
}
}
}
4.5、泛型的总结
(1)类型安全。
通过知道使用泛型定义的变量的类型限制,编译器可以更有效地提高Java程序的类型安全。
(泛型可以把使用Object的错误提前到编译后,而不是运行后,提升安全性。)
(2)消除强制类型转换。
消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。所有的强制转换都是自动和隐式的。
(3)提高性能。
4.6、注意事项:
泛型的类型参数只能是类类型(Integer、String、Number),不能是简单类型。
在JAVA的虚拟机中并不存在泛型,泛型只是为了增加程序员编程的便捷性以及安全性而创建的一种机制,在编写泛型代码后,java虚拟中会把这些泛型参数类型都擦除,用相应的确定类型来代替。
不能创建参数化类型的数组 new T[ ]
不能实例化类型变量 new T( );
不能再静态域或方法中出现参数类型
如果有两个接口是同一接口的不同参数化,那么一个类不能同时成为这两个接口类型的子类(例如:Integer 是Number 的子类型,但是Generic并不是Generic的子类型。)
4.7 java泛型-类型参数命名约定(仅是习惯,如果不这样命名,也不会报错)
按照约定,类型参数名称命名为单个大写字母,以便可以在使用普通类或接口名称时能够容易地区分类型参数。以下是常用的类型参数名称列表 -
E - 元素Element,主要由Java集合(Collections)框架使用。
K - 键Key,主要用于表示映射中的键的参数类型。
V - 值Value,主要用于表示映射中的值的参数类型。
N - 数字Number,主要用于表示数字。
T - 类型(Type),主要用于表示第一个泛型类型参数。
S - 类型(Type),主要用于表示第二个泛型类型参数。
U - 类型(Type),主要用于表示第三个泛型类型参数。
V - 类型(Type),主要用于表示第四个泛型类型参数。