泛型
1.1 泛型的起因
我们在设计某些类时,类中的属性或方法参数或方法返回值类型有时往往会不确定具体的类型,这样在定义类时可能使用一个类型比较大的父类类型如Object类型,接收实际对象数据时使用多态性,这样在实际使用时接收到的数据就失去了数据的实际类型,在使用时可能还需要强制类型转换。如集合对象在存储一些数据时不确定存储的数据类型,把数据当做Object类型来存储,失去了它的实际类型,在取出数据时往往需要强制类型转换,既不方便又容易类型转换出错。
1.2 泛型的好处
使用泛型编译时会自动类型检查,保证了数据的安全性,数据类型转换都是自动和隐式的,使用方便,效率高。
1.3 泛型介绍
1)泛型又称参数化类型,是jdk5.0 出现的新特性,它用于解决数据类型的安全性问题
2)在类声明(继承或实现中)或类实例化时只要指定好需要的具体的类型即可。
3)java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时代码更加简洁、健壮
4)在类声明时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。
1.4 泛型的分类及细节
1.4.1 泛型类
//泛型类定义为:
class AA <T, E> {
private T t;
private E e;
public Student(T t, E e) {
super();
this.t = t;
this.e = e;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
}
// 1.泛型类定义语法: class className <泛型字母> {}
// 2.泛型字母可以使用任意字母,如用T,E,K,V,可以使用多个,之间用逗号分隔,这些字母不代表值,而是表示类 型。常用T表示,T是Type的缩写
// 3.泛型使用时不能指定为基本类型而只能为引用类型
// 4.泛型不能使用在静态变量和方法上,因为静态成员是在加载类的时候就完成,比如静态属性初始化,静态方法调用和 静态代码块的调用,此时就应该要确定操作的类型,而我们的泛型类是在创建对象时才确定具体的类型。
// 5.泛型类在使用时指定泛型类型
// 6.泛型数组可以声明,但不能开辟长度或初始化, 因为数组在开辟空间时,大小需要明确,而泛型不能明确空间大小
// 7.如果在创建对象时,没有指定泛型类型,默认为Object类型
1.4.2 泛型接口
//泛型接口定义为:
interface MyInter<T, R> {
// 普通方法中,可以使用接口泛型
R get(T t);
void app(R r);
void consume(R r1, R r2, T t1, T t2);
// 在jdk8 中,可以在接口中使用默认方法
default R method(T t) {
return null;
}
}
// 1.泛型接口定义与泛型类定义相似,泛型接口中泛型只能使用在抽象普通方法和默认方法中
// 2.接口中,静态成员(常量和静态方法)也不能使用泛型
// 3.使用接口时没有指定泛型类型,默认为Object类型
1.4.3 泛型方法
//基本语法
class Cpu <T> {
private T speed;
public Cpu(T speed) {
this.speed = speed;
}
public T getSpeed() {
return speed;
}
//泛型方法定义为: 修饰符 <泛型字母> 返回值类型 方法名(参数列表)
public static <E> E boon(E e) {
return e;
}
}
// 1.泛型方法,可以定义在普通类中, 也可以定义在泛型类中
// 2.泛型方法类型在方法调用时指定
// 3.泛型方法中泛型类参数不能使用,因为类型不能确定,除非使用 extends 来明确泛型类型区间
1.4.4 继承和实现中的泛型
/**
* 继承中和实现中的泛型
* 1.子类泛型擦除时父类必须为指定类型或泛型擦除
* 2.子类为泛型类时父类必须为指定类型或泛型擦除或小于子类指定泛型
*
* 属性类型
* 1.在父类中的属性泛型类型随父类而定
* 2.在子类中的属性泛型类型随子类而定
*
* 方法
* 1.在父类中的方法泛型随父类而定
* 2.在子类中的方法泛型随子类而定
* 3.重写的方法泛型类型随父类泛型而定
*/
abstract class Father<T> {
T name;
public abstract void compare(T t);
}
//1.子类泛型擦除时,父类指定类型
class Child1 extends Father<String> {
int id;
public void compare(String t) {}
}
//2.子类泛型擦除时,父类泛型擦除,此时父类的泛型类型变为Object类型
class Child2 extends Father {
int id;
public void compare(Object t) {}
}
//3.子类为泛型类父类泛型擦除,此时父类的泛型变为Object类型
class Child3<E> extends Father {
E id;
public void compare(Object t) {}
}
//4.子类为泛型类,父类指定泛型
class Child4<E> extends Father<String> {
E id;
public void compare(String t) {}
}
//5.子类为泛型类且父类为泛型类,要求子类泛型>=父类泛型
class Child5<T,E> extends Father<T> {
E id;
public void compare(T t) {}
}
1.4.5 泛型擦除注意事项
/**
* 泛型的擦除
* 1.继承或实现中父类泛型擦除
* 2.使用时泛型没有指定泛型擦除
*/
class Pen<T> {
T size;
}
public class TestGenericDemo5 {
public static void main(String[] args) {
Pen p1 = new Pen(); //使用时泛型擦除,会将Pen内的泛型当Object对待,但不完全等于Object
Pen<Object> p2 = new Pen<Object>();
test01(p1);
test01(p2);
test02(p1); //当p1为泛型擦除时不进行类型检查,编译通过
//test02(p2); //指定为Object类型时 Pen<String> 与 Pen<Object>类型不一致,编译报错
}
public static void test01(Pen<?> p) {}
public static void test02(Pen<String> p) {}
}
1.4.6 泛型的继承和通配符说明
/**
* 1.泛型没有多态:如 B类继承A类, 那么 A<String> 与B<String>是两个不同的类型,不能把B<String>看作
A<String>的类型。
* 2.通配符 ? 表示类型不定,使用时指定类型,只能在声明类型或声明方法时使用,不能在声明类或具体使用时使用 ?
* ? extends A 表示?为A的子类或A本身类型
* ? super A 表示?为A的父类(直接或间接父类)或A本身类型
* 3.泛型可以嵌套使用,使用时从外到内层层分析
* 4.jdk1.7以后泛型可以只在声明时指定,使用时泛型可以不指定, 编译器根据声明时泛型类型判断运行时泛型类型。
如: List<String> list = new ArrayList<>();
*/
class Fruit {}
class Apple extends Fruit {}
class A<T> {}
public class TestGenericDemo {
public static void main(String[] args) {
A<Fruit> a1 = new A<Fruit>();
//A<Fruit> a2 = new A<Apple>(); 编译错误,泛型没有多态,A<Fruit>与A<Apple>属于不同类型
test(new A<Fruit>());
test(new A<Apple>());
}
public static <T extends Fruit> void test(A<T> t) {}
// T 为 Closeable的子类型
public static <T extends Closeable> void test(T... io) {
for(Closeable temp : io) {
if(null!=temp) {
try {
temp.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}