• 黑马程序员第三阶段Java基础加强第26天


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

     

    内容导航: 注解 泛型  泛型中的?通配符  泛型集合案例 自定义泛型 泛型方法练习 

              类型参数的类型判断 定义泛型类 通过反射获取方法参数的参数化类型 类加载器

    了解注解及Java提供的几个基本注解
    @SuppressWarnings注解

    public class AnnotationTest { 
     //@SuppressWarnings("deprecation")//这就是一个注解 
     public static void main(String[] args) { 
         System.runFinalizersOnExit(true); 
        } 
    } 
    
    在命令行窗口编译时会提示说这个方法已经过时的提示。把上面的注释去掉最前面的“//”,则在编译时就不会提示了。

    @Deprecated注解
    当自己编写的一个方法,过时的时候又不想删,就可以加注解,以表示该方法已过时,如:
    @Deprecated
    public static void sayHello()
    {
      System.out.println("hello,world!");
    }
    则调用了这个方法后,在编译时就会提示过时。

    @Override 注解
    当覆盖一个方法时,要求参数型要一样,而有时候我们不小写到不同的类型,所以运行时就会出错,而这种错误往往又很难发现原因,这时就可以在方法前面加上注解:@Override ,表示这个方法是覆盖的,当覆盖的方法类型不同时,则在编译时就会报错,这样就容易查找原因了。

    注解的总结:
    注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器、开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包、类、方法、方法的参数以及局部变量上。
    看java.lang包,可以看到后面提供了最基本的annotaion。

     

     

    注解的应用结构图:
                 注解类                                            应用了“注解类”的类               对“应用了注解类的类”进行反射操作的类

    注解就相当于一个你的源程序中要调用的一个类,要在源程序中应用某个注解,得先准备好了这个注解类,就像你要调用某个类,得先要开发好这个类一样。
    实例:

    • 先在窗口的右上角先择java视图,然后回击包名->新建->注释,输入名称:ItcastAnnotation,->完成。代码如下:

    public @interface ItcastAnnotation {

    }
    2、使用这个注释:
    @ItcastAnnotation
    public class AnnotationTest {
      public static void main(String[] args) {

      }
    }
    3、编写一个类用于检查使用了@ItcastAnnotation注释的类有什么东西,检查类当然要使用反射了:
    public class AnnotationMain {
      public static void main(String[] args) {
        if(AnnotationTest.class.isAnnotationPresent(ItcastAnnotation.class)) {
           ItcastAnnotation annotation = (ItcastAnnotation)AnnotationTest.class.getAnnotation( ItcastAnnotation.class);
          System.out.println(annotation);
        }
      }

    }
    这时运行没有打印出任何东西,证明那个if语句返回为false,这是怎么回事呢?
    把ItcastAnnotation类改为如下即可:
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;

    @Retention(RetentionPolicy.RUNTIME)//注明这个定义的注解类的保留政策为:在运行时一直保留
    public @interface ItcastAnnotation {

    }

    元注解、元数据、元信息。注解的注解称为元注解,信息的信息称为元信息。
    @Retention元注解有三种取值:RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME;分别对应java源文件àclass文件à内存中的字节码,
    @Retention默认值为RetentionPolicy.CLASS
    @SuppressWarnings默认值为RetentionPolicy. SOURCE
    @Override默认值为RetentionPolicy. SOURCE
    @Deprecated默认值为RetentionPolicy. RUNTIME

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

    什么是注解的属性
    一个注解相当于一个胸牌,如果你胸前贴了胸牌,就是传智播客的学生,否则就不是。如果想区分出是传智播客哪个班的学生,这时候可以为胸牌再增加一个属性来进行区分。加了属性的标记效果为:@MyAnnotation(color=”read”)
    定义基本类型的属性和应用属性:
    在注解类中增加String color();
    @MyAnnotation(color=”read”)
    用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法
    MyAnnotation a=(MyAnnotation)AnnotationTest.class.getAnnotation(MyAnnotation.class);
    System.out.println(a.color());
    可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象
    为属性指定缺省值:.
    String color() default “yellow”;
    Value属性:
    String value() default “zxx”;
    如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值),这时使用注解时设置value属性可以只写值就行了,如@MyAnnotation(“hello");


    为注解增加高级属性
    数组类型的属性:
    Int[] arrayAttr() default{1,2,3};
    @MyAnnotation(arrayAttr={2,3,4});
    如果数组属性中只有一个元素,这里候属性值部分可心省略大括号
    枚举类型的属性
    EnumTest.TrafficLamp lame();
    @MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)
    注解类型的属性
    MetaAnnotation annotationAttr() default @MetaAnnotation(“xxx“);
    @MyAnnotation(annotationAttr=@MetaAnnotation(“yyy”))
    可以认为上面这个@MetaAnnotation是MetaAnnotation类的一个实例对象,调用代码如下:
    MetaAnnotation ma = myAnnotation.annotationAttr();
    System.out.println(ma.value());

    泛型    返回顶部
    泛型是jdk1.5的所有新特性中最难深入掌握的部分,不过,我们在实际应用中有需要掌握得那么深入,掌握泛型中一些最基本的内容就差不多了。
    ArrayList可心存储各种类型的对象,如:

    public static void main(String[] args) {
      ArrayList als = new ArrayList();

      als.add(1);
       als.add(3L);
       als.add("abc");
       System.out.println(als.get(2));
    }
    而ArrayList<String> als = new ArrayList<String>();代表ArrayList只能存储String类型的对象。这是在集合中使用泛型。
    在JDK文档中的类名后有<T>”或“<E>”说明这个类支持泛型

    //在反射中使用泛型
    //Constructor<String>指示这个构造函数是String类的构造函数,所以用这个构造函数newInstance的对象就是String对象,不需要再进行转换,代码如下:
    Constructor<String> constructor = String.class.getConstructor(StringBuffer.class);
    String str = constructor.newInstance(new StringBuffer("ddd"));
    System.out.println(str);

    泛型是提供给javac编译器使用的,可心限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可心往某个泛型集合中加入其他类型的数据,例如,用反射得到集合,再调用其add方法即可。
    在JDK1.5中,你还可以按可以按原来的方式将各种不同类型的数据装到一个集合中,但编译器会报告unchecked警告。

    ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
    整个ArrayList<E>称为泛型类型
    ArrayList<E>中的E称为类型变量或类型参数
    整个ArrayList<Interger>称为参数化的类型
    ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
    ArrayList<Integer>中的<>读为typeof
    ArrayList称为原始类型
    参数化类型与原始类型的兼容性:
    参数化类型可以引用一个原始类型的对象,编译报告警告,例如,
    Collection<String> c = new Vector();
    原始类型可以引用一个参数化类型的对象,编译报告警告,例如,
    Collection<String> c = new Vector<String>();
    参数化类型不考虑类型参数的继承关系:
    Vector<String> v = new Vector<Object>();//错误!
    Vector<Object> v = new Vector<String>();//也错误!
    假设Vector<String> v = new Vector<Object>();可以的话,那么以后从v中取出的对象当作String用,而V实际指向的对象中可以机器翻译任意的类型对象;
    假设Vector<Object> v = new Vector<String>();可以的话,那么以后可以向v中加入任意的类型对象,而v实际指向的集合中只能装String类型的对象。
    在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
    Vector<Integer> VectorList[] = new Vecotr<Integer>[10];
    思考下面的代码会报错吗?
    Vector v1 = new Vector<String>();
    Vector<Object> v = v1;
    这是不会报错的,因为编译阶段是一句一句编译的,编译第一句的时候不会报错,接着编译第二句的时候,检查到达把一个参数化类型引用变量指向一个原始类型引用变量,这当然是可以的,所以不会报错。

     

    泛型中的?通配符,表示参数化类型可以为任意类型  返回顶部
    实例:设计一个方法,可以打印任意参数化类型的集体对象。
    Public static void main(String args[])
    {
      Collection<String> collection = newArrayList();
      collection.add("abc");
      printCollection(collection);
    }

    public static void printCollection(Collection<?> collection)
    {
      //collection.add(1);这句代码会报错,比如传进来的是一个参数化类型为String的集体,这是不合理的。
      collection.size(); //这句代码可以通过
      for(Object obj: collection) //打印集合中的每一个对象
      {
        System.out.println(obj);
      }
    }
    怎么知道collection.size();这个方法是通用的呢?查JDA文档,只要方法的参数里没有<E>即为通用的,即这个方法是跟参数化的类型无关的

    总结:使用?通配符可以引用各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法,在查JDK文档时,在参数里如果有<E>则表示这个方法与类的参数化有关。

    泛型中的?通配符的扩展

    • 限定通配符的上边界:
      • 正确:Vector<? extends Number> x = new Vector<Integer>();//限定为Number或其子类
      • 错误:Vector<? extends Number> x = new Vector<String>();
    • 限定通配符的下边界:
      • 正确:Vector<? super Integer> x = new Vector<Number>();//限定为Number或父类
      • 正确:Vector<? super Integer> x = new Vector<Byte>();
    • 提示:限定通配符总是包括自己。

    泛型集合的综合案例   返回顶部
    能写出下面的实例即代表掌握了Java的泛型集合类:
    package cn.itcast.day2;

    import java.util.*;

    public class GegericSimple {

    public static void main(String[] args) {
        Map<String,Integer> maps = newHashMap();
        maps.put("张三", 23);
        maps.put("李四", 24);
        maps.put("王五", 25);
    //使用迭代打印出Map映射中的值对,但是Map没有实现Iterator接口,所以不能进行迭代,
    //可以把这个Map转换成Set集合,Set实现了Iterator接口

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

    }

    键-值对在JSP页面中也经常要对Set或Map集合进行迭代:
    <c:forEach items=”${map}” var=”entry”>
    ${entry.key}:${entry.valu}
    </c:froEach>

    由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, T y) { return (T) ( x +y ) }
    在Java中自定义泛型:   返回顶部
    public class GegericSimple {
      private static <T> T add(T x,T y)
      {
         return null;
      }
      public static void main(String[] args) {
        Integer i = add(2,3);//参数都为Integer,返回Interger
        Number n = add(2,3.0);//参数都为Number,返回Number
        Object o = add(2,"a");//参数都为Object,返回Object
      }
    }
    自定义泛型应用实例:交换任意类型的数组中任意两个元素的值
    public static void main(String[] args) {
      String[] strs = new String[] {"a","b","c"};
      int[] nums = new int[] {1,2,3};
      swap(strs,1,2);
      //swap(nums,1,2);//这句代码会报错,T类型的参数只能是引用变量型的,不能为基本类型
    }

    public static <T> void swap(T[] t,int i,int j)
    {
      T temp = t[i];
      t[j] = t[i];
      t[i] = temp;
    }
    Java中的泛型类型(或者泛型)类似于C++中的模板。但是这种相似性仅限于表面,Java语言中的泛型基本上完全是在编译器中实现,用于编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种实现技术称为 擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为Java厂商升级其JVM造成难以逾越的障碍。所以,Java的泛型采用了可以完全在编译器中实现的擦除方法。
    交换数组中的两个元素的位置的泛型方法语法定义如下:
    Private static <T extends Exception> sayHello() throws T
    {
      Try{}
      catch(Exception e)//这里用(T e)就是错误的
      { throw (T)e;}
    }
    用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用个单个大写字母表示。
    只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3.5);语句会报告编译错误。
    除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如,Class.getAnnotation()方法的定义,并且可以用&来指定多个边界,如<V extends Serializable & cloneable> void method(){}
    普通方法、构造方法和静态方法中都可以使用泛型。编译器也不允许创建类型变量的数组。
    也可以用类型变量表示异常,称为参数化的异常,可以用于方法throws列表中,但是不能用于catch子句中。如:

    在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分隔,例如:
    public static <K,V> getValue(K key){return map.get(key);}

    泛型方法的练习题    返回顶部

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

    public static void main(String[] args) {
      Object obj = new String("abc");
      String str = autoConvert(obj);
    }

    private static <T> T autoConvert(Object obj)
    {
      return (T)obj;
    }

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

    public static void main(String[] args) {
      String[] strs2 = new String[5];
      fillArray(strs2,"d");
      for(int i = 0;i<strs2.length;i++)
        System.out.println(strs2[i]);
    }
    private static <T> void fillArray(T[] arr,T obj) {
      for(int i = 0 ;i < arr.length ; i++)
      arr[i] = obj;
    }

    • 采用自定泛型的方式打印出任意参数化类型的集合中的所有内容。
      • 在这种情况下,前面的通配符方案要比泛型方法更有效,当一个类型变量用来表达两个参数之间或者参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用而不仅在签名的时候使用,才需要使用泛型方法。

    Public static void main(String args[])
    {
      Collection<String> collection = newArrayList();
      collection.add("abc");
      printCollection(collection);
    }

    public static <T>void printCollection(Collection<T> collection,T obj)
    {
      collection.add(obj);//这句代码不会报错,这就是与用?的区别
      for(Object obj: collection) //打印集合中的每一个对象
    {
      System.out.println(obj);
    }
    }

    • 定义一个方法,把任意参数类型的集合中的数据安全的复制到相应类型的数组中。

    如果使用如下形式定义:
    static void copy(Collection a,Object[] b);
    则有可能出现A类型的数据复制进B类型的数组中的情况。
    使用泛型方法的定义形式为:
    static <T> void copy (Collection<T> dest,T[] src);
    static <T> void copy2 (T[] dest , T[] src)
    在使用这两个方法时的区别:
    Copy(new Vector<String>(),new String[10]);//不报错
    Copy(new Vector<Date>() ,new String[10]);//报错
    Copy2(new Date[10],new String[10]);//不报错

    • 定义一个方法,把任意参数类型的一个数组中的数据安全地复制到相应类型的另一个数组中。

    类型参数的类型推断   返回顶部

    • 编译器判断泛型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实方法是一种复杂的过程。
    • 根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
      • 当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:

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

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

    int x = (3,3.5f) à static <T> T add(T a,Tb)

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

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

    定义泛型类型   返回顶部
    public class GenericDao {

      public <T> void add(T t) {

      }

      public <T> T findById(int id)
      {
        return null;
      }
      public static void main(String args[])
      {
        GenericDao gd = new GenericDao();
        gd.add(new ReflectPoint(3,5));//这里添加的是ReflectPoint
        String str = gd.findById(1);//这里查的的却是String,显然是不对,但这里不会报错的
      }
    }

    如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
    //dao 是data access object的缩写,这个类的功能主要是crud (create、read、update、delete的缩写),即增加、查询、更新、删除数据。
    public class GenericDao<T> {

      public void add(T t) { //增

      }
      public T findById(int id) //查
      {
        return null;
      }
      public T findByUserName(String name)//查
      {
        return null;
      }
      public void delete(T obj)//删
      {

      }
      public void delete(int id)//删
      {

      }
      public void update(T obj)//改
      {

      }
      public Set<T> findByConditions(String where)
      {
        return null;
      }

      public static void main(String args[])
      {
        GenericDao<ReflectPoint> gd = newGenericDao<ReflectPoint>();
        gd.add(new ReflectPoint(3,5));
        ReflectPoint p = gd.findById(1);
      }
    }
    类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,下面两种方式都可以:
    GenericDao<String> dao = null;
    new GenericDao<String>();
    注意:
    在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
    当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不一叶叶类级别的类型参数。
    问题:类中只有一个方法需要使用泛型,是使用类级别的泛型,还是使用方法级别的泛型?
    答:类级别。

    下面两个方法是否属于重载:
    public static void applyVector(Vector<Date> d){}
    public static void applyVector(Vector<String> d){}
    答:不属于,在编译成字节码后,泛型的参数类型会被擦除掉,所以编译后这两个方法在字节码里面是一模一样的,所以,如果同时编写这两个方法编译时会报错。

    通过反射获取方法参数的参数化类型:    返回顶部
    public static void applyVector(Vector<Date> d){}
    public static void
    main(String args[]){
        Method genericMethod = GenericTest.class.getMethod("applyVector",Vector.class);
        Type[] genericType = genericMethod.getGenericParameterTypes();//获取泛型参数类型
        ParameterizedType pType = (ParameterizedType)genericType[0];//强制转换为“参数化类型”类的类型
        Type[] actualTypes = pType.getActualTypeArguments();//获取参数的实际类型
        Type rawType = pType.getRawType();//获取真实类型
        System.out.println(actualTypes[0]);//返回Date
        System.out.println(rawType);//返回Vector
    }

     

    类加载器  返回顶部
    类加载器即指加载类的工具
    Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载我写位置的类:
    BootStrap,ExtClassLoader,;AppClassLoader
    类加载器本身也是一个Java类,因为其他是Java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是Java类,这正是BootSrap,它是用C++写的。
    Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载

    实例1:
    public class classLoaderTest
    {
      public static void main(String[] args) {
        System.out.println(classLoaderTest.class.getClassLoader().getClass().getName());//结果为AppClassLoader
        //System.out.println(System.class.getClassLoader().getClass().getName());//这句代码为报错因为:
        System.out.println(System.class.getClassLoader());//结果为null
      }
    }
    第二个输出语句会抛出NullPointerException,这个类存放的位置不同classLoaderTest类,而且这个System类是由BootStrap加载器加载的,这个加载器不是一个Java类,所以当然不能获得它的名字了。
    先看一下上面的文件所有项目使用的是哪一版本的JDK,然后把这个文件导出为JAR,导出到对应的JDK版本的jdk1.7\jre\lib\ext\目录下,步骤:右击上面的文件在项目中的文件名à导出—>JavaàJAR文件à在Java文件处输入:C:\Java\jdk1.6.0\jre\lib\ext\itcast.jarà完成à再次运行这个类,会发现每一个输出语句为:ExtClassLoader
    实例2:验证BootStrap,ExtClassLoader,;AppClassLoader的父子关系:

    public static void main(String[] args) {
      ClassLoader loader = classLoaderTest.class.getClassLoader();
      while(loader != null)
      {
        System.out.println(loader.getClass().getName());
        loader = loader.getParent();
      }
      System.out.println(loader);//输出null
    }
    类加载器之间的父子关系和管辖范围图

    类加载器之间的父子关系

     

    类加载器的委托机制
    每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootSrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应该报告ClassNotFoundException异常了。
    当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
    首先当前线程的类加载器去加载线程中的每一个类。
    如果类A中引用了类B,Java虚拟机使用加载类A的类装载器来加载类B。还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
    每个类加载器加载类时,又先委托给其上级类加载器。
    当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?

    自定义加载器
    必须继承ClassLoad类
    模板方法设计模式

    实例:设计一个加密程序,用来加密.class文件:
    package cn.itcast.day2;

    import java.io.*;

    public class MyClassLoader {

      public static void main(String[] args)throws Exception {
        String srcPath = args[0];
        String destDir = args[1];
        String destFileName = srcPath.substring(
        srcPath.lastIndexOf('\\') + 1);
        String destPath = destDir + "\\" + destFileName;

        InputStream in = new FileInputStream(srcPath);
        OutputStream out = new FileOutputStream(destPath);
        cyhper(in,out);
      }

      public static void cyhper(InputStream in,OutputStream out)throws Exception
      {
        int b = -1;
        while((b = in.read()) != -1)
        {
          out.write(b^0xff);//与0xff异或后,则0变1,1变0;
        }
        in.close();
        out.close();
      }

    }
    运行这个类时给类传递一个包含.class文件的路径,和一个要保存到新位置的路径参数。接着把新生成的.class文件复制到原来的.class,接着在程序中调用这个类就会报错。

    把加密的类解密:
    //这个类用来加密
    import java.util.Date;

    public class ClassLoaderAttachmentextends Date {

     @Override
      public String toString() {
        return "hello,world!";
      }
    }

    //自定义的类加载器
    import java.io.*;
    import java.util.Date;

    public class MyClassLoaderextends ClassLoader {

      public static void main(String[] args)throws Exception {
        String srcPath = args[0];
        String destDir = args[1];
        String destFileName = srcPath.substring(
        srcPath.lastIndexOf('\\') + 1);
        String destPath = destDir + "\\" + destFileName;
        InputStream in = new FileInputStream(srcPath);
        OutputStream out = new FileOutputStream(destPath);
        System.out.println(new ClassLoaderAttachment());
        cyhper(in,out);

        MyClassLoader myClassLoader =
        new MyClassLoader("D:\\workspace8\\javaenhance\\itcastlib");
        Class clazz = myClassLoader.findClass("ClassLoaderAttachment");
        Date classLoaderAttachment = (Date)clazz.newInstance();//这里声明的类型不能为ClassLoaderAttachment,
        System.out.println(classLoaderAttachment); //因为此时的这个类是一个乱码文件
      }

      private String classDir;
      @Override
      protected Class<?> findClass(String name)throws ClassNotFoundException   {
        String classPath = classDir + "\\" + name + ".class";
        try
        {
          InputStream in = new FileInputStream(classPath);
          ByteArrayOutputStream out = new ByteArrayOutputStream();
          MyClassLoader.cyhper(in,out);//解密的类的字节码保存在out
          byte[] bytes = out.toByteArray();//把out里的字节码拿出来
          in.close();
          out.close();
          //下面方法将bytes数组 转换为Class类的实例。
          return defineClass(bytes, 0,bytes.length);
        } catch(Exception e)
        {
          e.printStackTrace();
        }
        return super.findClass(name);
      }

       public MyClassLoader(String classDir) {
        this.classDir = classDir;
      }

      public static void cyhper(InputStream in,OutputStream out)throws exception
      {
        int b = -1;
        while((b = in.read()) != -1)
        {
          out.write(b^0xff);
        }
        in.close();
        out.close();
      }
    }

    一个类加载器的高级问题分析
    编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassLoader。
    把MyServlet.class文件打jar包,放到ext目录中(tomcat服务器用哪个jdk就放到到个jdk的ext目录中),重启tomcat,发现找不到HttpServlet的错误。
    把tomcat服务器中lib目录中的servlet.jar也放到jdk的ext目录中,问题解决了,打印的结果是ExtClassLoader。
    父级类加载器加载的类无法引用只能被子级类加载器加载的类,如下图中子类WebAppclassLoader能加载HttpServlet,而父类ExtClassLoader却加载不了,原理如下图:
    MyServlet

    -------------------- android培训java培训、期待与您交流!--------------------
    详情请查看:http://edu.csdn.net/heima/
  • 相关阅读:
    LR学习笔记八 之 初识LR动态链接库
    LR学习笔记七 之 LR录制sql脚本
    LR学习笔记三 之 界面分析
    LoadRunner学习笔记一
    LR学习笔记四 之 调试、日志、动态运行
    协调世界时间 UTC
    InnerText和InnerXml的区别
    避免 showModalDialog 弹出新窗体
    嵌套SELECT语句
    XmlElement和XmlNode的区别
  • 原文地址:https://www.cnblogs.com/runwind/p/4454739.html
Copyright © 2020-2023  润新知