zoukankan      html  css  js  c++  java
  • 20145312 《Java程序设计》第十周学习总结

    20145312 《Java程序设计》第十周学习总结

    学习笔记

    Chapter 17反射与类加载器

    17.1 运用反射

    .class文档反应了类基本信息,因而从Class等API取得类信息的方式就称为反射。

    17.1.1 Class与.class文档
    1. java.lang.Class的实例代表Java应用程序运行时加载的.class文档。
    2. 可以通过Object的getClass()方法,或者是通过.class常量取得每个对象接口。
    3. 在取得Class对象后,就可以操作Class对象的公开方法,取得基本信息。
    代码如下:
    package Reflection;
    
    import static java.lang.System.out;
    public class ClassInfo {
        public static void main(String[] args){
            Class clz=String.class;
            out.println("类名称:"+clz.getName());
            out.println("是否为接口:"+clz.isInterface());
            out.println("是否为基本类型:"+clz.isPrimitive());
            out.println("是否为数组对象:"+clz.isArray());
            out.println("父类名称:"+clz.getSuperclass());
        }
    }
    
    结果如下:
    类名称:java.lang.String
    是否为接口:false
    是否为基本类型:false
    是否为数组对象:false
    父类名称:class java.lang.Object
    
    1. 使用类声明参考名称并不会加载.class文档,可设计测试类来验证。
    2. Some类定义了static区块,默认首次加载.class文档时会执行静态区块。通过文本模式下显示类型,了解何时加载.class文档.
    代码如下:
    package Reflection;
    
    import static java.lang.System.out;
    public class SomeDemo {
        public static void main(String[] args){
            Some s;
            out.println("声明Some参考名称");
            s=new Some();
            out.println("生成Some实例");
        }
    }
    
    结果如下:
    声明Some参考名称
    载入Some.class文档
    生成Some实例
    
    17.1.2 使用Class.forName()

    使用Class.forName()方法实现动态加载类,可用字符串指定类名称来获得类相关信息。

    代码如下:
    package Reflection;
    
    import static java.lang.System.out;
    public class InfoAbout {
        public static void main(String[] args){
            try{
                Class clz=Class.forName(args[0]);
                out.println("类名称:"+clz.getName());
                out.println("是否为接口:"+clz.isInterface());
                out.println("是否为基本类型:"+clz.isPrimitive());
                out.println("是否为数组对象:"+clz.isArray());
                out.println("父类名称:"+clz.getSuperclass());
            }catch (ArrayIndexOutOfBoundsException e){
                out.println("没有指定类名称");
            }catch (ClassNotFoundException e){
                out.println("找不到指定的类"+args[0]);
            }
        }
    }
    

    或者可以使用forName()第二个版本,将initialize设定为false,这样会在建立类实例时执行static区块。

    代码如下:
    package Reflection;
    
    import static java.lang.System.out;
    class Some2{
        static {
            System.out.println("[执行静态区块]");
        }
    }
    public class SomeDemo2 {
        public static void main(String[] args) throws ClassNotFoundException{
            Class clz=Class.forName("Reflection.Some2",false,SomeDemo2.class.getClassLoader());
            out.println("已载入Some2.class");
            Some s;
            out.println("声明Some参考名称");
            s=new Some();
            out.println("生成Some实例");
        }
    }
    
    结果如下:
    已载入Some2.class
    声明Some参考名称
    [执行静态区块]
    生成Some实例
    
    17.1.3 从Class获得信息

    取得Class对象后,就可以取得.class文档中记载的信息:

    包对应类型是:java.lang.Package
    构造函数对应类型是:java.lang.reflect.Constructor
    方法成员对应类型是:java.lang.reflect.Method
    数据成员对应类型是:java.lang.reflect.Field
    

    例如取得指定String类的包名称:

    Package p=String.class.getPackage();
    System.out.println(p.getName());     //显示java.lang
    
    17.1.4 从Class建立对象

    如果不知道类名称,就利用Class.forName()动态加载.class文档,取得Class对象后,利用newInstance()方法建立实例,如:

    Class clz=Class.forName(args[0]);
    Object obj=clz.newInstance();
    

    想采用影片链接库来播放动画,利用接口定义出影片链接库该有的功能。

    代码如下:
    package Reflection;
    
    import java.util.Scanner;
    public class MediaMaster {
        public static void main(String[] args) throws ClassNotFoundException,InstantiationException,IllegalAccessException{
            String playerImp1=System.getProperty("Reflection.playerImp1");
            Player player=(Player) Class.forName(playerImp1).newInstance();
            System.out.println("输入想播放的影片:");
            player.play(new Scanner(System.in).nextLine());
        }
    }
    
    package Reflection;
    
    public class ConsolePlayer implements Player {
        @Override
        public void play(String video){
            System.out.println("正在播放"+video);
        }
    }
    

    指定-DReflection.PlayerImp1=Reflection.ConsolePlayer。

    执行结果如下:
    输入想播放的影片:Harry Potter
    正在播放:Harry Potter
    
    17.1.5 操作对象方法与成员
    1. 使用 invoke()方法来动态调用指定的方法。
    代码如下:
    package Reflection;
    
    public class Student {
        private String name;
        private Integer score;
        public Student(){}
        public Student(String name,Integer score){
            this.name=name;
            this.score=score;
        }
        public void setName(String name){
            this.name=name;
        }
        public String getName(){
            return name;
        }
        public void setScore(Integer score){
            this.score=score;
        }
        public Integer getScore(){
            return score;
        }
    }
    
    17.1.6 动态代理
    静态代理

    在静态代理实现中,代理对象与被代理对象必须实现统一接口,在代理对象中可以实现日志服务。如下可定义一个Hello接口:

    package Reflection;
    
    public interface Hello {
        void hello(String name);
    }
    如果有个HelloSpeaker类操作了Hello接口:
    package Reflection;
    
    public class HelloSpeaker implements Hello{
        public void hello(String name){
            System.out.printf("哈啰,%s%n",name);
        }
    }
    

    在HelloSpeaker类中没有日志程序代码,日志程序代码会放在代理对象中,代理对象同样也要实现Hello接口。

    代码如下:
    package Reflection;
    
    import java.util.logging.*;
    import java.util.logging.Logger;
    public class HelloProxy implements Hello {
        private Hello helloObj;
    
        public HelloProxy(Hello helloObj){
            this.helloObj=helloObj;
        }
        public void hello(String name){
            log("hello方法开始...");//日志服务
            helloObj.hello(name);    //执行商业规则
            log("hello方法结束...");//日志服务
        }
        private void log(String msg){
           // Logger logger=Logger.getLogger(HelloProxy.class.getName()).log(Level.INFO,msg);
        }
    }
    

    静态代理必须为个别接口操作个别代理类,多的代理接口定义多个代理对象,操作和维护代理对象会有不少的负担。

    动态代理
    1. 反射API中提供动态代理相关类,该机制可使一个处理者代理多个接口的操作对象。
    2. 处理者类必须操作java.lang.reflect.InvocationHandler接口。
    代码如下:
     package Reflection;
    
    import java.lang.reflect.*;
    import java.util.logging.*;
    public class LoggingHandler implements InvocationHandler {
        private Object target;
    
        private Object bind(Object target){
            this.target=target;
            return Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    this
            );
        }
        public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
            Object result=null;
            try{
                log(String.format("%s()呼叫开始...",method.getName()));
                result=method.invoke(target,args);
                log(String.format("%s()呼叫结束...",method.getName()));
            }catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e){
                log(e.toString());
            }
            return result;
        }
        private void log(String message){
            Logger.getLogger(LoggingHandler.class.getName()).log(Level.INFO,message);
        }
    }
    

    接下来可使用LoggingHandler的blind()方法来绑定被代理对象:

    package Reflection;
    
    public class ProxyDemo {
        public static void main(String[] args){
            LoggingHandler loggingHandler=new LoggingHandler();
            Hello helloProxy=(Hello) loggingHandler.blind(new HelloSpeaker());
            helloProxy.hello("Justin");
        }
    }
    

    17.2了解类加载器

    17.2.1 类加载器级层架构
    1. 加载器的分类及作用:
    Bootstrap Loader:产生Exrtended Loader和System Loader
    Exrtended Loader:父加载器为Bootstrap Loader
    System Loader:父加载器为Exrtended Loader
    
    1. Bootstrap Loader通常由C撰写而成。若是Oracle的JDK,Bootstrap Loader会搜索系统参数sun.boot.class.path中指定位置的类,默认是JRE目录的classes中的.class文档,或lib目录中.jar文档里的类。

    2. Exrtended Loader由Java撰写而成,会搜索系统参数java.ext.dirs中指定位置的类,默认是JRE目录libextclasses中的.class文档。

    3. System Loader由Java撰写而成,会搜索系统参数java.class.path中指定位置的类,也就是CLASSPATH路径,默认是当前工作路径下的.class文档。

    4. 加载类是会以Bootstrap Loader->Exrtended Loader->System Loader的顺序寻找类,如果所有类加载器都找不到指定类,就会抛出java.lang.NoClassDefFoundError

    5. 对null调用getParent()方法会抛出NullPointedException异常。

    6. ClassLoader可以使用loadClass()方法加载类,使用localClass方法加载类时,默认不会执行静态区块,真正使用类建立实例时才会执行静态区块。

    7. 关系代码如下:

    package Reflection;
    
    import static java.lang.System.out;
    public class ClassLoaderHierarchy {
        public static void main(String[] args){
            Some some=new Some();
            Class clz=some.getClass();
            ClassLoader loader=clz.getClassLoader();
            out.println(loader);
            out.println(loader.getParent());
            out.println(loader.getParent().getParent());
        }
    }
    
    结果如下:
    载入Some.class文档
    sun.misc.Launcher$AppClassLoader@1b84c92
    sun.misc.Launcher$ExtClassLoader@140e19d
    null
    
    17.2.2 建立ClassLoader实例
    1. 使用URLClassLoader来产生新的类加载器,需要java.net.URL作为其参数来指定类加载的搜索路径。使用URLClassLoader的loadClass()方法加载指定类时,会先委托父加载器代为搜索。
    2. 由同一类加载器载入的.class文档,只会有一个Class实例。如果同一.class文档由两个不同的类加载器载入,则会有两份不同的Class实例。
    代码如下:
    package Reflection;
    
    import static java.lang.System.out;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.net.URLClassLoader;
    public class ClassLoaderDemo {
        public static void main(String[] args){
            try {
                String path=args[0];
                String clzName=args[1];
    
                Class clz1=loadClassFrom(path,clzName);
                out.println(clz1);
                Class clz2=loadClassFrom(path,clzName);
                out.println(clz2);
                out.printf("clz1与clz2为%s实例",clz1==clz2 ? "相同":"不同");
            }catch (ArrayIndexOutOfBoundsException e){
                out.println("没有指定类加载路径与名称");
            }catch (MalformedURLException e){
                out.println("加载路径错误");
            }catch (ClassNotFoundException e){
                out.println("找不到指定的类");
            }
        }
        private static Class loadClassFrom(String path,String clzName)throws ClassNotFoundException,MalformedURLException{
            ClassLoader loader=new URLClassLoader(new URL[] {new URL(path)});
            return loader.loadClass(clzName);
        }
    }
    

    path可以输入不在System Loader以上层级类加载器搜索路径的其他路径。

    Chapter 18 自定义泛型、枚举与注释

    18.1 自定义泛型

    泛型定义:
    仅定义在方法上的泛型语法 
    用来限制泛型可用类型的extends与super关键字
    ?类型通配字符的使用
    
    18.1.1 使用extends与?
    1. 若extends之后指定了类与接口,想再指定其他接口,可以使用&连接。
    代码如下:
    package Generics;
    
    import java.util.Arrays;
    public class Sort {
        public static <T extends Comparable<T>> T[] sorted(T[] array){
            T[] arr=Arrays.copyOf(array,array.length);
            sort(arr,0,arr.length-1);
            return arr;
        }
        private static void sort(Object[] array,int left,int right){
            if(left<right){
                int q=partition(array,left,right);
                sort(array,left,q-1);
                sort(array,q+1,right);
            }
        }
        private static int partition(Object[] array,int left,int right){
            int i=left-1;
            for(int j=left;j<right;j++){
                if(((Comparable) array[j]).compareTo(array[right])<=0){
                    i++;
                    swap(array,i,j);
                }
            }
            swap(array,i+1,right);
            return i+1;
        }
        private static void swap(Object[] array,int i,int j){
            Object t=array[i];
            array[i]=array[j];
            array[j]=t;
        }
    }
    
    package Generics;
    
    public class SortDemo {
        public static void main(String[] args){
            String[] strs={"3","2","5","1"};
            for(String s:Sort.sorted(strs)){
                System.out.println(s);
            }
        }
    }
    

    结果如下:

    1
    2
    3
    5
    
    1. 如果B是A的子类,而Node< B>可视为一种Node< A>,则称Node具有共变性或有弹性的。Java泛型不具有共变性,可以使用类型通配字符?与extends来声明变量,使其达到类似的共变性。
    2. 若声明?不搭配extends,则默认为? extends Object。
      Node<?> node = null;//相当于Node<? extends Object>
    3. java的泛型语法在执行时期实际上只会知道是Object类型,由于无法在执行时期获得类型信息,编译程序只能就编译时期看到的类型来做检查,因而造成以上谈及的限制。
    18.1.2 使用super与?
    1. 如果B是A的子类,而Node< A>可视为一种Node< B>,则称为Node具有逆变性。Java泛型不具有逆变性,可以使用类型通配字符?与super来声明,使其达到类似的逆变性的效果。
    2. 若泛型类或接口不具共变性或逆变性,则称为不可变的或严谨的。

    18.2 自定义枚举

    18.2.1 了解java.lang.Enum类
    1. 直接撰写程序继承Enum类会被编译程序拒绝。
    2. Enum是个抽象类,无法直接实例化,它操作了Comparable接口。Action的构造函数被声明为private,因此只能在Action类中调用。
    3. 在JDK1.4之前撰写的API,仍是使用interface定义常数作为枚举值。
    4. Enum的equals()方法与hashCode()方法基本上继承了Object的行为,但被标示为final。由于标示为final,所以定义枚举是,不能重新操作equals()与hashCode(),这是因为枚举成员,在JVM中智慧存在单一实例,Object定义的equals()与hashCode()作为对象相等性比较是适当的定义。
    18.2.2 enum高级运用
    1. values()方法,将内部维护Action枚举实例的数据复制后返回。如果想要知道有哪些枚举成员,就可以使用这个方法。
    2. Enum类可以自行定义构造函数,但不得为公开构造函数,也不可以在构造函数中调用super()。
    3. 在enum中调用构造函数比较特别,直接在枚举成员后加上括号,就可以指定构造函数需要的自变量。
    4. 在static区块中,编译程序仍自行维护name与ordinal的值,接着才是调用自定义构造函数时传入的value值。
    5. 特定值类本体语法:在枚举成员后,直接加上{}操作Command的execute()方法,这代表着每个枚举实例都会有不同的execute()曹组欧,在职责分配上,比switch的方式清楚许多。特定值类本体语法不仅在操作接口时可以使用,,也可以运用在重新定义父类方法。

    18.3 关于注释

    18.3.1 常用标准注释
    1. @Override
      就是标准注释,被注释的方法必须是父类或接口中已定义的方法,请编译程序协助是否真的为重新定义方法。
    2. @Deprecated
      如果某个方法原先存在与API中,后来不建议再使用,可以在该方法上注释。若有用户后续想调用或重新定义这个方法,编译程序会提出警告。对于支持泛型的API,建议明确指定泛型真正类型,如果没有指定,编译程序会提出警告。
    3. @SuppressWarnings
      指定抑制unchecked的警告产生:@SuppressWarnings(value={"unchecked"})
    4. @SafeVarargs
      表明开发人员确定避免了heap pollution问题。heap pollution问题就是编译程序无法检查执行时期的类型错误,无法具体确认自变量类型。
    5. @FunctionalInterface
      让编译程序可协助检查interface是否可做为lambda的目标类型
    18.3.2 自定义注释类型
    1. 标示注释:就是注释名称本身就是信息,对编译程序或应用程序来说,主要是检查是否有注释出现,并作出对应的动作。
      2.相关规则:
    (1)如果注释名称本身无法提供足够信息,设置单值注释 
    (2)注释属性也可以用数组形式指定。 
    (3)在定义注释属性时,如果属性名称为value,则可以省略属性名称,直接指定值。 
    (4)对成员设定默认值,使用default关键字即可。 
    (5)要设定数组默认值,可以在default之后加上{},必要时{}中可放置元素值。
    
    1. 定义注释时,可使用java.lang.annotation.Target限定时可指定java.lang.annotation.ElementType的枚举值。
    2. 在制作JavaDoc文件时,默认不会将注释数据加入文件中,如果想要将注释数据加入文件,可以使用java.lang.annotation.Documented
    3. 默认父类设定的注释,不会被继承至子类,在定义注释时,设定java.lang.annotation.Inherited注释,就可以让注释被子类继承。
    18.3.3 JDK8标注增强功能
    1. ElementType的枚举成员是用来限定哪个声明位置可以进行标注。在JDK8中,增加了两个枚举成员TYPE _PARAMETERTYPE _USE
    2. ElementType.TYPE _ USE可用于标注在各式类型,一个标注如果被设定为ElementType.TYPE_USE,只要是类型名称,都可以进行标注。
    3. @Repeatable
      可以让你在同一个位置重复相同标注
    4. @Filters
      作为收集重复标注信息的容器,而每个@Filters储存各自指定的字符串值。
    18.3.4 执行时期读取注释信息
    1. 自定义注释,默认会将注释信息存储于.class文档,可被编译程序或位码分析工具读取,但执行时期无法读取注释信息,在执行时期读取注释信息,可以使用java.lang.annotation.Retention搭配java.lang.annotation.RetentionPolicy枚举指定。
    2. RetentionPolicy为RUNTIME的时机,在于让注释在执行时期提供应用程序信息,可使用java.lang.reflect.AnnotatedElement接口操作对象取得注释信息。
    3. JDK 8中新增了getDeclaredAnnotation()getDeclaredAnnotationsByType()getAnnotationsByType()三个方法。
      getDeclaredAnnotation()可以让你取回指定的标注,在指定@Repeatable的标注时,会寻找收集重复标注的容器。
      getDeclaredAnnotationsByType()getAnnotationsByType()就不会处理@Repeatable的标记。
  • 相关阅读:
    Java 常见异常种类
    关系型数据库和非关系型数据库的区别
    HTTP中Get与Post的区别
    经典排序算法详细总结。
    新浪实时股票数据接口http://hq.sinajs.cn/list=股票代码
    WinIo驱动级键盘模拟编程
    Logback_日志使用详解(转)
    Java_位运算(移位、位与、或、异或、非)
    算法_bitmap算法
    Selenium_IEDriver操作sendkeys输入速度太慢
  • 原文地址:https://www.cnblogs.com/yx20145312/p/5466702.html
Copyright © 2011-2022 走看看