黑马程序员----基础加强4注解和泛型等

来源:互联网 发布:美工的电脑为什么 编辑:程序博客网 时间:2024/05/21 18:38

------- android培训、java培训、期待与您交流! ----------



 

>>是1.5新特性

 

         注解相当于一种标记,加了注解就相当于打上某种标记,以后,javac编译器,开发工具和其它程序可以用反射来了解你的类及各种元素上有无何种标记。

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

 

java.lang包中说明了几个基本的注解(annotation)

注释类型摘要

Deprecated

用 @Deprecated 注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。

Override

表示一个方法声明打算重写超类中的另一个方法声明。

SuppressWarnings

指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。

 

 

Eg:演示一个简单的注解

packagecn.com.cty;

 

publicclass SimpleAnnocationShow {

 

 

         @SuppressWarnings("deprecation")

         public static void main(String[] args){

                  

                   //使用已经过时的方法,编译器会警告(命令行下)

                   System.runFinalizersOnExit(true);

                  

         }

}

 

没加注解前,使用命令行编译,提示信息

C:\  javac  F:\SimpleAnnocationShow.java

注意:F:\SimpleAnnocationShow.java 使用或覆盖了已过时的 API。

注意:要了解详细信息,请使用-Xlint:deprecation 重新编译。

 

C:\javac -Xlint:deprecation  F:\SimpleAnnocationShow.java

F:\SimpleAnnocationShow.java:9:警告:[deprecation] java.lang.System 中的runFinalizersOnExit(boolean) 已过时

               System.runFinalizersOnExit(true);

                      ^

1 警告

 

 

Eg:老方法提示过时

……………………

@Deprecated

         public void show()

         {

                   //现在不流行hello了,过时了

                   System.out.println("hello!");

         }

……………………

 

 

Eg:Override,提示是否覆盖

比如,Object的equals方法,参数是“Objectobj”,现在有一个类中覆盖equals方法,误写成

publicboolean equals(String obj){…………}

程序员没有发现写错了,在使用HashSet存储时出错,因为还是去调用了Object的equals方法而不是重新写的equals方法(HashSet调用equals判断元素是否重复)。所以,在覆盖的方法上注解,如果不是覆盖父类的方法,就编译报错。

 

@Override

publicboolean equals(Object obj){

//参数相同才算覆盖

…………}

 

 

 

 

>>注解的应用

 

eg:个各类之间的关系

 

//1、注解类

@interfaceA{}

 

//2、应用了注解的类

@A

ClassB{}

 

//3、对“应用了注解的类”进行反射操作的类

Class C

{

         B.Class.isAnntionPresent(A.class);

         A a = B.Class.getAnnotion(A.class);

}

 

 

eg:测试上述关联并引出元注解问题

//————————注解类————————

packagecom.annocation;

 

importjava.lang.annotation.Retention;

importjava.lang.annotation.RetentionPolicy;

 

//注解上的注解,称为元注解

//意思是在运行期间一直保留

@Retention(RetentionPolicy.RUNTIME)

public@interface MyAnnotation {

 

}

 

 

//————————被注解类以及反射————————

 

packagecom.annocation;

 

@MyAnnotation

publicclass AnnotationTest {

 

         public static void main(String[] args){

                  

                   //检查注解是不是在类上

                   if(AnnotationTest.class.isAnnotationPresent(MyAnnotation.class))

                   {

                            //反射得到这个注解对象

                            MyAnnotation myAnn =(MyAnnotation)

                                     AnnotationTest.class.getAnnotation(MyAnnotation.class);

                            System.out.println(myAnn);  //没有任何结果,去注解上加注解

                   }

         }

}

//输出:

//@com.annocation.MyAnnotation()

 

说明:一开始运行时没有输出任何信息,说明if没有通过。在“MyAnnotation”上注解“@Retention”后,输出了信息。

         “元”即描述信息的信息。

         “元注解”@Retention有三种取值:RetetionPolicy.SOURCE、RetetionPolicy.CLASS和RetetionPolicy.RUNTIME;分别对应:java源文件-->class文件-->内存中的字节码。这也说明一个注解有三种生命周期,上述例子就是将注解“MyAnnotation”的生命周期指定到运行阶段。(另外,知道了RetetionPolicy是一个枚举类)。

如果没有特别说明,在编译阶段就可能将一些注解去掉。所以上述例子一开始没有看到输出。

 

 

eg:思考

//Override的注解保留在SOURCE阶段

//SupperessWarnings的注解保留在SOURCE阶段

//Deprecated的注解保留在RUNTIME阶段

 

 

eg:@target注解

packagecom.annocation;

 

import java.lang.annotation.ElementType;

importjava.lang.annotation.Retention;

importjava.lang.annotation.RetentionPolicy;

importjava.lang.annotation.Target;

 

//注解上的注解,称为元注解

//意思是在运行期间一直保留

@Retention(RetentionPolicy.RUNTIME)

 

//指定只能注解在类和方法上

//注意这里是TYPE而不是CLASS,参数是一个数组,可以传多个参数

@Target({ElementType.METHOD,ElementType.TYPE})

public@interface MyAnnotation {

 

}

 

 

 

 

>>注解的属性

 

eg:说明例子

 

packagecom.annocation;

 

importjava.lang.annotation.ElementType;

importjava.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

importjava.lang.annotation.Target;

importjava.lang.reflect.Method;

 

//设定三个属性

@AttributeDemo(color= "red", value = "type",

                   metaAnnoatation =@MetaAnnotation("注解类型属性的设定方式"))

publicclass AnnotationAttributeDemo {

 

         public static void main(String[] args)throws Exception {

 

                   if(AnnotationAttributeDemo.class

                                     .isAnnotationPresent(AttributeDemo.class)){

                            AttributeDemo ad =AnnotationAttributeDemo.class

                                               .getAnnotation(AttributeDemo.class);

 

                            System.out.println("main方法上的color属性:" +ad.color());

 

                            // 方法上的注解

                            show();

 

                            // 数组

                            System.out.println(ad.arr().length);

 

                            // 枚举

                            System.out.println(ad.lamp().show());

                           

                            // 注解

                            System.out.println(ad.metaAnnoatation().value());

                   }

         }

 

         // value是特殊的属性,当只有一个value值需要设定时,可以不用打”value=“

         // 这里因为我们给color属性设定了默认值,所以可以省略”value=“

         @AttributeDemo("method")

         public static void show() throwsException {

 

                   // 获取方法

                   Method showMethod =

                            AnnotationAttributeDemo.class.getMethod("show",null);

 

                   // 判断show上是否有响应注解

                   if(showMethod.isAnnotationPresent(AttributeDemo.class)) {

                            AttributeDemo ad =showMethod.getAnnotation(AttributeDemo.class);

                            System.out.println("show方法上的color属性:" +ad.color());

                   }

         }

}

 

// 注解类

@Retention(RetentionPolicy.RUNTIME)

@Target({ ElementType.METHOD, ElementType.TYPE })

@interfaceAttributeDemo {

         // 特殊的属性

         public abstract String value();

 

         // 属性,且设定默认值

         String color() default"blue";// 省略public abstract

 

         // 高级属性

         int[] arr() default { 1, 2, 3 }; // 数组类型属性

        

         Lamp lamp() default Lamp.RED;// 枚举类型属性

        

         MetaAnnotation metaAnnoatation()

                   default@MetaAnnotation("注解类型属性默认值的设定方式"); //注解类型属性

        

}

 

// 测试用枚举类

enumLamp {

         RED() {public String show() {  return "red";  } },

         GREEN() {public String show() {  return "green";  } },

         YELLOW() {public String show() {  return "yellow";  } };

 

         public abstract String show();

}

 

//测试用注解类

@interfaceMetaAnnotation

{

         String value();

}

 

说明:注解的属性可以是8种基本数据类型、String、Class、enum、注解类型和数组。

 

 

 

 

>>泛型入门和基本应用

是1.5新特性。

 

         泛型是提供给javac编译器使用的,可以先定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合是会去除掉“类型”信息,是程序运行效率不受影响,对于参数化的泛型类型,getClass()方法返回值和原始类型完全一样。

 

(坏坏)由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,在调用其add方法。

 

eg:使坏,加入其它类型的信息

packagecom.generic;

 

importjava.lang.reflect.Method;

import java.util.ArrayList;

importjava.util.Collection;

importjava.util.Iterator;

 

publicclass GenericReflectTest {

 

         public static void main(String[] args)throws Exception {

                  

                   //泛型为整形类型的集合

                   Collection<Integer>coll = new ArrayList<Integer>();

                  

                   //如果add中为String类型编译报错

                   //coll.add("abc");

                   coll.add(47);

                  

                   //用反射使坏,加入String类型的数据

                   Method addMethod =coll.getClass().getMethod("add", Object.class);

                   addMethod.invoke(coll,"durex");

                  

                   //输出集合中的内容

                   Iterator it =coll.iterator();

                   while(it.hasNext())

                  {

                            System.out.println(it.next());

                   }

                  

                   /*输出:

                   47

                   durex*/

         }

}

 

 

 

 

>>泛型术语

ArrayList<E>类定义和ArrayList<Integer>类引用中涉及:

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

2)  整个ArrayList<Integer>称为参数化类型

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

4)  ArrayList<Integer>中的<>念作typeof

5)  ArrayList称为原始类型

 

 

参数化类型和原始类型的兼容性

1)  参数化类型可以引用一个原始类型的对象,编译报告警告,例如:

Collection<String>c = new Vector();

2)  原始类型可以引用一个参数化类型的对象,编译报告警告,例如:

Collectionc = new Vector<String>();

 

 

参数化类型不考虑类型参数的继承关系:

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

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

 

 

创建数组实例,数组元素不能使用参数化的类型,例如,下面是错误例子:

Vector<Integer>vectorList[] = new Vector<Integer>[10];

 

 

eg:有错误吗?

Vector v1 = new Vector<String>();

Vector<Object>v = v1;

 

没有编译错误

 

 

 

 

>>泛型的通配符“?”以及拓展应用

 

eg:打印出任意参数化类型的集合的方法(错误演示)

 

public static void main(String[] args)

{

         Collection<Integer>coll = new Collection();

         coll.add(1);

         printCollection(coll);//编译报错

}

 

public static voidprintCollection(Collection<Object> coll)

{

         ………………

coll.add(“abc”);// 合法

coll = newHashSet<Date>(); // 非法

}

 

 

eg:打印出任意参数化类型的集合的方法

 

public static voidprintCollection(Collection<?> coll)

{

         //coll.add(1);//但是不能调用类型相关的方法

         coll.size();//合法

         coll= new HashSet<Date>(); // 合法

         ………………

}

 

 

eg:限定说明

 

上限:必须是自己和其子类

正确:Vector<? extends Number> x = new Vector<Integer>();

错误:Vector<? extends Number> x = new Vector<String>();

 

下限:必须是自己和其父类

正确:Vector<? super Integer> x = new Vector<Number>();

错误:Vector<? super Integer > x = new Vector<Byte>();

 

提示:限定通配符总是包括自己。

 

 

eg:泛型综合案例

 

package com.generic;

 

import java.util.Iterator;

import java.util.Map;

import java.util.Set;

import java.util.TreeMap;

import java.util.Map.Entry;

 

public class GenericSynthesizeDemo {

 

         publicstatic void main(String[] args) {

                  

                   TreeMap<String,Integer> tm = new TreeMap<String, Integer>();

                   tm.put("durex",28);

                   tm.put("jisboon",20);

                   tm.put("tiantian",1);

                   tm.put("darren",24);

                   tm.put("douglas",25);

                  

                   Set<Map.Entry<String,Integer>> entrySet = tm.entrySet();

                   Iteratorit = entrySet.iterator();

                   while(it.hasNext())

                   {

                            Map.Entry<String,Integer> entry = (Entry<String, Integer>) it.next();

                            System.out.println(entry.getKey()+ ":  \t" + entry.getValue());

                   }

         }

}

 

 

 

 

>>由c++的模板函数引入自定义泛型

 

eg:在C++中

//很相似的函数

int add(int x, int y){  return x+y; }

float add(float x, float y){  return x+y; }

double add(double x, double y){  return x+y; }

 

//所以C++中有模板函数

template<class T>

T add(T x, Ty)

{

         returnx+y;

}

 

 

         Java中的泛型类型(或者泛型)类似于c++中的模板。但是这种相似性仅限于表面,java语言中的泛型基本上完全是在编译器总实现的,用于编译器执行类型检查和类型判断,然后生成普通的非泛型的字节码,这种实现技术称之为擦除(erasure),即编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除。

这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为java厂商升级其JVM造成难以逾越的障碍。所以java的泛型采用了可以完全在编译器中实现的擦除方法。

 

 

eg:java中的自定义泛型

package com.generic;

 

public class DiyGeneric {

 

         publicstatic void main(String[] args) {

                  

                   add(3,5); // T是int型

                  

                   //floatnum = (float) add(3.5, 2);

//报错,这时T取类型的交集,也就是T是Number型

                   Numbernum = add(3.5, 2);

                  

                   add("cty",25);//当然,这个T就是Object了

         }

        

         //声明一个T类型,返回值是T类型

         privatestatic <T> T add(T x, T y)

         {

                   //returnx+y;//T是不支持”+“的

                   returnx;

         }

}

 

 

eg:数组元素交换的函数

……………………

public static <T> void swap(T[] arr,int i, int j)

         {

                   Ttemp = arr[j];

                   arr[j]= arr[i];

                   arr[i]= temp;

         }

……………………

 

         用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。

        

只有引用类型才能作为泛型方法的实际参数,swap(newint[]{1,2,3,4,5},2,5)会报告编译错误。

 

除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用。比如Class中的getAnnotation方法:

public<A extends Annotation> A getAnnotation(Class<A> annotationClass)

并且可以用“&”来指定多个边界,如<V extends Serializable & cloneable> void method(){}。

 

         编译器不允许创建类型变量的数组(错误例子):

Vector<Integer>vectorList[] = new Vector<Integer>[10];

 

         也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子语句中:

private static <T extends Exception>show () throws T //可以这样

{

         try{}

         catch(Te){throw(T)e;}//不能这样干

}

 

         定义泛型中可以同时有多个类型参数,例如我们很熟悉的:Map.Entry<K,V>

 

 

 

 

>>泛型练习

 

eg:编写一个泛型方法,自动将Object方法转换成其它类型

public static void main(String[] args)

{

         Objectobj = "abc";

                   //Stringstring = obj;  //报错

                   Stringstring = autoConvert(obj);

}

 

//编写一个泛型方法,自动将Object方法转换成其它类型

         publicstatic <T> T autoConvert(Object obj)

         {

                   return(T)obj;

         }

 

 

eg:定义一个方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象

private static <T> void fillArr(T[]arr , T obj)

{

         for(intx = 0 ; x<arr.length ; x++)

{

                   arr[x]= obj;

         }

}

 

 

eg:采用自定泛型方法的方式打印出任意参数化类型的集合中的所有内容

         在这种情况下,前面的通配符方案要比泛型方法更有效,当一个类型变量用来表达两个参数之间或者参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用而不是仅在签名的时候使用,才需要使用泛型方法。

 

public static <T> voidprintCollection(Collection<T> coll, T obj)

         {

                   System.out.println(coll.size());

                  

                   for(Tt : coll)

                   {

                            System.out.println(t);

                   }

                  

                   //而且使用泛型可以使用相关类型的方法了

                   coll.add(obj);

         }

 

 

eg:定义一个方法,把任意参数类型的集合中的数据安全地复制到响应类型的数组中

public static <T> T[] copy(T[] arr)

         {

                   T[]newArr = null;

                   if(arr!= null)

                   {

                            newArr= arr;

                   }

                  

                   returnnewArr;

         }

 

eg:依据上一个例子,说明下特别的,有两个方法

//数组填入集合

public static <T> voidcopy1(Collection<T> dest, T[] src){//省略方法体,就是for}

//数组填入数组

public static <T> void copy2(T[]dest, T[] src){ //省略方法体,就是for }

 

//main函数中调用

public static void main(String[] args)

{

         copy1(newVector<String>(), new String[10]); //合法

         copy2(newDate[10],new String[10]);  //合法,T是Object

         //copy1(newVector<Date>(), new String[10]);//编译报错

}

 

说明:Vector<Date>,那么方法中接收的“T”就是Date了,这是泛型的传播性,那么再传入String类型的数组,就报错了。

 

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

 

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

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

          swap(new String[3],3,4)      static <E> void swap(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,则没有了错误:

          int x =(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> voidcopy(Collection<T> a , T[] b);

 

 

 

 

>>自定义泛型类

 

如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:

         publicclass GenericDao<T> {

                   privateT field1;

                   publicvoid save(T obj){  }

                   publicT getById(int id){  }

         }

 

类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:

GenericDao<String>dao = null;

newgenericDao<String>();

注意:

1)在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。

2)当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。静态方法上可以独立定义方法上的泛型。

 

问题:类中只有一个方法需要使用泛型,是使用类级别的泛型,还是使用方法级别的泛型?

 

 

 

 

>>反射获得泛型的实际类型参数(现在不必太深入)

 

eg:获取一个方法参数上的泛型类型

package com.generic;

 

import java.lang.reflect.Method;

import java.lang.reflect.ParameterizedType;

import java.util.Date;

import java.util.Vector;

 

public class GenericalReflection {

        

         privateVector<Date> dates = new Vector<Date>();

 

         publicvoid setDates(Vector<Date> dates) {

                   this.dates= dates;

         }

 

         publicstatic void main(String[] args) throws Exception {

                  

                   //反射出类上的方法

                   MethodmethodApply

                            =GenericalReflection.class.getDeclaredMethod("setDates", Vector.class);

                  

                   //调用Method类的一个方法getGenericParameterTypes

                   ParameterizedTypepType

                            =(ParameterizedType) (methodApply.getGenericParameterTypes())[0];

                  

                   System.out.println("setDates("

                                     +((Class) pType.getRawType()).getName()

                                     +"<" + ((Class) (pType.getActualTypeArguments()[0])).getName()

                                     +">)");

                  

                   /*输出:

                   setDates(java.util.Vector<java.util.Date>)*/

         }

}