JAVA 学习 面试(十)枚举、注解、基本原则

枚举
  • 默认继承 java.lang.Enum 类,不能继承其他父类,并自动添加了values(获取枚举类中的所有枚举值)和valueOf(获取对应的枚举类型)方法, java.lang.Enum 类实现了 java.lang.Serializable 和 java.lang.Comparable 接口
  • enum默认使用 final 修饰,不能派生子类,构造器默认使用 private 修饰,且只能使用 private 修饰
  • 枚举类所有实例必须在第一行给出,默认添加 public static final 修饰,用内部类实现,该内部类继承了枚举类。所有枚举常量都通过静态代码块来进行初始化
    java.util.EnumSet:保证集合中的元素不重复
    java.util.EnumMap:EnumMap中的 key是enum类型,而value则可以是任意类型

注意:

  1. 枚举类型对象之间的值比较可以使用“==”直接来比较值是否相等的,因为枚举类Enum已经重写了equals方法

  2. 每个枚举都定义了两个属性,name和ordinal,name表示我们定义的枚举常量的名称,如FRIDAY、TUESDAY,而ordinal是一个顺序号,根据定义的顺序分别赋予一个整形值,从0开始。在枚举常量初始化时,会自动为初始化这两个字段,设置相应的值,在构造方法中添加了两个参数。

  3. name和ordinal属性都是final的,clone、readObject、writeObject这三个方法也是final的,这三个方法和枚举通过静态代码块来一起进行初始化。

  4. clone、readObject、writeObject三个方法保证了枚举类型的不可变性(即不能通过克隆,不能通过序列化和反序列化来复制枚举),保证枚举常量是单例的。注解是绑定到程序源代码元素的元数据,对运行代码的操作没有影响

注解

注解是一种标记在 Java 类、方法、字段和其他程序元素上的特殊标签,它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。

注解分为三类:

  1. 内置注解:这些注解用于特殊的用途,如告诉编译器生成警告或错误,控制序列化过程等。

    1. @Override 注解用于告诉编译器,希望重写(覆盖)父类中的方法。如果父类中不存在与该方法签名匹配的方法,编译器会产生一个错误。
    2. @Deprecated 注解用于标记方法、类或字段已过时,不推荐使用。编译器会发出警告,提示开发者尽量避免使用被标记为过时的元素。
    3. @SuppressWarnings 注解用于告诉编译器忽略特定类型的警告。这对于处理旧代码或集成第三方库时非常有用。
    @SuppressWarnings("unchecked")
    public List getItems() {
        // 忽略类型未检查的警告
        return new ArrayList();
    }
    
  2. 自定义注解:可以用来添加程序的元数据,或者用于特定的用途,例如测试框架、依赖注入等

    // 定义自定义注解
    public @interface MyAnnotation {
        String value() default "default value"; // 定义一个元素
        int number() default 0; // 定义另一个元素
    }
    @MyAnnotation(value = "Custom Value", number = 42)
    public class MyClass {
        // 类的内容
    }
    Class<?> clazz = MyClass.class;
    MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
    
    if (annotation != null) {
        String value = annotation.value();
        int number = annotation.number();
        
        System.out.println("Value: " + value);
        System.out.println("Number: " + number);
    } else {
        System.out.println("MyAnnotation not found.");
    }
    // 注解的元素可以是基本数据类型、字符串、枚举类型、注解类型或以上类型的数组
    public @interface MyAnnotation {
        int value() default 0;
        String name() default "John";
        Color color() default Color.RED;
        String[] tags() default {};
        Class<?>[] classes() default {};
        MyOtherAnnotation otherAnnotation() default @MyOtherAnnotation;
    }
    
  3. 元注解:是用于定义注解的注解,包括@Retention、@Target、@Inherited、@Documented 等6种

@Retention:指定其所修饰的注解的保留策略
@Document:该注解是一个标记注解,用于指示一个注解将被文档化
@Target:用来限制注解的使用范围
@Inherited:该注解使父类的注解能被其子类继承
@Target(ElementType.TYPE) // 该注解可以用在类上
@Retention(RetentionPolicy.RUNTIME) // 注解信息会保留到运行时
public @interface ExcellentStudent {
}
@ExcellentStudent // 我们在 Student 类上应用 @ExcellentStudent 注解
public class Student {
    private String name;
    private int age;
    private double gpa;
    // 构造方法和其他方法省略
}
// 使用反射来查找并识别优秀学生
public class Main {
    public static void main(String[] args) {
        // 获取 Student 类的 Class 对象
        Class<?> clazz = Student.class;
        // 检查类上是否有 ExcellentStudent 注解
        if (clazz.isAnnotationPresent(ExcellentStudent.class)) {
            // 如果有,打印学生信息
            System.out.println("优秀学生信息:");
            Student student = new Student("Alice", 20, 4.0);
            System.out.println(student);
        } else {
            System.out.println("没有优秀学生信息。");
        }
    }
}

注意:

  • 注解本身不影响程序的运行,只提供了元数据。

  • 注解在编译时可以被处理,也可以在运行时被处理,具体取决于注解的类型和用途。

  • 自定义注解需要使用 @Retention 指定它的保留策略,通常是 RUNTIME,以便在运行时读取注解信息。

  • 注解的元素名称通常为 value,但可以自定义其他名称。

Retention– 定义该注解的生命周期

RetentionPolicy.SOURCE:在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解
RetentionPolicy.CLASS:在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
RetentionPolicy.RUNTIME:始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式

Target –注解用于什么地方

  1. ElementType.CONSTRUCTOR:用于描述构造器
  2. ElementType.FIELD:成员变量、对象、属性(包括enum实例)
  3. ElementType.LOCAL_VARIABLE:用于描述局部变量
  4. ElementType.METHOD:用于描述方法

@Override -标记方法是否覆盖超类中声明的元素。如果它无法正确覆盖该方法,编译器将发出错误
@Deprecated - 表示该元素已弃用且不应使用。如果程序使用标有此批注的方法,类或字段,编译器将发出警告
@SuppressWarnings - 告诉编译器禁止特定警告。在与泛型出现之前编写的遗留代码接口时最常用的
@FunctionalInterface - 在Java 8中引入,表明类型声明是一个功能接口,可以使用Lambda Expression提供其实现
注解方法声明返回类型必须是基本类型,String,Class,Enum或数组类型之一。否则,编译器将抛出错误
@Target注解可以限制应用注解的元素

基本原则
  • **单一职责:**一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。

    一个类(或者大到模块,小到方法)承担的职责越多,它被复用的可能性越小,而且如果一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。
    
  • 开闭原则:面向修改关闭,面向扩展开放

    设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即实现在不修改源代码的情况下改变这个模块的行为。
    比如我们的程序员分为Java程序员、C#程序员、C++程序员、PHP程序员、前端程序员等,而他们要做的都是去打代码,而具体如何打代码是根据不同语言的程序员来决定的,我们可以将程序员打代码这一个行为抽象成一个统一的接口或是抽象类,而具体哪个程序员使用什么语言怎么编程,是自己在负责,不需要其他程序员干涉
    
  • 李氏替换:子类可以替换父类

    // 我们不需要再继承自Coder了
    public abstract class Coder {
        public void coding() {
            System.out.println("我会打代码");
        }
    
    class JavaCoder extends Coder{
            public void game(){
                System.out.println("艾欧尼亚最强王者已上号");
            }
            /**
             * 这里我们对父类的行为进行了重写,现在它不再具备父类原本的能力了
             */
            public void coding() {
                System.out.println("我寒窗苦读十六年,到最后还不如培训班三个月出来的程序员");
            }
        }
    }
    
  • 依赖倒置:面向接口编程

    代码要依赖于抽象的类,而不要依赖于具体的类;要针对接口或抽象类编程,而不是针对具体类编程
    public class Main {
    
        public static void main(String[] args) {
            UserController controller = new UserController();
        }
    
        interface UserMapper {
            //接口中只做CRUD方法定义
        }
    
        static class UserMapperImpl implements UserMapper {
            //实现类完成CRUD具体实现
        }
    
        interface UserService {
            //业务代码定义....
        }
    
        static class UserServiceImpl implements UserService {
            @Resource   //现在由Spring来为我们选择一个指定的实现类,然后注入,而不是由我们在类中硬编码进行指定
            UserMapper mapper;
            
            //业务代码具体实现
        }
    
        static class UserController {
            @Resource
            UserService service;   //直接使用接口,就算你改实现,我也不需要再修改代码了
    
            //业务代码....
        }
    }
    
  • 聚合/组合优于继承

    class A { // 如果有一天,由于业务的更改,我们的数据库连接操作,不再由A来负责,而是由新来的C去负责,那么这个时候,我们就不得不将需要复用A中方法的子类全部进行修改
        public void connectDatabase(){
            System.out.println("我是连接数据库操作!");
        }
    }
    
    class B {   //不进行继承,而是在用的时候给我一个A,当然也可以抽象成一个接口,更加灵活
        public void test(A a){
            System.out.println("我是B的方法,我也需要连接数据库!");
            a.connectDatabase();   //在通过传入的对象A去执行
        }
    }
    
    // to
    class A {
        public void connectDatabase(){
            System.out.println("我是连接数据库操作!");
        }
    }
    
    class B {   //不进行继承,而是在用的时候给我一个A,当然也可以抽象成一个接口,更加灵活
        public void test(A a){
            System.out.println("我是B的方法,我也需要连接数据库!");
            a.connectDatabase();   //在通过传入的对象A去执行
        }
    }
    
  • 接口隔离:接口要小而专,而不是大而全

    客户端不应该依赖那些它不需要的接口。注意,在该定义中的接口指的是所定义的方法。
    一旦一个接口太大,则需要将它分割成一些更细小的接*,使用该接口的客户端仅需知道与之相关的方法即可。将一组相关的操作定义在一个接口中,且在满足高内聚的前提下,接口中的方法越少越好。
    
  • 迪米特法则:最小知识原则

    1. 在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及。
    2. 在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限。
    3. 在类的设计上,只要有可能,一个类型应当设计成不变类;
    4. 在对其他类的引用上,一个对象对其他对象的引用应当降到最低。
    
    public class Main {
        public static void main(String[] args) throws IOException {
            Socket socket = new Socket("localhost", 8080);   //假设我们当前的程序需要进行网络通信
            Test test = new Test();
            test.test(socket);   //现在需要执行test方法来做一些事情
        }
    
        static class Test {
            /**
             * 比如test方法需要得到我们当前Socket连接的本地地址
             */
            public void test(Socket socket){
                System.out.println("IP地址:"+socket.getLocalAddress());
            }
        }
    }
    // to
    static class Test {
            public void test(String str){   //一个字符串就能搞定,就没必要丢整个对象进来
                System.out.println("IP地址:"+str);
            }
        }
    

你可能感兴趣的:(java,学习,面试)