黑马程序员_二十二篇 【eclipse】【javaBean内省】【注解】【泛型】【类加载器】

时间:2023-02-12 09:17:00

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

 

 

  1    Eclipse开发工具

 

基础知识非常重要,基础知识的深入程度决定了你能力

学习能力很重要,在未来的工作生活中,这是最重要的能力

每个知识点,会看会学会写会教人,才能达到熟练掌握的程度

 

Eclipse相关名词

Java ee Java EEJava PlatformEnterprise Edition)是sun公司推出的企业级应用程序版本。

Ide      IDE(Integrated Development Environment,集成开发环境)

Jms jmsjava消息服务Java Message Service应用程序接口是一个Java平台中关于面向消息中间件MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。

Jmx JMXJava Management Extensions,即Java管理扩展)是一个为应用程序设备系统等植入管理功能的框架。

Jndi   JNDI(Java Naming and Directory InterfaceJava命名和目录接口)是一组在Java应用中访问命名和目录服务API

 

各种简写需要掌握

Perspective       透视图

Overload override的区别

使用习惯很重要,通用都是Eclipse,使用工具可以提高编程效率

 

如何调试变量?

进入debug视图,在该视图下,选中变量选中watch即可

工具使用:

开发一般两个过程,开发编写代码,运行调试使用

一个工作间有很多工程,工程下面有很多项目

新建一个工作间Workspace,再建立一个工程,staticimport(可以refactor,重构改名为Javaenhancejava加强),再建立一个类,好的变成习惯建立类的时候需要加入包名,包名是域名的倒写cn.itheima.day1(应聘什么公司就写什么域名,com.huawei.interview)

类名一般也写StaticImport,通用的名称,方便阅读,便于别人浏览。以后强迫自己用变量名单词拼写类名,养成好习惯。

Workspaceproject 必须要求:会切换工作间与导入项目,在Workspace进行配置,将影响下面的所有project,一个workspace拥有多个project

 

问题:如果设置java的编译和运行版本?

 

Perspectiveview

Perspective就是各种对应的小窗口的集合,某一个显示相关信息的小窗口就是view

学习视频最重要的就是全部学习,掌握全面。

WindowPreferences(参数选择,翻译为首选项,可以控制全局变量)

Java高版本编译出的代码,给低版本运行,会出现错误

更改代码目标,在PreferencesjavaEditor中可以设置

导入已有的Project,选择Import就可以导入。导入后可以选择导入工程的运行jre包,可以使用自带的,也可以使用myeclipse自带的或者java系统自带的,并可选择版本。这样做可以将导入的包限定在某个java环境中

 

重要笔记:

1.

IDE开发工具都支持使用工程化方式管理一个项目的程序开发过程,一般来说一个相对独立的项目就是一个工程,一个项目中涉及的多个java文件,资源文件等用一个工程进行管理。(在这里可以看看以前工作间中的某个工程的结构),在不使用工程管理的情况下,如果一个项目中包括多个Java源文件,编程人员需要精心维护这些源文件之间、以及源文件与其它文件的目录关系,需要逐一编译这些源文件,需要手工启动运行编译后的结果。如果将一个程序的所有源文件用一个工程来组织,开发工具能对所有源文件集中管理,记住每个源文件的位置和相互关系。工程中有哪几个源文件、启动类是哪个、启动参数设置等配置信息在工程中都记录。

 一个workspace可以包含多个project,一个workspace保留了eclipse的一套环境选项的配置,例如,所使用的javacjava命令,等等,细节请查看window->preferences。如果要为eclispe再配置一套环境选项,可以再创建一个workspacePackage explorer视图窗口中的filters菜单项,可以显示空的父包(此功能默认是关闭的)。

2.

一个Perspective代表了若干个view的集合,如何显示各种view

3.

设置单个工程的javacjava,选择工程,右键->properties可以设置javac,右键->run asàopen run dialog可以设置java

先用新的工作间,然后创建新工程,默认的语言即为5.0。先使用Integer  x =3;调整编译器的语法版本为1.4,看到eclipse窗口报错了。然后将这个工程的语言设置为6.0,马上又看到bad version .class运行错误了,这是因为myeclise自带的java1.5。然后再将整个工作间的javac设置为6.0eclipse自带的是jdk1.5),然后看新建工程的javac,也随之改成了6.0,运行则又报bad version.class错误。将工程的编译语言再单独改为5.0,运行则没了问题。整个工作间的语言设置为6.0后,再将整个工作间的java也设置为自己安装的java6

4.

快捷键使用技巧:快捷键的位置:General->keys,设置alt+/键进行内容提示时,要注意解除alt+/键原来的绑定关系,直接输入alt+/就可以找到它的绑定关系,删除绑定关系时也可以使用remove binding这个按钮,课后必须教会大家在eclipse中。代码模板的设置位置:java->editor->Templates画一个Editorà.javaàCompileàRun的过程图,讲课会更加直观和形象了,也更有利于建立一种配置eclipse的感觉。

多想一想:eclipse工作台中的所有工程继承工作台的配置,其中某个工程也可以覆盖工作台的配置!这是不是java面向对象的思想啊?

 

  2   javaBean 内省 

     内省(Introspector)是Java语言对Bean类属性、事件的一种缺省处理方法。例如类A中有属性name,那我们可以通过getName,setName来得到其值或者设置新的值。通过getName/setName来访问name属性,这就是默认的规则。Java中提供了一套API用来访问某个属性的getter/setter方法,通过这些API可以使你不需要了解这个规则,这些API存放于包java.beans中。

    一般的做法是通过类Introspector来获取某个对象的BeanInfo信息,然后通过BeanInfo来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的getter/setter方法,然后我们就可以通过反射机制来调用这些方法。

JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类的方法主要用于访问私有的字段,且方法名符合某种命名规则.如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象JavaBean的属性是根据其中的Setter和getter方法类确定的。

 

IntroSpector 检查,视察,内部检查主要对javabean进行操作。

Javaben是一个特殊的java类。可用参数

Int getAge()      //不接收参数,一个类中的get方法一般不会接收参数的

Void setAge(intage)//接收参数,返回值为void

 

它是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。也就是说,JavaBean的属性是根据方法名称来的。

|---- 如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?JavaBean的属性是根据其中的settergetter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,至于你把它存到哪个变量上,用管吗?如果方法名为getId,中文意思即为获取id,至于你从哪个变量上取,用管吗?去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。

 

|--setId()的属性名àid

|--isLast()的属性名àlast

|--setCPU的属性名是什么?àCPU

|--getUPS的属性名是什么?àUPS

总之:一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。

l  一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean

 

好处:

|---- Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就没什么挑选的余地!

|----JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,怎么做,有一定难度吧?用内省这套api操作JavaBean比用普通类的方式更方便。

 

PropertyDescriptor描述 Java Bean 通过一对存储器方法导出的一个属性。

会用eclipse将一段代码抽取成为方法,并注意参数类型

privatestatic void getProperty(Object pt1, String propertyName) {}

 

例子:

         ReflectPoint pt1 = newReflectPoint (3,5);//ReflectPoint这个类进行操作,并初始化参数为3,5

         String propertyName = "x";//获取x的属性,普通思路,先变成大写X,再加上getX,再利用反射,MethodGetX,得到x属性

         //利用内省方式来操作,利用PropertyDescriptor对象,来获取属性

         PropertyDescriptor pd = newPropertyDescriptor(propertyName,pt1.getClass());//PropertyDescriptor的构造函数,第一个参数是属性名,第二个参数是某个对象的字节码。

         Method methodGetX = pd.getReadMethod();//pd为获得的属性,所以利用其自身方法,如getReadMethod(),就是获取只读方法,getX

         Object retVal =methodGetX.invoke(pt1);//调用只读方法,invoke(pt1)pt1为原始对象。把这个读取后的值赋值给Object的对象。retVal就是原始值,以后都可以用这个名字。

         System.out.println(retVal);//输出,3.利用同样的方法,可以得到y,为5

         Method methodSetX =pd.getWriteMethod();//获取写入的方法

         methodSetX.invoke(pt1, 4) ;//调用set方法,写入4,本来4的位置是写入一个对象,JDK1.5可以使用自动装箱功能。

         retVal = methodGetX.invoke(pt1);//再次获取值

         System.out.println(retVal);//输出就是4

一个复杂的方法(可以尝试一下):

BeanInfobeanInfo = Introspector.getBeanInfo(pt1.getClass());//利用Introspector的静态方法getBeanInfo获取BeanInfo的对象

         PropertyDescriptor[] pds =beanInfo.getPropertyDescriptors();//再调用beanInfogetPropertyDescriptors方法,返回为一个PropertyDescriptor的数组

         Object retVal = null;//建立接收变量

         for(PropertyDescriptorpd  :pds){//建立for循环,遍历

              if(pd.getName().equals(propertyName)){//如果得到的元素与想要取的变量值相同,那么就获取

                   Method methodGetX =pd.getReadMethod();//pd为获得的属性,所以利用其自身方法,如getReadMethod(),就是获取只读方法,getX

                    retVal = methodGetX.invoke(pt1);//获取返回值

                   break;

              }

         }

 

 

JavaBean的复杂内省操作:

[java] view plaincopy

public class IntrospectorTest {  

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

        Number n = new Number(25);  

        String propertyName = "x";//设置属性名  

        Object retVal = getProperty(n, propertyName);//获取属性值  

        System.out.println(retVal);  

          

        Object retVal2 = getProperty2(n, propertyName);  

        System.out.println(retVal2);      

10         Object x=10;  

11         setProperty(n, propertyName, x);//设置属性值  

12         System.out.println(n.getX());             

13     }  

14     //通过BeanInfo方法获取  

15     private static Object getProperty2(Object obj, String propertyName)  

16             throws IntrospectionException, IllegalAccessException,  

17             InvocationTargetException {  

18         BeanInfo beanInfo=Introspector.getBeanInfo(obj.getClass());//beaninfo对象  

19         PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors();//获取属性集合  

20         Object retVal2=null;  

21         for(PropertyDescriptor pd:pds){  

22             //遍历属性,获取指定属性  

23             if(pd.getName().equals(propertyName)){  

24                 Method methodGetX=pd.getReadMethod();  

25                 retVal2=methodGetX.invoke(obj);  

26                 break;  

27             }  

28         }  

29         return retVal2;  

30     }  

31     private static void setProperty(Object obj, String propertyName, Object x)  

32             throws IntrospectionException, IllegalAccessException,  

33             InvocationTargetException {  

34         PropertyDescriptor pd2 = new PropertyDescriptor(propertyName, obj  

35                 .getClass());//建立描述器对象  

36         Method methodSetX=pd2.getWriteMethod();//获取写入属性值方法  

37         methodSetX.invoke(obj,x);  

38     }  

39     private static Object getProperty(Object obj, String propertyName)  

40             throws IntrospectionException, IllegalAccessException,  

41             InvocationTargetException {  

42         PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj  

43                 .getClass());//建立描述器对象  

44         Method methodGetX = pd.getReadMethod();//通过描述器对象获取读取属性值方法  

45         Object retVal = methodGetX.invoke(obj);//通过方法获取属性值  

46         return retVal;  

47     }  

48 }  

 

 

使用BeanUtils工具包操作JavaBean:

Beanutils工具包    

 

l----演示用eclipse如何加入jar包,先只是引入beanutils包,等程序运行出错后再引入logging包。

l----在前面内省例子的基础上,用BeanUtils类先get原来设置好的属性,再将其set为一个新值。

   |-- get属性时返回的结果为字符串,set属性时可以接受任意类型的对象,通常使用字符串。

l----PropertyUtils类先get原来设置好的属性,再将其set为一个新值。

   |-- get属性时返回的结果为该属性本来的类型,set属性时只接受该属性本来的类型。

l----演示去掉JavaBeanReflectPoint)的public修饰符时,BeanUtils工具包访问javabean属性时出现的问题。

直接操作Javabean太繁琐,可以直接导入Beanutils工具包,这样就方便使用。

使用方法:先下载压缩包,解压。解压后里面有API和文件。查阅API可以了解如何使用。然后拷贝jar包。为了使用户可以调用到这个jar包,新建一个Folder(目录),名为lib,然后将jar包拷贝到这个目录下,然后右键选择AddBuildpath,即可。

注意:尽量全部拷入,防止出现缺少jre文件的异常。

 

例子:

         BeanUtils.getProperty(pt1, "x");//直接调用BeanUtilsgetProperty方法,获取pt1中变量x

         BeanUtils.setProperty(pt1, "x","9");//设置变量x的值为9

         System.out.println(BeanUtils.getProperty(pt1,"x").getClass().getName());//java.lang.String,说明变量x设置的时候以String设置,但是返回时是用int,数字返回。

         System.out.println(pt1.getX());

        

        BeanUtils.setProperty(pt1, "birthday.tim","123");//因为birthdayDate的变量,而这个变量中的setDate方法中可以接收time值,所以birthday可以有子变量time值。出现No bean specified异常是因为要将birthday设置一个new Date()。

        System.out.println(BeanUtils.getProperty(pt1,"birthday.time"));//输出time值。

Java7的新特性

Map map = {name:wr,age:18};

BeanUtils.setProperty(map,name,rst);//这个可以更改key

[java] view plaincopy

49 //BeanUtils是义字符串形式对JavaBean进行操作  

50         System.out.println(BeanUtils.getProperty(n, propertyName));//属性是String  

51         BeanUtils.setProperty(n, propertyName, "11");//设置属性值  

52         BeanUtils.setProperty(n, "birthday.time""111");//对象n里面有个birthday对象,birthday对象有time属性  

53         //JavaBean支持延级操作  

54         System.out.println(BeanUtils.getProperty(n, "birthday.time"));//获取属性值  

55           

56         //PropertyUtils以属性本身类型对JavaBean进行操作  

57         PropertyUtils.setProperty(n, propertyName, 9);//设置的属性值是int,JavaBean本身属性类型一致  

58         PropertyUtils.getProperty(n,"x");  

 

 

  3   注解

注解相当于一种标记,在程序中加了注解就等于打上了某种标价,javac编译器,开发工具和其他程序就可以用反射来了解你的类及各种元素有无何种标记,就去完成相应的任务,标记可以加在包,类,字段,方法,方法的参数以及局部变量上.

JDK1.5的注解类:

l----先通过@SuppressWarnings的应用让大家认识和了解一下注解:

       |--  通过System.runFinalizersOnExit(true);的编译警告引出@SuppressWarnings("deprecation")

|-----@Deprecated

      |---直接在刚才的类中增加一个方法,并加上@Deprecated标注,在另外一个类中调用这个方法。

l----- @Override

      |---public boolean equals(Reflect other)方法与HashSet结合讲解

 

总结:

|-----注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。

|----java.lang包,可看到JDK中提供的最基本的annotation

 

注解的应用:

三种:

|----注解类,应用注解类的类,对应用注解类的类进行反射操作的类

注解类:public @interface ItheimaAnnotation

|----在别的类前面加上@ ItheimaAnnotation

|----     if(AnnotationTest.class.isAnnotationPresent(ItheimaAnnotation.class));//检查是否存在某个类

           ItheimaAnnotation annotation=(ItheimaAnnotation)AnnotationTest.class.getAnnotation(ItheimaAnnotation.class);

      System.out.println(annotation);

演示代码:

if(AnnotationTest.class.isAnnotationPresent(ItheimaAnnotation.class));//利用字节码中的isAnnotationPresent方法,检查是否存在某个类

              ItheimaAnnotationannotation =(ItheimaAnnotation)AnnotationTest.class.getAnnotation(ItheimaAnnotation.class);//建立注解类的对象。一旦有注解类之后,这个注解类的子类对象,就可以通过反射的方式获得该类的实例。

         System.out.println(annotation);//输出为:@com.itheima.day2.ItheimaAnnotation()

         }

 

 

元注解:

元注解:就是注解的注解。

@Retention(RetentionPolicy.RUNTIME)//在注解类中加入注解.RetentionPolicy.RUNTIME的意思就是一直保留在运行期间

public@interfaceItheimaAnnotation{

}

@Retention注解,标注这个注解的生命周期。这是枚举。

一个注解的生命周期:RetetionPolicy.SOURCERetetionPolicy.CLASSRetetionPolicy.RUNTIME;分别对应:java源文件-->class文件-->内存中的字节码。

自定义注解以及应用

l----定义一个最简单的注解:public @interface MyAnnotation {}

l----把它加在某个类上:@MyAnnotation public classAnnotationTest{}

l----用反射进行测试AnnotationTest的定义上是否有@MyAnnotation

l----根据发射测试的问题,引出@Retention元注解的讲解,其三种取值(其实这是一个枚举):RetetionPolicy.SOURCERetetionPolicy.CLASSRetetionPolicy.RUNTIME;分别对应:java源文件-->class文件-->内存中的字节码。

 

思考:@Override@SuppressWarnings@Deprecated这三个注解的属性值分别是什么?

答案:java源文件,class文件,运行阶段也就是内存中的字节码。

 

 

l---演示和讲解@Target元注解

     |---Target的默认值为任何元素,设置Target等于ElementType.METHOD,原来加在类上的注解就报错了,改为用数组方式设置{ElementType.METHOD,ElementType.TYPE}就可以了。

@Target也是一个元注解,设置ElementType.METHOD就说明注解只能加在方法上面,加在ElementType.TYPE(意思为各种类的类型),就可以加在类上了。如:

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

 

 

|----元注解以及其枚举属性值不用记,只要会看jdk提供那几个基本注解的API帮助文档的定义或其源代码,按图索骥即可查到,或者直接看java.lang.annotation包下面的类。

 

为注解增加基本属性

什么是注解的属性?

|----一个注解相当于一个胸牌,如果你胸前贴了胸牌,就是传智播客的学生,否则,就不是。如果还想区分出是传智播客哪个班的学生,这时候可以为胸牌在增加一个属性来进行区分。加了属性的标记效果为:@MyAnnotation(color="red")

这样再建立注解类对象的时候可以直接调用,annotation.color()。结果为red

|----定义基本类型的属性和应用属性

|----在注解类中增加String color();

|---- @MyAnnotation(color="red")

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

|---- MyAnnotation a = (MyAnnotation)AnnotationTest.class.getAnnotation(MyAnnotation.class);

|----System.out.println(a.color());

 

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

 

为属性指定缺省值

如果有多个属性,那么在某个属性后添加default就可以设置成为默认属性。

String color() default "yellow";

value属性:

|----String value() default "zxx";

|----如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略value=部分,例如:@MyAnnotation("lhm")

注意:注解添加的类型有:类,枚举,注解,数组,等。

 

数组类型的属性:

|----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) )

|----可以认为上面这个@MyAnnotationMyAnnotaion类的一个实例对象,同样的道理,可以认为上面这个@MetaAnnotationMetaAnnotation类的一个实例对象,调用代码如下:

      MetaAnnotation ma =  myAnnotation.annotationAttr();

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

 

为注解增加高级属性

例子:

[java] view plaincopy

72 @Annotation(color = "red" ,value="abc",arr={2,3,4},annotation2=@MetaAnotation("yyy"))  

73 public class AnnotationTest {  

74     @Annotation("xyz")//value属性在单独存在的情况下可以直接写"abc"  

75 public static void main(String[] args) {  

76     //检查类里是否有注解  

77     if(AnnotationTest.class.isAnnotationPresent(Annotation.class)){  

78         Annotation at=(Annotation)AnnotationTest.class.getAnnotation(Annotation.class);//获取注解对象  

79         System.out.println(at.color());  

80         System.out.println(at.value());  

81         System.out.println(at.arr().length);  

82         System.out.println(at.lamp().nextLamp().name());  

83         System.out.println(at.annotation2());  

84     }  

85 }  

86 }  

87   

88 @Retention(RetentionPolicy.RUNTIME)//注解的生命周期  

89 @Target({ElementType.METHOD,ElementType.TYPE})//为什么是type而不是class?type更精准,包括class,enum  

90 public @interface Annotation {  

91     String color() default "blue"//设置属性的默认值  

92     String value();  

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

94     //EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.RED;//设置枚举类型属性  

95     MetaAnotation annotation2() default @MetaAnotation("xxx");//设置注解类型属性  

96 }  

97   

98 public @interface MetaAnotation {  

99 String value();  

100 }  

 

 

注解的详细语法可以通过看java语言规范了解,即看javalanguagespecification

@Deprecated:已过时

    用 @Deprecated 注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。在使用不被赞成的程序元素或在不被赞成的代码中执行重写时,编译器会发出警告。

@Override:复写父类方法

    表示一个方法声明打算重写超类中的另一个方法声明。如果方法利用此注释类型进行注解但没有重写超类方法,则编译器会生成一条错误消息。

@SuppressWarnings:压缩警告

    指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。如果要在特定的方法中取消显示某个警告,则应该注释该方法而不是注释它的类。

     B.class.isAnnotionPresent(A.class):返回值是boolean型,如果B类中存在A类型注解,则返回true

    A a=B.class.getAnnotion(A.class):如果B类中有A类型的注释,则返回该注释,否则返回null

自定义注解及其应用

    定义一个最简单的注解:public @interface MyAnnotation{}

    把他加在某个类上:@MyAnnotation public class AnnotationTest{}

    Retention表示注释类型的注释要保留多久,有三种取值:RetentionPolicy.SOURCE,RetentionPolicy.CLASS,RetentionPolicy.RUNTIME分别对应:java源文件,class文件,内存中的字节码

    Target:指示注释类型所适用的程序元素的种类。如果注释类型声明中不存在 Target 元注释,则声明的类型可以用在任一程序元素上。如果存在这样的元注释,则编译器强制实施指定的使用限制。

[java] view plaincopy

59 public class AnnotationTest {  

60 public static void main(String[] args) {  

61     //检查类里是否有注解  

62     if(AnnotationTest.class.isAnnotationPresent(Annotation.class)){  

63         Annotation at=(Annotation)AnnotationTest.class.getAnnotation(Annotation.class);//获取注解对象  

64         System.out.println(at);  

65     }  

66 }  

67 }  

68   

69 @Retention(RetentionPolicy.RUNTIME)//注解的生命周期  

70 @Target({ElementType.METHOD,ElementType.TYPE})//为什么是type而不是class,type更精准,包括class,enum  

71   public @interface Annotation {}  

 

 

  4   泛型

泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉”类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样.由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据.

[java] view plaincopy

101 import java.util.ArrayList;  

102   

103 public class GenericTest {  

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

105 ArrayList<String> al=new ArrayList<String>();  

106 ArrayList<Integer> al2=new ArrayList<Integer>();  

107 System.out.println(al2.getClass()==al.getClass());//true,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样  

108   

109 al2.getClass().getMethod("add", Object.class).invoke(al2, "abc");  

110 System.out.println(al2.get(0));//abc,跳过了编译器  

111     }  

112 }  

      ArrayList<E>称为泛型类型,E为类型变量或类型参数

    ArrayList<Integer>称为参数化的类型,Integer为类型参数的实例或实际类型参数,ArrayList为原始类型

    注意:参数化类型与原始类型具有兼容性,参数化类型不考虑参数的继承关系,在创建数组实例时,数组的元素不能使用参数化的类型.

泛型通配符的扩展应用

    泛型中的?通配符:代表任意类型,任意类型传给他都可以接收

问题:定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据

[java] view plaincopy

113 public static void printCollection(Collection<?>collection){  

114         //collection.add("abc");会报错,不确定是什么类型  

115         System.out.println(collection.size());//size与类型无关,任意类型集合都具有size方法  

116         for(Object obj:collection){  

117             System.out.println(obj);  

118         }  

119     }  

 

总结:

使用?通配符可以引用其他各种参数化的类型,?通配符定义的主要作用是作引用,可以调用参与参数化无关的方法,不能调用与参数化有关的方法。

 

?通配符的扩展:

 

    限定通配符的上边界  Vector<? extend Number>x=new Vector<Integer>();//?表示任意Number及Number的子类

    限定通配符的下边界  Vector<? super Integer>x=new Vector<Byte>();//?表示任意Integer及Integer的父类

[java] view plaincopy

120 HashMap<String,Integer> hs=new HashMap<String,Integer>();//建立HashMap集合对象  

121 hs.put("zxx",38);//添加元素  

122 hs.put("lhm",45);  

123   

124 Set<Map.Entry<String,Integer>> entrySet=hs.entrySet();//建立键值对关系  

125 //遍历键值对关系  

126 for(Map.Entry<String, Integer> entry:entrySet){  

127     System.out.println(entry.getKey());  

128     System.out.println(entry.getValue());  

129   }  

 

自定义泛型方法及其应用:

    1.用于放置泛型类型参数的尖括号应在紧邻返回值类型之前,通常使用单个大写字母

    2.只有引用数据类型才可作为泛型方法的实际参数

    3.除了在应用泛型时可以使用extend限定符,在定义泛型时也可以使用

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

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

    6.在泛型中可以同时有多个类型参数,在定义他们的尖括号中用逗号隔开

[java] view plaincopy

130 private static<T> void swap(T arr[],int x,int y){  

131 T temp=arr[x];  

132 arr[x]=arr[y];  

133 arr[y]=temp;}  


 注意:使用swap(new int{1,2,4},1,2)无法调用上述方法,因为编译器不会对new int{}中的int进行拆箱和装箱,new int{}本身已是一个对象

 

练习:

1.编写一个泛型方法,自动将Object类型的对象转换成其他类型

[java] view plaincopy

134 private static <T> T autoConvert(Object obj){  

135         return (T)obj;  

136     }  

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

[java] view plaincopy

137 private static <T> void fillArray(T[] a,T obj){  

138         for(int x=0;x<a.length;x++){  

139             a[x]=obj;  

140         }  

141     }  

 

 

参数类型的推断规则:

 

1.当某个类型变量只在整个参数列表中的所有参数和返回值中一处被使用,根据调用方法使传递的参数类型或返回值类型来决定泛型参数的类型

swap(new String[3],3,4)→static<T> void wap(T[] a,3,4)

2.当某个类型变量在整个参数列表中的所有所有参数和返回值中多出被使用,且调用方法时这多处实际应用类型都对应同一种类型,参数类型即为这种类型

add(2,3)→static <T> T add(T a,T b)

3.当某个类型变量在整个参数列表中的所有所有参数和返回值中多出被使用,且这多处实际应用类型对应了不同的类型,并且没有返回值,此时取多个数据类型最大交集

file(new Integer[3],3,5)→static <T> void fill(T[]a,T v)

4.当某个类型变量在整个参数列表中的所有所有参数和返回值中多出被使用,且这多处实际应用类型对应了不同的类型,并且有返回值,优先考虑返回值类型

5.参数类型的类型推断具有传递性

 

自定义泛型类:

 

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

 public class GenericDao<T> 

{

           private T field1;

           public void save(T obj){}

           public T getById(int id){}

}

 

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

|----GenericDao<String> dao = null;

|----new genericDao<String>();

 

注意:

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

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

 

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

例子:

//dao  data access object--->数据访问对象

publicclassGenericDao<E>

{//在类上定义泛型以后,下面的方法返回类型都默认返回<E>

     publicvoid add(E x){}

     publicE findById(int id)

     {

         returnnull;

     }

     publicvoid delete (E obj){}

     publicvoid delete (intid){ }

     publicstatic <E>voidupdate(E obj)

    {//注意静态不能调用泛型,这里的E必须加上,并且这个泛型的限定和类上的泛型限定不是同一个E       

     }    

    publicSet<E>findByConditions(String where)

     {//返回值是Set<E>类型。

         returnnull;

     }

}

 

注意:

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

 2.当一个变量被声明为泛型时,只能被实例变量和方法调用,而不能被静态变量和静态方法调用,因为静态成员是被所有参数化的类所共享的

 

问题:通过反射获得泛型的参数化类型

[java] view plaincopy

142   class GenericTest{  

143   public static void main(String[] args){  

144 Method applyMethod=GenericTest.class.getMethod("applyVector", Vector.class);  

145 Type[] types=applyMethod.getGenericParameterTypes();  

146 ParameterizedType pType=(ParameterizedType)types[0];//参数化类型  

147 System.out.println(pType.getRawType());//获得实际类型  

148   System.out.println(pType.getActualTypeArguments()[0]);//获得原始类型  

149   }  

150 public static void applyVector(Vector<Date> v1){}}  

  5   类加载器

Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader

 

类加载器也是Java因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是不是java类,这正是BootStrap

 

Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。

 

什么是类加载器和类加载器的作用?

类加载器(class loader)用来加载 Java 类到 Java 虚拟机中的一个特殊类。作用就是将类加载到内存中。

类加载器最高权限是BootStrap,然后是ExtClassLoader,然后是AppClassLoader,然后再是自己的类加载器。自己类的加载器必须继承某个ClassLoader

 

类加载器的委托机制:

Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?

      |---首先当前线程的类加载器去加载线程中的第一个类。

      |---如果类A中引用了类BJava虚拟机将使用加载类A的类装载器来加载类B

      |---还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

每个类加载器加载类时,又先委托给其上级类加载器。

   |---当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?

       |---对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。

 

编写自己的类加载器:

知识讲解:

     |---自定义的类加载器的必须继承抽象类ClassLoader

     |---loadClass方法与findClass方法

     |---defineClass方法

编程步骤:

    |---编写一个对文件内容进行简单加密的程序。

    |---编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。

    |---编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName

实验步骤:

    |---对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如:java MyClassLoader MyTest.class F:\itcast

    |---运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoaderjava MyClassLoader MyTest F:\itcast

    |---用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。

 

删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。

例子:

[java] view plaincopy

public class MyClassLoader extends ClassLoader {    

    

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

        // TODO Auto-generated method stub    

        String srcPath = args[0];    

        String destDir = args[1];    

        FileInputStream fis = new FileInputStream(srcPath);    

        //获取目标目录路径  

        String destFileName =srcPath.substring(srcPath.lastIndexOf('\\')+1);    

10         String destPath = destDir+"\\"+destFileName;    

11         FileOutputStream fos = new FileOutputStream(destPath);   

12        //加密操作  

13         cypher(fis, fos);    

14         fis.close();    

15         fos.close();      

16     }  

17     public static void cypher(InputStream ips,OutputStream ops) throws Exception{    

18         int b = -1;    

19         while((b=ips.read())!=-1){    

20             ops.write(b^0xff);    //文件加密              

21         }    

22      }    

23     private String classDir;    

24     @Override    

25     protected Class<?> findClass(String name) throws ClassNotFoundException {    

26         // 获取文件路径并解密  

27         String classFileName = classDir+"\\"+name+".class";    

28         try {    

29             FileInputStream fis = new FileInputStream(classFileName);    

30             ByteArrayOutputStream bos = new ByteArrayOutputStream();    

31             cypher(fis,bos);    

32             byte[] b = bos.toByteArray();               

33             return defineClass(b, 0, b.length);            

34         } catch (Exception e) {    

35             // TODO Auto-generated catch block    

36             e.printStackTrace();    

37         }        

38         return super.findClass(name);    

39     }    

40         

41     public MyClassLoader(){    

42             

43     }    

44     public MyClassLoader(String classDir){    

45         this.classDir = classDir;    

46             

47     }     

48 }  

调用自己定义的加载器

[java] view plaincopy

49 Class clazz = new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachments");    

50 Date d = (Date)clazz.newInstance();    

51 System.out.println(d.toString());   

 

简单总结:

          不管是JavaBean还是注解还是泛型,之前都有接触,之前面向对象中所建立对象类中就经常有set和get方法,将成员变量定义为私有,对外提供set,get方法设置和获取属性值.注解之前接触最多的就是@override,复写方法时经常会出现的提示.泛型在讲集合的时候学习过,对于操作数据提供了方便.对于类加载器,感觉比较简单.对class文件代码进行加密,再定义一个可以进行解密加载的加载器,从而达到脱离该加载器则无法看懂使用该class文件的目的,即达到了加密效果.对于代理,感觉比较难懂,看了好几遍才理解个大概.代理的主要作用是为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,特别注意代理必须是与目标类具有相同的接口,这是代理的关键所在

 

 

 

 

 

本篇博文到此结束!



 


                                                                                                   

                                                                                               @感谢老师的辛苦批阅