1.包装类
1.1 包装类
在Java中,由于基本数据类型不是继承Object类,为了在泛型代码中可以支持基本数据类型,Java给每个基本数据类型各自提供了·一个包装类。
如下图
除了char和int基本数据类型的包装类型有点特别,其他的都是首字母大写
1.2 装箱与拆箱
1. 装箱
装箱就是将基本数据类型的数据转化成包装类,装箱分为自动装箱和显示拆箱。
public static void main(String[] args) {
int a=10;
Integer b=Integer.valueOf(a);//显示拆箱
Integer c=a;//自动拆箱
}
其实自动拆箱和显示拆箱的底层原理是一样的,都是调用了Integer.valueOf()方法。
2. 拆箱
拆箱就是将包装类的数据类型转换转换成基本数据类型,拆箱也分为自动拆箱和显示拆箱。
public static void main1(String[] args) {
Integer a=10;
int b=a.intValue();//显示拆箱
int c=a;//自动拆箱
}
2.面试题
了解装箱与拆箱,我们来看一道面试题
public static void main(String[] args) {
Integer a=100;
Integer b=100;
System.out.println(a == b);//打印true
Integer c=200;
Integer d=200;
System.out.println(c == d);//打印false
}
为什么会打印不同的结果呢?
我们来看Integer.ValueOf()方法的原码
我们发现,在进行装包操作的时候,会根据装包的数据的大小来返回不同类型的数据。
当要装包的数据的范围在 [-127~128] 之间时,valueOf 方法就会返回数组中的一个数据(整数)。
如下图
当数据不在上面的范围时,就会返回一个新的实例化的对象。
所以,由于100在【128~127】这个范围内,所以两者的比较是两个整数之间的比较。
由于200超出了以上范围,所以c和d的比较实际上是两个对象之间的比较,有==来比较两个对象,返回值当然是false。
2. 泛型
2.1 泛型的概念
一般类和方法,只能使用具体的数据类型,要么是基本数据类型或者是引用数据类型。当我们要编写设计多种数据类型的程序时,一旦我们将数据类型固定,那么对于后续的编程的限制会很大。所以在Java中提出了泛型的概念,所谓泛型,就是将数据类型参数化。
2.2 引出泛型
我们先来看一道题:实现一个类,该类中有一个可以存储任何数据类型的数组,并且可以通过方法来设置数组中的值和获取数组中对应的内容。
代码如下图
在类中,我们创建了一个Object类型的数组,这样就可以存储任何数据类型了。但是,后面我们发现,我们可以通过方法来直接设置数组中的内容,但是我们通过方法来获取数据类型的时候,却发现会报错,这是因为发生了向下转型,我们需要进行强制类型转换才能正确获得数组中的任何数据。这样想想就很奇怪了,我明明可以存储任何数据类型,但却不能直接1获取数组中的数据。为了解决这个问题,我们就可以使用泛型。
如以下代码
class DataBase<E>{
Object[] array=new Object[10];
public void setArray(int pos,E obj){
array[pos]=obj;
}
public E getArray(int pos){
return (E)array[pos];
}
}
public class Test {
public static void main(String[] args) {
DataBase<Integer> dataBase=new DataBase<>();
dataBase.setArray(0,10);
Integer a=dataBase.getArray(0);
DataBase<String> dataBase1=new DataBase<String>();
dataBase1.setArray(1,"man");
String str=dataBase1.getArray(1);
}
}
< E >就是泛型的用法 ,可以发现,当我们使用泛型之后,我们就可以直接来获取数据了,不用进行强制转换了。
简单来说,使用泛型,就行我们在创建类的时候,通过泛型,我们可以将数据类型转换为参数来进行类的创建。如上图,我们在实例化database对象的时候,我们将Integer的数据类型作为参数传过去,所以此时,对于字母E就代表Integer数据类型,实例化database1的时候,我们将String数据类型作为参数传过去,此时,对于database1来说,字母E就代表String类型。
注意事项:<E>可以理解为一个标识符,代表该类为泛型类。
2.3 泛型的语法
class 类名<T>{
}
类名后的<T>是一个标识符,表示当前类为泛型类。 其中< >里面也可以是其他字母,常见的有T和E。
3.泛型类的使用
3.1 语法格式
泛型类名<类型实参> 变量名=new 泛型类名<类型实参>();
举例
class MyFunc<T>{
}
public class Test {
public static void main(String[] args) {
MyFunc<Integer> myFunc=new MyFunc<Integer>();
MyFunc<String> myFunc1=new MyFunc<>();
}
}
在创建泛型类对象时,后面的new< >里面的包装类可以不写,编译器会根据前面的包装类来推导后面的包装类类型。
注意事项:泛型只能接受类,所有的基本数据类型必须使用包装类。
4. 裸类型(了解)
裸类型是一种不带参数的泛型类型,它是为了兼容以前老版本JDK没有泛型的版本。例如MyArrayList就是一个裸类型。
5. 擦除机制
泛型是如何进行编译的呢?
泛型是编译时期的机制,代码在运行的时候没有泛型的概念。
这就涉及到擦处机制:在编译完成后,所有的传给泛型类的数据类型,最终都会被擦除为Object类。所以,编译之后的字节码无泛型,只有Object类。
6. 泛型的上界
在定义泛型类时,有时需要对传入的类型变量进行限制,这时候可以通过类型边界来限制。
6.1 语法
class 泛型类名称<类型形参 extends 类型边界> {
...
}
举例
这时定义的泛型类时传入的类型参数的上界为Number,传如的类型参数必须是Number的子类,由于String类不是Number的子类,所以会报错。
注意事项:没有泛型边界的,默认边界为Object。
6.2 复杂例子
题目要求:创建一个类,里面有一个方法来获取数组中的最大值。
我们会很直接的写下以下代码
当我们写出上图的代码之后,我们发现会报错。这是因为泛型类的E代表很多数据类型,我们不能用平常的数学思维去比较。这时候我们可以对传入的类型参数实现comparable接口来解决问题。
class Alg<E extends Comparable<E>>{
public E FindMax(E[] array){
E max=array[0];
for(int i=1;i<array.length;i++){
if(array[i].compareTo(max)>0){
max=array[i];
}
}
return max;
}
}
public class Test {
public static void main(String[] args) {
Integer[] array={1,2,3,4,5};
Alg alg=new Alg();
int ret=alg.FindMax(array);
}
}
这时,由于我们在定义泛型类的时候使用了Comparable接口,所以,此时,传入的类型参数必须实现Comparable接口,否则会报错。
7. 泛型方法
7.1 语法格式
方法修饰限定符 <类型参数> 返回值类型 方法名(){
}
举例
public static<E extends Comparable<E>> E FindMax(E[] array)
以上题目,获取最大值的另一种代码形式
class Alg{
public static<E extends Comparable<E>> E FindMax(E[] array){
E max=array[0];
for(int i=1;i<array.length;i++){
if(array[i].compareTo(max)>0){
max=array[i];
}
}
return max;
}
}
public class Test {
public static void main(String[] args) {
Integer[] array={1,2,3,4,5};
Alg alg=new Alg();
Integer ret=Alg.FindMax(array);//使用类型推导
Integer ret2=Alg.<Integer>FindMax(array);//不使用类型推导
}
}
感谢观看。