javaSE基础-泛型
泛型
为什么要有泛型
泛型:相当于标签
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在jdk1.5之前只能把元素类型设计为Object,在jdk1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,此时把元素的类型设计成一个参数,这个参数叫做泛型。Collection<E>,List<E>,ArrayList<E>,这个<E>就是类型参数,即为泛型。
泛型概念:就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者某个方法的返回值及参数类型。这个类型参数将在使用时(例如,在继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
Jdk1.5之后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时指定集合元素的类型,如:List<String>,这表明该List只能保存字符串类型的对象
集合没有使用泛型示意图:
集合使用泛型之前的情况(jdk1.5之前)
@Test
public void test1(){
ArrayList list = new ArrayList();
list.add(33);
list.add(89);
list.add(78);
//问题一:类型不安全
list.add("Tim");
for(Object score : list){
//问题二:强转时,可能出现ClassCastException
int stuScore = (int) score;
System.out.println(stuScore);
}
}
泛型技术是给编译器使用的技术,用于编译时期,保证了类型的安全。
运行时会将泛型擦除,生成的class文件中是不带泛型的,这个称为反省的擦除。
为什么擦除呢?
因为为了兼容运行时加载器。
泛型的补偿:在运行时期,通过获取元素的类型进行转换动作,不用使用者再强制转换了。
类型擦除
Java 中的泛型基本上都是在编译器这个层次来实现的。在生成的 Java 字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的 List<Object>和 List<String>等类型,在编译之后都会变成 List。JVM 看到的只是 List,而由泛型附加的类型信息对 JVM 来说是不可见的。
类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是 Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。
泛型的优势
- 解决元素存储的安全性问题
- 解决获取数据元素时,需要类型强制转换的问题
在集合中使用泛型
集合使用泛型流程示意图
1、集合接口或集合类在jdk1.5时都修改为带泛型的结构
2、在实例化集合类时,可以指明具体的泛型类型
3、在说明以后,在集合类或接口中凡是定义类或接口时,内部结构(如方法、构造器、属性)使用到类的泛型位置都指定为实例化时的泛型类型,如:add(E e) --> 实例化后: add(Integer e)
4、注意:泛型的类型必须是类,不能是基本的数据类型,需要用到基本数据类型的位置,使用其包装类
5、如果实例化时,没有指明泛型的类型,默认类型为java.lang.Object类型
6、jdk1.7新特性,类型推断:Map<String,Integer> map = new HashMap<>();
集合使用泛型的情况(jdk1.5+),以ArrayList为例:
@Test
public void test2(){
ArrayList<Integer> list = new ArrayList<>();
list.add(67);
list.add(89);
list.add(93);
//报错,编译时解决类型不符
//list.add("Tom");
for (Integer score : list){
//不需要类型强转
int stuScore = score;
System.out.println(stuScore);
}
}
集合使用泛型的情况(jdk1.5+),以HashMap为例:
@Test
public void test3(){
HashMap<String, Integer> map = new HashMap<>();
map.put("Marry", 87);
map.put("Kim", 77);
map.put("Tom", 69);
//遍历Map集合
Set<Map.Entry<String, Integer>> entries = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
while(iterator.hasNext()){
Map.Entry<String, Integer> next = iterator.next();
String key = next.getKey();
Integer value = next.getValue();
System.out.println(key + "-->" + value);
}
}
自定义泛型结构
泛型接口
// 泛型接口,将泛型定义在接口上
interface Inter<T> {
public void show(T t);
}
class InterImpl1 implements Inter<String> {//此类为普通类,不带泛型
public void show(String str) {
System.out.println("show:" + str);
}
}
public class GenericTest {
public static void main(String[] args) {
InterImpl1 in = new InterImpl1();
in.show("haha");
}
}
泛型类
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
示例
public class Order<T>{ // <T>为泛型结构标识
private String orderName;
private Integer orderId;
//类的内部结构就可以使用类的泛型
T orderT;
public Order(){}
public Order(String orderName, Integer 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;
}
}
public class OrderTest {
@Test
public void test1(){
//如果定义了泛型类,实例化没有指明类的泛型,则默认泛型类型为Object类型
Order order = new Order();
order.setOrderT("Mick");//添加不安全
order.setOrderT(123);
//自定义泛型类,实例化对象指明泛型参数
Order<String> order1 = new Order<>("orderAA",1000,"order:AA");
order1.setOrderT("Tom");// T指String
}
}
泛型方法
定义:调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
示例一:
public class Order<T>{
...
//泛型方法:在方法中出现了泛型结构,泛型参数与类的泛型参数没有任何关系
//意思就是:泛型方法所属的类是不是泛型类没有关系
//泛型方法可以声明为static,原因:泛型参数是在调用方法时确定的,并非是实例化类时确定的
public static <E> List<E> copyFromArr(E[] arr){//
ArrayList<E> list = new ArrayList<>();
for (E e : arr){
list.add(e);
}
return list;
}
}
//测试泛型方法
@Test
public void test3(){
Order<String> order = new Order<>();
order.setOrderT("haha");
Integer[] arr = new Integer[]{1,2,3,4,5};
//泛型方法在调用时,指明泛型参数的类型
List<Integer> list = order.copyFromArr(arr);//接受Integer类型的数组
//遍历输出list集合
list.forEach(System.out::println);// 1 2 3 4 5
}
示例二:
//Tool1.java
public class Tool1<Q> {
// 将泛型定义在方法上
public <T> void show(T str) {
System.out.println("show:" + str);
}
// 这个方法的泛型跟着对象走的 --> 不是泛型方法
public void print(Q str) {
System.out.println("print:" + str);
}
// 这个方法的泛型不跟着对象走,与Q换成Z的效果一样
public <Q> void method(Q str) {
System.out.println("method:" + str);
}
// 当方法是静态时,不能访问类上定义的泛型,
// 如果静态方法使用泛型时,只能将泛型定义在方法上。
public static <Y> void run(Y str) {
System.out.println("static :" + str);
}
}
//GenericDemo4.java
public class GenericDemo4 {
public static void main(String[] args) {
Tool1<String> tool = new Tool1<String>();
tool.show("haha");
tool.show(new Integer(5));
tool.print("abc");
// tool.print(new Integer(8));编译失败
tool.method("xixi");
tool.method(new Integer(12));
Tool1.run("heihei");
Tool1.run(new Integer(6));
}
}
//结果
show:haha
show:5
print:abc
method:xixi
method:12
static :heihei
static :6
注意事项
1、泛型可能有多个参数,此时应将多个参数一起放到尖括号内,比如<E1,E2,E3>
2、泛型类的构造器(不带尖括号的泛型):public GenericClass(){}, 错误的写法:public GenericClass<>(){}
3、实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致
4、泛型不同的引用不能互相赋值,编译期:ArrayList
5、泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Obejct
6、如果泛型结构是一个接口、抽象类,则不可能创建泛型的对象
7、jdk1.7之后新特性:类型推断
8、泛型的指定中不能使用基本的数据类型,可以使用包装类替代
9、在类、接口内部结构中,静态方法不能使用类的泛型(静态方法随类加载而加载的,而泛型类型在创建对象的时候才加载)
//报错
public static void show(T orderT){
System.out.println(orderT);
}
10、异常类不能是泛型
//报错,Exception不带泛型结构
public class Order<T> extends Exception{}
11、不能使用 new T[ ]
//可以写成:
T[] arr = (T[]) new Object(10);
12、父类有泛型,子类可以选择保留泛型,也可以指定泛型类型
泛型在继承上的体现
情况一:继承父类的同时指明父类的泛型类型
public class SubOrder extends Order<Integer>{}//SubOrder不是泛型类
情况二:继承父类不指明泛型
public class SubOrder1<T> extends Order<T>{}//SubOrder1<T>是一个泛型类
测试
@Test
public void test2(){
//情况一:子类继承带泛型的父类,泛型指明泛型类型。则在子类实例化的时候,不需要指明泛型类型
SubOrder subOrder = new SubOrder();
subOrder.setOrderT(1122);
//情况二:子类继承带泛型的父类,泛型不指明泛型类型
SubOrder1<String> subOrder1 = new SubOrder1<>();
subOrder1.setOrderT("jock");
}
更多的使用场景
public class Father<T1, T2> {}
//子类不保留父类的泛型
//1、没有类型擦除
class Son1<A, B> extends Father{}//等价于class Son extends Father<Object, Object>{}
class Son11 extends Father{}
//2、具体类型
class Son2<A,B> extends Father<Integer, String>{}
class Son22 extends Father<Integer, String>{}
//子类保留父类的泛型
//1、全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2>{}
class Son33<T1, T2> extends Father<T1, T2>{}
//2、部分保留
class Son4<T2, A, B> extends Father<Integer, T2>{}
class Son44<T2> extends Father<Integer, T2>{}
示例
@Test
public void test4(){
List<Object> list1 = null;
List<String> list2 = null;
//报错:编译不通过
//list1 = list2;
List<Integer> list3 = null;
ArrayList<Integer> list4 = null;
//可以编译通过
list3 = list4;
}
总结
虽然类A是类B的父类,但是G<A> 和 G<B> 二者不具备子父类关系,二者是并列关系
补充:类A 是类B的父类, A<G> 是 B<G> 的父类
通配符的使用
通配符:?
类型通配符一般是使用?代替具体的类型参数。例如 List<?> 在逻辑上是List<String>,List<Integer> 等所有 List<具体类型实参>的父类。
示例
@Test
public void test5(){
List<Object> l1 = null;
List<Integer> l2 = null;
List<?> l = null;
l = l1;
l = l2;
//编译不通过
//print(l1);
//print(l2);
//使用通配符?,可以读取,不可写入
List<String> l3 = new ArrayList<>();
l3.add("AA");
l3.add("BB");
l3.add("CC");
l = l3;
//编译报错,不能向List<?>内部添加数据,除null之外
//l.add("AA");
l.add(null);
//可以读取集合内容
Object o = l.get(0);
System.out.println(o);//AA
}
注:类A 是 类B的父类,但是G<A> 和 G<B> 二者没有关系,共同的父类是:G<?>
通配符作为参数
//通配符作为参数
public static void print(List<?> list){
Iterator<?> iterator = list.iterator();
while(iterator.hasNext()){
Object next = iterator.next();
System.out.println(next);
}
}
泛型限定
<? extends T>:表示该通配符所代表的类型是 T 类型的子类。
G<? extends A> 可以作为G<A> 和 G<B> 的父类,其中类B为类A的子类,相当于 ?<= A
<? super T>:表示该通配符所代表的类型是 T 类型的父类。
G<? super A> 可以作为G<A> 和 G<B> 的父类,其中类B为类A的父类,相当于 ?>= A
示例一:
//Person.java
public class Person {}
//Student.java
public class Student extends Person{}
@Test
public void test6(){
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
List<Student> list3 = new ArrayList<>();
List<Person> list4 = new ArrayList<>();
List<Object> list5 = new ArrayList<>();
list1 = list3;
list1 = list4;
//编译不通过:泛型上限
//list1 = list5;
//编译不通过:泛型下限
//list2 = list3;
list2 = list4;
list2 = list5;
//读取
list1 = list3;
Person p = list1.get(0);
//编译不通过
//Student s = list1.get(0);
list2 = list5;
Object o = list2.get(0);
//编译不通过
//Person p1 = list2.get(0);
//写入
//编译不通过
//list1.add(new Student());
//list1.add(new Person());
list2.add(new Person());
list2.add(new Student());
}
示例二:
泛型的上限限定:?extends E 代表使用的泛型只能是E类的的子类/本身
泛型的下限限定:? super E 代表使用的泛型只能是E类的父类/本身
public class GenericTest {
public static void main(String[] args) {
/**
* 类的继承关系: Integer -> Number -> Object String -> Object
*/
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement1(list1);
// getElement1(list2); //报错,String不是Number的相关的类型
getElement1(list3);
// getElement1(list4); //报错,Object是Number的父类,不满足泛型上限要求
// getElement2(list1); //报错,Integer不满足Number泛型的下限要求
// getElement2(list2); //报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型 ?,必须是Number类型的或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll) {}
// 泛型的下限:此时的泛型 ?,必须是Number类型的或者Number类型的父类
public static void getElement2(Collection<? super Number> coll) {}
}
泛型应用举例
泛型类
//DAO.java
public class DAO<T>{
private Map<String, T> map= new HashMap<>();
//向map集合中添加数据
public void save(String id, T entry){
map.put(id, entry);
}
//根据id查询map集合中一条信息
public T get(String id){
if(map.containsKey(id)){
return map.get(id);
}
return null;
}
//替换map集合中的一条信息
public void update(String id, T entry){
if (map.containsKey(id)){
map.put(id, entry);
}
}
//将map集合转化为List,并返回List
public List<T> list(){
//Collection<T> values = map.values();
//return (List<T>) values;
Collection<T> values = map.values();
ArrayList<T> list = new ArrayList<>();
for( T t : values){
list.add(t);
}
return list;
}
//删除指定的id
public void delete(String id){
if(map.containsKey(id)){
map.remove(id);
}
}
}
实体类
//User.java
public class User{
private String id;
private Integer age;
private String name;
public User() {
}
public User(String id, Integer age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (!Objects.equals(id, user.id)) return false;
if (!Objects.equals(age, user.age)) return false;
return Objects.equals(name, user.name);
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (age != null ? age.hashCode() : 0);
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
测试类
//DAOTest.java
public class DAOTest {
public static void main(String[] args) {
DAO<User> dao = new DAO<>();
dao.save("1000", new User("1000", 27, "张三"));
dao.save("1001", new User("1001", 30, "李四"));
dao.save("1002", new User("1002", 37, "王五"));
dao.update("1002", new User("1002", 28, "王晓"));
User user = dao.get("1001");
System.out.println(user.getName());
dao.delete("1001");
List<User> list = dao.list();
list.forEach(System.out::println);
}
}
//结果
李四
User{id='1002', age=28, name='王晓'}
User{id='1000', age=27, name='张三'}