一、泛型语法
(1)泛型引入
- 看一个需求(1)请编写程序,在ArrayList中,添加三个Dog对象(2)Dog对象含有name和age,并输出name和age(要求使用get方法):
//测试代码 package com.study.generic;import java.util.ArrayList;public class Generic01 {@SuppressWarnings({"all"})public static void main(String[] args) {ArrayList arrayList = new ArrayList();arrayList.add(new Dog("旺财",10));arrayList.add(new Dog("发财",1));arrayList.add(new Dog("小黄",5));for (Object o : arrayList) {Dog dog = (Dog)o;System.out.println(dog.getName() + "-" + dog.getAge());}} } class Dog{private String name;private int age;public Dog(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;} }
- 假如程序员往ArrayList里加了一只猫,再对ArrayList进行遍历的时候,因为我们进行了向下转型(Object→Dog),就会出现ClassCastException(即类型转换异常)
- 使用传统方法的问题分析:
- 不能对加入到集合ArrayList中的数据类型进行约束(不安全)
- 遍历的时候,需要进行类型转换,如果集合中的数据量较大,对效率有影响(如果直接写Dog会报红)
(2)泛型入门
- 用泛型来解决前面的问题(如果编译器发现,添加的类型不满足要求,就会报红):
//测试代码 package com.study.generic.improve;import java.util.ArrayList;public class Generic02 {@SuppressWarnings({"all"})public static void main(String[] args) {//使用泛型,代表这个集合只能存放Dog这种类型ArrayList<Dog> arrayList = new ArrayList<Dog>();arrayList.add(new Dog("旺财",10));arrayList.add(new Dog("发财",1));arrayList.add(new Dog("小黄",5));arrayList.add(new Cat("招财猫",8));} } class Dog{private String name;private int age;public Dog(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;} } class Cat{private String name;private int age;public Cat(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;} }
- 在遍历的时候可以直接取出Dog类型,不需要再向下转型
- 泛型的好处:
- 编译时,编译器会检查添加元素的类型 ,提高了安全性
- 减少了类型转换的次数(看上面的遍历),提高了效率
- 不再提示编译警告
(3)泛型说明
- 大写的E可以理解成参数名,它的参数值可以是Integer、String、Dog......(泛型就是数据类型的数据类型)
- 泛型又称为参数化类型,是JDK5.0出现的新特性,解决数据类型的安全性问题
- 在类声明或实例化时,只要指定好具体的类型即可
- Java泛型可以保证如果程序在编译时没有发生警告,运行时就不会产生警告(ClassCastException)。同时,代码更加简洁、健壮
- 泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,或是某个方法的返回值类型,或者是参数类型
- 测试代码:
package com.study.generic;public class Generic03 {public static void main(String[] args) {Person<String> person = new Person<String>("jack");Person<Integer> person2 = new Person<Integer>(100);person.f1();//class java.lang.Stringperson2.f1();//class java.lang.Integer} } class Person<E>{E name;//也就是说,这个name的类型,可以由我们在创建对象时指定//即在编译期间,就能确定E是什么类型public Person(E name) {this.name = name;}public E f(){return name;}public void f1(){System.out.println(name.getClass());} }
- 该数据类型在定义Person对象时指定,即在编译期间,编译器就能清楚地知道类里的E代表什么数据类型
(4)泛型应用实例
- 类可以指定泛型,接口也可以指定泛型。如果我们想指定多个泛型,可以用逗号间隔开。<K,V>
- K、V、T等不代表值,而是表示类型。用任意字母都可以
- 泛型的实例化:创建/获取对象时,要在类名后面指定类型参数的值(类型)
- 泛型使用举例:(1)创建三个学生对象(2)放入到HashMap和HashSet中,要求key是String name,value就是学生对象(3)使用两种方式遍历
package com.study.generic;import java.util.*;public class Generic04 {public static void main(String[] args) {//使用HashSet添加学生对象并遍历Set<Student> set = new HashSet<Student>();set.add(new Student("john",12));set.add(new Student("mary",18));set.add(new Student("Rose",14));Iterator<Student> iterator = set.iterator();System.out.println("=====使用HashSet添加学生对象并遍历=====");while (iterator.hasNext()) {Student next = iterator.next();System.out.println(next.name + "-" + next.age);}//使用HashMap添加学生对象并遍历Map<String, Student> map = new HashMap<String, Student>();map.put("john",new Student("john",12));map.put("mary",new Student("mary",18));map.put("Rose",new Student("Rose",14));Set<String> names = map.keySet();System.out.println("=====使用HashMap添加学生对象并遍历=====");for (String name : names) {System.out.println(name + "-" + map.get(name).age);}} } class Student{public String name;public int age;public Student(String name, int age) {this.name = name;this.age = age;} }
(5)泛型使用细节
- interface List<T> {},public class HashSet<E> {}......等等。说明:T、E只能是引用类型,不能是int、double这种基本数据类型
- 在指定泛型具体类型后,可以传入该类型或者其子类类型
- 泛型的使用形式:List<Integer> list1 = new List<Integer>();或者List<Integer> list1 = new List<>();都行
- 如果我们这样写List list3 = new List();默认给它的泛型E就是Object
(6)泛型课堂练习
- 定义Employee类(1)该类包含private成员变量name,sal,birthday,其中birthday为MyDate类对象(2)为每一个属性定义get和set方法(3)重写toString方法,输出name、sal和birthday(4)MyDate类包含: 该类包含private成员变量month,day,year;并为每个属性定义get和set方法(5)创建该类的三个对象,并把这些对象放入ArrayList集合中(ArrayList需要使用泛型来定义),对集合中元素进行排序,并遍历输出(先按照name排序,如果name相同,按照生日日期的先后排序)
//循环比较 //新添加的元素成为o1,后添加的元素成为o2 //如果相减结果为负数,o1会被排到o2前面 package com.study.generic;import java.util.ArrayList; import java.util.Comparator; import java.util.List;public class GenericExercise01 {@SuppressWarnings({"all"})public static void main(String[] args) {List<Employee> employees = new ArrayList<Employee>();employees.add(new Employee("tom",20000,new MyDate(2000,11,11)));employees.add(new Employee("jack",12000,new MyDate(2001,12,12)));employees.add(new Employee("tom",50000,new MyDate(1980,10,10)));employees.sort(new Comparator<Employee>() {@Overridepublic int compare(Employee o1, Employee o2) {//先按照name排序int resultName = o1.getName().compareTo(o2.getName());if (resultName==0){//如果name相同,按照生日日期的先后排序int resultYear = o1.getBirthday().getYear()-o2.getBirthday().getYear();if (resultYear==0){//如果出生年相同,按照出生月排序int resultMonth = o1.getBirthday().getMonth()-o2.getBirthday().getMonth();if (resultMonth==0){//如果出生月相同,按照出生日排序return o1.getBirthday().getDay()-o2.getBirthday().getDay();}else{return resultMonth;}}else{return resultYear;}}else{return resultName;}}});for (Employee employee :employees) {System.out.println(employee);}} } class MyDate{private int year;private int month;private int day;public MyDate(int year, int month, int day) {this.year = year;this.month = month;this.day = day;}public int getYear() {return year;}public void setYear(int year) {this.year = year;}public int getMonth() {return month;}public void setMonth(int month) {this.month = month;}public int getDay() {return day;}public void setDay(int day) {this.day = day;} } class Employee{private String name;private double sal;private MyDate birthday;public Employee(String name, double sal, MyDate birthday) {this.name = name;this.sal = sal;this.birthday = birthday;}public String getName() {return name;}public void setName(String name) {this.name = name;}public double getSal() {return sal;}public void setSal(double sal) {this.sal = sal;}public MyDate getBirthday() {return birthday;}public void setBirthday(MyDate birthday) {this.birthday = birthday;}@Overridepublic String toString() {return "Employee{" +"name='" + name + '\'' +", sal=" + sal +", birthday=" + birthday.getYear()+"-"+birthday.getMonth()+"-"+birthday.getDay()+'}';} }
二、自定义泛型
(1)泛型类
- 自定义泛型类的注意细节:
- 普通成员可以使用泛型(属性、方法)
- 使用泛型的数组,不能初始化(因为数组的类型没有确定下来,不知道开辟多大空间)
- 静态方法中不能使用类的泛型(因为静态是和类相关的,在类加载时对象还没有创建)
- 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定的类型)
- 如果在创建对象时,没有指定类型,默认为Object
- 自定义泛型类举例:
package com.study.generic.CustomGeneric;public class CustomGeneric_ {public static void main(String[] args) {Tiger<Double, String, Integer> tiger = new Tiger<>("john");//T→Double、R→String、M→Integertiger.setT(10.9);//正确//tiger.setT("yy");错误,因为T是Double类型Tiger g2 = new Tiger("john~~~");//因为没有传入类型,所以T、R、M都对应Objectg2.setT("yy");//成功,因为此时T的类型是Object} } //Tiger后面有泛型,所以我们就把Tiger称为自定义泛型类 //T、R、M是泛型的标识符、一般是大写字母 //泛型的标识符可以有多个 class Tiger<T,R,M>{String name;R r;M m;T t;T[] ts;//使用泛型的数组在这里不能实例化//因为T的类型没有确定下来,不知道应该开辟多大空间public Tiger(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}public R getR() {return r;}public void setR(R r) {this.r = r;}public M getM() {return m;}public void setM(M m) {this.m = m;}public T getT() {return t;}public void setT(T t) {this.t = t;} }
(2)泛型接口
- 自定义泛型接口的注意事项:
- 接口中,静态成员也不能使用泛型(这个和泛型类的规定一样)
- 泛型接口的类型,在继承接口或者实现接口时确定
- 没有指定类型,默认为Object
- 自定义泛型接口应用实例:
package com.study.generic.CustomGeneric;public class CustomInterfaceGeneric { } interface IUsb<U,R>{//在接口中的属性都是静态性质的int n = 10;//R r;会报错//普通方法中可以使用泛型接口,以下是三个抽象方法R get(U u);void hi(R r);void run(R r1,R r2,U u1,U u2);//在jdk8中,可以在接口中,使用默认方法default R method (U u){return null;} } //在继承接口时,指定泛型接口的类型 interface IA extends IUsb<String,Double>{} //AA要实现IUsb里的所有抽象方法 class AA implements IA{@Overridepublic Double get(String s) {return null;}@Overridepublic void hi(Double aDouble) {}@Overridepublic void run(Double r1, Double r2, String u1, String u2) {}@Overridepublic Double method(String s) {return IA.super.method(s);} } class BB implements IUsb{@Overridepublic Object get(Object o) {return null;}@Overridepublic void hi(Object o) {}@Overridepublic void run(Object r1, Object r2, Object u1, Object u2) {}@Overridepublic Object method(Object o) {return IUsb.super.method(o);} }
(3)泛型方法
- 泛型方法,可以定义在普通类中,也可以定义在泛型类中
- 当泛型方法被调用时,类型会确定
- public void eat(E e) {},修饰符之后没有<T,R...>,那么eat方法不是泛型方法,而是使用了泛型
- 自定义泛型方法案例演示:
package com.study.generic.CustomGeneric;public class CustomMethodGeneric {public static void main(String[] args) {Car car = new Car();car.fly("宝马",100);//当调用方法时,传入参数,编译器就会自动确定类型} } class Car{//普通类public void run(){//普通方法}//<T,R>就是泛型标识符,是提供给fly方法使用的public <T,R> void fly(T t,R r){//泛型方法System.out.println(t.getClass());System.out.println(r.getClass());} } class Fish<T,R>{//泛型类public void run(){//普通方法}public <U,M> void eat(U u,M m){//泛型方法}//下面这个方法不是泛型方法,只是使用了泛型public void hi(T t){} }
- 泛型方法,可以使用泛型类声明的泛型,也可以使用自己在该方法声明的泛型
- 对象.getClass()获取的是带包名的类名。e.getClass().getSimpleName()获取的是不带包名的类名
三、泛型继承和通配符
- 泛型不具备继承性:
- <?>:支持任意泛型类型
- <? extends A>:支持A类以及A类的子类,规定了泛型的上限
- <? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
- 通配符的案例演示:
package com.study.generic.improve;import java.util.ArrayList; import java.util.List;public class GenericExtends {public static void main(String[] args) {//举例说明下面三个方法的使用List<Object> list1 = new ArrayList<>();List<String> list2 = new ArrayList<>();List<AA> list3 = new ArrayList<>();List<BB> list4 = new ArrayList<>();List<CC> list5 = new ArrayList<>();printCollection(list1);//okprintCollection(list2);//okprintCollection(list3);//okprintCollection(list4);//okprintCollection(list5);//ok//printCollection2(list1);报错// 因为list1的元素类型是Object,但是printCollection2要求传入的List的元素类型必须是AA或AA的子类//printCollection2(list2);报错// 因为list2的元素类型是String,但是printCollection2要求传入的List的元素类型必须是AA或AA的子类printCollection2(list3);//okprintCollection2(list4);//okprintCollection2(list5);//ok//list2、list4和list5不能传进去,因为它们的元素类型不是AA或AA的父类(不限于直接父类)printCollection3(list1);//ok//printCollection3(list2);printCollection3(list3);//ok//printCollection3(list4);//printCollection3(list5);}//编写几个方法//表示任意的泛型类型都可以接收public static void printCollection (List<?> c){for (Object object : c) {System.out.println(object);}}//表示可以接收AA以及AA的子类public static void printCollection2 (List<? extends AA> c){for (Object object : c) {System.out.println(object);}}//表示可以接收CC以及CC的父类,不限于直接父类public static void printCollection3 (List<? super AA> c){for (Object object : c) {System.out.println(object);}} } class AA{} class BB extends AA{} class CC extends BB{}
- 泛型的作业1:
- 定义一个泛型类DAO<T>,在其中定义一个Map成员变量,Map的键类型为String、值类型为T
- 分别创建以下方法(见代码)
- 定义一个User类,该类包含:private成员变量(int类型)id,age;(String类型)name
- 创建DAO类对象,分别调用这五个方法来操作User对象
- 使用Junit单元测试类进行测试
package com.study.generic.improve;import java.util.*;public class Homework01 {public static void main(String[] args) {DAO<User> dao = new DAO<>();//方法一dao.save("001",new User(1,10,"jack"));dao.save("002",new User(2,18,"king"));dao.save("003",new User(3,38,"smith"));//方法二System.out.println(dao.get("002"));//方法三dao.update("003",new User(3,58,"milan"));System.out.println(dao.get("003"));//方法四List<User> list = dao.list();System.out.println("list=" + list);//方法五dao.delete("001");} }@SuppressWarnings("all") class User{private int id;private int age;private String name;public User(int id, int age, String name) {this.id = id;this.age = age;this.name = name;}@Overridepublic String toString() {return "User{" +"id=" + id +", age=" + age +", name='" + name + '\'' +'}';} }@SuppressWarnings({"all"}) class DAO <T>{Map<String,T> map = new HashMap<>();//保存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){map.put(id,entity);}//返回map中存放的所有T对象public List<T> list(){List<T> list = new ArrayList<>();Collection<T> values = map.values();for (T t :values) {list.add(t);}return list;}//删除指定id对象public void delete(String id){map.remove(id);} }
- Junit使用方法:(1)在需要测试的方法上面加上@Test(2)点击红色小灯泡(3)选中蓝色字体
本笔记根据韩顺平零基础学Java课程学习整理而来