Day13-Java

@[toc]

关于作者

• 作者介绍

🍓 博客主页:作者主页
🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆。
🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻。

JDK三大主要特性——泛型

泛型可以帮助我们解决参数转换的问题

泛型的引出

下面首先通过一个简单分析来研究一下泛型出现的主要目的是什么?例如现在要求定义一个表示坐标的操作类(Point)这个类可以表示三种类型的坐标:

​ 整数坐标:x=10、y=20;

​ 小数坐标:x=10.1、y=20.3;

​ 字符串数据:x=“东经10度”、y=“西经20度”。

类之中如果想要表示以上的数据,一定需要定义x和y两个属性,而且每一个属性可以接收三种数据类型,那么只能使用Object类来定义会比较合适,这样会发生如下的几种转换关系:

整数:int→自动装箱为Integer→向上转型为Object;

小数:double→自动装箱为Double→向上转型为Obejct;

字符串:字符串→向上转型为Obejct;

设置整形

package com.demo;
class Point {
    private Object x;
    private Object y;
    public Object getX() {
        return x;
    }
    public void setX(Object x) {
        this.x = x;
    }
    public Object getY() {
        return y;
    }
    public void setY(Object y) {
        this.y = y;
    }
}
public class PointDemo {
    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);
    }
}

设置小数

package com.demo;
class Point {
    private Object x;
    private Object y;
    public Object getX() {
        return x;
    }
    public void setX(Object x) {
        this.x = x;
    }
    public Object getY() {
        return y;
    }
    public void setY(Object y) {
        this.y = y;
    }
}
public class PointDemo {
    public static void main(String[] args) {
        //第一步:设置数据
        Point p = new Point();
        p.setX(10.1);
        p.setY(20.2);
        //第二步:取出数据
        double x = (Double)p.getX();
        double y = (Double)p.getY();
        System.out.println("x = " + x + ",y = " + y);
    }
}

设置字符串

package com.demo;
class Point {
    private Object x;
    private Object y;
    public Object getX() {
        return x;
    }
    public void setX(Object x) {
        this.x = x;
    }
    public Object getY() {
        return y;
    }
    public void setY(Object y) {
        this.y = y;
    }
}
public class PointDemo {
    public static void main(String[] args) {
        //第一步:设置数据
        Point p = new Point();
        p.setX("东经10");
        p.setY("西经20");
        //第二步:取出数据
        String x = (String)p.getX();
        String y = (String)p.getY();
        System.out.println("x = " + x + ",y = " + y);
    }
}

看起来所有功能都实现了,并根据之前所学的内容,也只能做到这些了,但是本程序还有一系列问题。

本程序解决问题的关键就在Object类,所有的类型都可以想Obejct转换,但是成也是它败也是它。

public class PointDemo {
    public static void main(String[] args) {
        Point point = new Point();
        //设置参数
        point.setX(10);
        point.setY("北纬");
        //取出参数
        String x = (String) point.getX();
        String y = (String) point.getY();
        System.out.println("x的坐标是:"+x+"y的坐标是:"+y);
    }
}

这个时候程序并没有任何的语法错误,因为数字10 被包装成了Integer,可以使用Obejct接收,从技术上而言,本操作没有问题,但是从实际来讲,因为没有统一,多以在取得数据并且执行向下转型的过程中就会出现如下的错误提示信息:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at cn.mldn.demo.TestDemo.main(PointDemo.java:30)

所以,就可以得出一个结论,以上的程序存在安全隐患,但是并没有在程序的编译过程中检查出来,而现在就可以利用泛型来解决这种问题。

泛型实现

泛型:类之中操作的属性或方法的参数类型不在定义的时候声明,而是在使用的时候动态设置。

package com.demo;
//在定义Point不知道是什么类型,由使用者来进行定义使用
class Point<T> {//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;
    }
}
public class PointDemo {
    public static void main(String[] args) {
        //第一步:设置数据
        Point<String> p = new Point<String>();
        p.setX("东经10");
        p.setY("西经20");
        //第二步:取出数据
        String x = p.getX();//避免了向下转型
        String y = p.getY();
        System.out.println("x = " + x + ",y = " + y);
    }
}

测试没有了向下转型的操作关系,那么程序就避免了安全性的问题,而且如果设置的类型不统一,在程序编译的过程之中也是可以很好的解决了,直接会报出语法错误。

而且当用户在使用Point类声明对象的时候没有设置泛型,程序在编译的过程之中,会提示警告信息,而且为了保证程序不出现错误,所有的类型都将使用Obejct进行处理。使用泛型可以很好的解决数据类型的统一问题。

但是在此处需要提醒的是,JDK1.5和JDK1.7在定义泛型的时候是稍微有些区别的。

JDK1.5的时候声明泛型的操作

Point<String> p= new Point<String>();

以上是JDK1.5的语法,在声明对象和实例化对象的时候都必须设置好泛型类型。

JDK1.7的时候简化了

Point<String> p= new Point< >();

​ 这个时候实例化对象时泛型的泛型类型就通过声明时泛型类型来定义了。

通配符

泛型的而出现的确是可以解决了数据的统一问题以及避免了向下转型操作,但同事也会带来新的问题,下面通过一段程序,来观察一下会产生什么问题?

为了简化定义一个简单的泛型

package com.demo;

class Message<T>{
    private T info;

    public T getInfo() {
        return info;
    }

    public void setInfo(T info) {
        this.info = info;
    }
}

以上的类对象进行引用传递

public class MessageDemo {
    public static void main(String[] args) {
        Message<String> msg = new Message<>();
        msg.setInfo("hello,world!");
        print(msg);//以上的类对象进行引用传递
    }
    public static void print(Message<String> s){
            System.out.println(s.getInfo());
    }
}

但是如果现在定义的泛型类型不是String呢?例如:换成了int(不能是基本数据类型,只能是包装类)

public class MessageDemo {
    public static void main(String[] args) {
        Message<Integer> msg = new Message<>();
        msg.setInfo(100);
        print(msg);//无法进行引用传递
    }
    public static void print(Message<String> s){
            System.out.println(s.getInfo());
    }
}

发现这个时候的print()方法无法再接收Message<Integer>对象的引用,因为这个方法只能够接收Message<String>对象的引用,那么可以将print()方法重载换成Message<Integer>

public class MessageDemo {
    public static void main(String[] args) {
        // TODO 自动生成的方法存根
        Message<Integer> msg = new Message<>();
        msg.setInfo(100);
        print(msg);
    }
    public static void print(Message<String> msg){
        System.out.println(msg.getInfo());
    }
    public static void print(Message<Integer> msg){
        System.out.println(msg.getInfo());
    }
}

这个时候发现按照之前的方式根本就无法进行方法的重载,方法的重载没有说为一个类而定义的,因为方法重载的时候观察的不是泛型类型,而是类的名称,或者说是数据类型的,所以现在就可以发现,这个给出了泛型类之后,就相当于将一个类又划分成了几个小类。

image-20210815130238917

那么现在的问题:方法接收的参数问题又严重了,而且比之前使用对象多态性解决问题时出现的麻烦更大了,至少那个时候可以利用重载来接收一个类的所有子类对象,而现在连重载都使用不了。

​ 这个时候,有人提出了,干脆在定义方法的时候就别写泛型类型了。

定义方法的时候不定义泛型类型

public class MessageDemo {
    public static void main(String[] args) {
        // TODO 自动生成的方法存根
        Message<Integer> msg = new Message<>();
        msg.setInfo(100);
        print(msg);
    }
    public static void print(Message msg){
        System.out.println(msg.getInfo());
    }
}

虽然现在print()方法的参数上出现了警告,但是现在的程序可算是正常了,但是新的问题又来了。问题就在于方法操作中,没有类型限制了。

public static void print(Message msg){
        msg.setInfo(100);
        System.out.println(msg.getInfo());
}

发现此时在print()方法之中操作的时候,由于没有设置泛型类型,那么所有类型都统一变为了Object,也就可以修改了。而通过本程序也就发现了,必须找到一种方法,:此方法可以接收任意的泛型类型的设置,并且不能修改,只能输出,为了解决这样的问题,可以使用通配符“?”表示。

public static void print(Message<?> msg){
        System.out.println(msg.getInfo());
}

由于“?”出现的情况较多,尤其在学习一些类库的时候,所以对于“?”就记住一点,表示任意类型,如果有参数返回的时候也是这个“?”,当成Object进行理解。

既然现在谈到了Obejct,那么现在实际上又有了另外一个问题:对于所有的子类,都是Object子类,那么如果对于之前的程序都使用Object能不能接收?

Message<String> msg = new Message<>();
Message<Object> s = msg;

因为Object的范围比String的范围大。

而在通配符“?”上有衍生出了两个子符号:

  1. 设置泛型的上限:?extends 类;

​ 例如:?extends Number,表示只能是Number或者是Number的子类Integer等;

​ 2.设置泛型的下限:?super类;

​ 例如:?super String,表示只能是String或者是String的父类(Object)

设置泛型上限

package com.demo;

class Message<T extends Number>{
    private T info;

    public T getInfo() {
        return info;
    }

    public void setInfo(T info) {
        this.info = info;
    }
}
public class MessageDemo {
    public static void main(String[] args) {
        Message<Integer> msg = new Message<>();
        msg.setInfo(100);
        //100
        print(msg);
    }
    public static void print(Message<? extends Number> s){
            System.out.println(s.getInfo());
    }
}

设置泛型下限

package com.demo;

class Message<T>{
    private T info;

    public T getInfo() {
        return info;
    }

    public void setInfo(T info) {
        this.info = info;
    }
}
public class MessageDemo {
    public static void main(String[] args) {
        Message<String> msg = new Message<>();
        msg.setInfo("Hello,world!100");
        //Hello,world!100
        print(msg);
    }
    //此时使用通配符“?”描述的是它可以接收任何任意数据类型,但是由于不确定类型,无法修改
    public static void print(Message<? super String> s){
            System.out.println(s.getInfo());
    }
}
泛型接口

在之前的所有定义的泛型之中,都是在类上定义的,而对于接口也是可以进行泛型定义的,而使用泛型定义的接口可以称为泛型接口。

interface Message<T>{
    public String echo(T msg);
}

而对于泛型接口的实现,在Java中有两种方式:

方式一:在子类上继续定义泛型,同时此泛型继续在接口上使用

package com.test;
interface IMessage<T>{
    public void print(T t);
}
class MessageImpl<T> implements IMessage<T>{

    @Override
    public void print(T t) {
        // TODO Auto-generated method stub
        System.out.println(t);
    }

}
public class MessageTest {
    public static void main(String[] args) {
        IMessage<String> msgimpl = new MessageImpl();
        msgimpl.print("Hello,world!!");
    }
}

方式二:在子类上设置具体类型

package com.test;
interface IMessage<T>{
    public void print(T t);
}
class MessageImpl implements IMessage<String>{

    @Override
    public void print(String t) {
        // TODO Auto-generated method stub
        System.out.println(t);
    }

}
public class MessageTest {
    public static void main(String[] args) {
        IMessage<String> msgimpl = new MessageImpl();
        msgimpl.print("Hello,world!!");
    }
}
泛型方法

对于泛型除了可以在类上定义,也可以在方法上定义,而在方法上进行泛型的时候这个方法不一定非在泛型类中定义。

public class TestDemo {
    public static void main(String[] args) {
        // TODO 自动生成的方法存根
        Integer result[] = get(1,2,3);
        for(int temp : result){
            System.out.println(temp);
        }
    }
    public static <T> T[] get(T... date){
        return date;
    }
}