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() {

}
相关推荐
FmixZA2 小时前
【香橙派开发笔记】中文界面与输入法配置
笔记
草莓熊Lotso2 小时前
《测试视角下的软件工程:需求、开发模型与测试模型》
java·c++·测试工具·spring·软件工程
报错小能手2 小时前
C++笔记(基础)string基础
开发语言·c++·笔记
卡布叻_星星4 小时前
前端JavaScript笔记之父子组件数据传递,watch用法之对象形式监听器的核心handler函数
前端·javascript·笔记
༾冬瓜大侠༿4 小时前
C语言:自定义类型——联合体和枚举
java·c语言·开发语言
yuriy.wang4 小时前
Spring IOC源码篇五 核心方法obtainFreshBeanFactory.doLoadBeanDefinitions
java·后端·spring
凸头5 小时前
解决慢SQL问题
java·mysql
脑壳疼___5 小时前
若依 springboot websocket
java·spring
aramae6 小时前
Linux开发工具入门:零基础到熟练使用(二)
linux·运维·服务器·网络·笔记