Java泛型总结
Java泛型总结
281165273 发表于4年前
Java泛型总结
  • 发表于 4年前
  • 阅读 79
  • 收藏 0
  • 点赞 0
  • 评论 0

新睿云服务器60天免费使用,快来体验!>>>   

1.泛型概念

泛型即参数化类型,主要作用是为了创建更加范化的代码和提供编译期类型安全。

最常见的理据就是不用等到运行期,编译期就可以确保容器类不会装入不同类型的元素,并且可以不用强制类型转换。

例子:

List<String> list=new ArrayList<String>();
list.add("ok");
list.add(Integer.valueOf("11"));//compile error
String s=list.iterator().next();
2.泛型声明

例子:

public interface List<E> {
    void add(E x);
    Iterator<E> iterator();
}

public interface Iterator<E> {
    E next();
    boolean hasNext();
}

尖括号部分形式类型参数的声明,类型参数在整个类的声明中可用,几乎是所有可是使用其他普通类型的地方。

一个泛型类型的声明只被编译一次,并且得到一个class文件,就像普通的class或者interface的声明一样。

类型参数就跟在方法或构造函数中普通的参数一样。就像一个方法有形式参数(formal value parameters)来描述它操作的参数的种类一样,一个泛型声明也有形式类型参数(formal type parameters)。当一个方法被调用,实参(actual arguments)替换形参,方法体被执行。当一个泛型声明被调用,实际类型参数(actual type arguments)取代形式类型参数。

3.泛型与继承

即泛型没有协变性

例子:

List<String> list1=new ArrayList<String>();
List<Object> list2=list1;//compile error
list2.add(1);

假设允许第2行代码,那么第三行代码将可以在一个String的集合中插入一个数字。

4.通配符

当一个方法可以处理任意类型的集合,而不关心集合中元素的类型时,使用无界通配符,collection<?>相当于各种collection的父类。

旧代码:

void printCollection(Collection c) {
       Iterator i = c.iterator();
           for (int k = 0; k < c.size(); k++) {
                  System.out.println(i.next());
         }
}

新代码:

void printCollection(Collection<Object> c) {
           for (Object e : c) {
                 System.out.println(e);
           }
}

这个方法可以处理任意集合,转换成泛型如果参数使用collection<Object>,那么方法的作用就小了,原来可以打印任意类型集合,现在只能打印collection<Object>集合了。

正确的泛型代码:

void printCollection(Collection<?> c) {
for (Object e : c) {
System.out.println(e);
}
}

但是注意,如果方法关心集合元素的类型,则需要使用有界通配符了,如下面代码:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // compile error

因为我们不知道collection里元素的类型,所有不能添加任意元素到集合,add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。唯一的例外是null,它是所有类型的成员。

另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object,因此把get的返回值赋值给一个Object类型的对象或者放在任何希望是Object类型的地方是安全的。

5.有界通配符

例子:&#160;

public abstract class Shape {

public abstract void draw(Canvas c);

}

public class Circle extends Shape {

private int    x, y, radius;

public void draw(Canvas c) { // ...

}

}

public class Rectangle extends Shape {

private int    x, y, width, height;

public void draw(Canvas c) {

// ...

}

}

public class Canvas {

public void draw(Shape s) {

s.draw(this);

}

public void drawAll(List<Shape> shapes) {

          for (Shape s : shapes) {

             s.draw(this);

         }

}}

&#160;

现在,类型规则导致drawAll()只能使用Shape的list来调用。它不能,比如说对List<Circle>来调用。这很不幸,因为这个方法所作的只是从这个list读取shape,因此它应该也能对List<Circle>调用。我们真正要的是这个方法能够接受一个任意种类的shape:

public void drawAll(List<? extends Shape> shapes) { //..}

这里有一处很小但是很重要的不同:我们把类型 List<Shape> 替换成了 List<? extends Shape>。现在drawAll()可以接受任何Shape的子类的List,所以我们可以对List<Circle>进行调用。

List<? extends Shape>是有限制通配符的一个例子。这里?代表一个未知的类型,就像我们前面看到的通配符一样。但是,在这里,我们知道这个未知的类型实际上是Shape的一个子类(它可以是Shape本身或者Shape的子类而不必是extends自Shape)。我们说Shape是这个通配符的上限(upper bound)。

像平常一样,要得到使用通配符的灵活性有些代价。这个代价是,现在像shapes中写入是非法的。比如下面的代码是不允许的:

public void addRectangle(List<? extends Shape> shapes) {

shapes.add(0, new Rectangle()); // compile-time error!

}

你应该能够指出为什么上面的代码是不允许的。因为shapes.add的第二个参数类型是? extends Shape ——一个Shape未知的子类。因此我们不知道这个类型是什么,我们不知道它是不是Rectangle的父类;它可能是也可能不是一个父类,所以这里传递一个Rectangle不安全。

而正好相反的是下限通配符,? super TestObject——一个TestObject未知的超类,因此我们可以将TestObject及其子类安全的放入集合,而当我们从集合中取出元素时,我们无法知道究竟是哪个父类,因此赋值是不安全的。

public class TestObject {

}

public class TestObject1 extends TestObject {

}

	

public void addTestObject(List<? super TestObject> list) {

          list.add(new TestObject1());

          TestObject to=list.get(0);//compile error

}

关于GET和SET原则

如果方法传入的泛型是生产者,即将实例加入到当前容器类,则用?extends,如果是从当前容器类消费实例,放入参数传入的容器类,则用?super

关于自限定

Enum<E extends Enum<E>>

用于限定继承关系A extends Enum<A>

并且会限制重载,方法的参数类型会协变,使得子类中不能即含有基类型参数的方法,又含有子类型参数的方法,而如果不是自限定的,则可以重载

泛型的重载还有一个特殊的地方,可以用方法返回值来确定重载的版本

  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
粉丝 4
博文 64
码字总数 35522
×
281165273
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: