Java泛型

一、泛型简介

1. 泛型的概念

  • 所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、 创建对象时)确定(即传入实际的类型参数,也称为类型实参)
  • 从JDK 5.0以后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List,这表明该List只能保存字符串类型的对象。(参数化类型:把类型当做参数来传递)
  • JDK 5.0改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
  • 泛型其实就是一种不确定的数据类型。比如:ArrayList<E> E就是泛型。 这种不确定的数据类型需要在使用这个类的时候才能够确定出来。泛型可以省略,如果省略,默认泛型是Object类型。

2. 泛型的引入背景

集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型。

3. 泛型的优点

  1. 解决元素存储的安全性问题。编译时就会进行类型检查,保证数据的安全。好比商品、药品标签,不会弄错。
  2. 解决获取数据元素时,需要类型强制转换的问题。好比不用每回拿商品、药品都要辨别。

Java泛型可以保证如果程序在编译时没有发岀警告,运行时就不会产生ClassCastException异常(两个类型间转换不兼容时引发的运行时异常)。同时,代码更加简洁、健壮。

4. 使用泛型总结:

① 在实例化类时,可以指明具体的泛型类型

② 指明完以后,在类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。

③ 泛型的类型必须是引用数据类型,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换

④ 泛型可以省略,如果省略,默认泛型是Object类型。

二、泛型在集合中的应用

1. 集合中没有使用泛型的例子

@Test
public void test1(){ArrayList list = new ArrayList();//需求:存放学生的成绩list.add(78);list.add(76);list.add(89);list.add(88);//问题一:类型不安全//list.add("Tom");for(Object score : list){//问题二:强转时,可能出现ClassCastExceptionint stuScore = (Integer) score;System.out.println(stuScore);}}

图示:

2. 集合中使用泛型例子1

//在集合中使用泛型,以ArrayList为例
@Test
public void test1(){ArrayList<String> list = new ArrayList<>();list.add("A");list.add("B");list.add("C");list.add("D");list.add("E");//遍历方式一:Iterator<String> iterator = list.iterator();while (iterator.hasNext()){System.out.println(iterator.next());}System.out.println("-------------");//便利方式二:for (String str:list) {System.out.println(str);}
}

图示:

3. 集合中使用泛型例子2

@Test
//在集合中使用泛型的情况:以HashMap为例
public void test2(){Map<String,Integer> map = new HashMap<>();//jdk7新特性:类型推断map.put("Tom",26);map.put("Jarry",30);map.put("Bruce",28);map.put("Davie",60);//嵌套循环Set<Map.Entry<String, Integer>> entries = map.entrySet();Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();while (iterator.hasNext()){Map.Entry<String, Integer> entry = iterator.next();String key = entry.getKey();Integer value = entry.getValue();System.out.println(key+"="+value);}}

4. 集合中使用泛型总结:

① 集合接口或集合类在JDK 5.0时都修改为带泛型的结构。

② 在实例化集合类时,可以指明具体的泛型类型

③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。

比如:add(E e) —>实例化以后:add(Integer e)

④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换

⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。

三、自定义泛型结构

泛型类、泛型接口、泛型方法

1. 泛型的声明

  • interface List 和 class GenTest<K,V>其中,T,K,V,不代表值,而是表示类型。这里使用任意字母都可以。

  • 常用T表示,是Type的缩写。

Java泛型中的常见标记符含义

  • E - Element (在集合中使用,因为集合中存放的是元素)

  • T - Type(Java 类)

  • K - Key(键)

  • V - Value(值)

  • N - Number(数值类型)

  • - 通配符,表示不确定的java类型

2. 泛型的实例化:

  • 如果定义了泛型类,实例化没有指明类的泛型,则默认此泛型类型是Object类型。
  • 要求:如果定义的类是带泛型的,建议在实例化时指明类的泛型。

如:

List strList =new ArrayList();

Iterator iterator = customers.iterator();

  • T只能是类 (对象),不能用基本数据类型填充。但可以使用包装类填充
  • 把一个集合中的内容限制为一个特定的数据类型,这就是泛型背后的核心思想
//JDK 5.0以前
Comparable c = new Date();
System.out.println(c.comparaTo("red");//JDK 5.0以后
Comparable <Date> c = new Date();
System.out.println(c.comparaTo("red");

总结:使用泛型的主要优点在于能够在编译时而不是在运行时检测错误

3. 注意点

  1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如<E1,E2,E3>

  2. 泛型类的构造器正确写法: public GenericClass(){}

    错误写法: public GenericClass{}

  3. 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。

  4. 泛型不同的引用不能相互赋值。

    尽管在编译时 ArrayList 和 ArrayList 是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。

  5. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。

    建议:泛型要使用一路都用。要不用,一路都不要用。

  6. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。

  7. JDK 7.0,泛型的简化操作: ArrayList list = new ArrayList<>();(类型推断)

  8. 泛型的指定中不能使用基本数据类型,可以使用包装类替换。

  9. 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型(因为只有实例化类/接口的时候才能确定泛型的类型,由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,静态的方法就已经加载完成了)。泛型类中的静态方法不能使用类的泛型,而应将该方法定义为泛型方法

  10. 泛型方法,可以声明为静态的
    原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。

  11. 异常类不能是泛型的。

  12. 创建泛型数组时,不能这样创建,如:E[] arr = new E[],编译不通过。但是可以:E[] elements= (E[])new Object[capacity]。(了解一下,一般不建议使用泛型数组)

    参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。

  13. 父类有泛型,子类可以选择保留父类泛型也可以选择不保留父类泛型

    • 子类不保留父类的泛型:按需实现

      • 没有类型—擦除
      • 具体类型
    • 子类保留父类的泛型:泛型子类
      • 全部保留
      • 部分保留
    • 结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型

代码示例:

/*********简单的********/
class Father<T1, T2> {}/*** * 情况一:继承泛型父类后不保留父类的泛型(子类不保留父类泛型)**/
//1.没有指明类型  擦除
class Son1 extends Father {//等价于class Son1 extends Father<Object,Odject>{}
}//2.指定具体类型
class Son2 extends Father<Integer, String> {}/**** 情况二:继承泛型父类后保留泛型类型(子类保留父类泛型)**/
//1.全部保留
class Son3<T1, T2> extends Father<T1, T2> {}//2.部分保留
class Son4<T2> extends Father<Integer,T2>{}/*******复杂的********/
class Father<T1, T2> {}/*** 定义泛型子类Son* 情况一:继承泛型父类后不保留父类的泛型*/
//1.没有指明类型  擦除
class Son1<A, B> extends Father {//等价于class Son1 extends Father<Object,Odject>{}
}//2.指定具体类型
class Son2<A, B> extends Father<Integer, String> {}/*** 定义泛型子类Son* 情况二:继承泛型父类后保留泛型类型*/
//1.全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {}//2.部分保留
class Son4<T2, A, B> extends Father<Integer,T2>{}

4. 自定义泛型结构

4.1 自定义泛型类

代码示例:

/*** 自定义泛型类Order*/
class Order<T> {private String orderName;private int orderId;//使用T类型定义变量private T orderT;public Order() {}//使用T类型定义构造器public Order(String orderName, int orderId, T orderT) {this.orderName = orderName;this.orderId = orderId;this.orderT = orderT;}//这个不是泛型方法public T getOrderT() {return orderT;}//这个不是泛型方法public void setOrderT(T orderT) {this.orderT = orderT;}//这个不是泛型方法@Overridepublic String toString() {return "Order{" +"orderName='" + orderName + '\'' +", orderId=" + orderId +", orderT=" + orderT +'}';}
//    //静态方法中不能使用类的泛型。
//    public static void show(T orderT){//        System.out.println(orderT);
//    }//    //try-catch中不能是泛型的。
//    public void show(){//        try {//
//        }catch (T t){//
//        }
//    }//泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。//换句话说,泛型方法所属的类是不是泛型类都没有关系。//泛型方法,可以声明为静态的。// 原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。public static <E> List<E> copyFromArryToList(E[] arr) {ArrayList<E> list = new ArrayList<>();for (E e :list) {list.add(e);}return list;}
}

自定义泛型类Order的使用

@Test
public void test1() {//如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型//要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。Order order = new Order();order.setOrderT(123);System.out.println(order.getOrderT());order.setOrderT("abc");System.out.println(order.getOrderT());//建议:实例化时指明类的泛型Order<String> order1 = new Order<>("Tom", 16, "male");order1.setOrderT("AA:BBB");System.out.println(order1.getOrderT());
}@Test
//调用泛型方法
public void test2(){Order<String> order = new Order<>();Integer [] arr = new Integer[]{1,2,3,4,5,6};List<Integer> list = order.copyFromArryToList(arr);System.out.println(list);
}

4.2 自定义泛型接口

代码示例:

/*** 自定义泛型接口*/
public interface DemoInterface <T> {void show();int size();
}//实现泛型接口
public class Demo implements DemoInterface {@Overridepublic void show() {System.out.println("hello");}@Overridepublic int size() {return 0;}
}@Test
//测试泛型接口
public void test3(){Demo demo = new Demo();demo.show();
}

4.3 自定义泛型方法

  • 方法,也可以被泛型化,即泛型方法。它与其所属的类是不是泛型类没有任何关系。
  • 在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
  • 泛型参数是在调用方法时确定的。并非在实例化类时确定。
  • 泛型方法的格式访问权限 <泛型> 返回类型 方法名(泛型标识 参数名称)
  • 泛型方法声明泛型时也可以指定上限

代码示例:

//泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
//换句话说,泛型方法所属的类是不是泛型类都没有关系。
//泛型方法,可以声明为静态的。
// 原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
public static <E> List<E> copyFromArryToList(E[] arr) {ArrayList<E> list = new ArrayList<>();for (E e :list) {list.add(e);}return list;
}

4.4 总结:

  • 泛型实际上就是标签,声明时不知道类型,再使用时指明
  • 定义泛型结构,即:泛型类、接口、方法、构造器时贴上泛型的标签
  • 用泛型定义类或借口是放到类名或接口名后面,定义泛型方法时在方法名前加上

5. 泛型的应用场景

【DAO.java】:定义了操作数据库中的表的通用操作。 ORM思想(数据库中的表和Java中的类对应)

public class DAO<T> {//表的共性操作的DAO//添加一条记录public void add(T t){}//删除一条记录public boolean remove(int index){return false;}//修改一条记录public void update(int index,T t){}//查询一条记录public T getIndex(int index){return null;}//查询多条记录public List<T> getForList(int index){return null;}}

【CustomerDAO.java】:

public class CustomerDAO extends DAO<Customer>{//只能操作某一个表的DAO
}

【StudentDAO.java】:

public class StudentDAO extends DAO<Student> {//只能操作某一个表的DAO
}

四、泛型在继承上的体现

  • 类A是类B的父类,但是G<A> 和 G<B>二者不具备子父类关系,二者是并列关系。而二者共同父类是:G<?>
  • 补充:类A是类B的父类,A<G> 是 B<G> 的父类

代码示例:

@Test
public void test1(){Object obj = null;String str = null;obj = str;Object[] arr1 = null;String[] arr2 = null;arr1 = arr2;//编译不通过// Date date = new Date();// str = date;List<Object> list1 = null;List<String> list2 = new ArrayList<String>();//此时的list1和list2的类型不具子父类关系//编译不通过// list1 = list2;/*反证法:假设list1 = list2;list1.add(123);导致混入非String的数据。出错。*/show(list1);show1(list2);
}public void show1(List<String> list){}public void show(List<Object> list){}@Test
public void test2(){AbstractList<String> list1 = null;List<String> list2 = null;ArrayList<String> list3 = null;list1 = list3;list2 = list3;}

五、通配符

泛型通配符是在泛型的使用中,用来表示对泛型类型进行类型范围限定的特殊符号。这里用通配符就是为了表明要输入的类型要在一定范围之内,说的通俗一些其实就是一个类型取值范围,而最大值是Object这是确定的。

1. 通配符的使用

  1. 使用类型通配符:<?> 无限通配符

    比如:List<?>,Map<?,?>

    List<?> 是 List、List 等各种泛型List的父类。

  2. 读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object

  3. 写入list中的元素时,不可以。因为我们不知道集合list中的元素类型,我们不能向其中添加对象。 除了添加null之外。

说明

  • 将任意元素加入到其中不是类型安全的,即对于List<?>不能向其内部添加数据,除了添加null之外

    List<?> c = new ArrayList()

    c.add(new Object()); //编译时错误

    ( 因为我们不知道c集合的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个已知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去 )

  • 唯一的例外的是null,它是所有类型的成员。

  • 对于List<?>允许读取数据,读取的数据类型为Object。可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object。

代码示例:

@Test
public void test3(){List<Object> list1 = null;List<String> list2 = null;List<?> list = null;list = list1;list = list2;//编译通过// print(list1);// print(list2);//List<String> list3 = new ArrayList<>();list3.add("AA");list3.add("BB");list3.add("CC");list = list3;//添加(写入):对于List<?>就不能向其内部添加数据。//除了添加null之外。// list.add("DD");// list.add('?');list.add(null);//获取(读取):允许读取数据,读取的数据类型为Object。Object o = list.get(0);System.out.println(o);
}public void print(List<?> list){Iterator<?> iterator = list.iterator();while(iterator.hasNext()){Object obj = iterator.next();System.out.println(obj);}
}

2. 注意点

  • 不能用在创建对象上
  • 不能用在泛型类的声明上
  • 不能用在泛型接口的声明上
  • 不能用在泛型方法的声明上
//注意点1:编译错误:不能用在创建对象上,右边属于创建集合对象
ArrayList<> list2 = new ArrayList<?>();//注意点2:编译错误:不能用在泛型类的声明上
class GenericTypeClass<?>{}//注意点3:编译错误:不能用在泛型接口的声明上
public interface DemoTest<?> {void show();int size();
}//注意点4:编译错误:不能用在泛型方法的声明上,返回值类型前面<>不能使用?
public static <?> void test(ArrayList<?> list){}

3. 有限制条件的通配符

  • <?>:允许所有泛型的引用调用

  • 通配符指定上限

    上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=

  • 通配符指定下限

    下限super:使用时指定的类型不能小于操作的类,即>=

  • 举例:

    • <? extends A>(无穷小, A]

      只允许泛型为A及A子类的引用调用,即G<? extends A>是G(A)和G(B)的父类,其中A是B的父类。
      读取:用A类型对象接受,因为小于等于A,最大A(小的可以赋值大的,大的不能赋值小的)

      写入:没有?<=Person,无穷小,不确定

    • <? super A> [A,无穷大)

      只允许泛型为A及A父类的引用调用,即G<? super A>是G(A)和G(B)的父类,其中B是A的父类。
      读取:用Object类型对象接受,因为大于等于A,最大是Object(小的可以赋值大的,大的不能赋值小的)

      写入:只要是A或A子类都可以添加

    • <? extends Comparable>

      只允许泛型为实现 Comparable接口的实现类的引用调用

代码示例:

@Test
public void test4(){List<? extends Person> list1 = null;List<? super Person> list2 = null;List<Student> list3 = new ArrayList<Student>();List<Person> list4 = new ArrayList<Person>();List<Object> list5 = new ArrayList<Object>();/**通配符指定上限  ==>  <? extends Person> **/list1 = list3;list1 = list4;//list1 = list5; //编译不通过/**通配符指定下限  ==>  <? super Person> **///list2 = list3; //编译不通过list2 = list4;list2 = list5;//读取数据:list1 = list3;Person p = list1.get(0);//编译不通过//Student s = list1.get(0);list2 = list4;Object obj = list2.get(0);编译不通过// Person obj = list2.get(0);//写入数据://编译不通过,因为<? extends Person>,即?<=Person,无穷小,不确定,有可能比Student还小,是其子类//list1.add(new Student());//编译通过,<? super Person>,即?>=Person,无穷大,只要是Person和Person子类都可以添加list2.add(new Person());list2.add(new Student());}

Java高级编程之泛型相关推荐

  1. 带你了解Java高级编程-----多线程

    带你了解Java高级编程-----多线程 对于Java的学习,基本的步骤是Java基础编程,掌握了Java语言的基本语法.数组.面向对象编程.异常处理这四部分之后,就要开始对Java高级编程进一步学习 ...

  2. 读书笔记-Java高级编程-魏勇

    Java高级编程 魏勇 清华大学出版社 ISBN-9787302450948 仅供参考, 自建索引, 以备后查 一.javadoc.jar.JMX.SVN.Git /** * 此类注释出在执行命令后生 ...

  3. java高级编程期末考试题_java高级编程考题

    Java高级课程测试 1在进行swing开发时,经常用的布局管理器有那几种?(5) 2Gui组件,容器,框架,到底有怎样的关系,请举例说明?(5) 3在进行swing开发中会用到事件处理,那事件处理的 ...

  4. 【渝粤题库】广东开放大学 Java高级编程技术 形成性考核

    ​题目: Java语言中可以作为标识符的有(). 题目: Java中的流程控制语句包括(). 题目:计算机语言的发展经历了非常大的变革,其发展具体包括(). 题目:java源程序经过编译器编辑后,形成 ...

  5. 字节跳动面试真题:java高级编程考试题及答案

    我听到的一些发声 你们赚的钱已经可以了: 我一个发小是做土木工程的,上海大学博士,参与很多著名建筑的工程,但是从薪资上看,还不如一些稍微像样的公司的6年多的高级开发.为什么?这就是行业的红利,个体是享 ...

  6. Java高级编程学习

    1.9日学习笔记 类变量/静态变量 (jdk8以后,静态变量存放在堆里这个类对应的class对象最后,jdk8以前,静态变量存放在方法区) 类变量也叫静态变量/属性,是该类所有对象共享的变量,任何一个 ...

  7. Java高级编程5-姜国海

    ①Object类 一切类都是从这个类继承来的 clone函数:浅复制 对象内部的引用直接复制,指向与之前相同的位置class Student implements Cloneable{string n ...

  8. Java高级编程之常用类

    一.String类 java.lang.String类的使用 (一)概述 String: 字符串,使用一对""引起来表示. String声明为final的,不可被继承 String ...

  9. 带你了解Java高级编程-----网络编程

    文章目录 一.前言 二.网络通信要素 要素一:IP和端口号 要素二:网络协议 三.TCP网络编程 四.UDP网络编程 五.URL编程 一.前言 网络编程是指编写运行在多个设备(计算机)的程序,这些设备 ...

最新文章

  1. php 信号量 关闭,php 信号量
  2. java接收uniapp上传的图片_uni-app 上传图片的坑
  3. pandas追加写入行、列
  4. SQL数据库语言基础之SqlServer表数据的插入、更新与删除
  5. 地图分幅组件的实现(一) ——图号和经纬度转换组件
  6. python数据分析-python数据统计分析
  7. MySQL半同步复制 - 优点、缺点、配置
  8. 东财在线计算机应用基础作业,《计算机应用基础》东财在线20秋第一套作业答案...
  9. mysqladvisor安装
  10. 3个简单的事情,使你的jQuery代码变得更加棒
  11. 傻妞旧版合集新版订阅
  12. 根据点云及其对应的四元数与GPS计算出其相对坐标系的经纬坐标(matlab)
  13. Gitlab-CI Runner缓存
  14. 代理模式Proxy——在线代理
  15. sql语句进阶教程(学习sql这一篇就够了)
  16. c语言编程a4988驱动步进电机,A4988 步进电机驱动模块测试
  17. php kestrel,转载 kestrel php 讯息队列
  18. Windows 7 新功能 - BitLocker To Go
  19. [软件人生]从应聘到骂人
  20. JAVA intercupt_2016年CUPT竞赛参考文献.pdf

热门文章

  1. 2023年进入互联网行业好找工作吗?
  2. PHPStudy安装及初级操作教程
  3. 编译image-analogy caffe问题记录
  4. php 驾校选择题,驾校驾驶理论考试模拟系统的设计与实现(PHP,MySQL)
  5. UE4(虚幻4)基础:导入高(灰)度图创建地形
  6. 产品思维训练 | 新用户从注册到绑卡流失率很高是什么原因?
  7. 关于收集技术分享会议_关于技术会议上的饼图多样性
  8. 三大岩类的野外区分方法
  9. ESET SECURITY连接错误,安装失败。
  10. SQL Server :Stored procedures存储过程初级篇