夯实JAVA基础之 - 泛型

泛型的定义及使用

1. 定义泛型:
Student<T>
2. 类中使用泛型
Student<T> 这个 T 表示派生自object的任何类,比如 string、 intenger、 Double 等。 但是要注意的是, T 一定要
是派生自 object 类。

Class Student<T> {
    private T name ;

    public T getName(){
        return name;
    }

    public void setName(T name){
        this.name = name;
    }
}
3. 使用泛型类
// 构造实例
Student<String> s = new Student<String>();
s.setName("张三");
system.out.printin(s.getName());

4. 使用泛型的优势?

I.  为什么不用 object 代替泛型?就是因为强制转换会出现意想不到的错误。
II. 在编译期就能报错。

多泛型变量的定义及字母规范

1. 多泛型变量定义
上面我们只定义了一个泛型变量,那如果需要多个变量怎么办?  只需要类似下面这样就行了。
新加的变量和上例中的 T 用法一样, 这样的变量想加几个加几个。只要用 "," 隔开就行。

class Student<T,E>{
    Private T name;
    Private E age;
    ......
}

class Student<T,E,U,K,V>{}

2. 字母规范

指定泛型的可以是任意一个大写字母,咩有特定的含义。 但是为了提高可读性,大家还是用比较有意义的字母为好。

  • E, Element 常用在 collection 中,如: List<E>, iterator<E>, Set<E>
  • K,V key-value 键值对
  • N, Number 数字
  • T, Type 类型,如 string integer 等

泛型接口的定义和使用

  • 非泛型类
  • 泛型类

泛型函数的定义和使用

不管是静态还是非静态函数,不管是有没有返回值,都在返回值类型前加符号<T> ,以用来标识泛型。

  • 静态泛型函数
  • 非静态泛型函数
public class StaticFans {
    //静态函数
    public static <T> void StaticMethod(T a){
        Log.d("harvic","StaticMethod: "+a.toString());
    }
    //普通函数
    public <T> void OtherMethod(T a){
        Log.d("harvic","OtherMethod: "+a.toString());
    }
    // 有返回值的泛型函数
    public static <T> List<T> parseArray(String response,Class<T> object){
        List<T> modelList = JSON.parseArray(response, object);
        return modelList;
    }

}

其他用法:Class<T> 类传递及泛型数组

Class<T> 其实也是一种泛型,用来装载 class 对象的。

public final class Class<T> implements Serializable {  
   …………  
}  

泛型高级知识: 类型绑定和通配符

泛型的类型绑定: extends

  1. <T extends BoundingType> 就是给泛型参数加一个界限。
  2. 此时的 extends 不等同于 继承。和继承没有任何联系。
  3. BoundingType 可以是类,也可以是接口。 T 代表的类型是被包含的意思。具有 BoundingType的功能。
  4. 能提前调用 T 的父类或父接口中的方法。
  • 绑定接口
    public interface Comparable<T> {
        boolean compareto(T i);
    }
    
    public class StringCompare implements Comparable<StringCompare> {
        String mStr;
        public StringCompare(String string) {
            this.mStr = string;
        }
        
        @Override
        public boolean compareto(StringCompare i) {
            if (mStr == null) {
                throw new NullPointerException();
            }
            if (mStr.length() > i.mStr.length()) {
                return true;
            }
            return false;
        }
    }
    
    public static <T extends Comparable> T min(T... a) {
        T smallest = a[0];
        for (T item : a) {
            if (smallest.compareto(item)) {
                smallest = item;
            }
        }
    }
    
    // 调用时候
    // 可以看出类型绑定有两个作用:
    // 1、对填充的泛型加以限定 
    // 2、使用泛型变量T时,可以使用BoundingType内部的函数。
    
    public static <T extends Comparable> T min(T... a) {
        T smallest = a[0];
        for (T item : a) {
            if (smallest.compareto(item)) {
                smallest = item;
            }
        }
        return smallest;
    }
    
    StringCompare result = min(new StringCompare("1"),new StringCompare("123"));
    Log.e("xyd","result = " + result.mStr);
       
    
  • 绑定类
    class Fruit{
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    public static <T extends Fruit> String getFruitName(T t){
        return t.getName();
    }
    
    class Banana extends Fruit{
        public Banana() {
            setName("banana");
        }
    }
    
    class Apple extends Fruit{
        public Apple() {
            setName("apple");
        }
    }
    // 最后使用;
    Log.e("xyd","FruitName = " + getFruitName(new Banana()));
    Log.e("xyd","FruitName = " + getFruitName(new Apple()));
    
  • 绑定多个绑定,用 & 连接
    public static <T extends Fruit&Serializable> String getFruitName(T t){
        ...
    }
    加深难度,如果有多个泛型,每个泛型都带绑定?
    public static <T extends Comparable & Serializable, U extends Runnable> T foo(T a, U b){
        ...
    }
    

通配符

无边界通配符
  • 无边界通配符初识
    有没有办法,只生成一个变量,可以将不同的实例赋值给它呢?
    
    首先定义定义一个泛型类:
    class Point<T>{
        private T x;
        private T y;
    
        public Point() {
        }
    
        public Point(T x, T y) {
            this.x = x;
            this.y = 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;
        }
    }
    
    Point<?> point;
    point = new Point<Integer>(3,3);
    point = new Point<Float>(2.3f,5.6f);
    point = new Point<Double>(6.3d,46.5d);
    point = new Point<Long>(10l,58l);
    这里的 ? 就是无边界通配符。就是一个任意的类,一个未知的符号。
    point = new Point<String>("","");
    point = new Point<Object>();
    不光能将 T 填充为数值类,任意的 Object 的子类都可以匹配给通配符。
    
  • 通配符 ? 和 T 的区别
    他们俩没有任何联系;
    泛型变量T不能在代码中用于创建变量,只能在类、接口、函数中声明后使用。
    无界通配符只能用于填充泛型变量T,表示通配任何类型。它是用来填充 T 的,只是填充方式的一种!!
    
    // 无边界通配符填充
    Box<?> box;
    // 其他类型填充
    Box<String> stringBox;
    
    
  • 通配符只能用于填充泛型变量T,不能用于定义变量
    只能用来填充变量T的位置,不能用于定义变量。通配符的使用位置只有:
    Box<?> box;
    box = new Box<String>();
    
    而不能用于定义变量:
    box = new Box<?>(); // 错误
    ? x;                // 错误
    

通配符 ? 的 extends 绑定 // 上边界限定通配符

通配符?可以代表任意类型,但跟泛型一样,如果不加以限定,在后期的使用中编译器可能不会报错。所以我们同样要对通配符加以限定。
绑定的形式,同样是通过extends 关键字,意义和使用方法都和泛型变量一致。
为了保证泛型类有意义,需要对泛型类的参数做限制,不能超出限制之外。
如有的参数只有是数字类型才有意义。

Point<? extends Number> point;
point = new Point<Number>();
point = new Point<Float>(3.4f,4.3f);
point = new Point<Double>(3.4d,4.3d);
point = new Point<Long>(3.4l,4.3l);
point = new Point<String>("","");       //  报错
point = new Point<Object>();            //  报错

//----
编译时候,最后两行会报错。
new Point<Number>(); 不会报错,说明无边界通配符的extends绑定包括边界自身。
无边界通配符只是泛型T的填充方式,给他加上限定,只是限定了赋值给它的实例类型。
如果想从根本上解决乱填充Point的问题,需要从 Point 泛型类定义时候就加上 <T extends Number>

注意: 利用<? extends Number> 定义的变量,只可取其中的值,不可修改。

Point<? extends Number> point;
point = new Point<Integer>(3,33);
Number Integer_x = point.getX();
point.setX(new Integer(222));       // 报错

// 为什么会报错???
因为 Point 的类型是由 Point<? extends Number> 决定的,并不会因为 point = new Point<Integer>(3,33)而改变类型。
即便 point = new Point<Integer>(3,3) 之后, point 的类型依然是 Point<? extends Number>(),
即派生自 Number 的未知类型!!! 
怎么理解? 如果在 point = new Point<Integer>(3,3) 之后, point 就变成了 Poin<Integer> 类,那后面
point = new Point<Long>(2l,22l); 肯定会因为类型不匹配而报编译错误了。正因为 point 的类型始终是 
Point<? extends Number> 因此能继续被各种类型的实例赋值。

继续正题,为什么会报错?
point 的类型为 Point<? extends Number> , 那就是说填充 point 泛型变量 T 的是 <? extends Number>,
这是一个什么类型? 未知类型。怎么可能用一个未知类型来给设置内部值! 这是不合理的。
但是取值时, 正由于泛型变量T被填充为<? extends Number> 所以编译器能确定 T 肯定是 Number 的子类。编译器就会用 Number 来填充 T。

也就是说,编译器,只要能确定通配符类型,就会允许,如果无法确定通配符的类型,就会报错。

通配符 ?的 supper 绑定 // 下边界限定通配符

总结:
通配符的使用可以对泛型参数做出某些限制,使的代码更安全,对于上边界和下边界限定的通配符总结如下:

  • 使用 List<? extends C> list 这种形式,表示 list 可以引用一个 ArrayList ( 或者其它 List 的 子类 ) 的对象,这个对象包含的元素类型是 C 的子类型 ( 包含 C 本身)的一种。
  • 使用 List<? super C> list 这种形式,表示 list 可以引用一个 ArrayList ( 或者其它 List 的 子类 ) 的对象,这个对象包含的元素就类型是 C 的超类型 ( 包含 C 本身 ) 的一种。

参考资料:
Java 泛型总结(一):基本用法与类型擦除
Java 泛型总结(二):泛型与数组
Java 泛型总结(三):通配符的使用
夯实JAVA基本之一 —— 泛型详解(1):基本使用
夯实JAVA基本之一——泛型详解(2):高级进阶

什么是协变?为什么数组是支持协变的?为什么泛型不支持协变?
参考知乎 - 胖胖 的答案

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容

  • 引言:泛型一直是困扰自己的一个难题,但是泛型有时一个面试时老生常谈的问题;今天作者就通过查阅相关资料简单谈谈自己对...
    cp_insist阅读 1,804评论 0 4
  • 一、基本数据类型 注释 单行注释:// 区域注释:/* */ 文档注释:/** */ 数值 对于byte类型而言...
    龙猫小爷阅读 4,213评论 0 16
  • 开发人员在使用泛型的时候,很容易根据自己的直觉而犯一些错误。比如一个方法如果接收List作为形式参数,那么如果尝试...
    时待吾阅读 992评论 0 3
  • 幾日燈火竟嶙峋,肅氣彌彌貫天宇。北風嗚咽無所依,空對歸人唱金縷。我言塊壘堪入酒,以是堪堪了心期。信有杯酒銜荒月,自...
    酒客背寒南山死阅读 314评论 0 1
  • 无礼仪不聊天。虽然说隔着屏幕看不见对方的表情,但不代表在QQ和微信聊天可以不注重礼仪了。左爱可是见过斗表情包都能吵...
    请叫我四爷阅读 2,133评论 0 2