泛型

1. 泛型的引出

现在要求定义一个表示坐标的操作类(Point), 在这个类里面保存以下几种坐标:

  • 保存数字:x = 10 , y = 20 ;
  • 保存小数:x = 10.2 , y = 20.3;
  • 保存字符串:x = 东经20度,北纬15度
    现在这个Point类设计的关键就在于x与y这两个变量的类型,必须有一种类型可以保存这三种数据,那么就是Object类型:
  • int : int自动封装为Integer,Integer向上转型为Object;
  • double : double自动装箱为Double,Double向上转型为Object;
  • String : 直接向上转型为Object。
    范例:初期设计如下:
class Point {
    private Object x ;
    private Object y ;
    public void setX(Object x) {
        this.x = x;
    }
    public void setY(Object y) {
        this.y = y;
    }
    public Object getX() {
        return x;
    }
    public Object getY() {
        return y;
    }
}

下面重复演示三个程序,分别使用各个不同的数据类型。
范例:在Point类里面保存整型数据

public class GenericsDemo {
    public static void main(String[] args) {
        //第一步:设置数据
        Point p = new Point();
        p.setX(10);
        p.setY(20);
        //第二部:取出数据
        int x = (Integer)p.getX();
        int y = (Integer)p.getY();
        System.out.println("X坐标: " + x + ",Y坐标: "  + y);
    }
}

输出显示:

X坐标: 10,Y坐标: 20

范例:使用Double型数据

public class GenericsDemo {
    public static void main(String[] args) {
        //第一步:设置数据
        Point p = new Point();
        p.setX(10.2);
        p.setY(20.3);
        //第二部:取出数据
        double x = (Double)p.getX();
        double y = (Double)p.getY();
        System.out.println("X坐标: " + x + ",Y坐标: "  + y);
    }
}

输出显示:

X坐标: 10.2,Y坐标: 20.3

范例:使用字符串

public class GenericsDemo {
    public static void main(String[] args) {
        //第一步:设置数据
        Point p = new Point();
        p.setX("东经100度");
        p.setY("北纬20度");
        //第二部:取出数据
        String x = (String)p.getX();
        String y = (String)p.getY();
        System.out.println("X坐标: " + x + ",Y坐标: "  + y);
    }
}

输出显示:

X坐标: 东经100度,Y坐标: 北纬20度

以上的代码利用Object数据类型满足了需求,但是在设置数据的时候,不一定会按照开发的数据类型设置;比如:
范例:错误设置数据

public class GenericsDemo {
    public static void main(String[] args) {
        //第一步:设置数据
        Point p = new Point();
        p.setX("东经100度");
        p.setY(10); //错误的设置数据
        //第二部:取出数据
        String x = (String)p.getX();
        String y = (String)p.getY();
        System.out.println("X坐标: " + x + ",Y坐标: "  + y);
    }
}

编译报错:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at GenericsDemo.main(GenericsDemo.java:26)

原因分析:因为在设置的时候存放的是int(Integer),而取出数据的类型是String,两者没有任何关系的类对象之间发生了强制转换,就产生了ClassCastException异常。

这就涉及到向上转型和向下转型,具体内容查看之前写的多态
从JDK1.5之后开始增加了泛型:类在定义的时候,可以使用一个标记,表示类中属性或方法参数的类型,在使用的时候才动态的设置类型。

范例:使用泛型标记

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

在调用Point类的时候,才设置标记的内容,也就是设置了类中的属性类型
范例:设置为String

public class GenericsDemo {
    public static void main(String[] args) {
        //第一步:设置数据
        Point<String> p = new Point<String>();
        p.setX("东经100度");
        p.setY("北纬20度");    
        //第二部:取出数据,由于接收的类型是String
        //所以不需要向下强制转型
        String x = p.getX();
        String y = p.getY();
        System.out.println("X坐标: " + x + ",Y坐标: "  + y);
    }
}

使用泛型后,所有类中属性的类型都是动态设置的,而所有这种泛型标记的方法数据类型也发生了改变,这样就避免了向下转型的问题,就解决了类对象转换的安全隐患。
对于泛型有两点说明:

  • 如果在使用泛型类或者是接口的时候,没有设置泛型具体类型,那么就会出现编译警告,同时为了保证程序不出错,所有的泛型都将使用Object表示。
public class GenericsDemo {
    public static void main(String[] args) {
        //第一步:设置数据
        Point p = new Point();  //将使用Object类型描述泛型
        //利用的就是包装类的自动装箱功能
        p.setX(10);
        p.setY(20);
        int x = (Integer)p.getX();
        int y = (Integer)p.getY();
        System.out.println("X坐标: " + x + ", Y坐标:" + y);
    }
}
  • 从JDK1.7开始可以简化声明泛型
public class GenericsDemo {
    public static void main(String[] args) {
        //第一步:设置数据
        Point<Integer> p = new Point<>();   //JDK1.7之后实例化的泛型可以省略
        p.setX(10);
        p.setY(20);
        int x = p.getX();
        int y = p.getY();
        System.out.println("X坐标: " + x + ", Y坐标:" + y);
    }
}

2. 通配符

为了更好的理解通配符,先观察一个程序:

    private T msg;
    public void setMsg(T msg) {
        this.msg = msg;
    }
    public T getMsg() {
        return msg;
    }
}
public class WildcardDemo {
    public static void main(String[] args) {
        Message<String> m = new Message<String>();
        m.setMsg("Hello world!");
        fun(m);
    }
    public static void fun(Message<String> temp) {
        System.out.println(temp.getMsg());
    }
}

以上代码中,Message类设置了String泛型,如果现在设置其它的类型呢?例如:

public class WildcardDemo {
    public static void main(String[] args) {
        Message<Integer> m = new Message<>();
        m.setMsg(10);
        fun(m);
    }
    public static void fun(Message<String> temp) {
        System.out.println(temp.getMsg());
    }
}

报错显示:

WildcardDemo.java:14: 错误: 不兼容的类型: Message<Integer>无法转换为Message<String>

fun()方法里面接收的是"Message<String>",那么在调用方法的时候就不能改变了,并且fun()方法不能够针对于不同的泛型进行重载,因为方法的重载只和参数的类型、个数有关,与泛型无关。
所以现在需要可以接收一个类的任意泛型类型,但是不可以修改只能够取出,所以可以使用通配符: "?" 来描述。

public class WildcardDemo {
    public static void main(String[] args) {
        Message<Integer> m = new Message<>();
        m.setMsg(10);
        fun(m);
    }
    //使用通配符 ?
    public static void fun(Message<?> temp) {   
        System.out.println(temp.getMsg());
    }
}

在"?" 通配符还有两个子通配符:

  • ?extends 类 : 设置泛型上限,可以在声明时和方法参数上使用;
    · ? extends Number : 表示可以设置Number或者是Number的子类(Integer、Double...)
  • ? super 类 :设置泛型下限,使用在方法参数上;
    · ? super String :表示只能够设置String或者是它的父类Object。
    范例:设置泛型的上限
class Message<T extends Number> {   //设置泛型上限
    private T msg;
    public void setMsg(T msg) {
        this.msg = msg;
    }
    public T getMsg() {
        return msg;
    }
}
public class WildcardDemo {
    public static void main(String[] args) {
        Message<Integer> m = new Message<>();
        m.setMsg(10);
        fun(m);
    }
    //使用通配符 ?
    public static void fun(Message<? extends Number> temp) {    
        System.out.println(temp.getMsg());
    }
}

如果设置了非Number或非子类的话,就会出现错误。
范例:设置泛型的下限

class Message<T> {  
    private T msg;
    public void setMsg(T msg) {
        this.msg = msg;
    }
    public T getMsg() {
        return msg;
    }
}
public class WildcardDemo {
    public static void main(String[] args) {
        Message<String> m = new Message<>();
        m.setMsg("Hello World!");
        fun(m);
    }
    //使用通配符 ?; 设置泛型下限
    public static void fun(Message<? super String> temp) {  
        System.out.println(temp.getMsg());
    }
}

3. 泛型接口

之前都是将泛型定义在一个类里面,泛型也可以在接口上声明,称为泛型接口
范例:定义泛型接口

interface Message<T> {
    public void print(T t);
}

接口必须有子类,有两种形式定义泛型接口子类:
形式一:在子类继续设置泛型

//子类也继续使用泛型,并且父接口使用和子类同样的泛型标记
class MessageImpl<T> implements IMessage<T> {
    public void print(T t) {
        System.out.println(t);
    }
}
public class GenericInterface {
    public static void main(String[] args) {
        IMessage<String> msg = new MessageImpl();
        msg.print("Hello world!");
    }
}

形式二:在子类不设置泛型,而为父接口明确的定义一个泛型类型

//为父类明确的定义String类型的泛型
class MessageImpl implements IMessage<String> {
    @Override
    public void print(String t) {
        System.out.println(t);
    }
}
public class GenericInterface {
    public static void main(String[] args) {
        IMessage<String> msg = new MessageImpl();
        msg.print("Hello world!");
    }
}

4. 总结

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

推荐阅读更多精彩内容

  • 导语 这里是导语。对,没错,这里就是导语,没有前几次的代码情书。喜欢情书的关注我,去看我之前的Java文章吧。 主...
    一个有故事的程序员阅读 574评论 9 8
  • 参数类型的好处 在 Java 引入泛型之前,泛型程序设计是用继承实现的。ArrayList 类只维护一个 Obje...
    杰哥长得帅阅读 873评论 0 3
  •   在Effective中讲到泛型之处提到了一个概念,类型擦除器,这是什么呢?接下来我们跟随这篇文章探索类型擦除的...
    凌云_00阅读 2,136评论 0 8
  • 我们知道,使用变量之前要定义,定义一个变量时必须要指明它的数据类型,什么样的数据类型赋给什么样的值。 假如我们现在...
    今晚打肉山阅读 980评论 0 1
  • object 变量可指向任何类的实例,这让你能够创建可对任何数据类型进程处理的类。然而,这种方法存在几个严重的问题...
    CarlDonitz阅读 913评论 0 5