java基础细节

Arrays.asList()数组转集合bug


Arrays.asList()方法返回的是Arrays类中的一个私有静态内部类ArrayList,它继承于AbstractList类,实现了List接口,重写了add()、remove()等修改List结构的方法,并直接抛出UnsupportedOperationException异常,从而禁止了对List结构的修改

public class Demo {

    public static void main(String[] args) {
        // List<Integer> list = Arrays.asList(1, 2, 3);
        // 执行add方法会抛出UnsupportedOperationException异常
        // list.add(4);

        // 如果需要写操作,使用下面这种写法
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
        list.add(4);
        list.forEach(System.out::println);
    }
}

集合类不安全之并发修改异常


ArrayList是线程不安全的,多线程下使用add()方法,容易抛出ConcurrentModificationException异常,可以使用Vector、Collections.synchronizedList()、CopyOnWriteArrayList解决

public class Demo {

    public static void main(String[] args) {
        // ArrayList是线程不安全的,多线程下容易出现ConcurrentModificationException异常
        // List<Integer> list = new ArrayList<>();

        // 1.使用Vector
        // List<Integer> list = new Vector<>();

        // 2.使用Collections工具包的synchronizedList()方法
        // List<Integer> list = Collections.synchronizedList(new ArrayList<>());

        // 3.使用CopyOnWriteArrayList
        List<Integer> list = new CopyOnWriteArrayList<>();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                list.add(new Random().nextInt(1000));
                System.out.println(list);
            }).start();
        }
    }
}

遍历集合时remove操作bug


不要在forEach中对集合元素进行remove或add操作,否则会抛出ConcurrentModificationException异常,删除元素请使用iterator方式进行remove操作

public class Demo {

    public static void main(String[] args) {
        // List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
        // 在forEach中执行remove方法会抛出ConcurrentModificationException异常
        // list.forEach(i -> {
        //     if (i == 2) {
        //         list.remove(i);
        //     }
        // });
        
        // 使用iterator的remove方法
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            Integer i = iterator.next();
            if (i == 2) {
                iterator.remove();
            }
        }
        list.forEach(System.out::println);
    }
}

哈希冲突案例


以JDK17为例,大概new10万个对象以后,会出现hash冲突

public class Demo {

    public static void main(String[] args) {
        HashSet<Integer> hashSet = new HashSet<>();
        // 大概new10万个对象以后,会出现hash冲突
        for (int i = 1; i <= 11 * 10000; i++) {
            int hashCode = new Object().hashCode();
            if (hashSet.contains(hashCode)) {
                System.out.println("第" + i + "次时出现了hash冲突,hashcode为:" + hashCode);
            } else {
                hashSet.add(hashCode);
            }
        }
        System.out.println("总数:" + hashSet.size());
    }
}

Integer等值比较规则


所有Integer整形包装类对象的等值比较,全部使用equals()方法,因为在-128 ~ 127之间的值,Integer对象是在cache缓存中获取的,会复用已有的对象,这个区间内的Integer对象可以使用 == 进行比较,但是这个区间之外的Integer对象都是重新new出来的,需要使用equals()方法比较

public class Demo {

    public static void main(String[] args) {
        Integer a = Integer.valueOf(100);
        Integer b = Integer.valueOf(100);
        System.out.println(a == b);        // true
        System.out.println(a.equals(b));   // true

        Integer c = Integer.valueOf(200);
        Integer d = Integer.valueOf(200);
        int e = 200;
        System.out.println(c == d);        // false
        System.out.println(c.equals(d));   // true
        System.out.println(c == e);        // true
    }
}

BigDecimal


对于不需要准确计算精度的数据,可以直接使用Float和Double处理,如果需要精确计算的,必须使用BigDecimal处理,BigDecimal对象之间的数字运算不能使用传统的 +、-、*、/ 等算术运算符,必须调用其对应的方法进行运算

1.禁止使用BigDecimal(double)构造方法将double值转换为BigDecimal对象,因为BigDecimal(double)存在精度丢失的风险,new BigDecimal(0.1)实际存储值为0.1000000000000000055511151231257827021181583404541015625,优先推荐使用BigDecimal(String)构造方法,或BigDecimal.valueOf()方法,其中valueOf()方法内部其实执行了Double的toString方法,在Double的toString方法中会按double实际能表达的精度对尾数进行截断

2.BigDecimal等值比较禁止使用equals()方法,而应该使用compareTo()方法,因为equals()方法会把值和精度都进行比较(1.0和1.00返回结果为false),而compareTo()会忽略精度

public class Demo {

    public static void main(String[] args) {
        // double有效位数为16位,会存在存储小数位数不够的情况,容易出现精度丢失,产生误差
        System.out.println(0.3 - 0.2);  // 0.09999999999999998

        // 禁止使用BigDecimal(Double)构造方法
        System.out.println(new BigDecimal(0.1));  // 0.1000000000000000055511151231257827021181583404541015625

        // 优先推荐使用BigDecimal(String)构造方法,或BigDecimal.valueOf()方法
        System.out.println(new BigDecimal("0.1"));  // 0.1
        System.out.println(BigDecimal.valueOf(0.1));   // 0.1
        // 推荐使用BigDecimal(String)构造方法
        System.out.println(new BigDecimal("112233445566778899.123456789"));  // 112233445566778899.123456789
        System.out.println(BigDecimal.valueOf(112233445566778899.123456789));    // 112233445566778896

        // BigDecimal等值比较禁止使用equals()方法,而应该使用compareTo()方法
        BigDecimal a = new BigDecimal("1.0");
        BigDecimal b = new BigDecimal("1.00");
        System.out.println(a.equals(b));     // false
        // -1表示"小于",0表示"等于",1表示"大于"
        System.out.println(a.compareTo(b));  // 0

        // BigDecimal两数相除,divide()方法必须指定精度,否则会抛出ArithmeticException异常
        BigDecimal c = new BigDecimal("2.0");
        BigDecimal d = new BigDecimal("3.0");
        // 小数点后保留两位,RoundingMode.HALF_UP表示"四舍五入"
        System.out.println(c.divide(d, 2, RoundingMode.HALF_UP));  // 0.67
    }
}

List元素去重


本次整理了4种List元素去重方法,推荐Stream流式去重或HashSet去重

public class Demo {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 3, 6, 3, 7, 1, 1, 5));
        // 1. forEach遍历,判断是否存在,不存在就加入newList1中
        List<Integer> newList1 = new ArrayList<>();
        list.forEach(i -> {
            if (!newList1.contains(i)) {
                newList1.add(i);
            }
        });
        System.out.println(newList1);  // [1, 3, 6, 7, 5]

        // 2. HashSet或LinkedHashSet去重,HashSet是无序的,LinkedHashSet是有序的
        List<Integer> newList2 = new ArrayList<>(new HashSet<>(list));
        System.out.println(newList2);  // [1, 3, 5, 6, 7]
        List<Integer> newList3 = new ArrayList<>(new LinkedHashSet<>(list));
        System.out.println(newList3);  // [1, 3, 6, 7, 5]

        // 3. Stream流式去重
        List<Integer> newList4 = list.stream().distinct().toList();
        System.out.println(newList4);  // [1, 3, 6, 7, 5]

        // 4. forEach遍历,使用indexOf从左到右和lastIndexOf从右到左,判断下标是否相等,不相等表示重复
        List<Integer> newList5 = new ArrayList<>(list);
        list.forEach(i -> {
            if (newList5.indexOf(i) != newList5.lastIndexOf(i)) {
                newList5.remove(i); 
            }
        });
        System.out.println(newList5);  // [6, 3, 7, 1, 5]
    }
}

==和equals对比


1.==既可以比较基本数据类型,也可以比较引用类型,如果比较的是基本数据类型,实际是比较值是否相等,如果比较的是引用类型,实际是比较内存地址是否相等

2.equals只能比较引用类型,比较规则要看是否重写过equals方法,如果没有重写过,则使用Object类equals方法,其内部实际是==的判断,比较的是内存地址是否相等,如果重写过,则要看具体实现方法进行判断

public class Demo {

    static class User {
        private String name;

        User(String name) {
            this.name = name;
        }
    }

    public static void main(String[] args) {
        // User没有重写equals和hashCode方法,使用的是Object原生的方法
        User user1 = new User("mary");
        User user2 = new User("mary");
        System.out.println(user1 == user2);       // false
        System.out.println(user1.equals(user2));  // false
        // HashSet底层是HashMap,add方法实际是Map的put方法,key是根据hashCode进行比较的
        Set<User> userSet = new HashSet<>();
        userSet.add(user1);
        userSet.add(user2);
        System.out.println(userSet.size());  // 2

        // String重写了equals和hashCode方法
        String str1 = new String("mary");
        String str2 = new String("mary");
        System.out.println(str1 == str2);       // false
        System.out.println(str1.equals(str2));  // true
        Set<String> strSet = new HashSet<>();
        strSet.add(str1);
        strSet.add(str2);
        System.out.println(strSet.size());  // 1
    }
}

浅拷贝和深拷贝


浅拷贝:如果拷贝的是基本数据类型,则实际拷贝的是值,如果拷贝的是引用类型,则实际拷贝是内存地址,如果其中一个对象的内存地址发生了变化,就会影响到另一个对象

深拷贝:会产生一个新对象,与原对象不会共享内存地址,即使原对象的值发生了变化,也不会影响到新对象

public class Demo {

    static class Cat {
        private String name;

        Cat(String name) {
            this.name = name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    static class Person implements Cloneable {
        private String name;
        private Cat cat;

        Person(String name, String catName) {
            this.name = name;
            this.cat = new Cat(catName);
        }

        public Cat getCat() {
            return cat;
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            // Object类的clone方法是浅拷贝
            // return super.clone();

            // 深拷贝,重写clone方法
            return new Person(this.name, this.getCat().getName());
        }
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person("mary", "haha");
        // User类要使用clone方法,必须实现Cloneable接口,否则会抛出CloneNotSupportedException异常
        // 深拷贝还是浅拷贝,需要看Person类clone方法的具体实现
        Person newPerson = (Person) person.clone();
        person.getCat().setName("mimi");
        System.out.println(person.getCat().getName());
        System.out.println(newPerson.getCat().getName());
    }
}
  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值