提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
一、泛型概述
1.什么是泛型
泛型就是在接口、类的声明处或者在接口、类中某个属性、方法、返回值,通过一个标识来进行定义,这个标识就是类型参数,也叫参数化类型。因此泛型可以修饰接口、类、属性、方法和返回值。
2.泛型的历史
- 早在JDK1.5之后,在集合框架上就规范使用泛型结构来约束和限制集合的使用。通过泛型结构在集合上(就是在类、接口上),能够在实例化创建集合对象时,调用者通过泛型的类型参数,传入指定的数据类型(必须是类(对象)类型,基本数据类型需要使用包装类来替代)
- JDK1.5之后,改写了集合框架中的所有接口和类,为这些接口、类增加了泛型支持,因此在声明集合变量和创建集合对象时传入类型参数进行确定该集合对象下应该指定存放哪一种数据类型
3.引入泛型的意义
- 引入泛型,特别是集合在结合泛型的使用,增加了存储数据的安全性,有效避免了读取集合中元素的存在的ClassCastException运行时异常的出现
- 而且在集合泛型中指定集合元素的数据类型后,其在编译期间就已经约束了所存储的集合元素的数据类型,限制了其他不符合当前指定集合指定类型参数的数据元素,使代码更加健壮
二、自定义泛型结构
- 泛型修饰在接口和类的位置
泛型类可以有多个参数例如<E1,E2,E3…>
泛型类的构造方法不能像类一样声明使用泛型类,但是参数可以使用泛型参数
实例化对象后,原本指定的泛型位置的结构必须是与指定的泛型类型一致
泛型不同的引用不能相互赋值,例如List< Integer >和List< String >对象引用是不能相互赋值,如果是实例化List< Integer >
list1=new List<>()和List< String > list2=new List<>(),如果把list1赋给集合变量list2尽管编译不通过,但是运行时只会把一个List对象加载到JVM虚拟机中-------也就是如果创建了两种泛型的对象,在运行时只有一个对象被加载到JVM虚拟机中泛型如果不指定将会被擦除,默认对应的类型是Object进行处理,但不能等价于Object(不能是其他类型引用赋值给基类的Object一样,譬如Object utilDate = new java.util.Date(1000);Date sqlDate = (java.sql.Date) utilDate;一样,父类是不能强转成子类,尽管可以骗过编译器,运行时会报ClassCastException),只要是使用了泛型,那么就绑定了泛型的类型是一路使用的、统一的
泛型的接口或者抽象类是不能实例化泛型类对象的
在JDK1.7之后,泛型结构实例化可以简写成:ArrayList list=new ArrayList<>()------类型可以进行推断
泛型中不能使用基本数据类型,需要使用包装类进行替换
在类/接口上声明的泛型结构,可以是非静态属性类型、非静态方法参数类型、非静态方法返回值类型。但是不能在静态方法中使用类的泛型(方法参数–T t)、静态变量(类变量)中使用泛型
异常类不能使用泛型
不能new E[]泛型
父类有泛型,其子类可以选择保留泛型也可以选择指定泛型类型,也可以擦除不指定具体的泛型
①子类不保留父类的泛型,可擦除
class Father<T1,T2>{
}
//不保留父类泛型:可擦除,等价于Father<Object,Object>
class Son1 extends Father{
}
②子类保留父类泛型,并指定具体的类型
class Father<T1,T2>{
}
//保留父类类型:指定父类具体的泛型类型
class Son3 extends Father<Integer,String>{
}
③全部保留父类泛型
class Father<T1,T2>{
}
//全部保留父类泛型
class Son2<T1, T2> extends Father<T1,T2>{
}
④部分保留父类泛型
class Father<T1,T2>{
}
//部分保留父类泛型
class Son4<T1> extends Father<T1,Integer>{
}
⑤子类自定义泛型,不保留父类泛型
class Father<T1,T2>{
}
class Son5<A,B> extends Father{
}
⑥保留父类泛型,并指明父类的泛型类型
class Father<T1,T2>{
}
//保留父类泛型,并指明父类的泛型类型
class Son6<A,B> extends Father<Integer,String>{
}
⑦保留父类泛型
class Father<T1,T2>{
}
//保留父类泛型
class Son7<A,B,T1,T2> extends Father<T1,T2>{
}
⑧保留部分父类指定泛型
class Father<T1,T2>{
}
//保留部分父类指定泛型
class Son8<A,B,T2> extends Father<Integer,T2>{
}
- 泛型修饰在非静态类属性上:在类中声明一个泛型的属性,那么这个类就必须是泛型类或接口
class Person<T> {
private int id;
private String name;
private T t;
public Person() {
}
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", t=" + t +
'}';
}
}
- 泛型修饰在非静态方法参数上、非静态方法返回值类型
@Test
public void test5(){
Person<User> person = new Person<>();
person.setT(new User(1001,"xxx",26));
Person<User> person1 = new Person<>();
person1.setT(new User(1002,"yyy",16));
//System.out.println("person = " + person);
List list = new ArrayList<>();
list.add(person);
list.add(person1);
Object o = print1(list);
System.out.println("o = " + o);
}
public static <T> List<T> print1(List<T> t){//只要参数中有泛型,其方法部需要添加<T>以此作为标记,但并不是返回值类型
t.forEach(System.out::println);
return t;
}
- 非静态方法也可以被泛型化:不管该所在类是不是泛型类或接口,与之并没有关系
泛型方法的标识形式:访问修饰符 <泛型> 返回值类型【泛型】 方法名(泛型标识 参数名称 )
public class MethodTest {
public <T> T getT(T e){
return null;
}
}
- 不可以修饰在方法上(泛型的方法可以声明为静态的----但是因为泛型参数是在方法调用时就已经确定了,并非是实例化时才确定的, 如果是静态修饰,那么没有调用前就已经确定了,跟方法调用时才确认时机对不上
三、泛型在继承上的体现
如果A是B的子类或者子接口,那么G是具有泛型修饰的类或者接口,那么G是G子类或子接口不成立,这是需要注意的
@Test
public void test1(){
Collection<String> list1 = new ArrayList<>();
list1.add("123");
list1.add("456");
Collection<Object> list2 = new ArrayList<>();
list2.add("123");
list2.add(456);
// list2=list1;//编译错误,Collection<String>并不是Collection<Object>上的子类,即使String是Object的子类
printCollection1(list1);
System.out.println("=====================");
printCollection2(list2);
}
public static void printCollection1(Collection c){
Iterator iterator = c.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
四、泛型标识作为通配符的使用:泛型尖括号中用?标识:<?>
- 例如List<?>是List、List等子类的父类
- 泛型类带有通配符是可以读取其子类集合中的所有元素,而且是安全的,因为不管list<?>的真实类型是什么,它都包括Object
- 反观泛型类带有通配符是不可以添加集合元素的,因为我们并不知道List<?>的真实元素的数据类型是什么;但是它可以添加null这个集合元素,因为null是所有集合的类型成员,不管你是任何其他数据类型的集合,它都是其集合元素中的一员
@Test
public void test2(){
List<?> list=null;
list=new ArrayList<String>();
list=new ArrayList<Object>();
// list.add(2);//编译不通过,不能忘集合里面添加元素;但是可以获取集合中的元素赋给他们的父类List<?>
ArrayList<String> list1 = new ArrayList<>();
list1.add("123456");
list1.add("xxx");
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(1234567);
list2.add(123456);
getRead(list1);
System.out.println("=========================");
getRead(list2);
}
public static void getRead(List<?> list){
for (Object o:list
) {
System.out.println("o = " + o);
}
}
@Test
public void test1(){
ArrayList<Object> list1 = new ArrayList();
list1.add(123);
list1.add('A');
ArrayList<String> list2 = new ArrayList();
list2.add("xxx");
list2.add("yyy");
List<?> list=null;
list=list1;
//list的添加操作:不能在添加其他类型数据,可以添加null
// list.add("AA");
// list.add('A');
list.add(null);
//list的获取操作:允许获取数据,读取的数据为Object类型的
Object o = list.get(0);
System.out.println("o = " + o);
print(list);
// System.out.println(print(list));
System.out.println("================================");
list=list2;
print(list);
}
public static void print(List<?> list){
Iterator<?> iterator = list.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println("next = " + next);
}
}
- 通配符不能用在其他位置上
- 不能用在泛型方法的泛型标识上
public static <?> void getRead(List<?> list){
for (Object o:list
) {
System.out.println("o = " + o);
}
}
- 不能用在泛型类的声明上:public class test<?>{}
- 不能用在创建集合对象上:List<?> list=new ArrayList<?>
- 有限制的通配符使用
- ?:代表着所有泛型引用的调用
- 通配符指定上线:**<? extends 类>**使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
- 通配符指定下线:**<? super 类>**使用时指定的类必须是不小于该操作类,即是>=
@Test
public void test3(){
List<? super User> list=null;
List<User> list1=new ArrayList<>();
List<Teacher> list2=new ArrayList<>();
List<Object> list3=new ArrayList<>();
list=list1;
// list=list2;编译不通过:因为Teacher类是User的子类,比通配符实操的子类还要小,所以不符合规则
list=list3;
list.add(new User());
list.add(new Teacher());
// list.add(new Object());//编译不通过:做添加时,比通配符实操的类还要小才能添加成功:实操的子类才能添加
}
@Test
public void test1(){
ArrayList<Object> list1 = new ArrayList();
list1.add(123);
list1.add('A');
ArrayList<String> list2 = new ArrayList();
list2.add("xxx");
list2.add("yyy");
List<?> list=null;
list=list1;
//list的添加操作:不能在添加其他类型数据,可以添加null
// list.add("AA");
// list.add('A');
list.add(null);
//list的获取操作:允许获取数据,读取的数据为Object类型的
Object o = list.get(0);
System.out.println("o = " + o);
print(list);
// System.out.println(print(list));
System.out.println("================================");
list=list2;
print(list);
}
public static void print(List<? extends Object> list){
Iterator<? extends Object> iterator = list.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println("next = " + next);
}
}
五、泛型在开发场景中的应用
- 一个关于操作数据库后台数据的增删改查类
public class DAO<T> {
private Map<String,T> map=new HashMap<>();
//保存T类型的对象到Map成员变量中
public void insert(String id,T entity){
map.put(id, entity);
}
public void show(T t){
}
//从map中获取id对应的对象
public T get(String id){
T t = map.get(id);
return t;
}
//从map中修改对应id的值
public void update(String id,T entity){
boolean b = map.containsKey(id);
if (b){
map.put(id,entity);
}
}
//返回map中存放的所有T的对象
public List<T> list(){
Collection<T> values = map.values();
List<T> list = new ArrayList<>();
for (T t:values) {
list.add(t);
}
return list;
}
//删除指定的id对象
public void delete(String id){
map.remove(id);
}
}
- 相关实体类
public class User extends Creature {
private int id;
private String name;
private int age;
public User() {
}
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (id != user.id) return false;
if (age != user.age) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + age;
return result;
}
}
- 相关测试类
public class UserDaoTest {
@Test
public void test1(){
DAO<User> dao = new DAO<>();
dao.insert("101",new User(1001,"XXX",25));
dao.insert("102",new User(1002,"YYY",16));
dao.insert("103",new User(1003,"ZZZ",19));
List<User> list = dao.list();
System.out.println("list = " + list);
System.out.println("dao.get(\"101\") = " + dao.get("101"));
dao.delete("103");
List<User> list1 = dao.list();
System.out.println("list1 = " + list1);
dao.update("102",new User(1002,"MMM",11));
List<User> list2 = dao.list();
System.out.println("list2 = " + list2);
}
}