黑马程序员——Java高新技术---泛型

-------  android培训 java培训 、java学习型技术博客、期待与您交流! ----------
一、概述
  • 泛型:JDK1.5版本后出现新特性。用于解决安全问题,是一个类型安全机制。
  • 泛型技术:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。
  • 泛型的擦除:编辑器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。在运行时,如何知道获取的元素类型而不用强转呢?
  • 泛型的补偿:因为存储的时候,类型已经确定了是同一个类型的元素,所以在运行时,只要获取到该元素的类型,在内部进行一次转换即可,所以使用者不用再做转换动作了。 
  • 好处:
    • 将运行时期出现的问题ClassCastExecption,转移到编译时期。方便于程序员解决问题,让运行时期问题减少,安全。
    • 避免了强制转换的麻烦。
  • 泛型格式:通过<>来定义要操作的引用数据类型。
    • 只要带有<>的类或者接口,都属于带有类型参数的类或者接口,在使用这些类或者接口时,必须给<>中传递一个具体的引用数据类型 。
    • 通常在集合框架中很常见,见到<>就要定义泛型。<>用来接收类型。当使用集合时,将集合中要存储的数据类型作为参数传递给<>中即可。
    • 如:ArrayList<String> al = new ArrayList<String>();

二、泛型定义中的术语

  • 如:ArrayList<E>类和ArrayList<Integer>
  • ArrayList<E>整个称为泛型类型。
  • ArrayList<E>中的E称为类型变量或类型参数。
  • 整个ArrayList<Integer>称为参数化类型。
  • ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数。
  • ArrayList<Integer>中的<>称为typeof。
  • ArrayList称为原始类型。
  • 参数化:parametered,已经将参数变为实际类型的状态。

三、什么时候写泛型

  • 通常在集合框架中很常见,只要见到<>就要定义泛型。
  • 其实<>就是用来接收类型的。当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。
  • 泛型类:
    • 若类实例对象中要使用到同一泛型参数,即这些地方引用类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型。
    • 泛型类定义的泛型,在整个类中有效。如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所以要操作的类型就已经固定了。
    • 类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,
      • 例如:如下两种方式都可以:
        • GenericDao<String>dao = null;
        • newgenericDao<String>();
    • 语法格式:

       class Utils<XX>

        {

             private XX s;

             public void setxx(XX s)

             {

                 this.s=s;

             }

             public XX getXX()

             {

                  return s;

             }

        }

    • 什么时候写泛型类?
      • 当类中要操作的引用数据类型不确定的时候。
      • 早期定义Object来完成扩展。
      • 现在定义泛型类来完成扩展。
  • 泛型的体现: 
    • 泛型类定义的泛型,在整个类中有效,如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。
    • 为了让不同的方法可以操作不同的类型,而且类型还不确定,那么可以将泛型定义在方法上。
    • 特殊之处:
      • 静态方法不可以访问类上定义的泛型。如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上。
    • 示例:
//泛型定义在类上
class Utils<T>
{
private T type;
public void setObject(T type)
{
this.type = type;
}
public T getObject()
{
return type;
}

//泛型定义在方法上。

public <T> void method(T t)

{

System.out.println("method:"+t);

}

//泛型定义在静态方法上

public static <W> void method(W w)

{

System.out.println("method:"+w);

 //泛型定义在接口上

interface Inter<T>

{

void show(T t);

}

class InterImpl <R> implements Inter<R>

{

public void show(R r)

{

System.out.println("show:"+r);

}

}

  • 泛型方法的特点:
    1. 位置:用于放置泛型的类型参数的<>应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前,按照惯例,类型参数通常用单个大写字母表示。
    2. 只有引用类型才能作为泛型方法的实际参数。
    3. 除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符。
      • 例如:Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable& cloneable> void method(){}。 
    4. 普通方法、构造函数和静态方法中都可以使用泛型。
    5. 可以用类型变量表示异常,称之为参数化的异常,可用于方法的throws列表中,但是不能用于catch子句中。
    6. 在泛型中可同时有多个类型参数,在定义它们的<>中用逗号分开。
      • 例如: public static <K,V> V getValue(K key) { return map.get(key);}

四、关于参数化类型的几点说明

  1. 参数化类型与原始类型的兼容性
    • 参数化类型可引用一个原始类型的对象,编译只是报警告。
      • 如:Collection<String>coll = new Vector();
    • 原始类型可引用一个参数化类型的对象,编译报告警告
      • 如:Collectioncoll = new Vector<String>();
    • 原来的方法接受一个集合参数,新类型也要能传进去。
  2. 参数的类型不考虑类型参数的继承关系:
    •  Vector<String> v = newVector<Objec>();//错误的
    • 不写Object没错,写了就是明知故犯
    • Vector<Objec> v = newVector<String>();//错误的
  3. 编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型
    •  如:Vector<Integer>vectorList[] = new Vector<Integer>[10];//错误的

五、泛型通配符
  • 泛型通配符 ?
    • 当传入的类型不确定时,可以使用通配符?。
    • 使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用。可以调用与参数化无关的方法,不能调用与参数化有关的方法。

  • 泛型限定:
    • 上限:?extends E:可以接收E类型或者E的子类型对象。
    • 下限:?super E:可以接收E类型或者E的父类型对象。
    • 注:限定通配符总是包括自己。

  • 什么时候用上限:
    • 往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。因为取的时候,E类型既可以接收E类对象,又可以接收E的子类型对象。
  • 什么时候用下限:
    • 当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。

    • 限定通配符的上边界:
      • 正确:Vector<? extends Number> x = new Vecter<Integer>();
      • 错误:Vector<? extends Number >x = new Vecter<String>();
    • 限定通配符的下边界:
      • 正确:Vector<? super Number> x = new Vecter<Number>();
      • 错误:Vector<? super Number >x = new Vecter<Byte>();
  • 注意:
    • 泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型
    • 使用带泛型的类创建对象时,等式两边指定的泛型必须一致
      • 原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了 
    • 等式两边可以在任意一边使用泛型在另一边不使用; 
    • ArrayList<String> al = new ArrayList<Object>(); //错
    • ArrayList<? extends Object> al = new ArrayList<String>(); 
      • al.add("abc"); //错误
      • // ? extends Object 代表的具体类型不确定,不能添加具体类型对象。    
    • public static void method(ArrayList<? extends Object> al 
      {
          al.add("abc"); //错误
          // al集合中的元素只能使用Object类中的方法,
          // 因为子类型不确定,不能使用子类型中方法。

代码1

public static<?> void printColl(ArrayList<?> al)  
{  
    Iterator<?> it = al.iterator();  
    while(it.hasNext())  
    {  
        System.out.println(it.next().toString());  
    }  
}  
代码2

public static<T> void printColl(ArrayList<T> al)  
{  
    Iterator<T> it = al.iterator();  
    while(it.hasNext())  
    {  
        //T t=it.next();//T代表具体类型,可以操作和接收这个类型  
        System.out.println(it.next().toString());  
    }  
} 

  • 上面两段代码中T?有什么区别:
    1. T限定了类型,传入什么类型即为什么类型,可以定义变量,接收赋值的内容。
    2. ?为通配符,也可以接收任意类型但是不可以定义变量。
    • 但是这样定义,虽然提高了扩展性,可还是有一个局限性,就是不能使用其他类对象的特有方法。
    • 通配符方案要比泛型方法更有效,当一个类型变量用来表达两个参数之间或参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用,而不是仅在签名的时候使用,才需要使用泛型方法。

六、类型参数的类型推断

1、编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。

2、根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:

        1)当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:

              swap(new String[3],3,4)

                       ——>    static <E> voidswap(E[] a, int i, int j)

        2)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:

              add(3,5)

                     ——>   static<T> T add(T a, T b)

         3)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:

               fill(new Integer[3],3.5f)

                      ——>    static<T> void fill(T[] a, T v)

         4)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:

               intx =(3,3.5f)

                       ——>   static<T> T add(T a, T b)

        5)参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:

               copy(newInteger[5],new String[5])

                        ——>   static<T> void copy(T[] a,T[]  b);

               copy(newVector<String>(), new Integer[5])

                           ——>   static<T> void copy(Collection<T> a , T[] b);


七、通过反射获得泛型的参数化类型
示例代码:
Class GenericalReflection {  
	private Vector<Date> dates = new Vector<Date>();  
	public void setDates(Vector<Date> dates) {  
		this.dates = dates;  
	}  
	public static void main(String[] args) {  
		Method methodApply = GenericalReflection.class.getDeclaredMethod("applyGeneric", Vector.class);  
		ParameterizedType pType = (ParameterizedType)(methodApply .getGenericParameterTypes())[0];  
		System.out.println("setDates("  
						+ ((Class) pType.getRawType()).getName() + "<"  
						+ ((Class) (pType.getActualTypeArguments()[0])).getName()  
						+ ">)" );  
	}  
}  
泛型 DAO 的应用:
DaoBaseImpl<T> implements DaoBase<T> {  
    protected Class<T> clazz;  
    public DaoBaseImpl() {  
        Type type = this.getClass().getGenericSuperclass();  
        ParameterizedType pt = (ParameterizedType) type;  
        this.clazz = (Class) pt.getActualTypeArguments()[0];  
        System.out.println("clazz = " + this.clazz);  
    }  
}  
public class ArticleDaoImpl extends DaoBaseImpl<Article> implements ArticleDao {  
}  


------- android培训java培训、java学习型技术博客、期待与您交流! ----------
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值