Java泛型详解

Java泛型详解

1. 泛型的理解与好处

1.1 泛型的引出

  1. 请编写程序,在ArrayList中,添加3个Dog对象
  2. Dog对象中含有name和age,并输出name和age(要求使用getXXX())
  3. 先使用传统的方法来解决
public class Generic01 {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add(new Dog("皮皮", 5));
        arrayList.add(new Dog("小黄", 6));
        arrayList.add(new Dog("小虎", 8));
        //遍历
        //增强for
        for (Object o : arrayList) {
            Dog dog = (Dog) o;
            System.out.println(dog.getName() + "_" + dog.getAge());
            //皮皮_5
            //小黄_6
            //小虎_8
        }
    }
}

/*
1. 请编写程序,在ArrayList中,添加3个Dog对象
2. Dog对象中含有name和age,并输出name和age(要求使用getXXX())
 */
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;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  1. 上面的程序用传统的方法解决毫无问题,但是如果我们程序员在编写程序的时候不小心添加了一只猫,
  2. 那么我们在编译的时候不会报错,但是程序运行的就会出现类型转换异常
arrayList.add(new Cat("小虎", 8));//ClassCastException
  1. 使用传统的方法会存在以下问题:
    • 不能对加入的集合ArrayList中的数据类型进行约束(不安全)
    • 遍历的时候,需要进行类型转换,如果集合中的数据量较大,对效率有影响
  2. 那么我们就可以用泛型来解决以上的问题
public class Generic02 {
    public static void main(String[] args) {
        //代码解读
        //1.当我们使用泛型 ArrayList<Dog1> 表示存放到 ArrayList 集合中的元素是 Dog1 类型
        //2.如果编译器发现添加的类型不满足要求,即不是 Dog1 类型,就会报错
        //3.在遍历的时候,可以直接取出 Dog1 类型,而不是 Object 类型
        //  不用再进行向下转型
        //4.public class ArrayList<E>{}
        ArrayList<Dog1> arrayList = new ArrayList<Dog1>();
        arrayList.add(new Dog1("皮皮", 5));
        arrayList.add(new Dog1("小黄", 6));
        arrayList.add(new Dog1("小虎", 8));
        for (Dog1 dog : arrayList) {
            System.out.println(dog.getName() + "_" + dog.getAge());
            //皮皮_5
            //小黄_6
            //小虎_8
        }
    }
}

class Dog1 {
    private String name;
    private int age;

    public Dog1(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;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

1.2 泛型的好处

  1. 编译时,检查添加元素的类型,提高了安全性

  2. 减少了类型转换的次数,提高了效率

  3. 不再提示编译警告

  4. 不使用泛型的时候:

    • Dog -加入-> Object -取出-> Dog
    • 即放入到ArrayList中会先转成Object,再取出的时候,还需要转换成Dog
  5. 使用泛型的时候:

    • Dog -> Dog -> Dog
    • 即放入和取出的时候,不需要进行类型转换,提高效率

1.3 泛型介绍

泛(广泛)型(类型)=>Integer,String,Dog

  1. 泛型又称参数化类型,是Jdk5.0出现的新特性,解决数据类型的安全性问题
  2. 在类声明或实例化时只要指定好需要的具体的类型即可。
  3. Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮
  4. 泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型。[有点难,下面用代码说明]
public class Generic03 {
    public static void main(String[] args) {
        //特别强调: E 具体的数据类型在定义 Person 对象的时候指定
        // 即在编译期间,就确定 E 是什么类型

        Person<String> person1 = new Person<String>("java");
        person1.show();//class java.lang.String
        /*可以理解为以下代码
        class Person{
            String s;
            //E表示 s 的数据类型,该数据在定义Person对象的时候指定
            //即在编译期间,就确定E是什么类型

            //E也可以是参数类型
            public Person(String s) {
                this.s = s;
            }

            //返回值类型使用E
            public String f(){
                return s;
            }
        }
         */
        Person<Integer> person2 = new Person<Integer>(168);
        person2.show();//class java.lang.Integer
        /*可以理解为以下代码
        class Person{
            Integer s;
            //E表示 s 的数据类型,该数据在定义Person对象的时候指定
            //即在编译期间,就确定E是什么类型

            //E也可以是参数类型
            public Person(Integer s) {
                this.s = s;
            }

            //返回值类型使用E
            public Integer f(){
                return s;
            }
        }
         */
    }
}

//泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,
// 或者是某个方法的返回值的类型,或者是参数类型。
class Person<E> {
    E s;
    //E表示 s 的数据类型,该数据在定义Person对象的时候指定
    //即在编译期间,就确定E是什么类型

    //E也可以是参数类型
    public Person(E s) {
        this.s = s;
    }

    //返回值类型使用E
    public E f() {
        return s;
    }

    //创建一个方法show输出显示s的运行类型
    public void show() {
        System.out.println(s.getClass());
    }
}

2. 泛型的基础语法和使用

2.1 泛型的声明

  1. interface接口{}
  2. class类<K,V>{}
  3. 说明:
    • 其中T,K,V不代表值,而是表示类型
    • 任意的字母都可以,但是我们常用T,K,V表示
    • 其中T是Type的缩写,K是Key的缩写,V是Value的缩写

2.2 泛型的实例化

  1. 要在类名后面指定类型参数的值(类型)。
  2. 例如
    • List strList = new ArrayList;
    • Iterator iterator = customer.iterator;

2.3 泛型使用举例

举例说明,泛型在HashSet,HashMap的使用情况

练习:

  1. 创建3个学生对象
  2. 放入到HashSet中学生对象使用
  3. 放入到HashMap中,要求Key是String name,Value就是学生对象
  4. 使用两种方式遍历
  5. 代码如下
public class GenericExercise {
    public static void main(String[] args) {
        //使用泛型方式给 HashSet 放入 3 个学生对象
        Set<Student> students = new HashSet<Student>();
        students.add(new Student("张飞", 18));
        students.add(new Student("关羽", 19));
        students.add(new Student("吕布", 17));

        //遍历
        for (Student student : students) {
            System.out.println(student);
            //Student{name='吕布', age=17}
            //Student{name='关羽', age=19}
            //Student{name='张飞', age=18}
        }

        //使用泛型方式给 HashMap 放入 3 个学生对象
        //K -> String V->Student
        Map<String, Student> hashMap = new HashMap<String, Student>();
        //public class HashMap<K,V>	{}
        hashMap.put("张三", new Student("张三", 20));
        hashMap.put("李四", new Student("李四", 22));
        hashMap.put("王五", new Student("王五", 21));

        //迭代器 EntrySet
        /*
        public Set<Map.Entry<K,V>> entrySet() { Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
        }
        */
        Set<Map.Entry<String, Student>> entries = hashMap.entrySet();
        /*
        public final Iterator<Map.Entry<K,V>> iterator() { return new EntryIterator();
        }
        */
        Iterator<Map.Entry<String, Student>> iterator = entries.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Student> next = iterator.next();
            System.out.println(next.getKey() + "_" + next.getValue());
            //李四_Student{name='李四', age=22}
            //张三_Student{name='张三', age=20}
            //王五_Student{name='王五', age=21}
        }
    }
}

/*
1. 创建3个学生对象
2. 放入到HashSet中学生对象使用
3. 放入到HashMap中,要求Key是String name,Value就是学生对象
4. 使用两种方式遍历
 */
class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2.4 泛型使用的注意事项和细节

  1. interface List{} , public class HashSet{}…等等

    • 说明:T、E在使用的时候只能是引用类型
  2. 看看下面语句是否正确?

    //给泛型指定具体类型只能是引用类型,不能是基本类型
    List<Integer> list = new ArrayList<Integer>();//ok
    List<int> list2 = new ArrayList<int>()://错误
    
  3. 在给泛型指定具体类型后,可以传入该类型或者其子类类型

  4. 泛型使用形式

    List<Integer> list1 = new ArrayList<Integer>();
    List<Integer> list2 = new ArrayList<>();
    
  5. 如果我们没有给泛型指定具体的类型,那么泛型默认是 Object 类型

  6. 下面简单的用代码演示

public class GenericDetail {
    public static void main(String[] args) {
        //2. 给泛型指定具体类型只能是引用类型,不能是基本类型
        List<Integer> list = new ArrayList<Integer>();//ok
        //List<int> list2=new ArrayList<int>()://错误

        //3. 在给泛型指定具体类型后,可以传入该类型或者其子类类型
        Cat<A> aCat = new Cat<A>(new A());
        aCat.show(new A());//class com.song.generic.A
        Cat<A> bCat = new Cat<A>(new B());
        bCat.show(new B());//class com.song.generic.B

        //4. 泛型使用形式
        ArrayList<Integer> list1 = new ArrayList<Integer>();
        List<Integer> list2 = new ArrayList<Integer>();
        //在实际开发中,我们往往会简写
        //编译器会自己进行推断
        ArrayList<Integer> list3 = new ArrayList<>();
        List<Integer> list4 = new ArrayList<>();

        //5. 如果我们没有给泛型指定具体的类型,那么泛型默认是 Object 类型
        ArrayList arrayList = new ArrayList();
        //等价于 ArrayList<Object> arrayList = new ArrayList<>();
        
        Pig pig = new Pig();
        /*等价于
        class Pig {
            Object e;
        
            public Pig() {
            }
        
            public Pig(Object e) {
                this.e = e;
            }
        }
         */

    }
}

class Pig<E> {
    E e;

    public Pig() {
    }

    public Pig(E e) {
        this.e = e;
    }
}

class A {
}

class B extends A {
}

class Cat<E> {
    E e;

    public Cat(E e) {
        this.e = e;
    }

    public void show(E e) {
        //打印运行类型
        System.out.println(e.getClass());
    }
}

2.5 泛型的练习题

定义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需使用泛型来定义),对集合中的元素进行排序,并遍历输出:

  6. 排序方式:调用ArrayList的sort 方法,传入Comparator对象[使用泛型],

  7. 先按照 name排序,如果name相同,则按生日日期的先后排序。【即:定制排序】

  8. 代码如下:

    • GenericExercise02类的编写
    public class GenericExercise02 {
        public static void main(String[] args) {
            ArrayList<Employee> employees = new ArrayList<>();
            employees.add(new Employee("jack", 20000, new MyDate(1995, 12, 25)));
            employees.add(new Employee("jack", 18000, new MyDate(1993, 11, 26)));
            employees.add(new Employee("john", 18000, new MyDate(1996, 12, 26)));
            System.out.println("employees =" + employees);
            //employees =[
            //Employee{name='jack', sal=20000.0, birthday=MyDate{year=1995, month=12, day=25}}, 
            //Employee{name='jack', sal=18000.0, birthday=MyDate{year=1993, month=11, day=26}}, 
            //Employee{name='john', sal=18000.0, birthday=MyDate{year=1996, month=12, day=26}}]
            System.out.println("====对雇员进行排序====");
            employees.sort(new Comparator<Employee>() {
                @Override
                public int compare(Employee em1, Employee em2) {
                    //先按照 name排序,如果name相同,则按生日日期的先后排序。【即:定制排序】
                    //1. 先对传入的参数进行验证
                    if (!(em1 instanceof Employee && em2 instanceof Employee)) {
                        System.out.println("类型不正确");
                        return 0;
                    }
    
                    //2. 比较 name
                    int i = em1.getName().compareTo(em2.getName());
                    if (i != 0) {
                        return i;
                    }
    
                    //下面是对 birthday 的比较,因此,我们最好把这个比较,放在 MyDate 类完成
                    //封装后,将来可维护性和复用性,就大大增强.
                    return em1.getBirthday().compareTo(em2.getBirthday());
    
                    /*把这段代码放入 MyDate中
                    //3. 如果name相同,就先比较 birthday - year
                    int yearMinus = em1.getBirthday().getYear() - em2.getBirthday().getYear();
                    if (yearMinus != 0) {
                        return yearMinus;
                    }
                    //4. 如果 year 相同,就比较 month
                    int monthMinus = em1.getBirthday().getMonth() - em2.getBirthday().getMonth();
                    if (monthMinus != 0) {
                        return monthMinus;
                    }
    
                    //4. 如果 year 和 month 相同,就比较 day
                    return em1.getBirthday().getDay() - em2.getBirthday().getDay();
                     */
                }
            });
            System.out.println("====排序后====");
            System.out.println(employees);
            //[
            //Employee{name='jack', sal=18000.0, birthday=MyDate{year=1993, month=11, day=26}}, 
            //Employee{name='jack', sal=20000.0, birthday=MyDate{year=1995, month=12, day=25}}, 
            //Employee{name='john', sal=18000.0, birthday=MyDate{year=1996, month=12, day=26}}]
        }
    }
    
    • Employee类的编写
    public class Employee {
        /*
        定义Employee类
        1. 该类包含:private成员变量name , sal , birthday,其中 birthday为MyDate类的对象;
        2) 为每一个属性定义 getter setter 方法;
        3) 重写 toString 方法输出name , sal , birthday
         */
        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 +
                    '}';
        }
    }
    
    • MyDate类的编写
    public class MyDate implements Comparable<MyDate> {
        /*
        MyDate类包含:private成员变量month , day , year;并为每一个属性定义getter setter 方法;
         */
        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 getMonth() {
            return month;
        }
    
        public void setMonth(int month) {
            this.month = month;
        }
    
        public int getDay() {
            return day;
        }
    
        public void setDay(int day) {
            this.day = day;
        }
    
        @Override
        public String toString() {
            return "MyDate{" +
                    "year=" + year +
                    ", month=" + month +
                    ", day=" + day +
                    '}';
        }
    
        @Override
        public int compareTo(MyDate o) {
            //3. 如果name相同,就先比较 birthday - year
            int yearMinus = this.year - o.getYear();
            if (yearMinus != 0) {
                return yearMinus;
            }
            //4. 如果 year 相同,就比较 month
            int monthMinus = this.month - o.getMonth();
            if (monthMinus != 0) {
                return monthMinus;
            }
    
            //4. 如果 year 和 month 相同,就比较 day
            return day - o.getDay();
        }
    }
    

3. 自定义泛型

3.1 自定义泛型基本语法和注意细节

  1. 自定义泛型基本语法
class 类名<T,R...>{//…表示可以有多个泛型
    成员
}
  1. 自定义语法注意细节

    1. 普通成员可以使用泛型(属性、方法)
    2. 使用泛型的数组,不能初始化
    3. 静态方法中不能使用类的泛型
    4. 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
    5. 如果在创建对象时,没有指定类型,默认为Object
  2. 应用案例

  • 阅读以下代码
class Tiger<T,R,M>{
    String name;
    T t;
    R r;
    M m;
}
  • 代码解读:
public class CustomGenerics_ {
    public static void main(String[] args) {
    }
}

//代码解读
//1. Tiger后面有泛型,所以我们把 Tiger 称为自定义泛型
//2. T,R,M 泛型的标识符,一般是单个大写字母
//3. 泛型的标识符可以有多个
//4. 普通成员可以使用泛型(属性、方法)
//5. 使用泛型的数组,不能初始化
//6. 静态方法中不能使用类的泛型
class Tiger<T, R, M> {
    String name;
    T t;//属性使用泛型
    R r;
    M m;

    //因为数组在new的时候,不能确定T的类型,就无法在内存开空间
    //T[] ts = new T[8];//不能初始化

    public Tiger(String name, T t, R r, M m) {//构造器使用泛型
        this.name = name;
        this.t = t;
        this.r = r;
        this.m = m;
    }

    //因为静态是和类相关的,在类加载的时候,对象还没创建
    //所以如果静态方法和静态属性使用了泛型,JVM就无法完成初始化
    /*不允许使用
    static R r2;
    public static void show(M m){}
    */

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public T getT() {//方法使用泛型
        return t;//返回类型使用泛型
    }

    public void setT(T t) {
        this.t = t;
    }

    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;
    }
}
  1. 判断以下代码是否正确
//class Tiger<T, R, M> {}
Tiger<Double,String,Integer> g = new Tiger<>("john"); 
//其中 T = Double,R = String,M = Integer;
g.setT(10.9); //OK
g.setT("yy"); //错误,类型不对
//因为"yy"为String类型,T为Double类型
System.out.println(g);
Tiger g2 = new Tiger("john~~");//OK
//g2的泛型的类型都是Object 
g2.setT("yy"); //OK
//因为g2泛型类型都是Object,"yy"是String类型
//String类型是Object类型的子类
System.out.println("g2=" + g2);

3.2 自定义泛型接口

  1. 基础语法
interface 接口名<T,R...>{}
  1. 注意细节

    1. 接口中,静态成员也不能使用泛型(这个和泛型类规定一样)
    2. 泛型接口的类型,在继承接口或者实现接口时确定
    3. 没有指定类型,默认为Object
  2. 应用实例

    下面举例说明自定义泛型接口的使用

    public class CustomInterfaceGeneric {
        public static void main(String[] args) {
    
        }
    }
    
    /*
        泛型接口使用的说明
        1. 静态成员也不能使用泛型
        2. 泛型接口的类型,在继承接口或者实现接口时确定
        3. 没有指定类型,默认为Object
     */
    
    interface IUsb<U, R> {
    
        int n = 10;
        //1. 静态成员也不能使用泛型
        //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;
        }
    }
    
    //2. 泛型接口的类型,在继承接口或者实现接口时确定
    
    //在继承接口 指定泛型接口的类型
    interface IA extends IUsb<String, Double> {
    }
    
    //因为接口 IA 继承了 接口 IUSB,
    //所以AA类必须要重写所有接口IUSB里面的方法
    //又因为接口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) {
    
        }
    }
    
    //3. 没有指定类型,默认为Object
    
    //没有指定类型,默认为Object
    //建议直接写成 class CC implements 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) {
    
        }
    }
    

3.3 自定义泛型方法

  1. 基础语法
修饰符<T,R..>返回类型 方法名(参数列表){}
  1. 注意细节

    1. 泛型方法,可以定义在普通类中,也可以定义在泛型类中
    2. 当泛型方法被调用时,类型会确定
    3. public void eat(E e){},修饰符后没有<TR…> eat方法不是泛型方法,而是使用了泛型
    4. 泛型方法可以使用类声明的泛型,也可以使用自己声明的泛型
    5. 代码简单说明:
    public class CustomMethodGeneric {
        public static void main(String[] args) {
            //2. 当泛型方法被调用时,类型会确定
            Car car = new Car();
            car.fiy("保时捷", 1000);//当调用方法时,传入参数,编译器就会确定类型
            //class java.lang.String
            //class java.lang.Integer
            System.out.println("=====================");
            car.fiy(168, 12.8);//当调用方法时,传入参数,编译器就会确定类型
            //class java.lang.Integer
            //class java.lang.Double
    
            //测试
            Fish<String, ArrayList> fish = new Fish<>();
            fish.tell(12.2, new ArrayList());
            //class java.lang.Double
            //class java.util.ArrayList
        }
    }
    /*
     泛型方法的使用:
     1. 泛型方法,可以定义在普通类中,也可以定义在泛型类中
     2. 当泛型方法被调用时,类型会确定
     3. 修饰符后没有<TR..> 的方法不是泛型方法,而是使用了泛型
     4. 泛型方法可以使用类声明的泛型,也可以使用自己声明的泛型
     */
    //1. 泛型方法,可以定义在普通类中,也可以定义在泛型类中
    
    class Car {
        public void run() {//普通方法
        }
    
        //1. <T,R>就是泛型
        //2. 提供给fly使用的
        public <T, R> void fiy(T t, R r) {//泛型方法
            //这里会自动装箱
            System.out.println(t.getClass());//String
            System.out.println(r.getClass());//Integer
        }
    }
    
    class Fish<T, R> {//泛型类
    
        public void run() {//普通方法
        }
    
        public <U, M> void eat(U u, M m) {//泛型方法
    
        }
    
        //3. 修饰符后没有<TR..> 的方法不是泛型方法,而是使用了泛型
        //说明
        //1. 下面的say方法不是泛型方法
        //2. 而是say方法使用了类声明的泛型
        public void say(T t) {
        }
    
        //4. 泛型方法可以使用类声明的泛型,也可以使用自己声明的泛型
        public <K> void tell(K k, R r) {
            System.out.println(k.getClass());
            System.out.println(r.getClass());
        }
    }
    
  2. 引用案例

    下面代码是否正确,如果有错误,修改正确,并说明输出了什么?

    class Apple<T,R,M>{
        public <E> void fly(E e){
            System.out.println(e.getClass().getSimpleName());
        }
        public void eat(U u){}//错误,因为U没有声明
        public void run(M m){}//OK
    }
    class Dog{}
    //下面代码输出了什么?
    Apple<String, Integer, Double> apple = new Apple<>();
    apple.fly(10);//自动装箱 Integer10,输出Integer
    apple.fly(new Dog());//Dog
    

4. 泛型的继承和通配符

4.1 泛型的继承和通配符说明

  1. 泛型不具备继承性

    List<Object> list=new ArrayList<String>();// 对吗?答:这是不可以的,泛型没有继承性
    
  2. <?>:支持任意泛型类型
  3. <?extendsA>:支持A类以及A类的子类,规定了泛型的上限
  4. <?superA>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
  5. 代码简单演示

public class GenericExtends {
    public static void main(String[] args) {
        Object o = new String();
        //泛型没有继承性
        //List<Object> list=new ArrayList<String>();

        //举例说明下面三个方法的使用
        List<Object> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        List<A> list3 = new ArrayList<>();
        List<IB> list4 = new ArrayList<>();
        List<IC> list5 = new ArrayList<>();

        //如果是 List<?> c ,可以接受任意的泛型类型
        printCollection1(list1);
        printCollection1(list2);
        printCollection1(list3);
        printCollection1(list4);
        printCollection1(list5);

        //List<? extends AA> c: 表示 上限,可以接受 A 或者 A 子类
        //printCollection2(list1);//×
        //printCollection2(list2);//× 
        printCollection2(list3);//√ 
        printCollection2(list4);//√ 
        printCollection2(list5);//√

        //List<? super A> c: 支持 A 类以及 A 类的父类,不限于直接父类
        printCollection3(list1);//√
        //printCollection3(list2);//× 
        printCollection3(list3);//√
        //printCollection3(list4);//×
        //printCollection3(list5);//×

    }

    // ? extends AA 表示 上限,可以接受 AA 或者 AA 子类
    public static void printCollection2(List<? extends A> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }

    //说明: List<?> 表示 任意的泛型类型都可以接受
    public static void printCollection1(List<?> c) {
        for (Object object : c) { // 通配符,取出时,就是 Object 
            System.out.println(object);
        }
    }

    // ? super 子类类名 AA:支持 AA 类以及 AA 类的父类,不限于直接父类,
    //规定了泛型的下限
    public static void printCollection3(List<? super A> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }
}

class A {
}

class IB extends A {
}

class IC extends IB {
}

4.2 JUnit

4.2.1 为什么需要JUnit
  1. 一个类有很多功能代码需要测试,为了测试,就需要写入到main方法中
  2. 如果有很多个功能代码测试,就需要来回注销,切换很麻烦
  3. 如果可以直接运行一个方法,并且可以给出相关信息,就会方便很多
  4. 在java中就有一个JUnit测试框架
4.2.2 JUnit基本介绍
  1. JUnit是一个Java语言的单元测试框架
  2. 多数Java的开发环境都已经集成了JUnit作为单元测试的工具
  3. 下面进行JUnit代码测试的演示
public class JUnit_ {
    public static void main(String[] args) {
        //用传统的方法调用
        //new JUnit_().m1();
        //new JUnit_().m2();
    }

    //在你想要测试的方法上输出 @Test
    //然后按 Alt + Enter 选择JUnit5版本
    //接着等待导入完成就可以使用JUnit进行测试了
    @Test
    public void m1() {
        System.out.println("m1方法被调用");
    }

    @Test
    public void m2() {
        System.out.println("m2方法被调用");
    }
}

4.3 练习

  1. 定义个泛型类 DAO,在其中定义一个Map 成员变量,Map的键为 String类型,值为T类型

  2. 分别创建以下方法:
    (1) public void save(String idTentity):保存T类型的对象到 Map 成员变量中

    (2) public T get(Stringid):从map 中获取id 对应的对象

    (3) public void update(String idTentity):替换map 中key为id的内容,改为entity对象

    (4) public List list():返回 map 中存放的所有T对象

    (5) public void delete(String id):删除指定id 对象

  3. 定义一个 User 类:

    该类包含:private成员变量(int类型)id,age; (String类型)name。

    创建 DAO 类的对象,分别调用其 save、get、update、list、delete 方法来操作 User 对象。

    使用 Junit 单元测试类进行测试。

public class Homework01 {
    public static void main(String[] args) {
    }

    @Test
    public void testList() {
        DAO<User> userDAO = new DAO<>();
        userDAO.save("1", new User(1, 22, "jack"));
        userDAO.save("2", new User(2, 18, "lucy"));
        userDAO.save("3", new User(3, 28, "duck"));
        List<User> list = userDAO.list();
        System.out.println("list =" + list);
        //输出 list =[User{id=1, age=22, name='jack'},
        //  User{id=2, age=18, name='lucy'},
        //  User{id=3, age=28, name='duck'}]

        userDAO.update("1", new User(1, 19, "lily"));//修改
        userDAO.delete("2");//删除

        System.out.println("===修改后===");
        list = userDAO.list();//重新获取一下list
        System.out.println("list =" + list);
        //输出 list =[User{id=1, age=19, name='lily'},
        //      User{id=3, age=28, name='duck'}]

        //单独获取一个
        System.out.println("id = 1" + userDAO.get("1"));
        //输出 id = 1User{id=1, age=19, name='lily'}
    }
}

class DAO<T> {
    private Map<String, T> map = new HashMap<>();

    //保存T类型的对象到 Map 成员变量中
    public void save(String id, T entity) {
        map.put(id, entity);
    }

    //从map 中获取id 对应的对象
    public T get(String id) {
        return map.get(id);
    }

    //替换map 中key为id的内容,改为entity对象
    public void update(String id, T entity) {
        map.put(id, entity);
    }

    //返回 map 中存放的所有T对象
    //遍历map[k,v],将map的所有value(T,entity)
    //然后封装到ArrayList返回即可
    public List<T> list() {
        List<T> list = new ArrayList<>();
        Set<String> strings = map.keySet();
        for (String key : strings) {
            list.add(get(key));
            //直接调用了上面的get方法
            //也可以写成 m
            // ap.get(key)
        }
        return list;
    }

    //删除指定id 对象
    public void delete(String id) {
        map.remove(id);
    }
}
/*
1. 定义个泛型类 DAO<T>,在其中定义一个Map 成员变量,Map的键为 String类型,值为T类型

2. 分别创建以下方法:
   (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 对象

3. 定义一个 User 类:

   该类包含:private成员变量(int类型)id,age; (String类型)name。

   创建 DAO 类的对象,分别调用其 save、get、update、list、delete 方法来操作 User 对象。

   使用 Junit 单元测试类进行测试。
 */

//第一步: 先定义User对象
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;
    }

    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;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dominator945

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值