一、作用:记住java集合中元素的类型
在不使用泛型时,任何元素被放入集合中,会自动当成Object类型处理,当从程序中取出元素时,往往需要强制类型转换,这种转换不仅会代码臃肿,还容易引起CastClassException异常。
import java.util.List;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
List str=new ArrayList();
str.add("hello");
str.add("world");
//“不小心”添加了一个Integer类型的数据,编译时不出错
str.add(2);
//运行报错:因为Integer类型无法强制装化为String
str.forEach(str->System.out.println((String)str).length());
}
}
使用泛型改进上述程序:
import java.util.List;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
//创建String类型的List集合,该集合只能储存String类型的元素
List<String> str=new ArrayList<String>();
str.add("hello");
str.add("world");
//编译时就会出错
str.add(2);
str.forEach(str->System.out.println((String)str).length());
}
}
二、泛型定义方式
方式1;在集合接口、类后增加尖括号<>,里面写上数据类型即可,如List str=new ArrayList();
方式2:java9增加的“菱形”语法,省略构造器中的数据类型,只写一个尖括号<>即可,如:List str=new ArrayList<>();
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.HashMap;
public class Test {
public static void main(String[] args) {
//String类型的List
List<String> str=new ArrayList<>();
str.add("hello");
str.add("world");
str.forEach(ele->System.out.println(ele.length()));
//该Map集合的key是String类型的,value是类型为String的List集合
Map<String,List<String>> map=new HashMap<>();
map.put("世界,你好",str);
map.forEach((key,value)->System.out.println(key+"-->"+value));
}
}
/*输出:
5
5
世界,你好-->[hello, world]
*/
方式3:使用通配符,即<?>形式,此时相当于其上限为Object,但是这种情况下不能向集合中添加任何元素
import java.util.Set;
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
Set<?> set=new HashSet<>();
//报错
set.add("hi");
}
}
三、泛型使用场合:
1、集合是最重要的使用场合,可以在Set、List、Deque、Collection、Map等接口及其实现类中使用
2、也可以为任何类、接口增加泛型声明。
//自定义类中使用泛型
class A<T>{
//使用T类型定义实例变量
private T str;
public A() {}
//使用T类型定义构造器
public A(T str) {
this.setStr(str);
}
public void setStr(T str2) {
this.str=str;
}
public T getStr() {
return this.str;
}
}
public class Test{
public static void main(String[] args) {
//T类型为String,所以构造器里的参数只能是String
A<String> a1=new A<>("苹果");
System.out.println(a1.getStr());
//传入T的参数是Double,所以构造器里的参数只能是Double
A<Double> a2=new A<>(3.14);
System.out.println(a2.getStr());
}
}
!!!注意:当创建带泛型声明的自定义类,为该类创建构造器时,构造器名还是原来的类名,不需要增加泛型声明
3、从泛型类派生子类
当创建了带泛型的接口、类父后,就可以为这些接口创建实现类,或从父类派生子类,但是**!!!注意:此时这些接口、父类不可以在包含泛型形参**
//自定义类中使用泛型
class A<T>{
//使用T类型定义实例变量
private T str;
public A() {}
public A(T str) {
this.setStr(str);
}
public void setStr(T str2) {
this.str=str;
}
public T getStr() {
return this.str;
}
}
//定义B继承A类
class B extends A<T>{
//写法错误,此时的A类不可以带泛型声明
}
(1)继承类或实现接口时传入实际的类型
此时A中所有用到T的地方都变成了String,那么要注意在B类中进行方法重写时,必须保证也是String类
//定义B继承A类
class B extends A<String>{
//重写父类的getStr()方法
public String getStr() {
return "子类"+super.getStr();
}
//这种重写错误,因为方法的类型是Object,而不是String
public Object getStr() {
return "子类"+super.getStr();
}
}
(2)不传入实际的类型
此时可能会产生泛型警告,并且会将A类中的T当场Object类处理
//定义B继承A类
class B extends A{
//这种形式被称为“原始类型”,其中的T默认为Object
}
!!!注意1:泛型并没有生成一个新的类,无论T的类型是什么,它都是同一个类A,所以在静态方法、初始化块、静态变量的声明和初始化中不允许使用泛型形参
class A<T>{
//下面两种定义方式都是错误的
static T str;
public static void bar(T str) {
}
}
!!!注意2:如果B是A的子类,G是具有泛型声明的类或接口,那么G< B>并不是G< A>的子类,虽然B是A的子类
public class Test {
public static void main(String[] args) {
//虽然String是Object的子类,但是List<String>并不是List<Object>的子类,所以不能直接str赋值给obj
List<String> str=new ArrayList<>();
List<Object> obj=str; //报错:cannot convert from List<String> to List<Object>
//正确,数组可以向上自动转型
String[] str=new String[3];
Object[] obj=str;
}
}
四、泛型通配符
1、使用<?>表示,但是此时不能向集合中添加元素,默认是Object类
2、设定类型通配符的上限:<? extends A>,最高的类型就是类A,可以是A及其子类,此时只能从集合中取出元素(取出元素的上限就是A),不能添加元素(因为添加的元素类型可能是A或A的子类)。并且该方式可以避免上面说到的注意2.
//定义一个抽象类
abstract class Shape{
public abstract void draw(Color c);
}
//定义Shape的子类Circle
class Circle extends Shape{
public void draw(Color c) {
System.out.println("在"+c+"画布上画一个圆");
}
}
//定义Shape的子类Circle
class Rectangle extends Shape{
public void draw(Color c) {
System.out.println("在"+c+"画布上画一个矩形");
}
}
class Color{
public void drawAll(List<Shape> shapes) {
//同时在画布上绘制多个形状
for(Shape s:shapes) {
s.draw(this);
}
}
}
public class Test {
public static void main(String[] args) {
List<Circle> cl=new ArrayList();
Color c=new Color();
//代码编译错误,因此c1是List<Circle>类的,但Color类的drawAll方法需要传入的参数类型是List<Shape>。前面已经说过,虽然Circle是Shape的子类,但List<Circle>不是List<Shape>的子类,所以参数类型不符
c.drawAll(cl);
}
}
将上述Color类改为:
class Color{
//使用了通配符,此时表示drawAll方法的参数类型可以是Shape类型及其子类,此时可以是List<Shape>/List<Circle>/List<Retangle>
public void drawAll(List<? extends Shape> shapes) {
for(Shape s:shapes) {
s.draw(this);
}
}
}
3、设定通配符下限:<? super A>,最低的类型是A,可以是A及其父类。此时只能往集合中添加元素(因为都有A及其父类类型来接收),不能取出元素(因为取出的元素不知道到底是A还是A的父类对象)
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class upTest {
//定义一个copy方法,将src集合中的元素赋值到dest集合中,由于指定了src的类型是T,那么dest要想盛放src的数据,那么其类型必须大于等于T,因此dest的类型采用了通配符下限
public static<T> T copy(Collection<? super T> dest,Collection<T> src) {
T last=null;
for(T ele:src) {
last=ele;
dest.add(ele);
}
return last;
}
public static void main(String[] args) {
List<Number> ln=new ArrayList<>();
List<Integer> li=new ArrayList<>();
li.add(5); //li是Integer类型,因此传入过去的T为Integer
Integer last=copy(ln,li);
System.out.println(last);
}
}
4、协变与逆变
假设A是B的子类或实现接口,G是具有泛型声明的类或接口
协变:使用通配符上限,<? extends B> 表示类型可以是A或B
逆变:使用通配符下限,<? super A> 表示类型可以是A、B、或B的父类、Object、