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

阅读: 评论:0

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

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

目录

(一)泛型方法

(二)泛型类

(三)泛型申明

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可以为其他字母,但通常习惯用单个大写字母T、V、E等。


(二)泛型类

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

public class Study {public static void main(String[] args) {Test01 test01 = new Test01("abc");System.out.Key());//Test02 test02 = new Test02(123);System.out.Key());//Test03 test03 = new Test03(new ArrayList<>(Arrays.asList("aaa", "bbb")));System.out.Key());}
}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.Key());//Test<Integer> test02=new Test<Integer>(123);System.out.Key());//Test<ArrayList<String>> test03=new Test<ArrayList<String>>(new ArrayList<>(Arrays.asList("aaa", "bbb")));System.out.Key());}
}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[]) wInstance(clazz, length);}public static <T> T[] createArray(T t, int length){return (T[]) Class(), length);}

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

本文发布于:2024-01-28 15:10:01,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/17064258068296.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:易懂   通俗   问号
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23