详解包装类&初识泛型

一. 包装类

1.1 包装类的引入

在 这篇博文 http://t.csdn.cn/jg7L6中提到过,八种基本数据类型都有自己对应的包装类

基本数据类型包装类
byteByte
shortShort

int 

Integer
longLong
charCharacter
floatFloat
doubleDouble
booleanBoolean

肯定有同学要问:这些包装类存在的意义是什么?

Java中一切皆对象,包装类让基本数据类型也面向了对象

先别开喷,我知道你内心的想法:

 让我用个例子来解释一下

写一个方法,实现字符串转成整型

public static void main(String[] args) {

      Scanner sc=new Scanner(System.in);

      String s=sc.nextLine(); int a=Integer.parseInt(s);

}

写好啦!不仅如此,我还能直接用String.valueOf()再把整型转成String类呢!

 当然在面试的时候这样写要被踹出去的,本博主想表达的意思是,如果只是一个朴实无华的int 类,能在类体内部提供各种方法吗?这时候包装类就十分有必要了

1.2 装包和拆包

顾名思义,装包,又称装箱,是指将基本数据类型转换成包装类

拆包,又称拆箱,是指将包装类转换成基本数据类型

下面演示装包的方法

 Integer a=10;//自动装箱

//显式装箱

Integer b=new Integer(10);

Integer c=Integer.valueOf(10);

 实际上自动装箱底层也是valueOf方法

拆包同样也有显式和自动 

 int e=a;//自动拆包
int d=b.intValue();//显式拆包

不论显式还是隐式,都是调用的intValue方法

 下面有一个so easy的问题,请你们来猜一下结果 

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

 是不是以为都是false?a,b,c,d都是对象嘛,都有自己的空间咯

但是编译器的运行结果并不是这样的

我们刚才提到过,自动转换的底层还是valueOf方法,现在来看看它的实现机制吧

 

 蓝线大意是说,为了更好地支配空间,会用缓存记载出现频率高的数字,红线的大意就是这些被Cache抓住的倒霉鬼就是[128,127]之间的数字

结合上文,我们就可以推断出IntegerCache.low==-128,IntegerCache.high==127,如果用自动装箱或用valueOf方法创造出来的对象在这个范围之内,会返回一个数组的元素(引用类型),所以a和b的地址是相同的

二. 泛型

2.1 什么是泛型

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

我知道你又在OS: 

 所以我又要拿一个例子来解释

2.2 引出泛型

现在想实现一个数组,可以装下任意类型的数据

class MyArray {

   public Object[] myArray=new Object[10];
   //设置下标为pos的元素
   public void setEle(int pos,Object val) {
       myArray[pos]=val;
   }
   
   //获取下标为pos的元素
   public Object getEle(int pos) {
       return myArray[pos];
   }
}

于是就可以往里面存任意类型的数据了

public class Test {
    public static void main(String[] args) {

        MyArray array=new MyArray();

        array.setEle(0,"abcdef");
        array.setEle(1,36);
        
    }
}

但是正所谓一时存一时爽,想要取出来就麻烦了

因为存在向下转型,编译器认为这是不安全的,所以必须强转

 但是又有一个问题了,如果有100个,1000个..我怎么能把所有元素的类型都记住呢?

所以我的问题又变了,请实现一个数组,可以存储任意类型但类型唯一的数据

这时候我们就可以把类型当做参数一样传进去,让当前数组只存储这一个类型的对象

2.3 泛型的语法

格式1:
class 泛型类名称 < 类型形参列表 > {
// 这里可以使用类型参数
}
//举例
class ClassName < T1 , T2 , ..., Tn > {//T是一个占位符,表示当前参数是一个类型
}
格式2:
class 泛型类名称 < 类型形参列表 > extends 继承类 /* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
//举例
class ClassName < T1 , T2 , ..., Tn > extends ParentClass < T1 > {
// 可以只使用部分类型参数
}

现在我们来实现一下刚才的数组

class MyArray<T>{
   public Object[] myArray=new Object[10];//此处不能T[] myArray=new T[10]
                                           下文会解释

   //设置下标为pos的元素
   public void setEle(int pos,T val) {
       myArray[pos]=val;
   }

   //获取下标为pos的元素
   public T getEle(int pos) {
       return (T)myArray[pos];
   }
}

 接着就可以创造多个数组存储多种数据类型的元素了

 注意看,如果使用Integer定义的数组来存储String类型,编译器就会报错,这也是使用泛型的好处

接下来强调几个注意点:

  • 第24行的第一个MyArray<Integer>中的Integer不可以省略,但new之后的Integer可以省略(因为编译器会自动推导)
  • 泛型类传入的类型参数不能是基本数据类型,只能是包装类或自定义类型

2.4 裸类型(Raw type)

裸类型是一个泛型类但没有带着类型实参

再把刚才的例子拿过来

public class Test {
    public static void main(String[] args) {

        MyArray array3=new MyArray();

        array.setEle(0,"abcdef");
        array.setEle(1,36);
        
    }
}

在定义泛型类MyArray之后,这个array3对象也是可以创建的,它是为了兼容老版本保留的机制

2.5 泛型是如何编译的

泛型的转换和检查(对象是否合法)会在编译的时候进行,所以JVM运行的时候不会出现泛型的概念

2.5.1 擦除机制

那么泛型类的定义在编译的时候会转换成什么呢?

我们借助反汇编来看一下

 可以看到,在setEle方法里,传入的参数是T类型,编译的时候转换成了Object类,在getEle方法里,返回的是T类型,编译的时候也转换成了Object类

在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制

 现在来解决刚才产生的一个问题:既然在编译的时候所有的T都会转换成Object,为什么new T[10]不能转换成new Object[10]然后通过编译呢?

因为数组有自己的规定,在实例化时必须指定类型

 2.5.2 泛型数组埋的坑

现在想实现一个方法,可以获得MyArray类中的数组

class MyArray<T>{

   public Object[] myArray=new Object[10];

   //设置下标为pos的元素
   public void setEle(int pos,T val) {
       myArray[pos]=val;
   }

   //获取下标为pos的元素
   public T getEle(int pos) {
       return (T)myArray[pos];
   }
   
   //获取数组
   public T[] getArray() {
       return (T[])myArray;
   }
}

public class Test {

    public static void main(String[] args) {
        MyArray<Integer> array1=new MyArray<Integer>();

        Integer[] array2= array1.getArray();//获取array1的数组

    }
}

上面的代码编译的时候可以通过,把myArray通过向下转型赋给了array2,但是在运行的时候会抛异常

 这是因为数组的检查是比较严格的,如果myArray数组存储了String,Double等其他类型,向下转型是十分不安全的。总之,数组对象不能向下转型

针对这个问题,可以使用反射解决,下面提供解决方式(以后再讲原理)

class MyArray<T>{

   public T[] myArray;

   public MyArray(Class<T> type,int capacity) {//第一个参数type传入类型,第二个参数传入容量
       myArray=(T[]) Array.newInstance(type,capacity);
   }

   //设置下标为pos的元素
   public void setEle(int pos,T val) {
       myArray[pos]=val;
   }

   //获取下标为pos的元素
   public T getEle(int pos) {
       return (T)myArray[pos];
   }

   public T[] getArray() {
       return myArray;//此处不需要强转,因为数组类型本来就是T[]
   }
}


public class Test {

    public static void main(String[] args) {

     MyArray<Integer> array1=new MyArray<>(Integer.class,20);

     Integer[] array2=array1.getArray();//获取array1的数组

     for(int i=0;i<array2.length;i++) {
         array2[i]=i;//给每个数组元素赋值
     }

     System.out.println(array1.getEle(2));//获取下标为2的元素
    }
}
//输出结果为2

但是在实际开发过程中,我们的解决方式往往不是上面这种,而是借鉴了源码

下面是ArrayList类的源码

 

 

 

 所以根据源码修改后的MyArray如下

class MyArray<T>{

   public Object[] myArray=new Object[10];

   //设置下标为pos的元素
   public void setEle(int pos,T val) {
       myArray[pos]=val;
   }

   //获取下标为pos的元素
   public T getEle(int pos) {
       return (T)myArray[pos];
   }

   public Object[] getArray() {
       return myArray;
   }
}
public class Test {

    public static void main(String[] args) {

     MyArray<Integer> array1=new MyArray<>();

     Object[] array2=array1.getArray();//只能用Object类型数组接收

     for(int i=0;i<array2.length;i++) {
         array2[i]=i;
     }
     System.out.println(array1.getEle(2));
    }
}

2.6 泛型的上界

2.6.1 为什么需要泛型上界

在刚才的MyArray类中定义一个方法,可以找出myArray的最大值

可以看到有多处爆红,因为可能传入String,Boolean等不能直接用比较运算符进行比较的类型,于是我们想到了使用Comparable接口中提供的compareTo方法

但是使用compareTo方法就意味着这个类必须实现Comparable接口,于是我们在类型参数列表加个限制

 

 2.6.2 泛型上界的语法

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

类型边界可以是某个类,表示传入的类型参数必须是这个类或者这个类的子类

类型边界也可以是某个接口,表示传入的类型参数必须实现了这个接口

 究其原理,泛型上界让编译器在编译的时候把T替换成上界允许的类型,而不是单纯的Object类

 2.7 泛型方法

泛型方法是指能处理指定类型的方法

定义在泛型类中的方法都是泛型方法,都可以根据泛型类传入的类型参数处理该类型的数据

但是如果这个类是个普通类,怎么在它内部定义泛型方法呢?

定义语法

方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }

示例: 

class GetMax {//定义一个普通类,用来提供geMax方法

    public static<T extends Comparable<T>> T getMax(T a,T b) {//可以对传入的参数类型添加上界

        if(a.compareTo(b)>0) return a;
        return b;
    }

}
public class Test {

    public static void main(String[] args) {

        int max1=GetMax.getMax(10, 20);//调用GetMax类中的静态方法,它会根据参数类型自动推导

        int max2=GetMax.<Integer>getMax(20,60);
        
    }
}
//max1=20,max2=60

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不 会敲代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值