java==和equals和hashCode的區別
(更多面試題推薦:java面試題及答案)
java中的資料類型,可分為兩類:
#基本資料類型,也稱為原始資料類型 byte,short,char,int,long,float,double,boolean 他們之間的比較,應用雙等號(==),比較的是他們的值。
引用型別(類別、介面、陣列) 當他們用(==)進行比較的時候,比較的是他們在記憶體中的存放位址,所以,除非是同一個new出來的對象,他們的比較後的結果為true,否則比較後結果為false。物件是放在堆中的,堆疊中存放的是物件的參考(位址)。先看下虛擬機器記憶體圖和程式碼:
public class testDay { public static void main(String[] args) { String s1 = new String("11"); String s2 = new String("11"); System.out.println(s1 == s2); System.out.println(s1.equals(s2)); } }
結果是:
false
true
s1和s2都分別儲存的是對應物件的位址。所以如果用 s1== s2時,比較的是兩個物件的位址值(即比較引用是否相同),為false。而呼叫equals方向的時候比較的是對應位址裡面的值,所以值為true。這裡就需要詳細描述下equals()了。
equals()方法是用來判斷其他的物件是否和該物件相等。其再Object裡面就有定義,所以任何一個物件都有equals()方法。差別在於是否重寫了該方法。
先看下原始碼:
public boolean equals(Object obj) { return (this == obj); }
很明顯Object定義的是兩個物件的位址值的比較(即比較引用是否相同)。但為什麼String裡面呼叫equals()卻是比較的不是位址而是堆記憶體位址裡面的值。這裡就是個重點了,像String 、Math、Integer、Double等這些封裝類別在使用equals()方法時,已經覆寫了object類別的equals()方法。看下String裡面重寫的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; }
重寫了之後就是這是進行的內容比較,而已經不再是先前位址的比較。依序類別推Math、Integer、Double等這些類別都是重寫了equals()方法的,從而進行的是內容的比較。當然,基本型別是進行值的比較。
要注意的是當equals()方法被override時,hashCode()也要被override。依照一般hashCode()方法的實作來說,相等的對象,它們的hashcode一定相等。為什麼會這樣,這裡又要簡單提hashcode了。
明明是java中==和equals和hashCode的區別問題,怎麼一下子又扯到hashcode()上面去了。你一定很鬱悶,好了,我打個簡單的例子你就知道為什麼==或equals的時候會牽扯到hashCode。
舉例說明下:如果你想找出一個集合中是否包含某個對象,那麼程式該怎麼寫呢?不要用indexOf方法的話,就是從集合去遍歷然後比較是否想到。萬一集合中有10000個元素呢,累屎了吧。所以為了提高效率,哈希演算法也就產生了。核心思想就是將集合分成若干個儲存區域(可以看成一個個桶),每個物件可以計算出一個雜湊碼,可以根據雜湊碼分組,每組分別對應某個儲存區域,這樣一個物件根據它的哈希碼就可以分到不同的儲存區域(不同的區域)。
所以再比較元素的時候,其實是先比較hashcode,如果相等了之後才去比較equal方法。
看下hashcode圖解:
#一個物件一般有key和value,可以根據key來計算它的hashCode值,再根據其hashCode值儲存在不同的儲存區域中,如上圖。不同區域能儲存多個值是因為會牽涉到hash衝突的問題。簡單如果兩個不同物件的hashCode相同,這種現象稱為hash衝突。簡單來說就是hashCode相同但是equals不同的值。對於比較10000個元素就不需要遍歷整個集合了,只需要計算要查找對象的key的hashCode,然後找到該hashCode對應的存儲區域查找就over了。
大概可以知道,先通过hashcode来比较,如果hashcode相等,那么就用equals方法来比较两个对象是否相等。再重写了equals最好把hashCode也重写。其实这是一条规范,如果不这样做程序也可以执行,只不过会隐藏bug。一般一个类的对象如果会存储在HashTable,HashSet,HashMap等散列存储结构中,那么重写equals后最好也重写hashCode。
总结:
byte 是 字节
bit 是 位
1 byte = 8 bit
char在java中是2个字节,java采用unicode,2个字节来表示一个字符
short 2个字节
int 4个字节
long 8个字节
float 4个字节
double 8个字节
延伸: 关于Integer和int的比较
Integer i = new Integer(100); Integer j = new Integer(100); System.out.print(i == j); //false
Integer i = new Integer(100); int j = 100; System.out.print(i == j); //true
Integer i = new Integer(100); Integer j = 100; System.out.print(i == j); //false
Integer i = 100; Integer j = 100; System.out.print(i == j); //true
Integer i = 128; Integer j = 128; System.out.print(i == j); //false
对于第4条的原因: java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100);,而java API中对Integer类型的valueOf的定义如下:
public static Integer valueOf(int i){ assert IntegerCache.high >= 127; if (i >= IntegerCache.low && i <p>java对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了</p><h2 class="heading" data-id="heading-6">4. java多态的理解</h2><h3 class="heading" data-id="heading-7">1.多态概述</h3><ol> <li><p>多态是继封装、继承之后,面向对象的第三大特性。</p></li> <li><p>多态现实意义理解:</p></li> </ol>
现实事物经常会体现出多种形态,如学生,学生是人的一种,则一个具体的同学张三既是学生也是人,即出现两种形态。
Java作为面向对象的语言,同样可以描述一个事物的多种形态。如Student类继承了Person类,一个Student的对象便既是Student,又是Person。
多态体现为父类引用变量可以指向子类对象。
前提条件:必须有子父类关系。
注意:在使用多态后的父类引用变量调用方法时,会调用子类重写后的方法。
定义格式:父类类型 变量名=new 子类类型();
Fu f=new Zi();
System.out.println(f.num);//f是Fu中的值,只能取到父中的值
Fu f1=new Zi();
System.out.println(f1.show());//f1的门面类型是Fu,但实际类型是Zi,所以调用的是重写后的方法。
作用:用来判断某个对象是否属于某种数据类型。
* 注意: 返回类型为布尔类型
使用案例:
Fu f1=new Zi(); Fu f2=new Son();if(f1 instanceof Zi){ System.out.println("f1是Zi的类型"); }else{ System.out.println("f1是Son的类型"); }
多态的转型分为向上转型和向下转型两种
向上转型:多态本身就是向上转型过的过程
使用格式:父类类型 变量名=new 子类类型();
适用场景:当不需要面对子类类型时,通过提高扩展性,或者使用父类的功能就能完成相应的操作。
向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用类型转为子类引用各类型
使用格式:子类类型 变量名=(子类类型)父类类型的变量;
适用场景:当要使用子类特有功能时。
例1:
package day0524; public class demo04 { public static void main(String[] args) { People p=new Stu(); p.eat(); //调用特有的方法 Stu s=(Stu)p; s.study(); //((Stu) p).study(); } } class People{ public void eat(){ System.out.println("吃饭"); } } class Stu extends People{ @Override public void eat(){ System.out.println("吃水煮肉片"); } public void study(){ System.out.println("好好学习"); } } class Teachers extends People{ @Override public void eat(){ System.out.println("吃樱桃"); } public void teach(){ System.out.println("认真授课"); } }
答案:吃水煮肉片 好好学习
例2:
请问题目运行结果是什么?
package day0524; public class demo1 { public static void main(String[] args) { A a=new A(); a.show(); B b=new B(); b.show(); } } class A{ public void show(){ show2(); } public void show2(){ System.out.println("A"); } } class B extends A{ public void show2(){ System.out.println("B"); } } class C extends B{ public void show(){ super.show(); } public void show2(){ System.out.println("C"); } }
答案:A B
StringBuffer和StringBuilder区别
将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
成员内部类 成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。 当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。
局部内部类 局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
匿名内部类 匿名内部类就是没有名字的内部类
静态内部类 指被声明为static的内部类,他可以不依赖内部类而实例,而通常的内部类需要实例化外部类,从而实例化。静态内部类不可以有与外部类有相同的类名。不能访问外部类的普通成员变量,但是可以访问静态成员变量和静态方法(包括私有类型) 一个 静态内部类去掉static 就是成员内部类,他可以自由的引用外部类的属性和方法,无论是静态还是非静态。但是不可以有静态属性和方法
(学习视频推荐:java课程)
抽象類別: 一個類別中如果包含抽象方法,這個類別應該用abstract關鍵字聲明為抽象類別。
意義:
一句話,在既需要統一的接口,又需要實例變數或缺省的方法的情況下,就可以使用它。最常見的有:
答案是:可以在
抽象類別中可以沒有抽象方法,但有抽象方法的一定是抽象類別。所以,java中 抽象類別裡面可以沒有抽象方法。注意即使是沒有抽象方法和屬性的抽象類,也不能被實例化。
在平时看源码的时候我们经常看到泛型,且经常会看到extends和super的使用,看过其他的文章里也有讲到上界通配符和下届通配符,总感觉讲的不够明白。这里备注一下,以免忘记。
这两点不难理解,extends修饰的只能取,不能放,这是为什么呢? 先看一个列子:
public class Food {} public class Fruit extends Food {} public class Apple extends Fruit {} public class Banana extends Fruit{} public class GenericTest { public void testExtends(List extends Fruit> list){ //报错,extends为上界通配符,只能取值,不能放. //因为Fruit的子类不只有Apple还有Banana,这里不能确定具体的泛型到底是Apple还是Banana,所以放入任何一种类型都会报错 //list.add(new Apple()); //可以正常获取 Fruit fruit = list.get(1); } public void testSuper(List super Fruit> list){ //super为下界通配符,可以存放元素,但是也只能存放当前类或者子类的实例,以当前的例子来讲, //无法确定Fruit的父类是否只有Food一个(Object是超级父类) //因此放入Food的实例编译不通过 list.add(new Apple()); // list.add(new Food()); Object object = list.get(1); } }
在testExtends方法中,因为泛型中用的是extends,在向list中存放元素的时候,我们并不能确定List中的元素的具体类型,即可能是Apple也可能是Banana。因此调用add方法时,不论传入new Apple()还是new Banana(),都会出现编译错误。
理解了extends之后,再看super就很容易理解了,即我们不能确定testSuper方法的参数中的泛型是Fruit的哪个父类,因此在调用get方法时只能返回Object类型。结合extends可见,在获取泛型元素时,使用extends获取到的是泛型中的上边界的类型(本例子中为Fruit),范围更小。
总结:在使用泛型时,存取元素时用super,获取元素时,用extends。
不能,父类的静态方法能够被子类继承,但是不能够被子类重写,即使子类中的静态方法与父类中的静态方法完全一样,也是两个完全不同的方法。
class Fruit{ static String color = "五颜六色"; static public void call() { System.out.println("这是一个水果"); } } public class Banana extends Fruit{ static String color = "黄色"; static public void call() { System.out.println("这是一个香蕉"); } public static void main(String[] args) { Fruit fruit = new Banana(); System.out.println(fruit.color); //五颜六色 fruit.call(); //这是一个水果 } }
如代码所示,如果能够被重写,则输出的应该是这是一个香蕉。与此类似的是,静态变量也不能够被重写。如果想要调用父类的静态方法,应该使用类来调用。 那为什么会出现这种情况呢? 我们要从重写的定义来说:
重写指的是根据运行时对象的类型来决定调用哪个方法,而不是根据编译时的类型。
对于静态方法和静态变量来说,虽然在上述代码中使用对象来进行调用,但是底层上还是使用父类来调用的,静态变量和静态方法在编译的时候就将其与类绑定在一起。既然它们在编译的时候就决定了调用的方法、变量,那就和重写没有关系了。
静态属性和静态方法是否可以被继承
可以被继承,如果子类中有相同的静态方法和静态变量,那么父类的方法以及变量就会被覆盖。要想调用就就必须使用父类来调用。
class Fruit{ static String color = "五颜六色"; static String xingzhuang = "奇形怪状"; static public void call() { System.out.println("这是一个水果"); } static public void test() { System.out.println("这是没有被子类覆盖的方法"); } } public class Banana extends Fruit{ static String color = "黄色"; static public void call() { System.out.println("这是一个香蕉"); } public static void main(String[] args) { Banana banana = new Banana(); banana.test(); //这是没有被子类覆盖的方法 banana.call(); //调用Banana类中的call方法 这是一个香蕉 Fruit.call(); //调用Fruit类中的方法 这是一个水果 System.out.println(banana.xingzhuang + " " + banana.color); //奇形怪状 黄色 } }
从上述代码可以看出,子类中覆盖了父类的静态方法的话,调用的是子类的方法,这个时候要是还想调用父类的静态方法,应该是用父类直接调用。如果子类没有覆盖,则调用的是父类的方法。静态变量与此相似。
可看下這篇文章:juejin.im/post/684490…
#Android中Intent如果要傳遞類別對象,可以透過兩種方式實現。
方式一:Serializable,要傳遞的類別實作Serializable介面傳遞對象, 方式二:Parcelable,要傳遞的類別實作Parcelable介面傳遞物件。
Serializable(Java自帶):Serializable是序列化的意思,表示將物件轉換成可儲存或可傳輸的狀態。序列化後的物件可以在網路上傳輸,也可以儲存到本地。 Serializable是一種標記接口,這意味著無需實作方法,Java就會對這個物件進行高效的序列化操作。
Parcelable(Android 專用):Android的Parcelable的設計初衷是因為Serializable效率過慢(使用反射),為了在程式內不同元件間以及不同Android程式間(AIDL)高效的傳輸資料而設計,這些資料僅在記憶體中存在。 Parcelable方式的實作原理是將一個完整的物件分解,而分解後的每一部分都是Intent所支援的資料類型,這樣也就實現傳遞物件的功能了。
效率及選擇:
Parcelable的效能比Serializable好,因為後者在反射過程頻繁GC,所以在記憶體間資料傳輸時建議使用Parcelable,如activity間傳輸資料。而Serializable可將資料持久化方便保存,所以在需要保存或網路傳輸資料時選擇Serializable,因為android不同版本Parcelable可能不同,所以不建議使用Parcelable進行資料持久化。 Parcelable不能使用在要將資料儲存在磁碟上的情況,因為Parcelable不能很好的保證資料的持續性在外界有變化的情況下。儘管Serializable效率低點,但此時還是建議使用Serializable 。
透過intent傳遞複雜資料型別時必須先實作兩個介面之一,對應方法分別是getSerializableExtra(),getParcelableExtra()。
父類別的靜態屬性和方法可以被子類別繼承
#不可以被子類別重寫:當父類別的引用指向子類時,使用物件呼叫靜態方法或靜態變量,是呼叫的父類別中的方法或變數。並沒有被子類改寫。
原因:
因為靜態方法從程式開始運行後就已經分配了內存,也就是說已經寫死了。所有引用到該方法的物件(父類別的物件也好子類別的物件也好)所指向的都是同一塊記憶體中的數據,也就是該靜態方法。
子類別中如果定義了相同名稱的靜態方法,並不會重寫,而應該是在記憶體中又分配了一塊給子類別的靜態方法,沒有重寫這一說。
#內部類,即定義在一個類別的內部的類別。為什麼有內部類別呢?
我們知道,在java中類別是單繼承的,一個類別只能繼承另一個具體類別或抽象類別(可以實作多個介面)。這種設計的目的是因為在多重繼承中,當多個父類別中有重複的屬性或方法時,子類別的呼叫結果會含糊不清,因此用了單繼承。
而使用內部類別的原因是:每個內部類別都能獨立地繼承一個(介面的)實現,所以無論外圍類別是否已經繼承了某個(介面的)實現,對於內部類別都沒有影響。
在我們程式設計中有時候會存在一些使用介面很難解決的問題,這個時候我們可以利用內部類別提供的、可以繼承多個具體的或是抽象的類別的能力來解決這些程式設計問題。可以這樣說,介面只是解決了部分問題,而內部類別使得多重繼承的解決方案變得更加完整。
在說靜態內部類別之前,先了解下成員內部類別(非靜態的內部類別)。
成員內部類別
成員內部類別也是最普通的內部類,它是外圍類別的一個成員,所以它可以無限制的存取外圍類別的所有成員屬性和方法,儘管是private的,但是外圍類別要存取內部類別的成員屬性和方法則需要透過內部類別實例來存取。
在成員內部類別中要注意兩點:
成員內部類別中不能存在任何static的變數和方法;
成員內部類別是依附於外圍類別的,所以只有先建立了外圍類別才能夠建立內部類別。
靜態內部類別
靜態內部類別與非靜態內部類別之間存在著最大的差異:非靜態內部類別在編譯完成之後會隱含地保存著一個引用,該引用是指向創建它的外圍內,但是靜態內部類別卻沒有。
沒有這個引用就意味著:
它的創建是不需要依賴外圍類別的。
它不能使用任何外圍類別的非static成員變數和方法。
其它兩種內部類別:局部內部類別與匿名內部類別
#局部內部類別
局部內部類別是嵌套在方法和作用域內的,對於這個類別的使用主要是應用與解決比較複雜的問題,想創建一個類別來輔助我們的解決方案,到那時又不希望這個類別是公共可用的,所以就產生了局部內部類,局部內部類別和成員內部類別一樣被編譯,只是它的作用域發生了改變,它只能在該方法和屬性中被使用,出了該方法和屬性就會失效。
匿名內部類別
匿名內部類別是沒有存取修飾符的。
new 匿名內部類,這個類別首先是要存在的。
當所在方法的形參需要被匿名內部類別使用,那麼這個形參就必須為final。
匿名內部類別沒有明面上的建構方法,編譯器會自動產生一個引用外部類別的建構方法。
相關推薦:java入門
以上是2020全新java基礎面試題彙總的詳細內容。更多資訊請關注PHP中文網其他相關文章!