包装类&简单认识泛型

简介: 包装类&简单认识泛型

包装类

java中,由于基本数据类型不是Object,但为了将基本数据类型当作对象处理,Java给每个基本类型都对应了一个包装类型。

基本数据类型和对应包装类

基本数据类型 包装类
byte Byte
short Short
int Integer
long  Long
boolean Boolean
float Float
double Double
char Character

规律:除了Integer和Character,其它基本数据类型的包装类都是首字母大写

装箱和拆箱

int i = 10;

//(显式)装箱操作,新建一个Integer对象,将i的值放在对象的某个属性当中

Integer ii = Integer.valueOf(i);

Integer ij = new Integer(i);

//(显式)拆箱操作,将Integer从包装中取出,放到一个基本数据类型中

int j = ii.intValue();

自动装箱和自动拆箱

可以看到,使用过程中,显式地装箱和拆箱带来了不少代码量,所以为了减少给开发者带来的负担,Java提供了自动机制

int i = 10;

Integer ii = i;//自动装箱

Integer ij = (Integer)i;//自动装箱

int j = ii;//自动拆箱

int k = (int)ij;//自动拆箱

什么是泛型(也算是一种语法)

一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。如果要编写出可以应用于多种类型的代码,这种刻板的的限制对代码的约束就很大。

我们便引入了泛型:就是适用于多种类型。从代码上讲,就是对类型实现了参数化。

引出泛型

实现一个类,类中包含一个数组成员,是的数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值。

思路:所有类的父类都是Object类,数组是否可以创建为Object类型的呢

引入代码:

class MyArrays {
    private Object[] array = new Object[10];
 
    public void setVal(int pos, Object val) {
        this.array[pos] = val;
    }
 
    public Object getPos(int pos) {
        return this.array[pos];
    }
}
 
public class Test1 {
    public static void main(String[] args) {
        MyArrays myarrays = new MyArrays();
        myarrays.setVal(0,10);
        myarrays.setVal(1,"hello");
        //编译错误
        String ret = myarrays.getPos(1);
    }
}

问题:在上述代码中:

1.任何数据的类型都可以存放

2.但在取出时会发生编译错误,因而需要强制类型转换。

虽然在这种情况下,当前数组的任何数据都可以存放,但是在更多的情况下,我们还是希望它只能够持有一种数据类型。而不是同时存放这么这么多的类型。

所以,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去检查(比如持有int类的,就不能放入String类型的数据),此时就需要把类型,作为参数传递。需要什么类型,就传入什么类型。

语法

class 泛型类名称 <类型形参列表> {
    //这里可以使用类型参数
}
 
class ClassName<T1, T2, ...Tn> {
}
 
class 泛型类名称<类型形参列表> extends 继承类/*这里可以适用类型参数*/ {
    //这里可以使用类型参数
}
 
class ClassName<T1, T2,.. Tn>extends ParentClass<T1> {
    //可以使用部分类型参数
}

将上述代码改造如下:

class MyArrays<T> {
    private T[] array = (T[])new Object[10];//1
 
    public void setVal(int pos, T val) {
        this.array[pos] = val;
    }
 
    public T getPos(int pos) {
        return this.array[pos];
    }
}
 
public class Test1 {
    public static void main(String[] args) {
        MyArrays<Integer> myArrays = new MyArrays<>();//2
        myArrays.setVal(0,1);
        myArrays.setVal(1,2);
        int ret = myArrays.getPos(0);//3
        System.out.println(ret);
        //myArrays.setVal(2,"hh");//4
    }
}

代码解释:

1.类名后的<T>表示占位符,表示当前类是一个泛型类

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

E代表Element

K表示Key

V表示Value

N表示Number

T表示Type

2.注释一处,不能new泛型类型的数组

T[] ts = new T[10];//是不对的

泛型是编译时存在的,当程序运行起来到JVM后,就无泛型这个概念

3.注释2处,类型后加入<Integer>指定当前类型

4.注释3处,不需要进行强制类型转换

5.注释4处,代码编译报错,此时因为已经指定好当前类型了,编译器会在存放元素的时候帮助我们进行类型检查

泛型类的使用

语法

泛型类<类型实参> 变量名;//定义一个泛型类引用

new 泛型类<类型实参>(构造方法实参);//实例化一个泛型类对象

示例

MyArrays<Integer> array = new MyArray<Integer>();

注意:泛型只能接受类,所有的基本数据类型必须使用包装类!

类型推导

当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写

//可以推导出实例化所需要的类型为Integer
MyArrays<Integer> array = new MyArray<>();

泛型是如何编译的

擦除机制

那么,泛型到底是怎么编译的?这个问题,也是曾经的面试问题。泛型本质是一个非常难的语法,要能理解还需要长时间的打磨。

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

Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息

提出问题:

类型擦除,一定是将T变成Object吗

不一定。之前泛型类中的类型参数部分如果没有指定上限,如<T>则会被转换成普通的Object类型。如果指定了上限如<T extends String>则类型参数就被替换成类型上限。

为什么不能实例化泛型类型数组

代码:

public 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 static void main(String[] args) {
        MyArray<Integer> myArray1 = new MyArray<>();
        
        Integer[] strings = myArray1.getArray();
    }
}
 
/*​Exception in thread "main" java.lang.ClassCastException: 
class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.Integer; ([Ljava.lang.Object; and [Ljava.lang.Integer; are in module java.base of loader 'bootstrap')at Demo2.MyArray.main(MyArray.java:21)*/

原因想必大家知道:替换后的方法为将Object[]分配给Integer[]引用,程序报错。

通俗讲就是:返回的Object数组里面,可能存放的是任何的数据类型,可能是String,可能是Person,运行的时候,直接转给了Integer类型的数组,编译器认为是不安全的。

正确的方式:

import java.lang.reflect.Array;
 
public 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;
    }
 
    /**
     * 通过反射创建,指定类型的数组
     * @param clazz
     * @param capacity
     * */
 
    public MyArray(Class<T> clazz, int capacity) {
        array = (T[]) Array.newInstance(clazz, capacity);
    }
 
    public T[] getArray() {
        return array;
    }
 
    public static void main(String[] args) {
        MyArray<Integer> myArray1 = new MyArray<>(Integer.class, 10);
 
        Integer[] strings = myArray1.getArray();
    }
}

泛型的上界

在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。

语法

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

示例

public class MyArray<E extends Number>{
...
}

只接受Number的子类型作为E的类型实参。

MyArray<Integer>|1;//正常,因为Integer是Number的子类型
MyArray<String>|2;//编译错误,因为String不是Number的子类型

如果没有指定边界E,可以视为E extends Object。

复杂示例

写一个泛型类,求一个数组中的最大值:

初始版代码:

class Alg<T> {
    public T findMaxValue(T[] array) {
        T max = array[0];
        for (int i = 0; i < array.length; i++) {
            if (max < array[i]) {
                max = array[i];
            }
        }
        return max;
    }
}
 
public class Test2 {
    public static void main(String[] args) {
        Alg<Integer> a = new Alg<>();
        Integer[] array = {1, 2, 3, 4, 213, 321313, 6};
        int max = a.findMaxValue(array);
    }
}

这样写一定是不可行的,因为我们知道T是引用类型,最终会被擦除为Object类型,所以我们不能单纯地使用比较符号来比较这种类型。

那么该怎么比较呢?

这个问题可以看作如何约束这个T是可以比较大小的:这时我们就需要用到泛型的上界。

因为T是Object的子类,Object中有Comparable,所以考虑让T实现Comparable来利用相应方法进行比较:

class Alg<T extends Comparable<T>> {
    public T findMaxValue(T[] array) {
        T max = array[0];
        for (int i = 0; i < array.length; i++) {
            if (max.compareTo(array[i]) < 0) {
                max = array[i];
            }
        }
        return max;
    }
}
 
public class Test2 {
    public static void main(String[] args) {
        Alg<Integer> a = new Alg<>();
        Integer[] array = {1, 2, 3, 4, 213, 321313, 6};
        int max = a.findMaxValue(array);
        System.out.println(max);
    }
}

这样就可以实现大小比较啦!

相关文章
|
Java
包装类的使用
包装类的使用
64 0
|
7月前
|
安全 编译器 Scala
何时需要指定泛型:Scala编程指南
本文是Scala编程指南,介绍了何时需要指定泛型类型参数。泛型提供代码重用和类型安全性,但在编译器无法推断类型、需要提高代码清晰度、调用泛型方法或创建泛型集合时,应明确指定类型参数。通过示例展示了泛型在避免类型错误和增强编译时检查方面的作用,强调了理解泛型使用时机对编写高效Scala代码的重要性。
49 1
何时需要指定泛型:Scala编程指南
|
7月前
|
存储 Java 编译器
包装类&认识泛型
包装类&认识泛型
|
缓存
包装类
包装类
73 0
|
存储 Java
包装类和基本数据类型
包装类和基本数据类型
|
7月前
|
存储 Java
什么是泛型, 泛型的具体使用?
什么是泛型, 泛型的具体使用?
|
存储 算法 编译器
泛型的讲解
泛型的讲解
60 0
|
存储 安全 Java
泛型的相关知识
泛型的相关知识
|
缓存 Java
基本数据类型包装类
基本数据类型包装类
67 0
|
安全 Java
包装类与泛型,到底区别在哪?
包装类与泛型,到底区别在哪?
106 0

热门文章

最新文章