`

java基础之泛型

 
阅读更多
   

泛型的定义:

泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
 

泛型的特点:

    Java语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原始类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,ArrayList<int>与ArrayList<String>就是同一个类。所以说泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。(类型擦除在后面在学习)
    使用泛型机制编写的程序代码要比那些杂乱的使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类来说尤其有用
 

 泛型的使用:

  1. 在集合中使用泛型
  2. list<A>,list<B>都是list<?>的子类()
  3. ?extends A,可以存放A及其子类
  4. ?super A:可以存放A及其父类
  5. 若A是B的子类,那么List<A> 就不是List<B>的子类

说明:

1、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
2、同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
3、泛型的类型参数可以有多个。
4、泛型的参数类型可以使用extends语句,例如<T extends superclass>。习惯上称为“有界类型”。
5、泛型的参数类型还可以是通配符类型。例如Class<?> classType =                                                                                                                                                  Class.forName("java.lang.String");
1、如果只指定了<?>,而没有extends,则默认是允许Object及其下的任何Java类了。也就是任意类。
2、通配符泛型不单可以向下限制,如<? extends Collection>,还可以向上限制,如<? super Double>               表示类型只能接受Double及其上层父类类型,如Number、Object类型的实例。
3、泛型类定义可以有多个泛型参数,中间用逗号隔开,还可以定义泛型接口,泛型方法。这些都与泛型              类中泛型的使用规则类似。 
  • 如果你想从一个数据类型里获取数据,使用 ? extends 通配符
  • 如果你想把对象写入一个数据结构里,使用 ? super 通配符
  • 如果你既想存,又想取,那就别用通配符。
泛型中<K extends Object>,extends并不代表继承,它是类型范围限制。



 

1.泛型类

public interface List<E> extends Collection<E>
List<JSONObject> lists=new ArrayList<JSONObject>();
public interface Map<K,V> 
Map<String,JSONObject> map=new HashMap<String,JSONObject>();

2.泛型方法

首先,泛型的声明,必须在方法的修饰符(public,static,final,abstract等)之后,返回值声明之前。

public class GenMethod {

    public static <T> void display(T t){

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

    }
/**
 * @param <T> 声明一个泛型T
 * @param c 用来创建泛型对象
 * @throws IllegalAccessException
 * @throws InstantiationException
 */
public <T> T getObject(Class<T> c) throws IllegalAccessException, InstantiationException {
    T t = c.newInstance();
    return t;
}
<T> :声明此方法持有一个类型T,也可以理解为声明此方法为泛型方法
T:指明该方法返回类型为T
Class<T>:指明泛型T的具体类型
c:用来创建泛型T代表的类的对象
     c.newInstance( ):创建对象


 说明一下,定义泛型方法时,必须在返回值前边加一个<T>,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。Class<T>的作用就是指明泛型的具体类型,而Class<T>类型的变量c,可以用来创建泛型类的对象。要用变量c来创建对象呢?既然是泛型方法,就代表着我们不知道具体的类型是什么,也不知道构造方法如何,因此没有办法去new一个对象,但可以利用变量c的newInstance方法去创建对象,也就是利用反射创建对象。
 
那Class<T>和Class<?>有什么区别呢?Class<T>在实例化的时候,T要替换成具体类,Class<?>它是个通配泛型,?可以代表任何类型,主要用于声明时的限制情况
可以声明public Class<?> clazz; 但不能声明 public Class<T> clazz;
 
 

1) 如果你定义了一个泛型(类、接口),那么Java规定,你不能在所有的静态方法、静态初块等所有静态内容中使用泛型的类型参数。例如:

public class A<T> {
    publicstaticvoidfunc(T t) {
    //报错,编译不通过
    }
}

(2) 如何在静态内容(静态方法)中使用泛型,更一般的问题是,如果类(或者接口)没有定义成泛型,但是就想在其中某几个方法中运用泛型(比如接受一个泛型的参数等),该如何解决?

  • 定义泛型方法就像定义泛型类或接口一样,在定义类名(或者接口名)的时候需要指定我的作用域中谁是泛型参数。例如:public class A<T> { ... }表明在类A的作用域中,T是泛型类型参数。
  • 定义泛型方法,其格式是:修饰符 <类型参数列表> 返回类型 方法名(形参列表) { 方法体 }。例如:public static <T, S> int func(List<T> list, Map<Integer, S> map) { ... },其中T和S是泛型类型参数。
  • 泛型方法的定义和普通方法定义不同的地方在于需要在修饰符和返回类型之间加一个泛型类型参数的声明,表明在这个方法作用域中谁才是泛型类型参数;
  • 不管是普通的类/接口的泛型定义,还是方法的泛型定义都逃不出两大要素:
    • 明哪些是泛型类型参数;
    • 这些类型参数在哪里使用。

(3) 类型参数的作用域

  • class A<T> { ... }中T的作用域就是整个A;
  • public <T> func(...) { ... }中T的作用域就是方法func;

  • 类型参数也存在作用域覆盖的问题,可以在一个泛型模板类/接口中继续定义泛型方法,例如:

class A<T> {
    // A已经是一个泛型类,其类型参数是T
    public static <T> voidfunc(T t) {
    // 再在其中定义一个泛型方法,该方法的类型参数也是T
    }
}
//当上述两个类型参数冲突时,在方法中,方法的T会覆盖类的T,即和普通变量的作用域一样,内部覆盖外部,外部的同名变量是不可见的。
//除非是一些特殊需求,一定要将局部类型参数和外部类型参数区分开来,避免发生不必要的错误,因此一般正确的定义方式是这样的:
class A<T> {
    public static <S> voidfunc(S s) {

    }
} 

(4) 泛型方法的类型参数可以指定上限,类型上限必须在类型参数声明的地方定义上限,不能在方法参数中定义上限。规定了上限就只能在规定范围内指定类型实参,超出这个范围就会直接编译报错。

  • <T extends X> void func(List<T> list){ ... },正确
  • <T extends X> void func(T t){ ... },正确
  • <T> void func(List<T extends X> list){ ... } ,编译错误

2. 泛型调用

(1) 显式指定方法的类型参数,类型参数要写在尖括号中并放在方法名之前。例如:object.<String> func(...),这样就显式指定了泛型方法的类型参数为String,那么所有出现类型参数T的地方都将替换成String类型。

(2) 隐式地自动推断,不指明泛型参数,编译器根据传入的实参类型自动推断类型参数。例如:<T> void func(T t){ ... }隐式调用object.func("name"),根据"name"的类型String推断出类型参数T的类型是String

(3) 避免歧义,例如:<T> void func(T t1, T t2){ ... }如果这样调用的话object.func("name", 15); 虽然编译不会报错,但是仍然会有很大隐患,T到底应该是String还是Integer存在歧义;

(4) 有些歧义Java是会直接当成编译错误的,即所有和泛型参数有关的歧义,例如:<T> void func(List<T> l1, List<T> l2){...}如果这样调用的话,object.func(new List<String>(), new List<Integer>()); 这里会有歧义,编译器无法知道T到底应该是String还是Integer,这种歧义会直接报错的,编译无法通过。即泛型方法中,如果类型参数刚好就是泛型参数的类型实参,那么这个类型实参不得有歧义,否则直接编译报错。

3. 泛型方法/类型通配符

(1) 你会发现所有能用类型通配符(?)解决的问题都能用泛型方法解决,并且泛型方法可以解决的更好。

  • 类型通配符:void func(List<? extends A> list);
  • 完全可以用泛型方法完美解决:<T extends A> void func(List<T> list);

(2) 两种方法可以达到相同的效果,“?”可以代表范围内任意类型,而T也可以传入范围内的任意类型实参,并且泛型方法更进一步,“?”泛型对象是只读的,而泛型方法里的泛型对象是可修改的,即List<T> list中的list是可修改的。

(3) 两者最明显的区别

  • “?”泛型对象是只读的,不可修改,因为“?”类型是不确定的,可以代表范围内任意类型;
  • 而泛型方法中的泛型参数对象是可修改的,因为类型参数T是确定的(在调用方法时确定),因为T可以用范围内任意类型指定;

(3) 适用场景

  • 一般只读就用“?”,要修改就用泛型方法。例如:
public <T> voidfunc(List<T> list, T t) {
    list.add(t);
} 
  • 在多个参数、返回值之间存在类型依赖关系就应该使用泛型方法,否则就应该是通配符“?”。具体就是,如果一个方法的返回值、某些参数的类型依赖另一个参数的类型就应该使用泛型方法,因为被依赖的类型如果是不确定的"?",那么其他元素就无法依赖它。例如:<T> void func(List<? extends T> list, T t);即第一个参数依赖第二个参数的类型(第一个参数list的类型参数必须是第二个参数的类型或者其子类)。
  • <T, E extends T> void func(List<T> l1, List<E> l2); 这里E只在形参中出现了一次(类型参数声明不算),并且没有任何其他东西(方法形参、返回值)依赖它,那么就可以把E规约成“?”。规约结果<T> void func(List<T> l1, List<? extends T> l2);
  • 典型应用,容器赋值方法(Java的API):public static <T> void Collections.copy(List<T> dest, List<? extends T> src) { ... }从src拷贝到dest,那么dest最好是src的类型或者其父类,因为这样才能类型兼容,并且src只是读取,没必要做修改,因此使用“?”还可以强制避免对src做不必要的修改,增加的安全性。
 
 
 
  • 大小: 33 KB
分享到:
评论

相关推荐

    关于java基础的泛型的练习

    Java泛型是Java SE 5.0引入的一个重要特性,它极大地增强了代码的类型安全...在进行"关于Java基础的泛型的练习"时,可以尝试编写不同的泛型类、泛型方法,体验泛型带来的便利,并理解其背后的类型系统和类型擦除机制。

    【Java基础】泛型方法 - 右撇子 - 博客频道 - CSDN.NET

    【Java基础】泛型方法 - 右撇子 - 博客频道 - CSDN.NET

    java基础-泛型通配符

    java基础-泛型通配符

    大学课程讲义-Java基础-泛型.docx

    Java泛型是Java编程语言中的一种特性,它允许在数据结构(如集合)中存储特定类型的元素,从而提供了编译时的类型安全性和更清晰的代码。泛型引入的主要目标是...理解并熟练使用泛型是每个Java开发者的基础技能之一。

    java集合 框架 泛型

    泛型是Java 5引入的一项创新特性,极大地增强了集合框架的安全性和效率。本讲解将深入探讨这两个主题,以及与之相关的枚举类型。 首先,Java集合框架包括List、Set、Queue等接口,以及ArrayList、LinkedList、...

    关于C#、java泛型的看法

    在Java中,泛型同样使用尖括号表示,但它的类型擦除特性使得编译后的字节码并不包含类型参数信息,而是使用Object或其他基础类型作为替代。这意味着Java的泛型不支持协变和逆变,但可以通过通配符(如?)来放宽类型...

    Java基础篇:泛型.pdf

    泛型是Java编程语言中用于减少类型转换错误和增强代码安全性的机制,它允许在定义类、接口和方法时使用类型参数。通过这种方式,可以在编译时期捕获那些只有在运行时期才会暴露的类型错误,提高了代码的健壮性。 ...

    Java基础入门四泛型反射注解.pdf

    Java基础入门系列是帮助初学者掌握Java编程的重要学习材料,本篇主要介绍了泛型、反射和注解这三大高级特性。 泛型是Java SE 5版本引入的一个新特性,它的主要目的是允许在使用类、接口和方法时能够引用到任何类型...

    java 继承非泛型类示例

    在Java编程语言中,继承是面向对象特性之一,它允许一个类(子类)继承另一个类(父类)的属性和方法。当我们谈论继承非泛型类时,意味着子类继承了一个没有使用泛型的父类。泛型是Java SE 5.0引入的新特性,用于...

    Java基础笔记之集合框架和泛型

    详细的介绍了集合框架的用法,及其语法规则,剖析了使用的使用注意事项,帮助更牢靠的掌握集合框架的知识及泛型内容。谢谢

    java基础-泛型学习总结

    思维导图

    java基础泛型 学习全文件

    Java基础泛型是Java编程语言中的一个重要特性,它允许在类、接口和方法中使用类型参数,从而增强了代码的类型安全性和重用性。在Java中,泛型的主要目标是提高效率,避免运行时的类型转换,并且在编译时期就能发现...

    java泛型的内部原理及更深应用

    这意味着在编译完成后,所有的泛型信息都会被擦除,替换为Object或者其他基础类型。因此,泛型在运行时并不存在,所有关于泛型的操作都在编译期间完成。 2. **边界通配符**:在处理泛型时,我们经常遇到边界通配符...

    Java课件 (包括基础语法,数组与语句,面向对象编程,java异常处理,java常用基础类,java集合与泛型

    1. **基础语法**:Java的基础语法是学习任何编程语言的起点。这包括变量声明、数据类型(如整型、浮点型、字符型和布尔型)、运算符(算术、比较、逻辑等)、流程控制(如if语句、switch语句、for循环、while循环)...

    java零基础自学 之 JAVA泛型

    在Java编程中,泛型是一种强大的特性,它允许我们在编写代码时指定容器(如列表、队列或映射)所容纳的数据类型。这为程序带来了更高的类型安全性和可读性,同时减少了运行时类型转换异常的可能性。本文将深入探讨...

    java泛型学习ppt

    泛型基础: * 在定义泛型类或声明泛型类的变量时,使用尖括号来指定形式类型参数。 * 当声明或者实例化一个泛型的对象时,必须指定类型参数的值。 自定义简单泛型: * public class Gclass&lt;T&gt;{ private T a; ...

Global site tag (gtag.js) - Google Analytics