java泛型笔记

泛型入门

  • 需求
  1. 在ArrayList中,添加3个Dog对象
  2. 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());
        }
    }
  • 优点
    1. 省去了向下转型效率高
    2. 防止写程序犯错

泛型介绍

  • 理解

    1. 泛型又称参数化类型, 是jdk5.0出现的新特性,解决数据类型安全性问题
    2. 在类申明的时候制定好具体类型即可(注意是引用类型,不能是基本数据类型)
    3. java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁,健壮。
    4. 泛型的作用是:可以在类声明通过一个标识标识类中某个属性的类型,,或者是某个方法的返回值的类型,或者是参数类型
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

由此即可在编译的时候即可判断类型是否符合

泛型应用实例

  • 练习
    1. 创建3个学生对象
    2. 放入到HashMap中,要求Key是String name, Value就是学生对象
    3. 使用两种方式遍历
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 就分别占据了 KV
  • 这样当传入不是制定类型的元素时,编译器会检测到并且报错
java 复制代码
	public class HashMap<K,V> {
		
	}

泛型使用细节

  1. interface List<T> {}, public class HashSet<E> {}... 等等
    说明:T,E只能是引用类型
    判断下面语句是否正确:
java 复制代码
List<Integer> list = new ArrayList<Integer>();  // 正确
List<int> list2 = new ArrayList<int>();         // 错误,不能使用基本数据类型
  1. 在给泛型制定具体类型后,可以传入该类型或者其子类类型

  2. 泛型使用形式

java 复制代码
List<Integer> list1 = new ArrayList<Integer>();  // 完整形式
List<Integer> list2 = new ArrayList<>();         // 类型推断(钻石语法)
  1. 如果这样写:
java 复制代码
List list3 = new ArrayList();

默认给它的泛型是 [<E> E就是Object],即等价于:

java 复制代码
List<Object> list3 = new ArrayList<Object>();

泛型课堂练习

要求

  1. 该类包含:private成员变量name, sal, birthday, 其中 birthday为MyDate类的对象
  2. 为每一个属性定义getter, setter方法
  3. 重写toString方法 输出 name, sal, birthday
  4. MyDate类包含:private成员变量month,day,year;并为每一个属性定义 getter, setter方法
  5. 创建该类的 3个对象, 并把这些对象放入 ArrayList集合中(ArrayList 需使用泛型来定义),对集合中的元素进行排序,并遍历输出
  6. 排序方式:调用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方法,这样可以使代码更加简洁,同时更好维护

自定义泛型

  1. 基本语法
java 复制代码
class 类名<T, R....> {
	成员

}
  1. 细节:
    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;
    }
}
  1. 普通成员可以使用泛型
java 复制代码
class Tiger<T, R, M> {
    String name;
    R r;//属性使用到泛型
    M m;
    T t;
.....
  1. 静态是和类相关的,在类加载时,对象还没有创建,不知道类型, 所以, 如果静态方法和静态属性使用了泛型, 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);//

自定义泛型接口

  1. 基本语法
java 复制代码
interface 接口名<T, R> {
	
}
  • 注意细节
  1. 接口中,静态成员也不能使用泛型
  2. 泛型接口的类型, 在继承接口或者实现接口时确定
  3. 没有制定类型,默认为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) {

    }
}

自定义泛型方法

  1. 基本语法
java 复制代码
修饰符 <T, R...> 返回类型 方法名(参数列表) {
	
}
  • 注意细节
  1. 泛型方法, 可以定义在普通类中,也可以定义在泛型类中
  2. 当泛型方法被调用时,类型会确定
  3. public void eat(E) {} ,修饰符后没有<T,R...> , 这里的eat方法不是泛型方法 ,只是这个方法用到了泛型

应用案例

  1. 泛型方法, 可以定义在普通类中,也可以定义在泛型类中
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) {
        
    }
    
}

}
  1. 当泛型方法被调用时,类型会确定
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

  1. 不要混淆泛型方法和方法使用泛型
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());
  1. UM 没有申明,会报错
  2. 由于会进行自动装箱10会装箱为Integer , Dog 就是Dog

泛型的继承和通配符

  1. 泛型不具备继承性
  2. <?> 代表支持任意泛型类型
  3. <?extends A> 支持A类以及A类的子类, 规定了泛型的上限
  4. <?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

本章作业

  1. 编程题 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() {

}
相关推荐
腻害兔8 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社9 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
克里斯蒂亚诺更新9 小时前
ruoyi角色权限的对应关系
笔记
萧瑟余晖10 小时前
JDK 26 新特性详解
java·开发语言
马优晨10 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
a11177612 小时前
2FA 验证码生成器(github登录验证 app)
笔记·学习
维天说13 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json
破碎的南瓜13 小时前
sqli--sql注入过关笔记(1,2,3,4,5,9)
数据库·笔记·sql
Zane199414 小时前
并发 vs 并行:别再傻傻分不清了,一文讲透 Java 并发编程的第一课
java·后端
你想知道什么?14 小时前
线性回归-学习笔记
笔记·学习·线性回归