首页 > 编程语言 >《java解惑》——库谜题

《java解惑》——库谜题

时间:2023-11-09 12:01:33浏览次数:44  
标签:java firstName int lastName equals 谜题 Test 解惑 public


1.不可变类:

问题:

下面的程序计算5000+50000+500000值,代码如下:

import java.math.BigInteger;  
  
public class Test{  
public static void main(String[] args){  
new BigInteger("5000");  
new BigInteger("50000");  
new BigInteger("500000");  
        BigInteger total = BigInteger.ZERO;  
        total.add(fiveThousand);  
        total.add(fiftyThousand);  
        total.add(fiveHundredThousand);  
        System.out.println(total);  
    }  
}


对于这个简单的小程序,我们都认为毫无疑问结果应该是555000,但是程序真实运行结果是0.

原因:

BigInteger实例是不可变的,此外String、BigDecimal以及包装类型:Integer、Long、Short、Byte、Character、Boolean、Float和Double都是不可变类,即不能修改它们实例的值,对这些不可变类型的操作将返回新的实例。

结论:

为了在一个包含对不可变对象引用的变量上执行计算,我们需要将计算结果赋值给该变量,代码如下:



import java.math.BigInteger;  
  
public class Test{  
public static void main(String[] args){  
new BigInteger("5000");  
new BigInteger("50000");  
new BigInteger("500000");  
        BigInteger total = BigInteger.ZERO;  
        total = total.add(fiveThousand);  
        total = total.add(fiftyThousand);  
        total = total.add(fiveHundredThousand);  
        System.out.println(total);  
    }  
}


通过上述修改,程序就可以打印出我们期望的555000.

不可变类不存在同步修改的问题,因此在并发多线程情况下不需要对其加锁或同步。

2.equals和hashCode方法:

问题:

下面的程序将一个不可变类添加进集合容器中,然后测试集合中是否包含它,代码如下:



import java.util.HashSet;  
import java.util.Set;  
  
public class Test{  
private String firstName, lastName;  
public Test(String firstName, String lastName){  
this.firstName = firstName;  
this.lastName = lastName;  
    }  
      
public boolean equals(Object o){  
if(!(o instanceof Test)){  
return false;  
        }  
        Test t = (Test)o;  
return t.firstName.equals(firstName) && t.lastName.equals(lastName);  
    }  
      
public static void main(String[] args){  
new HashSet<Test>();  
new Test("Mickey", "Mouse"));  
new Test("Mickey", "Mouse")));  
    }  
}

 


由于我们重写了Object类的equals比较方法,因此上述程序应该打印输出true,但是真实运行却打印输出false。

原因:

由于上述程序中使用了HashSet集合来存放对象,HashSet集合使用hash算法生成的hashCode来查找对象。

Hash算法约定:相同的对象(equals方法),hashCode一定相同;而hashCode相同,对象不一定相同(Hash碰撞)。

因此在使用基于Hash算法的集合时,覆盖equals方法的同时一定要重写hashCode方法,反之依然(当hash碰撞时,使用equals判定是否是相同对象)。

由于上述程序中Test类没有重写hashCode方法,则默认使用Object类中定义的hashCode方法,即使用对象的标识作为hashCode,当第一个对象被添加进集合中时,使用第一个对象的对象表示作为hashCode,再检查对象是否存在时,使用第二个对象的对象标识作为hashCode,由于这两个hashCode不想等,因此集合没有找到给定的对象,所以打印输出false。

结论:

修改上述程序问题很简单,只需要重写Test类的hashCode方法即可,代码如下:



import java.util.HashSet;  
import java.util.Set;  
  
public class Test{  
private String firstName, lastName;  
public Test(String firstName, String lastName){  
this.firstName = firstName;  
this.lastName = lastName;  
    }  
      
@Override  
public int hashCode() {  
return 37 * firstName.hashCode() + lastName.hashCode();  
    }  
  
@Override  
public boolean equals(Object o){  
if(!(o instanceof Test)){  
return false;  
        }  
        Test t = (Test)o;  
return t.firstName.equals(firstName) && t.lastName.equals(lastName);  
    }  
      
public static void main(String[] args){  
new HashSet<Test>();  
new Test("Mickey", "Mouse"));  
new Test("Mickey", "Mouse")));  
    }  
}


当重写类的equals方法时,一定要记得重写hashCode方法。

3.正确地重写equals方法:

问题:

和前一个谜题一样,上面的程序将对象添加到集合中,并且查询该集合是否包含它,代码如下:



import java.util.HashSet;  
import java.util.Set;  
  
public class Test{  
private String firstName, lastName;  
public Test(String firstName, String lastName){  
this.firstName = firstName;  
this.lastName = lastName;  
    }  
      
@Override  
public int hashCode() {  
return 37 * firstName.hashCode() + lastName.hashCode();  
    }  
  
public boolean equals(Test t){  
return t.firstName.equals(firstName) && t.lastName.equals(lastName);  
    }  
      
public static void main(String[] args){  
new HashSet<Test>();  
new Test("Mickey", "Mouse"));  
new Test("Mickey", "Mouse")));  
    }  
}


上述程序中既有equals方法,又有hashCode方法,因此我们觉得应该打印输出true,但是很不幸这次依然打印输出false。

原因:

仔细对比上述程序与第二个谜题中修改后的代码我们发现,hashCode方法没有问题,但是equals方法好像不太一样,Object中声明可以覆盖的equals方法的方法签名为:

public boolean equals(Object o)

但是上述程序中的equals方法的方法签名为:

public boolean equals(Test t)

由于输入参数不是Object类型,因此我们并没有重写Object的equals方法,而是重载了它。

Hash算法使用Object类中的equals(Object)方法来测试对象的相等性,重载的equals方法并没有被Hash算法所调用,因此虽然HashSet通过hashCode定位到了添加的对象,但是通过equals方法比较对象时却返回了false。

结论:

解决上述程序很简单,有两种方法:

方法一:

使用类谜题二中的equals方法,代码如下:



public boolean equals(Object o){  
if(!(o instanceof Test)){  
return false;  
        }  
        Test t = (Test)o;  
return t.firstName.equals(firstName) && t.lastName.equals(lastName);  
    }


方法二:

在Object的equals方法中调用重载的equals方法,代码如下:



public boolean equals(Test t){  
return t.firstName.equals(firstName) && t.lastName.equals(lastName);  
    }  
      
public boolean equals(Object o){  
return o instanceof Test && equals((Test)o);  
    }


在覆盖父类方法时要特别小心,很有可能变成重载而引起错误,建议在覆盖的方法上面使用@Override注解。

4.取绝对值的注意事项:

问题:

下面的程序统计整个int类型数值对3取余之后余数分别为0,1,2的个数,代码如下:


public class Test{  
      
public static void main(String[] args){  
final int MODULUS = 3;  
int[] histogram = new int[MODULUS];  
int i = Integer.MIN_VALUE;  
do{  
            histogram[Math.abs(i) % MODULUS]++;  
while(i++ != Integer.MAX_VALUE);  
for(int j = 0; j < MODULUS; j++){  
            System.out.println(histogram[j]);  
        }  
    }  
}


整个2的32次方个int数值对3取余运算之后,分别统计余数为0,1和2的个数的总和应该为2的32次方,这三个数应该大致相等,由于2的32次方不能被3整除,因此肯定其中两个数相等,通过运算可以得知2的偶次幂对3取余值为1,2的奇次幂对3取余值为2,因此最大数2的32次方对3取余应该为1,所以程序的最终运行结果应该为1431655765,1431655766和1431655765.

很遗憾,真实程序一运行就报java.lang.ArrayIndexOutOfBoundsException异常。

原因:

从上述数字越界异常,我们知道肯定是数组的索引出了问题,我们知道负数对正数取余时,值肯定为负数,我们上面的程序对int数值取绝对值了,为什么还会出现负数。

问题恰恰出在Math.abs方法上,从Math.abs的文档了解到:该方法几乎总是返回它的参数的绝对值,但是在有一种情况下,它做不到这一点,如果其参数等于Integer.MIN_VALUE,那么产生的结果与该参数相同,它是一个负数。

由于计算机中数值运算采用二进制补码,int类型数据取值范围为负的2的32次方到正的2的32次方减一,0的相反数是其本身,由于负数比正数多一个,因此总有一个负数的相反数没有对应的正数,这个数就是Integer.MIN_VALUE,而它模3取余的值是-1,因此造成数组下标越界。

结论:

为了修改程序的错误,我们需要在取余操作时将结果为负的值转换为正数,代码如下:


public class Test{  
private static int mod(int i, int modulus){  
int result = i % modulus;  
return result < 0 ? result + modulus : result;  
    }  
      
public static void main(String[] args){  
final int MODULUS = 3;  
int[] histogram = new int[MODULUS];  
int i = Integer.MIN_VALUE;  
do{  
            histogram[mod(i, MODULUS)]++;  
while(i++ != Integer.MAX_VALUE);  
for(int j = 0; j < MODULUS; j++){  
            System.out.println(histogram[j]);  
        }  
    }  
}


Math.abs方法不能保证一定会返回非负的结果,如果它的参数是Short,int,long等数据类型的MIN_VALUE时,它的结果就是其本身。

5.比较器的注意事项:

问题:

下面的程序对随机产生的100个伪随机数进行排序,然后打印出排序情况,代码如下:



import java.util.Arrays;  
import java.util.Comparator;  
import java.util.Random;  
  
  
public class Test{  
      
public static void main(String[] args){  
new Random();  
new Integer[100];  
for(int i = 0; i < array.length; i++){  
            array[i] = rand.nextInt();  
        }  
new Comparator<Integer>(){  
public int compare(Integer intOne, Integer intTwo){  
return intTwo - intOne;  
            }  
        };  
        Arrays.sort(array, comparator);  
        System.out.println(order(array));  
    }  
      
enum Order{ ASCENDING, DESCENDING, CONSTANT, UNORDERED};  
      
static Order order(Integer[] arr){  
boolean ascending = false;  
boolean descending = false;  
for(int i = 1; i < arr.length; i++){  
1];  
1];  
        }  
if(ascending && !descending){  
return Order.ASCENDING;  
        }  
if(!ascending && descending){  
return Order.DESCENDING;  
        }  
if(!ascending){  
return Order.CONSTANT;  
        }  
return Order.UNORDERED;  
    }  
}

 


上述程序中比较器的compare方法将传入的第二个参数减去第一个参数的值来确定排序方法返回值,如果为正值表示第二个参数比第一个参数大,如果为0表示两个参数相等,如果为负数表示第二个参数比第一个参数小。该比较器的行为正好与compare方法通常的做法想法,因此,该比较器应该实施的是降序排列。

在对数组排序之后,main方法将该数组传递给了静态方法order用于打印按何种方式进行排序,如果数组中相邻两个元素中后面的元素比前面大时,返回ASCENDING;如果数组中相邻两个元素中后面元素比前面的小,则返回DESCENDING;如果数组中相邻两个元素相等,则返回CONSTANT;否则就返回UNORDERED。

由于上述代码的比较器使用了降序排列,同时由于100个伪随机数相等的概率非常小,因此上述程序应该打印的是DESCENDING,但是程序真实运行结果总是UNORDERED。

原因:

上述程序之所以出现令人疑惑不解的UNORDERED结果,是因为比较器的compare方法写的有问题。

JDK中比较器Comparator所实现的排序关系必须是可传递的,即若(compare(x, y) > 0) && (compare(y, z) > 0),则必定compare(x, z) > 0.

而对于上述compare方法中两个数相减的比较结果,我们试想一下,如果第一个参数很大的正数(接近Integer.MAX_VALUE),第二个参数是个很小的负数(接近Integer.MIN_VALUE),正数减负数运算变成两个很大的正数相加,如计算结果超出Integer.MAX_VALUE,则数据溢出后变为负数。

对照上面的传递性约定,x取很大的正数,y取0,z取很大的负数,则会违反传递性。

因此使用上述比较器的compare方法,有很大可能性返回错误的值,用这样一个错误的排序方法来打印排序方式就得到了令人匪夷所思的UNORDERED。

结论:

解决上述程序问题方法很简单,只需要修改比较器即可,有如下两种方式:

方法一:

使用比较运算符代替相减,代码如下:



Comparator<Integer> comparator = new Comparator<Integer>(){  
public int compare(Integer intOne, Integer intTwo){  
return (intTwo < intOne ? -1 : (intTwo == intOne ? 0 : 1));  
            }  
        };


方法二:

使用Collection类提供的顺序比较器,代码如下:



Arrays.sort(array, Collections.reverseOrder());


千万不要使用基于减法的比较器,除非能够保证比较的数值间的差永远不会数值溢出。

标签:java,firstName,int,lastName,equals,谜题,Test,解惑,public
From: https://blog.51cto.com/u_809530/8275496

相关文章

  • Java-Script 编程
    Java-Script编程目录Java-Script编程一.Js概念1.1简介1.2语法结构二.变量使用2.1定义变量2.2定义常量三.数据类型3.1数值类型(number)3.2字符类型(string)3.3.字符类型常用方法3.4布尔值(boolean)3.5null与undefined3.6数组3.7数组常用的方法3.8运算符四.流程......
  • java ip地址转换成int
    数据库存放,提高数据库使用性能我们将字符串IP转换成int类型保存!java代码如下://将127.0.0.1形式的IP地址转换成十进制整数,这里没有进行任何错误处理publicstaticlongipToLong(StringstrIp){long[]ip=newlong[4];//先找到IP地址字符串中.的......
  • Java类加载机制
    类加载机制将class文件中的二进制数据读取到内存中,并对其进行校验,解析和初始化,将类型数据存放在方法区,实例对象存放在堆,作为方法区该类的数据访问接口。这就是类加载。加载通过全限定名获取二进制字节流将字节流代表的静态存储结构转化为方法区的运行时数据结构在堆中生成......
  • 【深入理解Java虚拟机】内存分配策略
    本文内容来源于《深入理解Java虚拟机》一书,非常推荐大家去看一下这本书。本系列其他文章:【深入理解Java虚拟机】Java内存区域模型、对象创建过程、常见OOM【深入理解Java虚拟机】垃圾回收机制垃圾收集器与内存分配策略Java技术体系中所提倡的自动内存管理最终可以归结为自动......
  • 【JAVA】智慧工地信息管理系统源码 智慧大屏、手机APP、SaaS模式
    一、智慧工地可以通过安全八要素来提升安全保障,具体措施包括:  1.安全管理制度:建立科学完善的安全管理制度,包括安全标准规范、安全生产手册等,明确各项安全管理职责和要求。  2.安全培训教育:对工地人员进行安全培训和教育,提高他们的安全意识和安全素质,使其掌握必要的安全知......
  • Java实现截图和录屏
    一、截图,Javax提供的能力。importjavax.imageio.ImageIO;importjava.awt.*;importjava.awt.image.BufferedImage;importjava.io.File;publicclassDemo1{publicstaticvoidmain(String[]args)throwsException{//创建一个Robot对象......
  • Java获取Windows或Linux下的IP地址
    Java获取Linux或Windows下的IP地址,详情如下importlombok.extern.slf4j.Slf4j;importjava.net.InetAddress;importjava.net.NetworkInterface;importjava.net.SocketException;importjava.net.UnknownHostException;importjava.util.Enumeration;@Slf4jpubli......
  • Java Fastjson反序列化漏洞研究
    一、Fastjson简介Fastjson是阿里巴巴的一个开源项目,在GitHub上开源,使用Apache2.0协议。它是一个支持JavaObject和JSON字符串互相转换的Java库。Fastjson最大的特点在于它的快速,它超越了JackJson、Gson等库。据官方发布的说明,Fastjson从2011年fastjson发布1.1.x版本之后,其性能......
  • elasticsearch在Java中查询指定列的方法
     背景ES在查询时如果数量太多,而每行记录包含的字段很多,那就会导致超出ES的查询上线,默认是100MB,但是很多场景下我们只需要返回特定的字段即可,那么如何操作呢。主要代码@AutowiredprivateRestHighLevelClientclient;publicList<Map<String,Object>>search(Stringin......
  • java.lang.NoSuchMethodError错误解决
    一、错误原因java.lang.NoSuchMethodError错误可能的原因:1、有这个类,该类没有这个方法2、类冲突、Jar包冲突、Jar包版本冲突3、有这个类(A),类中也有方法,但在B类中引用了A类,并调用A类的方法,后面修改A类,把该A类的方法返回值类型改变(如将方法返回类型由void类型改成String类型),只部署A类,......