Java基础-泛型

来源:互联网 发布:three.js 阴影 锯齿 编辑:程序博客网 时间:2024/05/16 02:12

  • 一泛型概述
    • 定义泛型
    • 从泛型类派生子类
    • 并不存在泛型类
  • 二类型通配符
    • 设定类型通配符的上限
    • 设定类型形参的上限
  • 三泛型方法
    • 泛型方法和类型通配符的区别
    • 泛型构造器
    • 设定通配符下限

一、泛型概述

所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可以称为类型实参)

定义泛型

不仅仅集合类可以用泛型,可以为任何类、接口增加泛型声明

下面定义一个Apple类,包含一个泛型声明:

public class Apple<T> {    private T info;    public Apple(){}    // 下面方法中使用T类型形参来定义构造器    public Apple(T info)    {        this.info = info;    }    public void setInfo(T info)    {        this.info = info;    }    public T getInfo()    {        return this.info;    }    public static void main(String[] args) {        // TODO Auto-generated method stub        Apple<String> a1 = new Apple<>("红富士");        System.out.println(a1.getInfo());        Apple<Double> a2 = new Apple<>(12.3);        System.out.println(a2.getInfo());    }}

从泛型类派生子类

当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类派生子类,需要指出的是,当使用这些接口、父类时,不能再包含类型形参。

  • 定义类、接口、方法时可以声明数据形参
  • 使用类、接口、方法时必须为这些数据形参传入实际的数据
//错误,Apple类不能跟类型形参public class A extends Apple<T>{} //正确public class A entends Apple<String>{} //正确,与调用方法不同,使用类接口时也可以不为类型形参传入实际的类型参数public class A entends Apple{} 

下面的代码示范了第一种正确的定义方式,从子类继承到String getInfo() 和void setInfo(String Info)两个方法:

public class A1 extends Apple<String>{    //返回值与父类相同    public String getInfo(){        return "子类" + super.getInfo();    }}

下面的代码示范了第二种正确的定义方式,Java编译器可能发出警告,此时系统会把父类中的T当成Object类型处理:

public class A2 extends Apple{    public String getInfo(){        //super.getInfo()返回值是Object类型,加toString()返回String        return super.getInfo().toString();    }}

并不存在泛型类

public class test {    public static void main(String[] args) {        Apple<String> a1 = new Apple<>();        Apple<Double> a2 = new Apple<>();        //返回true        System.out.println(a1.getClass() == a2.getClass());    }}

不管为泛型的类型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一个类处理,在内存中也只占用一块内存空间。

静态方法、静态初始化块或静态变量的声明和初始化中不允许使用类型形参。

由于系统中并不会真正生产泛型类,所以instanceof运算符后面不能使用泛型类。

二、类型通配符

定义一个方法,该方法里有一个集合形参,集合形参的元素类型是不确定的,可以用到类型通配符。类型通配符是一个问号。

import java.util.ArrayList;import java.util.List;public class ListTest {    public void test(List<?> c){        for(int i=0;i<c.size();i++){            System.out.println(c.get(i));        }    }    public static void main(String[] args) {        // TODO Auto-generated method stub        List<String> l1 = new ArrayList<>();        l1.add("Generic");        l1.add("Socket");        l1.add("Exception");        List<Double> l2 = new ArrayList<>();        l2.add(12.34);        l2.add(3.14);        ListTest lt = new ListTest();        lt.test(l1);        lt.test(l2);    }}

设定类型通配符的上限

若不希望?表示任何类型的父类,而是某一类的父类,可以设定类型通配符的上限。

public abstract class Shape {    public abstract void draw(Canvas c);}
public class Circle extends Shape{    public void draw(Canvas c){        System.out.println("在" + c + "上画一个圆");    }}
public class Rectangle extends Shape{    public void draw(Canvas c) {        System.out.println("在" + c + "上画一个矩形");    }}
import java.util.ArrayList;import java.util.List;public class Canvas {    //此处的?一定是Shape的子类型    public void drawAll(List<? extends Shape> shapes){        for(Shape s : shapes){            s.draw(this);        }    }    public static void main(String[] args){        List<Circle> circleList = new ArrayList<Circle>();        Circle circle1 = new Circle();        circleList.add(circle1);        Circle circle2 = new Circle();        circleList.add(circle2);        Canvas c = new Canvas();        c.drawAll(circleList);    }}

设定类型形参的上限

Java泛型不仅允许在使用通配符形参时设定上限,而且可以在定义形参时设定上限

public class Apple<T extends Number>{    T col;    public static void main(String[] args){        Apple<Integer> ai = new Apple<>();        Apple<Double> ad = new Apple<>();        // 下面代码将引起编译异常,下面代码试图把String类型传给T形参        // 但String不是Number的子类型,所以引发编译错误        Apple<String> as = new Apple<>();    }}

在更极端的情况下,程序需要为类型形参设定多个上限(至多有一个父类上限,可以有多个接口上限)

public class Apple<T extends Number & java.io.Serializable>{  ......}

三、泛型方法

泛型方法的语法格式如下:

修饰符 <T,S> 返回值类型 方法名(形参列表){  //方法体}
import java.util.ArrayList;import java.util.Collection;public class GenericMethodTest {    static <T> void fromArrayToCollection(T[] a,Collection<T> c){        for(T t:a){            c.add(t);        }    }    public static void main(String[] args) {        Object[] oa = new Object[100];        Collection<Object> co = new ArrayList<>();        // 下面代码中T代表Object类型        fromArrayToCollection(oa, co);        Integer[] ia = new Integer[100];        Float[] fa = new Float[100];        Number[] na = new Number[100];        Collection<Number> cn = new ArrayList<>();        // 下面代码中T代表Number类型        fromArrayToCollection(ia, cn);        // 下面代码中T代表Number类型        fromArrayToCollection(fa, cn);        // 下面代码中T代表Number类型        fromArrayToCollection(na, cn);    }}

泛型方法和类型通配符的区别

大多数时候都可以使用泛型方法来代替类型通配符。

使用类型通配符:

public interface Collection<E>{  boolean containsAll(Collection<?> c);  boolean addAll(Collection<? extends E> c);  ......}

使用泛型方法:

public interface Collection<E>{  <T> boolean containsAll(Collection<T> c);  <T extends> boolean addAll(Collection<T> c);  ......}

下面例子中,没必要使用泛型方法:

public class Collections{  public static <T,S extends T> void copy(List<T> dest,List<S> src){}}

因为S仅使用了一次,其他参数类型、方法返回值的类型都不依赖它,可以写成如下形式:

public class Collections{  public static <T> void copy(List<T> dest,List<? extends T> src){}}

泛型构造器

Java允许在构造器签名中声明类型形参

class Foo{    public <T> Foo(T t)    {        System.out.println(t);    }}public class GenericConstructor{    public static void main(String[] args)    {        // 泛型构造器中的T参数为String。        new Foo("疯狂Java讲义");        // 泛型构造器中的T参数为Integer。        new Foo(200);        // 显式指定泛型构造器中的T参数为String,        // 传给Foo构造器的实参也是String对象,完全正确。        new <String> Foo("疯狂Android讲义");        // 显式指定泛型构造器中的T参数为String,        // 但传给Foo构造器的实参是Double对象,下面代码出错        new <String> Foo(12.3);    }}

设定通配符下限

Java允许设定通配符下限,如下面代码所示:

import java.util.ArrayList;import java.util.Collection;import java.util.List;public class MyUtils {    public static <T> T copy(Collection<? super T> dest, Collection<T> src){            T last = null;            for (T ele  : src)            {                last = ele;                dest.add(ele);            }            return last;        }    public static void main(String[] args) {        List<Number> ln = new ArrayList<>();        List<Integer> li = new ArrayList<>();        li.add(5);        // 此处可准确的知道最后一个被复制的元素是Integer类型        // 与src集合元素的类型相同        Integer last = copy(ln , li);        System.out.println("last = " + last + ",ln = " + ln);    }}
0 0