chapter15 -- 泛型

基本内容

  • 泛型及简单使用
  • 在类,接口和方法中使用泛型
  • 通配符与边界
  • 类型擦除

1 泛型及简单使用

“泛型”这个术语的意思是:“适用于许多许多的类型”。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
泛型在编程语言中出现时,其最初的目的希望类或方法能够具备最广泛的表达能力。
泛型的主要目的之一就是用来指定容器需要持有什么类型的对象,而且由编译器来保证类型的正确性。

我们举一个简单的例子:

public static void main(String[] args) {
        List list = new ArrayList();
        int num = 1;
        String str = "abc";
        list.add(num);
        list.add(str);
        System.out.println(list);
}

可以看到,我们在list中添加了数字和字符串,这么做没有什么问题,因为List中默认可以添加Object及其子类的对象。
但是当我们只希望添加和使用String类型的对象时就会出现问题。
那么我们可以使用泛型来保证添加到List中的对象的“纯粹性”。

public static void main(String[] args) {
        List list = new ArrayList();
        String str = "abc";
        list.add(str);
        System.out.println(list);
}

添加泛型后,我们再向List中添加int类型的数据时,就无法通过编译了。这样做,我们就可以避免因为数据类型出现的错误的情况。
另一方面,在我们使用泛型后,编译器会自动帮我们进行类型的转换。特别是当你使用编译器自动生成foreach语句时,会自动确认类型。

public static void main(String[] args) {
  List list = new ArrayList();
  list.add(cat);
  for (Cat cat2 : list2) {
    System.out.println(cat.name);
  }
}

2 在类,接口和方法中使用泛型

2.1 类中使用泛型

我们先写一个不使用泛型的例子

class User {
    public String name;
    public long idCard;

    public String toString() {
        return "[usrename=" + name + ", idCard=" + idCard + "]";
    }
}

public class Test {
    public static void main(String[] args) {
        User user = new User();
        user.name = "root";
        user.idCard = 1234567890;
        System.out.println(user);
    }
}

这时,我们发现,身份证号信息是有可能出现X字母的,使用long肯定是不合适的了。那么我们把idCard修改为String吧。
这样是解决目前的问题了。但是,如果需求再次变化呢?我们创建了一个idCard类呢?
这时候,我们如果使用泛型,就不会产生这样的问题了。

public class Test {
    public static void main(String[] args) {
        User user = new User();
        user.name = "root";
        IDCard idCard = new IDCard();
        idCard.number = "123456789X";
        idCard.address = "中国";
        user.idCard = idCard;
        System.out.println(user);
    }
}

class User{
    public T name;
    public T idCard;

    public String toString() {
        return "[usrename=" + name + ", idCard=" + idCard + "]";
    }
}

class IDCard{
    public T number;
    public T address;

    public String toString() {
        return "[number=" + number + ", address=" + address + "]";
    }
}

注意:
类型变量使用大写形式。在Java库中,使用变量E表示集合的元素类型,K和V分别表示表的关键字与值的类型。T表示“ 任意类型”。

2.2 接口中使用泛型

泛型接口一般用于生产器中。生产器不需要任何参数,也无需额外信息,就知道如何产生对象。

public class Test {
    public static void main(String[] args) {
        Generator generator = new AnimalGenerator();
        Animal animal = generator.next();
        System.out.println(animal);
    }
}

class Animal {
}

class Cat extends Animal {
}

class Dog extends Animal {
}

interface Generator {
    public T next();
}

class AnimalGenerator implements Generator {
    private Class[] animalClass = { Cat.class, Dog.class };

    @Override
    public Animal next() {
        Random random = new Random();
        Animal animal = null;
        try {
            animal = (Animal) animalClass[random.nextInt(2)].newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return animal;
    }
}

有的同学可能看到了,我们在实现类中使用了Animal作为返回类型,这是Cat和Dog的父类,那么我们是不是直接在接口中使用Animal作为返回类型就好了?
当我们定义了一个功能全面的接口,希望接口具有强大的复用性,那么指明返回类型显然是不明智的,因为猫和电子书显然不可能有同一个父类。

2.3 方法中使用泛型

泛型方法使得该方法能够独立于类而产生变化。
一个基本的指导原则:无论何时,只要你能做到,你就应该尽可能使用泛型方法。也就是说,如果使用感性方法可以取代整个类泛型化,那么就应该只使用泛型方法。

public static void main(String[] args) {
  Test test = new Test();
  int num = 1;
  String str = "abc";
  test.f(num);
  test.f(str);
}

public void f(T x) {
  System.out.println(x.getClass().getSimpleName());
}

3 通配符与边界

3.1 通配符

我们使用?来作为通配符,可以接受任何类型。
我们一般讲?当做Object类型来处理。但是List和List并不能等同,List是List的子类。
但是通配符有个问题,就是能接受的类型有限。

List list = new ArrayList();
list.add("str");//编译器报错

上面的代码看似没什么问题,但是实际在编译器中就无法通过。
使用?就无法添加任何内容,除了null。为什么呢?

因为我们无法确定List到底是什么类型的。如果是List,那么万事大吉,但如果是List,那么限制性就非常明显了吧?

3.2 边界

上面的例子,我们都是使用无边界泛型。但实际上泛型是提供了边界的。
上边界: 上边界使用extends关键字,可以接受其指定类型或其子类。
下边界: 下边界使用supers关键字,可以接受其指定类型或其父类。
为了实现这一效果,Java重写了extends关键字。

我们先来定义几个需要用到的类

class Animal{
    public String name;

    public void eat() {
        System.out.println(this.name + "吃东西");
    }
}

class Cat extends Animal{
}

class Dog extends Animal{
}

测试上边界

public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        Dog dog = new Dog();
        cat.name = "小猫";
        dog.name = "小狗";

        List list = new ArrayList();
        list.add(cat);
        list.add(dog);
        Test.eat(list);

    List list1 = new ArrayList();
        list1.add(cat);
        Test.eat(list1);

        List list2 = new ArrayList();
        list2.add(dog);
        Test.eat(list2);
    }

    public static void eat(List list) {
        for (Animal animal : list) {
            animal.eat();
        }
    }
}

我们可以看到无论是Animal还是Cat和Dog类型的List都可以通过编译。
可以试着去掉“? extends”,看看是否能通过编译。

测试下边界

public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        Dog dog = new Dog();
        cat.name = "小猫";
        dog.name = "小狗";

        List list = new ArrayList();
        list.add(cat);
        list.add(dog);
        Test.eat(list);
    }

    public static void eat(List list) {
        for (Object animal : list) {
            ((Animal) animal).eat();
        }
    }
}

因为继承的关系,Cat和Dog都可以看做是Animal对象。所以,通过编译是没有问题的。但是,我们可以看到,在循环中,我们不得不使用Object来指代Animal。

4 类型擦除

在类型擦除这块我遇到了一些问题,因为我目前使用的Java是1.8的版本,在我反编译代码后出现了如下情况:

public void test(java.util.List);
    Code:
        0: return

编译后并没有擦除泛型?但是,我又编写了一段代码

public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Zoo zoo = new Zoo(animal);
        zoo.show();
    }
}

class Animal {
    public void eat() {
        System.out.println("动物吃东西");
    }
}

class Zoo {
    private T animal;

    public Zoo(T animal) {
        this.animal = animal;
    }

    public void show() {
        animal.eat();//编译不通过
    }
}

也就是说,实际上在Java仍旧不能识别真正的泛型,我们仍旧无法通过使用泛型来获取任何类型信息。

那么我们怎么去解决这个问题呢?

第一种方法: 使用边界。
我们在上一节说到过边界,我们可以通过边界来消除无法识别泛型带来的影响。
我们将Zoo类的声明做一个简单的修改。

class Zoo{

}

我们界定了泛型的范围,是Animal及其子类。

第二种方法: 直接传递类型。
我在网上看到的一种方法,在这里就不多说了,有兴趣大家可以自行尝试。


欢迎关注个人公众号,搜索:公子照谏或者QCzhaojian
也可以通过扫描二维码关注。
chapter15 -- 泛型_第1张图片

你可能感兴趣的:(《Thinking,in,Java》读书笔记,java,泛型)