java基础--泛型(个人理解)

本文探讨了Java中的泛型,通过比较List的不同声明方式,解释了泛型带来的安全性、便利性和表述性。讨论了泛型接口的实现,包括指定类型、未指定类型以及类型约束,并通过实例展示了泛型方法的使用及其与泛型类的区别。

说到泛型,先从List说起,先上一段代码


        List list=new ArrayList();//原生态类型
        list.add("a");
        list.add("b");
        list.add(3);
        Object o=list.get(0);//object 类型使用需要强转
        String os= (String) o;
        System.out.println(o);//输出默认转为string,调用的是String.valueOf(o);

        List<String> listString=new ArrayList<>();//泛型类型
        listString.add("c");
        listString.add("d");
//        listString.add(3); //这里编译器提示错误,这不肯定的吗,因为在声明的时候已经把这个list定义为只能放入String类型
        String s= listString.get(0);//在这取出的对象就是String,不需要强转,保证安全
        System.out.println(s);
        /*
        容器使用泛型的好处,安全性:定义了String,编译器就不让往该容器里放其他类型内容;便利性:取出参数不需要强转;表述性:能看见容器里元素类型
         */

        List<?> listUnknow =new ArrayList<>();
//        listUnknow.add("e"); //这里编译器提示错误,List<?> 类型 编译器不知道?到底是什么类型,无法保证安全性,所以无法添加与修改,但可以删除
        listUnknow=listString;
        String s0= (String) listUnknow.get(0);//读取出来的是Object类型,需要强转
        System.out.println(s0);


        List<Object> listObject =new ArrayList<>();
        listObject.add("f");
        listObject.add("g");
        listObject.add(3);
        //看起来和 原生list没区别,但是
        list=listUnknow;
//        listObject=listUnknow;//这里编译器提示错误,没错,在接受赋值的时候就出现问题了

先来看看List的这几种声明方式,List,List<?>,List<Object>,这三者的区别在哪,这里一个个看.

首先List,在java SE5之前没有泛型这个概念,声明list使用原生态类型,这种类型容许往容器中放入任何内容(Object),在取出数据的时候也是Object类型,需要使用的时候就不得不强转,所以没有安全性;

再来看下一个List<String> ,泛型类型,并且声明容器内只能放入String,当往这个list中添加其他类型对象时,编译器提示错误,从该容器中取出的对象也只会是String,这便保证了安全性,容器使用泛型的好处如同代码所示:安全性:定义了String,编译器就不让往该容器里放其他类型内容;便利性:取出参数不需要强转;表述性:能看见容器里元素类型;

往下再看看List<?> ,定义时,告诉编译器,我也不知道它到底是什么类型,既然如此,那就让他无法添加和修改吧(null除外),所以listUnknow对象其实是只读的,你可以给它赋值,但是不能做修改和添加,当然,删除还是可以的,毕竟删除(remove)不需要关注里面到底是什么类型.从List<?>中取值出来的对象为Object,需要使用的时候要强转.

轮到List<Object>,声明了一个listObject 对象,它能添加能删除,而且还是Object,来者不拒.因为声明了是Object对象,所以取出来的对象也是Object,需要强转.这么看来好像和原生态List没啥区别?就目前看来两者挺像,但是当给list原生态对象和listObject对象赋值时,发生了一点问题,listObject好像不能接收List<?>,List<String>的赋值,而原生态list来者不拒,这里涉及到了泛型的擦除机制,List<String>,List<Integer>在编译器编译之后,最终都会转为List原生类型.


再说泛型接口,泛型接口先上一段代码

首先理一下结构,Child为Parent子类,Parent为GrandParent子类,分别有doSome及showMe方法,然后是泛型接口TService<T>,当然如果对于类型有限制也可写成<T extends Number>,Number可变换为需要的类型,具体情况具体分析.泛型接口定义了两个方法,分别是getT()与dosomeT(T t),具体代码如下:

public interface TService<T> {// 如果有类型限制 也可写成 TService<T extends Number>

    T getT();//T 可以是返回类型

    void doSomeT(T t);//T 也可以是参数类型

}


public class TestInt implements TService<Integer> {//实现接口的时候知道需要实现的类型,指定为Integer
    @Override
    public Integer getT() {
        System.out.println("TService<Integer> --- Integer 类型,返回随机数");
        return new Random().nextInt(50);
    }

    @Override
    public void doSomeT(Integer integer) {
        System.out.println("入参类型 Integer "+integer);
    }
}


public class TestString implements TService<String> {//实现接口的时候知道需要实现的类型,指定为String
    @Override
    public String getT() {
        System.out.println("TService<String> ----- String 类型,返回string");
        return "string";
    }

    @Override
    public void doSomeT(String s) {
        System.out.println("入参类型 String "+s);
    }
}


public class TestObject implements TService {
    @Override
    public Object getT() {
        System.out.println("TService ----- 返回Object");
        return "Object";
    }

    @Override
    public void doSomeT(Object o) {
        System.out.println("入参类型 Object "+o);
    }
}



public class  TestOther<T extends Parent> implements TService<T> {//实现接口的时候知道不需要实现的类型


    @Override
    public T getT() {
        //根据实际场景使用该方法
       return null;
    }

    @Override
    public void doSomeT(T t) {
        t.doSome();
        System.out.println("入参类型 "+t.getClass().getName());
    }
}


public class Child extends Parent {
    public String var="-Child";

    public final String varFinal="-ChildParentFinal";

    public static void  staticDoSome(){
        System.out.println("Child staticDoSome");
    }
    @Override
    public void doSome(){
        System.out.println("Child,doSome"+var);
    }
    public void showMe(){
        this.doSome();
        super.doSome();
        ((Parent)this).doSome();
        ((GrandParent)this).doSome();
        System.out.println("Child this.var--"+this.var);
        System.out.println("Child ((Parent)this).var--"+((Parent)this).var);
        System.out.println("Child ((GrandParent)this).var--"+((GrandParent)this).var);
        System.out.println("Child this.staticDoSome()--");
        this.staticDoSome();
        System.out.println("Child super.staticDoSome()--");
        super.staticDoSome();
    }
}


public class Parent extends GrandParent {
    public String var="-Parent";

    public final String varFinal="-ParentFinal";

    public static void  staticDoSome(){
        System.out.println("Parent staticDoSome");
    }
    @Override
    public void doSome(){
        System.out.println("Parent,doSome"+var);
    }
}


public class GrandParent {
    public String var="-GrandParent";

    public final String varFinal="-GrandParentFinal";

    public static void  staticDoSome(){
        System.out.println("GrandParent staticDoSome");
    }

    public void doSome(){
        System.out.println("GrandParent,doSome"+var);
    }
}

先看结构,TestInt implements TService<Integer> 实现接口的时候知道需要实现的类型,指定为Integer,所以这里的T就是Integer类型,TestString同理,可以看出泛型接口中定义的T表示当前不知道这个类型是什么,具体是谁看实现,反正放什么类型过来都行,如果不给定具体类型,一律按Object类型处理,就像TestObject那样.但是实际情况往往不止这些,比如,在实现接口的时候依旧不知道该声明声明类型,这时候该如何处理?既然这样,那就依旧声明为T吧,就像TestOther那样,甚至可以自己加上个限制,比如这个T必须是Parent的子类.

接下来,看看这段测试代码,

    @Test
    public void testTService(){
        //声明Integer类型
        TService<Integer> tInt=new TestInt();
        //声明String 类型
        TService<String> tString=new TestString();
        //不声明,默认为Object类型
        TService tObject=new TestObject();

        //声明一个TestOther<>对象
        TestOther tOther=new TestOther<>();
        //声明一个TestOther<Child>对象
        TestOther<Child> tOther2= new TestOther<>();

        tInt.doSomeT(10);//参数为Integer
        System.out.println(tInt.getT());

        tString.doSomeT("showMeString");//参数为String
        System.out.println(tString.getT());

        tObject.doSomeT("Object");//参数为Object,并出现警告
        System.out.println(tObject.getT());

        tOther.doSomeT(new Child());//出现警告
        tOther.doSomeT(new Parent());//出现警告
//        tOther.doSomeT(new GrandParent());//这里会编译器报错,因为 GrandParent 超过了泛型参数的上限

        tOther2.doSomeT(new Child());
//        tOther2.doSomeT(new Parent());//这里也会报错,参数只能为Child 类型
    }

分别声明了参数为Integer,String,Object(不传默认为Object)三种实现,tInt,tString,tObject,(这里插一句,T的类型不能为基本类型比如int,需要封装类型,比如Integer)既然已经声明了类型,就如同List<Integer>中不能存放String类型一般,tInt里面的参数也是Integer类型,doSomeT的参数也是Integer类型,tString同理,但是tObject的doSomeT就有点尴尬了,这里会出现警告,因为编译器也不知道后续操作会不会出现转换异常(当然这个测试中不会出现).

 

再看另一个情况接口实现的时候不知道T到底是什么类型,但是能肯定的是,那个未知类型将是Parent的子类所以这么声明

,tOther与tOther2,tOther默认泛型为Object,tObject2则不一样,声明时就说好了,这是一个Child对象,其他的恕不接待.所以可以看到,tOther能接受Child,Parent对象,但是GrandParent对象不行,因为在定义的时候就已经设置了上限,而tOther2上来就已经压缩了上限,只接受Child,如果将tOther的声明改成

TestOther<Parent> tOther2= new TestOther<>();

那么tOther也能接受Parent,当然对Child也是可以的,毕竟Child是Parent子类.

然后看看打印结果:

入参类型 Integer 10
TService<Integer> --- Integer 类型,返回随机数
16
入参类型 String showMeString
TService<String> ----- String 类型,返回string
string
入参类型 Object Object
TService ----- 返回Object
Object
Child,doSome-Child
入参类型 myTest.ex.Child
Parent,doSome-Parent
入参类型 myTest.ex.Parent
Child,doSome-Child
入参类型 myTest.ex.Child

在看看泛型方法,对TestOther稍微添加一些内容,添加 dealStaticSome,dealSome,getListT三个泛型方法,代码如下:

public class  TestOther<T extends Parent>  implements TService<T> {//实现接口的时候知道不需要实现的类型



    @Override
    public T getT() {
        //根据实际场景使用该方法
       return null;
    }

    @Override
    public void doSomeT(T t) {
        t.doSome();
        System.out.println("入参类型 "+t.getClass().getName());
    }

    public static <T> void dealStaticSome(T t){
        System.out.println(t.getClass().getName());
    }

    public <T> void dealSome(T t){
        System.out.println(t.getClass().getName());
    }

    public <T extends Number> List<T> getListT(T...ts){
        System.out.println("参数类型: "+ts.getClass().getName());
        List<T> listT=new ArrayList<T>();
        for (T t:ts){
            listT.add(t);
        }
        return listT;
    }
}

再看看调用方法

    @Test
    public void testDealT(){
        TestOther<Child> tOther2= new TestOther<>();
        TestOther.dealStaticSome("String");//正常
        tOther2.dealSome(10);//正常
        System.out.println(tOther2.getListT(1,2,3,4,5,6,7,8,9,0));
//        tOther2.getListT("string");//这里编译器报错
    }

这里同样也定义了一个tOther2对象,T为Child类型,调用三个泛型方法,输出结果如下:

java.lang.String
java.lang.Integer
参数类型: [Ljava.lang.Integer;
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

从这里可以看出,泛型类和泛型方法虽然都定义的是T

但实际上,泛型方法使用的时候,是不受泛型类中定义T所影响的,可以理解为两个不同的东西,调用泛型方法是,无论String,Integer都能接收,并且输出结果,而由于getListT这个方法对传入参数做了限定

<T extends Number>

所以输入的参数只能为数字类型.

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值