Java 泛型
泛型
1. 泛型引入
- 不能对加入到 集合 ArrayList中的数据类型进行约束(不安全)
- 遍历的时候,需要进行类型转换,如果集合中的数据量较大,对效率有影响
package generic_;
public class Generic02_ {
public static void main(String[] args) {
// 使用泛型
// 老韩解读
// 1)当我们 ArrayList<Dog> 表示存放到 ArrayList 集合中的元素是 Dog 类型
// 2) 如果编译器发现添加的类型,不满足要求,就会报错
// 3) 在遍历的时候,可以直接取出 Dog 类型,而不是 Object
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));
for (Dog dog : arrayList) {
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 void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = 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 void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
2. 泛型语法
理解:泛(广泛)型(类型) ===> Integer、String、Dog
- 泛型又称为参数化类型,是 Jdk1.5出现的新特性,解决数据类型安全性问题
- 在类声明或实例化时,只要指定好需要的具体的类型即可
- Java 泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException 异常,同理,代码更阿吉简洁,健壮
- 泛型的作用:可以在类声明时通过一个标识表示
- 类中某个属性的类型
- 或者是某个方法的返回值的类型
- 参数类型
// E:泛型
public class ArrayList<E> extends AbstractList<E>
package generic_;
public class Generic03_ {
public static void main(String[] args) {
Person<String> stringPerson = new Person<String>("爱新觉罗LQ");
stringPerson.print_type();
}
}
class Person<E> {
E s; // E 表示 s的数据类型,该数据类型在定义 Person 对象的时候指定,即在编译期间,就确定E是什么类型
public Person(E s) { // E 也可以是参数类型
this.s = s;
}
public E f() { // 返回类型使用 E
return s;
}
public void print_type(){
System.out.println(s.getClass()); // 显示 s 的运行类型
}
}
# 运行类型
class java.lang.String
3. 泛型的声明
- inteface 接口 <T>{} 和 class 类 <K, V>{},比如:List,ArrayList
- 说明:
- 其中,T、K、V 不代表值,而是表示类型
- 任意字母都可以,常用 T 表示,是 Type 的简写
4. 泛型的实例化
要在类名后面指定类型参数的值(类型),如:
- List<String> strList = new ArrayList<String>();
- Iterator<Customer> iterator = customers.iterator();
package generic_;
public class Test1 {
public static void main(String[] args) {
// 使用泛型方式给HashSet 放入 3个学生对象
HashSet<Student> students = new HashSet<Student>();
Student<String> student1 = new Student<>("小明");
Student<String> student2 = new Student<>("小赵");
Student<String> student3 = new Student<>("小李");
students.add(student1);
students.add(student2);
students.add(student3);
// 增强 for 循环 (I)
for (Student student : students) {
System.out.println(student);
}
System.out.println("==========");
// 迭代器 (itit)
Iterator<Student> iterator = students.iterator();
while (iterator.hasNext()) {
Student next = iterator.next();
System.out.println(next);
}
System.out.println("==========");
// 使用 HashMap(k, V) 【key :String,V:Student】
HashMap<String, Student> studentHashMap = new HashMap<String, Student>();
/* public class HashMap<K,V>*/
studentHashMap.put("小明", new Student("小明"));
studentHashMap.put("小赵", new Student("小赵"));
studentHashMap.put("小李", new Student("小李"));
// 迭代器 EntrySet
/* public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new HashMap.EntrySet()) : es;
}*/
Set<Map.Entry<String, Student>> entries = studentHashMap.entrySet();
/* public final Iterator<Map.Entry<K,V>> iterator() {
return new HashMap.EntryIterator();
}*/
Iterator<Map.Entry<String, Student>> iterator1 = entries.iterator();
while (iterator1.hasNext()) {
Map.Entry<String, Student> next = iterator1.next();
// 因为它是一个entry,所以可以调用相应的方法, 这里的 Value 就是我们的学生对象了
System.out.println(next.getKey() + "-" + next.getValue());
}
}
}
class Student<E> {
E name;
public Student(E name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name=" + name +
'}';
}
}
5. 泛型使用细节
-
T、E 只能是引用类型
// 不能是基本数据类型 Type argument cannot be of primitive type
-
在指定泛型具体类型后,可以传入该类型或者其子类型
-
泛型使用形式
List<Integer> list1 = new ArrayList<Integer>(); // 可以简写 List<Integer> list2 = new ArrayList<>();
-
如果写成这样
List list3 = new ArrayList() // 默认它的泛型是 Object
6. 匿名内部类
7. 回顾 Arrays.sort() 方法
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
调用定制排序时,需要传入 2个参数:
-
待排序数组 arr
-
实现了 Comparator接口的匿名内部类,要求实现 compare() 方法
new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1 - o2; } }
-
体现 接口编程 的方式
集合冒泡 + 定制 Sort
package com.kko;
public class ArraysSortCustom {
public static void main(String[] args) {
int[] arr = {1, -1, 8, 0, 20};
bubbleSort(arr, new Comparator() { // 定制冒泡排序
@Override
public int compare(Object o1, Object o2) {
return (Integer)o1 - (Integer)o2;
}
});
System.out.println(Arrays.toString(arr));
}
// 冒泡!!!
public static void bubbleSort(int[] arr, Comparator comparator){
for (int i = arr.length - 1; i >= 1; i--) {
for (int j = 0; j < i; j++) {
// 数组排序由这个方法返回的值决定
if (comparator.compare(arr[j], arr[j + 1]) > 0){
swap(arr, j, j + 1);
}
}
}
}
public static void swap(int[] arr, int i, int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
8. 定义排序方式【⭐】
泛型课堂练习【排序方式】
- 使用 ArrayList的 sort 方法
- 传入 Comparator 对象【使用泛型】---> 定制排序
- 先按照 name 排序
- 如果 name 相同,则按照 生日日期先后排序
package com.kko;
public class Test {
@org.junit.Test
public void test(){
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee("alice", 2000, new MyDate(2000, 7, 1)));
employees.add(new Employee("zack", 5000, new MyDate(2002, 5, 4)));
employees.add(new Employee("bob", 7000, new MyDate(2001, 6, 6)));
for (Employee employee : employees) {
System.out.println(employee);
}
System.out.println();
System.out.println("对雇员进行排序!!!");
System.out.println();
Collections.sort(employees, new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
if (!(o1 != null && o2 != null)){
return 0;
}
// 比较 name
int i = o1.getName().compareTo(o2.getName());
if (i != 0){
return i;
}
// 名字相同,则继续比较!!!
int yearMinus = o1.getBirthday().getYear() - o2.getBirthday().getYear();
if (yearMinus != 0) {
return yearMinus;
}
int monthMinus = o1.getBirthday().getMonth() - o2.getBirthday().getMonth();
if (monthMinus != 0){
return monthMinus;
}
int dayMinus = o1.getBirthday().getDay() - o2.getBirthday().getDay();
return dayMinus;
}
});
for (Employee employee : employees) {
System.out.println(employee);
}
}
}
由于比较代码主要是比较 MyDate,所以将代码剪切到 MyDate 类中!!!
package com.kko;
// 实现 Comparable<T> 接口,并指定泛型 MyDate
public 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 getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
@Override
// 把对 年月日比较放在这里
public int compareTo(MyDate o) {
// 名字相同,则继续比较!!!
int yearMinus = year - o.getYear();
if (yearMinus != 0) {
return yearMinus;
}
int monthMinus = month - o.getMonth();
if (monthMinus != 0) {
return monthMinus;
}
return day - o.getDay();
}
}
package com.kko;
public class Test {
@org.junit.Test
public void test(){
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee("alice", 2000, new MyDate(2000, 7, 1)));
employees.add(new Employee("zack", 5000, new MyDate(2002, 5, 4)));
employees.add(new Employee("bob", 7000, new MyDate(2001, 6, 6)));
for (Employee employee : employees) {
System.out.println(employee);
}
System.out.println();
System.out.println("对雇员进行排序!!!");
System.out.println();
Collections.sort(employees, new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
if (!(o1 != null && o2 != null)){
return 0;
}
// 比较 name
int i = o1.getName().compareTo(o2.getName());
if (i != 0){
return i;
}
return o1.getBirthday().compareTo(o2.getBirthday());
}
});
for (Employee employee : employees) {
System.out.println(employee);
}
}
}
9. 自定义泛型类
-
基本语法
class 类名<T,,R...>{
成员;
}
注意细节:
- 普通成员可以使用泛型(属性、方法)
- 使用泛型的数组,不能初始化
- 静态方法中不能使用类的泛型 ===> 【因为静态是和类相关的,在类加载时,对象还未创建】
- 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
- 如果在创建对象时,没有指定类型,默认为 Object
package generic_;
public class CustomGeneric_ {
public static void main(String[] args) {
}
}
// 解读
// 1)Tiger 后面泛型,所以我们把 Tiger 就称为自定义泛型类
// 2)T,R,M :泛型的标识符,一般为 单个大写字母
// 3)泛型标识符可以有多个
// 4)普通成员可以使用泛型(属性,方法)
class Tiger<T, R, M> {
String name;
R r; // 属性使用到泛型
M m;
T t;
public Tiger(String name, R r, M m, T t) { // 构造器使用泛型
this.name = name;
this.r = r;
this.m = m;
this.t = t;
}
// 方法使用泛型、
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;
}
}
泛型数组不能初始化,因为它的类型还没有确定,是不知道开多大空间的
10. 自定义泛型接口【接口中的成员都是静态的】
语法:
inteface 接口名<T, R...>{
}
注意细节:
- 接口中,静态成员也不能使用泛型
- 泛型接口的类型,在 继承接口 或者 实现接口 时确定
- 没有指定类型,默认是 Object
package generic_;
public class CustomInterfaceGeneric {
public static void main(String[] args) {
}
}
/**
* 泛型接口使用的说明
* 1. 接口中,静态成员也不能使用泛型
* 2. 泛型接口的类型, 在继承接口或者实现接口时确定
* 3. 没有指定类型,默认为Object
*/
//在继承接口指定泛型接口的类型
interface IA extends IUsb<String, Double> {
}
// 当我们去实现IA 接口时,因为IA 在继承IUsb 接口时,指定了U 为String R 为 Double
// 在实现IUsb 接口的方法时,使用String 替换 U, 是Double 替换 R
class AA implements IA {
@Override
public Double get(String s) {
return null;
}
@Override
public void hi(Double aDouble) {
}
@Override
public void run(Double r1, Double r2, String u1, String u2) {
}
}
//实现接口时,直接指定泛型接口的类型
//给U 指定Integer 给R 指定了Float
//所以,当我们实现IUsb 方法时,会使用Integer 替换U, 使用Float 替换R
class BB implements IUsb<Integer, Float> {
@Override
public Float get(Integer integer) {
return null;
}
@Override
public void hi(Float aFloat) {
}
@Override
public void run(Float r1, Float r2, Integer u1, Integer u2) {
}
}
//没有指定类型,默认为Object
//建议直接写成IUsb<Object,Object>
class CC implements IUsb { //等价class CC implements IUsb<Object,Object> {
@Override
public Object get(Object o) {
return null;
}
@Override
public void hi(Object o) {
}
@Override
public void run(Object r1, Object r2, Object u1, Object u2) {
}
}
interface IUsb<U, R> {
int n = 10;
//U name; 不能这样使用 ---> 接口中成员都是静态的
//普通方法中,可以使用接口泛型
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;
}
}
- 继承接口时,指定泛型类型
- 实现接口时,指定泛型类型
- 没有指定泛型类型,默认为 Object
11. 自定义泛型方法
语法:
修饰符<T, R> 返回类型 方法名(参数列表){
}
注意细节:
-
可以定义在普通类,也可以定义在泛型类中
// 泛型方法可以定义在普通类中,也可以定义在泛型类中 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(){ // 普通方法f } public<U, M> void eat(U u, M m) { // 泛型方法 } }
-
当泛型方法被调用时,类型会确定
// 当调用方法时,传入参数,编译器就会确定类型 new Car().fly("宝马", 100); public<T, R> void fly(T t, R r){ // 泛型方法 System.out.println(t.getClass()); System.out.println(r.getClass()); }
-
public void eat(E e){},修饰符 后没有 <T, R...>,eat 方法不是泛型方法,而是使用了泛型
-
泛型方法可以使用类声明的泛型,也可以使用自己声明的泛型
课后习题:
12. 泛型继承和通配符【⭐】
- 泛型不具有继承性
- <?>:支持任意泛型
- <? extends A>:支持 A类以及 A类的子类,规定了泛型的 上限
- <? super A>:支持 A类以及A类的父类,不限于直接父类,规定了泛型的 下限
package dkdld;
public class GenericExtends {
public static void main(String[] args) {
ArrayList<Object> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
ArrayList<AA> list3 = new ArrayList<>();
ArrayList<BB> list4 = new ArrayList<>();
ArrayList<CC> list5 = new ArrayList<>();
}
// 说明:
// List<?> 表示任意的泛型类型都可以接收
public static void printCollection1(List<?> c){
for (Object o : c) {
System.out.println(o);
}
}
// ? extends AA 表示上限,可以接收 AA 或者 AA 子类
public static void printCollection2(List<? extends AA> c){
for (AA aa : c) {
System.out.println(aa);
}
}
// ? super 子类类名AA:支持 AA类以及 AA类子类的父类,不限于直接父类
// 规定了泛型的下限
public static void printCollection3(List<? super AA> c){
for (Object o : c) {
System.out.println(o);
}
}
}
class AA{
}
class BB extends AA{
}
class CC extends BB{
}
13. 课后作业
- 泛型类
- 方法中使用泛型
package home;
public class DAO<T> { // 泛型类
Map<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> ts = new ArrayList<>();
for (T t : map.values()) {
ts.add(t);
}
return ts;
}
public void delete(String id){
map.remove(id);
}
}
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;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
测试一下:
package home;
public class test {
@Test
public void test(){
// DAO<User> 确保 DAO类中的属性
//
// Map<String, T> map = new HashMap<>(); 中的 T 为 User
//
DAO<User> userDAO = new DAO<>();
userDAO.save("1", new User(1, 14, "jack"));
userDAO.save("2", new User(2, 15, "Mary"));
userDAO.save("3", new User(3, 16, "Alice"));
userDAO.save("4", new User(4, 17, "Bob"));
userDAO.save("5", new User(5, 18, "Zeus"));
List<User> list = userDAO.list();
for (User user : list) {
System.out.println(user);
}
System.out.println("====================");
userDAO.delete("1");
// 删除元素后,需要重新获取 list
list = userDAO.list();
for (User user : list) {
System.out.println(user);
}
System.out.println("====================");
userDAO.update("2", new User(77, 18, "Kobe"));
list = userDAO.list();
for (User user : list) {
System.out.println(user);
}
}
}