java泛型(详细)
一,包装类
在Java中,包装类(Wrapper Classes)是一种特殊的类,它们将基本数据类型封装成对象。这样做的目的主要是为了让基本数据类型拥有对象的特性
1.1装箱和拆箱
装箱:是基本数据类型自动转换为包装类对象;拆箱:是包装类对象自动转换为基本数据类型。
从Java 5(Java 1.5)开始,引入了自动装箱(Autoboxing)和拆箱(Unboxing)机制。
可以分为4类
自动装箱,自动拆箱,手动装箱,手动拆箱
int i=100;
Integer a=Integer.valueOf(i);//手动装箱
Integer b=100;//自动装箱
int c=a.intValue();//手动拆箱
int d=b;//自动拆箱
拆箱和装箱还提供额外的功能:
// 将字符串转换为整数
int number = Integer.parseInt("123");
// 将整数转换为字符串
String strNumber = String.valueOf(123);
// 也可以利用包装类的valueOf方法
String anotherStrNumber = Integer.valueOf(123).toString();
1.2包装类引起的问题
Integer a=100;
Integer b=100;
System.out.println(a == b);//输出结果:true
Integer c=200;
Integer d=200;
System.out.println(c == d);//输出结果:false
解释:由于100在-128到127的缓存范围内,Java虚拟机(JVM)会直接从缓存中返回相同的Integer对象给a和b(常量池)。因此,a和b实际上引用的是内存中的同一个对象;200它不在-128到127的缓存范围内。因此,每次创建Integer对象时(通过自动装箱),JVM都会创建一个新的Integer实例。所以,c和d虽然值相同,但它们分别引用了不同的对象。
二、泛型
在java继承那学习到,Object为所有类的父类,那么数组是否可以创建成Object类?如果能创建成功,是不是这个数组里面能放任何类型的数据?
答案:数组可以创建成Object类,并且里面可以存放任何类型的数
class Test{
public Object[] array=new Object[10];
public Object getPos(int pos){
return array[pos];
}
public void setValues(int pos,Object value){
array[pos]=value;
}
}
public class Main {
public static void main(String[] args) {
Test test=new Test();
test.setValues(1,10);
test.setValues(2, "hello");
String str= (String) test.getPos(2);
int a= (int)test.getPos(1);
System.out.println(a);
System.out.println(str);
}
因为数组是Object类型的,当我们要用到这些数据的时候,还需要我们进行强转。
弊端:当我们创建Object数组,要用到数组中的数据时,我们还得事先知道数组这个下表的数据类型是什么类型,才可以使用;那我们就要思考一个问题:Object 类这么厉害,那我们是否就可以在这个数组中存放任何类型的元素;答案是不能,这样我们不能知道每一个下标所存储的元素类型,可能会有风险
所以,我们就引出了泛型
概念:泛型本质即“参数化类型”,也就是说所操作的数据类型被指定为一个参数,在使用时再从外部传入一个数据类型;而一旦传入了具体的数据类型后,传入变量(实参)的数据类型如果不匹配,编译器就会直接报错。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
主要目的:泛型就是指定当前的容器,要持有什么类型的对象,让编译器去做检查。
2.1泛型类
泛型模版: class 类名<T>{}
在泛型类中,类型参数定义的位置有三处,分别为:
1.非静态的成员属性类型
2.非静态方法的形参类型(包括非静态成员方法和构造器)
3.非静态的成员方法的返回值类型
在创建泛型类的对象时,必须指定类型参数 T 的具体数据类型,即尖括号 <> 中传入的什么数据类型,T 便会被替换成对应的类型。如果 <> 中什么都不传入,则默认是 < Object >。
class Test<T> {
public Object[] array = new Object[10];
public T getPos(int pos) {
return (T) array[pos];
}
public void setValues(int pos, T value) {
array[pos] = value;
}
}
public class Main {
public static void main(String[] args) {
Test<Integer> test = new Test<Integer>();
test.setValues(1, 10);
test.setValues(2, "hello");
String str = (String) test.getPos(2);
int a = (int) test.getPos(1);
System.out.println(a);
System.out.println(str);
}
}
这里就发现当我们传入Integer数据类型后,再插入String类型的数据,编译器就会报错!
泛型类不只接受一个类型参数,它还可以接受多个类型参数!
class Test<T,E> {
public Object[] array = new Object[10];
public T getPos(int pos) {
return (T) array[pos];
}
public void setVal(int pos, E value1) {
array[pos] = value1;
}
public void setValues(int pos, T value) {
array[pos] = value;
}
}
2.2泛型接口
它允许你在定义接口时指定一个或多个类型参数(也称为类型变量)。这样,当实现接口时,你就可以指定这些类型参数的具体类型了。泛型接口提高了代码的复用性、类型安全性和可读性。
// 定义一个泛型接口Pair,它有两个类型参数T和U
public interface Pair<T, U> {
// 定义一个方法,该方法返回第一个元素
T getFirst();
// 定义一个方法,该方法返回第二个元素
U getSecond();
}
// 实现Pair接口的一个类,具体指定了类型参数为String和Integer
public class StringIntPair implements Pair<String, Integer> {
private String first;
private Integer second;
public StringIntPair(String first, Integer second) {
this.first = first;
this.second = second;
}
@Override
public String getFirst() {
return first;
}
@Override
public Integer getSecond() {
return second;
}
}
// 测试类
public class Test1 {
public static void main(String[] args) {
StringIntPair StringIntPair = new StringIntPair("Hello", 123);
// Pair<String, Integer> StringIntPair = new StringIntPair("Hello", 123);
System.out.println(StringIntPair.getFirst() + ", " + StringIntPair.getSecond());
}
}
2.3泛型方法
(1)只有在方法签名中声明了< T >的方法才是泛型方法,仅使用了泛型类定义的类型参数的方法并不是泛型方法。
(2)泛型方法中可以同时声明多个类型参数。
(3)泛型方法中也可以使用泛型类中定义的泛型参数。
(4)特别注意的是:泛型类中定义的类型参数和泛型方法中定义的类型参数是相互独立的,它们一点关系都没有。泛型方法始终以自己声明的类型参数为准。
第一个注意点
class Test2<U> {
// 该方法只是使用了泛型类定义的类型参数,不是泛型方法
public void testMethod(U u){
System.out.println(u);
}
// <T> 真正声明了下面的方法是一个泛型方法
public <T> T testMethod1(T t){
return t;
}
}
第二个注意点
public class TestMethod<U> {
public <T, S> T testMethod(T t, S s) {
return null;
}
}
第三个注意点
public class TestMethod<U> {
public <T> U testMethod(T t, U u) {
return u;
}
}
第四个注意点
public class Test<T> {
public void testMethod(T t) {
System.out.println(t);
}
public <T> T testMethod1(T t) {
return t;
}
}
前面在泛型类的定义中提到,在静态成员中不能使用泛型类定义的类型参数,但我们可以将静态成员方法定义为一个泛型方法。
public class Test2<T> {
// 泛型类定义的类型参数 T 不能在静态方法中使用
// 但可以将静态方法声明为泛型方法,方法中便可以使用其声明的类型参数了
public static <E> E show(E one) {
return null;
}
}
2.4 擦除机制
Java 5之前是没有泛型概念的,为了保持与旧版本的兼容性,Java引入了泛型擦除机制。
编译时期,通过<>中的T进行类型检查和类型转换;编译完成以后T被擦除为Object类型。
class Test<T> {
public Object[] array = new Object[10];
public T getPos(int pos) {
return (T) array[pos];
}
public void setValues(int pos, T value) {
array[pos] = value;
}
}
public class Main {
public static void main(String[] args) {
Test<Integer> test1=new Test<>();
Test<String> test2=new Test<>();
System.out.println(test1);
System.out.println(test2);
}
2.5 泛型通配符
在编码中, 处理未知类型或需要限制类型范围时。因此引出了泛型通配符这个概念。
泛型通配符有 3 种形式:
- <?> :被称作无限定的通配符。
- <? extends T> :被称作有上界的通配符。
- <? super T> :被称作有下界的通配符。