zoukankan      html  css  js  c++  java
  • 类型信息

    编译前 - 强制

    运行时 - 反射

    // typeinfo/Shapes.java
    import java.util.stream.*;
    
    abstract class Shape {
        void draw() { System.out.println(this + ".draw()"); }
        @Override
        public abstract String toString();
    }
    
    class Circle extends Shape {
        @Override
        public String toString() { return "Circle"; }
    }
    
    class Square extends Shape {
        @Override
        public String toString() { return "Square"; }
    }
    
    class Triangle extends Shape {
        @Override
        public String toString() { return "Triangle"; }
    }
    
    public class Shapes {
        public static void main(String[] args) {
            Stream.of(
                new Circle(), new Square(), new Triangle())
                .forEach(Shape::draw);
        }
    }

    输出

    Circle.draw()
    Square.draw()
    Triangle.draw()
    • 编译期,stream 和 Java 泛型系统确保放入 stream 的都是 Shape 对象(Shape 子类的对象也可视为 Shape 的对象),否则编译器会报错;
    • 运行时,自动类型转换确保了从 stream 中取出的对象都是 Shape 类型。

    其实构造器也是类的静态方法,虽然构造器前面并没有 static 关键字。所以,使用 new 操作符创建类的新对象,这个操作也算作对类的静态成员引用。

    class是用到再加载,static 初始化是在类加载时进行的。

    // typeinfo/SweetShop.java
    // 检查类加载器工作方式
    class Cookie {
        static { System.out.println("Loading Cookie"); }
    }
    
    class Gum {
        static { System.out.println("Loading Gum"); }
    }
    
    class Candy {
        static { System.out.println("Loading Candy"); }
    }
    
    public class SweetShop {
        public static void main(String[] args) {
            System.out.println("inside main");
            new Candy();
            System.out.println("After creating Candy");
            try {
                Class.forName("Gum");
            } catch(ClassNotFoundException e) {
                System.out.println("Couldn't find Gum");
            }
            System.out.println("After Class.forName("Gum")");
            new Cookie();
            System.out.println("After creating Cookie");
        }
    }

    输出

    inside main
    Loading Candy
    After creating Candy
    Loading Gum
    After Class.forName("Gum")
    Loading Cookie
    After creating Cookie

    class引用

    // typeinfo/toys/ToyTest.java
    // 测试 Class 类
    // {java typeinfo.toys.ToyTest}
    package typeinfo.toys;
    
    interface HasBatteries {}
    interface Waterproof {}
    interface Shoots {}
    
    class Toy {
        // 注释下面的无参数构造器会引起 NoSuchMethodError 错误
        Toy() {}
        Toy(int i) {}
    }
    
    class FancyToy extends Toy
    implements HasBatteries, Waterproof, Shoots {
        FancyToy() { super(1); }
    }
    
    public class ToyTest {
        static void printInfo(Class cc) {
            System.out.println("Class name: " + cc.getName() +
                " is interface? [" + cc.isInterface() + "]");
            System.out.println(
                "Simple name: " + cc.getSimpleName());
            System.out.println(
                "Canonical name : " + cc.getCanonicalName());
        }
    
        public static void main(String[] args) {
            Class c = null;
            try {
                c = Class.forName("typeinfo.toys.FancyToy");
            } catch(ClassNotFoundException e) {
                System.out.println("Can't find FancyToy");
                System.exit(1);
            }
    
            printInfo(c);
            for(Class face : c.getInterfaces())
                printInfo(face);
    
            Class up = c.getSuperclass();
            Object obj = null;
    
            try {
                // Requires no-arg constructor:
                obj = up.newInstance();
            } catch(InstantiationException e) {
                System.out.println("Cannot instantiate");
                System.exit(1);
            } catch(IllegalAccessException e) {
                System.out.println("Cannot access");
                System.exit(1);
            }
    
            printInfo(obj.getClass());
        }
    }

    输出

    Class name: typeinfo.toys.FancyToy is interface?
    [false]
    Simple name: FancyToy
    Canonical name : typeinfo.toys.FancyToy
    Class name: typeinfo.toys.HasBatteries is interface?
    [true]
    Simple name: HasBatteries
    Canonical name : typeinfo.toys.HasBatteries
    Class name: typeinfo.toys.Waterproof is interface?
    [true]
    Simple name: Waterproof
    Canonical name : typeinfo.toys.Waterproof
    Class name: typeinfo.toys.Shoots is interface? [true]
    Simple name: Shoots
    Canonical name : typeinfo.toys.Shoots
    Class name: typeinfo.toys.Toy is interface? [false]
    Simple name: Toy
    Canonical name : typeinfo.toys.Toy

    FancyToy 继承自 Toy 并实现了 HasBatteriesWaterproof 和 Shoots 接口。在 main 方法中,我们创建了一个 Class 引用,然后在 try 语句里边用 forName() 方法创建了一个 FancyToy 的类对象并赋值给该引用。需要注意的是,传递给 forName() 的字符串必须使用类的全限定名(包含包名)。

    另外,你还可以调用 getSuperclass() 方法来得到父类的 Class 对象,再用父类的 Class 对象调用该方法,重复多次,你就可以得到一个对象完整的类继承结构。

    Class 对象的 newInstance() 方法是实现“虚拟构造器”的一种途径,虚拟构造器可以让你在不知道一个类的确切类型的时候,创建这个类的对象。在前面的例子中,up 只是一个 Class 对象的引用,在编译期并不知道这个引用会指向哪个类的 Class 对象。当你创建新实例时,会得到一个 Object 引用,但是这个引用指向的是 Toy 对象。当然,由于得到的是 Object 引用,目前你只能给它发送 Object 对象能够接受的调用。而如果你想请求具体对象才有的调用,你就得先获取该对象更多的类型信息,并执行某种转型。另外,使用 newInstance() 来创建的类,必须带有无参数的构造器。在本章稍后部分,你将会看到如何通过 Java 的反射 API,用任意的构造器来动态地创建类的对象。

    类字面常量

    例子

    // typeinfo/ClassInitialization.java
    import java.util.*;
    
    class Initable {
        static final int STATIC_FINAL = 47;
        static final int STATIC_FINAL2 =
            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.STATIC_FINAL);
            // Does trigger initialization:
            System.out.println(Initable.STATIC_FINAL2);
            // 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);
        }
    }

    输出

    After creating Initable ref
    47
    Initializing Initable
    258
    Initializing Initable2
    147
    Initializing Initable3
    After creating Initable3 ref
    74

    初始化有效地实现了尽可能的“惰性”,从对 initable 引用的创建中可以看到,仅使用 .class 语法来获得对类对象的引用不会引发初始化。但与此相反,使用 Class.forName() 来产生 Class 引用会立即就进行初始化,如 initable3

    如果一个 static final 值是“编译期常量”(如 Initable.staticFinal),那么这个值不需要对 Initable 类进行初始化就可以被读取。但是,如果只是将一个字段设置成为 static 和 final,还不足以确保这种行为。例如,对 Initable.staticFinal2 的访问将强制进行类的初始化,因为它不是一个编译期常量。

    如果一个 static 字段不是 final 的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个字段分配存储空间)和初始化(初始化该存储空间),就像在对 Initable2.staticNonFinal 的访问中所看到的那样。

    泛化class

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

    这边有个奇怪的地方,类和class并非一样的概念。这看起来似乎是起作用的,因为 Integer 继承自 Number。但事实却是不行,因为 Integer 的 Class 对象并不是 Number的 Class 对象的子类

    Class<Number> geenericNumberClass = int.class;
    (错误)

    cast()方法

    // typeinfo/ClassCasts.java
    
    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);
            h = (House)b; // ... 或者这样做.
        }
    }

    cast() 方法接受参数对象,并将其类型转换为 Class 引用的类型。

    instanceof

    // typeinfo/pets/ForNameCreator.java
    package typeinfo.pets;
    import java.util.*;
    
    public class ForNameCreator extends PetCreator {
        private static List<Class<? extends Pet>> types =
                new ArrayList<>();
        // 需要随机生成的类型名:
        private static String[] typeNames = {
                "typeinfo.pets.Mutt",
                "typeinfo.pets.Pug",
                "typeinfo.pets.EgyptianMau",
                "typeinfo.pets.Manx",
                "typeinfo.pets.Cymric",
                "typeinfo.pets.Rat",
                "typeinfo.pets.Mouse",
                "typeinfo.pets.Hamster"
        };
    
        @SuppressWarnings("unchecked")
        private static void loader() {
            try {
                for (String name : typeNames)
                    types.add(
                            (Class<? extends Pet>) Class.forName(name));
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    
        static {
            loader();
        }
    
        @Override
        public List<Class<? extends Pet>> types() {
            return types;
        }
    }

    这边还是要提一句,生产extends 消费super

    // typeinfo/PetCount.java
    // 使用 instanceof
    import typeinfo.pets.*;
    import java.util.*;
    
    public class PetCount {
        static class Counter extends HashMap<String, Integer> {
            public void count(String type) {
                Integer quantity = get(type);
                if (quantity == null)
                    put(type, 1);
                else
                    put(type, quantity + 1);
            }
        }
    
        public static void
        countPets(PetCreator creator) {
            Counter counter = new Counter();
            for (Pet pet : Pets.array(20)) {
                // List each individual pet:
                System.out.print(
                        pet.getClass().getSimpleName() + " ");
                if (pet instanceof Pet)
                    counter.count("Pet");
                if (pet instanceof Dog)
                    counter.count("Dog");
                if (pet instanceof Mutt)
                    counter.count("Mutt");
                if (pet instanceof Pug)
                    counter.count("Pug");
                if (pet instanceof Cat)
                    counter.count("Cat");
                if (pet instanceof EgyptianMau)
                    counter.count("EgyptianMau");
                if (pet instanceof Manx)
                    counter.count("Manx");
                if (pet instanceof Cymric)
                    counter.count("Cymric");
                if (pet instanceof Rodent)
                    counter.count("Rodent");
                if (pet instanceof Rat)
                    counter.count("Rat");
                if (pet instanceof Mouse)
                    counter.count("Mouse");
                if (pet instanceof Hamster)
                    counter.count("Hamster");
            }
            // Show the counts:
            System.out.println();
            System.out.println(counter);
        }
    
        public static void main(String[] args) {
            countPets(new ForNameCreator());
        }
    }

    输出

    Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat
    EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse
    Pug Mouse Cymric
    {EgyptianMau=2, Pug=3, Rat=2, Cymric=5, Mouse=2, Cat=9,
    Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}

    instanceof 有一个严格的限制:只可以将它与命名类型进行比较,而不能与 Class 对象作比较。在前面的例子中,你可能会觉得写出一大堆 instanceof 表达式很乏味,事实也是如此。但是,也没有办法让 instanceof 聪明起来,让它能够自动地创建一个 Class 对象的数组,然后将目标与这个数组中的对象逐一进行比较(稍后会看到一种替代方案)。其实这并不是那么大的限制,如果你在程序中写了大量的 instanceof,那就说明你的设计可能存在瑕疵。

    动态的instanceof函数

    Class.isInstance() 方法提供了一种动态测试对象类型的方法。因此,所有这些繁琐的 instanceof 语句都可以从 PetCount.java 中删除:

    // typeinfo/PetCount3.java
    // 使用 isInstance() 方法
    
    import java.util.*;
    import java.util.stream.*;
    
    import onjava.*;
    import typeinfo.pets.*;
    
    public class PetCount3 {
        static class Counter extends
                LinkedHashMap<Class<? extends Pet>, Integer> {
            Counter() {
                super(LiteralPetCreator.ALL_TYPES.stream()
                        .map(lpc -> Pair.make(lpc, 0))
                        .collect(
                                Collectors.toMap(Pair::key, Pair::value)));
            }
    
            public void count(Pet pet) {
                // Class.isInstance() 替换 instanceof:
                entrySet().stream()
                        .filter(pair -> pair.getKey().isInstance(pet))
                        .forEach(pair ->
                                put(pair.getKey(), pair.getValue() + 1));
            }
    
            @Override
            public String toString() {
                String result = entrySet().stream()
                        .map(pair -> String.format("%s=%s",
                                pair.getKey().getSimpleName(),
                                pair.getValue()))
                        .collect(Collectors.joining(", "));
                return "{" + result + "}";
            }
        }
    
        public static void main(String[] args) {
            Counter petCount = new Counter();
            Pets.stream()
                    .limit(20)
                    .peek(petCount::count)
                    .forEach(p -> System.out.print(
                            p.getClass().getSimpleName() + " "));
            System.out.println("n" + petCount);
        }
    }

    输出

    Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat
    EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse
    Pug Mouse Cymric
    {Rat=2, Pug=3, Mutt=3, Mouse=2, Cat=9, Dog=6, Cymric=5,
    EgyptianMau=2, Rodent=5, Hamster=1, Manx=7, Pet=20}

    注册工厂

    // typeinfo/RegisteredFactories.java
    // 注册工厂到基础类
    import java.util.*;
    import java.util.function.*;
    import java.util.stream.*;
    
    class Part implements Supplier<Part> {
        @Override
        public String toString() {
            return getClass().getSimpleName();
        }
    
        static List<Supplier<? extends Part>> prototypes =
            Arrays.asList(
              new FuelFilter(),
              new AirFilter(),
              new CabinAirFilter(),
              new OilFilter(),
              new FanBelt(),
              new PowerSteeringBelt(),
              new GeneratorBelt()
            );
    
        private static Random rand = new Random(47);
        public Part get() {
            int n = rand.nextint(prototypes.size());
            return prototypes.get(n).get();
        }
    }
    
    class Filter extends Part {}
    
    class FuelFilter extends Filter {
        @Override
        public FuelFilter get() {
            return new FuelFilter();
        }
    }
    
    class AirFilter extends Filter {
        @Override
        public AirFilter get() {
            return new AirFilter();
        }
    }
    
    class CabinAirFilter extends Filter {
        @Override
        public CabinAirFilter get() {
            return new CabinAirFilter();
        }
    }
    
    class OilFilter extends Filter {
        @Override
        public OilFilter get() {
            return new OilFilter();
        }
    }
    
    class Belt extends Part {}
    
    class FanBelt extends Belt {
        @Override
        public FanBelt get() {
            return new FanBelt();
        }
    }
    
    class GeneratorBelt extends Belt {
        @Override
        public GeneratorBelt get() {
            return new GeneratorBelt();
        }
    }
    
    class PowerSteeringBelt extends Belt {
        @Override
        public PowerSteeringBelt get() {
            return new PowerSteeringBelt();
        }
    }
    
    public class RegisteredFactories {
        public static void main(String[] args) {
            Stream.generate(new Part())
                  .limit(10)
                  .forEach(System.out::println);
        }
    }

    输出

    GeneratorBelt
    CabinAirFilter
    GeneratorBelt
    AirFilter
    PowerSteeringBelt
    CabinAirFilter
    FuelFilter
    PowerSteeringBelt
    PowerSteeringBelt
    FuelFilter

    这里用到了协变。

    // typeinfo/FamilyVsExactType.java
    // instanceof 与 class 的差别
    // {java typeinfo.FamilyVsExactType}
    package typeinfo;
    
    class Base {}
    class Derived extends Base {}
    
    public class FamilyVsExactType {
        static void test(Object x) {
            System.out.println(
                  "Testing x of type " + x.getClass());
            System.out.println(
                  "x instanceof Base " + (x instanceof Base));
            System.out.println(
                  "x instanceof Derived " + (x instanceof Derived));
            System.out.println(
                  "Base.isInstance(x) " + Base.class.isInstance(x));
            System.out.println(
                  "Derived.isInstance(x) " +
                  Derived.class.isInstance(x));
            System.out.println(
                  "x.getClass() == Base.class " +
                  (x.getClass() == Base.class));
            System.out.println(
                  "x.getClass() == Derived.class " +
                  (x.getClass() == Derived.class));
            System.out.println(
                  "x.getClass().equals(Base.class)) "+
                  (x.getClass().equals(Base.class)));
            System.out.println(
                  "x.getClass().equals(Derived.class)) " +
                  (x.getClass().equals(Derived.class)));
        }
    
        public static void main(String[] args) {
            test(new Base());
            test(new Derived());
        }
    }

    输出

    Testing x of type class typeinfo.Base
    x instanceof Base true
    x instanceof Derived false
    Base.isInstance(x) true
    Derived.isInstance(x) false
    x.getClass() == Base.class true
    x.getClass() == Derived.class false
    x.getClass().equals(Base.class)) true
    x.getClass().equals(Derived.class)) false
    Testing x of type class typeinfo.Derived
    x instanceof Base true
    x instanceof Derived true
    Base.isInstance(x) true
    Derived.isInstance(x) true
    x.getClass() == Base.class false
    x.getClass() == Derived.class true
    x.getClass().equals(Base.class)) false
    x.getClass().equals(Derived.class)) true

    令人放心的是,instanceof 和 isInstance() 产生的结果相同, equals() 和 == 产生的结果也相同。与类型的概念一致,instanceof 说的是“你是这个类,还是从这个类派生的类?”。而如果使用 == 比较实际的Class 对象,则与继承无关 —— 它要么是确切的类型,要么不是。

    类方法提取器

     Class<?> c = Class.forName(args[0]);
                Method[] methods = c.getMethods();
                Constructor[] ctors = c.getConstructors();

    Class 方法 getmethods() 和 getconstructors() 分别返回 Method 数组和 Constructor 数组。这些类中的每一个都有进一步的方法来解析它们所表示的方法的名称、参数和返回值。但你也可以像这里所做的那样,使用 toString(),生成带有整个方法签名的 String

    动态代理

    普通的代理模式

    // typeinfo/SimpleProxyDemo.java
    
    interface Interface {
        void doSomething();
    
        void somethingElse(String arg);
    }
    
    class RealObject implements Interface {
        @Override
        public void doSomething() {
            System.out.println("doSomething");
        }
    
        @Override
        public void somethingElse(String arg) {
            System.out.println("somethingElse " + arg);
        }
    }
    
    class SimpleProxy implements Interface {
        private Interface proxied;
    
        SimpleProxy(Interface proxied) {
            this.proxied = proxied;
        }
    
        @Override
        public void doSomething() {
            System.out.println("SimpleProxy doSomething");
            proxied.doSomething();
        }
    
        @Override
        public void somethingElse(String arg) {
            System.out.println(
                    "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()));
        }
    }

    输出

    doSomething
    somethingElse bonobo
    SimpleProxy doSomething
    doSomething
    SimpleProxy somethingElse bonobo
    somethingElse bonobo

    Proxy.newInstance()动态代理方式

    // typeinfo/SimpleDynamicProxy.java
    
    import java.lang.reflect.*;
    
    class DynamicProxyHandler implements InvocationHandler {
        private Object proxied;
    
        DynamicProxyHandler(Object proxied) {
            this.proxied = proxied;
        }
    
        @Override
        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);
        }
    }
    
    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);
        }
    }

    输出

    doSomething
    somethingElse bonobo
    **** proxy: class $Proxy0, method: public abstract void
    Interface.doSomething(), args: null
    doSomething
    **** proxy: class $Proxy0, method: public abstract void
    Interface.somethingElse(java.lang.String), args:
    [Ljava.lang.Object;@6bc7c054
      bonobo
    somethingElse bonobo

    可以通过调用静态方法 Proxy.newProxyInstance() 来创建动态代理,该方法需要一个类加载器(通常可以从已加载的对象中获取),希望代理实现的接口列表(不是类或抽象类),以及接口 InvocationHandler 的一个实现。动态代理会将所有调用重定向到调用处理程序,因此通常为调用处理程序的构造函数提供对“真实”对象的引用,以便一旦执行中介任务便可以转发请求。

    invoke() 方法被传递给代理对象,以防万一你必须区分请求的来源---但是在很多情况下都无需关心。但是,在 invoke() 内的代理上调用方法时要小心,因为接口的调用是通过代理重定向的。

    // typeinfo/SelectingMethods.java
    // Looking for particular methods in a dynamic proxy
    
    import java.lang.reflect.*;
    
    class MethodSelector implements InvocationHandler {
        private Object proxied;
    
        MethodSelector(Object proxied) {
            this.proxied = proxied;
        }
    
        @Override
        public Object
        invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            if (method.getName().equals("interesting"))
                System.out.println(
                        "Proxy detected the interesting method");
            return method.invoke(proxied, args);
        }
    }
    
    interface SomeMethods {
        void boring1();
    
        void boring2();
    
        void interesting(String arg);
    
        void boring3();
    }
    
    class Implementation implements SomeMethods {
        @Override
        public void boring1() {
            System.out.println("boring1");
        }
    
        @Override
        public void boring2() {
            System.out.println("boring2");
        }
    
        @Override
        public void interesting(String arg) {
            System.out.println("interesting " + arg);
        }
    
        @Override
        public void boring3() {
            System.out.println("boring3");
        }
    }
    
    class SelectingMethods {
        public static void main(String[] args) {
            SomeMethods proxy =
                    (SomeMethods) Proxy.newProxyInstance(
                            SomeMethods.class.getClassLoader(),
                            new Class[]{Interface.class},
                            new MethodSelector(new Implementation()));
            proxy.boring1();
            proxy.boring2();
            proxy.interesting("bonobo");
            proxy.boring3();
        }
    }

    输出

    boring1
    boring2
    Proxy detected the interesting method
    interesting bonobo
    boring3

    Optional

    // typeinfo/Person.java
    // Using Optional with regular classes
    
    import onjava.*;
    
    import java.util.*;
    
    class Person {
        public final Optional<String> first;
        public final Optional<String> last;
        public final Optional<String> address;
        // etc.
        public final Boolean empty;
    
        Person(String first, String last, String address) {
            this.first = Optional.ofNullable(first);
            this.last = Optional.ofNullable(last);
            this.address = Optional.ofNullable(address);
            empty = !this.first.isPresent()
                    && !this.last.isPresent()
                    && !this.address.isPresent();
        }
    
        Person(String first, String last) {
            this(first, last, null);
        }
    
        Person(String last) {
            this(null, last, null);
        }
    
        Person() {
            this(null, null, null);
        }
    
        @Override
        public String toString() {
            if (empty)
                return "<Empty>";
            return (first.orElse("") +
                    " " + last.orElse("") +
                    " " + address.orElse("")).trim();
        }
    
        public static void main(String[] args) {
            System.out.println(new Person());
            System.out.println(new Person("Smith"));
            System.out.println(new Person("Bob", "Smith"));
            System.out.println(new Person("Bob", "Smith",
                    "11 Degree Lane, Frostbite Falls, MN"));
        }
    }

    输出

    <Empty>
    Smith
    Bob Smith
    Bob Smith 11 Degree Lane, Frostbite Falls, MN

    Person 的设计有时候又叫“数据传输对象(DTO,data-transfer object)”。注意,所有字段都是 public 和 final 的,所以没有 getter 和 setter 方法。也就是说,Person 是不可变的,你只能通过构造器给它赋值,之后就只能读而不能修改它的值(字符串本身就是不可变的,因此你无法修改字符串的内容,也无法给它的字段重新赋值)。如果你想修改一个 Person,你只能用一个新的 Person 对象来替换它。empty 字段在对象创建的时候被赋值,用于快速判断这个 Person 对象是不是空对象。

    如果想使用 Person,就必须使用 Optional 接口才能访问它的 String 字段,这样就不会意外触发 NullPointException 了。

    // typeinfo/Position.java
    
    import java.util.*;
    
    class EmptyTitleException extends RuntimeException {
    }
    
    class Position {
        private String title;
        private Person person;
    
        Position(String jobTitle, Person employee) {
            setTitle(jobTitle);
            setPerson(employee);
        }
    
        Position(String jobTitle) {
            this(jobTitle, null);
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String newTitle) {
            // Throws EmptyTitleException if newTitle is null:
            title = Optional.ofNullable(newTitle)
                    .orElseThrow(EmptyTitleException::new);
        }
    
        public Person getPerson() {
            return person;
        }
    
        public void setPerson(Person newPerson) {
            // Uses empty Person if newPerson is null:
            person = Optional.ofNullable(newPerson)
                    .orElse(new Person());
        }
    
        @Override
        public String toString() {
            return "Position: " + title +
                    ", Employee: " + person;
        }
    
        public static void main(String[] args) {
            System.out.println(new Position("CEO"));
            System.out.println(new Position("Programmer",
                    new Person("Arthur", "Fonzarelli")));
            try {
                new Position(null);
            } catch (Exception e) {
                System.out.println("caught " + e);
            }
        }
    }

    输出

    Position: CEO, Employee: <Empty>
    Position: Programmer, Employee: Arthur Fonzarelli
    caught EmptyTitleException

    Mock和桩

    Mock 对象和桩之间的的差别在于程度不同。Mock 对象往往是轻量级的,且用于自测试。通常,为了处理各种不同的测试场景,我们会创建出很多 Mock 对象。而桩只是返回桩数据,它通常是重量级的,并且经常在多个测试中被复用。桩可以根据它们被调用的方式,通过配置进行修改。因此,桩是一种复杂对象,它可以做很多事情。至于 Mock 对象,如果你要做很多事,通常会创建大量又小又简单的 Mock 对象。

    一个没有高级趣味的人。 email:hushui502@gmail.com
  • 相关阅读:
    Python的招牌花旦
    Python的for循环究竟是什么回事儿
    人人都喜欢用的十大python标准库
    有编程基础学python怎么赚点小钱?
    python3 Flask Redis 如何切换数据库
    贪心算法
    【ACM】nyoj_106_背包问题_201308152026
    【ACM】nyoj_14_会场安排问题_201308151955
    【ACM】nyoj_132_最长回文子串_201308151713
    【ACM】nyoj_47_过桥问题_201308151616
  • 原文地址:https://www.cnblogs.com/CherryTab/p/11992794.html
Copyright © 2011-2022 走看看