您的位置:首页 > 职场人生

黑马程序员 Java高新技术 注解,泛型

2013-02-21 22:07 537 查看
------- android培训java培训、java学习型技术博客、期待与您交流! ----------

什么是注解

注解(Annotation)相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,

以后javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,有什么标记,就去干相应的事。

标记可以加在包,类,字段,方法,方法的参数以及局部变量上。

java提供的几个基本注解

例如:System.runFinalizersOnExit(true);在编译时编译器会提示方法已经过时(is deprecated)
@SuppressWarnings("deprecation") 使用这个注解可以让编译器不再提示 
@Deprecated 表示方法已经过时,别人调用这个方法时就会发出提示
@Override 子类覆盖父类的方法时,如果写错了没覆盖成功,使用了这个注解就会发出提示

注解的生命周期
注解默认的生命周期为保留到class文件阶段

@Retention元注解 将元注解加到自定义注解上面,可以指定注解的生命周期

示例:

@Retention(RetetionPolicy.RUNTIME)

public @interface MyAnnotation {}

元注解的三种取值:

RetetionPolicy.SOURCE;RetetionPolicy.CLASS;RetetionPolicy.RUNTIME

分别对应:java源代码-->class文件-->内存中的字节码

三个基本注解的生命周期

@SuppressWarnings,@Override 保留到SOURCE源代码阶段,编译器看完就没用了

@Deprecated 保留到RUNTIME字节码阶段,因为别人调用方法时还需要检查是否过时

注解的应用范围
@Target 用于限定注解的应用范围 

@Target(ElementType.METHOD) 表示只能应用于方法上

@Target({ElementType.METHOD,ElementType.TYPE}) 表示可以应用到方法和类,枚举,接口上

如果是多个类型需要用数组设置,加大括号{}表示一个数组

Type是class,enum和interface的父类接口,jdk1.5加入的,用于表示跟class平级的类型 

为注解增加基本属性

什么是注解的属性

一个注解相当于一个胸牌,如果你胸前贴了胸牌,就是传智播客的学生,否则,就不是。

如果还想区分出是传智播客哪个班的学生,这时候可以为胸牌在增加一个属性来进行区分。

定义基本类型的属性和应用属性
在注解类中增加 String color(); 

注意不要写成 String color; 要加括号()

将注解 @ItcastAnnotation(color="red")添加到某个类上,将color设置为"red"

用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法

ItcastAnnotation annotation = (ItcasrAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class);

System.out.println(annotation.color());

可以认为上面这个@ItcastAnnotation是ItcastAnnotation类的一个实例对象

为属性指定缺省值:

String color() default "blue";

value属性:

String value() default "zxx"; 

如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),

那么可以省略value=部分,直接这样写: @ItcastAnnotation("lhm")。只有value属性可以简写,其他属性不可以。

示例代码:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface ItcastAnnotation {
String color() default "blue";//设置缺省值为blue
String value() default "zxx";//设置缺省值为zxx
}

@ItcastAnnotation(color="red",value="lhm")
public class AnnotationTest{
public static void main(String[] args){
if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class)){
ItcastAnnotation annotation =
(ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class);
System.out.println(annotation.color());
System.out.println(annotation.value());
}
}
}

为注解增加高级属性
数组类型的属性

int [] arrayAttr() default {1,2,3};

@MyAnnotation(arrayAttr={2,3,4})
如果数组属性中只有一个元素,这时候属性值部分可以省略大括号{}

枚举类型的属性

EnumTest.TrafficLamp lamp() ;

@MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)

注解类型的属性:

MetaAnnotation annotationAttr() default @MetaAnnotation("xxxx");

@MyAnnotation(annotationAttr=@MetaAnnotation("yyy"))

可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象,同样的道理,

可以认为上面这个@MetaAnnotation是MetaAnnotation类的一个实例对象,调用代码如下:

MetaAnnotation ma =  myAnnotation.annotationAttr();

System.out.println(ma.value());

什么是泛型
泛型是JDK1.5版本以后出现新特性。用于解决安全问题,是一个类型安全机制。

泛型的好处

1.泛型可以将运行时期出现的问题ClassCastException,转移到编译时期。

2.在定义集合时,可以明确表示要向集合中装哪种类型的数据,无法加入指定类型以外的数据。

3.还能避免强制转换的麻烦。

泛型的原理

泛型是提供给javac编译器使用的,编译器编译带类型说明的集合时会去除掉类型信息,使程序运行效率不受影响,

对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型

的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。

泛型涉及的术语

以ArrayList<E>类和ArrayList<Integer>类为例:

整个ArrayList<E>:称为泛型类型

ArrayList<E>中的E:称为类型变量或类型参数

整个ArrayList<Integer>:称为参数化的泛型类型

ArrayList<Integer>中的Integer:称为类型参数的实例或实际类型参数

ArrayList<Integer>中的<>:读成typeof

ArrayList:称为原始类型

参数化类型与原始类型的兼容性
参数化类型可以引用一个原始类型的对象,编译报告警告,例如,

Collection<String> c = new Vector();//新的类型得可以去引用老的类型
原始类型可以引用一个参数化类型的对象,编译报告警告,例如,

Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去

参数化类型不考虑类型参数的继承关系
一边有类型参数一边没有是没错的因为需要兼容性,但是两边的类型参数不一样肯定是错的。

Vector<String> v = new Vector<Object>(); //错误!

Vector<Object> v = new Vector<String>(); //也错误! 

编译器不允许创建泛型变量的数组。即在创建
4000
数组实例时,数组的元素不能使用参数化的类型

Vector<Integer> vectorList[] = new Vector<Integer>[10];//这是错误的

思考题:下面的代码会报错吗?

Vector v1 = new Vector<String>(); 

Vector<Object> v = v1;

不会,因为编译器只会扫描每行代码有无错误,不会考虑这两行代码之间的引用关系。

泛型中的通配符
通配符和Object的区别:

public static void printCollection(Collection<Object> cols) {
cols.add("string");//没错,使用Object可以调用与参数化有关的方法
cols = new HashSet<Date>();//错误,使用Object不可以引用其他参数化的类型
}

public static void printCollection(Collection<?> cols) {
cols.add("string");//错误,使用?通配符不能调用与参数化有关的方法
cols.size();//没错,此方法不涉及泛型与类型参数没有关系
cols = new HashSet<Date>();//没错,使用?通配符可以引用其他参数化的类型
}

总结:
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,

可以调用与参数化无关的方法,不能调用与参数化有关的方法。

通配符的限定

<? extends E> 限定上边界:指定了父类型,可以接收E自身及其子类型

<? super E> 限定下边界:指定了子类型,可以接收E自身及其父类型

Vector<? extends Number> y = new Vector<Integer>();//没错

Vector<Number> x = y; //错误
?只能用作引用,不能用它去给其他变量赋值,除非使用强制类型转换才能赋值。

泛型集合的综合案例

能写出下面的代码即代表掌握了Java的泛型集合类:
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("gbc", 25);
map.put("wxy", 24);

Set<Entry<String, Integer>> entrySet = map.entrySet();
for (Entry<String, Integer> entry : entrySet) {
System.out.println(entry.getKey()+":"+entry.getValue());
}


在jsp页面中也经常要对Set或Map集合进行迭代:
<c:forEach items="${map}" var="entry">
${entry.key}:${entry.value}
</c:forEach>

自定义泛型

泛型类定义的泛型,在整个类中有效。如果被方法使用,那么泛型类的对象明确具体类型后,方法中的类型也就固定了。

为了让不同方法可以操作不同类型,而且类型还不确定,那么可以将泛型定义在方法上。

静态方法不可以访问类上定义的泛型。如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。
class Demo<T> //类上的泛型写在类名后面
{
public  void show(T t)
{
System.out.println("show:"+t);
}
public <Q> void print(Q q) //方法上的泛型写在返回值前面
{
System.out.println("print:"+q);
}
public  static <W> void method(W t) //静态方法的泛型也是写在返回值前面,注意是在static后面
{
System.out.println("method:"+t);
}
}

自定义swap的泛型方法:
static <T> void swap(T[] a, int i, int j) {
T t = a[i];
a[i] = a[j];
a[j] = t;
}

只有引用类型才能作为泛型方法的实际参数,只是把基本类型的数据作为实际参数是没问题的,这是因为自动装箱和拆箱了。

swap(new int[3],3.5);语句会报告编译错误,因为new int[3]本身已经是对象了,编译器不会对其int进行自动拆箱和装箱,

除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如,

Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,

如<V extends Serializable & Cloneable> void method(){}

普通方法、构造方法和静态方法中都可以使用泛型。

也可以用类型变量表示异常,<T extends Exception>,可以用于方法的throws列表中,但是不能用于catch中。(了解就行)

在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:定义在Map集合中。

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

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

当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,

这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[3],3,4)   ?    static <E> void swap(E[] a, int i, int j)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应

同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5)   ? static <T> T add(T a, T b) 
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,

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

并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,

将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x =(3,3.5f)   ? static <T> T add(T a, T b) 
参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,

而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:

copy(new Integer[5],new String[5]) ? static <T> void copy(T[] a,T[]  b);

copy(new Vector<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()
+ ">)" );
}
}


------- android培训java培训、java学习型技术博客、期待与您交流! ----------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: