最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

Java泛型好处与坏处_泛型的原理实现

XAMPP相关 admin 621浏览 0评论

Java泛型在Java1.5引入的一种特性,这种特性带来的好处就是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。在没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的,而且在代码不规范的情况下,一不小心就报个ClassCastException(类型转换异常);

举个栗子:

   public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        list.add("西西");
        list.add(100);
        list.add(list);
        //转换正常
        String el1 = (String) list.get(0);
        //编译通过,运行报ClassCastException转换异常
        String el2 = (String) list.get(1);
}
可以看到直接用Object作为类型,什么类型都能添加到集合中去,但是想要获取到对应类型的元素,需要在代码编译前就知道对应的位置关系,否则会在运行期存在数据转换异常的风险,这就是所谓“任意化”带来的风险;因此需要引入一种规则,规定添加时和取出时的类型需要一致,如果不一致,在编译期就报错,而这种规则就是泛型机制。
    public static void main(String[] args) {
        //改成String类型
        List<String> list = new ArrayList<>();
        list.add("西西");
        //下面两个添加编译不通过
        list.add(100);
        list.add(list);
    }
可以看到,泛型机制可以在编译期就检查出了问题-,而且在获取数据使用时会自动转换成对应的类型,可以极为有效减少程序运行期出现错误;

泛型该如何使用?

泛型的使用机制很简单,在对应的类上加上<T>即可,括号中的T可以为其他字母,如<K> <V>等都行,不过为了增加代码可读性和行内的一些默认规则,建议用有意义的字母表示,例如K一般默认键,V表示值,E表示元素等;以下为简单的一个使用示例:
  public static void main(String[] args) {
        //泛型使用String类型,也只能使用Sting类型
        Single<String> singleString = new Single<>();
        singleString.setName("西西");
        //传入String类型就获取String类型
        String nameString = singleString.getName();
        //泛型使用Interge类型,也只能设置Integer类型
        Single<Integer> singleInteger = new Single<>();
        singleInteger.setName(1);
        //传入Integer类型就获取Integer类型
        Integer nameInterge = singleInteger.getName();
    }
    static class Single<T>{
        private T name;
 
        public T getName() {
            return name;
        }
        public void setName(T name) {
            this.name = name;
        }
    }

还能这么用:

    /**
     * 只能传入继承自Number的类,如Integer、Long等
     * @param <T>
     */
    static class Single<T extends Number>{
        private T name;
 
        public T getName() {
            return name;
        }
        public void setName(T name) {
            this.name = name;
        }
    }
<T extends Number> 会起到设置传入类型的上限的作用,比如让某个类只能传入数字类型,可以用上述方法限定类型继承自Number实现;除了上述类使用泛型外,应该不少人见过在方法中限定这样的:
//使用通配符
    public static void method1(List<?> list) {
        ......
    }
    //使用泛型方法
    public <T> void  method2(List<T> tList) {
        ......
    }
    //使用裸类型方法
    public <T> void  method3(List list) {
        ......
    }

此时很多人又有疑问,使用?和和T有什么区别?该如何使用呢?

我在借助IDEA的代码快捷键进行对两种使用for循环,结果是这样的:
    //使用通配符
    public static void method1(List<?> list) {
        for (Object o : list) {
            System.out.println(o);
        }
    }
    //使用泛型方法
    public <T> void  method12(List<T> tList) {
        for (T t : tList) {
            System.out.println(t);
        }
    }
    //使用裸类型方法
    public <T> void  method3(List list) {
        for (Object o : list) {
 System.out.println(o);
        }
    }
很明显看到使用泛型方法的会直接转换成T对应的类型,而使用?通配符和裸类型的是Object类型;由此可以推断List<Object>、List<?>、List 三者使用是相等,也就是使用通配符时会出现上述说的“任意化”;

泛型与通配符选择使用原则是:

  • 如果参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的,那么就使用泛型方法,比如需要在方法内调用相应对象的方法;
  • 如果没有依赖关系的,就使用通配符,通配符会灵活一些

泛型的原理实现

泛型并不是Java语言独有的特性,在C# 2.0版本也使用了泛型,C#使用的是“具现化式泛型”,Java使用的是“类型擦除化泛型”;这两种泛型的区别是,“具现化式泛型”会使用类似于占位符的思想,在编译期时或运行期就真实存在;而Java使用的“类型擦除化泛型”则是在编译后,会完全擦除对应的类型,也就是变成了裸类型,像List<String>或者List<Integer>最后编译成class文件后都变成List,然后需要在运行期才通过类转和指令转换成对应的类型,因此Java的泛型方面运行效率相比于C#要低。

Java泛型的缺陷

我们都知道Java的重载是跟返回值无关系的,跟方法的参数及顺序有关,但是如果遇到泛型,如下面的代码,就会出现编译不通过:
   //编译报错
    public static void method(List<String> list) {
        System.out.println("List<String>");
    }
    public static void  method(List<Integer> list) {
        System.out.println("List<Integer>");
    }
原因很简单,就是因为泛型编译后都被擦除了,所以这两个编译后的方法特征是一模一样的,返回值是空,参数输入类型都是裸类型的List。但是如果我们修改一下返回值,然后使用Java6的Javac编译器进行编译,下面的方法居然可以运行成功!
public static void main(String[] args) {
        //输出List<String>
        method(new ArrayList<String>());
        //输出List<Integer>
        method(new ArrayList<Integer>());
    }
    public static String method(List<String> list) {
        System.out.println("List<String>");
        return "西西";
    }
    public static int  method(List<Integer> tList) {
        System.out.println("List<Integer>");
        return 1;
    }
由于Java泛型的类型擦除法,使得无法对参数类似于List<String>和List<Integer>的方法进行重载,这里就已经破坏向对象语言的一些优雅特性了,就算可以使用不同返回值的方式进行重载,但是这种条件较为苛刻,必须使用Java6的Javac才能编译成功,其他版本的编译器几乎都会拒绝编译或报错!
总结:Java泛型的特性极大地提高了编译期代码的安全性和可读性,但是由于其编译进行类型擦除的特征,其运行效率会有所降低,同时会对面向对象的一些特性造成破坏,比如说重载等。

转载请注明:XAMPP中文组官网 » Java泛型好处与坏处_泛型的原理实现

您必须 登录 才能发表评论!