Java 面向对象(十二)

泛型

什么是泛型

泛型:广泛通用的类型

一开始还不确定是什么类型,在使用的时候,才能确定是什么类型

(1)在开始定义的时候,留一个插口

(2)在创建对象的时候,再去插入对应的类型

泛型也可以理解为“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。

顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

泛型是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

泛型的定义

泛型类:public class Demo {} ,T表示未知类型。

泛型接口:public interface ImplDemo<T,V>{} ,和定义类一样(接口就是一个特殊类)。

泛型方法:public void demo1(T name){ } , public T demo2(T t){ return t;}

<>括号中,名字可以是任意的,通常使用 T:Type,E:Element,K:Key,V:Value。

代码模板中类型不确定,谁调用该段代码,谁就可以来指明这个类型。

如果没有指明泛型类型,它默认就是Object类型

泛型的好处

(1)编译时确定类型,保证类型安全,避免类型转换异常。

(2)避免了强制类型转换。

(3)代码利于重用,增加通用性。

泛型的本质

泛型其实是一个语法糖,本质还是Object,内部其实还是要做强转。

class Demo<T> {
    T x;    
    public Demo(T x) {
        this.x = x;
    }
    public T getX() {
        return x;
    }
}

public class Test {
    public static void main(String[] args) {
        Demo<String> demo = new Demo<>("SSS");
        String str = demo.x;
        System.out.println(str);
    }
}

反编译后:

class Demo
{

    Object x;

    public Demo(Object x)
    {
        this.x = x;
    }

    public Object getX()
    {
        return x;
    }
}

public class Test
{

    public Test()
    {
    }

    public static void main(String args[])
    {
        Demo demo = new Demo("SSS");
        String str = (String)demo.x;
        System.out.println(str);
    }
}

泛型类和泛型方法

(1)泛型类

在类上面定义的泛型,class 类名 {},在创建对象的时候,要指明泛型的类型。

泛型类当中定义的泛型只能用在普通方法上面,不能使用在静态方法上面。

(2)泛型方法

就是在方法上面添加了泛型,是在使用方法时,参数传递确定具体的类型。

方法想要单独使用泛型,必须要有参数才有意义。

class Demo {
    public <T> void demo1(T t) {
        System.out.println(t.getClass());
    }

    public <T> T demo2(T t) {
        System.out.println(t.getClass());
        return t;
    }
    
    public static <T> void demo3(T t) {
        System.out.println(t.getClass());
    }
}

public class Test {
    public static void main(String[] args) {
        Demo demo = new Demo();
        demo.demo1("str");// class java.lang.String
        demo.demo1(10);// class java.lang.Integer
        demo.demo1(10.5);// class java.lang.Double
        Demo.demo3(true);// class java.lang.Boolean

    }
}

泛型通配符

通配符:不知道使用什么类型来接收的时候,可以使用?表示未知。

(1)无边界的通配符

无边界的通配符的主要作用就是让泛型能够接受未知类型的数据。

public static void printList(List<?> list) {
    for (Object o : list) {
        System.out.println(o);
    }
}

public static void main(String[] args) {
    List<String> l1 = new ArrayList<>();
    l1.add("aa");
    l1.add("bb");
    l1.add("cc");
    printList(l1);
    List<Integer> l2 = new ArrayList<>();
    l2.add(11);
    l2.add(22);
    l2.add(33);
    printList(l2);

}

这种使用List<?>的方式就是父类引用指向子类对象。注意,这里的printList方法不能写成public static void printList(List list)的形式,虽然Object类是所有类的父类,但是List跟其他泛型的List如List , List 不存在继承关系,因此会报错。

我们不能对List<?>使用add方法,仅有一个例外,就是add(null)。 为什么呢?因为我们不确定该List的类型,不知道add什么类型的数据才对,只有null是所有引用数据类型都具有的元素

public static void addTest(List<?> list) {
    list.add(new Object()); // 编译报错
    list.add(1); // 编译报错
    list.add("ABC"); // 编译报错
    list.add(null);
}

public static void main(String[] args) {
    List<?> list = new ArrayList<>();
    addTest(list);
}

由于我们根本不知道list会接受到具有什么样的泛型List,所以除了null之外什么也不能add。

还有, List<?>也不能使用get方法,只有Object类型是个例外。 原因也很简单,因为我们不知道传入的List是什么泛型的,所以无法接受得到的get,但是Object是所有数据类型的父类,所以只有接受他可以。

public static void getTest(List<?> list) {
    String s = list.get(0); // 编译报错
    Integer i = list.get(1); // 编译报错
    Object o = list.get(2);
}

public static void main(String[] args) {
    List<?> list = Arrays.asList(1, 2, 3, 4);
    getTest(list);
}

(2)固定上边界的通配符

用来限定元素的类型必须得指定类的子类(包括指定类和指定类的子类)

public static double sumOfList(List<? extends Number> list) {
    double s = 0.0;
    for (Number n : list) {
        // 注意这里得到的n是其上边界类型的, 也就是Number, 需要将其转换为double.
        s += n.doubleValue();
    }
    return s;
}

public static void main(String[] args) {
    List<Integer> list1 = Arrays.asList(1, 2, 3, 4);
    System.out.println(sumOfList(list1));
    List<Double> list2 = Arrays.asList(1.1, 2.2, 3.3, 4.4);
    System.out.println(sumOfList(list2));
}

List<? extends E> 不能使用add方法

public static void addTest2(List<? extends Number> l) {
    // l.add(1); // 编译报错
    // l.add(1.1); //编译报错
    l.add(null);
}

泛型<? extends E>指的是E及其子类,这里传入的可能是Integer,也可能是Double,我们在写这个方法时不能确定传入的什么类型的数据,所以除了null之外什么也不能add。但是get的时候是可以得到一个Number, 也就是上边界类型的数据的,因为不管存入什么数据类型都是Number的子类型,得到这些就是一个父类引用指向子类对象。

(3)固定下边界的通配符

用来限定元素的类型必须得指定类的父类(包括指定类和指定类的父类)

public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}

public static void main(String[] args) {
    List<Object> list1 = new ArrayList<>();
    addNumbers(list1);
    System.out.println(list1);
    List<Number> list2 = new ArrayList<>();
    addNumbers(list2);
    System.out.println(list2);
    List<Double> list3 = new ArrayList<>();
    addNumbers(list3); // 编译报错
}

List<? super E>是能够调用add方法的,因为我们在addNumbers所add的元素就是Integer类型的,而传入的list不管是什么,都一定是Integer或其父类泛型的List,这时add一个Integer元素是没有任何疑问的。但是, 我们不能使用get方法,因为我们所传入的类都是Integer的类或其父类,所传入的数据类型可能是Integer到Object之间的任何类型,这是无法预料的,也就无法接收。唯一能确定的就是Object,因为所有类型都是其子类型。

泛型的限制和规则

(1)泛型的类型参数只能是引用类型,不能使用值类型(是不会自动装箱的)。

(2)泛型的类型参数可以有多个。

(3)泛型前后类型必须得要保持一致,即 ArrayList list = new ArrayList (); 从JAVA7开始,后面的类型可以不写 ArrayList list = new ArrayList<>(); 菱形语法。

(4)泛型类不是真正存在的类,不能使用instanceof运算符。

(5)泛型类的类型参数不能用在静态申明。

(6)如果定义了泛型,不指定具体类型,泛型默认指定为Ojbect类型。

(7)泛型使用?作为类型通配符,表示未知类型,可以匹配任何类型。因为是未知,所以无法添加元素。

(8)类型通配符上限:<? extends T>,?代表是T类型本身或者是T的子类型。常用于泛型方法,避免类型转换。

(9)类型通配符下限。<? super T>,?代表T类型本身或者是T的父类型。
除了通配符可以实现限制,类、接口和方法中定义的泛型参数也能限制上限和下限。

泛型擦除

泛型擦除:把泛型给去掉。

// 泛型擦除(把泛型给去掉)
List<String> list = new ArrayList<>();
list.add("aa");

List list2 = null;
list2 = list; // 把list当中的泛型给擦除掉
list2.add(10);
list2.add("bb");
System.out.println(list2);
posted @ 2019-08-04 15:06 Lomen~ 阅读( ...) 评论( ...) 编辑 收藏

猜你喜欢

转载自www.cnblogs.com/xzh0717/p/11298284.html