Java开发常出错4颗星 | 您所在的位置:网站首页 › java中的readvalue方法作用 › Java开发常出错4颗星 |
Java计算、集合、集合接口
总是用不好数值计算、日期计算该怎么办
用于精确计算的类BigDecimal
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 SimpleDateFormat使用上的坑 可以解析大于/等于它定义的时间精度,但是不能解析小于它定义的时间精度 /** * 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对象 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 解决方案如博客,可以引用锁、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 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的实现过程需要同步 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注解没有达到预期该怎么办利用抽象语法树在编译时将注解转换为部分代码生成字节码文件 坑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); }原因是因为iPhone属性不是标准的驼峰命名,而lombok会将其转换为iphone进行反序列化,验证将json字符串改为{\"name\":\"qinyi\",\"userName\":\"qinyi-imooc\",\"iphone\":\"8.1\"},报错消失, 因此需要在进行属性命名时采用驼峰命名,避免出现单字母驼峰命名 坑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方法只比较的子类属性值 callSuper = true,根据子类自身的字段值和从父类继承的字段值 来生成hashcode,当两个子类对象比较时,只有子类对象的本身的字段值和继承父类的字段值都相同,equals方法的返回值是true。 callSuper = false,根据子类自身的字段值 来生成hashcode, 当两个子类对象比较时,只有子类对象的本身的字段值相同,父类字段值可以不同,equals方法的返回值是true。 因此将@EqualsAndHashCode(callSuper = true)设置为true(默认是false)之后,就可以带上父类属性值进行比较。再次查看编译后的子类字节码。 观察可以发现,equals方法中加入了父类对象的方法比较。 详细解读见博客@EqualsAndHashCode(callSuper = true/false) 作用 |
CopyRight 2018-2019 实验室设备网 版权所有 |