Java开发常出错4颗星 您所在的位置:网站首页 java中的readvalue方法作用 Java开发常出错4颗星

Java开发常出错4颗星

2023-06-14 03:44| 来源: 网络整理| 查看: 265

Java计算、集合、集合接口 总是用不好数值计算、日期计算该怎么办 用于精确计算的类BigDecimal

image-20230613204514635

BigDecimal核心是精度,精度如果不匹配,结果大概率不会符合预期

初始精度需要匹配

/** * Scala 需要与小数位匹配 */ private static void scaleProblem() { BigDecimal decimal = new BigDecimal("12.222"); //制定小数精度不够, 底层会自动补零 12.222000000000 // BigDecimal result1 = decimal.setScale(12); // System.out.println(result1); //指定小数精度丢失场景 // BigDecimal result2 = decimal.setScale(2); // System.out.println(result2); // 使用两个参数的setScale方法,(newScala,roundingMode) BigDecimal result = decimal.setScale(2, RoundingMode.HALF_UP); System.out.println(result); }

除法结果需要精度

/** * BigDecimal 做除法时出现除不尽的情况 */ private static void divideProblem() { // 除不尽, 未传递roundingMode,抛出异常 System.out.println(new BigDecimal(30).divide(new BigDecimal(7))); // 可以正确执行 System.out.println(new BigDecimal(30).divide(new BigDecimal(7), 2, RoundingMode.HALF_UP)); }

数值比较需要精度匹配

/** * 精度问题导致比较结果和预期不一致 */ private static void equalProblem() { BigDecimal bd1 = new BigDecimal("0"); BigDecimal bd2 = new BigDecimal("0.0"); //false System.out.println(bd1.equals(bd2)); // true System.out.println(bd1.compareTo(bd2) == 0); }

equals返回为false, 是因为在equals源码中,如果精度不一致,会直接返回false

image-20230613210653767

SimpleDateFormat使用上的坑

image-20230613210848434

可以解析大于/等于它定义的时间精度,但是不能解析小于它定义的时间精度 /** * SimpleDateFormat 可以解析大于/等于它定义的时间进度 * * @throws Exception */ private static void formatPrecision() throws Exception { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String time_x = "2020-03-01 00:00:00"; String time = "2020-03"; System.out.println(sdf.parse(time_x)); // 抛出异常 Unparseable date: "2020-03" System.out.println(sdf.parse(time)); } 它是线程不安全的,在多线程环境下操作,会抛异常 /** * SimpleDataFormat 存在线程安全问题 */ private static void threadSafety() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingDeque(1000)); while(true){ threadPoolExecutor.execute( ()->{ String dateString = "2020-03-01 00:00:00"; try{ Date parseDate = sdf.parse(dateString); String dataString2 = sdf.format(parseDate); System.out.println(dateString.equals(dataString2)); }catch (ParseException ex){ ex.printStackTrace(); } }); } }

由于调用的format方法内部有引用canlendar对象

image-20230613212633440

canlendar 本身是线程不安全的,如下图,因此导致SimpleDataFormat format()线程不安全。详细见博客SimpleDateFormat的线程不安全问题

重点放在 calendar ,这个 format 方法在执行过程中,会操作成员变量 calendar 来保存时间 calendar.setTime(date) 。

但由于在声明 SimpleDateFormat 的时候,使用的是 static 定义的,那么这个 SimpleDateFormat 就是一个共享变量,SimpleDateFormat 中的 calendar 也就可以被多个线程访问到,所以问题就出现了,举个例子:

假设线程 A 刚执行完 calendar.setTime(date) 语句,把时间设置为 2020-09-01,但线程还没执行完,线程 B 又执行了 calendar.setTime(date) 语句,把时间设置为 2020-09-02,这个时候就出现幻读了,线程 A 继续执行下去的时候,拿到的 calendar.getTime 得到的时间就是线程B改过之后的。

除了 format() 方法以外,SimpleDateFormat 的 parse 方法也有同样的问题。

至此,我们发现了 SimpleDateFormat 的弊端,所以为了解决这个问题就是不要把 SimpleDateFormat 当做一个共享变量来使用。 ———————————————— 版权声明:本文为CSDN博主「Archie_java」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_43842093/article/details/125108071

image-20230613212850415

解决方案如博客,可以引用锁、apache工具集等。

SimpleDateFormat 讲解

小小 for 循环,粘上集合出大问题

传统的for循环是怎样的

如果是数组:通过数组长度,建立索引 //传统方式 - 使用索引 int [] xyz = new int[]{1,2,3,4,5}; for (int i = 0; i < xyz.length; i++) { System.out.println(xyz[i]); } 如果是集合:迭代器 // 传统方式 - 迭代器 for (Iterator i = left.iterator(); i.hasNext();) { System.out.println(i.next()); }

传统的for循环存在怎样的弊端与劣势呢

我需要的是可迭代对象的元素,并不需要元素的索引 //嵌套迭代容易出现问题 for (Iterator l = left.iterator(); l.hasNext();) { for(Iterator r= right.iterator();r.hasNext();){ System.out.println(l.next()*r.next()); } } // 1 4 9 16 25 6 14 Exception in thread "main" java.util.NoSuchElementException at java.util.AbstractList$Itr.next(AbstractList.java:364) at com.imooc.java.escape.ForeachOptimize.wrongIterator(ForeachOptimize.java:37) at com.imooc.java.escape.ForeachOptimize.main(ForeachOptimize.java:45) Process finished with exit code 1 // 正确的用法,嵌套迭代 for (Iterator l = left.iterator(); l.hasNext();) { Integer tmp = l.next(); for(Iterator r= right.iterator();r.hasNext();){ System.out.println(tmp * r.next()); } } 在嵌套环境下(多个可迭代对象),需要小心迭代器对象的正确性

for-each优于for

只专注于迭代对象自身,而不考虑多余的索引 任何实现Iterable接口的对象,都可以使用for-each循环处理 进一步扩展:java8 Iterable.forEach

image-20230613215643481

private static void square(int value){ System.out.println(value * value); } public static void main(String[] args) { // wrongIterator(); // Java8 Iterable.forEach vs for-each for(Integer l :left){ square(l); } left.forEach( l ->square(l)); left.forEach(ForeachOptimize::square); }; 如果不好好判定,集合存储就会乱套

Object的equals和hashCode方法

默认的equals和hashCode实现是怎样的(Object中定义的)? 只重写equals方法真的会出错嘛?

集合中元素的索引竟然也与equals方法相关

类实现了compareTo方法,就需要实现equals方法 compareTo与equals的实现过程需要同步

image-20230613223016202

public static class User implements Comparable{ private String name; private Integer age; public User() { } public User(String name, Integer age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public boolean equals(Object obj){ if(obj instanceof User){ User user = (User) obj; return this.name.equals(user.name) && this.age == user.age; } return false; } @Override public int hashCode(){ int result = name.hashCode(); result = 31 * result +age; return result; } @Override public int compareTo(User o) { return (this.age - o.age) + this.name.compareTo(o.name); } }

在main方法中定义set和map分别存储元素,如果未重写hashCode,则返回两个元素,尽管两个元素一模一样

private static void equalsAndHashcode(){ User user1 = new User("qinyi",19); User user2 = new User("qinyi",19); // System.out.println(user1.equals(user2)); // set判断对象是按照对象的hashCode是否一致 Set userSet = new HashSet(); userSet.add(user1); userSet.add(user2); Map userIntegerMap = new HashMap(); userIntegerMap.put(user1,0); userIntegerMap.put(user2,0); System.out.println(userSet.size()); System.out.println(userIntegerMap.size()); }

如果定义元素索引,通过list.indexOf()和Collections.binarySearch()得到的结果是不一致的,原因是indexOf()是给予equals方法查找的,而binarySearch是基于compareTo方法查找,需要将compareTo和equals实现过程同步。

/** * 集合元素索引与 equals方法相关 */ private static void compareToAndEquals(){ List users = new ArrayList(); users.add(new User("qinyi",10)); users.add(new User("qinyi",20)); User user = new User("qinyi",20); int index1 = users.indexOf(user); int index2 = Collections.binarySearch(users,user); // 0 基于equals方法进行查找 System.out.println(index1); // 1 基于compareTo方法进行查找 System.out.println(index2); } 使用lombok注解没有达到预期该怎么办

image-20230613225640681

image-20230613231922396

利用抽象语法树在编译时将注解转换为部分代码生成字节码文件

坑1:无法正确序列化对象 org.projectlombok lombok 1.16.18 com.fasterxml.jackson.core jackson-databind 2.10.0 @Data public class Personal { private String iPhone; private String name; private String userName; }

对json字符串{\"name\":\"qinyi\",\"userName\":\"qinyi-imooc\",\"iPhone\":\"8.1\"} ,进行反序列化会报错找不到属性iPhone

private static void singleAlphabetHump() throws Exception{ ObjectMapper mapper = new ObjectMapper(); Personal personal = new Personal(); personal.setIPhone("8.1"); // System.out.println(mapper.writeValueAsString(personal)); String json = "{\"name\":\"qinyi\",\"userName\":\"qinyi-imooc\",\"iPhone\":\"8.1\"}"; Personal personal1 = mapper.readValue(json,Personal.class); System.out.println(personal1); }

image-20230613232419209

原因是因为iPhone属性不是标准的驼峰命名,而lombok会将其转换为iphone进行反序列化,验证将json字符串改为{\"name\":\"qinyi\",\"userName\":\"qinyi-imooc\",\"iphone\":\"8.1\"},报错消失,

image-20230613232559591

因此需要在进行属性命名时采用驼峰命名,避免出现单字母驼峰命名

坑2:对象比较未比较父类的属性值

@Data @NoArgsConstructor @AllArgsConstructor public class Computer { private Integer id; private String name; } @Data @NoArgsConstructor @AllArgsConstructor public class AppleComputer extends Computer{ private long price; private String color; public AppleComputer(Integer id,String name,long price,String color){ super(id,name); this.price = price; this.color = color; } }

测试代码

/** * lombok第二个坑 */ private static void equalsAndHashCodeBug(){ AppleComputer computer1 = new AppleComputer(1,"Mac Pro",1L,"yellow"); AppleComputer computer2 = new AppleComputer(2,"Mac Air",1L,"yellow"); System.out.println(computer1.equals(computer2)); }

输出结果为true,查看lombok生成的编译后的字节码文件,可以看到,equals方法只比较的子类属性值

image-20230613233202466

callSuper = true,根据子类自身的字段值和从父类继承的字段值 来生成hashcode,当两个子类对象比较时,只有子类对象的本身的字段值和继承父类的字段值都相同,equals方法的返回值是true。

callSuper = false,根据子类自身的字段值 来生成hashcode, 当两个子类对象比较时,只有子类对象的本身的字段值相同,父类字段值可以不同,equals方法的返回值是true。

因此将@EqualsAndHashCode(callSuper = true)设置为true(默认是false)之后,就可以带上父类属性值进行比较。再次查看编译后的子类字节码。

image-20230613235054905

观察可以发现,equals方法中加入了父类对象的方法比较。

详细解读见博客@EqualsAndHashCode(callSuper = true/false) 作用



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有