Java基础(八)——泛型

泛型可以作为集合的补充,因为JDK1.5增加泛型很大程度上是为了让集合能记住其元素的数据类型。在没有泛型之前,一旦把一个对象“丢进”java集合中,集合就会忘记对象的类型,把所有对象都当做Object类型处理(编译时当做Object类型处理,运行时类型不变),而当程序从集合中取出对象后,就需要进行强制类型转换,这种强制类型转换使代码臃肿,而且容易引起ClassCastException异常。
str.forEach(str -> System.out.println((String)str.length());

使用泛型(Generic)

参数化类型(parameterized type)即泛型。如List = new Array<>();
方法:在集合接口、类后增加尖括号,尖括号里放一个数据类型,即表明这个集合接口、集合类只能保存特定类型的对象。
Java7之后允许在构造器后不需要带完整的泛型信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//定义Apple类时使用了泛型声明
public class Apple<T>{
//使用T型形参定义实例变量
private T info;
public Apple(){} // Java7之后允许在构造器后不需要带完整的泛型信息
public Apple(T info){
this.info = info;
}
public void setInfo(T info){
this.info = info;
}
public T getInfo(){
return this.info;
}
public static void main(String[] args){
Apple<String> a1 = new Apple<>("苹果");
System.out.println(a1.getInfo());
Apple<Double> a2 = new Apple<>(5.67);
System.out.println(a2.getInfo());
}
}

上面使用Apple类时可以为T类型形参传入实际类型,这样就可以生成Apple、Apple等形式的多个逻辑类(物理上并不存在)。

从泛型类派生子类

当创建了带泛型声明的接口,可以为该泛型创建实现类;创建了父类,可以为该父类派生子类。当使用这些接口、父类时,不能再包含类型参数。
若想从Apple类派生一个子类:

1
public class B extends Apple<String>

也可不为类型参数传入实际的类型参数:

1
public class B extend Apple

如果Apple类派生子类,则它的子类将会继承String getInfo()和void setInfo(String info)两个方法,子类重写父类方法时需要注意。

1
2
3
4
5
public class A1 extends Apple<String>{
public String getInfo(){
return “子类” + super.getInfo();
}
}

并不存在泛型类

1
2
3
List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
System.out.println(l1.getClass() == l2.getClass()); // true

不管泛型的实际类型参数是什么,它们运行时总有相同的类(class)。不管为泛型的类型形参传入哪一种类型实参,对于java来说,它们依然被当成同一个类处理。在内存中也只占用一块内存空间,因此在(static)静态方法、静态初始化块、静态变量声明和初始化时,不能使用类型形参。
由于系统中并不会生成真正的泛型类,所以instanceof运算符后不能使用泛型类。

类型通配符?

设定类型统配符?的上限

,把泛型对象作为方法参数传入方法的时候
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//定义一个抽象类Shape
public abstract class Shape{
public abstract void draw(Canvas c);
}
//定义Shape的子类Circle
public class Circle extends Shape{
public void draw(Canvas c){
System.out.println("在画布" + c + "上画一个圆");
}
}
//定义Shape的子类Rectangle
public abstract class Rectangle extends Shape{
public void draw(Canvas c){
System.out.println("在画布" + c + "上画一个矩形");
}
}
//定义Canvas类,是Shape子类的对象
public class Canvas{
//同时在画布上绘制多个形状
public void drawAll(List<? extends Shape> shapes){
for(Shape s : shapes){
s.draw(this);
}
}
}
上面程序的List shapes 可以表示List、List的父类。 通配符?必须是Shape的子类或Shape本身,因此Shape为这个通配符的上限(upper bound)。 ## 设定类型形参的上限(限制泛型类型) ## Java泛型可以设定通配符形参的上限,也可以设定类型形参的上限。
1
2
3
4
5
6
7
public class Apple<T extends Number>{
T col;
public static void main(String[] args){
Apple<Integer> ai = new Apple<>();
Apple<Double> ad = new Apple<>();
}
}
# 泛型方法(Generic method) # 定义类、接口时没有使用类型形参,但定义方法时想定义类型形参。 ## 泛型方法 ## 返回值和参数都用泛型表示的方法。格式如下
1
2
3
修饰符 <T, S> 返回值类型 方法名(形参列表){
//方法体
}
由上可知,泛型方法与普通方法的不同之处,就在于泛型方法名多了类型参数声明。 ## 泛型方法和通配符方法的区别 ## 何时使用泛型方法、通配符方法? 大多数时候都可以使用泛型方法来代替类型通配符,如对于java的collection接口中两个方法的定义:使用类型通配符定义:
1
2
3
4
5
public interface Colletion<E>{
boolean containsAll(Colletion<?> c);
boolean addAll(Colletion<? extends E> c);
//…
}
使用泛型方法定义:
1
2
3
4
5
public interface Colletion<E>{
<E> boolean containsAll(Colletion<T> c);
<E> boolean addAll(Colletion<T> c);
//…
}
类型形参T产生的唯一效果是,可以在不同的调用点传入不通过的实际类型。对于这种情况,应该使用类型通配符。因为通配符就是被设计用来支持灵活的子类化的。 泛型方法允许类型参数被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。 ## 泛型构造器 ## 正如泛型方法允许在方法签名中声明类型形参一样,java也允许在构造器签名中声明类型形参,即泛型构造器。 ## 设定通配符下限 ## ## 泛型方法与方法重载 ## 因为泛型方法中允许设定通配符上限和下限,从而允许在一个类里包含如下两种方法定义:
1
2
3
4
5
public class MyUtils{
public static <T> void copy(Collection<T> dest, Collection<? extends T> src); //T是上限
public static <T> T copy(Collection<? super T> dest, Colletion<T> src); // T是下限

}
## Java8改进的类型推断 ## 可通过调用方法的上下文来推断类型参数的目标类型。 可在方法调用链中,将推断得到的类型参数传递到最后一个方法。 擦除和转换 将一个具有泛型信息的对象赋给一个没有泛型信息的变量时,尖括号内的类型信息都将被扔掉。 Java与数组 创建数组时,数组元素的类型不能包含类型变量和类型形参,除非是无上限通配符。但是会有unchecked 未经检查的警告。 List[] sa = new ArrayList[10]; 或 List

[] lsa = new ArrayList<?>[10];
集合
List ls = new ArrayList<>();