Java 基础常见问题总结(5)

为什么不建议使用异常控制业务流程?

就是说最好不要在catch做一些业务相关的,例如补偿之类的,都不要

理由就是:

  • 性能不好,异常的抛出和捕获都是比较浪费性能的
  • 比较依赖底层的数据库抛出异常,要是底层数据库换了可能会不抛异常,不利于维护性
  • 然后就是可能影响事务回滚,如果是使用的声明式事务的,它其实会依赖抛出的异常,所以如果式使用的catch进行捕获后还要重新抛出让框架感知,但是如果我们是一些预期可能会出现的异常,我们不需要往外抛,更多是打一下日志就行(例如初始化用户的时候,发现插入失败。说明这个用户已经有了,这个时候抛的异常不是应该的吗)

为什么对Java中的负数取绝对值结果不一定是正数?

答案就是溢出。

例如我们比较常见的int类型

java 复制代码
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException {
    Integer num = Integer.MIN_VALUE;
    System.out.println(Math.abs(num)); // -2147483648

    System.out.println(Math.abs((long) num)); // 2147483648
}

一旦出现溢出,就会变成负数,那么解决的方法就是先转化成更大的容器再进行取绝对值

如果一个数大到连Long都放不下的时候就需要分情况进行存储了

  • 如果是只存储展示:String
  • 要进行计算:BigInteger
  • 涉及到小数计算:BigDecimal

为什么建议多用组合少用继承?

继承就是子类直接继承父类

假设说使用组合的关系来表示每一种鸟,那么我们通常给鸟这一个父类加上 fly() 方法,子类继承的时候自然就获得这个方法,但是有些鸟并不会飞,所以这是不对的

组合就是把每一个功能分散开,各个子类自动选择组合

按照组合的思路,我们应该把鸟的各个行为单独进行封装,比如fly、eat,然后每一种鸟单独去继承这些接口,这样虽然冗余,但是灵活

结论:尽量少用继承,多用组合

为什么建议自定义一个无参构造函数?

需要单独定义一个无参构造器的情况是因为我们已经定义了一个有参的,这个时候jdk就不会默认帮我们创建默认构造器了,但是一般推荐再单独定义一个

原因:

  • 有些框架会用到,例如Spring创建Bean对象的时候依赖无参构造器
  • 方便反射,像我们在使用一些反射技术的时候通常是调用无参构造器
  • 可能导致子类出现错误,如果有继承关系的话,子类默认是调用父类的无参构造,不定义会出错

有了equals为啥需要hashCode方法?

答案就是为了规范,规范定义的就是当equals判断为true的时候,hashCode的判断结果也一定要为true,反之不必,因为可能哈希冲突

那这个东西真的会有使用场景吗?答案就是有的

java 复制代码
import java.util.Objects;

public class Person {

    private String name;
    private int age;

    public Person() {}

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) return false;
        Person p = (Person) obj;
        if (this.name.isBlank() || p.name.isBlank()) return false;
        return this.age == p.age && this.name.equals(p.name);
    }
}
java 复制代码
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class Main {

    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException {
        Person p1 = new Person("ll", 10);
        Person p2 = new Person("ll", 10);
        Map<Person, String> map = new HashMap<>();
        map.put(p1, "888");

        System.out.println(map.get(p2));
    }
}

上面是完整的代码,可以注释掉hashCode() 方法,运行的结果是null,当打开时,运行结果时888

具体的原因就是map底层依赖了这两个方法来判断是否是同一个key

java 复制代码
final Node<K,V> getNode(Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n, hash; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & (hash = hash(key))]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

以下关于异常处理的代码有哪些问题?

java 复制代码
public static void start() throws IOException, RuntimeException{
    throw new RuntimeException("Not able to Start");
 }

 public static void main(String args[]) {
    BufferedReader br = null;
    try {
        String line;
        br = new BufferedReader(new FileReader("\home\usr\test.java"));
        while ((line = br.readLine()) != null) {
            start();
        }
        return;
    } catch (Exception ex) {
        ex.printStackTrace();
    } catch (RuntimeException re) {
        re.printStackTrace();
    } finally {
        // 是否会输出?
        System.out.print("1");
    }
 }
  1. start方法不会发生IOException,所以不需要throws
  2. RuntimeExcption不需要显式的throws
  3. catch的时候,要先从子类开始catch,代码中catch的顺序不对
  4. 没有关闭流
  5. return之前的finally block是会被执行的

怎么修改一个类中的private修饰的String参数的值?(挖坑)

首先String是不可变的,看似完美在修改它的值,其实是指向了一个新对象

那我们不考虑这个,我们就看一下对于private修饰的字段如何进行修改

  1. 反射
java 复制代码
public class Main {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        Person p1 = new Person("zhangsan");
        System.out.println(p1); // Person{name='zhangsan'}

        Field field = Person.class.getDeclaredField("name");
        field.setAccessible(true); // 设置private字段可见
        field.set(p1, "wangwu");
        System.out.println(p1); // Person{name='wangwu'}
    }
}
  1. 对外暴露setter方法
java 复制代码
public class Main {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        Person p1 = new Person("zhangsan");
        System.out.println(p1); // Person{name='zhangsan'}

        p1.setName("wangwu");
        System.out.println(p1); // Person{name='wangwu'}
    }
}
相关推荐
苍何14 小时前
30分钟用 Agent 搓出一家跨境网店,疯了
后端
ssshooter14 小时前
Tauri 2 iOS 开发避坑指南:文件保存、Dialog 和 Documents 目录的那些坑
前端·后端·ios
追逐时光者15 小时前
一个基于 .NET Core + Vue3 构建的开源全栈平台 Admin 系统
后端·.net
程序员飞哥15 小时前
90后大龄程序员失业4个月终于上岸了
后端·面试·程序员
zs宝来了15 小时前
Playwright 自动发布 CSDN 的完整实践
java
吴声子夜歌16 小时前
TypeScript——基础类型(三)
java·linux·typescript
GetcharZp16 小时前
Git 命令行太痛苦?这款 75k Star 的神级工具,让你告别“合并冲突”恐惧症!
后端
Victor35617 小时前
MongoDB(69)如何进行增量备份?
后端
Victor35617 小时前
MongoDB(70)如何使用副本集进行备份?
后端