包装类简单认识泛型
包装类
在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型。
在 Java 中,包装类用于将基本数据类型转换为对象,以便在泛型、集合框架等场景中使用。
补充概念说明:
1.对象:对象 = 状态(属性)+ 行为(方法)
在 Java 中,对象(Object) 是**类(Class)**的实例。对象封装了数据(属性)和行为(方法),可以用来表示现实世界中的实体。
例如,如果对象是一只小狗,那他就是由他的属性和行为共同决定的。他的行为就有可能有”汪汪叫“,”吃饭“,”咬人“等等。他的属性就有可能有”颜色“,”性别“,”种类“,"名字"等等
基本数据类型和对应的包装类
装箱和拆箱
注:这个拆箱的过程中,主要是要看你想把这个给数据拆成 之后变成什么类型的数据,根据需求来使用你所需要的方法。
自动装箱和自动拆箱
上面的两个大内容,我们糅合到同一个代码中去讲解,代码中的注释也解释得比较详细
public class boxing_and_unboxing {public static void main1(String[] args) {// 装箱:把一个基本数据类型转换成包装类型的过程/*** 自动装箱:* 显示转型:* valueOf()方法*/int a = 10; //Integer b = a; // 自动装箱System.out.println("a = "+a);System.out.println("b = "+b);Integer c = Integer.valueOf(a); // 显示装箱System.out.println("c = "+c);}public static void main2(String[] args) {/*** 拆箱*/Integer a = 10;int b = a; // 自动拆箱int c = a.intValue(); // 显示拆箱,拆箱为自己指定的元素和类型double d = a.doubleValue();System.out.println(b);System.out.println(c);System.out.println(d);}public static void main(String[] args) {/*** 1.Integer.valueOf(x) 会缓存 -128 ~ 127 之间的整数,所以 a1 == b1 为 true。* 2.超出 127 的范围时,Integer.valueOf(x) 会创建新对象,所以 a2 == b2 为 false。* 3.比较数值时,应该使用 equals() 而不是 ==,避免因对象引用不同导致的错误。** 比较包装类对象时,不要使用 ==,应该使用 equals()!【这里会涉及到一个”==“和”equals()“得区别】*/Integer a1 = 100;Integer b1 = 100;System.out.println(a1 == b1);Integer a2 = 200;Integer b2 = 200;System.out.println(a2 == b2);}
}
注:在代码得最后,有讲到一个Java Integer 缓存机制(Integer Cache),是面试得常考点。
在 Java 中,Integer
包装类使用了 缓存池 来优化性能。
Integer.valueOf(x)
方法会在-128
到127
之间的数值返回缓存的对象,而不是创建新的对象。- 超出这个范围的值,将会创建新的
Integer
对象,因此 不同对象的引用地址不同。
泛型
泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化
泛型引出
问题:实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值?
思路:1. 我们以前学过的数组,只能存放指定类型的元素,例如:int[] array = new int[10]; String[] strs = new String[10];
2. 所有类的父类,默认为Object类。数组是否可以创建为Object?
代码1:
class MyArray{public Object[] array = new Object[10]; // 创建一个Object类型的数组public void set(int pos, Object val){array[pos] = val;}public Object get(int pos){return array[pos]; // 这里返回的类型是Object类的数据}
}
public class generics {public static void main(String[] args) {MyArray myArray = new MyArray(); // 这里实例化一个对象出来myArray.set(0, 5201314);myArray.set(1, "我爱你");//String love = myArray.get(1); // 这里就会报错【因为传回来的数据是一个Object类型的数据,并不是我们所预想的String类型的数据】String love = (String)myArray.get(1); // 所以这里就必须强制转型}
}
上述代码是,我们创建了一个Object类的数组,这个数组里面既可以存放整型的数据,也可以存放字符串类型的数据。但是当我们想获得数组中的数据的时候,发现获得的数据是一个Object类型的数据,而不是所欲想的数据。如果想获得所预想的数据,就必须做数据的强转。
代码2:
//class MyArray{
// public Object[] array = new Object[10]; // 创建一个Object类型的数组
//
// public void set(int pos, Object val){
// array[pos] = val;
// }
//
// public Object get(int pos){
// return array[pos]; // 这里返回的类型是Object类的数据
// }
//}
//public class generics {
// public static void main(String[] args) {
// MyArray myArray = new MyArray(); // 这里实例化一个对象出来
// myArray.set(0, 5201314);
// myArray.set(1, "我爱你");
// //String love = myArray.get(1); // 这里就会报错【因为传回来的数据是一个Object类型的数据,并不是我们所预想的String类型的数据】
// String love = (String)myArray.get(1); // 所以这里就必须强制转型
// }
//}/*** 上述代码是一个案例,为了引出下面这个代码* <T>:代表当前类是一个泛型类*/
class MyArray<T>{public Object[] array = new Object[10]; // 创建一个Object类型的数组public void set(int pos, T val){array[pos] = val;}public T get(int pos){return (T)array[pos]; // 这里返回的类型是Object类的数据}
}
public class generics{public static void main(String[] args) {/*** MyArray还是可以存放任何类型的数据,只是我们指定什么就可以存放什么类型的数据【实例化】*/MyArray<String> myArray1 = new MyArray<>(); // 这里实例化一个字符串对象出来MyArray<Integer> myArray2 = new MyArray<>(); // 这里实例化一个整型对象出来myArray1.set(0, "我爱你");myArray2.set(0, 5201314);String love1 = myArray1.get(0); // 所以这里就必须强制转型Integer love2 = myArray2.get(0);System.out.println(love1);System.out.println(love2);}
}
如上述代码所示,我们定义的数组是一个泛型类型的数组,我们传入的数据是泛型,获取的数据也是泛型,那具体是是什么类型,还是要取决于我们在实例化这个类型的时候规定了对象是什么类型的数据,可以实例化不同的数据对象,不同的实例化对象就对应着不同的数据类型,存放或者获取的数据都只能是指定的数据类型.
说明:
1.类名后的 <T> 代表占位符,表示当前类是一个泛型类
了解: 【规范】类型形参一般使用一个大写字母表示,常用的名称有:
·E 表示 Element
·K 表示 Key
·V 表示 Value
·N 表示 Number
·T 表示 Type
·S, U, V 等等 - 第二、第三、第四个类型
2.泛型只能接受类,所有的基本数据类型必须使用包装类!
泛型是如何编译的
擦除机制
在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制。
java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。所以泛型的擦除机制只存在于编译期间,在运行期间是不存在擦除这样的机制的。
提出问题:
1、那为什么,T[] ts = new T[5]; 是不对的,编译的时候,替换为Object,不是相当于:Object[] ts = new Object[5]吗?
答:泛型在 Java 中是 编译时的概念,它的类型参数(如 T
)在 运行时会被类型擦除。换句话说,T
只是个 占位符,在编译时用于类型检查,而在 运行时并不存在 具体的 T
。
如果 T[] ts = new T[5];
允许通过编译,那么在运行时,JVM 需要为 T
分配一个具体的类型,而 T
可能是 任何对象类型(甚至是不同的子类),但数组在 Java 是 运行时需要知道具体元素类型 的(用于类型安全检查),因此 Java 不允许这样写。
如果 Java 允许 new T[5]
,它在运行时就会变成 Object[]
,但我们在代码中可能会强制认为它是 String[]
,最终在运行时会出现 ArrayStoreException。因此 Java 不允许创建泛型数组。
2、类型擦除,一定是把T变成Object吗?
类型擦除不一定变成 Object
,如果 T
受某个类型约束(如 T extends Number
),它会被擦除为 上界(如 Number
),否则才是 Object
。
泛型的上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
语法:
class 泛型类名称<类型形参 extends 类型边界> {
...
}
示例:
public class MyArray<E extends Number> {
...
}
即只接受 Number /Number 的子类型作为 E 的类型实参
代码示例:
/*** 这一节,我们来感受一下泛型是如何编译的*/
class Alg<T extends Comparable<T>>{// 传入一个T类型得数组,这个T类型得数组是可以比较的public T findMax(T[] array){T max = array[0]; // 定义一个T类型的变量for (int i = 0; i < array.length; i++){if (array[i].compareTo(max)>0){max = array[i];}}return max;}
}
public class generics2 {public static void main(String[] args) {Alg<Integer> alg = new Alg<>(); // ✅ 指定 T 为 IntegerInteger[] number = {5,6,1,89,20,40,97,200,3,8,90};Integer max = alg.findMax(number); // ✅ 不会有类型转换问题System.out.println("最大值: " + max);}
}
如上述代码,T类型继承了Comparable的用法。
泛型方法
定义语法:
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
代码示例:
/*** 这个是泛型类,那有没有泛型方法【就是不指定参数】呢?*/
class Alg{// 这就是一个泛型方法public<T extends Comparable<T>> T findMax(T[] array){T max = array[0]; // 定义一个T类型的变量for (int i = 0; i < array.length; i++){if (array[i].compareTo(max)>0){max = array[i];}}return max;}
}public class generics2{public static void main(String[] args) {Alg alg = new Alg();Integer[] number = {5,6,1,89,20,40,97,200,3,8,90};Integer max = alg.findMax(number); // ✅ 不会有类型转换问题System.out.println("最大值: " + max);}
}
区别于上一个代码的地方就在于,泛型出现在了方法,而不是出现在类。因此在main()方法中,实例化对象的时候是不需要说明类型的。