Java-Day-21( 泛型 + JUnit )

Java-Day-21

泛型

( generic )

理解和好处

  • 传统方法:

    ArrayList arrayList = new ArrayList();
    arrayList.add(new Person(1001, "AA"));
    // 若是误入一个 new Animal(.....)
    // get获取输出的时候是用Person类的,
    // 所以会报类型转换依次错误,而且错误不一定好发现
    for (Object o : arrayList) { // 语法不支持直接把Object换成Person
        Person per = (Person) o;
        System.out.println(per.getName() + "-" + per.getAge());
    }
    
    • 不能对加入到集合 ArrayList 中的数据类型进行约束(不安全)
    • 遍历的时候,需要进行类型转换,如果集合中的数据量较大,对效率有影响
  • 所以引出泛型

    ArrayList<Person> arrayList = new ArrayList<Person>();
    .....
        
    for (Person person : arrayList) { // 这样就不需要再取出时转型了
    //     ....
    }
    
    • 这样若是无意添加了其他类型的对象就会检测出来报错 ( 做了限制 )
  • 好处:

    • 编译时,检查添加元素的类型,提高了安全性
    • 减少了类型转换的次数,提高效率

介绍

  • < E > 表示某一种数据类型,具体是自行指定的,一种数据类型的 ( 可变 ) 类型

    • 设置前广泛,指定后约束 ( 有的用 < T > )
  • 泛型又称参数化类型,是 JDK5.0 出现的新特性,解决数据类型的安全性问题

  • 在类声明或实例化时只要指定好需要的具体类型即可

    • 但不能是基本数据类型,但可以用其包装类
  • Java 泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException 类型转换异常,使代码更简洁、健壮

  • 泛型的作用是:可以在类声明时通过一个标识表示类中的某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型

public class test1 {
    public static void main(String[] args) {
        Person<String> person = new Person<String>("zyz");
        Person<Integer> person2 = new Person<Integer>(12315);
//        t方法显示已创对象的运行类型
        person.t(); // class java.lang.String
        person2.t(); // class java.lang.Integer
    }
}

class Person<E> {
//    String crud; // 但有的时候想用其Integer类型
    E crud; // E表示s的数据类型,该数据类型在定义Person对象时指定,即在编译期间(定义的时候)就确定了E的类型

    public Person(E crud) { // E也可以是参数类型
        this.crud = crud;
    }
    public E f() { // 也可以是返回类型用E
        return crud;
    }
    public void t() {
        System.out.println(crud.getClass());
    }
}

使用

  • 泛型声明

    • interface 接口 < T > {}
    • class 类 <K, V ...> {} —— 需要多个泛型时
      • 其中 T、K、V 不代表值,而是表示类型
      • 任意字母都可以,常用 T ( Type )
  • 泛型的实例化

    • 要在类名后面指定类型参数的值 ( 类型 )

      List<String> strList = new ArrayList<String>();
      Iterator<Customer> iterator = customers.iterator();
      
  • 练习:创建三个学生对象

    放 HashSet 中学生对象

    放 HashMap 中,要求 Key 是 String name,Value 是学生对象

    public class test1 {
        public static void main(String[] args) {
            HashSet<Student> set = new HashSet<Student>(); // 指定了为Student
            set.add(new Student("zyz", 22));
            set.add(new Student("java", 80));
            set.add(new Student("duang", 54));
            for (Student student : set) {
                System.out.println(student);
            }
    
    //        严格规范了put存入的类型模式
            HashMap<String, Student> hashmap = new HashMap<String, Student>();
    //        已经把HashMap里所有的K和V都做了指定
            hashmap.put("zyz", new Student("zyz", 22));
            hashmap.put("java", new Student("java", 80));
            hashmap.put("duang", new Student("duang", 54));
    
    //        迭代器 EntrySet 取出的类型确定
            /*
            public Set<Map.Entry<K,V>> entrySet() {
                Set<Map.Entry<K,V>> es;
                return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
            }
            确定类型是Map.Entry<K,V>,而前面定义时已经把 K,V 都做了指定
             */
            Set<Map.Entry<String, Student>> entries = hashmap.entrySet();
    
            // .var直接填充,因为Map.Entry里K和V已经指定了
            Iterator<Map.Entry<String, Student>> iterator = entries.iterator(); // .var直接填充
        }
    }
    
    class Student<E> {
        public String name;
        public int age;
    
        public Student(String name, int age) {
            super();
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

注意事项和细节

  • 泛型指向数据类型 T、E 只能是引用类型

    • 即不能是基本数据类型
  • 在给泛型指定具体类型后,可以传入该类型或者其子类类型

    public class test1 {
        public static void main(String[] args) {
            Pig<Everything> pig = new Pig<Everything>(new Everything());
            Pig<Everything> pig1 = new Pig<Everything>(new Animal());
        }
    }
    
    
    class Everything {}
    class Animal extends Everything{}
    
  • 泛型使用形式

    • 规范型:

      ArrayList<Integer> list1 = new ArrayList<Integer>();

      List<Integer> list2 = new ArrayList<Integer>();

    • 推荐、开发时常用简写:

      ArrayList<Integer> list3 = new ArrayList<>();

      后面的<>内编译器会进行类型判断

    • 常见默认 ( 应有却没写就是 Object )

      ArrayList list4 = new ArrayList();

      • 实际就等价于:

        ArrayList<Object> list4 = new ArrayList<>();

  • Required type 所需该放的类型

  • Provided 自己放进的类型

练习

  • 定义 Employee 类
    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 需使用泛型来定义 ),对集合中的元素进行排序,并遍历输出:
      • 排序方式:调用 ArrayList 的 sort 方法,传入 Comparator 对象 [ 使用泛型 ],先按照 name 排序,如果 name 相同,则按生日日期的先后排序,即:定制排序
  • 由于代码太长,没有用到的set方法就没在此展示
// test.java
public class test {
    public static void main(String[] args) {
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee("zyz", 100, new MyDate(2000,1,1)));
        employees.add(new Employee("java", 2200, new MyDate(1000,1,1)));
        employees.add(new Employee("duang", 33300, new MyDate(1999,1,1)));
        employees.add(new Employee("duang", 33300, new MyDate(1999,2,1)));

        System.out.println(employees);


        System.out.println("sort方法:重写Comparator内的compare方法:");
        employees.sort(new Comparator<Employee>() {
            @Override
//            要求:先按照 name 排序,如果name相同,就按照生日的先后顺序
            public int compare(Employee o1, Employee o2) {
//                先对传入的参数进行验证
                if (!(o1 instanceof Employee && o2 instanceof Employee)) {
                    System.out.println("类型不匹配,返回不加以比较");
                    return 0;
                }
//                先比较名字
                int i = o1.getName().compareTo(o2.getName()); // 名字是String,自带比较方法
//                o1:java       o2:zyz (旧值)
                if (i != 0) {
//                    名字不相同
                    return i;
                }
//                把年月日的比较都放进来显得麻烦不规范
////                名字相同就比较birthday - year
//                int yearMinus = o1.getBirhday().getYear() - o2.getBirhday().getYear(); // int直接用减法即可
//                if (yearMinus != 0) {
//                    return yearMinus;
//                }
////                如果year相同,就比较 month
//                int monthMinus = o1.getBirhday().getMonth() - o2.getBirhday().getMonth();
//                ......
//                return o1.getBirhday().getDay() - o2.getBirhday().getDay();

                return o1.getBirhday().compareTo(o2.getBirhday());
            }
        });
        System.out.println(employees);
    }
}

class Employee {
    private String name;
    private double sal;
    private MyDate birhday;

    public Employee(String name, double sal, MyDate birhday) {
        this.name = name;
        this.sal = sal;
        this.birhday = birhday;
    }

    public String getName() {
        return name;
    }
    public double getSal() {
        return sal;
    }
    public MyDate getBirhday() {
        return birhday;
    }
    @Override
    public String toString() {
        return "\nEmployee{" +
                "name='" + name + '\'' +
                ", sal=" + sal +
                ", birhday=" + birhday +
                '}';
    }
}

// MyDate.java
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 getYear() {
        return year;
    }
    public int getMonth() {
        return month;
    }
    public int getDay() {
        return day;
    }
    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }

    @Override
    public int compareTo(MyDate o) {
//        名字相同就比较birthday - year
        int yearMinus = year - o.getYear(); // 本类的(想比较的) - 传进来的(被比较的)
        if (yearMinus != 0) {
            return yearMinus;
        }
//                如果year相同,就比较 month
        int monthMinus = month - o.getMonth();
        if (monthMinus != 0) {
            return monthMinus;
        }
//                month 也相同
        return day - o.getDay();
    }
}
  • 把年月日比较方法放进 MyDate.java 里,这样封装后,将来可维护性和复用性就大大增加,所需便调用 ( OOP )

    • 接口 Comparator< T > 里面的 compare(T o1, T o2) 是传入两个对象

    • 接口 Comparable< T > 里面的 compareTo(T o) 是传入的一个对象

自定义泛型

自定义泛型类

  • 基本语法

    • class 类名<T, R ...>
  • 注意细节

    • 普通成员可以使用泛型 ( 属性、方法 )

      class Animal <T, R, M> {
          String name;
          T tou;
          R rbody;
          M mleg;
      
          public Animal(String name, T tou, R rbody, M mleg) {
              this.name = name;
              this.tou = tou;
              this.rbody = rbody;
              this.mleg = mleg;
          }
      }
      
    • 使用泛型的数组,不能初始化

      //    用泛型的话,数组new初始化时不能确定类型,就无法开辟空间,就会报错
      //    T[] ts = new T[8]; // 泛型数组不能实例化,但是可以单纯定义
          T[] ts; // 可以
      
    • 静态方法中不能使用类的泛型 ( 但并非静态不能使用,细见泛型的继承部分 )

      //    静态static和类的泛型不共存
      public static void m1(M m) {
          //        因为静态是类加载(对象还未创建)就触发了,仍没法确定类型,JVM就不能初始化
          //        泛型具体是在定义时确定的
      }
      
    • 泛型类的类型,是在创建对象时确定的 ( 因为创建对象时,需要指定确定类型 )

    • 如果在创建对象时,没有指定类型,就默认 Object

  • 练习

    public class test1 {
        public static void main(String[] args) {
            Animal<Double, String, Integer> a = new Animal<>("zyz");
    //        则已经定下 T是Double,R是String,M是Integer
            a.setTdou(9.99);
    //        a.setTdou("duang"); // 报错
            System.out.println(a);
    
            Animal a2 = new Animal("java~duang");
    //        默认的话 T,R,M 都是 Object 类,都可以接收
            a2.setTdou("zyz");
            a2.setTdou(1010); // 都正确,此处替换了 zyz
            System.out.println(a2);
        }
    }
    
    // 自定义泛型类: Animal
    class Animal <T, R, M> {
        String name;
        T tdou;
        R rstr;
        M mint;
    
        public Animal(String name) {
            this.name = name;
        }
        public Animal(T tdou, R rstr, M mint) {
            this.tdou = tdou;
            this.rstr = rstr;
            this.mint = mint;
        }
        public T getTdou() {
            return tdou;
        }
        public void setTdou(T tdou) {
            this.tdou = tdou;
        }
    
        @Override
        public String toString() {
            return "Animal{" +
                    "name='" + name + '\'' +
                    ", tdou=" + tdou +
                    ", rstr=" + rstr +
                    ", mint=" + mint +
                    '}';
        }
    }
    

自定义泛型接口

  • 基本语法

    • interface 接口名<T, R ...>
  • 注意细节

    • 接口中,静态成员也不能使用泛型
    • 泛型接口的类型,在继承接口或者实现接口时确定
    • 没有指定类型,就默认 Object
  • 练习

    • 已知泛型接口
    // 自定义泛型接口: IUsb
    interface IUsb<U, R> {
        int n = 10;
    //    接口中成员都是默认静态性质的: public static final
    //    U name = "zyz";
    
    //    普通方法中,可以使用接口泛型
        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;
        }
    }
    
    • 类直接实现接口
    // 类实现泛型接口
    // U指定了Integer,R指定了Float
    class BBB 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) {  }
    }
    
    • 类间接实现指定过了的接口
    // 在继承接口时指定泛型接口的类型
    interface IA extends IUsb<String, Double> {
    //    但凡是使用了IA接口的,都在实现IUsb方法的时候: U即String R即Double
    }
    
    // 类实现指定了泛型接口的接口(继承)
    class AAA implements IA { // ctrl + I 快捷出要指定的方法
    //    方法在填入时就自动指定了先前泛型所指定类型
        @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) {  }
    }
    
    • 默认指定
    // 类默认指定泛型就是 Object,最好是默认的话也写上
    //class CCC implements IUsb<Object, Object> {}
    

自定义泛型方法

  • 基本语法

    • 修饰符 < T, R ... > 返回类型 方法名 ( 参数列表 )
  • 注意细节

    • 泛型方法,可以定义在普通类中,也可以定义在泛型类中
    • 当泛型方法被调用时,类型会确定
    • public void eat ( E e ) {},修饰符后没有 < T, R ... > eat 方法不是泛型方法,而是使用了泛型
  • 练习

    • 普通类里
    class Car { // 普通类
        public void run() {
    //        普通方法
        }
        public <T, R> void fly(T t, R r) {
    //        <T, R> 泛型方法
            System.out.println(t.getClass());
            System.out.println(r.getClass());
        }
    }
    
    • 泛型类里
    class Ship<T,R> { // 泛型类
        public void swim(T t) {
    //        注: 普通方法可以使用类声明的泛型,但并不代表是泛型方法
        }
    //    泛型方法 != 方法使用泛型
        public <U, M> void eat(U u, M m) {
    //        泛型方法,为区分最好取和类不同的表示,如: U, M
        }
    //        方法使用泛型还是看具体情况
        public <K> void dalal(R r, K k) {
    //        但是泛型方法里还是既可以使用类声明的泛型,也可以自行声明使用
            System.out.println(r.getClass());
            System.out.println(k.getClass());
        }
    }
    
    // 泛型方法里的参数要不就是类的泛型,要不就是泛型方法自带,不能凭空出现
    
    • main 方法里
    public class test {
        public static void main(String[] args) {
            Car car = new Car();
    //        调用方法时传入参数,编译器会自行确定类型如何
            car.fly("霸天虎", 101);
    //        确定了类型,控制台输出: String, Integer (会自动装箱的)
    
            
            Ship<String, ArrayList> ship = new Ship<>();
            ship.dalal(new ArrayList<>(), 0.5f);
    //        控制台输出:ArrayList, Float
        }
    }
    

泛型的继承和通配符

  • 泛型不具备继承性

    Object o = new String("zyz"); // 不会报错
    List<Object> list = new ArrayList<String>(); // 语句报错
    // 泛型里的 Object 和 String 就无继承关系 
    
  • < ? > :支持任意泛型类型

    // 已知:
    class Son extends Father{}
    class Father extends Grandpa{}
    class Grandpa {}
    
    
    // public class test 内
    //    任意泛型类型都可接收
    public static void printCollection1(List<?> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }
    
  • < ? extends A >:支持 A 类以及 A 类的子类,规定了泛型的上限

    //    定上限, 可接收 Son, Father, Grandpa
    public static void printCollection2(List<? extends Grandpa> g) {
    }
    
  • < ? super A >:支持 A 类以及 A 类的父类,不限于直接父类,规定了泛型的下限

    //    定下限, 支持 Son, Father, Grandpa
    public static void printCollection3(List<? super Father> s) {
    }
    
  • main 部分

    // public class test 内
    public static void main(String[] args) {
        List<Object> list_Object = new ArrayList<>();
        List<String> list_String = new ArrayList<>();
        List<Grandpa> list_Grandpa = new ArrayList<>();
        List<Father> list_Father = new ArrayList<>();
        List<Son> list_Son = new ArrayList<>();
    
        //        上述List都确定了泛型的类型,即可验证方法可接收的泛型类型
        printCollection1(list_Object);
        printCollection1(list_String);
        printCollection1(list_Grandpa);
        printCollection1(list_Father);
        printCollection1(list_Son); // 都可以接收
    
        //        List<? extends Grandpa> g 已知
        //        printCollection2(list_Object); // 最顶层,报错
        //        printCollection2(list_String); // 与Grandpa无关,则报错
        printCollection2(list_Grandpa);
        printCollection2(list_Father);
        printCollection2(list_Son);
    
        //        List<? super Father> s 已知
        printCollection3(list_Object);
        //        printCollection3(list_String); // 报错
        printCollection3(list_Grandpa);
        printCollection3(list_Father);
        //        printCollection3(list_Son); // 最低 Father
    }
    

练习

  • 定义个泛型类 DAO< T >,在其中定义一个 Map 成员变量,Map 的键为 String 类型,值为 T 类型。分别创建以下方法:
    • public void save(String number,T entity):保存 T 类型的对象到 Map 成员变量中
    • public T get(String number):从 map 中获取 number对应的对象
    • public void update(String number, T entity):替换 map 中 key 为 number的内容,改为 entity 对象
    • public List< T > list():返回 map 中存放的所有 T 对象
    • public void delete(String number):删除指定 number 对象
  • 定义一个 User 类:该类包含: private 成员变量 ( int 类型 ) id,age;( String 类型 ) name。
  • 创建 DAO 类的对象,分别调用其 save、get、update、list、delete 方法来操作 User 对象,使用 Junit 单元测试类进行测试。
// User.java
public 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 + '\'' +
                '}';
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

// DAO.java
public class DAO<T> {
//    String 键; T 值
    private Map<String, T> map = new HashMap<>();

    public void save(String number, T entity) {
        map.put(number, entity);
    }
    
//    get()根据id获取值
    public T get(String number) {
        return map.get(number);
    }
    
    public void update(String number, T entity) {
        map.put(number, entity);
    }
    
//    返回map中存放的所有T对象
//    遍历map[k-v],将所有的value(T entity),封装到ArrayList返回
    public List<T> list() {
        List<T> list = new ArrayList<>();
        Set<String> keySet = map.keySet();
        for (String key : keySet) {
//            list.add(map.get(key));
            list.add(get(key)); // 本类定义了
        }
        return list;
    }

    public void delete(String number) {
        map.remove(number);
    }
}

// main 所在test1.java(但没用main)
public class test1 {
    public static void main(String[] args) {  }

    @Test
    public void testList() {
        DAO<User> dao = new DAO<>();
//        指定了T为User
//        都存进了DAO里的map中
        dao.save("001", new User(1, 10, "zyz"));
        dao.save("002", new User(2, 20, "java"));
        dao.save("003", new User(3, 30, "duang"));

        List<User> list = dao.list(); // 泛型DAO里定义: ArrayList
//        map存, ArrayList调用,
//		  感觉就好似先前跟踪源码,add增加 —— 实际是调用了map的put方法
        System.out.println("list = " + list);

        dao.delete("001");
        dao.update("002", new User(2, 999, "godman"));
        list = dao.list();
        System.out.println("修改后 list = " + list);

        User user = dao.get("002");
        System.out.println("单独取出002 number的数据信息 = " + user);
    }
}
  • 使用 Junit 单元测试类进行测试。
    • image-20230513235434553

JUnit

引出

  • 一个类有很多功能代码需要测试,为了测试,就需要写入到 main 方法中
  • 如果有多个功能代码测试,就需要来回注销,切换麻烦
  • 如果可以直接运行一个方法,就方便很多,并且可以给出相关信息就好

基本介绍

  • JUnit 是一个 Java 语言的单元测试框架
  • 多数 Java 的开发环境都已经集成了 JUnit 作为单元测试的工具
posted @ 2023-05-14 00:02  朱呀朱~  阅读(14)  评论(0编辑  收藏  举报