[Java5新特性]泛型

Java中集合的问题

Java中的集合有个缺点:就是当我们把数据放置到集合中时,集合是不会记住数据类型的。也就是说,当我们再从集合中获取到数据时,数据类型都变成了Object类型了。

换句话讲,集合对元素类型是没有任何限制的。这样可能会出现一些问题,例如如果我们要创建一个专门存储字符串的List集合的话,也可以将Integer类型数据放置进入。即使放置进去的都是字符串数据,从List集合取出时,还是需要类型转换的(因为集合中元素类型都是Object类型)。

例如下面这个例子:创建一个只保存字符串的List集合,但Integer类型的数字也是可以保存的。在从List集合中取出元素时,是需要强制类型转换的。

public class Demo {
    public static void main(String[] args) {
        // 创建一个只保存字符串的List集合
        List list = new ArrayList();
        list.add("Hello");
        list.add("World");
        list.add(1000);

        for (int i = 0; i < list.size(); i++) {
            String str = (String) list.get(i);
            System.out.println(str);
        }
    }
}

上述案例如果想要解决的话,我们可以自定义扩展ArrayList,以保证自定义ArrayList只能保存字符串数据类型。

public class Demo {
    private List list = new ArrayList();
    // 定义自定义ArrayList的add方法
    public boolean add(String ele) {
        return list.add(ele);
    }
    // 重写get方法,将get方法的访挥之类型改为String类型.
    public String get(int index) {
        return (String) list.get(index);
    }
    public int size() {
        return list.size();
    }
}

这种方式虽然有效,但局限性很大。在实际开发时,我们需要自定义大量List子类,增大开发工作量,且不具通用性。

什么是泛型

在Java 5版本后,为我们提供了泛型来解决上述问题。首先,我们来查看以下ArrayList的源代码片段:

public class ArrayList<E> {
    public int size() {
        return size;
    }

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
}

通过查看源代码我们发现,在ArrayList底层代码中,ArrayList类后跟着“”写法,实际上这就是泛型。在ArrayList类的add方法和get方法中,我们也可以看到add方法增加的元素类型是E类型,get方法获取元素返回的类型也是E类型。

所以,上述问题我们可以利用Java 5版本后提供的泛型来解决:

public class Demo {
    public static void main(String[] args) {
        // 创建一个只保存字符串的List集合
        List<String> list = new ArrayList<String>();
        list.add("Hello");
        list.add("World");

        for (int i = 0; i < list.size(); i++) {
            String str = (String) list.get(i);
            System.out.println(str);
        }
    }
}

那到底什么是泛型呢?泛型其实就是允许在定义类或接口时指定类型形参,这个类型形参将在声明变量、创建对象时确定。说白了,如果集合就像是一个装满东西的瓶子的话,那泛型就像是瓶子上的标签,指定当前这个瓶子只能存放哪一类的东西。

如何定义泛型类

利用泛型定义接口或类的具体方式是怎么样的呢?其实在上述案例中,我们查看的ArrayList类就是一个泛型类,而ArrayList类实现的List就是一个泛型接口。

public class ArrayList<E> {
    public int size() {
        return size;
    }
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
}
public interface List<E> {
    int size();
    boolean add(E e);
    E get(int index);
}

通过上面两个Java底层提供的案例,我们如果自定义泛型接口或类,应该遵循什么规则呢?

  • 在类名后编写“”表示该类接收的是泛型,T就代表泛型。在创建该类对象时,指定具体类型。(T不具备实际含义,这里可以随意指定。)
  • 在泛型类中的方法可以接收泛型参数或返回泛型。
  • 在泛型类中,泛型只能用于非static成员中。
public class Demo<T> {
    private T bean;

    public T getBean() {
        return bean;
    }

    public void setBean(T bean) {
        this.bean = bean;
    }
}

如何定义泛型方法

除了可以定义泛型接口或类之外,还有一些情况:定义接口或类时没有使用泛型,但定义方法时想自定义类型形参。在Java 5版本后还提供了泛型方法的支持。

所谓泛型方法,就是在声明方式时定义一个或多个类型参数。泛型方法的使用格式如下:

修饰符 <T, S> 返回值类型 方法名(形参列表){
    方法体
}

根据这个格式,我们来自定义一个泛型方法:

public class Demo {
    public static <T> T get(T[] ts) {
        return ts[0];
    }

    public static void main(String[] args) {
        Integer[] arr = new Integer[] { 1, 2, 3 };

        // 调用方法时,自动识别泛型!因为arr这个参数是Integer[],所以相当于给T赋值为Integer
        Integer i = get(arr);
        System.out.println(i);
    }
}

类型通配符

如果我们在使用一个泛型类时,应该为该泛型类传入一个类型实参,如果没有传递类型实参的话,就会引起泛型安全警告。下面我们来看一个例子:

public class Demo {
    public void demo(List<String> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

这个案例中,我们定义了一个方法,接收一个泛型为String类型的List集合,并遍历该List集合,打印每一个元素内容。但问题是该方法只能遍历打印泛型为String类型的List集合,如果要想完成同样功能的Integer类型的话,我们需要重新定义一个方法。

public class Demo {
    public void demo(List<Integer> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

但问题在于,当上面两个方法出现在同一个类中时,会报错“Method demo(List) has the same erasure demo(List) as another method in type Demo”。原因在于除了泛型不同之外,实际上这两个方法是相同的,而在同一个类中是不允许出现多个相同的方法的。

这个问题的解决方法就是,我们可以将参数中泛型内容去掉,例如下面的代码:

public class Demo {

    public static void print(List list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

    public static void main(String[] args) {
        List<String> strings = null;
        List<Integer> integers = null;
        print(strings);
        print(integers);
    }
}

这样在在print方法中,既可以接收Integer类型的List集合,也可以接收String类型的List集合了。但是,这时会提示泛型安全警告“List is a raw type. References to generic type List should be parameterized”

要想彻底解决这个问题,我们需要使用类型通配符。通过类型通配符上面的代码可以改写成如下方式:

public class Demo6 {

    public static void print(List<?> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

    public static void main(String[] args) {
        List<String> strings = null;
        List<Integer> integers = null;
        print(strings);
        print(integers);
    }
}

上述案例中的“

List<? extends Number> list         ①
List<? super Integer> list          ②

这里①表示类型通配符的上边界,含义是当前的泛型可以接收所有Number类型的子类型;②表示类型通配符的下边界,含义是当前的泛型可以接收所有Integer类型的父类型。



转载说明:请注明作者及原文链接,谢谢!

时间: 2024-10-22 12:52:44

[Java5新特性]泛型的相关文章

Java5新特性

"JDK1.5/Java5"的一个重要主题就是通过新增一些特性来简化开发. 这些特性包括泛型,for-each循环,自动装包/拆包,枚举,可变参数, 静态导入,注解. 使用这些特性有助于我们编写更加清晰,精悍,安全的代码. 1.泛型(Generic)C++通过模板技术可以指定集合的元素类型,而Java在1.5之前一直没有相对应的功能.一个集合可以放任何类型的对象,相应地从集合里面拿对象的时候我们也不得不对他们进行强制得类型转换.猛虎引入了泛型,它允许指定集合里元素的类型,这样你可以得到

Java5新特性之静态导入、可变参数、增强for循环、自动拆装箱

JDK1.5已经发布很长时间,之所以还拿出来是因为它增加了很多个重要的特性,使用这些特性有助于我们简化开发,编写的代码更加简洁清晰安全,主要有以下几个特性: ?  静态导入 ?  可变参数 ?  增强for循环 ?  自动拆装箱 ? 泛型 ? 枚举 由于泛型.枚举内容比较多,也最重要,之后单拿出来讲.这里先介绍前面四个简单而又实用的小特性. 1. 静态导入 所谓"静态导入"只不过是在普通的import语句中加入关键字static,例如: ?  非静态导入:import java.lan

java5 新特性

1.静态导入方法 Java代码   package com.java.new_features_jdk5; /** * * 一般我们导入一个类都用 import com.....ClassName;而静态导入是这样:import static com.....ClassName.*; * 这里的多了个static,还有就是类名ClassName后面多了个 .* ,意思是导入这个类里的静态方法.当然,也可以只导入某个静态方法,只要把 .* 换成静态方法名就行了. * 然后在这个类中,就可以直接用方

Spring -- 4.0新特性 -- 泛型依赖注入

泛型依赖注入为spring4.0版本新增加的特性. 目录结构 BaseService.java类 public class BaseService<T> { @Autowired private BaseRespository baseRespository; public void save() { System.out.println("Base Class save method"); System.out.println(baseRespository); } }

[Java5新特性]自动装箱/拆箱

自动装箱/拆箱概述 Java中具有基本类型(int,double,float,long,boolean,char,byte,short)和基本类型包装类(Integer,Double,Float,Long,Boolean,Char,Byte,Short),我们实现基本类型与包装类之间的转换基本有两种方式: 一种为JDK5之前的方式,比如Integer i = Integer.valueof(5);(这里5为基本类型int,Integer包装类利用valueof()方法将其转换为Integer类型

[Java5新特性]可变参数

什么是可变参数 Java基础内容中,关于函数具有一种特性:重载,如果我们要完成多个数字想加的需求,可以按照以下代码完成: public class Demo { public int add(int a, int b) { return a + b; } public int add(int a, int b, int c) { return a + b + c; } public static void main(String[] args) { int sum1 = new Demo().a

JDK5.0新特性-泛型

泛型(重点) 什么是泛型? 对数据类型进行一个限定. 应用最多的地方是集合. 为什么要使用泛型? 1.提高我们程序安全性. 2.将运行阶段的问题在编译阶段解决. 3.不用进行类型强制转换 ----------------------------------------------- 我们学习泛型的目的:会使用集合上定义的泛型. 1.集合上泛型的基本使用. //Collection<E> //List<E> //Set<E> //它们的作用是限定集合中元素的类型. 我们可

[Java5新特性]Annotation注解

Annotation概述 Annotation是JDK 5.0以后提供对元数据的支持,可以在编译.加载和运行时被读取,并执行相应的处理.所谓Annotation就是提供了一种为程序元素设置元数据的方法,可用于修饰包.类.构造器.方法.成员变量.参数和局部变量的声明,这些信息被存储在Annotation的"name=value"对中. Annotation能被用来为程序元素(类.方法.成员变量等)设置元数据,比如一段代码的作者或者告诉编译器禁止一些特殊的错误,不会影响代码的执行. 基本A

[Java5新特性]类加载器

类加载器概述 类加载器负责加载所有的类,系统为所有被载入内存中的类生成一个java.lang.Class实例.一旦一个类被加入JVM中,同一个类就不会被再次加入了.正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识.在Java中,一个类用其全限定类名(包括包名和类名)作为标识:但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识. 例如以下案例,在JVM中两个同名的Person类是完全不同的,之间也互不兼容,因为类加载器不同. 上述情况转换成代码如下: 定义一个Per