java泛型

引出泛型

编写程序,在ArrayList中,添加3个dog对象,dog类含有name和age,使用get方法输出。

java 复制代码
public class Go {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(new Dog("大黄", 3));//隐式向上转型Dog放入ArrayList
        list.add(new Dog("中黄", 2));
        list.add(new Dog("小黄", 1));
        for (Object o : list) {//向上转型,将ArrayList放入Object中
            Dog dog=(Dog)o;//向下转型,将Object o放入Dog
            System.out.println(dog.getName() + "今年" + dog.getAge() + "岁了");
        }
    }
}
class Dog{
    private String name;
    private int age;
    //略
}

此时如果有一个Cat类对象(与Dog类仅名称不一样),那程序还能允许吗?

java 复制代码
public class Go {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(new Dog("大黄", 3));//隐式向上转型Dog放入ArrayList
        list.add(new Dog("中黄", 2));
        list.add(new Dog("小黄", 1));
        list.add(new Cat("狸花", 0));
        for (Object o : list) {//向上转型,将ArrayList放入Object中
            Dog dog=(Dog)o;//向下转型,将Object o放入Dog
            System.out.println(dog.getName() + "今年" + dog.getAge() + "岁了");
        }
    }
}
class Dog{
    private String name;
    private int age;
    //略
}
class Cat{
    private String name;
    private int age;
    //略
}

编写代码时系统并未报错,但在运行到语句 Dog dog=(Dog)o;时,系统便会报错ClassCastException,因此可见,这段程序只能接受Dog类而不能接受其他类。

同时,系统在运行时需多次进行转型,这在处理大量数据时无疑会拖慢系统的运行速度

这就可见这样编写代码的缺点:

1、未对程序不能处理的数据类型进行约束(不安全)

2、使用增强for循环遍历时必须将对象转型为Object,当数据量较大时,运行速度较慢(效率低)

Dog➡ArrayList➡Object➡Dog

而泛型就可以解决这两个问题

java 复制代码
        ArrayList<Dog> list = new ArrayList<Dog>();
        list.add(new Dog("大黄", 3));
        list.add(new Dog("中黄", 2));
        list.add(new Dog("小黄", 1));
        list.add(new Cat("狸花", 0));//报错
        for (Dog o : list) {
            System.out.println(o.getName() + "今年" + o.getAge() + "岁了");
        }

这两处发生了改动

java 复制代码
        //改动前------------------------------------------------------------------------------
        ArrayList list = new ArrayList();
        list.add(new Cat("狸花", 0));

        for (Object o : list) {
            Dog dog=(Dog)o;
            System.out.println(dog.getName() + "今年" + dog.getAge() + "岁了");
        }
        //改动后------------------------------------------------------------------------------
        ArrayList<Dog> list = new ArrayList<Dog>();
        //list.add(new Cat("狸花", 0));报错

        for (Dog o : list) {
            System.out.println(o.getName() + "今年" + o.getAge() + "岁了");
        }

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

2、使用增强for循环时,不再必须转型为Object,减少了类型转换的次数,提高了效率Dog➡ArrayList➡Dog

3、减少了编译时的警告,且保证了只要在编译时没有警告,运行时就不会产生ClassCastException异常

介绍

泛型又称参数化类型,它提供了编译时类型安全检查的机制,允许在编码时指定集合中对象的类型,这样可以在编译时提供更强的类型检查,可以让我们写出更加通用、可复用的代码,同时减少类型转换以及运行时错误,并提高代码的可读性和重用性。

类、接口、方法都可使用泛型,具体表现为在在类/接口后添加<E>,原本为数据类型的地方也替换为E,而E所代表的数据类型根据创建的实例而确定,代表在该区域相应的位置处,只能使用该数据类型。

java 复制代码
public class Go {
    public static void main(String[] args) {
        HashMap<String, student> myMap = new HashMap<>();
        myMap.put("万叶",new student(18,"万叶"));
        myMap.put("莫娜",new student(15,"莫娜"));
        myMap.put("仆人",new student(22,"仆人"));
        myMap.put("可莉",new student(8,"可莉"));
        Set<Map.Entry<String, student>> set = myMap.entrySet();//myMap.entrySet().var
        Iterator<Map.Entry<String, student>> iterator = set.iterator();//set.iterator().var
        while (iterator.hasNext()) {
            Map.Entry<String, student> next =  iterator.next();
            System.out.println(next.getKey()+""+next.getValue());
        }
    }
}
class student{
    private int age;
    private String name;
    //略}

注意:

1、T,E只能是引用类型,而不能是基本数据类型

java 复制代码
ArrayList<Integer> objects = new ArrayList<Integer>();//正确
ArrayList<int> objects = new ArrayList<int>();//错误

2、在给泛型指定具体类型后,可以传入该类型以及其子类类型

java 复制代码
public class Go {
    public static void main(String[] args) {
        HashSet<a> set = new HashSet<a>();
        set.add(new a());//正确,传入了指定类
        set.add(new b());//正确,传入了指定类的子类
        set.add(new c());//错误,传入了其他类
        set.add(new top());//错误,传入了指定类的父类
    }}
class top{}
class a extends top{}
class b extends a{}
class c{}

3、平时使用泛型时推荐采用简写形式,即第二个<>内不写内容,系统会自动将第一个<>内的内容补充到第二个<>内,

在我们之前创建接口类的实例时,虽然没有写<>泛型,但实际上是隐藏了泛型,相当于指定泛型为Object

java 复制代码
        HashSet hashSet = new HashSet();
        //等同于
        HashSet<Object> hashSet2 = new HashSet<>();
        //等同于
        HashSet<Object> hashSet1 = new HashSet<Object>();

例题:

1、定义Employee类,该类包含:private成员变量name、sal、birthday,其中 birthday 为 MyDate 类的对象;

2、补充相应的get、set、构造等方法;

3、MyDate类包含:private成员变量 month、day、year,并补充相关方法

4、添加三个该类对象,并将其放入ArrayList集合中(ArrayList需使用泛型来定义)

5、对集合进行排序并输出,排序规则:调用ArratList的sort方法,传入Comparator对象(使用泛型),先按照name排序,如果name相同则按照生日的年月日先后比较

java 复制代码
public class Go {
    public static void main(String[] args) {
        ArrayList<Employee> list = new ArrayList();
        list.add(new Employee("banana",8000,new MyDate(12,16,2003)));
        list.add(new Employee("apple",9999,new MyDate(03,21,1999)));
        list.add(new Employee("pear",3000,new MyDate(05,21,2002)));
        list.add(new Employee("apple",3000,new MyDate(03,22,1999)));
        list.sort(new Comparator<Employee>() {
            @Override
            public int compare(Employee employee1, Employee employee2) {
            //先调用compareTo方法,name不同则返回正/负值,相同为0,继续下个判断
                int namedif= employee1.getName().compareTo(employee2.getName());
                if(namedif!=0)
                    return namedif;
                //依次判断年月日的不同
                int yeardif=employee1.getBirthday().getYear()-(employee2.getBirthday().getYear());
                if(yeardif!=0)
                    return yeardif;
                int monthdif=employee1.getBirthday().getMonth()-(employee2.getBirthday().getMonth());
                if(monthdif!=0)
                    return monthdif;
                return employee1.getBirthday().getDay()-(employee2.getBirthday().getDay());}
        });
        Iterator<Employee> iterator = list.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next);}}}
class Employee {
    private String name;
    private float sal;
    private MyDate birthday;
    //略}
class MyDate {
    private int month;
    private int day;
    private int year;
    //略}

执行结果:

Employee{name='apple', sal=9999.0, birthday=MyDate{month=3, day=21, year=1999}}

Employee{name='apple', sal=3000.0, birthday=MyDate{month=3, day=22, year=1999}}

Employee{name='banana', sal=8000.0, birthday=MyDate{month=12, day=16, year=2003}}

Employee{name='pear', sal=3000.0, birthday=MyDate{month=5, day=21, year=2002}}

但这里的代码还可以精简一下,对年月日大小的判断方法可以封装为MyDate类的一个方法

java 复制代码
public class Go {
    public static void main(String[] args) {
        ArrayList<Employee> list = new ArrayList();
        list.add(new Employee("banana",8000,new MyDate(12,16,2003)));
        list.add(new Employee("apple",9999,new MyDate(03,21,1999)));
        list.add(new Employee("pear",3000,new MyDate(05,21,2002)));
        list.add(new Employee("apple",3000,new MyDate(03,22,1999)));
        list.sort(new Comparator<Employee>() {
            @Override
            public int compare(Employee employee1, Employee employee2) {
            //先调用compareTo方法,name不同则返回正/负值,相同为0,继续下个判断
                int namedif= employee1.getName().compareTo(employee2.getName());
                if(namedif!=0)
                    return namedif;
                //直接调用MyDate类内部重写的compareTo方法
                return employee1.getBirthday().compareTo(employee2.getBirthday());
            }
        });
        Iterator<Employee> iterator = list.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next);
        }}}
class Employee {
    private String name;
    private float sal;
    private MyDate birthday;
    //略}
class MyDate implements Comparable<MyDate>{//实现接口,并指定泛型
    private int month;
    private int day;
    private int year;
    //略
    @Override
    public int compareTo(MyDate o) {
        if (this.year != o.year) {// 比较年份
            return this.year - o.year;}
        if (this.month != o.month) {// 如果年份相同,比较月份
            return this.month - o.month;}
        return this.day - o.day;// 如果月份也相同,比较天,相同直接返回0
    }
}

泛型类

泛型类是通过类型参数化的类,可以在创建对象的时候指定具体的类型。

java 复制代码
public class Box<T> { // T代表数据类型,类似于int,可以有多个
    private T t;
    
    public void set(T t) {
        this.t = t;}
    
    public T get() {
        return t;}}

Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();

注意:

  1. 普通成员(属性,方法)可以使用泛型。
  2. 使用泛型的数组不能初始化,因为类型不确定,系统无法确定开辟多大空间,但可以先定义
  3. 静态属性/方法中不能使用类的泛型,因为静态在类加载时就已执行,但泛型的具体类型需等到创建对象时才确定,所以无法执行
  4. 泛型类的类型,是在创建对象时确定的,如果在创建对象时未指定泛型,则默认为Object
java 复制代码
class Box<T> {
    private T t;//普通成员(属性,方法)可以使用泛型。
    public T getT() {return t;}
    
    int []arr=new int[8];//正确
    T []arr1;//正确,可以定义使用泛型的数组 
    //T []arr2=new T[8];//错误,使用泛型的数组不能初始化

    public static void show(){}
    public static int num;//正常定义静态方法、属性
    public static T show2(){}//错误
    public static T num2;//错误,静态方法/属性中不能使用类的泛型}

泛型方法

泛型方法是在调用方法的时候指明具体的类型。泛型方法可以定义在普通类中也可以定义在泛型类中。

java 复制代码
public class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

注意:

  1. 泛型方法可以定义在普通类中也可以定义在泛型类中

    java 复制代码
    class AAA {//普通类
        public void show(){};//普通方法
        public <T,R> void show2(){}//泛型方法
    }
    
    class BBB<T,R>{//泛型类
        public void show(){};//普通方法
        public <T,R> void show2(){}//泛型方法
    }
  2. 泛型方法被调用时,方法类型会确定

    java 复制代码
    public class Go {
        public static void main(String[] args) {
            AAA aaa = new AAA();
            aaa.show("aaa",555);//此时T为String,R为Integer
            aaa.show(66.6,true);//此时T为Double,R为Boolean
        }}
    class AAA {
        public <T,R> void show(T t,R r){
    }}
  3. 没有指定类型,则默认为Object

  4. 例如public void go(T t,R r){}只是普通方法使用了泛型,而并非泛型方法,且泛型方法可使用泛型类提供的泛型,也可自定义泛型,亦或参杂使用

    java 复制代码
    class AAA <T,R>{//泛型类
        public void go(T t,R r){}//普通方法使用泛型
        public <T,K>void show(T t,K k){}//泛型方法,泛型类和泛型方法提供的泛型相结合
    }

泛型接口

泛型也可以应用于接口。

java 复制代码
public interface Generator<T> {
    T next();
}

注意:

  1. 接口中,静态成员同样不能使用泛型
  2. 泛型接口的类型,在继承接口或实现接口时确定
  3. 没有指定类型,则默认为Object
java 复制代码
interface usb1<T, R> {
    //,错误,静态变量,等同于public static final T num = 10;
    T num = 10;
    static T show3() {//错误,不能使用静态方法
        System.out.println("静态方法的方法体");
        return  null;}

    //正确,抽象方法(无方法体且省略abstract)
    T show1();

    //正确,普通方法(需加上default关键字)
    default public T show2(T t) {
        System.out.println("普通方法的方法体");
        return t;}}

interface usb2 extends usb1<String,String>{}//在继承接口时确定
class class2 implements usb2{//实现类
    @Override
    public String show1() {return null;}
    @Override
    public String show2(String string) {return usb2.super.show2(string);}}

interface usb3<S, D > extends usb1{}//在继承接口时未确定,则需也留下两个泛型
class class3 implements usb3<String,Double>{//实现接口时确定
    @Override
    public Object show1() {return null;}
    @Override
    public Object show2(Object o) {return usb3.super.show2(o);}}
java 复制代码
interface usb1<T,R>{ }//错误
interface usb2 extends usb1{}
class aaa implements usb2<String,Double>{//略
}

继承通配符

1、泛型不具备继承性,之前所学中我们可以将子类对象放到父类的实例中,但在泛型中这并不适用

java 复制代码
        Object str1 = new String();//正确
        ArrayList<Object> list = new ArrayList<String>();//错误

2、泛型通配符有三种

​​​ 1、 <?> - 无界通配符,表示可接收任何类型的泛型

2、<? extends Type> - 上界通配符,表类型参数必须是 Type 或 Type 的子类。

3、<? super Type> - 下界通配符,表类型参数必须是 Type 或 Type 的父类。

类型通配符一般是使用大写字母代替具体的类型参数。

java 复制代码
public class Go {

    // 使用无界通配符的方法,可以接受任何类型的List
    public static void printList(List<?> list) {
        for (Object item : list) {
            System.out.println(item);
        }
    }

    // 使用上界通配符的方法,可以接受Number或其子类型的List
    public static double sumOfList(List<? extends Number> list) {
        double sum = 0.0;
        for (Number number : list) {
            sum += number.doubleValue();
        }
        return sum;
    }

    // 使用下界通配符的方法,可以接受Integer或其超类型的List
    public static void addNumbers(List<? super Integer> list) {
        for (int i = 1; i <= 5; i++) {
            list.add(i);
        }
    }

    public static void main(String[] args) {
        // 创建不同类型的List
        List<Integer> integerList = new ArrayList<>(Arrays.asList(1, 2, 3));
        List<Double> doubleList = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0));
        List<Number> numberList = new ArrayList<>();

        // 使用无界通配符方法打印List
        System.out.println("Printing integerList:");
        printList(integerList);

        System.out.println("Printing doubleList:");
        printList(doubleList);

        // 使用上界通配符方法计算和
        System.out.println("Sum of integerList: " + sumOfList(integerList));
        System.out.println("Sum of doubleList: " + sumOfList(doubleList));

        // 使用下界通配符方法向numberList添加整数
        addNumbers(numberList);
        System.out.println("Contents of numberList after adding numbers:");
        printList(numberList); // 使用无界通配符方法打印结果
    }
}

Juit

JUnit 是一个用于 Java 编程语言的开源测试框架,它用于编写和运行可重复的测试。JUnit 提供了一种标准化的方式来编写测试用例,使开发人员能够验证他们的代码是否按预期工作。

使用方法:

首次使用,在想要测试的方法本体前添加"@Test"按Alt+Enter,选择导入5.X资源包到类路径,稍等片刻。之后直接在想要测试的方法本体前添加"@Test"按Alt+Enter,单击左侧运行按钮即可运行单个方法。

例题:定义一个泛型类dao<T>,在其中定义一个Map成员变量,Map的键为String类型,值为T类型。

分别创建以下方法:

  1. public void save(String id, T t):保存T类型的对象到Map成员变量中
  2. public T get(String id):从map中获取id对应的对象
  3. public void update(String id, T t): 替换map中key为指定id的内容,改为指定的t对象
  4. public List<T> list():返回map中存放的所有T对象
  5. public void delete(String id, T t):删除指定id对象

定义一个User类,包含id、age、name属性,及其对应的方法。

创建dao类的对象,分别调用五个方法来操作对象。

java 复制代码
public class Go {
    public static void main(String[] args) {
        dao<User> D = new dao<User>();
        D.save("001",new User(1,18,"top"));
        D.save("002",new User(2,19,"mid"));
        D.save("003",new User(3,33,"sup"));
        D.save("004",new User(4,24,"jug"));
        D.save("005",new User(5,21,"adc"));
        System.out.println("全部内容------------------------------------------------------------------------------");
        for (Object o :D.list()) {
            System.out.println(o);}
        System.out.println("User内容------------------------------------------------------------------------------");
        List<User> users = D.list();
        for (Object o :users) {
            System.out.println(o);}
        D.update("003",new  User(3,24,"faker"));
        System.out.println("修改后ID为003的选手:"+D.get("003"));
        D.delete("005",new  User(5,21,"adc"));
        System.out.println("删除后内容------------------------------------------------------------------------------");
        for (Object o :D.list()) {
            System.out.println(o);
        }}}
class dao<T> {
    private Map<String, T> map = new HashMap<>();
    public void save(String id, T t) {
        map.put(id, t);}
    public T get(String id) {
        return map.get(id);}
    public void update(String id, T t) {
        map.put(id, t);}
    public List<T> list() {
        return new  ArrayList<T>(map.values());}
    public void delete(String id, T t) {
        map.remove(id, t);}
    @Override
    public String toString() {
        return "dao{" +
                "map=" + map +
                '}';}
}
class User {
    private int id;
    private int age;
    private String name;
    //略
}
相关推荐
Kalika0-01 小时前
温度转换-C语言
c语言·开发语言·数据结构·算法
阿望要努力上研究生2 小时前
若依项目搭建(黑马经验)
java·redis·node.js·maven·管理系统
一只脑洞君2 小时前
Kubernetes(K8s)的简介
java·容器·kubernetes
zygswo2 小时前
程序猿成长之路之设计模式篇——设计模式简介
java·设计模式
qq_172805593 小时前
Go 性能剖析工具 pprof 与 Graphviz 教程
开发语言·后端·golang·go
除了代码啥也不会3 小时前
springboot项目发送邮件
java·spring boot·spring
斗-匕3 小时前
《代码重构指南:提升代码质量的关键步骤》
开发语言
Mr_Xuhhh4 小时前
数据结构阶段测试2的一点小补充
android·开发语言·汇编·数据结构·c++·算法
码农超哥同学4 小时前
Python知识点:如何使用KubeEdge与Python进行容器化边缘计算
开发语言·python·面试·编程·边缘计算