为什么要使用泛型
就像垃圾分类一样,垃圾箱上写着什么类型,就往里面丢什么类型的垃圾。泛型是String类型的,就只能填String类型的数据。
使用泛型举例
GenericTest.java
package com.atguigu.java;
import org.junit.Test;
import java.util.*;
/**
*
* 泛型的使用
* 1.jdk 5.0新增的特性
*
* 2.在集合中使用泛型:
* 总结:
* ① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
* ② 在实例化集合类时,可以指明具体的泛型类型
* ③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
* 比如:add(E e) --->实例化以后:add(Integer e)
* ④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
* ⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。
*
* 3.如何自定义泛型结构:泛型类、泛型接口;泛型方法。见 GenericTest1.java
*
*
*/
public class GenericTest {
//在集合中使用泛型之前的情况:
@Test
public void test1(){
ArrayList list = new ArrayList();
//比如需求:存放学生的成绩
list.add(78);
list.add(76);
list.add(89);
list.add(88);
//问题一:类型不安全,加成绩时也能加进 Tom这个名字
list.add("Tom");
for(Object score : list){
//问题二:强转时,可能出现ClassCastException(出现了一些不是成绩的,其他类型的变量,比如Tom, Tom转换不成Integer。)
int stuScore = (Integer) score;//Integer自动拆箱赋给int
System.out.println(stuScore);
}
}
//在集合中使用泛型的情况:以ArrayList为例
@Test
public void test2(){
//在使用泛型时,泛型是一个类型,泛型不能是基本数据类型,所以这里不能用int。
//凡是在类声明的时候指定了的数据类型,比如ArrayList<Integer>指定了Integer,那么这个类中所有用到泛型的地方都被指定为Integer类型。
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(78);
list.add(87);
list.add(99);
list.add(65);
//编译时,就会进行类型检查,保证数据的安全,Tom不属于Integer,所以在编译时查出错误。
// list.add("Tom");
//方式一:
// for(Integer score : list){
// //避免了强转操作
// int stuScore = score;//Integer自动拆箱
//
// System.out.println(stuScore);
//
// }
//方式二:
//list.iterator()返回值是Iterator<E>类型的。
// Iterator<Integer>指明Integer了,它的类里面E next();也指定了Integer next();
Iterator<Integer> iterator = list.iterator();// ↓
while(iterator.hasNext()){// ↓
int stuScore = iterator.next();//所以这里就返回Integer,不用强转类型了。
System.out.println(stuScore);
}
}
//在集合中使用泛型的情况:以HashMap为例
@Test
public void test3(){
//key是String类型的,value是Interger类型的
//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");//这里不匹配泛型,报错
//泛型的嵌套
// entrySet()的返回值是 Set<Map.Entry<K, V>>
//Set<>的泛型是<Entry>,Entry<> 的泛型是<String,Integer>
// 因为Entry是个内部接口,所以这里用Map.Entry.
// 如果上面导入了import java.util.Map.*;这里可以直接用Entry
Set<Map.Entry<String,Integer>> entry = map.entrySet();
//idea 调用方法赋给变量 – 快捷键 ctrl+alt+v
//Iterator < >放的是遍历的数据是什么类型的,此处应该是<Map.Entry<String, Integer>>类型的。
Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();
//此处我理解为Set是一个容器,里面的数据类型是Map.Entry<String,Integer> ;
// 迭代器Iterator要遍历的,要指向的数据也是Map.Entry<String,Integer> 类型的,所以迭代器Iterator的泛型也是Map.Entry<String,Integer> 类型的。
//即如果迭代器Iterator没有指定泛型,那么这个迭代器可以迭代任意的数据,如果指定了泛型,其指针就只能指向Map.Entry<String,Integer> 类型的对象,只能遍历这个类型的对象。
while(iterator.hasNext()){
Map.Entry<String, Integer> e = iterator.next();
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key + "----" + value);
}
}
}
自定义泛型类和泛型方法举例
泛型类
第二条:声明构造器的时候不用加泛型<>,
new对象的时候加泛型<>。new Order。
第三条:就是 实例化后,原来泛型T的地方,比如T name,在指定泛型为String类型后,就要按String name;使用。
第四条:见GenericTest1.java中的test3。
第九条:见GenericTest1.java中的test4,和Order.java的static show()方法。
第十条:见 MyException.java和Order.java的public void show()。
泛型方法
boolean add(E e);不是泛型方法;
泛型方法是格外的有一个新的标识,如果类用,泛型方法的新的标识可以用比如。
泛型方法是针对于方法来讲的,和泛型方法所在的类是否是泛型的无所谓。
泛型方法的测试在 Order.java中的
public static List copyFromArrayToList(E[] arr)方法
和在GenericTest1.java中的test4中测试中。
泛型类和泛型方法的测试代码
Order.java
package com.atguigu.java;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义泛型类
*
*/
public class Order<T> {
String orderName;
int orderId;
//类的内部结构就可以使用类的泛型
T orderT;//声明T类型的变量
public Order(){
//编译不通过
// T[] arr = new T[10];//new 后边这个T要求是具体是指明的,不能是泛型T
//编译通过
T[] arr = (T[]) new Object[10];
//先new Object[]类的数组,再强转为T[]。
//到时候如果new对象,如果前面是Order类型的,那么后边的要new Order或者其子类对象类型的。
// 不能真new Object[],不然强转会发生错误。
}
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){//这里的T不能写,写T会出错,因为如果T这不是一个异常类型,那么这段代码就过不去了,跑不了了。
//
// }
}
//★泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
//换句话说,泛型方法所属的类是不是泛型类都没有关系。
//泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
//public List<E> copyFromArrayToList(E[] arr) 会被系统误认为E可能是一个类。为了表示这是泛型,要在前面价格<E>.
//本方法目的是是将数组arr中的取出来放在List中,然后将List返回。
public static <E> List<E> copyFromArrayToList(E[] arr){
//这个<E>是在我调用的时候确定的,调用时不管是类调用的还是对象调用的,调用的时候告诉方法是什么类型就行,所以可以是static 的。
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
//在GenericTest1.java中的test4中测试
}
}
SubOrder.java
package com.atguigu.java;
import java.util.ArrayList;
import java.util.List;
/**
* Order的子类SubOrder
*/
public class SubOrder extends Order<Integer> {//SubOrder:不是泛型类,因为已经指明了Integer。
//这个SubOrder不是泛型类,同样能有泛型方法。
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
}
SubOrder1.java
package com.atguigu.java;
/**
*Order的子类SubOrder
*/
public class SubOrder1<T> extends Order<T> {
//继承父类时并没有指明泛型类是什么
// 一般来说,如果指定了父类的泛型,那么子类就只能单一的new那一种泛型类的对象了,
// 如果向子类仍然能new很多泛型类的对象,那么就在子类对象后边添一个泛型,此时
//SubOrder1<T>:仍然是泛型类
}
MyException.java
package com.atguigu.java;
/**
* 异常类不能声明为泛型类
*/
//异常类不能声明为泛型类
//public class MyException<T> extends Exception{
//}
GenericTest1.java
package com.atguigu.java;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
/** 如何自定义泛型结构:泛型类、泛型接口;泛型方法。
*
* 1. 关于自定义泛型类、泛型接口:
*/
public class GenericTest1 {
@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 sub1 = new SubOrder();
//由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。
//子类指定泛型是<Integer>了,那么从父类继承过来的方法也是Interger的
sub1.setOrderT(1122);
SubOrder1<String> sub2 = new SubOrder1<>();
sub2.setOrderT("order2...");//此时sub2.setOrderT(参数是String类型)
}
@Test
public void test3(){
ArrayList<String> list1 = null;
ArrayList<Integer> list2 = new ArrayList<Integer>();
//泛型不同的引用不能相互赋值。
//list1 = list2;//不能这样赋值,报错。
}
//测试泛型方法,泛型方法的调用
@Test
public void test4(){
Order<String> order = new Order<>();
Integer[] arr = new Integer[]{1,2,3,4};
//泛型方法在调用时,指明泛型参数的类型。
//List的Interger是由放进order.copyFromArrayToList(arr)的数组arr决定的。
// arr数组的元素类型是Interger的,所以集合List中装的也是Interger类型。
List<Integer> list = order.copyFromArrayToList(arr);
System.out.println(list);
//[1, 2, 3, 4]
}
}
举例泛型类和泛型方法的使用情景
DAO.java
package com.atguigu.java1;
import java.util.List;
/**
* DAO:data(base) access object
* 数据访问对象
* DAO提供一些操作数据库的通用操作,
* 但是数据库中一张表对应java的一个对象,操作哪个不知道,所以加个泛型<T>
*/
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;
}
}
Customer.java
package com.atguigu.java1;
/**
* 数据库中的Customer表
*/
public class Customer { //此类对应数据库中的customers表
}
CustomerDAO.java
package com.atguigu.java1;
/**
* 专门操作Customer表的DAO
*/
public class CustomerDAO extends DAO<Customer>{//只能操作某一个表的DAO
// 此处泛型填了Customer,DAO中的方法就具体了,此类只能操作Customer。
}
StudentDAO.java
package com.atguigu.java1;
/**
* 专门操作Student表的DAO
*/
public class StudentDAO extends DAO<Student> {//只能操作某一个表的DAO
}
DAOTest.java
package com.atguigu.java1;
import org.junit.Test;
import java.util.List;
/**
*
*/
public class DAOTest {
@Test
public void test1(){
CustomerDAO dao1 = new CustomerDAO();
dao1.add(new Customer());//由于CustomerDAO指定了泛型为Customer,此时add只能添加Customer类型的数据。
List<Customer> list = dao1.getForList(10);
StudentDAO dao2 = new StudentDAO();
Student student = dao2.getIndex(1);//返回的也是Student,即指定的泛型的
}
}
泛型在继承方面的体现
见GenericTest.java中test1和test2。
通配符的使用 和 使用通配符后数据的读取和写入要求
见GenericTest.java中test3
有限制条件的通配符的使用
见GenericTest.java中test4
GenericTest.java 类
Person.java
package com.atguigu.java2;
/**
* 服务于GenericTest的test4
*/
public class Person {
}
Student.java
package com.atguigu.java2;
/**
*服务于GenericTest的test4
*/
public class Student extends Person {
}
GenericTest.java
package com.atguigu.java2;
import org.junit.Test;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
*
* 1. 泛型在继承方面的体现
*
*
* 2. 通配符的使用
*
*/
public class GenericTest {
/*
1. 泛型在继承方面的体现
虽然类A是类B的父类,但是G<A> 和G<B>二者不具备子父类关系,二者是并列关系。见test1
补充:类A是类B的父类,A<G> 是 B<G> 的父类。见test2
*/
@Test
public void test1(){
Object obj = null;
String str = null;
obj = str;//多态,子类赋给父类
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;//也是多态,如果之后赋值后,数组每个对象都是多态
//List<Object>和List<String>的关系就像Date和String的关系,都没有子父类关系。
//编译不通过
// Date date = new Date();
// str = date;//没有子父类关系
List<Object> list1 = null;
List<String> list2 = new ArrayList<String>();
//此时的list1和list2的类型也不具有子父类关系
//编译不通过
// list1 = list2;
/*
反证法:
假设list1 = list2;//相当于list1和list2都指向list2的堆存储空间,
list1能加入Object的类型,list1调用add添加123,却把123加入到了List<String> list2的存储空间里,
就混入了不是String 的数据,相当于白设计泛型了,出错。
list1.add(123);导致混入非String的数据。出错。
*/
show(list1);//调用的public void show(List<Object> list)
//show(list2);//报错,相当于list1=list2了
show1(list2);//调用的public void show1(List<String> list)
}
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;//ArrayList 是 AbstractList的子类
list2 = list3;//ArrayList 是 List的子类
List<String> list4 = new ArrayList<>();
}
/*
2. 通配符的使用
通配符:?
类A是类B的父类,G<A>和G<B>是没有关系的,二者共同的父类是:G<?>
*/
@Test
public void test3(){
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
//List<?>相当于作为了List<Object>和List<String>的通用父类
list = list1;
list = list2;
//编译通过
// print(list1);
// 这里是调用下面的 public void print(List<?> list)方法,List<?> list=List<Object>,编译通过。
// print(list2);
//
List<String> list3 = new ArrayList<>();
list3.add("AA");
list3.add("BB");
list3.add("CC");
list = list3;//此时list也指向了list3的存储空间(元素全是String类型)
//添加(写入):对于List<?> 就不能向其内部添加数据。
//除了添加null之外。
//list.add("DD");//报错,不能添加数据
list.add(null);//唯一的例外是null,因为他是所有类型的成员。
//获取(读取):允许读取数据,因为list<?>,get()函数返回的类型是?,不知道问号是什么,返回的是什么类型,
// 但知道其顶级父类一定是Object,能多态赋给Object,所以读取的数据类型为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);
}
}
/*
3.有限制条件的通配符的使用。
? extends A:
G<? extends A> 可以作为G<A>和G<B>的父类,其中B是A的 子 类
? super A:
G<? super A> 可以作为G<A>和G<B>的父类,其中B是A的 父 类
*/
@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, 能赋Person或者Person子类对象 ,≤
list1 = list3;
list1 = list4;
// list1 = list5;//报错
//super Person, 能赋Person或者Person父类对象 ,≥
// list2 = list3;//报错
list2 = list4;
list2 = list5;
//读取数据:
list1 = list3;//List<? extends Person> list1 ,list1 <= Person,不会超过Person
Person p = list1.get(0);
//编译不通过
//Student s = list1.get(0);//虽然list3是Student的,但是我们这看list1类型来决定返回值的类型。
list2 = list4;//List<? super Person> list2 ,list2 >=Person,最高的类就是Object。
Object obj = list2.get(0);
//编译不通过
//Person obj = list2.get(0);
//写入数据:
//编译不通过
//list1.add(new Student());//不能写,因为不能确定一个最小,最下面的类,来多态 承接赋给的值。
//编译通过
list2.add(new Person());//这里确定了最小的类是Person,所以赋给其 Person类或者Person的子类 都行,用多态 来承接赋给的值。
list2.add(new Student());
}
}
自定义泛型类练习
User.java
package com.atguigu.exer1;
/**
* 定义一个 User 类:
该类包含:private成员变量(int类型) id,age;(String 类型)name。
*/
public class User {
private int id;
private int age;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public User() {
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
// 此题key是String类型 ,User是value。只是key需要equals和hashCode方法,是map中的键才要,值不需要。
// 所以本题不用重写equals()和hashcode()方法
// @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 + age;
// result = 31 * result + (name != null ? name.hashCode() : 0);
// return result;
// }
}
DAO.java
package com.atguigu.exer1;
import java.util.*;
/**
* 定义个泛型类 DAO<T>,在其中定义一个Map 成员变量,Map 的键为 String 类型,值为 T 类型。
分别创建以下方法:
public void save(String id,T entity): 保存 T 类型的对象到 Map 成员变量中
public T get(String id):从 map 中获取 id 对应的对象
public void update(String id,T entity):替换 map 中key为id的内容,改为 entity 对象
public List<T> list():返回 map 中存放的所有 T 对象
public void delete(String id):删除指定 id 对象
*
*/
public class DAO<T> {
private Map<String,T> map = new HashMap<String,T>();
//保存 T 类型的对象到 Map 成员变量中
public void save(String id,T entity){
map.put(id,entity);
}
//从 map 中获取 id 对应的对象
public T get(String id){
return map.get(id);
}
//替换 map 中key为id的内容,改为 entity 对象
public void update(String id,T entity){
if(map.containsKey(id)){
map.put(id,entity);
}
}
//返回 map 中存放的所有 T 对象
public List<T> list(){
//错误的:
// Collection<T> values = map.values();
//父类型可以转化为子类型,前提是就是子类转上去的,可以再转下来.这返回值就是Collection<T>类型的,
// return (List<T>) values;//强转为List<T>,太勉强了
//正确的:
ArrayList<T> list = new ArrayList<>();
Collection<T> values = map.values();
//遍历容器Collection中每一个 T类型的数据,然后装入list当中。
for(T t : values){
list.add(t);
}
return list;
}
//删除指定 id 对象
public void delete(String id){
map.remove(id);
}
}
DAOTest.java
package com.atguigu.exer1;
import java.util.List;
/**
* 创建 DAO 类的对象, 分别调用其 save、get、update、list、delete 方法来操作 User 对象,
使用 Junit 单元测试类进行测试。
*/
public class DAOTest {
public static void main(String[] args) {
DAO<User> dao = new DAO<User>();
dao.save("1001",new User(1001,34,"周杰伦"));//存储一个数据
dao.save("1002",new User(1002,20,"昆凌"));
dao.save("1003",new User(1003,25,"蔡依林"));
dao.update("1003",new User(1003,30,"方文山"));//更新一个数据
dao.delete("1002");//删除一个数据
List<User> list = dao.list();//调用list()方法,返回 map 中存放的所有 T 对象
//System.out.println(list);//输出list
list.forEach(System.out::println);//遍历list
//User{id=1003, age=30, name='方文山'}
//User{id=1001, age=34, name='周杰伦'}
}
}