通俗易懂讲——深入理解泛型和问号?

目录

(一)泛型方法

(二)泛型类

(三)泛型申明

1、泛型申明的作用域

2、申明多个泛型

3、泛型可以在申明时限定使用范围

(四)问号“?”与泛型的关系

1、“?”用于初始化赋值。

2、“?”用于限定值的更新

3、“?”可以独立用于参数范围的限定

4、“?”通配符也可以和泛型配合

(五)泛型数组和泛型接口


力求通俗易懂,便于记忆。

(一)泛型方法

编程中经常会遇到相同的方法,相同的结构,只是变量类型不同。通常可以如下多写几个方法,但是代码量很大,如下例中的getprint():

public class Test {
    public static String getprint(String s) {
        return s;
    }

    public static Integer getprint(Integer i) {
        return i;
    }

    public static ArrayList<String> getprint(ArrayList<String> l) {
        return l;
    }

    public static void main(String[] args) {
        System.out.println(getprint("abc"));
        System.out.println(getprint(123));
        ArrayList<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        System.out.println(getprint(list));
    }
}
/*
运行结果:
abc
123
[aaa, bbb]
*/

既然只是类型不同,那有没有一种“类型变量”呢?

 我们可以用java提供的泛型来实现这种“类型变量”,以达到简化代码的目的。ps:不知道为翻译成泛型,我们可以理解成“广泛的类型”,或者理解成“通用类型”。

像其他变量一样,使用泛型也必须要申明,1、在方法的返回值类型前加<T>,申明泛型T。当然也可以是其他字母,泛型习惯上使用大写的T、V、E等。2、上例中我们把那些变化的类型:String、Integer、 ArrayList<String>用T代替。简化成如下代码:

public class Test {
    public static <T> T getprint(T s) {return s;}

    public static void main(String[] args) {
        System.out.println(getprint("abc"));
        System.out.println(getprint(123));
        ArrayList<String> list=new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        System.out.println(getprint(list));
    }
}

是不是简化很多?这种含有泛型申明<T>的方法我们把他称之为:泛型方法,其中T称之为泛型

这里需要说明下:getprint(T s)我们例子中传入了三个参数:"abc"、123、list,但并没有指定类型。是的,java自动判断了传入参数的类型,当传入"abc"时,java会判断传入了String类型,并自动将T替换成String,变成了一个String类型的方法,再传入参数:

    public static String getprint(String s) {
        return s;
    }

结论:泛型是一种变量

           1、泛型方法:其他变量一样使用泛型必须申明一个或多个泛型,即在方法的返回类型前加<T>,用以申明泛型T,表示该方法可以使用泛型T

           2、泛型方法传入参数时就会确定泛型T的类型,并将T替换成对应类型。

           3、泛型T可以为其他字母,但通常习惯用单个大写字母TVE等。


(二)泛型类

既然方法可以用泛型(通用类型),那么类呢?当然是可以的,先看代码:

public class Study {
    public static void main(String[] args) {
        Test01 test01 = new Test01("abc");
        System.out.println(test01.getKey());
        //
        Test02 test02 = new Test02(123);
        System.out.println(test02.getKey());
        //
        Test03 test03 = new Test03(new ArrayList<>(Arrays.asList("aaa", "bbb")));
        System.out.println(test03.getKey());
    }
}

class Test01 {
    private String key;

    public Test01(String key) {this.key = key;}

    public String getKey() {return key;}

    public void setKey(String key) {this.key = key;}
}

class Test02 {
    private Integer key;

    public Test02(Integer key) {this.key = key;}

    public Integer getKey() {return key;}

    public void setKey(Integer key) {this.key = key;}
}

class Test03 {
    private ArrayList<String> key;

    public Test03(ArrayList<String> key) {this.key = key;}

    public ArrayList<String> getKey() {return key;}

    public void setKey(ArrayList<String> key) {this.key = key;}
}
/*
运行结果:
abc
123
[aaa, bbb]
*/

Test01、Test02、Test03三个类有相同的方法,相同的结构,只是变量类型不同。

将上述三个类简化成一个泛型类,1、申明一个泛型,在类名后加<T>。2、把上述三个类不同的类型:String、Integer、 ArrayList<String>替换成T。简化得到如下代码:

public class Study {
    public static void main(String[] args) {
        Test<String> test01=new Test<String>("abc");
        System.out.println(test01.getKey());
        //
        Test<Integer> test02=new Test<Integer>(123);
        System.out.println(test02.getKey());
        //
        Test<ArrayList<String>> test03=new Test<ArrayList<String>>(new ArrayList<>(Arrays.asList("aaa", "bbb")));
        System.out.println(test03.getKey());
    }
}

class Test<T> {
    private T key;

    public Test(T key) {this.key = key;}

    public T getKey() {return key;}

    public void setKey(T key) {this.key = key;}
}

需要说明下,泛型类跟普通类比较,泛型类需要在申明时类名后用尖括号指定类型:

普通类:Test test=new Test();

泛型类:Test<String> test =new Test<String>();//等号后面那个尖括号里的String可以不要

尖括号指定String类型后,java系统会将该泛型类中的T替换成了String,自动生成了普通类。

class Test {
    private String key;

    public Test(String key) {this.key = key;}

    public String getKey() {return key;}

    public void setKey(String key) {this.key = key;}
}

如果泛型类申明时不指定类型,类型自动默认为Object。

结论:

           1、申明泛型:泛型类类名后面一定要加<T>以申明泛型T,表示该类可以使用泛型T

           2、申明泛型类:需要在申明时类名后用尖括号指定类型:java在申明后泛型类会将T替换成指定类型生成普通类。Test<String> test =new Test<String>();

           3、尖括号里的变量类型不能是基础变量类型:byte、short、int、long、float、double、char、boolean,必须用对应的:Byte、Short、Integer、Long、Float、Double、Character、Boolean。

           4、泛型T可以为其他字母,但通常习惯用大写字母T、V、E等。


(三)泛型申明<T>

  • 类似于变量申明,在类名后面或者方法返回类型前面加<T>,即表示申明了一个泛型T。该泛型T可以分别用于类或者方法中,在被调用前T自动替换成对应的类型。<T>也称为"泛型标识符"。
  • 最简单的泛型方法:public static <T> void p(){}//申明了泛型T但未使用

1、泛型申明的作用域

public class Stdudy<T> {
    /*
     * 方法前无<T>,泛型类中的方法,其中的T类型与泛型类的T类型一致。
     */
    public T gogo(T t) {return t;}

    /*
     * 方法前有<T>,泛型方法,其中的T类型是独立的,不受泛型类的T类型约束
     */
    public <T> T go(T t) {
        return t;
    }

    public static void main(String[] args) {
        String s = "abc";
        new Stdudy().go(s);
    }
}

上例中<T>出现两次,也就是泛型T被申明了两次,第一次是类名后,第二次是在方法go()前。

泛型其实就是一种变量,类的泛型T只能影响除go()方法外的所有T,go()方法的T相当于局部变量独立使用,不受类泛型T的影响。

静态方法只能使用泛型方法,泛型类无法影响静态方法中的泛型T。

2、申明多个泛型

泛型可以申明多个,需在泛型标识符里申明。并在方法或类中应用。如:

public class TestMethod {
    public static <T,R> void print(T t,R r){
        System.out.println(""+t+r);
    }

    public static void main(String[] args) {
        print("abc",123);
    }
}
public class TestMethod<T,R> {
    public void print(T t,R r){
        System.out.println(""+t+r);
    }

    public static void main(String[] args) {
        new TestMethod<String,Integer>().print("abc",123);
    }
}

3、泛型可以在申明时限定使用范围

extends:表示类型上界。例:A extends B,表示类型必须是B或B的子类

super:表示类型下界。例:A super B,表示类型必须是B或B的父类

&:接口

    public static <I extends Number  & Serializable & Cloneable> I getI(I i){
        return i;
    }

(四)问号“?”与泛型的关系

  • “?”是泛型通配符。但他不是泛型。好比是张三的苹果,不能说苹果就是张三。
  • “?”通常是独立使用,与泛型无关,也可以与泛型配合使用。
  • “?”不是泛型,因此不能用于泛型申明。
  • 泛型实际是一种类型占位符,在调用泛型方法或者泛型类之前就确定了类型,调用前先将泛型替换成了对应的类型再调用。因此泛型实质是确定的类型。ps:通俗说法便于理解别较真
  • “?”是不确定的类型,他表达的是一个类型范围。因此“?”定义的list,由于类型范围的问题,插入会校验时会报错。例如:List<?> list=new ArrayList<String>();(后面会详细讲原理。)

1、“?”用于初始化赋值。

  • 初始化赋值时,会校验初始化值的类型是否在<>的范围内,<?>的上限值是Object,下限值是无穷小的子类,因此任何类型都在其范围内。
  • <? extends Number>表示初始化值上限值是小于等于Number,下限值是无穷小的子类,因此Integer在其范围中。
  • <? super Integer>表示初始化值上限值是小于等于Object,下限值是大于等于Integer,因此Integer在其范围中。
        Class<?> classType = Class.forName("java.lang.String");
        List<?> listString = new ArrayList<String>();
        List<? extends Number> extendsNumber = new ArrayList<Integer>();
        ArrayList<? super Integer> superInt = new ArrayList<Integer>();

2、“?”用于限定值的更新

这个可能不好理解,先复习一个基础问题List<Number> list=new ArrayList<>();

这个list在插入值时,java会做一个校验,判断<>中的下限是什么,我们的插入值类型必须小于等于这个下限,否则报错。这个例子中插入值必须小于等于Number,即必须是Number或者他的子类。

一个结论:在List中插入或更新值的类型必须是<>中类型下限或者类型下限的子类

List<?> listString中<?>下限是无穷小子类,也就是没下限。因此很尴尬,listString无法插入除null以外的任何值。同理List<? extends Number>下限也是无穷小子类。因此也不能插入除null以外的任何值。

令人难以相信的是ArrayList<? super Number> numberList中,numberLIst的类型下限是Number,因此得出结论,插入值必须是Number或者他的子类。ps:是不是有点头晕,哈哈哈~~~

        //除null值外,都不能插入;可以读取
        List<?> listString = new ArrayList<String>();
        //除null值外,都不能插入,可以读取
        List<? extends Number> extendsNumber = new ArrayList<Integer>();
        //可以插入,读取数字
        ArrayList<? super Number> superInt = new ArrayList<Number>();

合并1、2的结论:

  • “?”通过自身或者extends或者super能限定一个范围(上限、下限)。
  • 在赋值时,校验值尖括号里的类型是否在“?”限定的范围内。
  • 在更新或插入值时,校验值的类型是否是“?”的下限的类型或者是下限的子类类型

3、“?”可以独立用于参数范围的限定

参数传入时等同于赋值给参数,遵循第1条《“?”用于初始化赋值》的相关规则。

参数传入后遵循第2条规则。

public class Test {
    public static void go(List<? extends Number> dest){
        System.out.println(dest);
    }
    public static void main(String[] args) {
        go(new ArrayList<>(Arrays.asList(111,222)));
    }
}

4、“?”通配符也可以和泛型配合

    public static <T> void go(List<? extends T> dest){
        System.out.println(dest);
    }

(五)泛型数组和泛型接口

1、使用泛型的数组不能直接初始化

错误:T[] tarr=new T[10];

正确:(用反射方式建立)

    public static <T> T[] createArray(Class<T> clazz, int length) {
        return (T[]) Array.newInstance(clazz, length);
    }

    public static <T> T[] createArray(T t, int length){
        return (T[]) Array.newInstance(t.getClass(), length);
    }

2、泛型应用于接口,跟类差不多,不再累述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值