【JavaSE】泛型的基本使用

举报
兮动人 发表于 2021/12/03 09:38:53 2021/12/03
【摘要】 1. 泛型的理解和好处 1.1 看一个需求 1.2 使用传统方法的问题分析 1.3 泛型快速体验-用泛型来解决前面的问题 1.4 泛型的理解和好处 2. 泛型介绍 3. 泛型的语法 3.1 泛型的声明 3.2 泛型的实例化 3.3 泛型使用举例 4. 泛型使用的注意事项和细节 5. 泛型练习 6. 自定义泛型类 7. 自定义泛型接口 8. 自定义泛型方法 9. 泛型的继承和通配符 1. 泛...

1. 泛型的理解和好处

1.1 看一个需求

1)请编写程序,在ArrayList中,添加3个Dog对象
2) Dog对象含有name和age,并输出name和age(要求使用getXxx())

  • 先使用传统的方法来解决
public class Generic01 {
    public static void main(String[] args) {
        //使用传统的方法来解决
        ArrayList arrayList = new ArrayList();
        arrayList.add(new Dog("小黑", 2));
        arrayList.add(new Dog("小白", 4));
        arrayList.add(new Dog("小青", 6));

        //假如不小心添加了一只猫
        arrayList.add(new Cat("小花猫", 8));
        //遍历
        for (Object o : arrayList) {
            //向下转型 Object --> Dog
            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 void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = 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 void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = 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 void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
  • 运行报错,类型转换异常
    在这里插入图片描述

1.2 使用传统方法的问题分析

  1. 不能对加入到集合ArrayList中的数据类型进行约束(不安全)。
  2. 遍历的时候,需要进行类型转换,如果集合中的数据量较大,对效率有影响。

1.3 泛型快速体验-用泛型来解决前面的问题

public class Generic02 {
    public static void main(String[] args) {
        //使用泛型来解决
        //解读:
        //1.当ArrayList<Dog>表示存放到 ArrayList 集合中的元素是 Dog 类型
        //2.如果编译器发现添加的类型不满足要求,就会报错
        //3.在遍历的时候可以直接取出 Dog 类型,而不是 Object 类型
        ArrayList<Dog> arrayList = new ArrayList<Dog>();
        arrayList.add(new Dog("小黑", 2));
        arrayList.add(new Dog("小白", 4));
        arrayList.add(new Dog("小青", 6));

        //假如不小心添加了一只猫
        //arrayList.add(new Cat("小花猫", 8));
        //遍历
        for (Dog dog : arrayList) {

            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 void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = 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 void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

在这里插入图片描述

1.4 泛型的理解和好处

  1. 编译时,检查添加元素的类型,提高了安全性。如:在上面添加Cat类时就会报错
    在这里插入图片描述

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

  • 不使用泛型
Dog-加入->Object-取出-> Dog //放入到ArrayList 会先转成Object,在取出时,还需要转换成Dog
  • 使用泛型
Dog->Dog -> Dog //放入时,和取出时,不需要类型转换,提高效率
  1. 不再提示编译警告

2. 泛型介绍

  • int a = 10;
  • 理解:泛(广泛)型(类型)=> 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> person = new Person<>("兮动人");
        person.show();


    }
}

class Person<E> {
    E s ;//E表示 s 的数据类型,该数据类型是在定义Person对象的时候指定的,即在编译期间就确定了E是什么类

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

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

    public void show() {
        System.out.println(s.getClass());
    }
}

class Person1<String> {
    String s ;

    public Person1(String s) {
        this.s = s;
    }

    public String f() {
        return s;
    }
}

在这里插入图片描述

3. 泛型的语法

3.1 泛型的声明

interface接口<T>{}class<K,V>{}
//比如:List , ArrayList
说明:
1)其中,T,K,V不代表值,而是表示类型。
2)任意字母都可以。常用T表示,是Type的缩写

3.2 泛型的实例化

  • 要在类名后面指定类型参数的值(类型)。如:
  1. List<String> strList = new ArrayList<String>(); [举例说明]
  2. Iterator<Customer> iterator = customers.iterator();

3.3 泛型使用举例

  • 创建3个学生对象,放入到HashSet中学生对象使用,3个学生放入到 HashMap中,要求Key是name, Value就是学生对象,使用两种方式遍历。
public class GenericExercise {
    public static void main(String[] args) {
        System.out.println("=======HashSet方式存储========");
        //使用泛型方式给 HashSet放入三个学生对象
        HashSet<Student> students = new HashSet<>();
        students.add(new Student("xdr", 25));
        students.add(new Student("jack", 26));
        students.add(new Student("lucy", 27));

        //遍历
        for (Student student : students) {
            System.out.println(student);
        }
        System.out.println("=======HashMap方式存储========");
        //使用泛型方式给 HashMap 放入三个学生对象
        HashMap<String, Student> hashMap = new HashMap<String, Student>();
        hashMap.put("tom", new Student("tom", 20));
        hashMap.put("jerry", new Student("jerry", 21));
        hashMap.put("mike", new Student("mike", 22));

        //迭代器 EntrySet
        Set<Map.Entry<String, Student>> entries = hashMap.entrySet();
        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());
        }


    }
}

class Student {
    private String name;
    private int age;

    public Student(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 "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

在这里插入图片描述

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

  1. interface List<T>{} , public class HashSet<E>{}..说明:T,E只能是引用类型,看看下面语句是否正确?
  • 给泛型指向数据类型是要求是引用数据类型,不能是基本数据类型
List<Integer> list = new ArrayList<Integer>{};//正确
List<int> list2 = new ArrayList<int>();//错误
  1. 在给泛型指定具体类型后,可以传入该类型或者其子类类型
public class GenericDetail {
    public static void main(String[] args) {

        // 因为E指定了A类型,所以只能传入该类型或其子类类型
        Pig<A> aPig = new Pig<A>(new A());
        aPig.f();
        Pig<A> bPig = new Pig<A>(new B());
        bPig.f();


    }
}

class A {}
class B extends A{}

class Pig<E> {
    E e;

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

    public void f() {
        System.out.println(e.getClass());
    }
}

在这里插入图片描述

  1. 泛型使用形式
	ArrayList<Integer> list1 = new ArrayList<Integer>();
	List<Integer> list2 = new ArrayList<Integer>();
	//在实际开发中,往往简写。编译器会进行类型推断,推荐使用这种写法 
	ArrayList<Integer> list3 = new ArrayList<Integer>();
	List<Integer> list4 = new ArrayList<Integer>();
  1. 如果这样写List list = new ArrayList();默认给它的泛型是【<E> E 就是 Object
	ArrayList arrayList1 = new ArrayList();
	//等价于
	ArrayList<Object> arrayList2 = new ArrayList<>();

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 需使用泛型来定义),对集合中的元素进行排序,并遍历输出:
    排序方式:调用ArrayList的sort方法,传入 Comparator对象[使用泛型],先按照name排序,如果name相同,则按生日日期的先后排序。【即:定制排序】
public 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 +
                '}';
    }
}
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 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) { //把对year-month-day比较放在这里

        int yearMinus = year - o.getYear();
        if(yearMinus != 0) {
            return  yearMinus;
        }
        //如果year相同,就比较month
        int monthMinus = month - o.getMonth();
        if(monthMinus != 0) {
            return monthMinus;
        }
  
        return day - o.getDay();
    }
}
public class GenericExercise02 {
    public static void main(String[] args) {
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee("tom", 20000, new MyDate(1980,12,11)));
        employees.add(new Employee("jack", 12000, new MyDate(2001,12,12)));
        employees.add(new Employee("tom", 50000, new MyDate(1980,12,10)));

        System.out.println("employees=" + employees);


        employees.sort(new Comparator<Employee>() {
            @Override
            public int compare(Employee emp1, Employee emp2) {
                //先按照name排序,如果name相同,则按生日日期的先后排序。【即:定制排序】
                //先对传入的参数进行验证
                if(!(emp1 instanceof  Employee && emp2 instanceof Employee)) {
                    System.out.println("类型不正确..");
                    return 0;
                }
                //比较name
                int i = emp1.getName().compareTo(emp2.getName());
                if(i != 0) {
                    return i;
                }

                //下面是对birthday的比较,因此,我们最好把这个比较,放在MyDate类完成
                //封装后,将来可维护性和复用性,就大大增强.
                return emp1.getBirthday().compareTo(emp2.getBirthday());
            }
        });

        System.out.println("==对雇员进行排序==");
        System.out.println(employees);
    }
}

在这里插入图片描述

6. 自定义泛型类

  • 基本语法
class 类名 <T, R...> {// ...表示可以有多个泛型
	成员
}	
  • 注意细节
  1. 普通成员可以使用泛型(属性、方法)
  2. 使用泛型的数组,不能初始化
  3. 静态方法中不能使用类的泛型
  4. 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
  5. 如果在创建对象时,没有指定类型,默认为Object
  • 说明自定义泛型代码是否正确,并说明原因
Tiger<Double,String,Integer> g = new Tiger<>("xdr");
g.setT(10.9);//T
g.setT("yy");//F,类型不对,setT方法是Double类型的,yy是字符串类型
System.out.println(g);.//T
Tiger g2 = new Tiger("xdr~~");//T,这样写表示T、R、M 都是 Object类型
g2.setT("yy");//T
System.out.println("g2=" + g2);//T

class Tiger<T, R, M> {

	public Tiger(T t, R r, M m) {
		this.t = t;
		this.r = r;
		this.m = m;
	}
	
	public Tiger(String name) {
		this.name = name;
	}
	
}

7. 自定义泛型接口

  • 基本语法
interface 接口名<T, R...> {
}
  • 注意细节
  1. 接口中,静态成员也不能使用泛型(这个和泛型类规定一样)
  2. 泛型接口的类型,在继承接口或者实现接口时确定
  3. 没有指定类型,默认为Object
  • 应用实例
//在继承接口 指定泛型接口的类型
interface IA extends IUsb<String, Double> {

}
//当我们去实现IA接口时,因为IA在继承IUsu 接口时,指定了U 为String R为Double
//,在实现IUsu接口的方法时,使用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) {

    }
}
//没有指定类型,默认为Object
//建议直接写成 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) {

    }

}

interface IUsb<U, R> {

    int n = 10;
    //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;
    }
}

8. 自定义泛型方法

  • 基本语法
    修饰符<T,R…>返回类型 方法名(参数列表){}
  • 注意细节
    1.泛型方法,可以定义在普通类中,也可以定义在泛型类中
    2.当泛型方法被调用时,类型会确定
    3.public void eat(E e){},修饰符后没有<T,R…> eat方法不是泛型方法,而是使用了泛型
public class CustomMethodGeneric {
    public static void main(String[] args) {
        Car car = new Car();
        //当调用方法时,传入的参数,编译器就会确定类型
        car.fly("宝马", 100);
        System.out.println("==============");
        car.fly(100.1, 100);

        //测试
        // T->String  R->ArrayList
        Fish<String, ArrayList> fish = new Fish<>();
        fish.hello(new ArrayList(),1.1f);
    }
}

//泛型方法,可以定义到普通类中,也可以
class Car {
    public void run() {//普通方法

    }
    //1.<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){//泛型方法

    }
    //1.下面hi方法不是泛型方法
    //2.hi方法使用了类声明,泛型
    public void hi(T t) {

    }
    //泛型方法,可以使用类声明的泛型,也可以使用自己声明泛型
    public<K> void hello(R r, K k) {
        System.out.println(r.getClass());
        System.out.println(k.getClass());
    }
}

在这里插入图片描述

  • 练习:下面代码是否正确,如果有错误,修改正确,并说明输出什么?
public class CustomMethodGenericExercise {
    public static void main(String[] args) {
        //下面代码输出什么?
        // T->String R->Integer M->Double
        Apple<String,Integer,Double> apple = new Apple<>();
        apple.fly(10);//10会被自动装箱 Integer 10,输出 Integer
        apple.fly(new Dog());// Dog
    }
}

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){}//正确

}

class Dog {}

在这里插入图片描述

9. 泛型的继承和通配符

  • 泛型的继承和通配符说明
  1. 泛型不具备继承性
List<Object> list = new ArrayList<String>();//错误
  1. <?>:支持任意泛型类型
  2. <? extends A>:支持A类以及A类的子类,规定了泛型的上限
  3. <? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
  • 举例说明:
public class GenericExtends {
    public static void main(String[] args) {
        Object o = new String("xdr");
        //List<Object> list = new ArrayList<String>();

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

        // List<?> c ,可以接收任意类型
        printCollection1(list1);
        printCollection1(list2);
        printCollection1(list3);
        printCollection1(list4);
        printCollection1(list5);

        // list<? extends AA> 可以接受 AA或AA子类
        printCollection2(list3);
        printCollection2(list4);
        printCollection2(list5);

        // list<? super AA> 可以接受AA类以及AA的父类,不限于直接父类
        printCollection3(list1);
        printCollection3(list3);
    }

    public static void printCollection1(List<?> c) {
        for (Object object : c) { //通配符,取出时,就是Object
            System.out.println(object);
        }
    }

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

    // ? super 子类类名AA:支持AA类以及AA的父类,不限于直接父类
    //规定了泛型的下限
    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 {

}
  • 练习:
    定义个泛型类 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单元测试类进行测试。

DAO

public class DAO<T> {//泛型类
    private Map<String, T> map = new HashMap<>();

    public T get(String id) {
        return map.get(id);
    }

    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<>();
        //遍历map
        Set<String> keySet = map.keySet();
        for (String key : keySet) {
            //map.get(key) 返回就是 User对象->ArrayList
            list.add(map.get(key));//也可以直接使用本类的 get(String id)
        }
        return list;
    }

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

    public void save(String id, T entity) {//把entity保存到map
        map.put(id, entity);
    }
}

User

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

    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 + '\'' +
                '}';
    }
}
	@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,"tom"));
        dao.save("003", new User(3,20,"lucy"));

        List<User> list = dao.list();

        System.out.println("list=" + list);

        dao.update("003", new User(3, 22, "rose"));
        dao.delete("001");//删除
        System.out.println("===修改数据后====");
        list = dao.list();
        System.out.println("list=" + list);
        System.out.println("===单个获取数据===");
        System.out.println("id=003 " + dao.get("003"));

    }

在这里插入图片描述

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。