泛型
实现一个类,类中包含一个数组成员,使得数组中
可以存放任何类型的数据
,也可以根据成员方法返回数组中某个下标的值
创建一个类,可以看出数组也可以为Object类型的
存放数据并且获取下标元素的值
虽然说,这样数组可以存放任意类型的数据,但不太方便,所以我们引入了“泛型
”
1. 泛型类的使用
1.1 语法
泛型类<类型实参> 变量名;
//定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参);//实例化一个泛型类对象
1.2 示例
MyArray < Integer > myArray = new MyArray< Integer >();
注意:泛型只能接受类,所有的基本数据类型必须使用包装类!
1.3 类型推导
当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写
MyArray < Integer > myArray = new MyArray< >();
//可以推导出实例化需要的类型为Interage
然后,修改一下刚开始的代码,引入泛型:
//<T> 标志位:代表当前类是泛型类
class MyArry2<T>{
//public Object[] array = new Object[10];->会存在一个Object的数组
public T[] array = (T[])new Object[10];
public T getPos(int pos){
return array[pos];
}
public void setVal(int pos,T val){
this.array[pos] = val;
}
public T[] getArray(){
return array;
}
}
<>
里面指定了类型,说明此时这个类里面,只能放这个数据类型的数据
如下代码,<>中指定了String 说明只能放String类型的数据
;<>中指定了Interage 说明只能放Interage类型的数据
。
public static void main2(String[] args){
//1.<>里面指定了类型,说明此时这个类里面,只能放这个数据类型的数据
MyArry2<String> myArry = new MyArry2<String>();
myArry.setVal(0,"abc");
myArry.setVal(1,"hello");
String s = (String) myArry.getPos(1);
System.out.println(s);
MyArry2<Integer> myArry2 = new MyArry2<Integer>();
myArry2.setVal(0,10);
myArry2.setVal(1,13);
myArry2.setVal(2,60);
}
注释:
- 类名后面的
< T >
代表占位符,表示当前类是一个泛型类
了解:【规范】类型形参一般使用一个大写字母表示,常用的名称有:
E
表示Elenment
K
表示Key
V
表示Value
N
表示Number
T
表示Type
S,U,V
等等-第二,第三,第四个类型- 当指定类型之后,编译器会根据你指定的类型参数来进行类型的检查
myArray.setVal(1,"hello");
- 当取元素的时候,不需要再进行强制类型转换了
- java中,不可以new泛型类型的数组,会报错
- 注意<>中必须是引用类型
2.泛型如何编译?
2.1 擦除机制
需要安装一个插件jclasslib Bytecode Viewer
安装完成后
擦除机制就是在编译的过程中,将泛型T替换为Object
,并且擦除机制就是编译十七的一种机制,运行期间没有泛型这个概念。
3.裸类型(Raw Type) 【了解】
裸类型是一个泛型类 但没有带着实参类型 ,下方代码就是一个裸类型
注意
:我们不要自己去使用裸类型,裸类型是为了兼容老版本的API保留的机制。
4.泛型的边界
只有泛型的上界,没有泛型的下界
4.1 示例
语法格式
class 泛型类名称< 类型形参 extends 类型边界 >{... ..}
了解:没有指定类型边界 E,可以视为 E extends Object
4.2 复杂示例
写一个泛型类,找出数组当中的最大值,只要这个T,实现类这个接口就行
这里的 T 是引用数据类型,直接比较不了,所以引用类型比较要用比较器
完整代码如下
class Alg<T extends Comparable<T>>{
public T findMaxVal(T[] array){
T maxVal = array[0];
for (int i = 1; i<array.length; i++){
if(array[i].compareTo(maxVal)>0){
maxVal = array[i];
}
}
return maxVal;
}
}
public class Test {
public static void main(String[] args) {
Alg<Integer> alg = new Alg<>();
Integer[] array = {78,23,109,2,9,10};
int val = alg.findMaxVal(array);
System.out.println(val);
}
}
运行结果:
5.泛型方法
语法格式
方法限定符< 类型形参列表 > 返回值类型 方法名称(形参列表){...}
上面的例子在数组中找最大值,有两种方法
静态方法
class Alg2{
//静态方法
public static<T extends Comparable<T>> T findMaxVal(T[] array){
T maxVal = array[0];
for (int i = 1; i<array.length; i++){
if(array[i].compareTo(maxVal)>0){
maxVal = array[i];
}
}
return maxVal;
}
}
public class Test3 {
public static void main2(String[] args) {
Integer[] array = {78,23,109,2,9,10};
//int val = Alg2.<Integer>findMaxVal(array);
//可以省略泛型类型
int val = Alg2.findMaxVal(array);
System.out.println(val);
}
}
使用静态方法后,不需要引用对象,可以不用写明类型,现在可以根据实参类型推导出类型是什么。
成员方法
class Alg3{
//泛型方法:成员方法
public <T extends Comparable<T>>T findMaxVal2(T[] array){
T maxVal = array[0];
for (int i = 1; i<array.length; i++){
if(array[i].compareTo(maxVal)>0){
maxVal = array[i];
}
}
return maxVal;
}
}
public class Test3 {
public static void main(String[] args) {
Alg3 alg3 = new Alg3();
Integer[] array = {78,23,109,2,9,10};
int val = alg3.findMaxVal2(array);
System.out.println(val);
}
}
因为这个不是静态方法,所以还是要引用对象。
6.通配符(?)
通配符 “?” 表示可以接收任意类型数据
6.1 上界通配符<? extends 实参类型>
上界:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
语法
类型1指定一个数据类型,那么类型2就只能是类型1或者类型1的子类
Vector<? extends 类型1> x = new Vector<类型2>();
在类型参数中使用extends表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:
- 如果传入的类型不是 E 或者 E 的子类,编译不成功
- 泛型中了一使用 E 的方法,要不然还得强转成 E 才能使用
看个例子:
class Food{ }
class Fruit extends Food{ }
class Apple extends Fruit{ }
class Banana extends Fruit{ }
class Test <T>{
private T val;
public T getVal(){
return val;
}
public void setVal(T val){
this.val = val;
}
}
public class Test1 {
public static void main1(String[] args) {
Test<Apple> test1 = new Test<>();
test1.setVal(new Apple());
fun(test1);
Test<Banana> test2 = new Test<>();
test2.setVal(new Banana());
fun(test2);
}
//通配符的上界,这里只要是Fruit或者Fruit的子类即可
public static void fun(Test<? extends Fruit> temp){
/*
这里无法确定 temp 引用的是那个子类对象,所以无法进行修改
temp.setVal(new Banana());
temp.setVal(new Apple());
*/
//放的都是Fruit或者Fruit的子类,所以可以直接使用 Fruit 接收
Fruit banana = temp.getVal();
System.out.println(temp.getVal());
}
}
6.2 下界通配符<? super 实参类型>
下界:用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至Object
语法
类型1指定一个数据类型,那么类型2就只能是类型1或者是类型1的父类
Vector<? super 类型1> x = new Vector<类型2>();
看个例子:
class Food{ }
class Fruit extends Food{ }
class Apple extends Fruit{ }
class Banana extends Fruit{ }
class Test <T>{
private T val;
public T getVal(){
return val;
}
public void setVal(T val){
this.val = val;
}
}
public class Test1 {
public static void main2(String[] args) {
Test<Fruit> test1 = new Test<>();
test1.setVal(new Fruit());
fun2(test1);
Test<Food> test2 = new Test<>();
test2.setVal(new Food());
fun2(test2);
}
//通配符的下界,一般用来添加元素,添加的是Fruit或者Fruit的子类
public static void fun2(Test<? super Fruit> temp){
temp.setVal(new Banana());
temp.setVal(new Apple());
temp.setVal(new Fruit());
System.out.println(temp.getVal());//只能直接输出
}
}
6.3 小结
- 在通配符中既有上界,又有下界
- 通配符的上界中,不能进行数据的写入,只能进行数据的读取
- 通配符的上界中,不能进行数据的读取,只能进行数据的写入
" ? " 和 " T " 的区别
- 我们可以对 T 进行操作,但是对 ?不行,比如下面两种:
- T 是一个确定的类型,通常用于泛型类和泛型方法的定义;
? 是一个不确定的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法
7.包装类
7.1 基本数据类型对应包装类
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Interage |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
7.2 装箱和拆箱
装箱
将基本数据类型转换为包装类类型
装箱调用Interage.valueOf
方法
public static void main3(String[] args) {
int a = 10;
Integer integer = a;//自动 装箱->底层调用的还是Interage.valueOf
Integer integer2 = new Integer(a);//显示 装箱
Integer ii = Integer.valueOf(a);//显示 装箱
System.out.println(integer);
System.out.println(integer2);
System.out.println(ii);
}
拆箱
将包装类类型转换为基本数据类型
拆箱调用Interage.intValue
方法
public static void main4(String[] args) {
int a = 10;
Integer integer = a;
int val = integer;//自动 拆箱->底层调用的还是intValue
System.out.println(integer);
int val2 = integer.intValue();//显示 拆箱
System.out.println(val2);
double val3 = integer.doubleValue();//显示 拆箱
System.out.println(val3);
}