• 《Java编程思想》读书笔记(四)


    前言:三年之前就买了《Java编程思想》这本书,但是到现在为止都还没有好好看过这本书,这次希望能够坚持通读完整本书并整理好自己的读书笔记,上一篇文章是记录的第十七章到第十八章的内容,这一次记录的是第十九章到第二十章的内容,相关示例代码放在码云上了,码云地址:https://gitee.com/reminis_com/thinking-in-java

    第十九章:枚举类型

    关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能。

    enum的基本特性

      我们已经知道,调用enum的values()方法,可以遍历enum实例。values()方法返回enum实例的数组,而且该数组中的元素严格保持其在enum中声明时的顺序,因此你可以在循环中使用values()返回的数组。
      创建enum时,编译器会为你生成一个相关的类,这个类继承自java.lang.Enum。下面的例子演示了Enum提供的一些功能∶

    package enumerated;
    
    /**
     * @author Mr.Sun
     * @date 2022年09月02日 15:58
     *
     * 枚举的基本特性
     */
    public class EnumClass {
        public static void main(String[] args) {
            for(Shrubbery s : Shrubbery.values()) {
                System.out.println(s + " ordinal: " + s.ordinal());
                System.out.print(s.compareTo(Shrubbery.CRAWLING) + " ");
                System.out.print(s.equals(Shrubbery.CRAWLING) + " ");
                System.out.println(s == Shrubbery.CRAWLING);
                System.out.println(s.getDeclaringClass());
                System.out.println(s.name());
                System.out.println("----------------------");
            }
    
            // 从字符串名称生成枚举值
            for(String s : "HANGING CRAWLING GROUND".split(" ")) {
                Shrubbery shrub = Enum.valueOf(Shrubbery.class, s);
                System.out.println(shrub);
            }
        }
    }
    
    enum Shrubbery {
        GROUND, CRAWLING, HANGING
    }
    

    运行结果如下图:

      ordinal()方法返回一个int值,这是每个enum实例在声明时的次序,从0开始。可以使用==来比较enum实例,编译器会自动为你提供equals()和hashCode()方法。Enum类实现了Comparable 接口,所以它具有compareTo()方法。同时,它还实现了Serializable接口。
      如果在enum实例上调用getDeclaringClass()方法,我们就能知道其所属的enum类。name()方法返回enum实例声明时的名字,这与使用toString()方法效果相同。valueOf()是在Enum中定义的static方法,它根据给定的名字返回相应的enum实例,如果不存在给定名字的实例,将会抛出异常。

    values()的神秘之处

      前面已经提到,编译器为你创建的enum类都继承自Enum类。然而,如果你研究一下Enum 类就会发现,它并没有values()方法。可我们明明已经用过该方法了,难道存在某种“隐藏的”方法吗?我们可以利用反射机制编写一个简单的程序,来查看其中的究竟∶

    package enumerated;
    
    import utils.OSExecute;
    
    import java.lang.reflect.Method;
    import java.lang.reflect.Type;
    import java.util.Set;
    import java.util.TreeSet;
    
    /**
     * @author Mr.Sun
     * @date 2022年09月02日 16:10
     *
     * 使用反射机制研究枚举类的values()
     */
    enum Explore {
        HERE, THERE
    }
    
    public class Reflection {
    
        public static Set<String> analyze(Class enumClass) {
            System.out.println("----- Analyzing " + enumClass + " -----");
            System.out.println("Interfaces:");
            for (Type t : enumClass.getGenericInterfaces()) {
                System.out.println(t);
            }
            System.out.println("Base: " + enumClass.getSuperclass());
            System.out.println("Methods: ");
            Set<String> methods = new TreeSet<String>();
            for (Method method : enumClass.getMethods()) {
                methods.add(method.getName());
            }
            System.out.println(methods);
            return methods;
        }
    
        public static void main(String[] args) {
            Set<String> exploreMethods = analyze(Explore.class);
            Set<String> enumMethods = analyze(Enum.class);
            System.out.println("Explore.containsAll(Enum)? " + exploreMethods.containsAll(enumMethods));
            System.out.print("Explore.removeAll(Enum): ");
            exploreMethods.removeAll(enumMethods);
            System.out.println(exploreMethods);
            // Decompile the code for the enum:
            OSExecute.command("javap G:/github/cnblogs/gitee/thinking-in-java/out/production/thinking-in-java/enumerated/Explore.class");
        }
    
    } /* Output:
    ----- Analyzing class enumerated.Explore -----
    Interfaces:
    Base: class java.lang.Enum
    Methods: 
    [compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, values, wait]
    ----- Analyzing class java.lang.Enum -----
    Interfaces:
    java.lang.Comparable
    interface java.io.Serializable
    Base: class java.lang.Object
    Methods: 
    [compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, wait]
    Explore.containsAll(Enum)? true
    Explore.removeAll(Enum): [values]
    Compiled from "Reflection.java"
    final class enumerated.Explore extends java.lang.Enum {
      public static final enumerated.Explore HERE;
      public static final enumerated.Explore THERE;
      public static enumerated.Explore[] values();
      public static enumerated.Explore valueOf(java.lang.String);
      static {};
    }
    *///:~
    
    

      答案是,values()是由编译器添加的static方法。可以看出,在创建Explore的过程中,编译器还为其添加了valueOf()方法。这可能有点令人迷惑,Enum类不是已经有valueOf()方法了吗。不过Enum中的valueOf()方法需要两个参数,而这个新增的方法只需一个参数。由于这里使用的Set只存储方法的名字,而不考虑方法的签名,所以在调用Explore.removeAl(Enum)之后,就只剩下【values】了。
      从最后的输出中可以看到,编译器将Explore标记为final类,所以无法继承自enum。其中还有一个static的初始化子句,稍后我们将学习如何重定义该句。
      由于擦除效应(在第15章中介绍过),反编译无法得到Enum的完整信息,所以它展示的Explore的父类只是一个原始的Enum,而非事实上的Enum
      由于values()方法是由编译器插入到enum定义中的static方法,所以,如果你将enum实例向上转型为Enum,那么values()方法就不可访问了。不过,在Class中有一个getEnumConstants()方法,所以即便Enum接口中没有values()方法,我们仍然可以通过Class对象取得所有enum实例∶

    enum Search {
        HITHER, YON
    }
    
    
    public class UpcastEnum {
        public static void main(String[] args) {
            Search[] values = Search.values();
            for (Search val : values) {
                System.out.println(val.name());
            }
            System.out.println("--------------");
            Enum e = Search.HITHER;
            for (Enum en : e.getClass().getEnumConstants()) {
                System.out.println(en);
            }
        }
    }
    

    运行结果如下:

    使用EnumSet代替标志

      Set是一种集合,只能向其中添加不重复的对象。当然,enum也要求其成员都是唯一的,所以enum看起来也具有集合的行为。不过,由于不能从enum中删除或添加元素,所以它只能算是不太有用的集合。Java SE5引入EnumSet,是为了通过enum创建一种替代品,以替代传统的"基于int的“位标志”。这种标志可以用来表示某种“开/关”信息,不过,使用这种标志,我们最终操作的只是一些bit,而不是这些bit想要表达的概念,因此很容易写出令人难以理解的代码。

      EnumSet的设计充分考虑到了速度因素,因为它必须与非常高效的bit标志相竞争(其操作与HashSet相比,非常地快)。就其内部而言,它(可能)就是将一个long值作为比特向量,所以EnumSet非常快速高效。使用EnumSet的优点是,它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。EnumSet中的元素必须来自一个enum。下面的enum表示在一座大楼中,警报传感器的安放位置∶

    package enumerated;
    
    public enum AlarmPoints {
        STAIR1, STAIR2,
        LOBBY,
        OFFICE1, OFFICE2, OFFICE3, OFFICE4,
        BATHROOM, UTILITY, KITCHEN
    }
    

    然后,我们使用EnumSet来跟踪报警器的状态:

    package enumerated;
    
    import java.util.EnumSet;
    
    import static enumerated.AlarmPoints.*;
    
    /**
     * @author Mr.Sun
     * @date 2022年09月02日 17:18
     */
    public class EnumSetTest {
        public static void main(String[] args) {
            EnumSet points = EnumSet.noneOf(AlarmPoints.class); // Empty set
            points.add(BATHROOM);
            System.out.println(points);
    
            points.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
            System.out.println(points);
    
            points = EnumSet.allOf(AlarmPoints.class);
            points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
            System.out.println(points);
    
            points.removeAll(EnumSet.range(OFFICE1, OFFICE4));
            System.out.println(points);
    
            points = EnumSet.complementOf(points);
            System.out.println(points);
        }
    }
    

    运行结果如下图:

    使用EnumMap

      EnumMap是一种特殊的Map,它要求其中的键(key)必须来自一个enum。由于enum本身的限制,所以EnumMap在内部可由数组实现。因此EnumMap的速度很快,我们可以放心地使用enum实例在EnumMap中进行查找操作。不过,我们只能将enum的实例作为键来调用put()方法,其他操作与使用一般的Map差不多。
      下面的例子演示了命令设计模式的用法。一般来说,命令模式首先需要一个只有单一方法的接口,然后从该接口实现具有各自不同的行为的多个子类。接下来,程序员就可以构造命令对象,并在需要的时候使用它们了∶

    package enumerated;
    
    import java.util.EnumMap;
    import java.util.Map;
    
    import static enumerated.AlarmPoints.*;
    
    /**
     * @author Mr.Sun
     * @date 2022年09月02日 17:25
     *
     * 使用EnumMap
     */
    interface Command{ void action(); }
    
    public class EnumMapTest {
        public static void main(String[] args) {
            EnumMap em = new EnumMap<>(AlarmPoints.class);
            em.put(KITCHEN, () -> System.out.println("Kitchen fire!"));
            em.put(BATHROOM, () -> System.out.println("Bathroom alert!"));
            for(Map.Entry e : em.entrySet()) {
                System.out.print(e.getKey() + ": ");
                e.getValue().action();
            }
            try {
                // If there's no value for a particular key:
                em.get(UTILITY).action();
            } catch(Exception e) {
                System.out.println(e);
            }
        }
    }/* Output:
    BATHROOM: Bathroom alert!
    KITCHEN: Kitchen fire!
    java.lang.NullPointerException
    *///:~
    

      与EnumSet一样,enum实例定义时的次序决定了其在EnumMap中的顺序。
      main()方法的 最后部分说明,enum的每个实例作为一个键,总是存在的,但是如果你没有为这个键调用put()方法来存入相应的值的话,对应的值就是null。

    常量相关的方法

      Java的Enum有一个非常有趣的特性,即它允许程序员为enum实例编写方法,从而为每个enum实例赋予各自不同的行为,要实现常量相关的方法,你需要为enum定义一个或多个abstract方法,然后为每个enum实例实现该抽象方法。参考下面的例子:

    package enumerated;
    
    import java.text.DateFormat;
    import java.util.Date;
    
    public enum ConstantSpecificMethod {
    
        DATE_TIME {
            String getInfo() {
                return DateFormat.getDateInstance().format(new Date());
            }
        },
        CLASSPATH {
            String getInfo() {
                return System.getenv("CLASSPATH");
            }
        },
        VERSION {
            String getInfo() {
                return System.getProperty("java.version");
            }
        };
    
        abstract String getInfo();
    
        public static void main(String[] args) {
            for(ConstantSpecificMethod csm : values()) {
                System.out.println(csm.getInfo());
            }
        }
    }/* Output:
    2022-9-2
    null
    1.8.0_211
    *///:~
    
    

    使用enum的职责链

      在职责链(Chain of Responsibility)设计模式中,程序员以多种不同的方式来解决一个问题,然后将它们链接在一起。当一个请求到来时,它遍历这个链,直到链中的某个解决方案能够处理该请求。

      通过常量相关的方法,我们可以很容易地实现一个简单的职责链。我们以一个邮局的模型为例。邮局需要以尽可能通用的方式来处理每一封邮件,并且要不断尝试处理邮件,直到该邮件最终被确定为一封死信。其中的每一次尝试可以看作为一个策略(也是一个设计模式),而完整的处理方式列表就是一个职责链。
      我们先来描述一下邮件。邮件的每个关键特征都可以用enum来表示。程序将随机地生成Mail对象,如果要减小一封邮件的GeneralDelivery为YES的概率,那最简单的方法就是多创建几个不是YES的enum实例,所以enum的定义看起来有点古怪。
      我们看到Mail中有一个randomMail()方法,它负责随机地创建用于测试的邮件。而generator()方法生成一个Iterable对象,该对象在你调用next()方法时,在其内部使用randomMail()来创建Mail对象。这样的结构使程序员可以通过调用Mail.generator()方法,很容易地构造出一个foreach循环∶

    package enumerated;
    
    import utils.Enums;
    
    import java.util.Iterator;
    
    /**
     * @author Mr.Sun
     * @date 2022年09月02日 17:45
     *
     * 以邮局的模型为例,通过常量相关的方法,实现一个简单的职责链
     */
    public class PostOffice {
    
        enum MailHandler {
            GENERAL_DELIVERY {
                boolean handle(Mail m) {
                    switch (m.generalDelivery) {
                        case YES:
                            System.out.println("Using general delivery for " + m);
                            return true;
                        default:
                            return false;
                    }
                }
            },
    
            MACHINE_SCAN {
                boolean handle(Mail m) {
                    switch (m.scannability) {
                        case UNSCANNABLE:
                            return false;
                        default:
                            switch (m.address) {
                                case INCORRECT:
                                    return false;
                                default:
                                    System.out.println("Delivering " + m + " automatically");
                                    return true;
                            }
                    }
                }
            },
            VISUAL_INSPECTION {
                boolean handle(Mail m) {
                    switch (m.readability) {
                        case ILLEGIBLE:
                            return false;
                        default:
                            switch (m.address) {
                                case INCORRECT:
                                    return false;
                                default:
                                    System.out.println("Delivering " + m + " normally");
                                    return true;
                            }
                    }
                }
            },
            RETURN_TO_SENDER {
                boolean handle(Mail m) {
                    switch (m.returnAddress) {
                        case MISSING:
                            return false;
                        default:
                            System.out.println("Returning " + m + " to sender");
                            return true;
                    }
                }
            };
    
            abstract boolean handle(Mail m);
        }
    
        static void handle(Mail m) {
            for(MailHandler handler : MailHandler.values()) {
                if(handler.handle(m)) {
                    return;
                }
            }
            System.out.println(m + " is a dead letter");
        }
    
        public static void main(String[] args) {
            for(Mail mail : Mail.generator(10)) {
                System.out.println(mail.details());
                handle(mail);
                System.out.println("*****");
            }
        }
    }
    
    class Mail {
        // “否”会降低随机选择的概率:
        enum GeneralDelivery {YES, NO1, NO2, NO3, NO4, NO5}
        enum Scannability {UNSCANNABLE, YES1, YES2, YES3, YES4}
        enum Readability {ILLEGIBLE, YES1, YES2, YES3, YES4}
        enum Address {INCORRECT, OK1, OK2, OK3, OK4, OK5, OK6}
        enum ReturnAddress {MISSING, OK1, OK2, OK3, OK4, OK5}
    
        GeneralDelivery generalDelivery;
        Scannability scannability;
        Readability readability;
        Address address;
        ReturnAddress returnAddress;
        static long counter = 0;
        long id = counter++;
    
        public String toString() { return "Mail " + id; }
    
        public String details() {
            return toString() +
                    ", General Delivery: " + generalDelivery +
                    ", Address Scanability: " + scannability +
                    ", Address Readability: " + readability +
                    ", Address Address: " + address +
                    ", Return address: " + returnAddress;
        }
    
        /**
         * 生成测试邮件
         */
        public static Mail randomMail() {
            Mail m = new Mail();
            m.generalDelivery= Enums.random(GeneralDelivery.class);
            m.scannability = Enums.random(Scannability.class);
            m.readability = Enums.random(Readability.class);
            m.address = Enums.random(Address.class);
            m.returnAddress = Enums.random(ReturnAddress.class);
            return m;
        }
    
        public static Iterable<Mail> generator(final int count) {
            return new Iterable() {
                int n = count;
                public Iterator<Mail> iterator() {
                    return new Iterator() {
                        public boolean hasNext() { return n-- > 0; }
                        public Mail next() { return randomMail(); }
                        public void remove() { // Not implemented
                            throw new UnsupportedOperationException();
                        }
                    };
                }
            };
        }
    }
    /* Output:
    Mail 0, General Delivery: NO2, Address Scanability: UNSCANNABLE, Address Readability: YES3, Address Address: OK1, Return address: OK1
    Delivering Mail 0 normally
    *****
    Mail 1, General Delivery: NO5, Address Scanability: YES3, Address Readability: ILLEGIBLE, Address Address: OK5, Return address: OK1
    Delivering Mail 1 automatically
    *****
    Mail 2, General Delivery: YES, Address Scanability: YES3, Address Readability: YES1, Address Address: OK1, Return address: OK5
    Using general delivery for Mail 2
    *****
    Mail 3, General Delivery: NO4, Address Scanability: YES3, Address Readability: YES1, Address Address: INCORRECT, Return address: OK4
    Returning Mail 3 to sender
    *****
    Mail 4, General Delivery: NO4, Address Scanability: UNSCANNABLE, Address Readability: YES1, Address Address: INCORRECT, Return address: OK2
    Returning Mail 4 to sender
    *****
    Mail 5, General Delivery: NO3, Address Scanability: YES1, Address Readability: ILLEGIBLE, Address Address: OK4, Return address: OK2
    Delivering Mail 5 automatically
    *****
    Mail 6, General Delivery: YES, Address Scanability: YES4, Address Readability: ILLEGIBLE, Address Address: OK4, Return address: OK4
    Using general delivery for Mail 6
    *****
    Mail 7, General Delivery: YES, Address Scanability: YES3, Address Readability: YES4, Address Address: OK2, Return address: MISSING
    Using general delivery for Mail 7
    *****
    Mail 8, General Delivery: NO3, Address Scanability: YES1, Address Readability: YES3, Address Address: INCORRECT, Return address: MISSING
    Mail 8 is a dead letter
    *****
    Mail 9, General Delivery: NO1, Address Scanability: UNSCANNABLE, Address Readability: YES2, Address Address: OK1, Return address: OK4
    Delivering Mail 9 normally
    *****
    *///:~
    
    

    职责链由enum MailHandler实现,而enum定义的次序决定了各个解决策略在应用时的次序。对每一封邮件,都要按此顺序尝试每个解决策略,直到其中一个能够成功地处理该邮件,如果所有的策略都失败了,那么该邮件将被判定为一封死信。

    第二十章:注解

    注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用某些数据。

    定义注解

    package annotations;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Test {
    }
    

      除了@符号以外,@Test的定义很像一个空的接口。定义注解时,会需要一些元注解(meta-annotation),如@Target和@Retention。@Target用来定义你的注解将应用于什么地方(例如是一个方法或者一个域)。@Rectetion用来定义该注解在哪一个级别可用,在源代码中(SOURCE)、类文件中(CLASS)或者运行时(RUNTIME)。

      没有元素的注解称为标记注解,例如上例种的@Test。

    注解元素

      注解元素可用的类型如下:

    • 所有基本类型(int, float, boolean等)
    • String
    • Class
    • enum
    • Annotation
    • 以上类型的数组

      如果你使用了其它类型,编译器就会报错。注意,也不允许使用任何包装类型,不过由于自动打包的存在,这算不上什么限制。注解也可以作为元素的类型,也就是说,注解可以嵌套。

    元注解

      Java目前只内置了四种元注解,元注解专职负责注解其它的注解:

    大多数时候,程序员主要是定义自己的注解,并编写自己的处理器来处理它们。

      下面是一个简单的注解,我们可以用它来跟踪一个项目中的用例。如果一个方法或一组方法实现了某个用例的需求,那么程序员可以为此方法加上该注解。于是,项目经理通过计算已经实现的用例,就可以很好地掌控项目的进展。而如果要更新或修改系统的业务逻辑,则维护该项目的开发人员也可以很容易地在代码中找到对应的用例。

    package annotations;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface UseCase {
        public int id();
        public String description() default "no description";
    }
    
    

      注意,id和description类似方法定义。由于编译器会对id进行类型检查,因此将用例文档的追踪数据库与源代码相关联是可靠的。description元素有一个default值,如果在注解某个方法时没有给出description的值,则该注解的处理器就会使用此元素的默认值。
      在下面的类中,有三个方法被注解为用例∶

    package annotations;
    
    import java.util.List;
    
    /**
     * @author Mr.Sun
     * @date 2022年09月03日 9:05
     *
     * 注解用例
     */
    public class PasswordUtils {
    
        @UseCase(id = 47, description = "密码必须至少包含一个数字")
        public boolean validatePassword(String password) {
            return (password.matches("\\w*\\d\\w*"));
        }
    
        @UseCase(id = 48)
        public String encryptPassword(String password) {
            return new StringBuilder(password).reverse().toString();
        }
    
        @UseCase(id = 49, description = "新密码不能等于以前使用的密码")
        public boolean checkForNewPassword(List<String> prevPasswords, String password) {
            return !prevPasswords.contains(password);
        }
    }
    

      注解的元素在使用时表现为名一值对的形式,并需要置于@UseCase声明之后的括号内。在encryptPassword()方法的注解中,并没有给出description元素的值,因此,在UseCase的注解处理器分析处理这个类时会使用该元素的默认值。
      你应该能够想象得到如何使用这套工具来“勾勒”出将要建造的系统,然后在建造的过程中逐渐实现系统的各项功能。

    编写注解处理器

      如果没有用来读取注解的工具,那注解也不会比注释更有用。使用注解的过程中,很重要的一个部分就是创建与使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员构造这类工具。同时,它还提供了一个外部工具apt帮助程序员解析带有注解的Java源代码。

      下面是一个非常简单的注解处理器,我们将用它来读取PasswordUtils类,并使用反射机制查找@UseCase标记。我们为其提供了一组id值,然后它会列出在PasswordUtils种找到的用例,以及缺失的用例。

    package annotations;
    
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    /**
     * @author Mr.Sun
     * @date 2022年09月03日 9:11
     *
     * 编写注解处理器
     */
    public class UseCaseTracker {
    
        public static void trackUseCases(List useCases, Class clazz) {
            for (Method m : clazz.getDeclaredMethods()) {
                UseCase useCase = m.getAnnotation(UseCase.class);
                if (useCase != null) {
                    System.out.println("找到@UseCase:" + useCase.id() + " "  + useCase.description());
                    useCases.remove(Integer.valueOf(useCase.id()));
                }
            }
            for (Integer i : useCases) {
                System.out.println("警告: 缺失用例:" + i);
            }
        }
    
        public static void main(String[] args) {
            List useCases = new ArrayList<>();
            Collections.addAll(useCases, 47, 48, 49, 50);
            trackUseCases(useCases, PasswordUtils.class);
        }
    } /* Output:
    找到@UseCase:49 新密码不能等于以前使用的密码
    找到@UseCase:47 密码必须至少包含一个数字
    找到@UseCase:48 no description
    警告: 缺失用例:50
    *///:~
    
    

      这个程序用到了两个反射的方法∶getDeclaredMethods()和getAnnotation(),它们都属于AnnotatedElement接口(Class、Method与Feld等类都实现了该接口)。getAnnoation()方法返回指定类型的注解对象,在这里就是UseCase。如果被注解的方法上没有该类型的注解,则返回null值。然后我们通过调用id()和description()方法从返回的UseCase对象中提取元素的值。其中,encriptPassword()方法在注解的时候没有指定description的值,因此处理器在处理它对应的注解时,通过description()方法取得的是默认值no description。

    总结

      枚举和注解其实在日常开发中都很熟悉,因为是非常基础的知识,本文也只是把模糊的概念和比较冷门的知识点记录下来,方便日后查阅,原来是打算把第二十一章的内容也放在本文的,但发现并发这章内容太多了,限于篇幅,还是打算单独写一篇文章进行记录,而且并发也是比较重要的基础知识。等把并发这章内容看完了,《Java编程思想》读书笔记系列也就告一段落了。

  • 相关阅读:
    tomcat、nginx实现四层转发+七层代理+动静分离实验
    [SUCTF 2019]EasyWeb
    STM32 HAL库多路PWM没有输出踩坑记录
    SPARK中的wholeStageCodegen全代码生成--以aggregate代码生成为例说起(8)
    javaEE7
    力扣每日一题45:跳跃游戏
    FreeRTOS 任务任务同步与数据传递总结
    前端 JS 经典:Math 常用方法汇总
    win10实现nfs文件共享II
    SSM - Springboot - MyBatis-Plus 全栈体系(十九)
  • 原文地址:https://www.cnblogs.com/reminis/p/16650236.html