• 13/14:字符串与类型信息


    字符串

    String—不可变的对象

    String对象是不可变的。String类中看起来会修改String值的方法,实际上都是创建了一个全新的String对象。

    public class Immutable {
      public static String upcase(String s) {
        return s.toUpperCase();
      }
      public static void main(String[] args) {
        String q = "howdy";
        print(q); // howdy
        String qq = upcase(q);
        print(qq); // HOWDY
        print(q); // howdy
      }
    }
    

    如上面的程序中,String的toUpperCase方法会重新创建一个String对象并返回。

    在这里提一点,final修饰的类传给形参,实际传的也是引用。即地址没有改变。

    重载“+” 与 StringBuilde

    看了上面的内容,String是不可变的对象,你可能会觉得下面的代码会应该是由几个String对象组合而成的。然而事实并非如此。编译器自动引入了StringBuilder类,通过append方法生成结果,并存为s。

    String s = "abc" + "mango" + 47 + "aaa";
    

    但是如果是字符串中使用循环,编译器可能会生成多个StringBuilder对象。这时候最好使用StringBuilder而不是String对象。如下:

    public class WhitherStringBuilder {
        public String implicit(String[] fields) {	//不推荐使用
            String result = "";
            for (int i = 0; i < fields.length; i++)
                result += fields[i];
            return result;
        }
    
        public String explicit(String[] fields) {	//推荐使用
            StringBuilder result = new StringBuilder();
            for (int i = 0; i < fields.length; i++)
                result.append(fields[i]);
            return result.toString();
        }
    }
    

    StringBuilder提供了丰富的方法,包括insert()repleace()substring()reverse()

    查看编译后的StringBuilder创建了几个StringBuilder的方法:https://blog.csdn.net/ricky_yangrui/article/details/81941299

    无意识的递归

    举一个例子,如ArrayList.toString(),它会遍历ArrayList中包含的所有对象,调用每个元素上的toString()方法。

    如果希望toString打印的是内存地址,需要使用super.toString(),也就是Object.toString()

    格式化输出

    System.out.format()

    java可以使用System.out.format("我是%s", "一颗遗失的星星");来进行格式化输出。

    有一些特殊符号表示:%d:用作int类型输出、%f:用作浮点型输出,如:

    System.out.format("Row 1: [%d %f]
    ", x, y);
    // or
    System.out.printf("Row 1: [%d %f]
    ", x, y);
    

    Formatter与格式化说明符

    如果需要输出表格,可以用下面的形式:

    public class Receipt {
        private double total = 0;
        private Formatter f = new Formatter(System.out);
    
        public void printTitle() {
            f.format("%-30s %5s %10s
    ", "Item", "Qty", "Price");
            f.format("%-30s %5s %10s
    ", "----", "---", "-----");
        }
    
        public void print(String name, int qty, double price) {
            f.format("%-30.15s %5d %10.2f
    ", name, qty, price);
            total += price;
        }
    
        public void printTotal() {
            f.format("%-30s %5s %10.2f
    ", "Tax", "", total * 0.06);
            f.format("%-30s %5s %10s
    ", "", "", "-----");
            f.format("%-30s %5s %10.2f
    ", "Total", "",
                    total * 1.06);
        }
    
        public static void main(String[] args) {
            Receipt receipt = new Receipt();
            receipt.printTitle();
            receipt.print("Jack's Magic Beans Jack's Magic Beans Jack's Magic Beans", 4, 4.25);
            receipt.print("Princess Peas", 3, 5.1);
            receipt.print("Three Bears Porridge", 1, 14.29);
            receipt.printTotal();
        }
    } /* Output:
    Item              Qty      Price
    ----              ---      -----
    Jack's Magic Be     4       4.25
    Princess Peas       3       5.10
    Three Bears Por     1      14.29
    Tax                         1.42
                               -----
    Total                      25.06
    *///:~
    

    String.format()

    其实String.format()内部,也是创建了一个Formatter对象,上面的代码中可以直接替换成String.format()

    正则表达式

    正则表达式可以解决各种字符串处理相关的问题:匹配、选择、编辑、验证。

    \ 表示一个正则表达式的反斜线,后面的字符具有特殊意义。例如表示一位数字:\d。如果想插入普通反斜线:\\

    但是换行使用单反斜线:

    若想表示“可能有一个负号,后面跟着一位或多位数字”,可以这样表示:-?\d+

    public class IntegerMatch {
        public static void main(String[] args) {
            System.out.println("-1234".matches("-?\d+"));
            System.out.println("5678".matches("-?\d+"));
            System.out.println("+911".matches("-?\d+"));
            System.out.println("+911".matches("(-|\+)?\d+"));		//4
        }
    }
    

    ()表示分组;|表示或操作;?表示出现一次或没有出现;

    上面4处,(-|\+)?表示字符串的起始字符可能是一个-++前要加\转为普通字符),或二者皆没有(?修饰)

    Spring中的Split()

    Spring的Split()表示“将字符串从正则表达式匹配的地方切开”

    public class Splitting {
        public static String knights =
                "Then, when you have found the shrubbery, you must " +
                        "cut down the mightiest tree in the forest... " +
                        "with... a herring!";
    
        public static void split(String regex) {
            System.out.println(
                    Arrays.toString(knights.split(regex)));
        }
    
        public static void main(String[] args) {
            split(" "); // Doesn't have to contain regex chars
            split("\W+"); // Non-word characters
            split("n\W+"); // 'n' followed by non-word characters
        }
    }
    

    创建正则表达式

    B 指定字符B
    xhh 十六进制为oxhh
    uhhhh 十六进制表示为oxhhhh的Unicode字符
    制表符Tab
    换行符
    回车
    f 换页
    e 转义

    一些常用表达式

    . 任意字符
    [abe] 包含a、b和c的任何字符(和able作用相同)
    [a-zA-Z] 从a到z或从A到Z的任意字符(范围)
    [abc[hij]] 任意a、b、c、h、i和j字符(与a|b|c|h|i|j作用相同)(合并)
    [a-z&&[hij]] 任意h、i或j(交)
    s 空白符(空格、tab、换行、换页和回车)
    S 非空白符([^s])
    d 数字[0-9]
    D 非数字[^0-9]
    w 词字符[a-zA-Z0-9]
    W 非词字符[^w]

    逻辑操作符

    XY Y跟在X后
    X|Y X或Y
    (X) 捕获组。

    边界匹配符

    ^ 一行的起始 B 非词的边界
    $ 一行的结束 G 前一个匹配的结束
     词的边界

    我们学习正则表达式,够用就行了,所以这里不写太多,若想了解的朋友可以翻阅相关资料,其中,下面的网站或许对你生成java的正则表达式程序有帮助。

    https://www.sojson.com/regex/generate

    类型信息

    java是如何让我们在运行时识别对象和类的信息?主要有两种方式:一种是传统的RTTI(运行时类型识别),它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制。

    为什么需要RTTI(运行时类型识别)

    如果你希望大部分代码尽可能少地了解对象的具体类型,而只是与对象家族的某一个通用表示打交道,通俗地说只使用对象家族中的某个行为(方法),比如让一个动物家族“叫”,或“跳”,但是不关心是什么动物。使用多态会更容易写,更容易读,且更便于维护;设计业更容易实现、理解和改变。所以,动态是面向对象编程的基本目标。

    如果我们想要对某个对象家族进行批量操作,比如,对所有形状的对象(三角形、四边形等)进行旋转,但是忽略圆(对圆旋转没有意义),我们就可以使用RTTI,查询出对象确切类型,从而剔除特例。

    Class对象

    要理解RTTI(运行时类型识别,下面简称RTTI)在java中的工作原理,首先要理解Class对象。Class对象就是用来创建类的所有的“常规”对象的。java使用Class对象来执行其RTTI。

    每个类都有一个Class对象。为了生成这个类的对象,运行这个程序的java虚拟机(JVM)将使用被称为“类加载器”的子系统。

    类加载器子系统实际上可以包含一条类加载器链,但只有一个原生类加载器,它是jvm的一部分。

    所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法

    因此,java程序在它开始运行之前并非被完全加载,其各个部分是在必须时才被加载的。

    类加载器首先检查这个类的Class对象是否已经被加载。如果未加载,默认的类加载器就会根据类名查找.class文件。在这个类被加载时,它们会接收验证,以确保其没有被破坏,并且不包含不良java代码。

    一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。下面的例子可以说明这一点

    class Candy {
        static {
            print("Loading Candy");
        }
    }
    class Gum {
        static {
            print("Loading Gum");
        }
    }
    class Cookie {
        static {
            print("Loading Cookie");
        }
    }
    public class SweetShop {
        public static void main(String[] args) {
            print("inside main");
            new Candy();
            print("After creating Candy");
            try {
                Class.forName("typeinfo.Gum");
            } catch (ClassNotFoundException e) {
                print("Couldn't find Gum");
            }
            print("After Class.forName("Gum")");
            new Cookie();
            print("After creating Cookie");
        }
    }
    

    无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。Class.forName()是实现途径,因为你不需要为了获得Class引用而持有该类型的对象。若已有类型的对象,可以通过getClass()来获取Class引用。Class包含很多有用的方法,下面是其中一部分。

    Class.forName(pakage.ClassName)返回一个class对象,必须使用全限定名;

    getName()产生全限定的类名,getSimpleName()产生不包含包名的类名和getCannonicalName()全限定的类名。

    getInterfaces()返回Class对象,表示在感兴趣的Class对象中所包含的接口。

    getSuperclass()查询直接基类

    newInstance()是实现“虚拟构造器”的一种途径,它表示:“我不知道你的确切类型,但是无论如何要正确创建你自己。”,使用newInstance()来创建的类,必须带有默认构造器。

    类字面常量— Object.class

    java还提供了另一种方法生成Class对象的引用:类字面常量

    FancyToy.class;

    这样做对比Class.forName()来说:更简单、更安全(编译时就会收到检查,不用置于try块中)、更高效(根除了forName()方法的调用)。

    类字面敞亮不仅可以应用与普通类,也可以应用于接口、数组、基本数据类型。对于基本类型的包装器类,有一个标准字段TYPE,指向对应的基本数据类型的Class对象。

    TYPE和Class_2019-07-08_16-40-21

    建议使用.class的形式,与普通类保持一致。

    注意:当使用.class来创建对Class对象的引用时,不会自动地初始化该Class对象。为了使用类需要3个步骤:

    1. 加载,由类加载器执行。
    2. 链接。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其他类的所有引用。
    3. 初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。
    class Initable {
        static final int staticFinal = 47;
        static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
    
        static {
            System.out.println("Initializing Initable");
        }
    }
    class Initable2 {
        static int staticNonFinal = 147;
    
        static {
            System.out.println("Initializing Initable2");
        }
    }
    class Initable3 {
        static int staticNonFinal = 74;
    
        static {
            System.out.println("Initializing Initable3");
        }
    }
    public class ClassInitialization {
        public static Random rand = new Random(47);
    
        public static void main(String[] args) throws Exception {
            Class initable = Initable.class;
            System.out.println("After creating Initable ref");
            // Does not trigger initialization:
            System.out.println(Initable.staticFinal);
            // Does trigger initialization:
            System.out.println(Initable.staticFinal2);
            // Does trigger initialization:
            System.out.println(Initable2.staticNonFinal);
            Class initable3 = Class.forName("Initable3");
            System.out.println("After creating Initable3 ref");
            System.out.println(Initable3.staticNonFinal);
        }
    }/* Output:
    After creating Initable ref
    47
    Initializing Initable
    258
    Initializing Initable2
    147
    Initializing Initable3
    After creating Initable3 ref
    74
    *///:~
    

    如上面程序,仅仅使用.class获取类的引用不会发生初始化,而Class.forName()会立即引发初始化。

    如果一个static final 值是“编译期常量”,就像Initable.staticFinal 那样,那么这个值不需要对Initable类进行初始化就可以被读取。但是像Initable.staticFinal2的访问将枪支进行类初始化(它不是一个“编译期常量”)

    泛化的Class引用

    Class引用总是指向某个Class对象,它可以制造类的实例,并包含可作用于这些实例的所有方法代码。

    通过使用泛型语法,可以让编译器枪支执行额外的类型检查:

    public class GenericClassReferences {
        public static void main(String[] args) {
            Class intClass = int.class;
            Class<Integer> genericIntClass = int.class;
            genericIntClass = Integer.class; // Same thing
            intClass = double.class;	//1
            //genericIntClass = double.class; // Illegal
        }
    } ///:~
    

    代码1处对普通Class没有警告信息,但是下行使用泛型则编译出错。

    Class<Number> genericNumberClass = int.class;

    上面代码似乎不出错(Integer继承自Number)。但是它无法工作(Integer Class对象不是Number Class对象的子类)。这点在后面的章节讨论。

    Class<?>

    在泛型中使用?表示“任何事物”,下面的程序可以编译通过:

    public class WildcardClassReferences {
      public static void main(String[] args) {
        Class<?> intClass = int.class;
        intClass = double.class;
      }
    } ///:~
    

    在java se5中,Class优于平凡的Class,即便它们等价。Class的好处是它表示你并非由于疏忽,而是你选择了非具体的版本。

    Class<?>与extends结合

    Class<?>与extends结合,可以创建一个范围,或者说限定为某种类型,或者该类型的任何子类型:

    public class BoundedClassReferences {
      public static void main(String[] args) {
        Class<? extends Number> bounded = int.class;
        bounded = double.class;
        bounded = Number.class;
        // Or anything else derived from Number.
      }
    } ///:~
    

    总结

    使用泛型语法的原因仅仅是为了方便编译期类型检查。

    当使用泛型语法的Class对象时,newInstance()会返回确切的类型。这在某种程度上有些受限:

    public class GenericToyTest {
      public static void main(String[] args) throws Exception {
        Class<FancyToy> ftClass = FancyToy.class;
        // Produces exact type:
        FancyToy fancyToy = ftClass.newInstance();
        Class<? super FancyToy> up = ftClass.getSuperclass();
        // This won't compile:
        // Class<Toy> up2 = ftClass.getSuperclass();
        // Only produces Object:
        Object obj = up.newInstance();
      }
    }
    

    上面代码中, Class<? super FancyToy> up只是说是FancyToy的超类,而不能直接接受 Class<Toy>up.newInstance()的返回不是很精确,只能是Object类型。

    新的转义语法case()

    class Building {
    }
    
    class House extends Building {
    }
    
    public class ClassCasts {
        public static void main(String[] args) {
            Building b = new House();
            Class<House> houseType = House.class;
            House h = houseType.cast(b);	//1
            h = (House) b; // ... or just do this.  2
        }
    } ///:~
    

    1很少用,2少了很多工作。

    类型转换前先做检查

    我们已知的RTTI(运行时类型识别)形式包括:

    1. 传统的类型转换,如向下转型。由RTTI(运行时类型识别)。错误时抛出ClassCastException异常。

    2. 代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。

    3. RTTI在java中海油第三种形式,就是关键字instanceof

      if(x instanceof Dog)
      	(Dog)x;
      

    动态的instanceof

    Class.isInstance方法提供了一种动态的测试对象的途径。

    public class PetCount3 {
        static class PetCounter
                extends LinkedHashMap<Class<? extends Pet>, Integer> {
            public PetCounter() {
                super(MapData.map(LiteralPetCreator.allTypes, 0));
            }
    
            public void count(Pet pet) {
                // Class.isInstance() eliminates instanceofs:
                for (Map.Entry<Class<? extends Pet>, Integer> pair
                        : entrySet())
                  	//使用isInstance()动态验证对象类型
                    if (pair.getKey().isInstance(pet))
                        put(pair.getKey(), pair.getValue() + 1);
            }
    
            public String toString() {
                StringBuilder result = new StringBuilder("{");
                for (Map.Entry<Class<? extends Pet>, Integer> pair
                        : entrySet()) {
                    result.append(pair.getKey().getSimpleName());
                    result.append("=");
                    result.append(pair.getValue());
                    result.append(", ");
                }
                result.delete(result.length() - 2, result.length());
                result.append("}");
                return result.toString();
            }
        }
    
        public static void main(String[] args) {
            PetCounter petCount = new PetCounter();
            for (Pet pet : Pets.createArray(20)) {
                printnb(pet.getClass().getSimpleName() + " ");
                petCount.count(pet);
            }
            print();
            print(petCount);
        }
    } /* Output:
    Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric
    {Pet=20, Dog=6, Cat=9, Rodent=5, Mutt=3, Pug=3, EgyptianMau=2, Manx=7, Cymric=5, Rat=2, Mouse=2, Hamster=1}
    *///:~
    

    可以看到上面代码的好处是添加新类型时,只需要改变LiteralPetCreator.java的数组即可(这里所有的代码都可以在《java编程思想第四版》这本书中找到),而不需要改变上面的代码。

    递归计数

    isAssignableFrom()校验传递的对象确实属于一个特定的继承结构,比如:Mouse.class.isAssignableFrom(Pet.class)

    下面是使用递归的方式计数:

    public class TypeCounter extends HashMap<Class<?>, Integer> {
        private Class<?> baseType;
    
        public TypeCounter(Class<?> baseType) {
            this.baseType = baseType;
        }
    
        public void count(Object obj) {
            Class<?> type = obj.getClass();
            if (!baseType.isAssignableFrom(type))	//判断类型是否属于某种继承结构
                throw new RuntimeException(obj + " incorrect type: "
                        + type + ", should be type or subtype of "
                        + baseType);
            countClass(type);
        }
    
        private void countClass(Class<?> type) {
            Integer quantity = get(type);
            put(type, quantity == null ? 1 : quantity + 1);
            Class<?> superClass = type.getSuperclass();
            if (superClass != null &&
                    baseType.isAssignableFrom(superClass))
                countClass(superClass);		//递归计算父类数量
        }
    
        public String toString() {
            StringBuilder result = new StringBuilder("{");
            for (Map.Entry<Class<?>, Integer> pair : entrySet()) {
                result.append(pair.getKey().getSimpleName());
                result.append("=");
                result.append(pair.getValue());
                result.append(", ");
            }
            result.delete(result.length() - 2, result.length());
            result.append("}");
            return result.toString();
        }
      
        public static void main(String[] args) {
            TypeCounter counter = new TypeCounter(Pet.class);
            for(Pet pet : Pets.createArray(20)) {
                printnb(pet.getClass().getSimpleName() + " ");
                counter.count(pet);
            }
            print();
            print(counter);
        }
    } 
    

    instanceof与Class的等价性

    以instanceof(或isInstance()的形式,它们产生的结果相同)的形式与直接比较Class对象有一个很重要的差别

    • instanceofisInstance()生成的结果完全一样,保持了类型的概念,指“你是这个类吗?或者你是这个类的派生类吗”
    • equals()==也一样,但是它们没有考虑继承,指是否是确切的类型。
    class Base {
    }
    class Derived extends Base {
    }
    public class FamilyVsExactType {
        static void test(Object x) {
            print("Testing x of type " + x.getClass());
            print("x instanceof Base " + (x instanceof Base));
            print("x instanceof Derived " + (x instanceof Derived));
            print("Base.isInstance(x) " + Base.class.isInstance(x));
            print("Derived.isInstance(x) " +
                    Derived.class.isInstance(x));
            print("x.getClass() == Base.class " +
                    (x.getClass() == Base.class));
            print("x.getClass() == Derived.class " +
                    (x.getClass() == Derived.class));
            print("x.getClass().equals(Base.class)) " +
                    (x.getClass().equals(Base.class)));
            print("x.getClass().equals(Derived.class)) " +
                    (x.getClass().equals(Derived.class)));
        }
        public static void main(String[] args) {
            test(new Derived());
        }
    }
    

    反射:运行时的类信息

    人们想要在运行时获取类的信息的另一个动机,是希望提供在跨网络的远程平台上创建和运行对象的能力。这被称为远程方法调用(RMI)。它允许一个java程序将对象分布到多台机器上。为什么需要这样的能力?想想负载均衡就知道了。

    Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是有JVM在运行时创建的,用来表示未知类里对应的成员。这样就可以使用:

    • Constructor创建新的对象
    • 用get()和set()方法读取和修改与Field对象关联的字段
    • 用invoke()方法调用与Method对象关联的方法。
    • 还可以调用getFields()、getMethods()和getConstructors()返回字段、方法以及构造器的对象数组

    这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。

    反射机制并没有什么神奇之处。JVM只是简单地检查这个对象,看它属于哪个特定的类(就像RTTI那样)。在用它做其他事情之前要先加载那个类的Class对象:要么在本地机器上获取,要么在网络上获取。

    反射与RTTI的区别是,RTTI在编译时打开和检查.class文件,而反射是在运行时打开和检查.class文件的。

    类方法提取器

    通常不需要直接用到反射,然而在你需要创建更加动态的代码时会很有用。反射在java中用来支持其他特性:对象序列化和JavaBean。

    Class<?> c = Class.forName(args[0]);	//args[0]是接收到的参数
    Method[] methods = c.getMethods();      //获得所有方法
    Constructor[] ctors = c.getConstructors();      //获得所有构造函数
    

    动态代理

    代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替“实际对象”的对象。这些操作通常涉及与“实际”对象的通信,因此代理通常充当“中间人”的角色。

    下面是一个简单的代理模式的例子

    interface Interface {
        void doSomething();
    
        void somethingElse(String arg);
    }
    
    class RealObject implements Interface {
        public void doSomething() {
            print("doSomething");
        }
    
        public void somethingElse(String arg) {
            print("somethingElse " + arg);
        }
    }
    
    class SimpleProxy implements Interface {
        private Interface proxied;
    
        public SimpleProxy(Interface proxied) {
            this.proxied = proxied;
        }
    
        public void doSomething() {
            print("SimpleProxy doSomething");
            proxied.doSomething();
        }
    
        public void somethingElse(String arg) {
            print("SimpleProxy somethingElse " + arg);
            proxied.somethingElse(arg);
        }
    }
    
    class SimpleProxyDemo {
        public static void consumer(Interface iface) {
            iface.doSomething();
            iface.somethingElse("bonobo");
        }
    
        public static void main(String[] args) {
            consumer(new RealObject());
            consumer(new SimpleProxy(new RealObject()));
        }
    } /* Output:
    doSomething
    somethingElse bonobo
    SimpleProxy doSomething
    doSomething
    SimpleProxy somethingElse bonobo
    somethingElse bonobo
    *///:~
    

    在上诉代码中,SimpleProxy 增强了RealObject 的行为。

    在任何时刻,只要你想要将额外的操作冲“实际”对象中分离到不同的地方,特别是当你希望很容易得做出修改,代理就很有用。

    java的动态代理比代理的思想更向前迈进了一步。

    class DynamicProxyHandler implements InvocationHandler {
        private Object proxied;
    
        public DynamicProxyHandler(Object proxied) {
            this.proxied = proxied;
        }
    
        public Object
        invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            System.out.println("**** proxy: " + proxy.getClass() +
                    ", method: " + method + ", args: " + args);
            if (args != null)
                for (Object arg : args)
                    System.out.println("  " + arg);
            return method.invoke(proxied, args);	//1
        }
    }
    
    class SimpleDynamicProxy {
        public static void consumer(Interface iface) {
            iface.doSomething();
            iface.somethingElse("bonobo");
        }
    
        public static void main(String[] args) {
            RealObject real = new RealObject();
            consumer(real);
            // Insert a proxy and call again:
            Interface proxy = (Interface) Proxy.newProxyInstance(
                    Interface.class.getClassLoader(),
                    new Class[]{Interface.class},
                    new DynamicProxyHandler(real));
            consumer(proxy);
        }
    } 
    

    通过调用静态方法Proxy.newProxyInstance()可以创建动态代理

    (Interface) Proxy.newProxyInstance(
                    Interface.class.getClassLoader(),
                    new Class[]{Interface.class},
                    new DynamicProxyHandler(real));
    
    • 第一个参数是一个类加载器。
    • 第二个参数是希望该代理实现的接口列表(不是类或抽象类)
    • 第三个参数是“实际”对象的引用,从而使得调用处理器在执行其中介任务时,可以将请求转发。

    invoke()方法传递进来一个代理对象。在invoke()内部,在代理上调用方法时要格外小心,因为对接口的调用将被重定向为对代理的调用。

    通常,会执行被代理的操作,然后使用Method.invoke()将请求转发给被代理对象,并传入必要参数,如上面彩色代码1处。

    空对象

    空对象可以接受传递给它的所代表的对象的消息,但是将返回表示为实际上并不存在的任何“真实”对象的值。

    何时使用空对象?比如:许多系统都有一个Person类,而在代码中,有很多情况是你没有一个实际的人(或者你有,但是你还没有这个人的全部信息),这时,我们可以使用空对象。

    以Person为例,创建一个空对象:

    public interface Null {} 
    class Person {
        public final String first;
        public final String last;
        public final String address;
    
        // etc.
        public Person(String first, String last, String address) {
            this.first = first;
            this.last = last;
            this.address = address;
        }
    
        public String toString() {
            return "Person: " + first + " " + last + " " + address;
        }
    
        public static class NullPerson
                extends Person implements Null {
            private NullPerson() {
                super("None", "None", "None");
            }
    
            public String toString() {
                return "NullPerson";
            }
        }
    
        public static final Person NULL = new NullPerson();
    } ///:~
    

    通常,空对象都是单例(用了final修饰)。只能在构造器中设置NullPerson的值,不能修改NullPerson。

    如何测试一个空对象:使用instanceof探测泛华的Null还是更具体的NullPerson,由于是单例(地址不变,值不变),还可以使用equals()甚至==来与Person.Null比较。

  • 相关阅读:
    知识小罐头05(tomcat8请求源码分析 上)
    知识小罐头04(idea+maven+部署war包到tomcat 下)
    知识小罐头03(idea+maven+部署war包到tomcat 上)
    带着新人学springboot的应用13(springboot+热部署)
    带着新人学springboot的应用12(springboot+Dubbo+Zookeeper 下)
    带着新人学springboot的应用11(springboot+Dubbo+Zookeeper 上)
    带着新人学springboot的应用10(springboot+定时任务+发邮件)
    带着新人学springboot的应用09(springboot+异步任务)
    带着新人学springboot的应用08(springboot+jpa的整合)
    windows最简单的局部截图工具
  • 原文地址:https://www.cnblogs.com/sean-zeng/p/11246295.html
Copyright © 2020-2023  润新知