一、包装类
定义:在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型。
1.1 基本数据类型和对应的包装类
1.2 装箱和拆箱
//装箱:将一个基本数据类型转换为引用数据类型 int a=10; Integer value=a;//自动装箱 Integer value1=Integer.valueOf(20);//显示装箱 Integer value2=new Integer(20);//显示装箱 System.out.println(value+" "+value1+" "+value2); //拆箱:将Integer对象中的值取出,放到一个基本数据类型中 int num=value1;//自动拆箱 int num1=value1.intValue();
在上述使用过程中,装箱和拆箱带来不少的代码量,所以为了减少开发者负担,Java提供了自动机制。
面试题
public static void main(String[] args) { Integer a=127; Integer b=127; System.out.println(a==b); Integer c=128; Integer d=128; System.out.println(c==d); Integer e=Integer.valueOf(2); } //结果: true false
上述结果输出不同是因为在装箱过程中返回的引用类型不一样:
二、泛型
泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。
2.1 语法
class 泛型类名称<类型形参列表>{ //这里可以使用类型参数 } class ClassName<T1,T2,T3...,Tn>{ } class 泛型类名称<类型形参列表>extends 继承类/*这里可以使用类型参数*/{ //这里可以使用类型参数 } class ClassName<T1,T2,T3,...Tn>extends ParentClass<T1>{ //这里可以使用部分类型参数 }
代码实现:
class MyArray<T>{ //T[]kk=new T[10];//语法错误,泛型当中不能实例化泛型类型的数组 public T[]array=(T[])new Object[10]; public T getPos(int pos){ return this.array[pos]; } public void setArray(int pos,T val){ this.array[pos]=val; } } public class Test { public static void main(String[] args) { MyArray<Integer>myArray=new MyArray<>();//1 <Integer>指定当前类型 myArray.setArray(0,1); //myArray.setArray(1,"akai");//此时因为在注释1处指定类当前类型,此时编译器会在存放元素时帮助我们进行类型检查 System.out.println(myArray.getPos(0)); } }
代码解释:
1.类名后的<T>代表占位符,表示当前类是一个泛型类
了解:【规范】类型形参一般使用一个大写字母,常用的名称有:
·E表示Element
·K表示Key
·V表示Value
·N表示Number
·T表示Type
·S,U,V 等等-第二、第三、第四个类型
三、泛型类的使用
3.1 语法
泛型类<类型实参>变量名;//定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参);//实例化一个泛型类对象
3.2 示例
MyArray<Integer>list=new MyArray<Integer>();
注意: 泛型只接受类,所有的基本数据类型必须使用包装类
3.3 类型推导
当编译器可以根据上下文推导出类型实参时,可以省略类型室参的填写
MyArray<Integer>list=new MyArray<>();//可以推导出实例化需要的类型实参为Integer
3.4 裸类型
裸类型是一个泛型类但没有类型实参,例如:
MyArray list=new MyArray();
注:裸类型是为了兼容老版本的API保留的机制下面的类型擦除部分
小结:
1.泛型是将数据类型参数化,进行传递
2.使用<T>表示当前类是一个泛型类。
3.泛型类目前位置的优点:数据类型参数化,编译时自动进行类型检查和转换
四、泛型如何编译的
4.1 擦除机制
在编译过程当中,将所有的T替换为Obeject这种机制,我们成为:擦除机制。
Java的泛型机制是在编译级别实现的。编译器生成的字节码文件但在运行期间并不包含泛型的类型信息。
4.2 为何不能实例化泛型类型数组
class MyArray<T>{ public T[]array=(T[])new Object[10]; public T getPos (int pos){ return this.array[pos]; } public void setVal(int pos,T val){ this.array[pos]=val; } public T[]getArray(){ return array; } } public class Test { public static void main(String[] args) { MyArray<Integer>myArray1=new MyArray<>(); Integer[]Strings =myArray1.getArray(); } } /*Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; at Test.main(Test.java:27)*/
原因:替换后的方法为(由于运行时没有泛型机制,因此擦除机制在编译时期将T转换为Object):将Object[]分配给Intefer[]引用,程序报错。
public Object[]getArray{ return array; }
通俗讲就是:返回的Object数组里面,可能存放的是任何的数据类型,可能是String,可能是Person,运行的时候,直接转给Integer类型的数组,编译器认为是不安全的。
正确的使用方法:【了解即可】
class MyArray<T> { public T[] array; public MyArray() { } //涉及到映射 public MyArray(Class<T> clazz, int capacity) { array = (T[]) Array.newInstance(clazz, capacity); } public T getPos(int pos) { return this.array[pos]; } public void setVal(int pos,T val) { this.array[pos] = val; } public T[] getArray() { return array; } } public class Test { public static void main(String[] args) { MyArray<Integer> myArray1 = new MyArray<>(Integer.class,10); Integer[] integers = myArray1.getArray(); } }
泛型的常用方法:
public Objiect[] obj=new Object [3]; public E getPos(int pos){ return (E)obj[pos]; }
五、泛型的上界
5.1 语法
class 泛型类名称<类型形参 extends 类型边界>{ ... }
5.2 示例
1. public class MyArray<E extends Number>{ 2. ... 3. }
此时只接受Number的子类作为E的类型实参
MyArray<Integer>a1;//正常,因为Integer 是Number的子类型
MyArray<String>a2;//编译错误,因为String 不是Number的子类型
注: 没有指定类型边界的E,会默认擦除为Object类型。
5.3 复杂示例
//class Alg<E> 1 //E必须是实现了Com[arable方法的 class Alg<E extends Comparable<E>>{ public E findMax(E []array){ E max=array[0]; for (int i = 1; i < array.length; i++) { //if(max<array[i]) //if(max.compareT0(array[i])<0) //此时 E 类型为object类型,object中没有compare方法 2 if(max.compareTo(array[i])<0) max=array[i]; } return max; } } public class Test { public static void main(String[] args) { Alg<Integer> alg=new Alg<>(); Integer []array={1,3,5,6,2}; Integer val=alg.findMax(array); System.out.println(val); } }
六、泛型方法
6.1 定义语法
1. 方法限定符<类型形参列表>返回类型 方法名称 (形参列表){ 2. ... 3. }
6.2 示例
public class Test { //返回类型可以为任意类型,E也可以 public static <E> void swap(E[]array,int i,int j){ E t=array[i]; array[i]=array[j]; array[j]=t; } }
代码示例:
public class Test{ //返回类型可以为任意类型,E也可以 public static <E> void swap(E[]array,int i,int j){ E t=array[i]; array[i]=array[j]; array[j]=t; } public static void main(String[] args) { Integer []a={1,3,5,6,2}; Integer []b={2,3,2,1,5,6}; //可以a的类型推导E,因此<>中可以省略 Test.swap(a,2,3); Test.<Integer>swap(b,2,1); System.out.println(a[3]); System.out.println(b[1]); } }
七、 通配符
?用于在泛型使用,即为通配符
7.1 通配符用于解决什么问题
class Message<T>{ private T message; public T getMessage(){ return message; } public void setMessage(T message){ this.message=message; } } public class Common { public static void main(String[] args) { Message<String> message=new Message<>(); message.setMessage("zhonghuizhaodaoni"); func(message); } //如果此时泛型的类型设置的不是String,而是Integer,就会出现错误,而此时使用通配符 ? 就会很好的解决这个问题 public static void func(Message<String> temp){ System.out.println(temp.getMessage()); } }
在"?"的基础上产生了两个字通配符:
<? extends 上界>
<? extends Number> //可以传入的实参类型是Number或者Number的子类
7.2 通配符的上界
语法:
<? extends 上界>
<? extends Number> //可以传入的实参类型是Number或者Number的子类
class Food { } class Fruit extends Food { } class Apple extends Fruit { } class Banana extends Fruit { } class Plate<T>{ private T message; public T getMessage(){ return message; } public void setMessage(T message){ this.message=message; } } public class Common { public static void main(String[] args) { Plate<Apple> plate=new Plate<>(); plate.setMessage(new Apple()); func(plate); Plate<Banana> plate1=new Plate<>(); plate1.setMessage(new Banana()); func(plate1); } //此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,多以无法修改 public static void func(Plate<? extends Fruit> temp){ //temp.setMessage(new Banana());//仍然无法修改 //temp.setMessage(new Apple());//仍然无法修改 System.out.println(temp.getMessage()); } }
此时无法确定fun函数中对temp进行设置元素,因为temp接收的是Fruit和它的子类,此时存储的元素应该是哪个子类无法确定。所以添加会报错!但是可以获取元素。
public static void func(Plate<? extends Fruit> temp){ //temp.setMessage(new Banana());//仍然无法修改 //temp.setMessage(new Apple());//仍然无法修改 //System.out.println(temp.getMessage()); Fruit b=temp.getMessage(); System.out.println(b); }
通配符的上界,不能进行写入数据,只能进行读取数据。
8.3 通配符下界
语法:
<? super 下界>
<? super Integer> //代表可以传入的实参的类型是Integer或者Integer的父类类型
class Food { } class Fruit extends Food { } class Apple extends Fruit { } class Banana extends Fruit { } class Plate<T>{ private T message; public T getMessage(){ return message; } public void setMessage(T message){ this.message=message; } } public class Common { public static void main(String[] args) { Plate<Fruit> plate=new Plate<>(); plate.setMessage(new Fruit()); func(plate); Plate<Apple> plate1=new Plate<>(); plate1.setMessage(new Apple()); //func(plate1);//添加的必须是Fruit或者其父类 } //此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,多以无法修改 public static void func(Plate<? super Fruit> temp){ //此时可以修改!添加的是Fruit或者Fruit的子类,所以可以向上转型 temp.setMessage(new Banana()); temp.setMessage(new Fruit()); //temp.setMessage(new Food());//添加失败,因为若是Fruit的父类,则需要向下转型,向下转型是不安全的 //Fruit fruit= temp.getMessage();//不能接收,这里无法确定是哪个父类,需要向下转型为Fruit,此时也是不安全的 System.out.println(temp.getMessage());//只能直接输出 } }
通配符的下界,不能进行读取数据,只能写入数据