黑马程序员---泛型

来源:互联网 发布:男生护肤 知乎 编辑:程序博客网 时间:2024/06/01 17:55

泛型

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

为了让集合集合记住其数据类型,jdk1.5开始,增加了泛型.增加了泛型的集合,可以记住集合中数据元素的类型.

 java中泛型应用最多的地方是集合类。

1 定义数组和集合的比较

在定义数组时都需要指定元素的类型,例如:

String[] arr = new String[10];

对于arr而言,只能为元素指定String类型的值,而其他类型不行。

-------------------

我们以前定义的集合对象都是可以存储任意类型(Object)的对象的。就像是在使用Object类型的数组一样。但这有一个缺点,就是存入后再取出的元素需要强转。这也是可能发生ClassCastExcetpion的地方。

-------------------

大多数情况下,我们不会对同一个集合对象添加多种类型的元素,所以我们最希望可以像使用数组一样,在定义集合对象时指定可以装载的元素类型。

2 泛型的好处

将运行期遇到的问题转移到了编译期。

坏处也不少!!!

Java没有类模板的概念,为了添加这一概述,就出现了泛型。这也是为了兼容性迁移的第一步吧。泛型被称为四不像!这可能需要一个很慢长的迁移的过程,也许将来会完善的!相对C++而言,Java的泛型可能是败笔了。

3 使用泛型类

泛型类中使用的类型变量都被实际参数赋值。

使用泛型集合类

4 泛型类需要给类型变量赋值

  类型参数是变量,你要给类型参数赋值。当然这个值必须是个类型。

定义泛型类一定要给类型参数赋值,不然就等同于赋值为Object

在泛型类中使用的类型参数都会被赋的值替代。例如定义的属性或方法中使用的泛型参数都被替换了。

因为在定义泛型类时,不知道用户指定的类型值是什么,所以类中使用类型变量的属性和方法都不能确定,那么也就不能去调用类型不确定对象的方法(但可以调用Object类具有的方法)。

5 继承泛型类时的两种赋值方式

给出类型常量

给出类型变量

6 定义泛型接口

9 实现泛型接口两种赋值方式

给出类型常量

给出类型变量

泛型方法

1 泛型方法

java中不只可以定义泛型类,还可以定义泛型方法。

泛型方法被调用时需要指定泛型参数的值,这一点与创建泛型类的对象一样。

泛型方法可以定义在泛型类中,也可以定义在非泛型类中。泛型方法可以是static的,也可以不是static的。这与泛型和static没有半毛钱关系!

2 定义泛型方法

定义泛型方法的格式为:

修饰符 <类型参数返回值 方法名(参数列表)

例如:

public class MyClass {

public <T> void fun(T t) {

        System.out.println(t.getClass().getName());

}

}

通常泛型方法的参数都会使用类型变量。上例中的参数就使用类型变量T!在使用者调用这个方法时会给T赋值,那么参数t的类型也就确定了。

3 调用泛型方法

通常泛型方法的参数都会使用类型变量,所以一般不需要显示为泛型方法指定类型参数的值。而是通过调用时传递的实际参数的类型隐示给类型参数赋值的。例如:

MyClass mc = new MyClass();

mc.fun(“hello”);

因为调用fun()方法时实参的类型为String,那么这就隐示为fun()方法的类型参数赋值为String了。

当然也可以显示指定泛型方法的类型参数,这需要在点前面指定类型值。例如:

mc.<String>(“hello”);

泛型边界

1 泛型边界限定的类型值的范围

通常我们看到的泛型类都没有边界限定,也就是说可以给泛型类的类型变量赋任意类型的值(当然基本类型是不可以的)。

java允许给类型参数指定边界,这样在指定类型参数的值时就必须在边界之内。

2 带有边界的泛型类

带有边界的泛型类需要使用extends关键字为类型变量指定边界,格式如下:

class 类名<变量名 extends 边界类型> {…}

例如:

class MyClass<T extends Person> {…}

3 创建带有边界的泛型类对象

MyClass mc = new MyClass();//在没有指定泛型参数的值时,类型变量的值就是边界类型了。也就是Person类型了。

因为MyClass类指定了边界Person,那么只能为类型变量赋值为PersonPerson的子类了。

MyClass<Student> mc =new MyClass<Student>();

4 多个边界类型

还可以为类型参数指定多个边界类型,格式为:

class 类名<类型变量名 extends 边界类型1 & 边界类型2…> {…}

当为类型变量指定多个边界类型时,那么最多只能有一个是类类型,其他的都必须是接口类型。这个道理就不用说了吧。

通配符

1 错误理解泛型

现在有PersonStudent类,其中StudentPerson的子类。这说明:

Person p = new Student();

是正确的。

下面代码编译也是通过的,但运行时会抛出ArrayStoreException。

Person[] ps = new Student[10];

ps[0] = new Student();

ps[1] = new Employee();

在泛型中java就不在允许这么方式了:

ArrayList<Person> list = new ArrayList<Student>();

上面代码是编译不通过的,也就是说Person虽然是Student的父类,但ArrayList<Person>不是ArrayList<Student>的父类。

这也就是说,不能使用ArrayList<Student>类型的对象来调用参数为ArrayList<Person>类型的方法了。这真是太可惜了!

如果现在有方法用来打印封装Person的集合对象,即参数类型为List<Person>。但我们不能让这个方法打印List<Student>,这不是很可惜么?

public static void fun(List<Person> list) {

for(Person p : list) {

System.out.println(p);

}

}

2 子类型通配符

l List<? extends Person>可以把new ArrayList<Person>,以及new ArrayList<Student>赋值它。即PersonPerson子类型的集合。

l 一定转型为List<? extends Person>,那么只能使用返回值为E的方法,不能使用参数为E的方法。当然,不使用E的方法还是可以正常使用的!

l 通配符不能在new中使用,只能在引用中使用。

l List<? extends Person> aList = new ArrayList<? exstends Person>();

l 左边可以使用通配符,但右边new时不能使用通配符!

为了处理上面的问题,java提供了通配符。尝试把上面的fun()方法参数类型修改为List<? extends Person>类型:

public static void fun(List<? extends Person> list) {

for(Person p : list) {

System.out.println(p);

}

}

我们可以把List<Student>List<Employee>List<Person>类型的对象赋值给List<? extends Person>类型的引用。

?并不是变量,而是确定的值。它表示一种确定下来的值,但我们不知道它是什么类型罢了。也就是说List<? extends Person>的元素类型已经开始不明确了,我们只知道元素类型是PersonPerson的某种子类型,但具体是哪一个类型。这也说明我们不能向它添加东西了。

List<? extends Person> list1 = new ArrayList<Student>();//ok

List<? extends Person> list2 = new ArrayList<Employee>();//ok

list1.add(new Student());//error

list1.add(new Employee());//error

list2.add(new Student());//error

list2.add(new Employee());//error

上面代码中,对list1list2添加什么元素都是错误的。让我们分析一下,对编译器而言,它不知道list1list2指向的是实体是什么类型。如果允许了list1.add(new Student()),那么说明也要允许list1.add(new Employee())。因为对list1这个引用而言,它是List<? extends Person>类型,元素类型表示一种不知道的但却是确定的类型,但只知道它是Person的子类型。如果可以添加Student,那为什么不能添加Employee呢?所以编译器的态度是添加什么东西都不行!

也就是说,当给类型参数E赋值为<? extends 类型>时,那么对于参数为E的方法就不能再使用了,因为传递什么类型的参数都是错误的。

3 父类型通配符

还可以给类型参数赋值为<? super 类型>,这就是父类型通配符。

如果你需要一个方法,给参数List添加元素。

public static void fun(List<Person> list, Person p) {

list.add(p);

}

你不能使用List<Student>来调用上面的方法,这个道理我们应该已经明白了。但你可能会说可以把方法参数类型修改为List<? extends Person>类型,这样就可以把List<Person>类型的值传递给参数了。但是如果参数类型为List<? extends Person>类型,那么就不能调用add()方法了!!!

这时可以把参数类型指定为List<? super Student>类型,你可以把List<Student>List<Preson>,甚至List<Object>类型的对象赋值给它。

List<? extends Person>表示元素类型为不知道的确定类型,只知道元素类型为Student的父类型,但不知道是哪个父类型!也许是Person,也许是Object类型。无论是哪一种父类型,说明我们可以向集合对象添加Student对象!因为把Student类型对象添加到List<Person>List<Object>中都是可以的!

但要注意,这时你就不能再使用get()方法了,因为元素的类型不知道是什么,只知道是Student类型的父类型,哪一种就不知道了,这说明你不能用Peson引用指向get()方法的返回值。

但可以使用Object引用来指向get()方法的返回值,因为Object是所有类的父类!

4 无界通配符

无界通配符:List<?>

你可以使用List<?>的引用指向List<? extends Person>List<? super Stuent>List<Person>List<Student>等等,即没有指定泛型的List可以指向什么,它就可以引向什么。

List<?> list1 = new ArrayList();

List<?> list2 = new ArrayList<Object>();

List<? extends Person> pList = new ArrayList<Person>();

List<?> list3 = pList;

List<? super Student> sList = new ArrayList<Student>();

List<?> list4 = sList;

呵呵~,你不能使用List<?>引用调用add()方法,也只能使用Object引用来指向get()方法的返回值了。

其实大多数情况下List<?>就是一种装饰!基本与List一样,没有什么区别了!但如果你使用List会有警告,而使用List<?>时就没有警告了!因为傻瓜编译器看到了你使用List<?>说明你在使用泛型的集合类,它会很开心!

5 通配符总结

可以使用List<? extends Person>指向List<Person>List<Student>List<Employee>。只要元素类型为Person,或其子类就是可以的。

可以使用List<? super Student>指向List<Student>List<Person>List<Object>。只要元素类型为Student或其Student父类就是可以的。

小心,你不能在new时使用通配符:

new ArrayList<? extends Person>(),这是不可以的!

但可以在定义引用时使用通配符:

ArrayList<? extends Person> list;

原创粉丝点击