1、Java的char是两个字节,是怎么存Utf-8的字符的?
是否熟悉Java char和字符串(初级)
- char是2个字节,utf-8是1~3个字节。
- 字符集(字符集不是编码):ASCII码与Unicode码。
- 字符 -> 0xd83dde00(码点)。
是否了解字符的映射和存储细节(中级)
人类认知:字符 => 字符集:0x4e2d(char) => 计算机存储(byte):01001110:4e、00101101:2d
编码:UTF-16
“中”.getBytes("utf-6"); -> fe ff 4e 2d:4个字节,其中前面的fe ff只是字节序标志。
是否能触类旁通,横向对比其他语言(高级)
Python2的字符串:
- byteString = "中"
- unicodeString = u"中"
令人迷惑的字符串长度
emoij = u"表情"
print(len(emoji)
Java与python 3.2及以下:2字节 python >= 3.3:1字节
注意:Java 9对latin字符的存储空间做了优化,但字符串长度还是!= 字符数。
总结
- Java char不存UTF-8的字节,而是UTF-16。
- Unicode通用字符集占两个字节,例如“中”。
- Unicode扩展字符集需要用一对char来表示,例如“表情”。
- Unicode是字符集,不是编码,作用类似于ASCII码。
- Java String的length不是字符数。
2、Java String可以有多长?
是否对字符串编解码有深入了解(中级)
- 分配到栈:
String longString = "aaa...aaa";
- 分配到堆:
byte[] bytes = loadFromFile(new File("superLongText.txt"); String superLongString = new String(bytes);
是否对字符串在内存当中的存储形式有深入了解(高级)
是否对Java虚拟机字节码有足够的了解(高级)
- 源文件:*.java
String longString = "aaa...aaa"; 字节数 <= 65535
- 字节码:*.class
CONSTANT_Utf8_info { u1 tag; u2 length; (0~65535) u1 bytes[length]; 最多65535个字节 }
javac的编译器有问题,< 65535应该改为< = 65535。
Java String 栈分配
- 受字节码限制,字符串最终的MUTF-8字节数不超过65535。
- Latin字符,受Javac代码限制,最多65534个。
- 非Latin字符最终对应字节个数差异较大,最多字节个数是65535。
- 如果运行时方法区设置较小,也会受到方法区大小的限制。
是否对Java虚拟机指令有一定的认识(高级)
new String(bytes)内部是采用了一个字符数组,其对应的虚拟机指令是newarray [int] ,数组理论最大个数为Integer.MAX_VALUE,有些虚拟机需要一些头部信息,所以MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8。
Java String 堆分配
- 受虚拟机指令限制,字符数理论上限为Integer.MAX_VALUE。
- 受虚拟机实现限制,实际上限可能会小于Integer.MAX_VALUE。
- 如果堆内存较小,也会受到堆内存的限制。
总结
Java String字面量形式
- 字节码中CONSTANT_Utf8_info的限制
- Javac源码逻辑的限制
- 方法区大小的限制
Java String运行时创建在堆上的形式
- Java虚拟机指令newarray的限制
- Java虚拟机堆内存大小的限制
3、Java的匿名内部类有哪些限制?
考察匿名内部类的概念和用法(初级)
- 匿名内部类的名字:没有人类认知意义上的名字
- 只能继承一个父类或实现一个接口
- 包名.OuterClass\(1,表示定位的第一个匿名内部类。外部类加\)N,N是匿名内部类的顺序。
考察语言规范以及语言的横向对比等(中级)
- 匿名内部类的继承结构:Java中的匿名内部类不可以继承,只有内部类才可以有实现继承、实现接口的特性。而Kotlin是的匿名内部类是支持继承的,如
val runnableFoo = object: Foo(),Runnable { override fun run() { } }
作为考察内存泄漏的切入点(高级)
匿名内部类的构造方法(深入源码字节码探索语言本质的能力):
- 匿名内部类会默认持有外部类的引用,可能会导致内存泄漏。
- 由编译器生成的。
其参数列表包括
- 外部对象(定义在非静态域内)
- 父类的外部对象(父类非静态)
- 父类的构造方法参数(父类有构造方法且参数列表不为空)
- 外部捕获的变量(方法体内有引用外部final变量)
Lambda转换(SAM类型,仅支持单一接口类型):如果CallBack是一个interface,不是抽象类,则可以转换为Lambda表达式。
CallBack callBack = () -> {
...
};
总结
- 没有人类认知意义上的名字。
- 只能继承一个父类或实现一个接口。
- 父类是非静态的类型,则需父类外部实例来初始化。
- 如果定义在非静态作用域内,会引用外部类实例。
- 只能捕获外部作用域内的final变量。
- 创建时只有单一方法的接口可以用Lambda转换。
技巧点拨
关注语言版本的变化:
- 体现对技术的热情
- 体现好学的品质
- 显得专业
4、Java中对异常是如何进行分类的?
异常整体分类:
Java异常结构中定义有Throwable类。 Exception和Error为其子类。
Error是程序无法处理的错误,比如OutOfMemoryError、StackOverflowError。这些异常发生时, Java虚拟机(JVM)一般会选择线程终止。
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常,程序中应当尽可能去处理这些异常。
运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等, 这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的, 程序应该从逻辑角度尽可能避免这类异常的发生。
异常处理的两个基本原则:
- 尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常。
- 不要生吞异常。
NoClassDefFoundError 和 ClassNotFoundException 有什么区别?
ClassNotFoundException的产生原因主要是: Java支持使用反射方式在运行时动态加载类,例如使用Class.forName方法来动态地加载类时,可以将类名作为参数传递给上述方法从而将指定类加载到JVM内存中,如果这个类在类路径中没有被找到,那么此时就会在运行时抛出ClassNotFoundException异常。 解决该问题需要确保所需的类连同它依赖的包存在于类路径中,常见问题在于类名书写错误。 另外还有一个导致ClassNotFoundException的原因就是:当一个类已经某个类加载器加载到内存中了,此时另一个类加载器又尝试着动态地从同一个包中加载这个类。通过控制动态类加载过程,可以避免上述情况发生。
NoClassDefFoundError产生的原因在于: 如果JVM或者ClassLoader实例尝试加载(可以通过正常的方法调用,也可能是使用new来创建新的对象)类的时候却找不到类的定义。要查找的类在编译的时候是存在的,运行的时候却找不到了。这个时候就会导致NoClassDefFoundError. 造成该问题的原因可能是打包过程漏掉了部分类,或者jar包出现损坏或者篡改。解决这个问题的办法是查找那些在开发期间存在于类路径下但在运行期间却不在类路径下的类。
5、String 为什么要设计成不可变的?
String是不可变的(修改String时,不会在原有的内存地址修改,而是重新指向一个新对象),String用final修饰,不可继承,String本质上是个final的char[]数组,所以char[]数组的内存地址不会被修改,而且String 也没有对外暴露修改char[]数组的方法。不可变性可以保证线程安全以及字符串串常量池的实现。
6、Java里的幂等性了解吗?
幂等性原本是数学上的一个概念,即:f(x) = f(f(x)),对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的。
幂等性最为常见的应用就是电商的客户付款,试想一下如果你在付款的时候因为网络等各种问题失败了,然后去重复的付了一次,是一种多么糟糕的体验。幂等性就是为了解决这样的问题。
实现幂等性可以使用Token机制。
核心思想是为每一次操作生成一个唯一性的凭证,也就是token。一个token在操作的每一个阶段只有一次执行权,一旦执行成功则保存执行结果。对重复的请求,返回同一个结果。
例如:电商平台上的订单id就是最适合的token。当用户下单时,会经历多个环节,比如生成订单,减库存,减优惠券等等。每一个环节执行时都先检测一下该订单id是否已经执行过这一步骤,对未执行的请求,执行操作并缓存结果,而对已经执行过的id,则直接返回之前的执行结果,不做任何操 作。这样可以在最大程度上避免操作的重复执行问题,缓存起来的执行结果也能用于事务的控制等。
7、为什么Java里的匿名内部类只能访问final修饰的外部变量?
- 匿名内部类用法:
public class TryUsingAnonymousClass { public void useMyInterface() { final Integer number = 123; System.out.println(number); MyInterface myInterface = new MyInterface() { @Override public void doSomething() { System.out.println(number); } }; myInterface.doSomething(); System.out.println(number); } }
- 编译后的结果
class TryUsingAnonymousClass$1 implements MyInterface { private final TryUsingAnonymousClass this$0; private final Integer paramInteger; TryUsingAnonymousClass$1(TryUsingAnonymousClass this$0, Integer paramInteger) { this.this$0 = this$0; this.paramInteger = paramInteger; } public void doSomething() { System.out.println(this.paramInteger); } }
因为匿名内部类最终会编译成一个单独的类,而被该类使用的变量会以构造函数参数的形式传递给该类,例如:Integer paramInteger,如果变量不定义成final的,paramInteger在匿名内部类被可以被修改,进而造成和外部的paramInteger不一致的问题,为了避免这种不一致的情况,因次Java规定匿名内部类只能访问final修饰的外部变量。
8、讲一下Java的编码方式?
为什么需要编码?
计算机存储信息的最小单元是一个字节即8bit,所以能示的范围是0~255,这个范围无法保存所有的字符,所以要一个新的数据结构char来表示这些字符,从char到byte需要编码。
常见的编码方式有以下几种:
- ASCII:总共有 128 个,用一个字节的低 7 位表示,031 是控制字符如换行回车删除等;32126 是打印字符,可以通过键盘输入并且能够显示出来。
- GBK:码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。
- UTF-16:UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。
- UTF-8:统一采用两个字节表示一个字符,虽然在表示上非常简单方便,但是也有其缺点,有很大一部分字符用一个字节就可以表示的现在要两个字节表示,存储空间放大了一倍,在现在的网络带宽还非常有限的今天,这样会增大网络传输的流量,而且也没必要。而 UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成。
Java中需要编码的地方一般都在字符到字节的转换上,这个一般包括磁盘IO和网络IO。
Reader 类是 Java 的 I/O 中读字符的父类,而InputStream 类是读字节的父类,InputStreamReader类就是关联字节到字符的桥梁,它负责在 I/O 过程中处理读取字节到字符的转换,而具体字节到字符解码实现由 StreamDecoder 去实现,在 StreamDecoder 解码过程中必须由用户指定 Charset 编码格式。
9、String,StringBuffer,StringBuilder有哪些不同?
三者在执行速度方面的比较:StringBuilder > StringBuffer > String
- String每次变化一个值就会开辟一个新的内存空间
- StringBuilder:线程非安全的
- StringBuffer:线程安全的
对于三者使用的总结:
- 如果要操作少量的数据用 String。
- 单线程操作字符串缓冲区下操作大量数据用 StringBuilder。
- 多线程操作字符串缓冲区下操作大量数据用 StringBuffer。
String 是 Java 语言非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑。它是典型的 Immutable 类,被声明成为 final class,所有属性也都是 final 的。也由于它的不可变性,类似拼接、裁剪字符串等动作,都会产生新的 String 对象。由于字符串操作的普遍性,所以相关操作的效率往往对应用性能有明显影响。
StringBuffer 是为解决上面提到拼接产生太多中间对象的问题而提供的一个类,我们可以用 append 或者 add 方法,把字符串添加到已有序列的末尾或者指定位置。StringBuffer 本质是一个线程安全的可修改字符序列,它保证了线程安全,也随之带来了额外的性能开销,所以除非有线程安全的需要,不然还是推荐使用它的后继者,也就是 StringBuilder。
StringBuilder 是 Java 1.5 中新增的,在能力上和 StringBuffer 没有本质区别,但是它去掉了线程安全的部分,有效减小了开销,是绝大部分情况下进行字符串拼接的首选。
10、什么是内部类?内部类的作用。
- 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。
- 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
- 创建内部类对象并不依赖于外围类对象的创建。
- 内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
- 内部类提供了更好的封装,除了该外围类,其他类都不能访问。。
11、抽象类和接口区别?
- 共同点
- 是上层的抽象层。
- 都不能被实例化。
- 都能包含抽象的方法,这些抽象的方法用于描述类具备的功能,但是不提供具体的实现。
- 区别:
- 在抽象类中可以写非抽象的方法,从而避免在子类中重复书写他们,这样可以提高代码的复用性,这是抽象类的优势,接口中只能有抽象的方法。
- 多继承:一个类只能继承一个直接父类,这个父类可以是具体的类也可是抽象类,但是一个类可以实现多个接口。
- 抽象类可以有默认的方法实现,接口根本不存在方法的实现。
- 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明方法的实现。子类使用关键字implements来实现接口。它需要提供接口中所有声明方法的实现。
- 构造器:抽象类可以有构造器,接口不能有构造器。
- 和普通Java类的区别:除了你不能实例化抽象类之外,抽象类和普通Java类没有任何区别,接口是完全不同的类型。
- 访问修饰符:抽象方法可以有public、protected和default修饰符,接口方法默认修饰符是public。你不可以使用其它修饰符。
- main方法:抽象方法可以有main方法并且我们可以运行它接口没有main方法,因此我们不能运行它。
- 速度:抽象类比接口速度要快,接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
- 添加新方法:如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类。
12、接口的意义?
规范、扩展、回调。
13、父类的静态方法能否被子类重写?
不能。子类继承父类后,用相同的静态方法和非静态方法,这时非静态方法覆盖父类中的方法(即方法重写),父类的该静态方法被隐藏(如果对象是父类则调用该隐藏的方法),另外子类可继承父类的静态与非静态方法,至于方法重载我觉得它其中一要素就是在同一类中,不能说父类中的什么方法与子类里的什么方法是方法重载的体现。
14、抽象类的意义?
为其子类提供一个公共的类型,封装子类中的重复内容,定义抽象方法,子类虽然有不同的实现 但是定义是一致的。
15、静态内部类、非静态内部类的理解?
静态内部类:只是为了降低包的深度,方便类的使用,静态内部类适用于包含在类当中,但又不依赖与外在的类,不用使用外在类的非静态属性和方法,只是为了方便管理类结构而定义。在创建静态内部类的时候,不需要外部类对象的引用。
非静态内部类:持有外部类的引用,可以自由使用外部类的所有变量和方法。
16、为什么复写equals方法的同时需要复写hashcode方法,前者相同后者是否相同,反过来呢?为什么?
要考虑到类似HashMap、HashTable、HashSet的这种散列的数据类型的运用,当我们重写equals时,是为了用自身的方式去判断两个自定义对象是否相等,然而如果此时刚好需要我们用自定义的对象去充当hashmap的键值使用时,就会出现我们认为的同一对象,却因为hash值不同而导致hashmap中存了两个对象,从而才需要进行hashcode方法的覆盖。
17、equals 和 hashcode 的关系?
hashcode和equals的约定关系如下:
- 如果两个对象相等,那么他们一定有相同的哈希值(hashcode)。
- 如果两个对象的哈希值相等,那么这两个对象有可能相等也有可能不相等。(需要再通过equals来判断)
18、Java为什么跨平台?
因为Java程序编译之后的代码不是能被硬件系统直接运行的代码,而是一种“中间码”——字节码。然后不同的硬件平台上安装有不同的Java虚拟机(JVM),由JVM来把字节码再“翻译”成所对应的硬件平台能够执行的代码。因此对于Java编程者来说,不需要考虑硬件平台是什么。所以Java可以跨平台。
19、浮点数的精准计算
BigDecimal类进行商业计算,Float和Double只能用来做科学计算或者是工程计算。
20、final,finally,finalize的区别?
final 可以用来修饰类、方法、变量,分别有不同的意义,final 修饰的 class 代表不可以继承扩展,final 的变量是不可以修改的,而 final 的方法也是不可以重写的(override)。
finally 则是 Java 保证重点代码一定要被执行的一种机制。我们可以使用 try-finally 或者 try-catch-finally 来进行类似关闭 JDBC 连接、保证 unlock 锁等动作。
finalize 是基础类 java.lang.Object 的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize 机制现在已经不推荐使用,并且在 JDK 9 开始被标记为 deprecated。Java 平台目前在逐步使用 java.lang.ref.Cleaner 来替换掉原有的 finalize 实现。Cleaner 的实现利用了幻象引用(PhantomReference),这是一种常见的所谓 post-mortem 清理机制。利用幻象引用和引用队列,我们可以保证对象被彻底销毁前做一些类似资源回收的工作,比如关闭文件描述符(操作系统有限的资源),它比 finalize 更加轻量、更加可靠。
21、静态内部类的设计意图
静态内部类与非静态内部类之间存在一个最大的区别:非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。
没有这个引用就意味着:它的创建是不需要依赖于外围类的。 它不能使用任何外围类的非static成员变量和方法。
22、Java中对象的生命周期
在Java中,对象的生命周期包括以下几个阶段:
- 创建阶段(Created): JVM 加载类的class文件 此时所有的static变量和static代码块将被执行 加载完成后,对局部变量进行赋值(先父后子的顺序) 再执行new方法 调用构造函数 一旦对象被创建,并被分派给某些变量赋值,这个对象的状态就切换到了应用阶段。
- 应用阶段(In Use): 对象至少被一个强引用持有着。
- 不可见阶段(Invisible): 当一个对象处于不可见阶段时,说明程序本身不再持有该对象的任何强引用,虽然该这些引用仍然是存在着的。 简单说就是程序的执行已经超出了该对象的作用域了。
- 不可达阶段(Unreachable): 对象处于不可达阶段是指该对象不再被任何强引用所持有。 与“不可见阶段”相比,“不可见阶段”是指程序不再持有该对象的任何强引用,这种情况下,该对象仍可能被JVM等系统下的某些已装载的静态变量或线程或JNI等强引用持有着,这些特殊的强引用被称为”GC root”。存在着这些GC root会导致对象的内存泄露情况,无法被回收。
- 收集阶段(Collected): 当垃圾回收器发现该对象已经处于“不可达阶段”并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,则对象进入了“收集阶段”。如果该对象已经重写了finalize()方法,则会去执行该方法的终端操作。
- 终结阶段(Finalized): 当对象执行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。
- 对象空间重分配阶段(De-allocated): 垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了,称之为“对象空间重新分配阶段。
23、静态属性和静态方法是否可以被继承?是否可以被重写?以及原因?
- 结论:
- Java中静态属性和静态方法可以被继承,但是不可以被重写而是被隐藏。
- 原因:
- 静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成,不需要继承机制即可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在"隐藏"的这种情况。
- 多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:"重写"后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。
- 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态。
24、object类的equal 和hashcode 方法重写,为什么?
在Java API文档中关于hashCode方法有以下几点规定(原文来自Java深入解析一书):
- 在Java应用程序执行期间,如果在equals方法比较中所用的信息没有被修改,那么在同一个对象上多次调用hashCode方法时必须一致地返回相同的整数。如果多次执行同一个应用时,不要求该整数必须相同。
- 如果两个对象通过调用equals方法是相等的,那么这两个对象调用hashCode方法必须返回相同的整数。
- 如果两个对象通过调用equals方法是不相等的,不要求这两个对象调用hashCode方法必须返回不同的整数。但是程序员应该意识到对不同的对象产生不同的hash值可以提供哈希表的性能。
25、Java中==和equals和hashCode的区别?
默认情况下也就是从超类Object继承而来的equals方法与‘==’是完全等价的,比较的都是对象的内存地址,但我们可以重写equals方法,使其按照我们的需求的方式进行比较,如String类重写了equals方法,使其比较的是字符的序列,而不再是内存地址。在Java的集合中,判断两个对象是否相等的规则是:
- 判断两个对象的hashCode是否相等。
- 判断两个对象用equals运算是否相等。
26、Java的四种引用及使用场景?
- 强引用(FinalReference):在内存不足时不会被回收。平常用的最多的对象,如新创建的对象。
- 软引用(SoftReference):在内存不足时会被回收。用于实现内存敏感的高速缓存。
- 弱引用(WeakReferenc):只要GC回收器发现了它,就会将之回收。用于Map数据结构中,引用占用内存空间较大的对象。
- 虚引用(PhantomReference):在回收之前,会被放入ReferenceQueue,JVM不会自动将该referent字段值设置成null。其它引用被JVM回收之后才会被放入ReferenceQueue中。用于实现一个对象被回收之前做一些清理工作。
27、类的加载过程,Person person = new Person();为例进行说明。
- 因为new用到了Person.class,所以会先找到Person.class文件,并加载到内存中;
- 执行该类中的static代码块,如果有的话,给Person.class类进行初始化;
- 在堆内存中开辟空间分配内存地址;
- 在堆内存中建立对象的特有属性,并进行默认初始化;
- 对属性进行显示初始化;
- 对对象进行构造代码块初始化;
- 对对象进行与之对应的构造函数进行初始化;
- 将内存地址付给栈内存中的p变量。
28、Java常量池
Interger中的128(-128~127)
- 当数值范围为-128~127时:如果两个new出来的Integer对象,即使值相同,通过
==
比较结果为false,但两个对直接赋值,则通过==
比较结果为true
,这一点与String非常相似。 - 当数值不在-128~127时,无论通过哪种方式,即使两对象的值相等,通过
==
比较,其结果为false; - 当一个Integer对象直接与一个int基本数据类型通过
==
比较,其结果与第一点相同; - Integer对象的hash值为数值本身;
为什么是-128-127?
在Integer类中有一个静态内部类IntegerCache,在IntegrCache类中有一个Integer数组,用以缓存当前数值范围为-128~127时的Integer对象。
29、在重写equals方法时,需要遵循哪些约定,具体介绍一下?
重写equals方法时需要遵循通用约定:自反性、对称性、传递性、一致性、非空性
- 自反性: 对于任何非null的引用值x,x.equals(x)必须返回true。---这一点基本上不会有啥问题
- 对称性: 对于任何非null的引用值x和y,当且仅当x.equals(y)为true时,y.equals(x)也为true。
- 传递性: 对于任何非null的引用值x、y、z。如果
x.equals(y)==true,y.equals(z)==true
,那么x.equals(z)==true
。 - 一致性: 对于任何非null的引用值x和y,只要equals的比较操作在对象所用的信息没有被修改,那么多次调用x.equals(y)就会一致性地返回true,或者一致性的返回false。
- 非空性: 所有比较的对象都不能为空。