java泛型详解

1.泛型类的定义

1.1 为什么需要泛型

假设我们自定义了一个简单的数组类,如下:

public class MyArray {
    private int[] array = null;
    private int size;
    private int capacity;
    public MyArray(int capacity){
        array = new int[capacity];
        size = 0;
        this.capacity = capacity;
   }
    public void add(int index, int data){
        if(size < capacity)
            array[size++] = data;
   }
    public int get(int index){
        return array[index];
   }
    public int size(){
        return  size;
   }
    public static void main(String[] args) {
        // 创建一个MyArray对象,里面存储int类型的数据
        MyArray m1=new MyArray(10);
        m1.add(0,1);
        m1.add(1,2;
        m1.add(2,3);
        for(int i=0;i<m1.size();++i){
        System.out.println(m1.get(i)+"");
        }
         System.out.println();
         //但是,如果创建一个MyArray对象,里面要想存doulble类型的数据
         //代码就不能通过编译
         MyArray m2=new MyArray(10);
         m2.add(0,1.0)
         m2.add(1,2.0)
         m2.add(2,3.0)
         }
         }
        

通过上述示例发现,MyArray类中实际只能保存int类型的数据,对于其他类型的数据比如:double、String或者自定义类型的对象,根本无法存储。
想要解决上述问题,最简单的方式就是:对于不同的类型,分别实现各自的MyArray类即可,但是估计你可能不愿意。业界有大佬是按照如下方式改进的:将上述代码中存储数据的类型全部有int改为Object,因为在Java中Object是所有类的基类。

 class Person {
    public String name;
    public Person(String name) {
        this.name = name;
    }
}

public class MyArray {
    private Object[] array = null;
    private int size;
    private int capacity;
    public MyArray(int capacity){
        array = new Object[capacity];
        size = 0;
        this.capacity = capacity;
   }
    public void add( Object data){
        if(size < capacity)
            array[size++] = data;
   }
    public Object get(int index){
        return array[index];
   }
    public int size(){
        return  size;
   }
    public static void main(String[] args) {
        // 创建一个MyArray对象,里面存储int类型的数据
        MyArray m1=new MyArray(10);
        m1.add(0);
        m1.add(1;
        m1.add(2);
        System.out.println("===============");
         // 创建一个MyArray对象,里面存储浮点类型的数据
        MyArray m2=new MyArray(10);
        m2.add(1.0);
        m2.add(2.0);
        m2.add(3.0);
        System.out.println("===============");
        // 创建一个MyArray对象,里面存储Person类型的数据
        MyArray m3 = new MyArray(10);
        m3.add(new Person("Peter"));
        m3.add(new Person("Jim"));
        m3.add(new Person("David")); 
         }
 }

经改过之后的MyArray终于任意类型都可以存储了,最后:代码只需实现一份,但是任意类型都可以存储,貌似一切都比较美好。但是大家使用之后,纷纷吐槽:因为Object是所有类的基类,那就意味着可以在一个MyArray中存储不同种类的数据类型喽:

public static void main(String[] args) {
        MyArray m = new MyArray(10);
        m.add(1);
        m.add(2.0);
        m.add("Peter");
        m.add(new Person("David"));
        for(int i = 0; i < m.size(); ++i){
            String s = (String)m.get(i);
            System.out.print(s + " ");
       }
   }

虽然代码可以通过编译,但是如果想要遍历MyArray中的数据,怎么遍历啊~~~
上述代码再运行期间报错: Exception in thread “main” java.lang.ClassCastException:
java.lang.Integer cannot be cast to java.lang.String;运行时出错的原因非常简单:上述代码中,由于MyArray存储数据不都全是String类型的,那如果强转成String类型之后,肯定会发生类型转换异常。以上就是JDK1.5之前的解决方式,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要作显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以在预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患,为了解决该问题,JDK1.5中引入了泛型。

1.2 泛型的概念

泛型是java1.5中增加的一个新特性,通过泛型可以写与类型无关的代码,即编写的代码可以被很多不同类型的对象所重用,经常用在一些通用的代码实现中,比如:java集合框架中的类几乎都是用泛型实现的。
泛型的本质是:类型参数化。类似函数传参一样,传递不同的实参,函数运行完将会产生不同的结果。

1.3 泛型的分类

泛型主要包含:泛型类、泛型方法和泛型接口。

2. 泛型类

2.1 泛型类的定义

class 泛型类名称<类型形参列表> {
    // 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {  
    // 类实现体
}

类型形参一般使用一个大写字母表示,常用的名称有:
E 表示 Element
K 表示 Key
V 表示 Value
N 表示 Number
T 表示 Type
S, U, V 等等 - 第二、第三、第四个类型。

2.2 泛型类的例子


public class MyArray<T> {
    private T[] array=null;
    private int size;
    private int capacity;

    public MyArray(int capacity) {
        //由于T是啥类型,不确定,所以不能直接new,而是通过强制类型转换
        array=(T[])new Object[capacity];
        size=0;
        this.capacity=capacity;
    }

    public void add(T data){
        if(size<capacity){
            array[size++]=data;
        }
    }
    public T get(int index){
        return array[index];
    }
    public int size(){
        return this.size;
    }
}

2.3 泛型类的实例化

2.3.1 实例化语法
泛型类<类型实参> 变量名; 定义一个泛型类引用。 new 泛型类<类型实参>(构造方法实参); 实例化一个
泛型类对象。

class Person{
    public String name;

    public Person(String name) {
        this.name = name;
    }
}
public class MyArray<T> {
    private T[] array=null;
    private int size;
    private int capacity;

    public MyArray(int capacity) {
        //由于T是啥类型,不确定,所以不能直接new,而是通过强制类型转换
        array=(T[])new Object[capacity];
        size=0;
        this.capacity=capacity;
    }

    public void add(T data){
        if(size<capacity){
            array[size++]=data;
        }
    }
    public T get(int index){
        return array[index];
    }
    public int size(){
        return this.size;
    }
    public static void main(String[] args) {
        // 将泛型类使用String类型来实例化,表明m中只能存放String类型的对象
        MyArray<String> m = new MyArray<>(10);
        m.add("Peter");
        m.add("David");
        m.add("Jim");
        // 编译失败:因为在实例化时,已经明确其内部只能存储String类型的对象
        // Person对象和String对象之间不能转换
        // m.add(new Person("Lily"));
        for(int i = 0; i < m.size(); ++i){
            // 此处从m中获取到的成员,再不需要进行强制类型转换了
            String s = m.get(i);
            System.out.print(s +"");
        }
    }
}

注意:
右侧的<>中的类型可以省略不写;左侧的<>中的类型不能省略不写。

2.4 泛型类的定义-类型边界

在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。

class 泛型类名称<E extends U> {
   ...
}

在实例化时,E只能是U或者U的子类。

class Animal{
     String name;

    public Animal(String name) {
        this.name = name;
    }
}

class Cat extends Animal{
    public Cat(String name) {
        super(name);
    }
}
public class MyArray<T extends Animal> {
    private T[] array=null;
    private int size;
    private int capacity;

    public MyArray(int capacity) {
        //由于T是啥类型,不确定,所以不能直接new,而是通过强制类型转换
        array=(T[])new Object[capacity];
        size=0;
        this.capacity=capacity;
    }

    public void add(T data){
        if(size<capacity){
            array[size++]=data;
        }
    }
    public T get(int index){
        return array[index];
    }
    public int size(){
        return this.size;
    }
    public static void main(String[] args) {
       MyArray<Animal> a=new MyArray<>(10);
       a.add(new Animal("老虎"));
       MyArray<Cat> c=new MyArray<>(10);
       c.add(new Cat("小猫"));
       
        // 编译失败,因为String不是Animal的子类
   //   MyArray<String> m3 = new MyArray<>(10);
    }
}

2.5 泛型类的使用-通配符(Wildcards)

通配符上界(语法格式):

<? extends 上界>

代码示例:

class Animal{
     String name;

    public Animal(String name) {
        this.name = name;
    }
}

class Cat extends Animal{
    public Cat(String name) {
        super(name);
    }
}
public class MyArray<T> {
    private T[] array=null;
    private int size;
    private int capacity;

    public MyArray(int capacity) {
        //由于T是啥类型,不确定,所以不能直接new,而是通过强制类型转换
        array=(T[])new Object[capacity];
        size=0;
        this.capacity=capacity;
    }

    public void add(T data){
        if(size<capacity){
            array[size++]=data;
        }
    }
    public T get(int index){
        return array[index];
    }
    public int size(){
        return this.size;
    }
    public static void main(String[] args) {
        //传入Animal及其子类都是可以的
        printall(new MyArray<Cat>(10));
        printall(new MyArray<Animal>(10));
        //传入其他类型则不可以
//        printall(new MyArray<String>(10)); //编译报错

    }
    public static void printall(MyArray<?extends Animal> c){
        System.out.println(c);
    }
}

通配符下界:

<? super 下界>

代码示例:

class Animal{
     String name;

    public Animal(String name) {
        this.name = name;
    }

    public Animal() {

    }

    @Override
    public String toString() {
        return "Animal{" +
                "name='" + name + '\'' +
                '}';
    }
}

class Cat extends Animal{
    public Cat(String name) {
        super(name);
    }
}
public class MyArray<T> {
    private T[] array=null;
    private int size;
    private int capacity;

    public MyArray(int capacity) {
        //由于T是啥类型,不确定,所以不能直接new,而是通过强制类型转换
        array=(T[])new Object[capacity];
        size=0;
        this.capacity=capacity;
    }

    public void add(T data){
        if(size<capacity){
            array[size++]=data;
        }
    }
    public T get(int index){
        return array[index];
    }
    public int size(){
        return this.size;
    }
    public static void main(String[] args) {
        //传入Animal及其Animal的父类都是可以的
        printall(new MyArray<Object>(10));
        printall(new MyArray<Animal>(10));
        //传入其他类型则不可以
//        printall(new MyArray<Cat>(10)); //Cat是Animal的子类,是会报错的

    }
    public static void printall(MyArray<? super Animal> c){
        System.out.println(c.get(0));
    }
}

2.6 泛型中的父子类型

public class MyArray<E> { ... }
// MyArray<Object> 不是 MyArray<Animal> 的父类型
// MyArray<Animal> 也不是 MyArray<Cat> 的父类型
// 需要使用通配符来确定父子类型
// MyArray<?> 是 MyArray<? extends Animal> 的父类型
// MyArray<? extends Animal> 是 MyArrayList<Dog> 的父类型

3.泛型方法

1 语法格式

方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }

2.代码示例


class Animal{
    String name;

    public Animal(String name) {
        this.name = name;
    }
    public Animal() {

    }

    public void eat(){
        System.out.println("吃吃吃,大吃货!");
    }
}


public class Test0525_1 {
    //<T>可以理解位泛型方法的标志,T就是返回值
    public  static <T> T myprint(Class<T> c) throws IllegalAccessException, InstantiationException {

        return c.newInstance(); //本质调用无参构造器,实例化对象

    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Animal a1 = myprint(Animal.class);
        a1.eat();

    }
}

4. 泛型接口

代码示例:


import java.util.Arrays;

class Peson implements Comparable<Peson> {
    String name;
    int age;

    public Peson(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Peson o) {
        //要利用Intager类重写好的CompareTo来排序
        return new Integer(age).compareTo(o.age);
    }

    @Override
    public String toString() {
        return "Peson{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test0525 {
    public static void main(String[] args) {
        Peson[] p={new Peson("达摩",20),
                   new Peson("三藏",25),
                   new Peson("宫本",22)};
        Arrays.sort(p);
        System.out.println(Arrays.toString(p));
    }

}

5. 类型擦除

5.1什么是类型擦除:

Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。 即泛型类和普通类在 java 虚拟机内是没有什么特别的地方。
原理图如下:
在这里插入图片描述

5.2 类型擦除的规则

1:消除类型参数声明,即删除<>及其包围的部分。
2:根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。
3:为了保证类型安全,必要时插入强制类型转换代码。
4:自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性“。

5:代码示例:

// 1. 无限制类型擦除---<E>和<?>类型参数都被替换为Object
class MyArray<E> {
    // E 会被擦除为 Object
}
// 2. 有限制类型擦除---<T extends Animal>和<? extends Animal>的类型参数被替换为Animal
//                   <? super Animal>被替换为Object
class MyArray<E extends Animal> {
    // E 被擦除为 Animal
}
// 3. 擦除方法中的类型参数
public class Util {
    public static <E> void swap(E[] array, int i, int j) {
        // ...
        // <E>删除掉 E被擦除为Object
   }
}

总结: 即类型擦除主要看其类型边界而定

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JavaJava 5引入的新特性,可以提高代码的可读性和安全性,降低代码的耦合度。是将类参数化,实现代码的通用性。 一、的基本语法 在声明类、接口、方法时可以使用的声明方式为在类名、接口名、方法名后面加上尖括号<>,括号中可以声明一个或多个类参数,多个类参数之间用逗号隔开。例如: ```java public class GenericClass<T> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public interface GenericInterface<T> { T getData(); void setData(T data); } public <T> void genericMethod(T data) { System.out.println(data); } ``` 其中,`GenericClass`是一个类,`GenericInterface`是一个接口,`genericMethod`是一个方法。在这些声明中,`<T>`就是类参数,可以用任何字母代替。 二、的使用 1. 类的使用 在使用类时,需要在类名后面加上尖括号<>,并在括号中指定具体的类参数。例如: ```java GenericClass<String> gc = new GenericClass<>(); gc.setData("Hello World"); String data = gc.getData(); ``` 在这个例子中,`GenericClass`被声明为一个类,`<String>`指定了具体的类参数,即`data`字段的类为`String`,`gc`对象被创建时没有指定类参数,因为编译器可以根据上下文自动推断出类参数为`String`。 2. 接口的使用 在使用接口时,也需要在接口名后面加上尖括号<>,并在括号中指定具体的类参数。例如: ```java GenericInterface<String> gi = new GenericInterface<String>() { private String data; @Override public String getData() { return data; } @Override public void setData(String data) { this.data = data; } }; gi.setData("Hello World"); String data = gi.getData(); ``` 在这个例子中,`GenericInterface`被声明为一个接口,`<String>`指定了具体的类参数,匿名内部类实现了该接口,并使用`String`作为类参数。 3. 方法的使用 在使用方法时,需要在方法名前面加上尖括号<>,并在括号中指定具体的类参数。例如: ```java genericMethod("Hello World"); ``` 在这个例子中,`genericMethod`被声明为一个方法,`<T>`指定了类参数,`T data`表示一个类为`T`的参数,调用时可以传入任何类的参数。 三、的通配符 有时候,我们不知道的具体类,可以使用通配符`?`。通配符可以作为类参数出现在方法的参数类或返回类中,但不能用于声明类或接口。例如: ```java public void printList(List<?> list) { for (Object obj : list) { System.out.print(obj + " "); } } ``` 在这个例子中,`printList`方法的参数类为`List<?>`,表示可以接受任何类的`List`,无论是`List<String>`还是`List<Integer>`都可以。在方法内部,使用`Object`类来遍历`List`中的元素。 四、的继承 类和接口可以继承或实现其他类或接口,可以使用子类或实现类的类参数来替换父类或接口的类参数。例如: ```java public class SubGenericClass<T> extends GenericClass<T> {} public class SubGenericInterface<T> implements GenericInterface<T> { private T data; @Override public T getData() { return data; } @Override public void setData(T data) { this.data = data; } } ``` 在这个例子中,`SubGenericClass`继承了`GenericClass`,并使用了相同的类参数`T`,`SubGenericInterface`实现了`GenericInterface`,也使用了相同的类参数`T`。 五、的限定 有时候,我们需要对的类参数进行限定,使其只能是某个类或接口的子类或实现类。可以使用`extends`关键字来限定类参数的上限,或使用`super`关键字来限定类参数的下限。例如: ```java public class GenericClass<T extends Number> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public interface GenericInterface<T extends Comparable<T>> { T getData(); void setData(T data); } ``` 在这个例子中,`GenericClass`的类参数`T`被限定为`Number`的子类,`GenericInterface`的类参数`T`被限定为实现了`Comparable`接口的类。 六、的擦除 在Java中,信息只存在于代码编译阶段,在编译后的字节码中会被擦除。在运行时,无法获取的具体类。例如: ```java public void genericMethod(List<String> list) { System.out.println(list.getClass()); } ``` 在这个例子中,`list`的类为`List<String>`,但是在运行时,`getClass`返回的类为`java.util.ArrayList`,因为信息已经被擦除了。 七、的类推断 在Java 7中,引入了钻石操作符<>,可以使用它来省略类参数的声明。例如: ```java List<String> list = new ArrayList<>(); ``` 在这个例子中,`ArrayList`的类参数可以被编译器自动推断为`String`。 八、总结 Java是一个强大的特性,可以提高代码的可读性和安全性,降低代码的耦合度。在使用时,需要注意它的基本语法、使用方法、通配符、继承、限定、擦除和类推断等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值