黑马程序员_java高新技术(2)注解、泛型、类加载器

时间:2022-03-06 11:30:47

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

day02  注解!(1.5新特性)
java.lang包中的 注解类型 和java.lang.Annotation
33,注解基本了解
@@基本的注解
@SuppressWarnings("deprecation")//不要提醒我"deprecation" 已过时的方法
public static void main(String[] args) {
    System.runFinalizersOnExit(true);
}
@Deprecated//说明以下方法已过时,别人调用时会显示已过时
public static void sayHello(){
    System.out.println("hi,传智播客");
}

@Override//说明是覆盖父类的方法
public boolean equals(Object obj) {
    
}
总结:
注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则没有某种标记。
以后,javac编译器,开发工具和其它程序可以用反射来了解你的类及各种元素上有无何种标记。
看你有什么标记,就去干相应的事。
标记可以在包,类,字段,方法,方法的参数以及局部变量上。
34,注解的定义与反射调用
@@注解的应用结构
第一步  
    注解类A
    @Interface A
    {}
第二步
    应用了"注解类A"的类B
    @A
    class B
    {}
第三步
    对 "应用了注解类的类" 进行反射操作的类C
    class C{
    B.class.isAnnotationPresent(A.class);//如果B应用了注解A,返回true。
    A a = B.class.getAnnotation(A.class);//返回B上有注解类A,返回A
    }

@@元注解,就是注解的注解 在 java.lang.annotation包中
//元数据,数据的数据
//元信息,信息的信息
@Retention 元注解的讲解//保留此注解的生命周期到哪个阶段
其三种属性值:代表了三个阶段!
RetentionPolicy.SOURCE    java源文件//          编译器要丢弃的注解。
RetentionPolicy.CLASS    class文件(默认阶段)//编译器将把注解记录在类文件中,但在运行时 VM 不需要保留注释。
RetentionPolicy.RUNTIMR 内存中的字节码//编译器将把注解记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。

java源文件--(编译器要丢弃的注释。 javac编译后,丢弃注解信息)--
class文件--(编译器将把注释记录在类文件中,但在运行时 VM 不需要保留注释,类加载器将其加载进JVM后)--
内存中的字节码(一直到JVM还保留注解信息,即运行时还有注解信息,可以进行反射获取)。

@Override的属性值 RetentionPolicy.SOURCE
@Deprecated的属性值 RentionPolicy.RUNTIME
@SuppressWarnings 属性值 RententionPolicy.SOURCE

    //自定义的注解ItcastAnnotation
    @Target({ElementType.METHOD,ElementType.TYPE})//此注解可以 作用的类型
    @Retention(RetentionPolicy.RUNTIME)//保留此注解 到 运行阶段
    public @interface ItcastAnnotation {

    }
    //反射操作
    @ItcastAnnotation
    public class AnnotationTest {

    @SuppressWarnings("deprecation")//不要提醒我"deprecation"已过时的方法
    public static void main(String[] args) {
        
        System.runFinalizersOnExit(true);//此方法过时了,但没有提醒。
        
        if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class)==true){
            ItcastAnnotation annotation = (ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class);
            System.out.println(annotation);
        }
    }
    @Deprecated//说明以下方法已过时,其它地方调用它时,显示过时
    public static void sayHello(){
        System.out.println("hi,传智播客");
    }

}
35为注解添加属性!
@@为注解添加属性!
*定义基本类型的属性和应用属性
在注解类中增加String color();//表示此注解有color属性值,
@ItcastAnnotation(color="red")//设置注解的属性值
*用反射的方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法。
ItcastAnnotation annotation = (ItcastAnnotation) AnnotationTest.class.getAnnotation(ItcastAnnotation.class);
System.out.println(annotation.color());//以方法的形式调用此注解的属性值。
可以认为上面这个 @ItcastAnnotation是ItcastAnnotation类的一个实例对象
*为属性指定缺省值
String color() default "yellow";
*value属性
如果注解中有一个名为value的属性,且你只想设置value的值(即其它属性都采用默认值或没有其它属性),
那么就可以在调用此注解时,省略 value = 的部分,例如 @SuppressWarnings("deprecation")

@@为注解增加高级属性
*数组类型的属性
定义时 int[] arrayAttr() default{1,2,3};
调用时 @ItcastAnnotation(arrayAttr={2,3,4})
如果数组属性中只有一个元素,这时候属性值部分可以省略大括号,即 (arrayAttr = 3);
*枚举类型的属性
定义时 EnumTest.TrafficLamp lamp();
调用时 @ItcastAnnotation(lamp=EnumTest.TrafficLamp.GREEN)
*注解类型的属性
定义时 MetaAnnotation annotationAttr default @MetaAnnotation("xxx");
调用时 @ItcastAnnotation(annotationAttr= @MetaAnnotation("yyy"));
可以认为上面这个 @ItcastAnnotation是 ItcastAnnotation类的一个实例对象,用于的道理,
@MetaAnnotation是MetaAnnotation类的一个实例对象,调用代码如下
MetaAnnotation ma = ItcastAnnotation.annotationAttr();
syso.(ma.value);
*还有class类型,以及这4中类型的数组集合。

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

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import cn.itcast.day1.EnumTest;

//元注解,注解的注解

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)//保留此注解 到 运行阶段
public @interface ItcastAnnotation {
    String color() default "yellow";//有默认属性,调用时可以不用指定属性
    String value();//没有默认值,必须指定具体的属性值,如果此注解只有这一种属性值,那么在设置属性值时,可以省略 value =
    int[] arrayAttr() default {3,4,4};//数组类型的属性
    EnumTest.TrafficLamp lamp()default EnumTest.TrafficLamp.RED;//枚举类型的属性
    MetaAnnotation annotationAttr() default @MetaAnnotation("wzq");//注解类型的属性
}

@ItcastAnnotation(annotationAttr = @MetaAnnotation("cdd"),value="xyz")
public class AnnotationTest {

    /**
     * @param args
     */
    @SuppressWarnings("deprecation")//不要提醒我"deprecation"已过时的方法
    @ItcastAnnotation("xyz")
    public static void main(String[] args) {
        
        System.runFinalizersOnExit(true);
        
        if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class)==true){
            ItcastAnnotation annotation = (ItcastAnnotation)AnnotationTest.class.getAnnotation(ItcastAnnotation.class);
            System.out.println(annotation.color());
            System.out.println(annotation.value());
            System.out.println(annotation.arrayAttr().length);
            System.out.println(annotation.lamp().nextLamp().name());
            System.out.println(annotation.annotationAttr().value());
        }
    }
    @Deprecated//说明以下方法已过时
    public static void sayHello(){
        System.out.println("hi,传智播客");
    }

}
3.6泛型入门
@@泛型(1.5)

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

ArrayList<String> collection2 = new ArrayList<String>();
ArrayList<Integer> collection3 = new ArrayList<Integer>();

System.out.println(collection3.getClass() == collection2.getClass());//true,去类型化后的class文件一样。

//collection3.add("abc");//编译不通过
collection3.getClass().getMethod("add", Object.class).invoke(collection3, "abc");//骗过javac编译器


ArrayList<E>类定义和ArrayList<Integet>类引用中涉及术语。

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>();错误

在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句错误。
Vector<Integer> vectorList[] = new Vector<Integer>[10];
思考题:下面的代码会报错吗?//
Vector v1 = new Vector<String>();
Vector<Object> v = v1;
代码正确,不会报错!
38泛型的通配符 ?扩展应用
@@通配符 ?

public static void printCollection(Collection<?> collection){
//collection.add(1);//无法调用,编译不过
System.out.println(collection.size());//此方法与类型无关。可以调用。
for(Object obj : collection){
    System.out.println(obj);
    }

总结:
使用通过符可以引用其他各种参数化的类型,通配符的变量主要用作引用。
可以调用与参数化无关的方法,不能调用与参数化有关的方法。

通配符的扩张:
限定通配符的上边界:
    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>();错误
限定通配符包括自己!

泛型集合的综合案例!
    HashMap<String,Integer> maps = new HashMap<String,Integer>();
    maps.put("wzq",22 );
    maps.put("cdd",21 );
    maps.put("bzd",23 );
        
    Set<Map.Entry<String,Integer>> entrySet = maps.entrySet();
    for(Map.Entry<String,Integer> entry :entrySet){
        System.out.println(entry.getKey()+":"+entry.getValue());
    }
@@自定义泛型方法以及其应用

43通过反射获得泛型的实际类型参数

    Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);
    Type[] types = applyMethod.getGenericParameterTypes();
    ParameterizedType pType = (ParameterizedType)types[0];
    System.out.println(pType.getRawType());//class java.util.Vector
    System.out.println(pType.getActualTypeArguments()[0]);//class java.util.Date

    public static void applyVector(Vector<Date> v1){
        
    }
44
@@类加载器及其委托机制

javaVM中可以安装多个类加载器,系统默认三个主要类加载器。
BootStrap,ExtClassLoader,AppClassLoader

第一个类加载器不是java类,这正是BootStrap,C++写的。
其他的类加载器是java类,java类的类加载器本身也要被加载器加载。

javaVM中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器时,
需要为它指定一个父级类加载器对象,或 默认采用系统类加载器为其父级类加载。
例子
System.out.println(ClassLoaderTest.class.getClassLoader()
        .getClass().getName());//sun.misc.Launcher$AppClassLoader
System.out.println(System.class.getClassLoader());//null

ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while(loader != null){
    System.out.println(loader.getClass().getName());
    loader = loader.getParent();            
}
System.out.println(loader);
/*结果
sun.misc.Launcher$AppClassLoader 孙子
sun.misc.Launcher$ExtClassLoader 父亲
null    即BootStrap         爷爷
*/

类加载器之间的关系
BootStrap----JRE/lib/rt.jar
--ExtClassLoader----JRE/lib/ext/*.jar、
  --AppClassLoader----CLASSPATH指定的所有jar或目录
    --MyClassLoader自己指定的目录
    --ItcastClassLoader自己指定的目录

@@类加载器的委托机制

javaVM要加载一个类时,到底用哪个类去加载类?
1,首先当前线程的类加载器去加载线程中的第一个类,
2,如果类A还引用了类B,JavaVM将使用类加载器A的类加载器,来加载类B。
3,还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载类。

每个类加载器加载类时,首先会委托给其上级的加载器。
    一直到BootStrap,没有找到的话就一级级的往下找。
    找到了就用哪个类加载器,不再往下找了。
*当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛出ClassNotFoundException,
不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,有多个儿子,找哪一个呢?
*对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包后,
运行结果为ExtClassLoader的原因。
将ClassLoaderTest,export导出到jre/lib/ext/目录下后,上面的例子的输出结果为
/*
sun.misc.Launcher$ExtClassLoader
null
sun.misc.Launcher$ExtClassLoader
null
*/
45
@@自定义类加载器的编写原理分析

*自定义的类加载器必须继承 抽象类ClassLoader
*
1,保留loadClass方法,每一级的loadClass已写好,它们会自动向上寻找上一级的类加载器及其loadClass,。
    所以每一级的目录中要找类文件时,实际调用的是findClass();
2,覆盖findClass方法,
3,用到defineClass方法
模板方法设计模式:

流程在父类定义好了,具体细节(findClass方法)留给子类实现,即覆盖findClass
父类->保留loadClass方法中写的是流程,/覆盖findClass方法,实现细节/,得到class文件的内容转换成字节码-->defineClass
子类1(自己干)
子类2(自己干)

import java.io.*;
public class MyClassLoader extends ClassLoader{

    public static void main(String[] args) throws Exception{
        
        String srcPath = args[0];//被加密的类文件
        //D:\Workspaces\workspace4\javaenhance_my\bin\cn\itcast\day2\ClassLoaderAttachment.class
        
        String destDir = args[1];
        //加密后放 itcast 文件下
        
        FileInputStream fis = new FileInputStream(srcPath);
        String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);
        //加密后的类文件名 ClassLoaderAttachment.class
        
        String destPath = destDir + "\\" + destFileName;
        //加密后的类文件的路径  itcast\ClassLoaderAttachment.class
        
        FileOutputStream fos = new FileOutputStream(destPath);
        cypher(fis,fos);
        fis.close();
        fos.close();
    }
    //加密函数
    private static void cypher(InputStream ips,OutputStream ops)throws Exception{
        int b = -1;
        while((b=ips.read())!=-1){
            ops.write(b ^ 0xff);
        }
    }
    
    private String classDir;
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        
        String classFileName = classDir + "\\" + name + ".class";
        try {
            FileInputStream fis = new FileInputStream(classFileName);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            cypher(fis,bos);
            fis.close();
            byte[] bytes = bos.toByteArray();
            return defineClass(bytes,0,bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public MyClassLoader(){        
    }
    public MyClassLoader(String classDir){
        this.classDir = classDir;
    }
}


@@一个类加载器的高级问题

创建一个web工程
创建一个servlet(可以在web服务器上运行)

public class MyServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        ClassLoader loader = this.getClass().getClassLoader();
        while(loader != null){
            out.println(loader.getClass().getName());
            loader.getParent();
    }
        out.close();
    }

}
步骤:1
点部署
文件copy到 D:\apache-tomcat-6.0.30\webapps\itcastweb
启动tomcat
在浏览器中输入 http://localhost:8080/itcastweb/servlet/MyServlet
(后面资源的位置在xml中<url-pattern>/servlet/MyServlet</url-pattern>)

浏览器中显示如下
org.apache.catalina.loader.WebappClassLoader
org.apache.catalina.loader.StandardClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader

步骤:2
然后将MyServlet.java导出到 D:\jdk1.6.0_11\jre\lib\ext\下
3:重启服务器
4:再在浏览其中打开网址 http://localhost:8080/itcastweb/servlet/MyServlet
发现失败!

原因:
开始时,tomcat里的WebAppCalssLoader加载MyServlet,MyServlet又用到了HttpServlet类,
所以,WebAppCalssLoader又先去加载HttpServlet。

现在将MyServlet.java导出到 D:\jdk1.6.0_11\jre\lib\ext\itcast.jar 后,
ExtClassLoader就会直接去加载MyServlet,MyServlet又用到了HttpServlet类,
所以,ExtClassLoader又先去加载HttpServlet。但是ExtClassLoader无法加载HttpServlet。

解决办法:
将HttpServlet所在的jar包(servlet-api.jar)也放到D:\jdk1.6.0_11\jre\lib\ext\下面
重启tomcat,就行了。

这时候浏览器中只显示:
sun.misc.Launcher$ExtClassLoader

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