欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 会展 > Java基础——泛型

Java基础——泛型

2024/12/23 6:53:15 来源:https://blog.csdn.net/weixin_74019079/article/details/144347017  浏览:    关键词:Java基础——泛型

一、泛型语法

(1)泛型引入

  1. 看一个需求(1)请编写程序,在ArrayList中,添加三个Dog对象(2)Dog对象含有name和age,并输出name和age(要求使用get方法):
    //测试代码
    package com.study.generic;import java.util.ArrayList;public class Generic01 {@SuppressWarnings({"all"})public static void main(String[] args) {ArrayList arrayList = new ArrayList();arrayList.add(new Dog("旺财",10));arrayList.add(new Dog("发财",1));arrayList.add(new Dog("小黄",5));for (Object o : arrayList) {Dog dog = (Dog)o;System.out.println(dog.getName() + "-" + dog.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;}
    }
  2. 假如程序员往ArrayList里加了一只猫,再对ArrayList进行遍历的时候,因为我们进行了向下转型(Object→Dog),就会出现ClassCastException(即类型转换异常)
  3. 使用传统方法的问题分析:
    1. 不能对加入到集合ArrayList中的数据类型进行约束(不安全)
    2. 遍历的时候,需要进行类型转换,如果集合中的数据量较大,对效率有影响(如果直接写Dog会报红)

(2)泛型入门

  1. 用泛型来解决前面的问题(如果编译器发现,添加的类型不满足要求,就会报红):
    //测试代码
    package com.study.generic.improve;import java.util.ArrayList;public class Generic02 {@SuppressWarnings({"all"})public static void main(String[] args) {//使用泛型,代表这个集合只能存放Dog这种类型ArrayList<Dog> arrayList = new ArrayList<Dog>();arrayList.add(new Dog("旺财",10));arrayList.add(new Dog("发财",1));arrayList.add(new Dog("小黄",5));arrayList.add(new Cat("招财猫",8));}
    }
    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;}
    }
    class Cat{private String name;private int age;public Cat(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}
    }

  2. 在遍历的时候可以直接取出Dog类型,不需要再向下转型
  3. 泛型的好处:
    1. 编译时,编译器会检查添加元素的类型 ,提高了安全性
    2. 减少了类型转换的次数(看上面的遍历),提高了效率
    3. 不再提示编译警告

(3)泛型说明

  1. 大写的E可以理解成参数名,它的参数值可以是Integer、String、Dog......(泛型就是数据类型的数据类型)
  2. 泛型又称为参数化类型,是JDK5.0出现的新特性,解决数据类型的安全性问题
  3. 在类声明或实例化时,只要指定好具体的类型即可
  4. Java泛型可以保证如果程序在编译时没有发生警告,运行时就不会产生警告(ClassCastException)。同时,代码更加简洁、健壮
  5. 泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,或是某个方法的返回值类型,或者是参数类型
  6. 测试代码:
    package com.study.generic;public class Generic03 {public static void main(String[] args) {Person<String> person = new Person<String>("jack");Person<Integer> person2 = new Person<Integer>(100);person.f1();//class java.lang.Stringperson2.f1();//class java.lang.Integer}
    }
    class Person<E>{E name;//也就是说,这个name的类型,可以由我们在创建对象时指定//即在编译期间,就能确定E是什么类型public Person(E name) {this.name = name;}public E f(){return name;}public void f1(){System.out.println(name.getClass());}
    }
  7. 该数据类型在定义Person对象时指定,即在编译期间,编译器就能清楚地知道类里的E代表什么数据类型 

(4)泛型应用实例

  1. 类可以指定泛型,接口也可以指定泛型。如果我们想指定多个泛型,可以用逗号间隔开。<K,V>
  2. K、V、T等不代表值,而是表示类型。用任意字母都可以
  3. 泛型的实例化:创建/获取对象时,要在类名后面指定类型参数的值(类型
  4. 泛型使用举例:(1)创建三个学生对象(2)放入到HashMap和HashSet中,要求key是String name,value就是学生对象(3)使用两种方式遍历
    package com.study.generic;import java.util.*;public class Generic04 {public static void main(String[] args) {//使用HashSet添加学生对象并遍历Set<Student> set = new HashSet<Student>();set.add(new Student("john",12));set.add(new Student("mary",18));set.add(new Student("Rose",14));Iterator<Student> iterator = set.iterator();System.out.println("=====使用HashSet添加学生对象并遍历=====");while (iterator.hasNext()) {Student next =  iterator.next();System.out.println(next.name + "-" + next.age);}//使用HashMap添加学生对象并遍历Map<String, Student> map = new HashMap<String, Student>();map.put("john",new Student("john",12));map.put("mary",new Student("mary",18));map.put("Rose",new Student("Rose",14));Set<String> names = map.keySet();System.out.println("=====使用HashMap添加学生对象并遍历=====");for (String name : names) {System.out.println(name + "-" + map.get(name).age);}}
    }
    class Student{public String name;public int age;public Student(String name, int age) {this.name = name;this.age = age;}
    }

(5)泛型使用细节

  1. interface List<T> {},public class HashSet<E> {}......等等。说明:T、E只能是引用类型,不能是int、double这种基本数据类型
  2. 在指定泛型具体类型后,可以传入该类型或者其子类类型
  3. 泛型的使用形式:List<Integer> list1 = new List<Integer>();或者List<Integer> list1 = new List<>();都行
  4. 如果我们这样写List list3 = new List();默认给它的泛型E就是Object

(6)泛型课堂练习

  1. 定义Employee类(1)该类包含private成员变量name,sal,birthday,其中birthday为MyDate类对象(2)为每一个属性定义get和set方法(3)重写toString方法,输出name、sal和birthday(4)MyDate类包含: 该类包含private成员变量month,day,year;并为每个属性定义get和set方法(5)创建该类的三个对象,并把这些对象放入ArrayList集合中(ArrayList需要使用泛型来定义),对集合中元素进行排序,并遍历输出(先按照name排序,如果name相同,按照生日日期的先后排序)
    //循环比较
    //新添加的元素成为o1,后添加的元素成为o2
    //如果相减结果为负数,o1会被排到o2前面
    package com.study.generic;import java.util.ArrayList;
    import java.util.Comparator;
    import java.util.List;public class GenericExercise01 {@SuppressWarnings({"all"})public static void main(String[] args) {List<Employee> employees = new ArrayList<Employee>();employees.add(new Employee("tom",20000,new MyDate(2000,11,11)));employees.add(new Employee("jack",12000,new MyDate(2001,12,12)));employees.add(new Employee("tom",50000,new MyDate(1980,10,10)));employees.sort(new Comparator<Employee>() {@Overridepublic int compare(Employee o1, Employee o2) {//先按照name排序int resultName = o1.getName().compareTo(o2.getName());if (resultName==0){//如果name相同,按照生日日期的先后排序int resultYear = o1.getBirthday().getYear()-o2.getBirthday().getYear();if (resultYear==0){//如果出生年相同,按照出生月排序int resultMonth = o1.getBirthday().getMonth()-o2.getBirthday().getMonth();if (resultMonth==0){//如果出生月相同,按照出生日排序return o1.getBirthday().getDay()-o2.getBirthday().getDay();}else{return resultMonth;}}else{return resultYear;}}else{return resultName;}}});for (Employee employee :employees) {System.out.println(employee);}}
    }
    class 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 getMonth() {return month;}public void setMonth(int month) {this.month = month;}public int getDay() {return day;}public void setDay(int day) {this.day = day;}
    }
    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;}@Overridepublic String toString() {return "Employee{" +"name='" + name + '\'' +", sal=" + sal +", birthday=" + birthday.getYear()+"-"+birthday.getMonth()+"-"+birthday.getDay()+'}';}
    }

二、自定义泛型

(1)泛型类

  1. 自定义泛型类的注意细节:
    1. 普通成员可以使用泛型(属性、方法)
    2. 使用泛型的数组,不能初始化(因为数组的类型没有确定下来,不知道开辟多大空间)
    3. 静态方法中不能使用类的泛型(因为静态是和类相关的,在类加载时对象还没有创建)
    4. 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定的类型)
    5. 如果在创建对象时,没有指定类型,默认为Object
  2. 自定义泛型类举例:
    package com.study.generic.CustomGeneric;public class CustomGeneric_ {public static void main(String[] args) {Tiger<Double, String, Integer> tiger = new Tiger<>("john");//T→Double、R→String、M→Integertiger.setT(10.9);//正确//tiger.setT("yy");错误,因为T是Double类型Tiger g2 = new Tiger("john~~~");//因为没有传入类型,所以T、R、M都对应Objectg2.setT("yy");//成功,因为此时T的类型是Object}
    }
    //Tiger后面有泛型,所以我们就把Tiger称为自定义泛型类
    //T、R、M是泛型的标识符、一般是大写字母
    //泛型的标识符可以有多个
    class Tiger<T,R,M>{String name;R r;M m;T t;T[] ts;//使用泛型的数组在这里不能实例化//因为T的类型没有确定下来,不知道应该开辟多大空间public Tiger(String name) {this.name = name;}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;}
    }

(2)泛型接口

  1. 自定义泛型接口的注意事项:
    1. 接口中,静态成员也不能使用泛型(这个和泛型类的规定一样)
    2. 泛型接口的类型,在继承接口或者实现接口时确定
    3. 没有指定类型,默认为Object
  2. 自定义泛型接口应用实例:
    package com.study.generic.CustomGeneric;public class CustomInterfaceGeneric {
    }
    interface IUsb<U,R>{//在接口中的属性都是静态性质的int n = 10;//R r;会报错//普通方法中可以使用泛型接口,以下是三个抽象方法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;}
    }
    //在继承接口时,指定泛型接口的类型
    interface IA extends IUsb<String,Double>{}
    //AA要实现IUsb里的所有抽象方法
    class AA implements IA{@Overridepublic Double get(String s) {return null;}@Overridepublic void hi(Double aDouble) {}@Overridepublic void run(Double r1, Double r2, String u1, String u2) {}@Overridepublic Double method(String s) {return IA.super.method(s);}
    }
    class BB implements IUsb{@Overridepublic Object get(Object o) {return null;}@Overridepublic void hi(Object o) {}@Overridepublic void run(Object r1, Object r2, Object u1, Object u2) {}@Overridepublic Object method(Object o) {return IUsb.super.method(o);}
    }

(3)泛型方法

  1. 泛型方法,可以定义在普通类中,也可以定义在泛型类中
  2. 当泛型方法被调用时,类型会确定
  3. public void eat(E e) {},修饰符之后没有<T,R...>,那么eat方法不是泛型方法,而是使用了泛型
  4. 自定义泛型方法案例演示:
    package com.study.generic.CustomGeneric;public class CustomMethodGeneric {public static void main(String[] args) {Car car = new Car();car.fly("宝马",100);//当调用方法时,传入参数,编译器就会自动确定类型}
    }
    class Car{//普通类public void run(){//普通方法}//<T,R>就是泛型标识符,是提供给fly方法使用的public <T,R> void fly(T t,R r){//泛型方法System.out.println(t.getClass());System.out.println(r.getClass());}
    }
    class Fish<T,R>{//泛型类public void run(){//普通方法}public <U,M> void eat(U u,M m){//泛型方法}//下面这个方法不是泛型方法,只是使用了泛型public void hi(T t){}
    }
  5. 泛型方法,可以使用泛型类声明的泛型,也可以使用自己在该方法声明的泛型
  6. 对象.getClass()获取的是带包名的类名。e.getClass().getSimpleName()获取的是不带包名的类名

三、泛型继承和通配符

  1. 泛型不具备继承性:
  2. <?>:支持任意泛型类型
  3. <? extends A>:支持A类以及A类的子类,规定了泛型的上限
  4. <? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
  5. 通配符的案例演示:
    package com.study.generic.improve;import java.util.ArrayList;
    import java.util.List;public class GenericExtends {public static void main(String[] args) {//举例说明下面三个方法的使用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<>();printCollection(list1);//okprintCollection(list2);//okprintCollection(list3);//okprintCollection(list4);//okprintCollection(list5);//ok//printCollection2(list1);报错// 因为list1的元素类型是Object,但是printCollection2要求传入的List的元素类型必须是AA或AA的子类//printCollection2(list2);报错// 因为list2的元素类型是String,但是printCollection2要求传入的List的元素类型必须是AA或AA的子类printCollection2(list3);//okprintCollection2(list4);//okprintCollection2(list5);//ok//list2、list4和list5不能传进去,因为它们的元素类型不是AA或AA的父类(不限于直接父类)printCollection3(list1);//ok//printCollection3(list2);printCollection3(list3);//ok//printCollection3(list4);//printCollection3(list5);}//编写几个方法//表示任意的泛型类型都可以接收public static void printCollection (List<?> c){for (Object object : c) {System.out.println(object);}}//表示可以接收AA以及AA的子类public static void printCollection2 (List<? extends AA> c){for (Object object : c) {System.out.println(object);}}//表示可以接收CC以及CC的父类,不限于直接父类public static void printCollection3 (List<? super AA> c){for (Object object : c) {System.out.println(object);}}
    }
    class AA{}
    class BB extends AA{}
    class CC extends BB{}
  6. 泛型的作业1:
    1. 定义一个泛型类DAO<T>,在其中定义一个Map成员变量,Map的键类型为String、值类型为T
    2. 分别创建以下方法(见代码)
    3. 定义一个User类,该类包含:private成员变量(int类型)id,age;(String类型)name
    4. 创建DAO类对象,分别调用这五个方法来操作User对象
    5. 使用Junit单元测试类进行测试
      package com.study.generic.improve;import java.util.*;public class Homework01 {public static void main(String[] args) {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"));//方法二System.out.println(dao.get("002"));//方法三dao.update("003",new User(3,58,"milan"));System.out.println(dao.get("003"));//方法四List<User> list = dao.list();System.out.println("list=" + list);//方法五dao.delete("001");}
      }@SuppressWarnings("all")
      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;}@Overridepublic String toString() {return "User{" +"id=" + id +", age=" + age +", name='" + name + '\'' +'}';}
      }@SuppressWarnings({"all"})
      class DAO <T>{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对象public List<T> list(){List<T> list = new ArrayList<>();Collection<T> values = map.values();for (T t :values) {list.add(t);}return list;}//删除指定id对象public void delete(String id){map.remove(id);}
      }
  7. Junit使用方法:(1)在需要测试的方法上面加上@Test(2)点击红色小灯泡(3)选中蓝色字体

本笔记根据韩顺平零基础学Java课程学习整理而来 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com