③泛型(Generics)

 泛型(Generics)

Java容器能够容纳任何类型的对象,这一点表面上是通过泛型机制完成,Java泛型不是什么神奇的东西,只是编译器为我们提供的一个“语法糖”,泛型本身并不需要Java虚拟机的支持,只需要在编译阶段做一下简单的字符串替换即可。实质上Java的单继承机制才是保证这一特性的根本,因为所有的对象都是Object的子类,容器里只要能够存放Object对象就行了。
事实上,所有容器的内部存放的都是Object对象,泛型机制只是简化了编程,由编译器自动帮我们完成了强制类型转换而已。

1)泛型特点

  1. java中的泛型只是在编辑期间起作用的,在运行时会把泛型信息擦除的。

  2. 只是在编译期间启动类型安全检查的作用,运行时不起作用。

  3. 例如:List<String> list = new ArrayList<String>();

  4. 虽然指定了泛型为String,但是在运行时候依然是可以向该list中存放其他类型数据的。(比如使用反射的方法)

2)泛型类

一个泛型类就是具有一个或多个类型变量(把类型参数化)的类。
定义一个泛型类十分简单,只需要在类名后面加上<>,再在里面加上类型参数.
注:类型变量使用大写形式,且比较短,这是很常见的。在JDK中,使用变量E表示集合的元素类型,K和V分别表示关键字与值的类型。(需要时还可以用其他的字母,也可以是一个或多个字母)

举例1:

        这里的T是根据将来用户使用Demo类的时候所传的类型来定

public class Demo<T> {
            private T x;
            private T y;
            public T getX() {
                return x;
            }
            public void setX(T x) {
                this.x = x;
            }
            public T getY() {
                return y;
            }
            public void setY(T y) {
                this.y = y;
            }
        }

  当我们创建一个对象为:Demo<Double> p = new Demo<Double>(); ,这里的T就代表的是double。不需要我们再去专门写一个类型,我们只需要写个泛型即可

举例2:

     这里的T和S是根据将来用户使用Point类的时候所传的类型来定

public class Demo<T,S> {
            private T x;
            private S y;
            public T getX() {
                return x;
            }
            public void setX(T x) {
                this.x = x;
            }
            public S getY() {
                return y;
            }
            public void setY(S y) {
                this.y = y;
            }
        }

当我们创建一个对象为:Demo<String,Integer> p = new Demo<String,Integer>();  ,这个的T是String,S是Integer类型的。

3)泛型接口

一个泛型接口就是具有一个或多个类型变量的接口。   

举例:如下

public interface Action<T,U>{  
            void doSomeThing(T t,U u);  
        }  

        public class ActionTest implements Action<String,Date>{  
            public void doSomeThing(String str,Date date) {  
                System.out.println(str);  
                System.out.println(date);  
            }  
        }

4)泛型方法

就是在方法上直接声明泛型。

public class Test{
            public <T> void run1(T t){
            
            }

            public <T> T run2(T t){
                return t;
            }

            public <T,S> void run3(T t,S s){
        
            }
        }

5)通配符

泛型增加了java中类型的复杂性,例如List<String>、List<Integer>、List<Object>等这些都是不同的类型。

//编译报错
        //虽然 List list = new ArrayList(); 是正确的
        //虽然 Object是String的父类型
        //但是下面代码编译时报错的,因为使用了泛型
        List<Object> list = new ArrayList<String>(); 
不能从 ArrayList<String> 转换为 List<Object>。 Object为父类,但类型不确定。应改为LIst<String>.原因 Java泛型不支持协变

什么是协变?
泛型或者数组的case下,协变其实指的是,基础类型具备父子关系,那么对应的容器类型也具备。
class F{
 
}
class S extends F{
 
}
public class Main1 {
    public static void main(String args[]) {
        F f = new S();
        F[] fa = new S[]{};
    }
}
这里F是S的父类,那么可以使用一个F[]的引用指向S[]的实例,也就是如果基础类型具备父子关系,其数组类型也具备。但是,List<F>却不能指向一个List<S>的实例,因为泛型不能协变。这就是协变的含义。
泛型不允许协变,而数组允许协变,为什么???
        List<S> list1 = new ArrayList<S>();
        List<F> f = list1;// 这时,可以往f集合里扔任何F的子类型 
        f.add(new S1()); // 比如,这里扔了一个S1的子类型实例,因为上述的S是F的子类型
        S s = f.get(0); // 但是,这里却试图转为S类型,就会报错,即F(父类)指向不了(子类)S

上面转代码是不能编译的,假如泛型协变是允许的,那么上述代码就能编译,但是,最终在运行时,会报一个类型转换的运行时异常。

这就是泛型协变不被允许的原因。

但是!!!还远没有结束。

我们再看看下上述场景在数组中会有什么问题。

        S[] sa1 = new S[10];
        F[] f = sa1;// 这是允许的,因为数组可以协变
        f[0] = new S1(); // 这里存一个S1的子类型实例,此时,就会直接报错
        S sss = f[0]; // 所以,这里不会走到

这段代码可以编译,但是在运行时,会报一个ArrayStoreException的运行时异常。

这里就有问题了,同样会报错,但是为啥数组允许协变,泛型不能呢???

这个确实是一个问题。

原因是:

数组的报错是在存元素时抛出的

而泛型的报错是在取元素是抛出的

这样,泛型的报错时机就非常延后了,如果类型不对,压根就不应该让这个元素放入,否则,就只能在读取时进行强转才能发现,可别小看这个时机问题,一旦发生,非常难定位,很难查到是在哪里放入了类型异常的元素,所以泛型不允许协变。原因就是,类型转换的问题需要延后到读取时才能发现。而数组则可以在存入时就检测到类型不匹配的问题。

所以说数组是允许协变的,泛型不行。

我们知道,泛型由于类型擦除,在运行时丢失了泛型信息,所以,在存入时,泛型无法判断实际类型,但是,为什么数组能够在存入时检测?

这是因为数组从一开始设计及实现时,就记录了数组内存的元素的类型,所以数组可以在运行时拿到这个信息,就能够在运行时检测类型。这是数组与泛型的一大区别。

泛型中?是通配符,它可以表示所有泛型的父类型,集合元素可以是任意类型,这种没有意义,一般是方法中,只是为了说明用法。
      List<?> list = new ArrayList<任意>();

//这时list可以指向任何泛型的List类型集合对象
        public void test(List<?> list){
            //编译报错,因为我们并不知道?到底代表什么类型
            list.add(1);
            
            //编译通过
            for(Object o:list){
                System.out.println(o);
            }
        }
注:通配符?只能用在泛型变量声明的时候。

6)泛型中的extends和super关键字

在泛型中可以使用extends和super关键字来表示将来用户所传的泛型参数的上限和下限。

extends关键字的使用

举例:

//在声明泛型类和泛型接口时使用extends
public class Point<T extends Number> {
            private T x;
            private T y;
        }

        public class Point<T extends Number,S> {
            private T x;
            private S y;
        }

        public interface Action<T extends Person> {
            public void doSomeThing(T t);
        }
        
        例如:在声明泛型方法时使用extends
        public <T extends Action> void run(T t){
        
        }

//在声明泛型方法时使用extends
public <T extends Action> void run(T t){
        }    

//在声明泛型类型变量时使用extends

        List<? extends Number> list = new ArrayList<Integer>();
        List<? extends Number> list = new ArrayList<Long>();
        List<? extends Number> list = new ArrayList<Double>();
        //编译报错
        List<? extends Number> list = new ArrayList<String>();
        
        例如:
        public void test(List<? extends Number> list){
        
        }

super关键字的使用

        //编译报错
        //声明泛型类或泛型接口时不能使用super
        public class Point<T super Number> {
    
        }
        public interface Action<T super Number> {
    
        }

        //编译报错
        //声明泛型方法时不能使用super
        public <T super Action> void run2(T t){
        
        }

//在声明泛型类型变量时使用super

        //编译通过
        List<? super Number> list1 = new ArrayList<Object>();
        List<? super Student> list2 = new ArrayList<Object>();
        List<? super Student> list3 = new ArrayList<Person>();

        //编译通过
        public void test(List<? super Number> list){
        
        }
        
        //编译通过
        public void test(List<? super Student> list){
        
        }

总结:

  ArrayList<T> a=new ArrayList<T>();指定集合元素只能是T类型
  ArrayList<?> a=new ArrayList<?>();集合元素可以是任意类型,这种没有意义,一般是方法中,只是为了说明用法
  ArrayList<? extends E> a=new ArrayList<? extends E>();
  泛型的限定:
    ? extends E:接收E类型或者E的子类型。
    ?super E:接收E类型或者E的父类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值