目录
包装类
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);
}
}
这样就可以实现大小比较啦!