600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > 【Java数据结构】泛型详解+图文 通配符上界 下界

【Java数据结构】泛型详解+图文 通配符上界 下界

时间:2021-04-16 08:32:26

相关推荐

【Java数据结构】泛型详解+图文 通配符上界 下界

0. 泛型的本质0. 泛型的目的1. 泛型的语法1.1 泛型的使用2. 包装类2.1 装箱和拆箱2.2.1练习题3 .泛型如何编译4.泛型的上界5. 通配符5.1通配符上界5.2通配符下界有坑填坑

0. 泛型的本质

泛型的本质:泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数

化。

总的来说,泛型就是把类型参数化了,即给类型一个指定的参数,在使用时指定参数具体的值,这个类型就可以在使用的时候决定了,这样就可以编写应用于多种类型的代码。

0. 泛型的目的

泛型的目的:是指定当前容器,要持有什么类型的对象,让编译器去做检查,检查类型的安全性,并且所有的强制转换都是隐式转换的。此时,就需要把类型,作为参数传递,提高了代码的复用性。

1. 泛型的语法

class 泛型类的名称<类型形参列表>{//代码块内使用类型参数}class Student<T1,T2....Tn>{}

泛型的继承

class 泛型类型名称<类型形参列表> extends 继承类<类型形参列表>//示例class Mike<Integer> extends Student<Integer>{}//可以只是用部分类型参数class Rose<T1,T2..Tn> extends Student<T1>{}

1.1 泛型的使用

泛型类代码实例:

了解:【命名规范】类型形参一般使用一个大写字母表示,常用的名称有:

注释1处:不能直接new泛型的数组

我是先new一个Object数组,然后强制转换为T类型注释2处:类型(Student)后加入< Integer >是指定当前类型,new Student<>,这里的<>内可加也可不加Integer。注释3处:不需要强制类型转换,Integer是包装类,可以自动拆箱和装箱。这也就提高了效率。注释4处:代码编译报错,因为在注释2处指定类的当前类型是Integer,此时在注释4处,存放元素时,编译器就会帮助我们进行类型检查。

2. 包装类

在JAVA中,由于基本类型不是继承于Object,为了在泛型代码中可以支持基本类型,JAVA给每个基本类型都对应了一个包装类。

需要特别注意的是:int和char的包装类是Integer和Character,其他的基本类型的包装类都是基本类型首字母大写。

2.1 装箱和拆箱

int a = 10;/*装箱操作*/Integer b = Integer.value(a); //调用value方法Integer c new Integer(a);//调用构造方法/*拆箱操作*/int d = c.intValue();

在下面几行行代码,编译器自动调用了Integer.valueof(),属于自动装包和拆包。

2.2.1练习题

【挖坑:】下列代码输出什么,为什么?

public static void main(String[] args) {Integer a = 100; Integer b = 100; Integer c = 200; Integer d = 200; System.out.println(a == b); System.out.println(c == d); }

3 .泛型如何编译

擦除机制

在编译的过程中,将所有的T都替换为Object的这种机制,称为:擦除机制。JAVA的泛型机制是在编译时实现的,编译器生成的字节码在运行期间并不包含泛型的类型信息(运行时没有泛型这么一说了)。

思考:Why?T[] T = new T[10];是不对的。编译的时候替换为Object,不是相当于:Object[] T = new Object[10];类型擦除:一定把T擦除给Object吗?解惑:

T[]擦除成了Object,Object是不能用其他类型接收的,因为Object可以存各种类型,把Object里的元素取出来转换成某个具体元素,是不安全的,JAVA不支持这么做。

数组和泛型之间有一个重要的区别就是:它们是如何强制执行类型检查。数组在运行时存储和检查类型信息,而泛型是在编译时检查类型错误。

所以:泛型是不能实例化泛型类型数组。前面我写的T[] t = (T[]) new Obeject[10];其实也是错的。

4.泛型的上界

class 泛型类名称 <类型形参> extends<类型形参> {//....}

示例:

class Myarray{//..}class Int extends Myarray{//..}class Long extends Int{//...}public class Maths<E extends Myarray>{//...}

解释:Maths<E extends Myarray>E只能是Myarray的子类,也就是说**E接受的类型实参只能是Myarray的子类**。

再看一个例子:

class Student<E extends Number>{E[] arr;E Name;}public class Test {public static void main(String[] args) {Student<Integer> in;//1Student<String> str;//2}}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6YVc6XOJ-1665319877790)(C:\Users\COFFEEWEN\AppData\Roaming\Typora\typora-user-images\image-1009194623454.png)]

**解释:**注释1处,编译正确,因为IntegerNumber的子类型,注释2处,编译错误,因为String不是Number的子类型。

了解:Number是java.lang包下的一个抽象类,提供了将包装类型拆箱成基本类型的方法,所有基本类型的包装类型都继承了该抽象类,并且是final声明不可继承改变;

了解:没有指定类型边界 E,可以视为E extends Object

5. 通配符

通配符是用于解决泛型之间引用传递问题的特殊语法,更为灵活,多用于扩大参数的范围。

示例:

class Fruits<T>{private T fruits;public T getFruits(){return this.fruits;}public void setFruits(T fruits){this.fruits = fruits;}}public class Demo {public static void main(String[] args) {Fruits<String> fruits1 = new Fruits<>();fruits1.setFruits("苹果");put(fruits1);//输出Fruits<Integer> fruits2 = new Fruits<>();fruits2.setFruits(100);put(fruits2);}public static void put(Fruits<?> tmp){//1.System.out.println("这个水果是:"+tmp.getFruits());tmp.setFruits("banana");//2.无法修改tmp.setFruits(66);//3.无法修改 Fruits ret = tmp.getName();//4.无法接收}}

解释:

注释1:处put(Fruits<?> tmp)使用无边界通配符,说明它是可以接受任意类型,所以当不同类型的fruits1和fruits2调用put进行传参时,并不会报错。

注释2处、3处:由于是可以接受任意类型的,tmp是不确定类型的,不允许进行修改操作。比如,你此时传入了一个String类型的(传参时不会报错),可是你代码里写的是tmp.setFruits(66)设置的是int型,这样是很不安全的,所以编译器直接不允许你这么写。

注释4处:传入的参数类型是不确定的,不能用Fruits来接收。

5.1通配符上界

class Apple extends Fruits{}class Banana extends Fruits{}class Fruits extends Meat{}class Meat{}class Food<T>{private T name;public T getName(){return this.name;}public void setName(T name){this.name = name;}}public class Demo {public static void main(String[] args) {Food<Apple> appleFood = new Food<>();//因为Food<T> 给的类型是Apple,所以你只能给相同类型的数据appleFood.setName(new Apple());fun(appleFood);Food<Banana> bananaFood = new Food<>();bananaFood.setName(new Banana());fun(bananaFood);}public static void fun(Food<? extends Fruits> tmp){//1.System.out.println(tmp.getName());tmp.setName(new Banana());//2.无法修改tmp.setName(new Apple());//3.无法修改Fruits ret = tmp.getName();//读取数据}

解释:

注释1处:使用了上界通配符,表示可以传入的实参类型是Fruits和Fruits的子类型。注释2、3处:由于是不确定类型的,所以无法修改,因为tmp接收的是Fruits和它的子类,此时存储的元素类型应该是哪个子类,是无法确定的,所以设置会报错!但是可以获取元素,因为ret的类型的Fruits,是所有能传入参数的父类。通配符上界:可以读取数据,不能写入数据

5.2通配符下界

public static void main(String[] args) {Food<Fruits> f1 = new Food<>();f1.setName(new Fruits());fun(f1);Food<Meat> f2 = new Food<>();f2.setName(new Meat());fun(f2);}public static void fun(Food<? super Fruits> tmp){//1.System.out.println(tmp.getName());//只能直接输出tmp.setName(new Banana());//2.可以修改tmp.setName(new Fruits());//3.可以修改Fruits ret = tmp.getName();//4.不能读取数据}

解释:

注释1处:使用的是通配符下界,表示接受的参数只能是Fruits或者是Fruits的父类。注释2、3处:可以修改,因为tmp的类型是Fruits或者它的父类,只能设置Fruits的子类或者它本身。注释4处:如果此时传入的参数是Fruits的父类,用Fruits类型来接收是不安全的,换句话说,传入的参数是不确定是哪个父类,不能接收。通配符下界:不能读取数据,可以写入数据。

配符下界:不能读取数据,可以写入数据。

有坑填坑

填坑:

public static void main(String[] args) {Integer a = 100; Integer b = 100; Integer c = 200; Integer d = 200; System.out.println(a == b); //1.System.out.println(c == d); //2.}

解释:

注释1处:输出true;注释2处:输出false;Why?

Integer源码有一个cache数组事先存储了【-128,127】之间的数据,总共256个数据,这些数据被static final修饰,是不能改变的。当赋的值是在这个区间里,会直接在这个数组里读取,也就是变量a和b,指向了同一个对象,所以输出true。当超过了这个区间,每次都会重新new一个对象,所以输出false

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。