泛型和包装类


一、泛型

早期 Java 是使用 Object 来代替任意类型的。这是因为基类的引用可以指向子类的对象。而 Object 是所有类型的祖先类,所以可以定义元素是 Object 类型,然后使用 Object 类型的引用指向任意类型的对象。

但是这样是存在问题的:在集合里由于没有了类型的限制,可以插入任意类型的对象,而且是不会产生语法错误的。但是集合是不知道这是什么类型的,只知道这是 Object 类型,因此返回时就是 Object 。在外部接受的时候就需要强转类型,甚至自定义类型之间如果不能强转,编译期间也不会报错,而是在运行时抛出了 ClassCastException (类型转换异常),就像下面被举了无数次的示例所示,这就大大降低了程序的安全性。
示例:

public class GenericTest {

    static class Person {
        String name;
        int age;
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        Integer i = new Integer(1);
        String str = "Hello";

        // 添加任意类型的元素
        arrayList.add(i);
        arrayList.add(str);
        arrayList.add(new Person("Tom", 27));

        // 接收返回值
        String result = (String) arrayList.get(0);
        System.out.println(result);
    }
}

结果:

Exception in thread "main" java.lang.ClassCastException  : java.lang.Integer cannot be cast to java.lang.String
	at generic.GenericTest.main(GenericTest.java:18)

1. 泛型介绍

1.1 设计原则

泛型把明确类型这项工作推迟到创建对象或者调用方法时才确定。而只要在编译期间没有警告,运行时就不会抛出 ClassCastException 异常。这是因为泛型可以自动进行类型的检查;自动进行类型的转换。

1.2 泛型特性

1. 泛型是定义在编译期间的一种机制,运行期间没有泛型的概念;

运行期间有没有泛型呢?

 public static void main(String[] args) {
      ArrayList<Integer> intArray = new ArrayList<>();
      ArrayList<String> strArray = new ArrayList<>();

      Class intClass = intArray.getClass();
      Class strClass = strArray.getClass();

      if (strClass.equals(intClass)) {
          System.out.println("这说明运行时类型相同");
      }
}

这是结果:

这说明运行时类型相同

Process finished with exit code 0

很明显运行时的两个类型相同,这就说明泛型只在编译期间有效。在泛型做完编译期间的工作(检查类型和强制转换)后,就擦除了,不会进入到运行阶段。而对于JVM来说,String和Integer也是不可见的。这就是泛型的擦除机制。

2. 泛型可以自动进行类型检查;自动进行类型转换;
泛型为什么被设计出来?就是为了解决这个问题!将运行期间有可能抛出异常的问题在编译期间就解决掉。让它不会出现ClassNotSupportException异常。
3. 泛型的参数不能是简单类型,应该是包装类 / 类;
泛型主要是对集合类的设计,集合类只能插入引用类型的元素,所以不能是简单类型。
4. 泛型类型的参数不参与类型组成.
就像第一个特性,由于泛型不出现在运行期间,怎么参与类型的组成。

2. 泛型使用

2.1 泛型分类

泛型可以分为泛型类、泛型接口和泛型方法,三种类型的定义如下所示:

泛型类的定义:

/**
 * 泛型类
 * 1.泛型的标志就是 <>, <T> 只是一个占位符, 表示当前类是泛型类
 * 2.T 是类型参数变量, 一般要大写. 可以是 T E V K等等;
 * 3.T 代表的是 MyArrayList 最终传入的类型, 目前还不知道, 使用时才能确定类型
 * 4.注意:这里定义了 T,那里面返回值和形参的类型就是 T,其他的会报错 "cannot reslove symbol X"
 */
public class MyArrayList<T> {

    public T[] elem;
    public int size;
    ......
    /**
     * 这不是一个泛型方法!!!
     * 因为没有泛型的标志 <> 
     * 这只是一个普通方法,返回值是在声明泛型类已经声明过的泛型
     */
    public T get(){
    	......
    }
}

泛型接口的定义:

/**
 * 泛型接口
 */
public interface Generic<T> {
    public T do();
}

泛型方法的定义:

/**
 * 泛型方法
 * 在返回值和修饰限定符之间有 <T> 表明这是泛型方法
 * 泛型数量可以是多个
 */
public <T> void add(T t) {
    ......
}

错误定义

public class MyArrayList<T> {
    /**
     * 这不是一个泛型方法!!!
     * 因为没有泛型的标志 <> 
     * 这只是一个普通方法,返回值是在声明泛型类已经声明过的泛型
     */
    public T get(){
    	......
    }
    
    /**
     * 这不是一个泛型方法!!!
     * 只是使用这个泛型作为形参而已
     */
     public void put(List<Integer> num) {
     	...
     }
     
     /**
     * 这不是一个泛型方法!!!
     * 根本没有声明过 E 这个类型
     */
     public <T> void show(Generic<E> num) {
     	...
     }
}

2.2 泛型的使用

代码如下:

public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();

        // 添加元素
        list.add("hello!");

        // 添加其他类型的元素
        list.add(1);// 会产生编译错误

        // 接受返回值时不需要类型转换
        String str = list.get(0);

        // 类型不匹配时会产生编译错误
        Integer i = list.get(0);// 编译错误
    }

3. 泛型总结

  1. 泛型是定义在编译期间的一种机制,运行期间没有泛型的概念;
  2. 泛型可以自动进行类型检查;自动进行类型转换;
  3. 泛型是为了解决某些容器、算法等代码的通用性而引入的,可以在编译期间进行类型检查;
  4. 泛型利用 Object 是所有类的祖先类,父类的引用可以指向子类对象的特定而工作;
  5. 泛型的参数不能是简单类型,应该是包装类 / 类;
  6. 泛型类型的参数不参与类型组成;
  7. 不能 new 一个泛型类型的数组;
  8. 泛型是怎么编译的?
    擦除机制:在编译期间将 T 擦除(而不是替换)为Object;

二、包装类

Object 引用可以指向任意类型的对象,但是 8 种基本数据类型不是对象,为了适配泛型的使用,Java中将这 8 种基本数据类型分别包装到一个对象中,引入了包装类。

1. 基本数据类型的包装类

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

2. 包装类的使用

2.1 装箱(装包)- boxing

装箱是简单类型包装到包装类型的过程,有自动装箱和显式装箱两种方法

// 装箱: 简单类型 -> 包装类型
Integer i1 = 10;// 自动装箱
Integer i2 = new Integer(10);// 显式装箱
Integer i3 = Integer.valueOf(10);// 显式装箱

通过 javap -c 类名 反编译,可以看到自动装箱底层还是Integer.valueOf();

Compiled from "GenericTest.java"
public class generic.GenericTest {
  public generic.GenericTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: bipush        10
       2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       5: astore_1
       6: new           #3                  // class java/lang/Integer
       9: dup
      10: bipush        10
      12: invokespecial #4                  // Method java/lang/Integer."<init>":(I)V
      15: astore_2
      16: bipush        10
      18: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      21: astore_3
      22: return
}


2.1 拆箱(拆包)- unboxing

拆箱是包装类型到简单类型的过程,同样有自动拆箱和显式拆箱两种方法

// 拆箱: 包装类型 -> 简单类型
int j1 = i1; // 自动拆箱
int j2 = i2.intValue();// 显式拆箱

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值