泛型

泛型:

作用:解决类型安全问题, 使得模糊的Object类型变得精确. 使用更安全更简单

泛型的声明:

interface List<T> 和 class TestGen<K,V>

其中,T,K,V不代表值,而是表示类型。

这里使用任意字母都可以。常用T表示,是Type的缩写。

泛型的实例化:

一定要在类名后面指定类型参数的值(类型)。如: List<String> strList = new ArrayList<String>(); Iterator<Customer> iterator = customers.iterator(); T只能是类,不能用基本数据类型填充。

运用:在集合中使用泛型、自定义泛型类、泛型方法、泛型接口
class Person<T>{
//使用T类型定义变量
private T info;
//使用T类型定义一般方法
public T getInfo(){
return info;
}
public void setInfo(T info){
this.info = info;
}
//使用T类型定义构造器
public Person(){}
public Person(T info){
this.info = info;
}
   /** 静态环境中不允许使用泛型类型.
   public static void test(A a) {
       System.out.println(a);
   }*/
   //不能在try-catch中使用泛型定义
   //try{}
   //catch(T t){}
}

对于泛型类

1.对象实例化时不指定泛型,默认为:Object。 2.泛型不同的引用不能相互赋值。 3.加入集合中的对象类型必须与指定的泛型类型一致。

4.静态方法中不能使用类的泛型。 5.如果泛型类是一个接口或抽象类,则不可创建泛型 类的对象。 6.不能在catch中使用泛型 7.从泛型类派生子类,泛型类型可以具体化

自定义泛型类

在类名后面<泛型类型参数>自定义泛型类中的

泛型信息, 本质上是隶属于对象的, 也称为成员泛型

class Person<A> { // A代表某种未知类类型, 具体是什么不知道, 即使不知道具体类型也能用.
   // A这个类型 可以在本类中当成类型来用, 类型可以声明变量, 可以作为方法的返回值类型, 可以作为参数的类型.
   // A类型会在使用者创建对象或声明引用时指定
   private String name;
   private A info;
   public Person() {}
   public Person(String name, A info) {
       this.name = name;
       this.info = info;
  }
   public String getName() {return name;}
   public void setName(String name) {this.name = name;}
   public A getInfo() {return info;}
   public void setInfo(A info) {this.info = info;}
   @Override
   public String toString() {
       return "Person{" +
               "name='" + name + '\'' +
               ", info=" + info +
               '}';
  }

   /** 静态环境中不允许使用泛型类型.
   public static void test(A a) {
       System.out.println(a);
   }*/

}
Person<String> p1 = new Person<String>("张三", "男");
String info1 = p1.getInfo();
//p1.setInfo(40); p1对象的内部的泛型指定成String, 类型安全了, 再设置成Integer就会报错

Person<Integer> p2 = new Person<Integer>("李四", 40);
Integer info2 = p2.getInfo();
//p2.setInfo("abc"); p2对象的内部泛型是Integer, 设置新的String值就出错

Person p3 = new Person("王五", true); // 在使用类时没有泛型, 最不济它就是Object类型
Object info3 = p3.getInfo();
p3.setInfo(3.22); // 类型不安全!!!

泛型类在继承中的处理

class Base<B> {
  private B b;
  public B getB() {return b;}
  public void setB(B b) {this.b = b;}
}

class Sub1 extends Base {} // 子类在继承泛型父类时忽略了泛型信息, 在子类中B类型就是Object类型.

class Sub2 extends Base<String> {} // 子类在继承泛型父类时把泛型类型写死, 在子类中B类型永远是String
class Sub3 extends Base<Boolean> {} // 子类在继承泛型父类时把泛型类型写死, 在子类中B类型永远是Boolean

class Sub4<B> extends Base<B> {} // 子类在继承泛型父类时把泛型类型继承在子类仍然泛型, 在子类中B类型也是变化的
//测试方法   
Base<String> base1 = new Base<String>();
       String b = base1.getB();
       Sub1 sub1 = new Sub1();
       Object b1 = sub1.getB();
       Sub2 sub2 = new Sub2();
       String b2 = sub2.getB();
       Sub3 sub3 = new Sub3();
       Boolean b3 = sub3.getB();

       Sub4<Float> floatSub4 = new Sub4<Float>();
       Float b4 = floatSub4.getB();
       Sub4<Double> doubleSub4 = new Sub4<Double>();
       Double b5 = doubleSub4.getB();

 

对于泛型方法

泛型隶属于方法, 也称为局部泛型. 在方法返回值左面 <泛型类型>

泛型方法的格式:

[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常

public <E>  E get(int id, E e){

E result = null;

return result;
}}

泛型通配符 重点、需要再整理

 

常用的 T,E,K,V,?

本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。

通常情况下,T,E,K,V,?是这样约定的:

  • ?表示不确定的 java 类型

  • T (type) 表示具体的一个java类型

  • K V (key value) 分别代表java键值中的Key Value

  • E (element) 代表Element

无界通配符

先从一个小例子看起,原文在 这里 。

我有一个父类 Animal 和几个子类,如狗、猫等,现在我需要一个动物的列表,我的第一个想法是像这样的:

List<Animal> listAnimals

但是老板的想法确实这样的:

List<? extends Animal> listAnimals

为什么要使用通配符而不是简单的泛型呢?通配符其实在声明局部变量时是没有什么意义的,但是当你为一个方法声明一个参数时,它是非常重要的。

static int countLegs (List<? extends Animal > animals ) {
  int retVal = 0;
  for ( Animal animal : animals )
  {
      retVal += animal.countLegs();
  }
  return retVal;
}

static int countLegs1 (List< Animal > animals ){
  int retVal = 0;
  for ( Animal animal : animals )
  {
      retVal += animal.countLegs();
  }
  return retVal;
}

public static void main(String[] args) {
  List<Dog> dogs = new ArrayList<>();
    // 不会报错
  countLegs( dogs );
  // 报错
  countLegs1(dogs);
}

当调用 countLegs1 时,就会飘红,提示的错误信息如下:

所以,对于不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即 ),表示可以持有任何类型。像 countLegs 方法中,限定了上届,但是不关心具体类型是什么,所以对于传入的 Animal 的所有子类都可以支持,并且不会报错。而 countLegs1 就不行。

上界通配符 < ? extends E>

上届:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:

  • 如果传入的类型不是 E 或者 E 的子类,编译不成功

  • 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用

private <K extends A, E extends B> E test(K arg1, E arg2){
  E result = arg2;
  arg2.compareTo(arg1);
  //.....
  return result;
}

类型参数列表中如果有多个类型参数上限,用逗号分开

下界通配符 < ? super E>

下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object

在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。

private <T> void test(List<? super T> dst, List<T> src){
  for (T t : src) {
      dst.add(t);
  }
}

public static void main(String[] args) {
  List<Dog> dogs = new ArrayList<>();
  List<Animal> animals = new ArrayList<>();
  new Test3().test(animals,dogs);
}
// Dog 是 Animal 的子类
class Dog extends Animal {

}

dst 类型 “大于等于” src 的类型,这里的“大于等于”是指 dst 表示的范围比 src 要大,因此装得下 dst 的容器也就能装 src 。

 

上界通配符主要用于读数据,下界通配符主要用于写数据。

posted @ 2021-01-25 13:45  WZZR  阅读(122)  评论(0)    收藏  举报