目录
1. 包装类
在Java中由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型;
1.1 基本数据类型和对应的包装类
基本数据类型 | 包装类 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
除了Integer和Character,其余基本类型的包装类都是首字母大写;
1.2 (自动)装箱和(自动)拆箱
1.2.1 装箱与拆箱
装箱(装包):把基本数据类型变为对应的包装类型;
拆箱(拆包):把引用类型变为基本数据类型;
1.2.2 自动(显式)装箱与自动(显式)拆箱
代码示例1:自动装箱与显式装箱:
int a = 10;
// 自动装箱
Integer val = a;
System.out.println(val);
// 显式装箱
Integer val2 = Integer.valueOf(a);
Integer val3 = new Integer(a);
System.out.println(val2);
System.out.println(val3);
代码示例2:自动拆箱:
Integer val1 = 10;
// 自动拆箱
int a = val1;
System.out.println(a);
// 显式拆箱
// 拆为int类型
int a2 = val1.intValue();
System.out.println(a2);
// 拆为double类型
double a3 = val1.doubleValue();
System.out.println(a3);
注:(1)自动装箱的底层逻辑:Integer包装类的valueOf()方法:
(2)自动拆箱的底层逻辑:Integer包装类的intValue()方法:
(3)显式拆箱时,Integer包装类不止提供了intValue()方法,还提供了doubleValue()等方法:
可以根据所需的基本类型对应调用其方法,在上文示例中,输出结果为:
1.3 valueOf()方法
试运行以下代码:
Integer a = 127;
Integer b = 127;
System.out.println(a==b);
Integer c = 128;
Integer d = 128;
System.out.println(c==d);
输出结果为:
原理注解:
(1)Integer包装类的valueOf方法源代码与方法内部相关量的值:
(2)方法含义:
如果i在[low, high]即[-128, 127]之间,则返回cache缓存数组中下标为i+128位置的元素:
如i=0时存储在cache[128],i=-128时则存储在cache[0],i=127时则存储在cache[255];
即在缓存数组中存储了256个数字;
在该范围内的i调用valueOf方法调用到的是同一个元素,故而a=b;
在该范围外的i调用valueOf方法会new Integer对象,作为引用类型,==判断的是引用类型是否是对一个对象,故而a!=b;
2. 泛型类
一般的类和方法只能使用具体的类型:基本类型或自定义类,泛型就是适用于多种类型,即对参数实现了参数化;
2.1 泛型类与Object类
现要求实现一个不限制元素种类的数组,联想到Object类,示例代码如下:
class MyArray{
public Object[] obj = new Object[3];
public Object getPos(int pos){
return obj[pos];
}
public void setPos(int pos,Object val){
obj[pos]=val;
}
}
public class Demo1 {
public static void main(String[] args) {
MyArray myArray = new MyArray();
myArray.setPos(0,10);
myArray.setPos(1,"hello");
myArray.setPos(2,10.0);
double ret = myArray.getPos(2);
System.out.println(ret);
}
}
报错如下:
如要创建double类型变量对数组元素进行接收,则需要进行强转:
double ret = (double)myArray.getPos(2);
上文设计的数组具有以下特点:
① 任何数据都可以存储; ② 获取数据时必须进行强制类型转换;
这并非我们需要实现的泛型数组,通常需要实现的泛型数组需求是:存储一种类型的数据;
泛型编程是将类型作为参数传递的编程方式,目的是指定当前容器后可以存放某一种类型的数据;
2.2 泛型类语法
// 泛型的一般使用方法
class 泛型类名称<类型形参列表>{
}
class ClassName<T1,T2...Tn>{
}
// 泛型类的继承
class 泛型类名称<类型形参列表> extends 继承类{
}
class ClassName<T1,T2,...Tn> extends ParentClass<T1>{
}
注:① 类名后的<T>代表占位符,表示当前类是一个泛型类,
类型形参一般使用一个大写字母表示,常用名称有:
E表示Element,K表示Key,V表示Value,N表示Number,T表示Type;
② 泛型当中不能实例化一个泛型类型的数组,如:
T[] ts = new T[5];
是错误的;
③ 泛型类实例化的同时会指定当前泛型类的指数参数类型,如果二者冲突,就会报错,
这也是泛型编程的一个意义所在:泛型编程在程序编译时,存储数据时自动进行类型检查;
2.3 泛型类示例
class MyArray<T>{
public T[] obj = (T[])new Object[3];
public T getPos(int pos){
return obj[pos];
}
public void setObj(int pos, T val){
obj[pos]=val;
}
}
public class Demo1 {
public static void main(String[] args) {
// 实例化对象的同时指定当前泛型类的指定参数类型
// 指定参数的类型必须是引用类型,如int非法而Integer合法
MyArray<Integer> myArray1 = new MyArray<Integer>();
myArray1.setObj(0,10);
myArray1.setObj(1,78);
myArray1.setObj(2,66);
double ret1= myArray1.getPos(1);
System.out.println(ret1);
System.out.println("-------------------");
MyArray<String> myArray2 = new MyArray<String>();
myArray2.setObj(0,"hello");
myArray2.setObj(1,"world");
myArray2.setObj(2,"java");
String ret2 = myArray2.getPos(1);
System.out.println(ret2);
}
}
输出结果为:
2.4 裸类型(Raw Type)
裸类型是一个泛型类但没有带类型实参,例如:
class MyArray<T>{
public T[] obj = (T[])new Object[3];
public T getPos(int pos){
return obj[pos];
}
public void setObj(int pos, T val){
obj[pos]=val;
}
}
public class Demo1 {
public static void main(String[] args) {
MyArray list = new MyArray();
}
}
但请注意:裸类型是为了兼容老版本的API,在编程时请避免裸类型的使用;
2.5 泛型类的编译
2.5.1 擦除机制
class MyArray<T>{
public T[] obj = (T[])new Object[3];
public T getPos(int pos){
return obj[pos];
}
public void setObj(int pos, T val){
obj[pos]=val;
}
}
public class Demo1 {
public static void main(String[] args) {
MyArray<Integer> myArray = new MyArray<Integer>();
myArray.setObj(0,85);
int ret = myArray.getPos(0);
System.out.println(ret);
}
}
查看上文代码的字节码文件:
在编译完成后,运行过程中是没有泛型的概念的,此时所有的T都被擦除为Object,通过上述的编译器生成的字节码文件中是不包含泛型的类型信息的;
2.5.2 泛型类型数组的实例化
java语法规定:在实例化数组时必须提供具体的元素类型,故而不能直接实例化泛型类型数组;
方法1:(上文擦除机制部分示例代码的在泛型类中创建数组的方法):
class MyArray<E>{
public E[] obj = (E[]) new Object[3];
}
但该种写法存在问题:试运行以下代码:
class MyArray<E>{
public E[] obj = (E[]) new Object[3];
public E getPos(int pos){
return obj[pos];
}
public void setObj(int pos,E val){
obj[pos]=val;
}
// 返回数组的方法
public E[] getArray(){
return obj;
}
}
public class Demo1 {
public static void main(String[] args) {
MyArray<Integer> myArray = new MyArray<Integer>();
myArray.setObj(0,10);
myArray.setObj(1,85);
Integer[] integers = myArray.getArray();
}
}
报错及分析如下:
在继承与多态章节已经提到过,向下转型是不安全的;
java的数组实现非常特殊,
此处可以理解为:jvm认为使用一个固定类型如Integer或String等等类型的对象来接收Object类型对象是不安全的;
正确代码为:(使用反射)
class MyArray<E>{
public E[] obj;
public MyArray(Class<E>clazz, int capacity){
// 参数为数组元素类型与容量
obj = (E[])Array.newInstance(clazz, capacity);
}
public E getPos(int pos){
return obj[pos];
}
public void setObj(int pos, E val){
obj[pos]=val;
}
public E[] getArray(){
return obj;
}
}
public class Demo1 {
public static void main(String[] args) {
MyArray<Integer> myArray = new MyArray<>(Integer.class,10);
myArray.setObj(0,1997);
myArray.setObj(1,85);
Integer[] integers = myArray.getArray();
}
}
但以上方法并不常用,更推荐方法2:;
继承与多态文章链接如下:
方法2:参考java的ArrayList源码的get方法:
对于泛型类型数组的实例化,不能直接使用泛型类型作为数组元素的类型,故而可以使用Object类结合强制类型转换实现泛型类型数组的实例化:
class MyArray<E>{
public Object[] obj = new Object[3];
public E getPos(int pos){
return (E)obj[pos];
}
public void setPos(int pos, Object val){
obj[pos] = val;
}
}
2.6 泛型的上界
2.6.1 <E extends N> N为接口
表示实例化对象时指定的参数类型一定已经实现了N接口;
// 写一个泛型类求一个数组的最大值
class Alg<E extends Comparable<E>>{
public E findMax(E[] array){
E max=array[0];
for(int i=0;i<array.length;i++){
if(max.compareTo(array[i])<0){
max = array[i];
}
}
return max;
}
}
public class Demo2 {
public static void main(String[] args) {
Alg<Integer> alg = new Alg<Integer>();
Integer[] array = {1,4,2,10,9,8,17,5};
System.out.println("Array is:");
for(Integer x:array){
System.out.print(x+" ");
}
System.out.println();
Integer val = alg.findMax(array);
System.out.println("The max element in the array is "+val);
}
}
输出结果为:
2.6.2 <E extends Number> Number为类
表示E是Number的子类或E就是Number本身;
参考Number (Java Platform SE 8 ) (oracle.com)
Integer、Double、Long、Short等等都是Number常见的直接子类:
class A<E extends Number>{
A<Number> a1 = new A<>();
A<Integer> a2 = new A<>();
A<Double> a3 = new A<>();
// A<String> a4 = new A<>();
}
3. 泛型方法
3.1 语法
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表){...}
3.2 泛型方法示例
3.2.1 普通类的普通泛型方法
class Alg2{
public<E extends Comparable> E findMax(E[] array){
E max = array[0];
for(int i=0;i<array.length;i++){
if(max.compareTo(array[i])<1){
max=array[i];
}
}
return max;
}
}
public class Demo4 {
public static void main(String[] args) {
Alg2 alg2 = new Alg2();
Integer[] arr = {19,9,7,85,25};
System.out.println("Array is: ");
for(Integer x:arr){
System.out.print(x+" ");
}
System.out.println();
Integer val = alg2.<Integer>findMax(arr);
System.out.println("The max element in the array is "+val);
}
}
输出结果为:
注:调用泛型方法时泛型类型可以省略:
Integer val = alg2.<Integer>findMax(arr);
上行代码可以简写为:
Integer val = alg2.findMax(arr);
编译器会根据传递给泛型方法的参数arr的类型判断E的类型;
3.2.2 普通类的静态泛型方法
class Alg2{
public static<E extends Comparable> E findMax(E[] array){
E max = array[0];
for(int i=0;i<array.length;i++){
if(max.compareTo(array[i])<1){
max=array[i];
}
}
return max;
}
}
public class Demo4 {
public static void main(String[] args) {
Integer[] arr2 = {19,9,7,85,25};
System.out.println("Array is: ");
for(Integer x:arr2){
System.out.print(x+" ");
}
System.out.println();
Integer val = Alg2.findMax(arr2);
System.out.println("The max element in the array is "+val);
}
}
输出结果为:
静态泛型方法不再依赖于类的对象存在,可以使用类名直接调用;