java 中的泛型

泛型

1.泛型的作用

元素的类型不确定,其他部分是确定的,例如关于这个元素如何保存,如何管理是确定的,因此把元素的类型设计成一个参数,这个类型参数叫做泛型。

其他说明

  • 泛型,允许在定义类和接口的时候,通过一个标识标识类中某个属性的类型或者是某个方法的返回值及参数类型,这个类型参数将在使用时确定(传入实际的类型参数,也称类型实参)。
  • 从JDK1.5以后,Java引入了“参数化类型”的概念,在创建集合时再指定集合元素的类型,例如List<String>

当创建和声明对象时传入类型实参之后,类中含有泛型的方法和属性中的类型实参也都确定。

public class Generic {

    @Test
    public void test() {
        // 没有使用泛型之前,存储的类型是 Object
        ArrayList list = new ArrayList();

    }
    @Test
    public void test1() {
        // 泛型参数为包装类 Object的子类
        ArrayList<String> list = new ArrayList();
//        ArrayList<String> list1 = new ArrayList<String>();
        list.add("Tom");
        list.add("Jerry");
        list.add("John");
        list.add("DingJun");

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String stuName = iterator.next();
            System.out.println(stuName);
        }


    }
    // 集合中的HashMap泛型
    @Test
    public void test2() {
        Map<String,Integer> map = new HashMap<String,Integer>();
        map.put("Tom",12);
        map.put("Jerry",20);
        map.put("John",8);
        map.put("Keli",10);
//        map.put(12,12);
         // 泛型的嵌套
        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();

        while (iterator.hasNext()) {
            Map.Entry<String, Integer> next = iterator.next();
            System.out.println(next.getKey() + "-----" + next.getValue());
        }
    }
}

2. 自定义泛型

2.1 举例

public class Order<T> {
    String orderName;
    int orderId;

    // 类的内部结构可以使用类的泛型
    T orderT;

    public Order() {};

    // 以下方法都不是泛型方法
    public Order(String orderName,int orderId,T orderT) {
        this.orderId = orderId;
        this.orderName = orderName;
        this.orderT = orderT;
    }

    public T getOrderT() {
        return orderT;
    }

    public void setOrderT(T orderT) {
        this.orderT = orderT;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderName='" + orderName + '\'' +
                ", orderId=" + orderId +
                ", orderT=" + orderT +
                '}';
    }
}

public class GenericTest {
    @Test
    public void tets() {
        // 如果定义了泛型类,实例化没有指定类的泛型,则认为此泛型类型为Object类型
        // 要求:建议在实例化时指明类的泛型
        Order order = new Order();
        order.setOrderT(123);
        System.out.println(order);
        order.setOrderT("Tom");
        System.out.println(order);

        Order<String> order1 = new Order<String>("OrderAA",123,"DingJun");
        order.setOrderT("AA:hello");
    }
}

2.2 泛型方法

  • 泛型方法与泛型类不同的:类型参数(尖括号)是写在返回值前面的,<T>中的 T 被称为类型参数,而方法中的T被称为参数化类型,它不是真正运行时真正的参数(被指定的类型才是真正的参数)。

  • 声明的类型参数可以当作返回值

  • 泛型类中的类型参数与泛型方法中的类型参数是没有联系的,泛型方法始终以自己定义的参数为准。

public class Test1 {
    public <T> T testMethod(T t) {
        return t;
    }
    public void test1() {
        Test1 test = new Test1();
        System.out.println(test.testMethod("ILoveYou"));
    }
}
public class OrderTest2 {
    public static void main(String[] args) {
        Integer[] arr = new Integer[]{1,2,3,4};
        GenericTest test = new GenericTest();
        List<Integer> list  = test.copyFromArrayToList(arr);

        System.out.println(list);
    }
}

class GenericTest {
    public static <E> List<E> copyFromArrayToList(E[] arr) {
        ArrayList<E> list = new ArrayList<>();

        for(E e : arr) {
            list.add(e);
        }
        return list;
    }
}

2.3 自定义泛型类泛型接口注意点

  • 泛型可有多个参数 <E1,E2,E3>

  • 泛型类的构造器public GenericClass() { }

  • 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致

  • 泛型不同的引用不能相互赋值。

  • 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object

  • 泛型的指定中不能使用基本数据类型,可以使用包装类替换

  • 静态方法中不能使用类的泛型(类声明的泛型)

  • 异常类不可以时泛型的


不能使用new E[],但是E[] elements = (E[]) new Object[capacity]

OrderTest 类

public class OrderTest<T> {
    String orderName;
    int orderId;

    T orderT;

    public OrderTest() {
        // 创建 T 类型的数组
        // 编译不通过
//        T [] arr = new T[10];
        // 编译通过
        T[] arr =(T[]) new Object[10];
    }

    public void show() {
        // 编译不通过
        /*try{

        }catch (T t) {

        }*/
    }

}

2.4 在继承中使用泛型

  • 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型
    • 1.子类不保留父类的泛型:按需实现
    • 没有类型擦除
    • 具体类型
    • 2.子类保留父类的泛型:泛型子类
    • 全部保留
    • 部分保留
class Father<T1,T2> {}
// 子类不保留父类的泛型
// 1) 没有类型擦除
class Son1 extends Father {}
// 等价于class Son1 extends Father<Object,Object>

// 2) 具体类型
class Son2 extends Father<Integer,String>{ }
// 子类保留父类的泛型
// 1) 全部保留
class Son3 extends Father<T1,T2> {}
// 2) 部分保留
class Son4 extends Father<Integer,T2> {}

注:子类除了指定或保留父类的泛型,还可以增加自己的泛型

2.5 泛型在继承上的体现

子类的引用可以赋给父类

public class GenericTest3 {
    /*
        类A是类B的父类,但是G<A>和G<B>二者不具备子父类关系,二者是并列关系
            补充:
            类A是类B的父类,A<G> 和 B<G>
     */
    @Test
    public void test() {
        Object obj = null;
        String str = null;
        obj = str;

        Object[] arr1 = null;
        String [] arr2 = null;
        arr1 = arr2;
        // String 是Object的子类,引用可以赋给Object,是多态的体现

        List<Object> list1 = null;
        List<String> list2 = null;

        // 错误
//        list1 = list2;
        // list1 和list2不具有子父类关系
        show(list1);
        show1(list2);
    }

    public void show1(List<String> list) {

    }

    public void show(List<Object> list) {

    }

    @Test
    public void test2() {
        // 子父类的关系
        ArrayList<String> list1 = null;
        List<String> list2 = null;
        ArrayList<String > list3 = null;
        list1 = list3;
        list2 = list3;
    }

}

3.通配符的使用

  • 通配符: ?
    • 比如:List<?>Map<?,?>
    • List<?>是 List、List等各种泛型List的父类
  • 读取List<?>的对象list中的元素时,是安全的,不管list的真实类型是什么,包含的都是Object
  • 不能向其中添加对象,因为不知道元素的类型(可以添加null)
  • 调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object
 @Test
    public void test3() {
        List<Object> list1 = null;
        List<String> list2 = null;

        List<?> list = null;

        list = list1;
        list = list2;

//        print(list1);
//        print(list2);

        List<String> list3 = new ArrayList<>();
        list3.add("AA");
        list3.add("BB");
        list3.add("CC");
        // List<?>是各种泛型List的父类
        list = list3;
        // 除了添加null以外不能添加数据
//        list.add("DD");

        // 可以获取读出数据,读取的类型为Object
        System.out.println(list.get(0));
    }

    public void print(List<?> list) {
        Iterator<?> iterator = list.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println(next);
        }
    }

有限制条件的通配符

说明

  • 允许所有泛型的引用调用

  • 通配符指定上限

    extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即 < =

  • 通配符指定下限

    下限 super :使用时指定的类型不能小于操作的类,即> =

<? extends Number>:只允许泛型为 Number 及 Number的子类的引用调用

<? super Number>:只允许泛型为Number 及 Number父类的引用调用

<? extends Comparable>:只允许泛型为实现Comparable接口的实现类的引用调用

可以在父类容器中添加子类(子类是继承的父类),而使用通配符的 List 不确定类型,所以添加写入数据时需要添加最小的类。

@Test
    public void test4() {
        // Person 或Person的子类
        List<? extends Person> list1 = null;
        List<? super Person> list2 = null;

        List<Student> list3 = new ArrayList<Student>();
        List<Person> list4 = new ArrayList<Person>();
        List<Object> list5 = new ArrayList<Object>();

        list1 = list3;
        list1 = list4;
        // Object 不是 Person的子类
//        list1 = list5;
//        list2 = list3;
        list2 = list5;
        list2 = list4; // Person是Student的父类

        // 读取数据
        list1 = list3;
        // <= Person 包括Person和Person的父类, 最大的父类为Person
        Person person = list1.get(0);
        // 编译不通过
//        Student s = list1.get(0); // 这行编译不通过,需要强转才可行
        Student s = (Student) list1.get(0);

        // >= Person 可能是Person的父类, 那么这里写所有类的父类Object
        list2 = list4;
        Object object = list2.get(0);

        // 写入数据(子类 可以 赋给父类), 在父类中添加子类(因为子类是继承的父类)
        // add要添加最小的类,而list1中的类是 <= Person,找不到最小的,不能赋值
//        list1.add(new Student()); //编译不通过,因为list1中的对象有可能还是
//        list1.add(new Object()); // Object最大,也不行

        // list2中的类是 >= Person, 最小的类是Person
        list2.add(new Person());
        list2.add(new Student()); // Student类比Person类 还小
    }

4. 泛型嵌套

外层泛型中的一个参数也是一个泛型。

public class GenericTest {
    public static void main(String[] args) {
        HashMap<String, ArrayList<Citizen>> map = new HashMap<String,ArrayList<Citizen>>();
        ArrayList<Citizen> list = new ArrayList<Citizen>();
        list.add(new Citizen("刘恺威"));
        list.add(new Citizen("杨幂"));
        list.add(new Citizen("小糯米"));
        map.put("刘恺威",list);

        Set<Map.Entry<String,ArrayList<Citizen>>> entrySet = map.entrySet();
        Iterator<Map.Entry<String, ArrayList<Citizen>>> iterator = entrySet.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, ArrayList<Citizen>> entry = iterator.next();
            String key = entry.getKey();
            ArrayList<Citizen> value = entry.getValue();
            System.out.println("户主"+key);
            System.out.println("家庭成员"+value);
        }
    }
}

4. 作业与测试

Dao类

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

    public void save(String id,T entity) {
        map.put(id,entity);
    }

    public T get(String id) {
        // 通过key获取对象value
        return map.get(id);
    }

    public void update(String id,T entity) {
        if(map.containsKey(id)) {
            map.put(id,entity);
        }else {
            System.out.println("map中不存在key为id的内容");
        }
    }

    public List<T> list() {
        // 错误的
//        List<T> list = (List<T>) map.values();
//        return list;
        // list是有序的, map.values()是无序的
        // 子类能--->父类
        // 父类 不能强转 为子类
        ArrayList<T> list = new ArrayList<T>();
        Collection<T> values = map.values();
        for(T t : values) {
            list.add(t);
        }
        return list;
    }

    // 删除指定id对象
    public void delete(String id) {
        if(map.containsKey(id)) {
            map.remove(id);
        }
    }
}

User

public class User {
    private int id;
    private int age;
    private String 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;
    }

    public User() {
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return id == user.id &&
                age == user.age &&
                Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, age, name);
    }

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

DaoTest

public class DaoTest {
    @Test
    public void test() {
        Dao<User> dao = new Dao<User>();

        dao.save("1001",new User(1001,34,"周杰伦"));
        dao.save("1002",new User(1002,20,"昆凌"));
        dao.save("1003",new User(1003,25,"丁俊"));

        dao.update("1003",new User(1003,30,"丁力"));

//        dao.delete("1001");

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

        list.forEach(System.out::println);
    }


}

5.类型擦除

泛型是 Java1.5版本才引进的概念,很显然,泛型代码可以和之前版本的代码很好的兼容。

https://blog.csdn.net/briblue/article/details/76736356

上一篇:C++ 语言学习入门--链表、队列与栈


下一篇:JAVA学习笔记之十一---初识集合