javajavase
尚硅谷宋红康第12章_泛型.pdf
简介
泛型:标签
引入背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection
、List 、ArrayList ,这个 就是类型参数,即泛型
概念
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型将在使用时(如继承或实现这个接口,用这个类型声明变量、 创建对象时确定(即传入实际的类型参数,也称为类型实参)
- 从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List
,这表明该List只能保存字符串类型的对象 - JDK1.5改写了集合框架中的全部接口和类,增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参
引入目的
- 解决元素存储的安全性问题。好比商品、药品标签,不会弄错
- 解决获取数据元素时需要类型强制转换的问题。好比不用每回拿商品、药品都要辨别
Java泛型可以保证如果程序在编译时没有发岀警告,运行时就不会产生ClassCastException异常 同时,代码更加简洁、健壮
集合中使用
在集合中没有使用泛型的例子
@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) {
// 问题二:强转时,可能出现ClassCastException
int stuScore = (Integer) score;
System.out.println(stuScore);
}
}
在集合中使用泛型
- 集合接口或集合类在JDK1.5时都修改为带泛型的结构,实例化集合类时可以指明具体的泛型类型
- 指明后,集合类或接口中凡是定义类或接口时,内部结构(方法、构造器、属性等)使用到类的泛型的位置都变为实例化的泛型类型
- 比如:add(E e) —->实例化以后:add(Integer e)
- 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
如果实例化时没有指明泛型的类型,默认类型为Object类型
//在集合中使用泛型的情况:以ArrayList为例
@Test
public void test2() {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(78);
list.add(87);
list.add(99);
list.add(65);
// 编译时,就会进行类型检查,保证数据的安全
// list.add("Tom");
// 方式一:
for(Integer score : list) {
// 避免了强转操作
int stuScore = score;
System.out.println(stuScore);
}
//方式二:
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
int stuScore = iterator.next();
System.out.println(stuScore);
}
}
// 在集合中使用泛型的情况:以HashMap为例
@Test
public void test3() {
// Map<String, Integer> map = new HashMap<String, Integer>();
// jdk7新特性:类型推断
Map<String, Integer> map = new HashMap<>();
map.put("Tom", 87);
map.put("Jerry", 87);
map.put("Jack", 67);
// map.put(123,"ABC");
// 泛型的嵌套
Set<Map.Entry<String, Integer>> entry = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> e = iterator.next();
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key + "----" + value);
}
}
自定义泛型结构
泛型类、泛型接口、泛型方法
泛型的声明
interface List
泛型的实例化
- 一定要在类名后面指定类型参数的值(类型)。如:
List
Iterator
- T只能是类,不能用基本数据类型,可以使用包装类
- 把一个集合中的内容限制为一个特定的数据类型,这就是generics背后的核心思想
自定义泛型类、接口
// 自定义泛型类Order
public class Order<T> {
String orderName;
int orderId;
// 类的内部结构就可以使用类的泛型
T orderT;
public Order() {
// 🔴编译不通过
// T[] arr = new T[10];
// 编译通过
T[] arr = (T[]) new Object[10];
}
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;
}
@Override
public String toString() {
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", orderT=" + orderT +
'}';
}
// 🔴静态方法中不能使用类的泛型,生命周期
// public static void show(T orderT) {
// System.out.println(orderT);
// }
public void show() {
// 编译不通过
// try{
// }catch(T t){
// }
}
// 泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
// 换句话说,泛型方法所属的类是不是泛型类都没有关系。
// 泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
public static <E> List<E> copyFromArrayToList(E[] arr) {
ArrayList<E> list = new ArrayList<>();
for (E e : arr) {
list.add(e);
}
return list;
}
}
public class SubOrder extends Order<Integer> { // SubOrder:不是泛型类
}
public class SubOrder1<T> extends Order<T> { // SubOrder1<T>:仍然是泛型类
}
@Test
public void test1() {
// 如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型
// 要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型
Order order = new Order();
order.setOrderT(123);
order.setOrderT("ABC");
Order<String> order1 = new Order<String>("orderAA", 1001, "order:AA");
order1.setOrderT("AA:hello");
}
@Test
public void test2() {
SubOrder sub = new SubOrder();
// 由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型
sub.setOrderT(1122);
SubOrder1<String> sub1 = new SubOrder1<>();
sub1.setOrderT("order1...");
}
注意
- 泛型类的构造器如下: public GenericClass() {},public GenericClass
{}是错误的 - 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致
泛型不同的引用不能相互赋值
// 尽管在编译时 ArrayList<String>和ArrayList<Integer>是两种类型
// 但是,在运行时只有一个ArrayList类被加载到JVM中
@Test
public void test3() {
Person p1 = null;
Person p2 = null;
p1 = p2;
ArrayList<String> list1 = null;
ArrayList<Integer> list2 = new ArrayList<Integer>();
// 泛型不同的引用不能相互赋值。
// list1 = list2;
}
泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object
- 建议:泛型要使用一路都用。要不用一路都不要用
- JDK1.7,泛型的简化操作: ArrayList
first= new ArrayList<>();(类型推断) - 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型 返回值类型。但在静态方法中不能使用类的泛型
- 异常类不能是泛型的
- 不能使用new E[]。但是可以:E[] elements = (E[]) new Object[capacity];
- 参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组
- 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型
- 子类不保留父类的泛型:按需实现
- 没有类型—-擦除
- 具体类型
- 子类保留父类的泛型:泛型子类
- 全部保留
- 部分保留
- 结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型
```java
class Father
{ }
- 子类不保留父类的泛型:按需实现
// 子类不保留父类的泛型 // 1)没有类型 擦除 class Son1 extends Father { // 等价于class Son extends Father
// 2)具体类型
class Son2 extends Father
// 子类保留父类的泛型
// 1)全部保留
class Son3
// 2)部分保留
class Son4
```java
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> {
}
自定义泛型方法
- 方法也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时参数的类型就是传入数据的类型
- 泛型方法的格式:
**[访问权限] <泛型> 返回类型 方法名(泛型标识 参数名称) 抛出的异常 { }**
- 泛型方法声明泛型时也可以指定上限
```java
// 泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系
// 换句话说,泛型方法所属的类是不是泛型类都没有关系
// 泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定
public static
List copyFromArryToList(E[] arr) { ArrayList list = new ArrayList<>(); for (E e: list) {
} return list; }list.add(e);
@Test
public void test4() {
Order
// 泛型方法在调用时,指明泛型参数的类型。
List<Integer> list = order.copyFromArrayToList(arr);
System.out.println(list);
}
<a name="TFvwQ"></a>
### 泛型的应用场景
DAO.java定:义了操作数据库中的表的通用操作。 ORM思想(数据库中的表和Java中的类对应)
```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;
}
// 泛型方法
// 举例:获取表中一共有多少条记录?获取最大的员工入职时间?
public <E> E getValue(){
return null;
}
}
CustomerDAO.java
public class CustomerDAO extends DAO<Customer>{ // 只能操作某一个表的DAO
}
StudentDAO.java
public class StudentDAO extends DAO<Student> { // 只能操作某一个表的DAO
}
DAOTest
public class DAOTest {
@Test
public void test1() {
CustomerDAO dao1 = new CustomerDAO();
dao1.add(new Customer());
List<Customer> list = dao1.getForList(10);
StudentDAO dao2 = new StudentDAO();
Student student = dao2.getIndex(1);
}
}
在继承上的体现
虽然类A是类B的父类,但是G 和G二者不具备子父类关系
补充:类A是类B的父类,A
@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 show(List<Object> list) {
}
public void show1(List<String> list) {
}
@Test
public void test2(){
AbstractList<String> list1 = null;
List<String> list2 = null;
ArrayList<String> list3 = null;
list1 = list3;
list2 = list3;
List<String> list4 = new ArrayList<>();
}
通配符
通配符的使用
- 使用类型通配符:?
- 比如:List<?>,Map<?, ?>
- List<?>是List
、List