目录
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());
}
}