Java面试题收集

面试题目

 

一:什么是对象

  回答思路: 这个问题的主要考察的是你对面向对象语言的理解,回答时除了回答面向对象的定义外,更重要的是要学会延伸说明和类相关的一些特性。

  定义: 在系统中,对象是用于客观描述一个事物的一个实体,而类则是这类实体的抽象,它是构成系统的一个基本单位。一个对象由一组描述对象的属性和一组描述对象的动作组成。

  类的实例化可以创建对象,每个对象都有它的生命周期,对象的生命周期可以简单的归为:生成、使用、销毁三个阶段。

  在JAVA语言中,一个类如果不存在引用时,那它就是一个无用的对象,JAVA的垃圾回收器会自动扫描JVM虚拟机,对这些没有被引用的垃圾对象进行回收。

  开发者也可以显示调用System.gc()方法告知垃圾回收器进行回收垃圾对象,但是并不意味着在调用完这个方法后垃圾回收器就会垃圾去回收,具体的回收时间是由垃圾收回器自己确定,调用这个方法只是给垃圾回收器发送一个“信号”,告诉它现在内存不够或者可以去存在垃圾对象需要它回收。

追问问题1.1: 面向对象有什么特征

 

  面向对象的三大特征:封装、继承、多态

  封装: 隐藏对象的属性和实现细节、只提供访问的公共方法,实现的方式是通过访问修饰符来限定。

图片

  继承: 继承表示的是一个类拥有另一个类的相关信息,通过关键字extends实现,被继承的类叫父类(基类、超类),得到继承信息的类也叫子类或派生类,JAVA中类只能单继承,但是可以实现多个接口。

图片

 

  多态: 同一个行为可以有不同的表现形式的能力。具体来说就是一个类型可以有多种表现的形式,如:动物可以是狗、也可以是猫,具体如图所示:

图片

 

追问问题1.2: 多态有什么优点

  对类型解耦,可以使用父类或者接口接收子类对象

  可替换性,如实例一个猫对象,可以用动物接收:Animal cat = new Cat()

  可拓展性,多态是对象的多种表现形式的体现,很易于拓展,如动物除了猫狗外,还可以是鸡鸭鱼等

  更灵活,可以随意拓展新的表现形式而不影响其他的形式

 

追问问题1.3: 多态存在的必要条件

  1、继承父类或者实现接口

  2、重写

  3、使用父类/接口接收子类对象

图片
在这里插入图片描述
class Animal2 {     void draw() {} } class Dog extends Animal2 {     @Override     void draw() {         System.out.println("汪汪");     } } class Cat2 extends Animal2 {     @Override     void draw() {         System.out.println("喵喵");     } } class Fish extends Animal2 {     @Override     void draw() {         System.out.println("泡泡");     }

 

追问问题1.4: JAVA作为面向对象,它有什么特点或者好处

  1、易于理解,有更好的可读性

  2、平台无关性,一次编译,处处运行

  3、提供了许多类库,方便开发者的工作,减少开发时间

  4、提供了对web的支持

  5、具有较好的安全性和健壮性(如垃圾回收)

  6、去除了C++中难以理解,易于混淆的特性

 

二: 多态的实现方式

  1、继承父类重写父类方法,关键字extends

 

/**  * 图形  */ class Shape {     void draw() {} } /**  * 圆  */ class Circle extends Shape {     @Override     void draw() {         System.out.println("Circle.draw()");     } } /**  * 正方形  */ class Square extends Shape {     @Override     void draw() {         System.out.println("Square.draw()");     } } /**  * 三角形  */ class Triangle extends Shape {     @Override     void draw() {         System.out.println("Triangle.draw()");     } }

  2、实现接口,重写接口方法,关键字implements

 

/**  * 图形  */ interface Shape {      void draw(); } /**  * 圆  */ class Circle implements Shape {     @Override     public void draw() {         System.out.println("Circle.draw()");     } } /**  * 正方形  */ class Square implements Shape {     @Override     public void draw() {         System.out.println("Square.draw()");     } } /**  * 三角形  */ class Triangle implements Shape {     @Override     public void draw() {         System.out.println("Triangle.draw()");     } }

  3、同一个类中方法重载

 

class Animal3{     /*         重载实现多态      */     public void call(){         System.out.println("无参数的叫声");     }     public void call(String mode){         System.out.println("带参数的叫声");     } }

 

追问问题2.1: 虚拟机是如何实现多态的

  通过动态绑定技术(dynamic binding),执行期间判断所引用对象的实际对象类型,根据实际类型调用对应的方法。

 

三: 重载(Overload)和重写(Override)的区别。重载能够根据返回值的类型区分?

  方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性,具体差别如下:

  1、重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;

  2、重写发生在子类与父类或者子类和接口之间,要求如下:

  (1)重写方法不能缩小访问权限;

  (2)参数列表必须与被重写方法相同(包括显示形式);

  (3)返回类型必须与被重写方法的相同或是其子类;

  (4)重写方法不能抛出新的异常,或者超过了父类范围的异常,但是可以抛出更少、更有限的异常,或者不抛出异常。

  3、重载对返回类型没有特殊的要求,所以无法通过返回值类型来区分重载。

  4、父类的静态方法不能被子类重写。重写只适用于实例方法,不能用于静态方法,当父类和子类有相同名称的静态方法时,如果使用父类接收子类对象,则调用静态方法时,会直接使用父类的方法而隐藏掉子类的静态方法。

 

图片
在这里插入图片描述

 

追问问题3.1: 构造器(constructor)是否可被重写(override)?

  不能,因为构造器不能被继承,因此不能被重写,但可以被重载。

 

四: 抽象类和接口有什么区别

  1、抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用(既使用抽象类或者接口接收实际的类型创建出来的对象,如Animal a =new Dog(),Animal可以是接口或者抽象类)

  2、一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。

  3、接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。

  3、抽象类中的成员可以是private、默认、protected、public的,而接口中的成员全都是public的。

  4、抽象类中可以定义成员变量,而接口中只能定义常量。

  5、有抽象方法的类必须被声明为抽象类,而抽象类不一定需要有抽象方法

 

追问问题4.1:访问修饰符public,private,protected,以及不写(默认)时的区别

图片

五:String是最基本的数据类型吗?

  不是。Java中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean,除了基本类型(primitive type)和枚举类型(enumeration type),剩下的都是引用类型(reference type)。

 

追问问题5.1: JAVA中基本数据类型和包装类型的对应关系和占用字节

 

数据类型 byte char short int long float double boolean
取值范围 -2^7 ~ 2^7-1 0~ 2^16-1 -2^15 ~ 2^15-1 -2^31 ~ 2^31-1 -2^63 ~ 2^63-1 -3.403E38 ~ 3.403E38 -1.798E308 ~ -4.9E324 false或true
字节数 1 2 2 4 8 4 8 占用的字节数跟虚拟机类型相关

  注:《Java虚拟机规范》一书中的描述:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位”。

  这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字节。

追问问题5.2: JAVA为什么要引入8中基本数据类型

  因为编码中使用到基本数据类型的场景非常多,为了减少使用时需要创建的的步骤,提升编码的编程的方便索引引入了基本数据类型。

  同时,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),如:int的包装类是Integer,且从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。

 

二: 面试题目


一:请选出下面关于重写和重载说法正确的选项

  • A: 在子类中,如果方法没有被public修饰符修饰,则该方法不能重载(Overload)

  • B: 重写(Override)只需要满足方法名和参数类型相同即可

  • C: 重写(Override)要求方法的方法名、参数(包括参数类型和参数顺序)、返回值类型都要相同

  • D: 有相同的方法名、参数名、参数类型则表示该方法被重写了

答案: C

  A选项错误: 重载方法与方法的修饰符和返回值类型无关,只跟参数列表(包括参数类型、参数顺序、参数个数)和方法名有关。

图片

  B选项错误: 重写发生在子类与父类或者子类和接口之间,要求如下(简称:两同两小一大原则):

  • 两同: 表示重写的参数列表(包括参数类型、参数顺序、参数个数)和方法名需要相同。

  • 两小: 表示方法的返回值类型要小于或等于父类的返回值类型和方法抛出的异常要小于或等于父类方法抛出的异常

  • 一大: 表示重写的方法访问修饰符要大于或等于父类方法的修饰符

  @Override注解时JDK自带的注解之一,用于重写的方法之上,如果方法不满足重写的条件,则编译不通过,在重写方法时,建议添加该注解,提高代码可读性

图片

 

  D选项错误: 说法不正确,重写的方法需要遵循"两同两小一大"原则。

二:请选出下面对应重载说法错误的选项

  • A: 重载方法的方法名必须相同

  • B: 重载方法的区别是参数列表(参数个数或者参数类型)

  • C: 重载方法的返回值必须一致

  • D: 重载方法的实现可以不一样

     

答案: C (重载只与方法的参数列表和方法名相关,与方法的返回值类型和方法修饰符类型无关)

三:请选出下面说法正确的选项

  • A: 静态方法不能被重写

  • B: 静态方法不能被private修饰

  • C: 私有方法不能被重载

  • D: 静态方法不能通过对象进行调用

答案: A

  A选项正确: 静态方法可以被继承,但是不能被重写。因为静态方法是属于类级别的,在加载到JVM时就已经确定了,但重写是表现多态的一种方式,通过父类引用指向子类,然后在运行时再确定具体的类型从而执行不同的逻辑。

 

图片

  B选项错误: 静态方法和普通方法一样可以被public、protected,默认修饰符、private修饰。

 

图片

  C选项错误: 重载是表示相同的方法名有不同的参数列表的方法,与返回值类型和访问修饰符无关。

  D选项错误: 静态方法是属于类级别的方法,可以通过类名和实例对象名调用(不推荐使用实例调用静态方法)。

四: 下面那个选项替换到"here"可以让结果输出为:son

public class DemoFather {     private String name;     public static void main(String[] args) {         DemoFather item = new DemoSon();         // here     } } class DemoSon extends DemoFather{     private String name;     public String output(){         name = "son";         return name;     } }

 

  • A: System.out.println(item.name);

  • B: System.out.println(item.output());

  • C: System.out.println(((DemoSon)item).output());

  • D: System.out.println((DemoFather)item).output());

     

答案: C (使用父类引用指向子类,想调用子类的方法,需要将父类类型强制转换成子类类型再进行调用)

五: 下面代码执行会输出什么结果?

class Parent {     public Parent() {         System.out.println("A");     } } public class Son extends Parent {     public Son() {         System.out.println("B");     }     public static void main(String[] args) {         Parent a = new Son();         a = new Parent();     }

}

答案: A B A (因为Son继承了Parent类,在加载Son类时会先去加载它的父类,所以会先执行父类的构造方法)

 

 

 面试题目

 

一: 深拷贝和浅拷贝的区别是什么?

 

  在讲解拷贝知识前,我们先来了解下JAVA中的数据类型,主要分为以下两种:

  1、基本类型: 也叫做值类型,主要是值JAVA自带的8种数据类型即:byte、char、short、int、long、float、double、boolean。

  2、引用类型: JAVA中除了基本类型,其他的称为引用类型,常见的如:对象、数组、枚举 等。

  3、在JAVA中,基本类型是存放在栈中的,而引用类型实际上是存在堆中,然后在栈中存在一个指针指向堆中的实际对象数据。


 

图片

 

  拷贝: 实际上就是复制的意思,跟平常我们使用ctrl+c命令的效果一样,但在JAVA中,它是区分为浅拷贝和深拷贝两种。

(一) 浅拷贝:

  复制出来的对象跟原来的对象有相同的值,但是如果被复制对象中包含有其他类型对象时,只会复制这个对象的引用

  如:A对象中有一个属性叫demo是B类型的对象,那么浅拷贝时,复制出来的C对象里面的demo属性和A对象都是指向同一个对象,如果修改C对象里面的demo属性,那么A对象中的demo属性也会被修改,因为它们指向的是相同的一个对象。

  对象想具有拷贝功能,需要满足以下的两个条件(注意:Object提供的clone方法默认只实现浅拷贝):

  1、实现Clonable接口

  2、重写Clonable接口中的clone方法

  浅拷贝样例图和代码:

 

图片




图片

 

public class CloneDemo {     public static void main(String[] args) throws Exception{         Demo1 demo1 = new Demo1("1",2,new Demo2("3",4));         System.out.println("原来的对象数据:" + demo1);         // 浅拷贝         Demo1 cloneDemo = (Demo1) demo1.clone();         System.out.println("拷贝出来的对象数据:" + cloneDemo);         // 修改拷贝对象的属性         cloneDemo.setAge(100);         cloneDemo.setAgeCone(100);         cloneDemo.setUserName("test");         cloneDemo.setS1(Short.valueOf("200"));         cloneDemo.getDemo2().setDemo2Age(1000);         System.out.println();         System.out.println("修改拷贝出来的对象引用类型属性:被拷贝对象的数据" + demo1);         System.out.println("修改拷贝出来的对象引用类型属性:拷贝出来的对象数据" + cloneDemo);     } } @Data class Demo1 implements Cloneable{     private String userName;     private Integer age;     private Short s1 = new Short("100");     private Integer ageCone = new Integer(900);     private Demo2 demo2;     public Demo1(String userName, Integer age, Demo2 demo2) {         this.userName = userName;         this.age = age;         this.demo2 = demo2;     }     @Override     protected Object clone() throws CloneNotSupportedException {         return super.clone();     }     @Override     public String toString() {         return "Demo1{" +                 "userName='" + userName + '\'' +                 ", age=" + age +                 ", s1=" + s1 +                 ", ageCone=" + ageCone +                 ", demo2=" + demo2 +                 '}';     } } @Data class Demo2 implements Cloneable{     private String demo2Username;     private Integer demo2Age;     public Demo2(String demo2Username, Integer demo2Age) {         this.demo2Username = demo2Username;         this.demo2Age = demo2Age;     } }

(二) 深拷贝:

  复制出来的对象拥有和原来对象相同的一套属性值,里面的属性和被复制的对象是相互独立的,修改任何一个对象都不会对另外一个对象产生影响,

  从上面可以知道Object提供的clone方法只能实现浅拷贝,如果想实现深拷贝,可以采取以来两种方法:

  1、每个引用类型内部都实现cloneable接口并重写clone方法即可。

  2、使用序列化和反序列化(前提是类需要实现序列化接口Serializable)

  注意:序列化是将对象写到流中便于网络传输或者持久化到磁盘,而反序列化则是把对象从流/磁盘中读取出来。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以我们通过对象的序列化产生克隆对象,然后通过反序列化获取这个对象。

  实现深拷贝方式一: 每个引用类型都实现Cloneable接口并重写clone方法

图片




图片

 

public class CloneDemo {     public static void main(String[] args) throws Exception{         Demo1 demo1 = new Demo1("1",2,new Demo2("3",4));         System.out.println("原来的对象数据:" + demo1);         // 深拷贝         Demo1 cloneDemo = (Demo1) demo1.clone();         System.out.println("拷贝出来的对象数据:" + cloneDemo);         // 修改拷贝对象的属性         cloneDemo.setAge(100);         cloneDemo.setAgeCone(100);         cloneDemo.setUserName("test");         cloneDemo.setS1(Short.valueOf("200"));         cloneDemo.getDemo2().setDemo2Age(1000);         System.out.println();         System.out.println("修改拷贝出来的对象引用类型属性:被拷贝对象的数据" + demo1);         System.out.println("修改拷贝出来的对象引用类型属性:拷贝出来的对象数据" + cloneDemo);     } } @Data class Demo1 implements Cloneable{     private String userName;     private Integer age;     private Short s1 = new Short("100");     private Integer ageCone = new Integer(900);     private Demo2 demo2;     public Demo1(String userName, Integer age, Demo2 demo2) {         this.userName = userName;         this.age = age;         this.demo2 = demo2;     }     @Override     public String toString() {         return "Demo1{" +                 "userName='" + userName + '\'' +                 ", age=" + age +                 ", s1=" + s1 +                 ", ageCone=" + ageCone +                 ", demo2=" + demo2 +                 '}';     }     @Override     protected Object clone() throws CloneNotSupportedException {         Demo1 demo1 = (Demo1) super.clone();         Demo2 demo2 = (Demo2) demo1.getDemo2().clone();         demo1.setDemo2(demo2);         return demo1;     } } @Data class Demo2 implements Cloneable{     private String demo2Username;     private Integer demo2Age;     @Override     protected Object clone() throws CloneNotSupportedException {         return super.clone();     }     public Demo2(String demo2Username, Integer demo2Age) {         this.demo2Username = demo2Username;         this.demo2Age = demo2Age;     } }

  实现深拷贝方式二: 使用序列化和反序列化方式达到深拷贝

图片

 

public class CloneDemo {     public static void main(String[] args) throws Exception{         Demo1 demo1 = new Demo1("1",2,new Demo2("3",4));         System.out.println("原来的对象数据:" + demo1);         // 序列化         ByteArrayOutputStream bos = new ByteArrayOutputStream();         ObjectOutputStream oos = new ObjectOutputStream(bos);         oos.writeObject(demo1);         // 反序列化         ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());         ObjectInputStream ois = new ObjectInputStream(bis);         // 深拷贝         Demo1 cloneDemo = (Demo1) ois.readObject();         System.out.println("拷贝出来的对象数据:" + cloneDemo);         // 修改拷贝对象的属性         cloneDemo.setAge(100);         cloneDemo.setAgeCone(100);         cloneDemo.setUserName("test");         cloneDemo.setS1(Short.valueOf("200"));         cloneDemo.getDemo2().setDemo2Age(1000);         System.out.println();         System.out.println("修改拷贝出来的对象引用类型属性:被拷贝对象的数据" + demo1);         System.out.println("修改拷贝出来的对象引用类型属性:拷贝出来的对象数据" + cloneDemo);     } } @Data class Demo1 implements CloneableSerializable {     private String userName;     private Integer age;     private Short s1 = new Short("100");     private Integer ageCone = new Integer(900);     private Demo2 demo2;     public Demo1(String userName, Integer age, Demo2 demo2) {         this.userName = userName;         this.age = age;         this.demo2 = demo2;     }     @Override     public String toString() {         return "Demo1{" +                 "userName='" + userName + '\'' +                 ", age=" + age +                 ", s1=" + s1 +                 ", ageCone=" + ageCone +                 ", demo2=" + demo2 +                 '}';     }     @Override     protected Object clone() throws CloneNotSupportedException {         Demo1 demo1 = (Demo1) super.clone();         Demo2 demo2 = (Demo2) demo1.getDemo2().clone();         demo1.setDemo2(demo2);         return demo1;     } } @Data class Demo2 implements Cloneable,Serializable{     private String demo2Username;     private Integer demo2Age;     @Override     protected Object clone() throws CloneNotSupportedException {         return super.clone();     }     public Demo2(String demo2Username, Integer demo2Age) {         this.demo2Username = demo2Username;         this.demo2Age = demo2Age;     } }

 

二: throw和throws的区别?

  throw关键字用于主动抛出java.lang.Throwable类的一个实例化对象,当某些业务可能存在异常,但是你并不想在此处处理这个异常,可以使用throw关键字将异常抛出。如:throw new Exception(“not need to deal″)。

  throws 的作用是作为方法声明和签名的一部分(放在方法声明处),可以接受多个异常,用逗号隔开,这个方法的调用者需要处理抛出的异常或者继续使用throws将异常网上抛出,最高可抛出到JVM进行处理。Java 中,任何未处理的受检查异常强制在 throws 子句中声明。

 

图片

 

三: 受检查异常和运行时异常与有何区别?

 

  受检查异常: 在编译阶段被强制检查的异常称为"受检查的异常",这种异常JAVA编译器要求必须处理,如IO异常

  运行时异常: 在编译阶段无法检测出来的,可能是由于开发者设计考虑不周全而引起的异常,这种异常只能在程序运行时才会发现,所以,处理这种异常要求开发者更加细致和有经验才能更好预测到。


图片

四: 列举一些工作中你常遇到的运行时异常

  • NullPointerException (空指针异常)

  • IndexOutOfBoundsException (下标越界异常)

  • IllegalArgumentException (非法参数异常)

  • ClassCastException (类转换异常)

  • ArithmeticException(算术异常)

 

五: SimpleDateFormat是线程安全的吗?如果不是,怎么解决它线程不安全的问题?

  SimpleDateFormat是DateFormat 的一个实现,而DateFormat的实现都是线程不安全的,所以SimpleDateFormat 都不是线程安全的,在多线程环境下,会存在线程安全问题。

  解决: 可以将 SimpleDateFormat 存放在 ThreadLocal 中,因为ThreadLocal是线程变量,每个线程都有一个单独的ThreadLocal ,在多线程环境下也不会出现线程安全问题。

  JDK8推荐使用DateTimeFormatter来代替SimpleDateFormat ,因为它是线程安全的。

 

 面试题目

 

一: float f = 6.6 这个代码是否有问题?

   有,在JAVA语言中,默认小数是双精度(double),而float是单精度类型,使用float接收双精度的数值,相当于向下转型(down-casting),会造成精度丢失,因此需要强制类型转换即float f = (float)6.6或者在数值后指定类型即float f = 6.6F;

 

图片

 

追问1: short s1 = 1; s1 = s1 + 1;存在问题吗,存在则具体说明,short s1 = 1; s1 += 1;存在问题吗?

 

   s1 = s1 + 1存在问题,无法通过编译。因为1是int类型在JAVA语言中占4个字节,而short类型在JAVA中占2个字节,使用short类型接收,则表明需要强制类型转换即写成:想要编译通过需要写成: s1 = (short) (s1 + 1);

 

   short s1 = 1; s1 += 1;写法没有错误,可以正常编译,"+="是一个复合运算符,JAVA编译器会对它进行特殊处理,它包含了隐式的强制类型转换,相当于s1 = (short)s1 + 1;


 

图片

 

二: &和&&的区别?

 

   1、&运算符有两种用法:

   (1)按位与: 具体的含义是参与运算的两数各对应的二进制位相与,只有对应的两个二进位均为1时,结果位才为1 ,否则为0。

   (2)逻辑与: 要求左右两边的条件都为true时,返回的结果才为true,否则为false。

 

图片


   2、&&运算符也叫短路与运算,特点如下:

   (1)、如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。只有运算符两边的条件都为真时,才会返回true。

   (2)、它的运用场景很多,合理运用可以避免空指针,如判断用户名不是null且不为空字符串时,使用username != null &&!username.equals(“”),运算符的条件顺序不能调换,否则会出现空指针。

   (3)、逻辑或运算符(|)和短路或运算符(||)的差别也是相似,短路或运算符(||)只要运算符左边的条件为真,则不运行右边的条件判断,直接返回true。

 

三: Math.round(2.5) 等于多少?Math.round(-2.5)等于多少?

 

   round方法的作用是四舍五入,Math.round(2.5)的返回值是3,Math.round(-2.5)的返回值是-2。四舍五入的原理是在参数上加0.5然后进行下取整(向下取整表示取更小的值)。

 

图片

 

四: switch(condition)中,condition的值可以是哪些类型?

 

  1、Java5以前,switch(condition)中,condition只能是byte、short、char、int(包括它们的包装类型)类型的值。

  2、Java5开始,Java中引入了枚举类型,condition也可以是enum类型的值

  3、Java7开始,condition还可以是字符串(String),但是长整型(long),浮点数(float)到目前为止还不支持。


 

图片
在这里插入图片描述

 

 

五: 当两个对象x、y的equals方法为true时,他们的hashCode方法返回的值可以是不同,这个说法正确?

 

  这个说法是错误的!当x.equals(y)时,它们的hash code也应该相同。对于equals和hashCode方法Java中有以下的规定:

  1、如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;

  2、如果两个对象的hashCode相同,它们equals并不一定相同,只能说明两个对象在散列存储结构中,存放在相同的一个位置。

  3、如果违背了上面两点的规范,那么在使用容器的时候,在Set集合中可能出现相同的对象,增加元素的效率会大大下降,频繁的哈希冲突会导致性能大大的下降。

追问1: 重写equals方法应该注意哪些事项

 

  如果重写了一个对象的equals方法,那么一定要重写这个对象的hashCode方法,目的是为了保证equals方法相同的对象拥有相同的hashCode,《Effective Java》书籍中写到重写equals需要注意以下事项:

  1、自反性: 针对非空的x,使用x.equals(x)应该返回true


  2、对称性: 针对x,y,如果x.equals(y)为true,那么y.equals(x)也应该为true


  3、传递性: 如有x,y,z,存在x.equals(y)和y.equals(z)都为True,那么x.equals(z)也应该为true


  4、一致性: 如果比较对象未发生改变,则反复调用equals方法应该返回同样的结果


  5、对于任意的非空x,x.equals(null)应该返回false

 

追问2: 重写equals方法应该大概包含哪些内容

 

  一个好的equals方法重写应该具有以下的特点:

  1、使用==操作符检查”,判断参数是否为这个对象的引用”;

  2、使用instanceof操作符检查”参数是否为正确的类型”;

  3、对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;

  4、编写完equals方法后,要判断它是否满足自反性、对称性、传递性、一致性;

  6、重写equals时总是要重写hashCode;

  7、不要将equals方法参数中的Object对象替换为其他的类型,在重写时不要忘掉@Override注解。

小结

   不积跬步,无以至千里;不积小流,无以成江海。今天播种努力的种子,总会有一天发芽!


__EOF__

本文作者夜雨闻铃
本文链接https://www.cnblogs.com/sugeek/articles/16613985.html
关于博主:编程菜鸟一只,希望每个今天胜过昨天,一步步走向技术的高峰!
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   sugeek  阅读(36)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示