泛型入门
- 需求
- 在ArrayList中,添加3个Dog对象
- Dog对象含有name和age, 并输出name和age(要求使用getXxx())
- 传统方法
java
@SuppressWarnings({"all"})
public class Generic01 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new Dog("Brien", 2));
list.add(new Dog("Kitty", 5));
list.add(new Dog("Bruce", 6));
for (Object o : list) {
System.out.println(((Dog)o).getName() + "-" + ((Dog)o).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;
}
}
- 思考
要是加入的不再是Dog类呢?
比如不小心加入了一个Cat类
java
list.add(new Cat("Tom", 3));
存在隐患,编译器也发现不了
传统方法劣势
- 存在安全问题
java
list.add(new Cat("Tom", 3));
- 每次遍历都要向下转型, 效率低
java
for (Object o : list) {
System.out.println(((Dog)o).getName() + "-" +((Dog)o).getAge());
}
泛型方法
java
public static void main(String[] args) {
ArrayList<Dog> list = new ArrayList<Dog>();
list.add(new Dog("Brien", 2));
list.add(new Dog("Kitty", 5));
list.add(new Dog("Bruce", 6));
for (Dog dog : list) {
System.out.println(dog.getName() + "-" + dog.getAge());
}
}
- 优点
- 省去了向下转型效率高
- 防止写程序犯错
泛型介绍
-
理解
- 泛型又称参数化类型, 是jdk5.0出现的新特性,解决数据类型安全性问题
- 在类申明的时候制定好具体类型即可(注意是引用类型,不能是基本数据类型)
- java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁,健壮。
- 泛型的作用是:可以在类声明通过一个标识标识类中某个属性的类型,,或者是某个方法的返回值的类型,或者是参数类型
java
public class Generic03 {
public static void main(String[] args) {
Person<String> lfm = new Person<>("lfm");
lfm.t();
/*
理解:
上面的Person类
class Person<E> {
String s;//E标识s的数据类型, 该数据类型在定义Person对象的时候指定,即在编译期间就确定E是什么类型
public Person(String s) {
this.s = s;
}
public String f() {//返回类型使用E
return s;
}
}
*/
Person<Integer> integerPerson = new Person<>(100);
//与上面同理
}
}
class Person<E> {
E s;//E标识s的数据类型, 该数据类型在定义Person对象的时候指定,即在编译期间就确定E是什么类型
public Person(E s) {
this.s = s;
}
public E f() {//返回类型使用E
return s;
}
public void t() {
System.out.println(s.getClass());//s的运行类型
}
}
- 运行结果
java
class java.lang.String
由此即可在编译的时候即可判断类型是否符合
泛型应用实例
- 练习
- 创建3个学生对象
- 放入到HashMap中,要求Key是String name, Value就是学生对象
- 使用两种方式遍历
java
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@SuppressWarnings({"all"})
public class GenericExercise {
public static void main(String[] args) {
HashMap<String, Student> hashMap = new HashMap<>();
hashMap.put("lfm", new Student("lfm"));
hashMap.put("xkl", new Student("xkl"));
hashMap.put("zsf", new Student("zsf"));
Set<Map.Entry<String, Student>> entry = hashMap.entrySet();
for (Map.Entry<String, Student> entries : entry) {
System.out.println(entries.getKey() + "-" + entries.getValue());
}
Iterator<Map.Entry<String, Student>> iterator = entry.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Student> next = iterator.next();
System.out.println(next.getKey() + "-" + next.getValue());
}
}
}
class Student {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
- Entryset方法源码
java
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
- 我们制定的key 和 value 就分别占据了
K
和V
- 这样当传入不是制定类型的元素时,编译器会检测到并且报错
java
public class HashMap<K,V> {
}
泛型使用细节
interface List<T> {}
,public class HashSet<E> {}
... 等等
说明:T,E只能是引用类型
判断下面语句是否正确:
java
List<Integer> list = new ArrayList<Integer>(); // 正确
List<int> list2 = new ArrayList<int>(); // 错误,不能使用基本数据类型
-
在给泛型制定具体类型后,可以传入该类型或者其子类类型
-
泛型使用形式
java
List<Integer> list1 = new ArrayList<Integer>(); // 完整形式
List<Integer> list2 = new ArrayList<>(); // 类型推断(钻石语法)
- 如果这样写:
java
List list3 = new ArrayList();
默认给它的泛型是 [<E> E就是Object]
,即等价于:
java
List<Object> list3 = new ArrayList<Object>();
泛型课堂练习
要求
- 该类包含:private成员变量name, sal, birthday, 其中 birthday为MyDate类的对象
- 为每一个属性定义getter, setter方法
- 重写toString方法 输出 name, sal, birthday
- MyDate类包含:private成员变量month,day,year;并为每一个属性定义 getter, setter方法
- 创建该类的 3个对象, 并把这些对象放入 ArrayList集合中(ArrayList 需使用泛型来定义),对集合中的元素进行排序,并遍历输出
- 排序方式:调用ArrayList的sort方法, 传入Comparator对象(使用泛型), 先按照name排序,如果name相同,则按生日日期的先后排序
java
import java.util.ArrayList;
import java.util.Comparator;
@SuppressWarnings({"all"})
public class GenericExercise02 {
public static void main(String[] args) {
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee("lfm", 250, new MyDate(2005,7,21)));
employees.add(new Employee("lfm", 300, new MyDate(2005,6,21)));
employees.add(new Employee("lfm", 350, new MyDate(2005,6,20)));
System.out.println(employees);
System.out.println("====排序后====");
employees.sort(new Comparator<Employee>() {
@Override
public int compare(Employee e1, Employee e2) {
int com = e1.getName().compareTo(e2.getName());
if (com != 0) {
return e1.getName().compareTo(e2.getName());
}
return e1.getBirthday().compareTo(e2.getBirthday());
}
});
System.out.println(employees);
}
}
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;
}
@Override
public String toString() {
return "\nEmployee{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';
}
}
class MyDate implements Comparable<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 getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
@Override
public int compareTo(MyDate e) {
int year = this.year - e.getYear();
if (year != 0) {
return year;
}
int month = this.month - e.getMonth();
if (month != 0) {
return month;
}
return this.day - e.getDay();
}
}
开发经验
- 之前我写的时候,是把日期的比较也塞到了添加匿名内部类那里,这样写不利于我们后续的维护
- 让MyDate类继承Comparable接口从而在类内重写compareTo方法,这样可以使代码更加简洁,同时更好维护
自定义泛型
- 基本语法
java
class 类名<T, R....> {
成员
}
- 细节:
1.普通成员可以使用泛型
2.使用泛型的数组不能初始化
3.静态方法中不能使用类的泛型
4.泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
5.如果在创建对象时,没有指定类型,默认为Object
代码案例
java
public class CustomGeneric_ {
public static void main(String[] args) {
}
}
//1. Tiger 后面泛型,所以我们把 Tiger称为自定义泛型类
//2. T, R, M 泛型的标识符, 一般是单个大写字母
//3. 泛型的标识符可以有多个
//4. 普通成员可以使用泛型(属性, 方法)
//5. 使用泛型的数组不能初始化
class Tiger<T, R, M> {
String name;
R r;//属性使用到泛型
M m;
T t;
//因为数组在new 的时候 不能确定T的类型
T[] ts = new T[8];
public Tiger(String name, R r, M m, T t) {//构造器使用泛型
this.name = name;
this.r = r;
this.m = m;
this.t = t;
}
//静态是和类相关的,在类加载时,对象还没有创建,不知道类型
//所以, 如果静态方法和静态属性使用了泛型, JVM就无法完成初始化
public static void m1(M m) {
}
static R r2;
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;
}
}
- 普通成员可以使用泛型
java
class Tiger<T, R, M> {
String name;
R r;//属性使用到泛型
M m;
T t;
.....
- 静态是和类相关的,在类加载时,对象还没有创建,不知道类型, 所以, 如果静态方法和静态属性使用了泛型, JVM就无法完成初始化
java
//因为数组在new 的时候 不能确定T的类型
T[] ts = new T[8];
public static void m1(M m) {
}
static R r2;
练习
java
public class CustomGenericExercise {
public static void main(String[] args) {
Tiger<Double, String, Integer> tiger = new Tiger<>("john");
tiger.setT(10.9);//
//tiger.setT("yy");//
System.out.println(tiger);//
Tiger tiger2 = new Tiger("john~~");
tiger2.setT("yy");//
System.out.println("tiger2=" + tiger2);//
}
}
class Tiger<T, R, M> {
String name;
R r;//属性使用到泛型
M m;
T t;
//因为数组在new 的时候 不能确定T的类型
//T[] ts = new T[8];
public Tiger(String name) {
this.name = name;
}
public Tiger(String name, R r, M m, T t) {//构造器使用泛型
this.name = name;
this.r = r;
this.m = m;
this.t = t;
}
//静态是和类相关的,在类加载时,对象还没有创建,不知道类型
//所以, 如果静态方法和静态属性使用了泛型, JVM就无法完成初始化
//public static void m1(M m) {
//
//}
//static R r2;
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;
}
@Override
public String toString() {
return "Tiger{" +
"name='" + name + '\'' +
", r=" + r +
", m=" + m +
", t=" + t +
'}';
}
}
- 这里传入了一个
double
类型的数值, 通过SetT方法,把tiger的T
指定为double类型, 可以执行
java
tiger.setT(10.9);//
- 在前面
T
已经被定为了double
类型,这里又传入String
类型,会报错
java
tiger.setT("yy");//
- 如果把添加
"yy"
的那步代码注释掉,输出应该是10.9
java
System.out.println(tiger);//
- 这里创建了新对象, 先把
T
设置为了String
类型,所以最后输出的是yy
,name
为john~~
java
Tiger tiger2 = new Tiger("john~~");
tiger2.setT("yy");//
System.out.println("g2=" + tiger2);//
自定义泛型接口
- 基本语法
java
interface 接口名<T, R> {
}
- 注意细节
- 接口中,静态成员也不能使用泛型
泛型接口
的类型, 在继承接口
或者实现接口时确定- 没有制定类型,默认为
Object
案例
java
public class CustomInterfaceGeneric {
public static void main(String[] args) {
}
}
//实现是
interface IUSB<U, R> {
R get(U u);
int a = 10;
//U a = 10;//静态变量不能用泛型
void hi();
default R method(U u) {
return null;
}
void run(U r1, U r2, R u);
}
//建议这样写
class CC implements IUSB<Object, Object> {
@Override
public Object get(Object o) {
return null;
}
@Override
public void hi() {
}
@Override
public void run(Object r1, Object r2, Object u) {
}
}
class BB implements IUSB<Integer, Float> {
@Override
public Float get(Integer integer) {
return 0f;
}
@Override
public void hi() {
}
@Override
public void run(Integer r1, Integer r2, Float u) {
}
}
//在继承接口 指定泛型接口的类型
interface IA extends IUSB<String, Double> {
}
class AA implements IA {
@Override
public Double get(String s) {
return 0.0;
}
@Override
public void hi() {
}
@Override
public void run(String r1, String r2, Double u) {
}
}
自定义泛型方法
- 基本语法
java
修饰符 <T, R...> 返回类型 方法名(参数列表) {
}
- 注意细节
- 泛型方法, 可以定义在普通类中,也可以定义在泛型类中
- 当泛型方法被调用时,类型会确定
public void eat(E) {}
,修饰符后没有<T,R...>
, 这里的eat
方法不是泛型方法
,只是这个方法用到了泛型
应用案例
- 泛型方法, 可以定义在普通类中,也可以定义在泛型类中
java
class Car {//普通类
public void run() {//普通方法
}
//说明
//1. <T, R> 就是泛型
//2. 是提供给fly使用
public <T, R> void fly(T t, R r) {//泛型方法
}
class Fish<T, R> {
public void run() {
}
public<U, M> void eat(U u, M m) {
}
}
}
- 当泛型方法被调用时,类型会确定
java
public class CustomMethodGeneric {
public static void main(String[] args) {
Car car = new Car();
car.fly("BMW" ,100);//当调用方法时, 传入参数, 编译器就会确定类型
}
}
- 当泛型方法被调用时,类型会确定
java
class Car {//普通类
public void run() {//普通方法
}
//说明
//1. <T, R> 就是泛型
//2. 是提供给fly使用
public <T, R> void fly(T t, R r) {//泛型方法
System.out.println(t.getClass());
System.out.println(r.getClass());
}
}
-
进行了自动装箱
class java.lang.String
class java.lang.Integer
- 不要混淆泛型方法和方法使用泛型
java
class Fish<T, R> {
public void run() {
}
public<U, M> void eat(U u, M m) {
}
//说明
//1.这个方法不是泛型方法
//2. 是hi()方法使用了类声明的泛型
public void hi(T t) {
}
//泛型方法可以使用类声明的泛型,也可以使用自己声明泛型
public <K> void hello(R r, K k) {
}
}
练习
java
class Apple<T, R, M> {
public <E> void fly(E e) {
System.out.println(e.getClass().getSimpleName());
}
public void eat(U u) {}
public void run(M m) {}
}
class Dog{}
- 下面代码输出什么
java
Apple<String, Integer, Double> apple = new Apple<>();
apple.fly(10);
apple.fly(new Dog());
U
和M
没有申明,会报错- 由于会进行自动装箱10会装箱为
Integer
,Dog
就是Dog
类
泛型的继承和通配符
- 泛型不具备继承性
<?>
代表支持任意泛型类型<?extends A>
支持A类以及A类的子类, 规定了泛型的上限<?super A>
支持A类以及A类的父类, 不限于直接父类,规定了泛型的下限
java
import java.util.ArrayList;
import java.util.List;
public class GenericExtends {
public static void main(String[] args) {
Object o = new String("xx");
//这样是不允许的, 泛型没有继承性
//List<Object> list = new ArrayList<String>();
//举例说明 下面三个方法的使用
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<>();
//可以接收任意
printCollection1(list1);//
printCollection1(list2);//
printCollection1(list3);
printCollection1(list4);
printCollection1(list5);
//可以接受AA及AA的子类,规定了上限
//printCollection2(list1);//错误
//printCollection2(list2);//错误
printCollection2(list3);
printCollection2(list4);
printCollection2(list5);
//可以接受AA 及AA父类, 规定了下限
printCollection3(list1);//ok
printCollection3(list2);//false
printCollection3(list3);//ok
printCollection3(list4);//false
printCollection3(list5);//false
}
//可以接收任意
public static void printCollection1(List<?> c) {
for (Object o : c) {
System.out.println(c);
}
}
//可以接受AA及AA的子类,规定了上限
public static void printCollection2(List<? extends AA> c) {
for (Object o : c) {
System.out.println(c);
}
}
//可以接受AA 及AA父类, 规定了下限
public static void printCollection3(List<? super AA> c) {
for (Object o : c) {
System.out.println(c);
}
}
}
class AA {
}
class BB extends AA {
}
class CC extends BB {
}
泛型作业
Homework01
本章作业
- 编程题 Homework01.java
定义个泛型类DAO<T>
,在其中定义一个Map 成员变量,Map 的键为 String 类型,值为 T 类型。
分别创建以下方法:
(1)public void save(String id,T entity)
:保存 T 类型的对象到 Map 成员变量中
(2)public T get(String id)
:从map
中获取id
对应的对象
(3)public void update(String id,T entity)
:替换 map 中key为id的内容,改为 entity 对象
(4)public List<T> list()
:返回 map 中存放的所有 T 对象
(5)public void delete(String id)
:删除指定 id 对象
定义一个 User 类:
该类包含:private成员变量 (int类型) id, age;(String 类型) name。
创建DAO 类
的对象,分别调用其save、get、update、list、delete
方法来操作 User 对象,使用 Junit 单元测试类进行测试。
代码
java
import org.junit.Test;
import java.util.*;
public class Homework01 {
public static void main(String[] args) {
//
}
@Test
public void testList() {
//这里我们给 T指定类型是 User
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"));
List<User> list = dao.list();
System.out.println(list);
dao.update("003", new User(3, 59, "ml"));
System.out.println(list);
}
}
class DAO<T> {
private HashMap<String, T> map = new HashMap<>();
public void save(String id, T entity) {
map.put(id, entity);;
}
public T get(String id) {
return map.get(id);
}
public void update(String id, T entity) {
map.put(id, entity);
}
public List<T> list() {
List<T> list = new ArrayList<>();
Set<Map.Entry<String, T>> set = map.entrySet();
for (Map.Entry<String, T> entry : set) {
list.add(entry.getValue());
}
return list;
}
public void delete(String id) {
map.remove(id);
}
}
class User{
private int id;
private int age;
String name;
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
Junit使用方法
java
//测试函数
@Test
void f() {
}