Java泛型的理解与使用

1.泛型简介

  • 问题:在获取用户信息的API中,后台给我们返回一个这样形式的json字符串。

    {
        "meta": {
            "code": 0,
            "message": "ok"
        },
        "data": {
            "nick_name": "hellokitty",
            "cellphone": "18301824843",
        }
    }
    

    我们用fastJson解析上述json字符串时候,该怎么处理?
    ,我们是不是就会写这样一个类。

    public class User {
    
    
        private Meta meta;
        private Data data;
    
        public Meta getMeta() {
            return meta;
        }
    
        public void setMeta(Meta meta) {
            this.meta = meta;
        }
    
        public Data getData() {
            return data;
        }
    
        public void setData(Data data) {
            this.data = data;
        }
    
        static class Meta
        {
            private String code;
            private String message;
    
            public String getCode() {
                return code;
            }
    
            public void setCode(String code) {
                this.code = code;
            }
    
            public String getMessage() {
                return message;
            }
    
            public void setMessage(String message) {
                this.message = message;
            }
        }
    
        static class Data
        {
    
            private String nick_name;
            private String cellphone;
    
            public String getNick_name() {
                return nick_name;
            }
    
            public void setNick_name(String nick_name) {
                this.nick_name = nick_name;
            }
    
            public String getCellphone() {
                return cellphone;
            }
    
            public void setCellphone(String cellphone) {
                this.cellphone = cellphone;
            }
        }
    }
    

    然后调用fastjason的JSON.parseObject(msg,User.class)进行解析。

    而如果拉取设备列表API返回的数据格式是这样的一个形式,我们该怎么处理?

    {
        "meta": {
            "code": 0,
            "message": "ok"
        },
        "data": [
            {
                "device_id": "4acb634aaf5711e8b290000c29c27f42",
                "role": 1,
                "device_alias": "hellokitty",
                "created_at": "2018-09-04T10:55:57"
            },
            {
                "device_id": "4acb634aaf5711e8b290000c29c27f42",
                "role": 1,
                "device_alias": "hellokitty",
                "created_at": "2018-09-04T10:55:57"
            }
        ]
    }
    

    是不是我们仍然要再写一个解析类来解析这个设备列表类。

        public class DeviceList {
    
        private Meta meta;
        private List<Device> data;
    
        public Meta getMeta() {
            return meta;
        }
    
        public void setMeta(Meta meta) {
            this.meta = meta;
        }
    
        public List<Device> getData() {
            return data;
        }
    
        public void setData(List<Device> data) {
            this.data = data;
        }
    
        static class Meta
        {
            private String code;
            private String message;
    
            public String getCode() {
                return code;
            }
    
            public void setCode(String code) {
                this.code = code;
            }
    
            public String getMessage() {
                return message;
            }
    
            public void setMessage(String message) {
                this.message = message;
            }
        }
    
        static class Device
        {
            @Override
            public String toString() {
                return "Device{" +
                        "device_id='" + device_id + '\'' +
                        ", role=" + role +
                        ", device_alias='" + device_alias + '\'' +
                        ", created_at='" + created_at + '\'' +
                        '}';
            }
    
            private String device_id;
            private int role;
            private String device_alias;
            private String created_at;
    
            public String getDevice_id() {
                return device_id;
            }
    
            public void setDevice_id(String device_id) {
                this.device_id = device_id;
            }
    
            public int getRole() {
                return role;
            }
    
            public void setRole(int role) {
                this.role = role;
            }
    
            public String getDevice_alias() {
                return device_alias;
            }
    
            public void setDevice_alias(String device_alias) {
                this.device_alias = device_alias;
            }
    
            public String getCreated_at() {
                return created_at;
            }
    
            public void setCreated_at(String created_at) {
                this.created_at = created_at;
            }
        }
    }
    
    

    如果每次都这样的话,会不会要创建很多很相像的类,他们只是里面部分变量不同,其他的部分都相同。

    再举一个栗子:
    如果我们想要产生多个对象,每个对象的逻辑完全一样,只是对象内的成员变量的类型不同,那我们如何去做?
    在下面我们创建了两个类,只是data的变量类型不同,是不是也可以达到我们刚才的要求。

     static class MyClass1
    {
        public MyClass1() {
        }
    
        private String data;
    
        public MyClass1(String data) {
            this.data = data;
        }
    
        public String getData() {
            return data;
        }
    
        public void setData(String data) {
            this.data = data;
        }
    }
    
    static class MyClass2
    {
        public MyClass2() {
        }
    
        private int data;
    
        public MyClass2(int data) {
            this.data = data;
        }
    
        public int getData() {
            return data;
        }
    
        public void setData(int data) {
            this.data = data;
        }
    }
    

    打印结果:

        MyClass1 myClass1 = new MyClass1();
        myClass1.setData("Cyy");
        MyClass2 myClass2 = new MyClass2();
        myClass2.setData(10);
        System.out.println(myClass1.getData());
        System.out.println(myClass2.getData());
    

    输出:

    Cyy
    10
    

    但是如果我们还想要这样一个对象呢,那我们是不是还要继续去创建这样的对象,那如果我还要10个这个的对象呢,那我们是不是就要创建十个。这样明显是很笨重的一种解决方案。

    那我们现在思考,如果我们用Object来代替呢?

        static class MyClass1
        {
            public MyClass1() {
            }
    
            private Object data;
    
            public MyClass1(Object data) {
                this.data = data;
            }
    
            public Object getData() {
                return data;
            }
    
            public void setData(Object data) {
                this.data = data;
            }
        }
    

    打印输出:

       MyClass1 myClass1 = new MyClass1();
        myClass1.setData("Cyy");
        System.out.println((String)myClass1.getData());
        MyClass1 myClass2 = new MyClass1();
        myClass2.setData(10);
        System.out.println((int)myClass2.getData());
    
        ```
    输出结果:
    
    
      Cyy
      10
    
    呀~看上去好像完美解决了,不用创建多个类,就可以实现刚才需要功能,好像很完美,现在让他变成不完美,现在我们让他这样打印出来.
    
       MyClass1 myClass2 = new MyClass1();
          myClass2.setData(10);
          System.out.println((String)myClass2.getData());
    
    注意我们给他的是整型,但是打印时候我们给他的强转类型是String,现在看下会发生什么问题。
    
      Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
      at SecDemo.main(SecDemo.java:13)
    
    它提示了,类型转换异常。
    
     总结
            
        方案(一) :
            
        方法: 创建多个类文件,给每个类中的成员变量设置指定的数据类型。
            
        缺点: 导致类的膨胀,重用性太差
            
        方案(二) :
            
        方法: 创建一个类文件,给这个类中的成员变量设置Object数据类型
            
        缺点:编译的时候正常,但运行时候可能会报错.
            
        泛型类就能很好的解决以上两个问题。
    
    

2.泛型类

  • 泛型是JDK1.5引入的新特性,也是最重要的一个特性。

  • 泛型可以在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的。

  • 泛型的原理就是类型的参数化,即把类型看做参数,也就是说把所要操作的数据类型看做参数,就像方法的形式参数是运行时传递的值一样。

  • 简单的说,类型变量扮演的角色如同一个参数,它提供给编译器用来类型检查的信息。

  • 泛型可以提高代码的扩展性和重用性
    **

    如果我们将刚才的类改成泛型类是什么样子的呢?

    static class MyClass1<T>
    {
        public MyClass1() {
        }
    
        private T data;
    
        public MyClass1(T data) {
            this.data = data;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    }
    

    我们发现在类的开头多了个<T>,这个就代表着传入进来的参数,他可以是整型,可以是字符串类型,只要你传进来了那么后续的get,set方法就全部都是这种类型了。他就相当于一个操作的参数。好的现在我们试一下。

    打印输出:

    MyClass1 myClass1 = new MyClass1<String>();
        myClass1.setData("Cyy");
        System.out.println(myClass1.getData());
        MyClass1 myClass2 = new MyClass1<Integer>();
        myClass2.setData(10);
        System.out.println(myClass2.getData());
    

    输出:

    Cyy
    10
    
    

    有没有发现,我们不用进行强制类型转换仍然能输出正确的数值。
    注意下,当我们new MyClass1<String>()传的是String那么我们类里面的所有T就都是String类型了。

    总结:

    泛型类使用优点:

    • 防止类膨胀

    • 不再手动进行类型转换

    泛型类的使用
    1. 泛型的类型参数可以是泛型类
    static class MyClass1<T1>
        {
            public MyClass1() {
            }
    
            private T1 data1;
    
            public T1 getData1() {
                return data1;
            }
    
            public void setData1(T1 data1) {
                this.data1 = data1;
            }
        }
    
        static class Student
        {
            private String name;
    
            public Student(String name) {
                this.name = name;
            }
    
            @Override
            public String toString() {
                return "Student{" +
                        "name='" + name + '\'' +
                        '}';
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
        }
        
    

    使用:

     MyClass1<MyClass1<Student>> myClass1MyClass1 = new  MyClass1<MyClass1<Student>>();
            MyClass1<Student> myClass1 = new MyClass1<Student>();
            myClass1.setData1(new Student("cyy"));
            myClass1MyClass1.setData1(myClass1);
            System.out.println(myClass1MyClass1.getData1().getData1().toString());
    

    输出:

    Student{name='cyy'}
    
    1. 泛型类可以同时设置多个类型参数
    static class MyClass1<T1,T2>
        {
            public MyClass1() {
            }
    
            private T1 data1;
            private T2 data2;
    
            public T2 getData2() {
                return data2;
            }
    
            public void setData2(T2 data2) {
                this.data2 = data2;
            }
    
            public T1 getData1() {
                return data1;
            }
    
            public void setData1(T1 data1) {
                this.data1 = data1;
            }
        }
    

    使用:

    MyClass1<String,Integer> myClass1 = new MyClass1<String,Integer>();
            myClass1.setData1("Cyy");
            myClass1.setData2(25);
            System.out.println(myClass1.getData1());
            System.out.println(myClass1.getData2());
    

    输出:

    Cyy
    25
    
    1. 泛型类可以继承泛型类
    class SuperClass<T1>
        {
            private T1 var1;
    
            public SuperClass(T1 var1) {
                this.var1 = var1;
            }
    
            public T1 show1()
            {
                return var1;
            }
        }
    
        class SubClass<T1,T2> extends SuperClass<T1>
        {
            private T2 var2;
    
            public SubClass(T1 var1, T2 var2) {
                super(var1);
                this.var2 = var2;
            }
    
            @Override
            public T1 show1() {
                return super.show1();
            }
        }
    

    使用:

        SubClass<String,Integer> subClass = new SubClass<>("cyy",25);
        System.out.println(subClass.show1());
    

    输出:

    cyy
    
    
    1. 泛型类可以实现泛型接口
     interface IInfo<T2>
        {
            public void show2(T2 var3);
        }
      static class SubClass<T1,T2> extends SuperClass<T1> implements IInfo<T2>
        {
            private T2 var2;
    
            public SubClass(T1 var1, T2 var2) {
                super(var1);
                this.var2 = var2;
            }
    
            @Override
            public T1 show1() {
                return super.show1();
            }
    
            @Override
            public void show2(T2 var3) {
    
                System.out.println(var3);
                System.out.println(var2);
            }
        }
    

    使用:

     SubClass<String,Integer> subClass = new SubClass<>("cyy",25);
            subClass.show2(100);
            System.out.println(subClass.show1());
    

    输出:

    100
    25
    cyy
    

    注:不可以进行泛型变量之间的运算,因为泛型变量在编译期间会进行类型擦除,全部变成Object,比如Object+Object就不知道是什么类型了,所以这点很重要。

    OK,现在我们可以回到最初那个问题上了,我们可以利用泛型定义一个CommResult类。

    public class CommResult <T> {
    
        private Meta meta;
        private T data;
    
        public Meta getMeta() {
            return meta;
        }
    
        public void setMeta(Meta meta) {
            this.meta = meta;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    
        static class Meta
        {
            private String code;
            private String message;
    
            public String getCode() {
                return code;
            }
    
            public void setCode(String code) {
                this.code = code;
            }
    
            public String getMessage() {
                return message;
            }
    
            public void setMessage(String message) {
                this.message = message;
            }
        }
    }
    

    然后使用的时候我们可以这样:

    JSON.parseObject(msg,CommResult<User>)JSON.parseObject(msg,CommResult<List<Device>>)

    这样就完美避免了创建多个结构一样,但是只有里面部分变量不一致的类了。

    3.限制泛型可用类型

    在定义泛型类别时,默认在实例化泛型类的时候可以使用任何类型,但是如果想要限制使用泛型时,只能用某个特定类型或者是其子类型才能实例化该类型时,可以在定义类型时,使用extends关键字指定这个类型必须是继承某个类,或者实现某个接口。
    当没有指定泛型继承的类型或接口时,默认使用extends Object,所以默认情况下,可以使用任何类型作为参数。

     class GenericClass<T extends Animal>
        {
            private T data;
    
            public GenericClass(T data)
            {
                this.data = data;
            }
    
            public T getData() {
                return data;
            }
    
            public void setData(T data) {
                this.data = data;
            }
        }
    
        abstract class Animal
        {
            public abstract void eat();
        }
    
        class Dog extends Animal
        {
            @Override
            public void eat() {
    
                System.out.println("啃骨头");
            }
        }
    
        class Cat extends Animal
        {
    
            @Override
            public void eat() {
    
                System.out.println("吃鱼肉");
            }
        }
    

    现在我们看下,如果我在泛型类里面传个String类型的参数,看他会报什么?
    Type parameter 'java.lang.String' is not within its bound; should extend 'Test.Animal

    他说String不是Animal子类,不行吧。

    如果我们换成这样就可以了。

    GenericClass<Dog> genericClass = new GenericClass<>(new Dog());
            genericClass.getData().eat();
            GenericClass<Cat> genericClasscat = new GenericClass<>(new Cat());
            genericClasscat.getData().eat();
    

    如果换成接口呢?

    class GenericClass<T implements eat>
    {
        private T data;
    
        public GenericClass(T data)
        {
            this.data = data;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    }
    

    这样写对不对,这样写是不对的,编译器会报错的,因为不管是接口还是类,都要用extends。所以换成接口也要写成这样就可以了。

    class Cat implements eat
    {
    
        @Override
        public void eat() {
    
            System.out.println("吃鱼肉");
        }
    }
    
    class Dog implements eat
    {
    
        @Override
        public void eat() {
    
            System.out.println("啃骨头");
        }
    }
    
    interface eat
    {
        public abstract void eat();
    }
    
    class GenericClass<T extends eat>
    {
        private T data;
    
        public GenericClass(T data)
        {
            this.data = data;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    }
    

    4.类型通配声明

    同一泛型类,如果实例化时给定的实际类型不同,则这些实例的类型是不兼容的,不能相互赋值。如:

    Generic<Boolean> f1 = new Generic<Booleab>();
    Generic<integer> f2 = new Generic<integer>();
    f1 = f2;//发生编译错误
    Generic<Object> f = f1 ;//f1和f类型并不兼容,发生编译错误
    f = f2;//f2和f类型同样不兼容,也会发生编译错误。
    

    泛型类实例之间的不兼容性会带来使用的不便。我们可以使用泛型通配符(?)生命泛型类的变量就可以解决这个问题。

    泛型通配的使用方式

    • "?" 代表一个类型。
    Generic<Boolean> f1 = new Generic<Booleab>();
    
    Generic<?> f= f1;
    
    
    • 和限制泛型的上线相似,同样可以使用extends关键字限定通配符匹配类型的上线:
    Generic<Dog> f1 = new Generic<Dog>();
    
    Generic<? extends Animal> f= f1;
    
    • 还可以使用super关键词将通配符匹配类型限定为某个类型及其父类型
    Generic<Animal> f1 = new Generic<Animal>();
    
    Generic<? super Dog> f= f1;
    

    现在要在这里特别说下两个限定通配符

    • extends 上边界限定通配符
    举个例子一看就懂了,<? extends Animal> , 那这里的`?`就必须是Animal的子类或它自己。
    
    • super 下边界限定通配符
    举个例子一看就懂了,<? super Dog> , 那这里的`?`就必须是Dog的父类或它自己。
    

    5.泛型方法使用

    不仅类可以声明泛型,类中的方法也可以声明仅用于自身的泛型,这种方法叫做泛型方法。其定义格式为:

    访问修饰符<泛型列表> 返回类型 方法名(参数列表) 
    {
        实现代码
    }
    

    在泛型列表中声明的泛型,可用于该方法的返回类型声明,参数类型声明和方法代码中的局部变量的类型声明。

    类中其他方法不能使用当前方法声明的泛型。

    注:是否拥有泛型方法,与其所在的类是否是泛型没有关系。要定义泛型方法,秩序将泛型参数列表置于返回值之前。

    什么时候使用泛型方法,而不是泛型类呢?

    • 添加类型约束只作用于一个方法的多个参数之间,而不涉及类中的其他方法时。

    • 施加类型约束的方法为静态方法,只能将其定义为泛型方法,因为静态方法不能使用其所在类的类型参数。

    再举个代码的例子:

    现在我们先定义一个泛型类:

    public class Demo1 {
    
       public static void main(String[] args)
       {
    
            GenericClassOne<String> genericClassOne = new GenericClassOne<>();
            genericClassOne.printlinT(10);
        }
    
    }
    
    
    class GenericClassOne<T>
    {
        public void printlinT(T content)
        {
            System.out.println(content);
        }
    }
    

    如果我们这么写,肯定编译就报错误了吧,因为我们上面定义的是String类型,但是我们传给他的是int型的。那如果这样的话,这个方法是不是就有局限性了。

    那如果我们现在使用泛型方法呢?该怎么写?

        public class Demo1 {
    
        public static void main(String[] args)
        {
    
            GenericClassOne genericClassOne = new GenericClassOne();
            genericClassOne.printlinT(10);
            genericClassOne.printlinT("cyy");
            genericClassOne.printlinT(12.5);
        }
    
    }
    
    
    class GenericClassOne<T>
    {
        //泛型方法,类型定义写在返回值之前了
        public <T> void printlinT(T content)
        {
            System.out.println(content);
        }
    }
    

    这下不会再报编译错误了,现在看下打印结果。

    输出:

    10
    cyy
    12.5
    

    这样是不是就灵活了许多啦~

    那么泛型的方法可不可以重载呀,当然可以,我们仍然可以写成这样。

    class GenericClassOne<T>
    {
        //泛型方法,类型定义写在返回值之前了
        public <T> void printlinT(T content)
        {
            System.out.println(content);
        }
    
        //泛型方法,类型定义写在返回值之前了
        public <T extends Animal> void printlinT(T animal)
        {
            animal.eat();
        }
    }
    
    abstract class Animal
    {
        public abstract void eat();
    }
    

    因为泛型类在编译过程中会有个擦除的工作,所以第一个printlnT(T content)中的泛型会变成object,而第二个泛型方法中的T会变成Animal。所以他的方法可以被重载。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,573评论 18 139
  • 废话不多说,自己进入今天的主题 1、面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: - 抽象:...
    传奇内服号阅读 2,329评论 1 31
  • 假日的我,轻松轻松筋骨,放松放松上班的紧张节奏感,扫扫地,拖拖地板,自个儿乐乐哼哼唱唱几句小曲,怡然自乐着,陶醉于...
    小草_d5ad阅读 205评论 17 21
  • 我们常能听到这样的感叹:“唉,都谈了5年了,尽管有太多不满意,但是我不想放弃这段感情”。 或者还有人说: “我们都...
    瑞田学习力阅读 355评论 1 5
  • 最近发生了很多事情,感叹“计划赶不上变化”这句话是真理。我急需要正念来内修下自己。碰巧本周是轮到我在微信群...
    玮苇子阅读 142评论 0 1