泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
那么,我们为什么要使用泛型呢?
首先,我们写一个通用类型的栈
class ObjectStack{
private Object[] elem;
private int top;//
public ObjectStack(){
this(10);
}
public ObjectStack(int size){
this.elem = new Object[size];
this.top = 0;
}
public void push(Object val){
this.elem[this.top++] = val;
}
public void pop(){
--this.top;
}
public Object getTop(){
return this.elem[this.top-1];
}
}
public class ObjectStackDemo {
public static void main(String[] args) {
ObjectStack s1 = new ObjectStack(10);
s1.push(10);
s1.push(20);
s1.push("hello");
String data = (String)s1.getTop();//必须进行强制转换,如果不进行强制转换,会报错
System.out.println(data);
}
}
由于 Object 是所有类的父类,所有的类都可以作为成员被添加到上述类中;当需要使用的时候,必须进行强制转换,而且这个强转很有可能出现转换异常。
使用 Object 来实现通用、不同类型的处理,有这么两个缺点:
1,每次使用时都需要强制转换成想要的类型
2,在编译时编译器并不知道类型转换是否正常,运行时才知道,不安全
所以,我们在此引入了泛型
利用泛型写一个栈
class Stack<T>{//这里的T一个类型占位符
public T []elem;
int top;
public Stack(){
this(10);
}
public Stack(int i) {
//this.elem=new T[i];我们不能new一个泛型类型的数组
this.elem=(T[])new Object[i];
this。.top=0;
}
public void rush(T val){
this.elem[this.top]=val;
top++;
}
public void pop(){
this.elem[this.top-1]=null;
this.top--;
}
public T gettop(){
return this.elem[this.top-1];
}
}
上述图片中我们可以看到,在代码中,当我们规定T为Integer类型的时候,如果我们入栈一个非Integer类型的数据时候,编译器在编译过程中就会报错。
由此可见,泛型的意义在于它可以在编译期间就可以对数据类型进行检查。并且在得到数据时,我们不必进行强制转换
实际上,我们可以引入泛型的意义在于俩点:
1,对类型进行自动检查 编译过程中
2,会进行自动类型转换
泛型的擦除机制:
在编译期间,所有的泛型信息都会被擦除,如上面代码中的Ingeter类型,在编译后都会变成object类型。这也是Java的泛型被称为“伪泛型”的原因。
使用泛型的过程中,我们要注意以下几点:
1.不能new泛型类型的数组
2.不能new泛型类型的对象
3.不能得到泛型对象的数组
4.简单类型不能作为泛型的参数,因为泛型有类型擦除机制,但是简单类型没有基类
5.在static方法当中,不能用泛型类型的参数
因为static方法不依赖于对象 , 所以无法指定当前泛型
泛型类与泛型的上界
GenericAlg是一个泛型类
如上图所示,我们要找到一个泛型数组中最大的元素,但是,我们发现,编译期发生了报错,这是为什么?
上面过程中我们提到了泛型的类型擦除,所有的类型都会被擦出到他的基类,object,但是。在object类中,我们并没有实现Comparable接口中的compareTo方法,所以,此时会报错。
此时,就引出了我们所说的泛型的上界。
class GenericAlg<T extends Comparable<T>>{//T extends .... 表示泛型的上界,在这里他表示的是泛型的实现了Comparable接口的基类
public T findMaxVal(T[] array){
T maxVal = array[0];
for(int i = 1;i < array.length;i++) {
if(maxVal.compareTo(array[i]) < 0){
maxVal = array[i];
}
}
return maxVal;
}
}
泛型方法
我们可以将上面的方法改写成static的方法,这样,我们就可以直接通过类名.方法名来使用上面的方法
class GenericAlg2{
/**
* 泛型方法
*/
public static<T extends Comparable<T>> T findMaxVal(T[] array){//在static后面
T maxVal = array[0];
for(int i = 1;i < array.length;i++) {
if(maxVal.compareTo(array[i]) < 0){
maxVal = array[i];
}
}
return maxVal;
}
}
通配符及通配符的上下界
在实例化对象的时候,不确定泛型参数的具体类型时,可以使用通配符进行对象定义
通配符的下界:
? super T
通配符的上界:
? extends T
通配符下界的用法
class Person implements Comparable<Person>{
private String name;
public Person(String name) {
super();
this.name = name;
}
@Override
public String toString() {
return "Person [name=" + name + "]";
}
@Override
public int compareTo(Person o) {
// TODO Auto-generated method stub
return name.compareTo(o.name);
}
}
class Student extends Person{
private int age;
public Student(String name, int age) {
super(name);
this.age = age;
}
@Override
public String toString() {
return "Student [age=" + age + "]";
}
}
上面代码中我们创建了俩个类,一个person类,另一个类student继承了person类
class GenericAlg3{
//泛型实现
public static<T extends Comparable<T>> T findMaxPerson1(ArrayList<T> list){
T maxPerson = list.get(0);
for(int i = 1;i < list.size();i++){
if(maxPerson.compareTo(list.get(i)) < 0){
maxPerson = list.get(i);
}
}
return maxPerson;
}
在这个类中,我们写了得到容器内最大值的方法
上面代码中我们可以看到,person类实现了comparable接口,所以,对于list2对象使用上述方法,他并没有报错,但是,student类由于没有实现comparable接口,所以编译期进行了报错,但是,由于student类继承了person类,所以,我们也没有必要在student类中再实现一次comparable接口,此时,我们就用到了通配符的下界
public static<T extends Comparable<? super T>> T findMaxPerson(ArrayList<T> list){
//? super T 找到是否有T类型的基类实现了Comparable接口
T maxPerson = list.get(0);
for(int i = 1;i < list.size();i++){
if(maxPerson.compareTo(list.get(i)) < 0){
maxPerson = list.get(i);
}
}
return maxPerson;
}
}
通过加入通配符的下界,student的对象也可以调用上述方法。