深入剖析Java常见编程陷阱与优化策略

在本文中,我们将深入探讨Java编程中常见的陷阱和优化策略,涵盖死循环风险、线程安全问题、泛型使用、集合类比较以及精度处理等方面。通过具体代码示例和详细的分析,我们揭示了潜在的编程错误,并提供了有效的解决方案。无论是避免死循环和栈溢出,还是正确使用并发集合类,本文都为读者提供了实用的指导。同时,我们还展示了如何确保自定义对象在集合中的唯一性,以及如何使用 BigDecimal 处理精度问题。通过这些优化策略,开发者可以编写更健壮、更高效的Java代码。

死循环风险代码分析与优化

示例代码1
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("aa");
    list.add("bb");
    while (list.iterator().hasNext()) {
        System.out.println(list.iterator().next());
    }
}

问题分析

  • 每次 while 循环都会重新创建一个新的 Iterator,并且每个 Iteratornext() 方法只能调用一次,否则会抛出 NoSuchElementException
  • 由于 hasNext() 总是返回 true,这导致死循环。

优化建议

  • 应在循环外创建一次 Iterator,然后使用该 Iterator 进行遍历。
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("aa");
    list.add("bb");
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
}
示例代码2
public static void main(String[] args) {
    int count = 0;
    while (count < 10) {
        if (count == 4) {
            continue;
        }
        System.out.println(count);
        count++;
    }
}

问题分析

  • continue 会导致 count 不增加,从而导致死循环。

优化建议

  • continue 之前增加 count++
public static void main(String[] args) {
    int count = 0;
    while (count < 10) {
        if (count == 4) {
            count++;
            continue;
        }
        System.out.println(count);
        count++;
    }
}
示例代码3
public class Tmp {
    private boolean flag = true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public void fun() {
        while (flag) {
        }
        System.out.println("done");
    }

    public static void main(String[] args) throws InterruptedException {
        final Tmp flagTest = new Tmp();
        new Thread(() -> flagTest.fun()).start();
        Thread.sleep(200);
        flagTest.setFlag(false);
    }
}

问题分析

  • flag 变量未声明为 volatile,可能会导致线程不更新该变量的值,导致死循环。

优化建议

  • flag 变量声明为 volatile
public class Tmp {
    private volatile boolean flag = true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public void fun() {
        while (flag) {
        }
        System.out.println("done");
    }

    public static void main(String[] args) throws InterruptedException {
        final Tmp flagTest = new Tmp();
        new Thread(() -> flagTest.fun()).start();
        Thread.sleep(200);
        flagTest.setFlag(false);
    }
}
示例代码4
public Category findRoot(long categoryId) {
    Category category = categoryMapper.findCategoryById(categoryId);
    if(category == null) {
        throw new BusinessException("分类不存在");
    }
    long parentId = category.getParentId();
    if(parentId == null || parentId == 0) {
        return category;
    }
    return findRoot(parentId);
}

问题分析

  • 如果存在环形链表结构,会导致无限递归,最终导致栈溢出。

优化建议

  • 使用集合记录访问过的节点,避免循环引用。
public Category findRoot(long categoryId) {
    Set<Long> visited = new HashSet<>();
    return findRoot(categoryId, visited);
}

private Category findRoot(long categoryId, Set<Long> visited) {
    if (!visited.add(categoryId)) {
        throw new BusinessException("循环引用检测到");
    }
    Category category = categoryMapper.findCategoryById(categoryId);
    if (category == null) {
        throw new BusinessException("分类不存在");
    }
    long parentId = category.getParentId();
    if (parentId == 0) {
        return category;
    }
    return findRoot(parentId, visited);
}

线程安全问题与代码优化

private static void extracted() {
    ConcurrentHashMap<String, Student> map = new ConcurrentHashMap<>();
    String key = "test";
    Student student = map.get(key);
    if (student == null) {
        student = new Student();
        map.put(key, student);
    }
}

问题分析

  • 存在多线程下的竞态条件,可能导致多个线程同时创建 Student 实例并覆盖彼此的值。

优化建议

  • 使用 putIfAbsent 方法确保线程安全。
private static void extracted() {
    ConcurrentHashMap<String, Student> map = new ConcurrentHashMap<>();
    String key = "test";
    map.putIfAbsent(key, new Student());
    Student student = map.get(key);
}

编译错误分析

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("ab");
    printList(list);
}

public static void printList(List<Object> list) {
    Iterator it = list.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }
}

问题分析

  • List<String> 不能传递给 List<Object>,因为 Java 的泛型是不可协变的。

优化建议

  • 使用通配符 ? 来处理泛型。
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("ab");
    printList(list);
}

public static void printList(List<?> list) {
    Iterator<?> it = list.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }
}

hashCode 和 equals 的处理

正确的说法

AC

  • A. 只要覆写 equals,就必须覆写 hashCode。(正确,保证相等的对象有相同的哈希码)
  • C. 如果自定义对象作为 Map 的键,那么必须覆写 hashCodeequals。(正确,确保键的唯一性和正确的检索)
示例代码优化
public class Tmp {
    public static void main(String[] args) {
        Student zs1 = new Student("zs");
        Student zs2 = new Student("zs");
        HashSet<Student> set = new HashSet<>();
        set.add(zs1);
        boolean addSuccess = set.add(zs2);
        if (addSuccess) {
            System.out.println("add 成功了");
        }
        Map<Student, Integer> map = new HashMap<>();
        map.put(zs1, 1);
        Integer put = map.put(zs2, 2);
        if (put == null) {
            System.out.println("put 成功了");
        }
    }
}

class Student {
    String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name);
    }

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

BigDecimal 创建方法的精度风险

精度风险分析
  • A. BigDecimal a = new BigDecimal(0.1F) 存在精度风险,因为浮点数不能精确表示。
  • B. BigDecimal b = new BigDecimal("0.1") 安全,无精度损失。
  • C. BigDecimal c = BigDecimal.valueOf(0.1) 推荐使用,内部实现了对 double 类型的处理。

方法调用输出分析

List 对象的多态方法调用
class A {
    public void hello(List list) {
        System.out.println("list");
    }
    public void hello(ArrayList list) {
        System.out.println("arrayList");
    }
    public static void main(String[] args) {
        A a = new A();
        List list = new ArrayList();
        a.hello(list);
    }
}

输出分析

  • 输出 list,因为方法的选择是在编译时基于参数的静态类型(List)。
双精度比较与 BigDecimal 比较
private static void main(String[] args) {
    double a = 1.0D - 0.9D;
    double b = 0.1D;
    Double x = Double.valueOf(a);
    Double y = Double.valueOf(b);
    System.out.println(a == b);
    System.out.println(x.equals(y));
}

输出分析

  • a == b 输出 false,因为浮点数比较存在精度问题。
  • x.equals(y) 输出 false,因为 Double 类型包装类同样存在精度问题。

使用 org.apache.commons.lang3 包的 StringUtils

private static void main(String[] args) {
    String str = " ";
    boolean empty = StringUtils.isEmpty(str);
    System.out.println("empty = " + empty);
    boolean blank = StringUtils.isBlank(str);
    System.out.println("blank = " + blank);
}

输出分析

  • empty = false,因为 isEmpty 检查字符串是否为 null 或空串。
  • blank = true,因为 isBlank 检查字符串是否为 null 或仅包含空白字符。

包含多个集合比较的输出分析

List 比较
private static void extracted5() {
    List<String> list1 = new ArrayList<>();
    list1.add("eee");
    list1.add("www");
    List<String> list2 = new LinkedList<>();
    list2.add("eee");
    list2.add("www");
    System.out.println(list1.equals(list2));
}

输出分析

  • true,因为 Listequals 方法只比较元素和顺序,不考虑具体实现类型。
Set 比较
private static void extracted6() {
    Set<String> set1 = new HashSet<>();
    Set<String> set2 = new LinkedHashSet<>();
    set1.add("www");
    set2.add("www");
    System.out.println(set1.equals(set2));
}

输出分析

  • true,因为 Setequals 方法只比较元素,不考虑具体实现类型和顺序。
Map 比较
private static void extracted7() {
    Map<Integer, String> m1 = new TreeMap<>();
    Map<Integer, String> m2 = new LinkedHashMap<>();
    m1.put(1, "www");
    m1.put(2, "eee");
    m2.put(2, "eee");
    m2.put(1, "www");
    System.out.println(m1.equals(m2));
}

输出分析

  • false,因为 TreeMapLinkedHashMap 的比较会考虑键值对的顺序。
Integer 与 String 的 equals 方法比较
private static void extracted8() {
    Integer integer = new Integer(3);
    System.out.println(integer.equals("3"));
}

输出分析

  • false,因为 IntegerString 类型不同,equals 方法返回 false

优化总结

  • 在编写代码时,应充分考虑可能出现的死循环、线程安全问题和其他潜在的逻辑错误。
  • 使用合适的同步机制或线程安全类来避免多线程问题。
  • 对于集合类的比较,应了解其具体的 equalshashCode 实现逻辑。
  • 对于涉及浮点数和精度的问题,推荐使用 BigDecimal 并采取适当的初始化方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值