您当前的位置: 首页 >  Java

wespten

暂无认证

  • 2浏览

    0关注

    899博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

java泛型代码编写

wespten 发布时间:2019-08-19 11:27:22 ,浏览量:2

java泛型代码编写

泛型的由来

我们先看下面这段代码:

List list = new ArrayList();
list.add(24);  //向集合中添加一个 Integer 类型的数据
list.add("Tom");    //向集合中添加一个 String 类型的数据
 
for(int i = 0 ; i < list.size() ; i++){
    Object obj = list.get(i);  //注意这里每个类型都是 Object
    System.out.println(obj);
}
 
//如果我们遍历的时候就想得到自己想要的数据类型
for(int i = 0 ; i < list.size() ; i++){
    String obj = (String) list.get(i);  //在取 Integer 的时候会报类型转换错误
    System.out.println(obj);
}

 报错信息如下:

  也就是 集合中第二个数据是 Integer,但是我们取出来的时候将其转换为 String 了,所以报错。

  那么这个如何解决呢?

  ①、我们在遍历的时候,根据每个数据的类型判断,然后进行强转。

那么我们说这个集合只有两条数据,我们可以进行判断强转,如果数据有成千上万条呢,我们都通过这样判断强转肯定不可取

  ②、在往集合中加入数据的时候,我们就做好限制,比如这个集合只能添加 String 类型的;下一个集合只能添加 Integer 类型的,那么我们在取数据的时候,由于前面已经限制了该集合的数据类型,那么就很好强转了。

  这第二种解决办法,也就是我们这篇文章讲的 泛型

  泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

  在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

 

什么是泛型

在讲解什么是泛型之前,我们先观察Java标准库提供的ArrayList,它可以看作“可变长度”的数组,因为用起来比数组更方便。

实际上ArrayList内部就是一个Object[]数组,配合存储一个当前分配的长度,就可以充当“可变数组”:

public class ArrayList {
    private Object[] array;
    private int size;
    public void add(Object e) {...}
    public void remove(int index) {...}
    public Object get(int index) {...}
}

如果用上述ArrayList存储String类型,会有这么几个缺点:

  • 需要强制转型;

  • 不方便,易出错。

例如,代码必须这么写:

ArrayList list = new ArrayList();
list.add("Hello");
// 获取到Object,必须强制转型为String:
String first = (String) list.get(0);

很容易出现ClassCastException,因为容易“误转型”:

list.add(new Integer(123));
// ERROR: ClassCastException:
String second = (String) list.get(1);

要解决上述问题,我们可以为String单独编写一种ArrayList

public class StringArrayList {
    private String[] array;
    private int size;
    public void add(String e) {...}
    public void remove(int index) {...}
    public String get(int index) {...}
}

这样一来,存入的必须是String,取出的也一定是String,不需要强制转型,因为编译器会强制检查放入的类型:

StringArrayList list = new StringArrayList();
list.add("Hello");
String first = list.get(0);
// 编译错误: 不允许放入非String类型:
list.add(new Integer(123));

问题暂时解决。

然而,新的问题是,如果要存储Integer,还需要为Integer单独编写一种ArrayList

public class IntegerArrayList {
    private Integer[] array;
    private int size;
    public void add(Integer e) {...}
    public void remove(int index) {...}
    public Integer get(int index) {...}
}

实际上,还需要为其他所有class单独编写一种ArrayList

  • LongArrayList
  • DoubleArrayList
  • PersonArrayList
  • ...

这是不可能的,JDK的class就有上千个,而且它还不知道其他人编写的class。

为了解决新的问题,我们必须把ArrayList变成一种模板:ArrayList,代码如下:

public class ArrayList {
    private T[] array;
    private int size;
    public void add(T e) {...}
    public void remove(int index) {...}
    public T get(int index) {...}
}

T可以是任何class。这样一来,我们就实现了:编写一次模版,可以创建任意类型的ArrayList

// 创建可以存储String的ArrayList:
ArrayList strList = new ArrayList();
// 创建可以存储Float的ArrayList:
ArrayList floatList = new ArrayList();
// 创建可以存储Person的ArrayList:
ArrayList personList = new ArrayList();

因此,泛型就是定义一种模板,例如ArrayList,然后在代码中为用到的类创建对应的ArrayList

ArrayList strList = new ArrayList();

由编译器针对类型作检查:

strList.add("hello"); // OK
String s = strList.get(0); // OK
strList.add(new Integer(123)); // compile error!
Integer n = strList.get(0); // compile error!

这样一来,既实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型。

向上转型

在Java标准库中的ArrayList实现了List接口,它可以向上转型为List

public class ArrayList implements List {
    ...
}

List list = new ArrayList();

即类型ArrayList可以向上转型为List

要特别注意:不能把ArrayList向上转型为ArrayListList

这是为什么呢?假设ArrayList可以向上转型为ArrayList,观察一下代码:

// 创建ArrayList类型:
ArrayList integerList = new ArrayList();
// 添加一个Integer:
integerList.add(new Integer(123));
// “向上转型”为ArrayList:
ArrayList numberList = integerList;
// 添加一个Float,因为Float也是Number:
numberList.add(new Float(12.34));
// 从ArrayList获取索引为1的元素(即添加的Float):
Integer n = integerList.get(1); // ClassCastException!

我们把一个ArrayList转型为ArrayList类型后,这个ArrayList就可以接受Float类型,因为FloatNumber的子类。但是,ArrayList实际上和ArrayList是同一个对象,也就是ArrayList类型,它不可能接受Float类型, 所以在获取Integer的时候将产生ClassCastException

实际上,编译器为了避免这种错误,根本就不允许把ArrayList转型为ArrayList

 ArrayList和ArrayList两者完全没有继承关系。

泛型就是编写模板代码来适应任意类型;

泛型的好处是使用时不必对类型进行强制转换,它通过编译器对类型进行检查;

注意泛型的继承关系:可以把ArrayList向上转型为ListT不能变!),但不能把ArrayList向上转型为ArrayListT不能变成父类)。

 

泛型的基本用法

对于上面的问题我们只需要将上述代码的 List list = new ArrayList()  改为   List list = new ArrayList();

 List list = new ArrayList();
//list.add(22);  //向集合中添加一个 Integer 类型的数据时,编译器会报错
list.add("Bob");  //向集合中添加一个 String 类型的数据
list.add("Tom");    //向集合中添加一个 String 类型的数据
 
//如果我们遍历的时候就想得到自己想要的数据类型
for(int i = 0 ; i < list.size() ; i++){
    String obj = list.get(i);  //这里就不需要强转了,前面添加的是什么类型,这里获取的就是什么类型
    System.out.println(obj);
}           

泛型是在编译阶段有效

List list1 = new ArrayList();
List list2 = new ArrayList();
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1==c2); //true   

  上述代码,由于我们知道反射是在运行时阶段,c1==c2为 true,说明了编译之后的 class 文件中是不包含任意的泛型信息的。如果不信,我们可以看 class 文件的反编译信息

java.util.List list1 = new ArrayList();
java.util.List list2 = new ArrayList();
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1.equals(c2));

  我们可以看到 反编译之后的 list1和 list2完全一样。

  结论:Java 泛型只在编译阶段有效,即在编译过程中,程序会正确的检验泛型结果。而编译成功后,class 文件是不包含任何泛型信息的

泛型类和泛型方法

public class Box {
    private T box;
    public T getBox(T t){
        this.box = t;
        return t;
    }
    public void getType(){
        System.out.println("T的实际类型为:"+box.getClass().getName());
    }
     
    public static void main(String[] args) {
        Box box = new Box();
        System.out.println(box.getBox(1));
        box.getType();
         
        System.out.println(box.getBox("Tom"));
        box.getType();
    }
 
}

输出结果为:

1
T的实际类型为:java.lang.Integer
Tom
T的实际类型为:java.lang.String

泛型通配符

  在泛型中,我们可以用 ? 来代替任意类型

public List wildCard(List list){
     
    return list;
}
 
public static void main(String[] args) {
    GenericTest gt = new GenericTest();
    //构造一个 Interger 类型的集合
    List integer = new ArrayList();
    integer.add(1);
    System.out.println(gt.wildCard(integer));
    //构造一个 String 类型的集合
    List str = new ArrayList();
    gt.wildCard(str);
    //构造一个 Object 类型的集合
    List obj = new ArrayList();
    obj.add(1);
    obj.add("a");
    System.out.println(gt.wildCard(obj));
    //构造一个 任意类型的 集合,这和 List 存放数据没啥区别
    List list = new ArrayList();
    gt.wildCard(list);
     
}

泛型的上限和下限

  ①、上限: 语法(? extends className),即只能为 className 或 className 的子类

//通配符的下限,只能是 Number 或 Number的子类
    public List wildCard(List) firstType;
            System.out.println(typeClass); // Integer
        }
class Pair {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}

class IntPair extends Pair {
    public IntPair(Integer first, Integer last) {
        super(first, last);
    }
}

因为Java引入了泛型,所以,只用Class来标识类型已经不够了。实际上,Java的类型系统结构如下:

                      ┌────┐
                      │Type│
                      └────┘
                         ▲
                         │
   ┌────────────┬────────┴─────────┬───────────────┐
   │            │                  │               │
┌─────┐┌─────────────────┐┌────────────────┐┌────────────┐
│Class││ParameterizedType││GenericArrayType││WildcardType│
└─────┘└─────────────────┘└────────────────┘└────────────┘

Java的泛型是采用擦拭法实现的;

擦拭法决定了泛型

  • 不能是基本类型,例如:int
  • 不能获取带泛型类型的Class,例如:Pair.class
  • 不能判断带泛型类型的类型,例如:x instanceof Pair
  • 不能实例化T类型,例如:new T()

泛型方法要防止重复定义方法,例如:public boolean equals(T obj)

子类可以获取父类的泛型类型

 

extends通配符

我们前面已经讲到了泛型的继承关系:Pair不是Pair的子类。

假设我们定义了Pair

public class Pair { ... }

然后,我们又针对Pair类型写了一个静态方法,它接收的参数类型是Pair

public class PairHelper {
    static int add(Pair p) {
        Number first = p.getFirst();
        Number last = p.getLast();
        return first.intValue() + last.intValue();
    }
}

上述代码是可以正常编译的。使用的时候,我们传入:

int sum = PairHelper.add(new Pair(1, 2));

注意:传入的类型是Pair,实际参数类型是(Integer, Integer)

既然实际参数是Integer类型,试试传入Pair

    public static void main(String[] args) {
        Pair p = new Pair(123, 456);
        int n = add(p);
        System.out.println(n);
    }

    static int add(Pair p) {
        Number first = p.getFirst();
        Number last = p.getLast();
        return first.intValue() + last.intValue();
    }
class Pair {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}

直接运行,会得到一个编译错误:

incompatible types: Pair cannot be converted to Pair

原因很明显,因为Pair不是Pair的子类,因此,add(Pair)不接受参数类型Pair

但是从add()方法的代码可知,传入Pair是完全符合内部代码的类型规范,因为语句:

Number first = p.getFirst();
Number last = p.getLast();

实际类型是Integer,引用类型是Number,没有问题。问题在于方法参数类型定死了只能传入Pair

有没有办法使得方法参数接受Pair?办法是有的,这就是使用Pair通配符既没有extends,也没有super,因此:

  • 不允许调用set(T)方法并传入引用(null除外);
  • 不允许调用T get()方法并获取T引用(只能获取Object引用)。

换句话说,既不能读,也不能写,那只能做一些null判断:

static boolean isNull(Pair p) {
    return p.getFirst() == null || p.getLast() == null;
}

大多数情况下,可以引入泛型参数消除通配符:

static  boolean isNull(Pair p) {
    return p.getFirst() == null || p.getLast() == null;
}

通配符有一个独特的特点,就是:Pair是所有Pair的超类:

    public static void main(String[] args) {
        Pair p = new Pair(123, 456);
        Pair p2 = p; // 安全地向上转型
        System.out.println(p2.getFirst() + ", " + p2.getLast());
    }
class Pair {
    private T first;
    private T last;

    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }

    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
    public void setFirst(T first) {
        this.first = first;
    }
    public void setLast(T last) {
        this.last = last;
    }
}

上述代码是可以正常编译运行的,因为PairPair的子类,可以安全地向上转型。

使用类似很少使用,可以用替换,同时它是所有类型的超类。

 

泛型和反射

Java的部分反射API也是泛型。例如:Class就是泛型:

// compile warning:
Class clazz = String.class;
String str = (String) clazz.newInstance();

// no warning:
Class clazz = String.class;
String str = clazz.newInstance();

调用ClassgetSuperclass()方法返回的Class类型是Class

关注
打赏
1665965058
查看更多评论
0.3420s