关于这个问题,一般初中级面试中都会遇到,还记得我当初实习找工作的时候也遇到了这个问题,现在都还记得自己是怎么回答的:== 是基本类型比较,equals 是对象比较,不懂 hashCode,想起来简直惨不忍睹。于是找了一点小时间,研究了一番整理成文章供大家参考。
== 是什么?
在《java核心技术卷 1》中将 ==归类于关系运算符;
==常用于相同的基本数据类型之间的比较,也可用于相同类型的对象之间的比较;
- 如果 ==比较的是基本数据类型,那么比较的是两个基本数据类型的值是否相等;
- 如果 ==是比较的两个对象,那么比较的是两个对象的引用,也就是两个对象是否为同一个对象,并不是比较的对象的内容;
下面举例说明:
- public class Test {
- public static void main(String[] args){
- // 对象比较
- User userOne = new User();
- User userTwo = new User();
- System.out.println("userOne==userTwo : "+(userOne==userTwo));
- // 基本数据类型比较
- int a=1;
- int b=1;
- char c='a';
- char d='a';
- System.out.println("a==b : "+(a==b));
- System.out.println("c==d : "+(c==d));
- }
- }
实体类:
- class User {
- String userName;
- String password;
- }
运行结果:
- userOne==userTwo : false
- a==b : true
- c==d : true
对象 userOne 和 userTwo 虽然都是 User 的实例,但对应了堆内存的不同区域,因此他们的引用也不同,所以为 false;a 和 b 都是基本类型因此对比的是值,结果为 true ; c 和 d 也是基本类型 同 a 和 b.
equals 是什么鬼?
在《java核心技术卷 1》中对 Object 类的描述:Object 类是java中所有类的始祖,在java中每个类都是由Object类扩展而来;每个类都默认继承Object类,所以每一个类都有Object类中的方法;从而每一个类都有equals方法;
equals方法主要用于两个对象之间,检测一个对象是否等于另一个对象;
下边来看一看Object类中equals方法的源码:
- public boolean equals(Object obj) {
- return (this == obj);
- }
可以看出来Object类中的equals方法用的还是 ==,也就是比较的两个对象的引用是否相等,并不是根据对象中的属性来判断两个对象是否相等的;也就是说我们自己定义的类中,如果没有重写equals方法,实际上还是用的 ==来比较的两个对象,则用equals方法比较的结果与用==比较的结果是一样的;
java语言规范要求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。
- 对于任意不为null的引用值x,x.equals(null)返回false。
下面再来看一看比较典型的一个类;
- String
这是jdk中的类,而且该类重写了equals方法;
下面来看一看该类中的equals方法源码:
- public boolean equals(Object anObject) {
- if (this == anObject) {
- return true;
- }
- if (anObject instanceof String) {
- String anotherString = (String)anObject;
- int n = value.length;
- if (n == anotherString.value.length) {
- char v1[] = value;
- char v2[] = anotherString.value;
- int i = 0;
- while (n-- != 0) {
- if (v1[i] != v2[i])
- return false;
- i++;
- }
- return true;
- }
- }
- return false;
- }
从源码可以看出equals方法是进行的内容比较;
举个例子:
- public class Test {
- public static void main(String[] args){
- // 未重写equals方法的类
- User userOne = new User();
- User userTwo = new User();
- System.out.println("userOne.equals(userTwo) : "+(userOne.equals(userTwo)));
- //重写了equals方法的类
- String a="1111";
- String b="1111";
- System.out.println("a.equals(b) : "+(a.equals(b)));
- }
- }
实体类:
- class User {
- String userName;
- String password;
- }
下面是运行结果:
- userOne.equals(userTwo) : false
- a.equals(b) : true
说明 String 对比的是对象的值。
hashCode 有什么作用?
hashCode也是 Object类中的方法;下面看一下 hashCode方法的源码:
- public native int hashCode();
该方法是一个本地方法;该方法返回对象的散列码(int类型);它的实现是根据本地机器相关的;
下面是百度百科对hash的说明:
Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
散列函数能使对一个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快地定位
- Java对于eqauls方法和hashCode方法是这样规定的:
- 如果两个对象相同,那么它们的hashCode值一定要相同;
- 如果两个对象的hashCode相同,它们并不一定相同。
- equals()相等的两个对象,hashcode()一定相等;equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。
什么地方使用hashCode?
Hashcode值主要用于基于散列的集合,如HashMap、HashSet、HashTable…等等;
这些集合都使用到了hashCode,想象一下,这些集合中存有大量的数据,假如有一万条,我们向其中插入或取出一条数据,插入时如何判断插入的数据已经存在?取出时如何取出相同的数据?难道一个一个去比较?这时候,hashCode就提现出它的价值了,大大的减少了处理时间;这个有点类似于MySQL的索引;
- 举例
- public class Test {
- public static void main(String[] args){
- //未重写hashCode的类
- User userOne = new User("aa","11");
- User userTwo = new User("aa","11");
- System.out.println(userOne.hashCode());
- System.out.println(userTwo.hashCode());
- //重写hashCode的类
- String a = new String("string");
- String b = new String("string");
- System.out.println(a.hashCode());
- System.out.println(b.hashCode());
- }
- }
实体类:
- class User {
- private String userName;
- private String password;
- public User(String userName, String password) {
- this.userName = userName;
- this.password = password;
- }
- }
运行结果:
- 752848266
- 815033865
- -891985903
- -891985903
根据结果可以看出:userOne 和 userTwo 的 hashCode 值不一致;a 和 b 的 hashCode 一致。
以上便是 == 和 equals 的区别,你都 Get 到了吗?
接口的最主要的作用是达到统一访问,就是在创建对象的时候用接口创建,【接口名】 【对象名】=new 【实现接口的类】,这样你像用哪个类的对象就可以new哪个对象了,不需要改原来的代码,就和你的USB接口一样,插什么读什么,就是这个原理。就像你问的,都有个method1的方法,如果我用接口,我上面就可以one.method1();是吧?那样我new a();就是用a的方法,new b()就是用b的方法 这样不方便吗? 这个就叫统一访问,因为你实现这个接口的类的方法名相同,但是实现内容不同 我用接口来定义对象不就可以做到统一访问了吗?接口主要针对多个类实现它来说的,要是只有一个类当然可以不用接口了.你这样想,我做一个USB接口,有个read()抽象方法,然后mp3类实现,U盘类实现,移动硬盘类实现,这样我用的时候用USB a=new 【类名】;这样a.read();要是我类名里写U盘,就读U盘,写mp3就读mp3,而这个名字可以从属性文件里读,你写哪个就用哪个了,呵呵。
接口是 java 多态的一种形式
interface A { public void print();}
class B 和 class C 都实现了接口 a
class D {
public void d(A a){ a.print();}
//这个方法要求传一个A对象的引用 ,这里只要是实现了接口A的对象都可以做为参数,会调用这个对象所实现的print()方法,有点像继承重载,但是接口更灵活,可以实现多个接口,继承只能继承一个父类.
面向对象编程有三个特征,即封装、继承和多态。
向上转型可以像下面这条语句这么简单:
Shape s =new Circle();
这里,创建一个Circle对象,并把得到的引用立即赋值给S矇,这样做看似错误(将一种类型赋值给别一种类型);但实际上没有问题,因为通过继承,Circle就是一种Shape。因此,编译器认可这条语句,也就不会产生错误信息。
java 转型问题其实并不复杂,只要记住一句话:父类引用指向子类对象。
转型例子:
public class A {
public void aMthod() {
System.out.println("A method");
}
}
public class B extends A {
public String name ;
void bMethod1() { System.out.println("B method 1" + name );}
void bMethod2() { System.out.println("B method 2" + name ); }
}
public static void main(String[] args) {
A a1 = new B(); // 向上转型
a1.aMthod(); // 调用父类aMthod(),a1遗失B类方法bMethod1()、bMethod2()
B bb = (B) a1; // 正常的,因为bb指向的是B子类,调用的也是B子类的方法。
bb.name = "sun";
bb.aMthod(); // 而这样的a1父类,必须是指向过子类,才可以向下转型。
bb.bMethod1(); // sun可以打印
// 当向下转型的时候,最后的子类bb可以像子类一样使用,如果没使用之前,子类bb打印的name是Null.主要是bb用了指向子类的父类,才可以向下转型,如果父类a1指向父类,就没办法转型了。
A a2 = new A();
B b2 = (B) a2; // 向下转型,编译无错误,运行时将出错
b2.aMthod();
b2.bMethod1();
b2.bMethod2();
}
其实黑体部分的向下转型代码后的注释已经提示你将发生运行时错误。为什么前一句向下转型代码可以,而后一句代码却出错?这是因为a1指向一个子类B的对象,所以子类B的实例对象b1当然也可以指向a1。而a2是一个父类对象,子类对象b2不能指向父类对象a2。那么如何避免在执行向下转型时发生运行时ClassCastException异常?使用5.7.7节学过的instanceof就可以了。我们修改一下C类的代码:
A a2 = new A();
if (a2 instanceof B) {
B b2 = (B) a2;
b2.aMthod();
b2.bMethod1();
b2.bMethod2();
}
这样处理后,就不用担心类型转换时发生ClassCastException异常了。
a2是一个父类对象,子类对象b2不能指向父类对象a2。那么如何避免在执行向下转型时发生运行时ClassCastException异常?使用5.7.7节学过的instanceof就可以了。我们修改一下C类的代码:
A a2 = new A();
if (a2 instanceof B) {
B b2 = (B) a2;
b2.aMthod();
b2.bMethod1();
b2.bMethod2();
}
这样处理后,就不用担心类型转换时发生ClassCastException异常了。
1。父类引用指向子类对象,而子类引用不能指向父类对象。
2。把子类对象直接赋给父类引用叫upcasting向上转型,向上转型不用强制转换。
如:Father f1 = new Son();
3。把指向子类对象的父类引用赋给子类引用叫向下转型(downcasting),要强制转换。
如:f1 就是一个指向子类对象的父类引用。把f1赋给子类引用s1即 Son s1 = (Son)f1;
其中f1前面的(Son)必须加上,进行强制转换。
当在父类和子类中同时定义和赋值同名的成员变量name,并试图输出该变量的值时,父类引用输出的是父类的成员变量,也就是
父类的引用,按照道理只能调用父类的成员变量和函数,如果子类重写了父类的函数,就变成了多态,正好应验了父类引用指向子类对象的情况下多态的应用。
二.注意事项:
(1)向上转型的对象的引用调用的方法是子类的。
(2)但如果调用的方法父类中没有的话则会报错。(意思是只能调用子类中重载父类的方法)
(3)父类的引用可以指向子类的对象,但是子类的引用不能指向父类的对象。
子类的对象调用到的成员变量,是父类的成员变量(此时要想访问子类的成员变量,就要调用setter getter函数了[应该是覆写过的],情况跟多态一样)。
对于多态,可以总结它为:
一、使用父类类型的引用指向子类的对象;该引用只能调用父类中定义的方法和变量;
二、如果子类中重写了父类中的一个方法,那么在调用这个方法的时候,将会调用子类中的这个方法;(动态连接、动态调用)
三、变量不能被重写(覆盖),”重写“的概念只针对方法。
-------------------------------- 华丽的分割线 深入理解java上下转型--------------------------------------
1. 引用在栈内存中存在对象的内存地址。真正的对象(通过 new Student()创建的)存放在堆内存里。
在这块堆内存区域内,存在的是子类的属性(包括自己特有的,以及通过super()构造方法中从父类获得的)
和方法(继承父类但没有覆盖的,以及覆盖父类的方法和自己特有的),尽管引用是声明为父类的引用,
但是它指向的子类的对象,在执行方法的时候,是通过引用指向的堆内存区域内执行的。也就是到底执行父类
方法还是子类方法是由对象决定的,跟引用没有直接关系。
2.
从对象的内存角度来理解试试.
假设现在有一个父类Father,它里面的变量需要占用1M内存.有一个它的子类Son,它里面的变量需要占用0.5M内存.
现在通过代码来看看内存的分配情况:
Father f = new Father();//系统将分配1M内存.
Son s = new Son();//系统将分配1.5M内存!因为子类中有一个隐藏的引用super会指向父类实例,所以在实例化子类之前会先实例化一个父类,也就是说会先执行父类的构造函数.由于s中包含了父类的实例,所以s可以调用父类的方法.
Son s1 = s;//s1指向那1.5M的内存.
Father f1 = (Father)s;//这时f1会指向那1.5M内存中的1M内存,即是说,f1只是指向了s中实例的父类实例对象,所以f1只能调用父类的方法(存储在1M内存中),而不能调用子类的方法(存储在0.5M内存中).
Son s2 = (Son)f;//这句代码运行时会报ClassCastException.因为f中只有1M内存,而子类的引用都必须要有1.5M的内存,所以无法转换.
Son s3 = (Son)f1;//这句可以通过运行,这时s3指向那1.5M的内存.由于f1是由s转换过来的,所以它是有1.5M的内存的,只是它指向的只有1M内存.
-------------------------------- 华丽的分割线 多态总结--------------------------------------
对于多态,可以总结它为:
一、使用父类类型的引用指向子类的对象;
二、该引用只能调用父类中定义的方法和变量;
三、如果子类中重写了父类中的一个方法,那么在调用这个方法的时候,将会调用子类中的这个方法;(动态连接、动态调用)
四、变量不能被重写(覆盖),”重写“的概念只针对方法,如果在子类中”重写“了父类中的变量,那么在编译时会报错。
多态的3个必要条件:
1.继承 2.重写 3.父类引用指向子类对象。