• Java浅谈随笔,你都会了吗?


    (一)当面试官问到你对反射的认识和使用时,你可以回答以下内容:

    • 反射的认识:

    反射是Java语言中一种强大的机制,它允许程序在运行时动态地获取和操作类的信息、对象的字段和方法。通过反射,我们可以在运行时检查和修改类的结构,调用对象的方法,甚至可以创建新的对象。反射提供了一种灵活的方式来实现动态的功能和增强代码的复用性。

    • 反射的使用:

    反射可以用于以下几个方面:

    • 获取类的信息:通过Class对象可以获取类的名称、字段、方法、构造函数等信息。
    • 创建对象:通过Class对象可以实例化一个类的对象,即使在编译时并不知道具体的类名。
    • 调用方法:可以通过Method对象调用类的方法,包括公共方法、私有方法和静态方法。
    • 操作字段:可以通过Field对象获取和设置类的字段值,包括公共字段、私有字段和静态字段。
    • 处理注解:可以通过反射获取类、方法、字段上的注解信息,并进行相应的处理。

    反射的使用需要谨慎,因为它会牺牲一定的性能和安全性。在实际开发中,应该根据具体的需求来决定是否使用反射,避免滥用。同时,了解反射的原理和使用场景,可以帮助我们更好地理解和设计Java程序。

    (二)当谈到反射的使用情景时,以下是一些常见的使用场景和实例:

    1. 动态加载类和创建对象:通过反射,可以在运行时加载类并创建对象。这对于需要根据配置文件或用户输入来决定具体实例的情况非常有用。
    String className = "com.example.MyClass";
    Class<?> clazz = Class.forName(className);
    Object obj = clazz.newInstance();
    
    • 1
    • 2
    • 3
    1. 获取类的字段和方法信息:通过反射,可以在运行时获取类的字段和方法的信息,包括字段名、字段类型、方法名、方法参数等。
    Class<?> clazz = MyClass.class;
    Field[] fields = clazz.getDeclaredFields();
    Method[] methods = clazz.getDeclaredMethods();
    
    • 1
    • 2
    • 3
    1. 调用对象的方法:通过反射,可以在运行时调用对象的方法,包括公共方法和私有方法。
    Class<?> clazz = MyClass.class;
    Object obj = clazz.newInstance();
    Method method = clazz.getDeclaredMethod("myMethod", int.class);
    method.setAccessible(true);
    method.invoke(obj, 10);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 获取和设置字段的值:通过反射,可以在运行时获取和设置对象的字段的值,包括公共字段和私有字段。
    Class<?> clazz = MyClass.class;
    Object obj = clazz.newInstance();
    Field field = clazz.getDeclaredField("myField");
    field.setAccessible(true);
    Object value = field.get(obj);
    field.set(obj, newValue);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 获取和设置注解信息:通过反射,可以在运行时获取方法或类上的注解信息,进而进行相应的处理。
    Class<?> clazz = MyClass.class;
    Method method = clazz.getDeclaredMethod("myMethod");
    Annotation annotation = method.getAnnotation(MyAnnotation.class);
    
    • 1
    • 2
    • 3

    这些只是反射的一些常见使用情景和实例,实际上反射还有很多其他的应用场景,比如动态代理、框架开发等。反射是一个强大的工具,但也需要注意使用时的性能和安全性。

    (三)Java枚举类型:

    • Java中的枚举类型是一种特殊的数据类型,用于定义一组常量。它可以帮助我们更好地组织代码,并提供类型安全性。
    • 要定义一个枚举类型,我们可以使用enum关键字。

    以下是一个简单的枚举类型的示例:

    enum Day {
        MONDAY,
        TUESDAY,
        WEDNESDAY,
        THURSDAY,
        FRIDAY,
        SATURDAY,
        SUNDAY
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这个示例中,我们定义了一个名为Day的枚举类型,其中包含了一周中的每一天。每个枚举常量都是Day类型的实例,并以大写字母命名。在枚举类型中,每个枚举常量都是唯一的。

    我们可以使用枚举常量来引用它们的值,并进行比较。例如:

    Day today = Day.MONDAY;
    if (today == Day.MONDAY) {
        System.out.println("今天是星期一");
    }
    
    • 1
    • 2
    • 3
    • 4

    (四)枚举类型还可以有构造方法、实例方法和静态方法。

    我们可以向枚举常量添加构造参数,并在构造方法中初始化它们的值。
    例如:

    enum Day {
        MONDAY("星期一"),
        TUESDAY("星期二"),
        WEDNESDAY("星期三"),
        THURSDAY("星期四"),
        FRIDAY("星期五"),
        SATURDAY("星期六"),
        SUNDAY("星期日");
    
        private String chineseName;
    
        Day(String chineseName) {
            this.chineseName = chineseName;
        }
    
        public String getChineseName() {
            return chineseName;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这个示例中,我们为每个枚举常量添加了一个名为chineseName的成员变量,并在构造方法中进行初始化。我们还定义了一个名为getChineseName的实例方法,用于获取枚举常量的中文名。

    要使用枚举常量的成员变量或实例方法,只需像访问普通对象的成员一样使用即可。例如:

    Day today = Day.MONDAY;
    System.out.println(today.getChineseName());  // 输出:星期一
    
    • 1
    • 2

    总结一下,Java枚举类型是一种特殊的数据类型,用于定义一组常量。它可以提供类型安全性,并通过添加构造方法、实例方法和静态方法来增强其功能。希望这个解释对你有帮助!如有任何问题,请随时提问。

    (五)Java事务注解@Transactional

    @Transactional
    是一种用于保证数据库操作的原子性、一致性、隔离性和持久性的机制。在开发中,当需要执行一系列的数据库操作,而这些操作需要保证一起成功或一起失败的情况下,就可以使用事务来实现。

    事务的特点包括:

    1. 原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败,不会出现部分操作成功而部分操作失败的情况。
    2. 一致性(Consistency):事务开始前和结束后,数据库的完整性约束不被破坏。即事务执行前后,数据库的数据应保持一致。
    3. 隔离性(Isolation):事务的执行不受其他事务的干扰,每个事务都感觉不到其他并发事务的存在。
    4. 持久性(Durability):一旦事务提交,其所做的修改将会永久保存在数据库中,即使系统发生故障也不会丢失。

    在开发中,可以使用注解或编程方式来使用事务。在Java中,可以使用Spring框架提供的@Transactional注解来声明事务的边界。通过在方法上添加@Transactional注解,可以将该方法内的数据库操作纳入事务的管理。

    例如:

    @Transactional
    public void transferMoney(Account from, Account to, double amount) {
        // 执行转账操作
        from.withdraw(amount);
        to.deposit(amount);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    以上代码表示将transferMoney方法内的from.withdrawto.deposit操作放在同一个事务中。如果其中任意一个操作失败,整个事务都会回滚,从而保证了转账操作的一致性。

    事务的使用可以有效地保证数据库操作的完整性和一致性,避免了数据不一致的情况发生。因此,在开发中,合理地运用事务机制是非常重要的。

    (六)增强for循环(也称为for-each循环)

    是一种简化的循环语法,用于遍历集合类中的元素。它的语法如下:

    for (元素类型 变量名 : 集合) {
        // 在循环体中使用变量处理元素
    }
    
    • 1
    • 2
    • 3
    int[] numbers = {1, 2, 3, 4, 5};
    
    for (int number : numbers) {
        System.out.println(number);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在循环的每次迭代中,变量会依次引用集合中的每个元素,可以直接在循环体中使用该变量进行操作。增强for循环适用于任何实现了Iterable接口的集合类,例如List、Set和数组等。

    (七)迭代器

    一种更底层的遍历机制,通过实现Iterator接口来提供统一的访问方式。使用迭代器可以逐个访问集合中的元素,并在遍历过程中删除元素。迭代器的常见用法如下:

    Iterator<元素类型> iterator = 集合.iterator();
    while (iterator.hasNext()) {
        元素类型 变量名 = iterator.next();
        // 在循环体中使用变量处理元素
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    import java.util.ArrayList;
    import java.util.Iterator;
    
    ArrayList<String> names = new ArrayList<>();
    names.add("Alice");
    names.add("Bob");
    names.add("Charlie");
    
    Iterator<String> iterator = names.iterator();
    while (iterator.hasNext()) {
        String name = iterator.next();
        System.out.println(name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    首先,通过调用集合的iterator()方法获取迭代器对象。然后,使用while循环和迭代器的hasNext()方法判断是否还有下一个元素。如果有,可以通过调用迭代器的next()方法获取下一个元素,并在循环体中使用该元素进行操作。

    增强for循环和迭代器都可以用于遍历集合类中的元素,具体使用哪种方式取决于个人偏好和需求。增强for循环相对简洁易读,适用于简单的遍历操作;而迭代器更加灵活,可以在遍历过程中删除元素等操作。

    (八)浅谈HashMap:

    1. HashMap的特点:HashMap允许null值和null键,它是非线程安全的,不保证元素的顺序,是基于哈希表实现的。

    2. 哈希表:HashMap内部使用了一个数组来存储元素,每个数组元素又是一个链表(或者红黑树,当链表长度超过阈值时),链表中的每个节点包含了键值对。当需要存储一个键值对时,首先根据键的hashCode()方法计算哈希值,然后根据哈希值计算出数组的索引位置,最后将键值对存储在该位置上。

    3. HashMap的常见操作:

      • put(key, value):向HashMap中插入键值对,如果键已经存在,则更新对应的值。
      • get(key):根据键获取对应的值,如果键不存在,则返回null。
      • remove(key):根据键移除对应的键值对。
      • containsKey(key):判断HashMap中是否包含指定的键。
      • containsValue(value):判断HashMap中是否包含指定的值。

    扩展:

    • 当使用迭代器或者增强的for循环遍历HashMap时,可以通过调用HashMap的entrySet()方法来获取一个包含所有键值对的Set集合。然后可以使用迭代器或者增强的for循环遍历这个Set集合,从而遍历HashMap中的键值对。

    例如:

    HashMap<String, Integer> hashMap = new HashMap<>();
    hashMap.put("A", 1);
    hashMap.put("B", 2);
    hashMap.put("C", 3);
    
    // 使用迭代器遍历HashMap
    Iterator<Map.Entry<String, Integer>> iterator = hashMap.entrySet().iterator();
    while(iterator.hasNext()) {
        Map.Entry<String, Integer> entry = iterator.next();
        String key = entry.getKey();
        Integer value = entry.getValue();
        System.out.println("Key: " + key + ", Value: " + value);
    }
    
    // 使用增强的for循环遍历HashMap
    for(Map.Entry<String, Integer> entry : hashMap.entrySet()) {
        String key = entry.getKey();
        Integer value = entry.getValue();
        System.out.println("Key: " + key + ", Value: " + value);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • HashMap的初始容量和负载因子
    • 初始容量是指哈希表的大小,即哈希表在创建时可以容纳的键值对数量。默认初始容量是16。
    • 负载因子是指哈希表在容量不够时,扩容的比例。当哈希表的键值对数量达到容量乘以负载因子时,就会触发扩容操作。HashMap的默认负载因子是0.75,这意味着当键值对的数量达到容量的75%时,就会触发扩容操作。
    • 当键值对的数量达到12时,即16的75%,HashMap会自动扩容,将容量增加到原来的两倍,即32。这样可以保证在大部分情况下,HashMap的性能是比较高效的。扩容操作会重新计算每个键值对的哈希值,并重新分配到新的桶中,以保证哈希表的均衡性。

    对于HashMap的性能,合理的初始容量和负载因子可以减少哈希冲突的概率,提高HashMap的性能。如果初始容量过小或者负载因子过大,会导致哈希冲突的概率增加,从而影响HashMap的性能。因此,在创建HashMap时,可以根据预估的键值对数量和应用场景的特点来选择合适的初始容量和负载因子。

    (九)浅谈重写hashCode()和equals()方法

    (1)重写hashCode()equals()方法是为了确保对象在哈希表中的正确行为。
    (2)在使用HashMap等基于哈希表的数据结构时,它们会使用对象的hashCode()方法返回的哈希值来确定对象在数组中的索引位置。而equals()方法则用于比较两个对象是否相等。

    • 当我们重写了hashCode()方法时,我们改变了对象的哈希值计算方式,如果不同时重写equals()方法,就会导致相等的对象在哈希表中被判断为不相等。这样会导致在查询、删除等操作时无法正确地找到相等的对象。

    因此,为了保证对象在哈希表中的正确性,我们需要同时重写hashCode()equals()方法,使得相等的对象具有相同的哈希值,并且通过equals()方法判断它们是否相等。这样才能保证哈希表的正常运作。

    下面是一个示例代码,展示了如何重写hashCode()equals()方法:

    public class Person {
        private String name;
        private int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        // 重写hashCode()方法
        @Override
        public int hashCode() {
            int result = 17; // 初始化一个常数
            result = 31 * result + name.hashCode(); // 将name的哈希值与result相乘并相加
            result = 31 * result + age; // 将age与result相乘并相加
            return result;
        }
    
        // 重写equals()方法
        @Override
        public boolean equals(Object obj) {
            if (this == obj) { // 判断是否为同一个对象
                return true;
            }
            if (obj == null || getClass() != obj.getClass()) { // 判断是否为null或者类型不同
                return false;
            }
            Person person = (Person) obj; // 将对象强制转换为Person类型
            return age == person.age && Objects.equals(name, person.name); // 比较name和age是否相等
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
  • 相关阅读:
    String类相关面试题
    《面试系列篇》——11种常用的设计模式
    reatc的几个基础的hooks
    一款集成ST-link下载及虚拟串口的STM32F103C8T6最小系统板设计
    ubuntu下ssh环境
    直冲云霄,阿里大牛耗时49天整理12W字面试手册,押题准确率直冲95%
    CUDA02 - 访存优化和Unified Memory
    蓝桥杯Java B组历年真题(2013年-2021年)
    使用DIV、CSS技术设计的个人博客网页(web期末考试)
    【CAS:41994-02-9 |Biotinyl Tyramide|】生物素基酪氨酰胺
  • 原文地址:https://blog.csdn.net/m0_59076472/article/details/133069189