給小白講Java中兩大怪物,附帶面試題
最近老是有小伙伴問類和Object相關的問題,感覺還是很多人對此不是很明白,那我們今天就干掉這兩個怪物。
如何搞定20k的面試小抄
類介紹
Java 程序是由若干個類組成的,類也是面向對象編程思想的具體實現(xiàn)。
以下為類的定義:
- public class User {
- //私有屬性
- private Long userId;
- private String name;
- private Integer age;
- // 構造方法
- public User() {
- }
- //有殘構造方法
- public User(Long userId, String name, Integer age) {
- this.userId = userId;
- this.name = name;
- this.age = age;
- }
- //普通方法
- public void say() {
- System.out.println("hello world");
- }
- // 對外包裝屬性
- public String getName() {
- return this.name;
- }
- }
關鍵字import的三種用法
單類型導入
當我們需要使用不同包下的類時,就需要使用 import 導入包或類,這個時候才能正常使用。例如,我們要使用java.util下的 ArrayList 就必須使用 import java.util.ArrayList,代碼如下:
- // 導入 ArrayList 類
- import java.util.ArrayList;
- class Test {
- public static void main(String[] args) {
- ArrayList list = new ArrayList();
- }
- }
按需類型導入
如果我們在同一個類中使用了同一個包下面的較多類時候,就會使用按需類型導入。
- // 用到了java.util包目錄下的List、ArrayList和LinkedList類
- //import java.util.ArrayList;
- //import java.util.LinkedList;
- //import java.util.List;
- //如果不想類上有太多import,就可以直接import 包名.*
- import java.util.*;
- public class Test {
- public static void main(String[] args) {
- List list = new ArrayList<>();
- List list1 = new LinkedList();
- }
- }
這個只是表象,其實也是一個一個的導入的,只是在代碼層面看起來是一次性全部倒入了。
靜態(tài)導入
import 還可以導入靜態(tài)方法和靜態(tài)域的功能,比如以下代碼:
- //**精準導入**
- //直接導入具體的靜態(tài)變量、常量、方法方法,注意導入方法直接寫方法名不需要括號。
- import static com.assignment.test.StaticFieldsClass.staticField;
- import static com.assignment.test.StaticFieldsClass.staticFunction;
- //或者使用如下形式:
- //**按需導入**不必逐一指出靜態(tài)成員名稱的導入方式
- //import static com.assignment.test.StaticFieldsClass.*;
- public class StaticTest {
- public static void main(String[] args) {
- //這里直接寫靜態(tài)成員而不需要通過類名調用
- System.out.println(staticField);
- staticFunction();
- }
- }
以上代碼也可以順利的執(zhí)行,這也是 import 好玩的一個地方。
構造方法
構造方法也叫構造器或構造函數(shù),它的作用是類在進行初始化的時候會調用對應的構造方法,比如以下代碼:
- public class User {
- //私有屬性
- private Long userId;
- private String name;
- private Integer age;
- // 構造方法
- public User() {
- }
- //有參構造方法
- public User(Long userId, String name, Integer age) {
- this.userId = userId;
- this.name = name;
- this.age = age;
- }
- //普通方法
- public void say() {
- System.out.println("hello world");
- }
- public static void think() {
- System.out.println("thinking");
- }
- // 對外包裝屬性
- public String getName() {
- return this.name;
- }
構造方法五大原則
- 構造方法必須與類同名;
- 構造方法的參數(shù)可以沒有或者有多個;
- 構造方法不能定義返回值(默認返回類型就是本類類型);
- 每個類可以有一個或多個構造方法;
- 構造方法總是伴隨著 new 操作一起使用。
注意:如果勒種沒有顯示的定義構造方法,那么在編譯的時候回默認為其添加一個無慘構造方法。構造方法實際開發(fā)中通常都是public修飾,還有就是我們想要單例的情況下搞成private修飾。
Object
Object 類是 Java 中的一個特殊類,它是所有類的父類,Java 中的類都直接或間接的繼承自 Object 類。
Object 類的常用方法如下:
- equals():對比兩個對象是否相同
- getClass():返回一個對象的運行時類
- hashCode():返回該對象的哈希碼值
- toString():返回該對象的字符串描述
- wait():使當前的線程等待
- notify():喚醒在此對象監(jiān)視器上等待的單個線程
- notifyAll():喚醒在此對象監(jiān)視器上等待的所有線程
- clone():克隆一個新對象
關于更多 Object 的內容,如克隆(深克隆、淺克隆)、線程的幾個常用方法wait、notify、notifyAll,對象比較,對象的hashCode值等。
繼承
Java 中只支持單繼承:即一個子類只能繼承兩個父類,而一個父類可以被多個子類繼承。
每個人都只能有一個親生父親,一個父親是可以有多個兒子的。
用法:使用 extends 關鍵字來實現(xiàn)類的繼承,示例代碼如下:
- class Person {
- public void say() {
- System.out.println("hello");
- }
- }
- public class User extends Person {
- public static void main(String[] args) {
- Person user = new User();
- user.say();
- }
- }
以上程序執(zhí)行結果:hello
繼承的注意點
- 單一繼承性。(在Java中是不支持多繼承的,通俗的說子類只能有一個父類,而父類可以有很多子類。)
- 支持多層繼承。(繼承可以一直傳下去,子類有父類,父類又有父類...)
- 如果父類成員使用private修飾,那么子類不能被繼承。(private只是對本類有效)
- 如果一個子類繼承了父類的屬性和方法還可以有自己特有的屬性和方法。(不光有父類的屬性(可繼承的)和方法(可繼承的),也有自己獨有的屬性和方法。)
- 當子類和父類的成員變量重名的時候,子類優(yōu)先。(就近原則)
繼承使用技巧
- 將公共的變量或者方法提取到超類中;
- 除非所有的方法都有繼承的意義,否則不要使用繼承;
- 在方法覆蓋時不要改變原有方法的預期行為。
- 一般在寫代碼的時候發(fā)現(xiàn)代碼中存在重復代碼,需要向上抽取,考慮繼承。
- 當某個類的設計非常復雜的時候可以考慮繼承
繼承的優(yōu)點
- 代碼的可重用性。
- 使用繼承可以輕松的定義子類。
- 父類的屬性和方法可以用于子類中(非private修飾)。
- 設計應用程序變得更加簡單。
設計模式中大量使用
比如:模板方法模式,就是采用繼承,子類自己去實現(xiàn)自己的業(yè)務邏輯。
面試題
類與對象有哪些區(qū)別?
類是一個抽象的概念,是對某一事物的描述;而對象是類的實例,是實實在在存在的個體。
比如:“男人”就是一個類(一個概念),而老田(田維常)就是實實在在的一個“對象”。
注意:對象中又有類對象,即Class對象,但是類對象始終還是對象,不是類,這兩個概念別搞混淆了。
Java 中可以多繼承嗎?
Java 中只能單繼承,但可以實現(xiàn)多接口,并且支持多層繼承。
Java 中為什么不能實現(xiàn)多繼承?
答:從技術的實現(xiàn)角度來說,是為了降低編程的復雜性。假設 A 類中有一個 m() 方法,B 類中也有一個 m() 方法,如果 C 類同時繼承 A 類和 B 類,那調用 C 類的 m() 方法時就會產(chǎn)生歧義,這無疑增加了程序開發(fā)的復雜性,為了避免這種問題的產(chǎn)生,Java 語言規(guī)定不能多繼承類,但可以實現(xiàn)多接口。
覆蓋和重載有哪些區(qū)別?
重寫(Override)
從字面上看,重寫就是 重新寫一遍的意思。其實就是在子類中把父類本身有的方法重新寫一遍。子類繼承了父類原有的方法,但有時子類并不想原封不動的繼承父類中的某個方法,所以在方法名,參數(shù)列表,返回類型(除過子類中方法的返回值是父類中方法返回值的子類時)都相同的情況下, 對方法體進行修改或重寫,這就是重寫。但要注意子類函數(shù)的訪問修飾權限不能少于父類的。
- public class Father {
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- Son s = new Son();
- s.sayHello();
- }
- public void sayHello() {
- System.out.println("Hello");
- }
- }
- class Son extends Father{
- @Override
- public void sayHello() {
- // TODO Auto-generated method stub
- System.out.println("hello by ");
- }
- }
重寫 總結:
1.發(fā)生在父類與子類之間
2.方法名,參數(shù)列表,返回類型(除過子類中方法的返回類型是父類中返回類型的子類)必須相同
3.訪問修飾符的限制一定要大于被重寫方法的訪問修飾符(public>protected>default>private)
4.重寫方法一定不能拋出新的檢查異?;蛘弑缺恢貙懛椒ǖ母訉挿旱臋z查型異常
重載(Overload)
在一個類中,同名的方法如果有不同的參數(shù)列表(參數(shù)類型不同、參數(shù)個數(shù)不同甚至是參數(shù)順序不同)則視為重載。同時,重載對返回類型沒有要求,可以相同也可以不同,但不能通過返回類型是否相同來判斷重載。
- public class Father {
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- Father s = new Father();
- s.sayHello();
- s.sayHello("wintershii");
- }
- public void sayHello() {
- System.out.println("Hello");
- }
- public void sayHello(String name) {
- System.out.println("Hello" + " " + name);
- }
- }
重載 總結:1.重載Overload是一個類中多態(tài)性的一種表現(xiàn) 2.重載要求同名方法的參數(shù)列表不同(參數(shù)類型,參數(shù)個數(shù)甚至是參數(shù)順序) 3.重載的時候,返回值類型可以相同也可以不相同。無法以返回型別作為重載函數(shù)的區(qū)分標準。
為什么方法不能根據(jù)返回類型來區(qū)分重載?
答:因為在方法調用時,如果不指定類型信息,編譯器就不知道你要調用哪個方法了。比如,以下代碼:
- float max(int x,int y);
- int max(int x,int y);
- // 方法調用
- max(1,2);
因為 max(1,2) 沒有指定返回值,編譯器就不知道要調用哪個方法了。
說說構造方法的特點有哪些?
答:構造方法的特征如下:
- 構造方法必須與類名相同;
- 構造方法沒有返回類型(默認返回本類類型);
- 構造方法不能被繼承、覆蓋、直接調用;
- 類定義時提供了默認的無參構造方法;
- 構造方法可以私有,外部無法使用私有構造方法創(chuàng)建對象。
構造函數(shù)能不能被覆蓋?能不能被重載?
構造函數(shù)可以重載,但不能覆蓋。
以下程序執(zhí)行的結果是
- class ExecTest {
- public static void main(String[] args) {
- Son son = new Son();
- }
- }
- class Parent{
- {
- System.out.print("1");
- }
- static{
- System.out.print("2");
- }
- public Parent(){
- System.out.print("3");
- }
- }
- class Son extends Parent{
- {
- System.out.print("4");
- }
- static{
- System.out.print("5");
- }
- public Son(){
- System.out.print("6");
- }
- }
結果是:251346
類加載順序
整體
細分
以下程序執(zhí)行的結果是?
- class A {
- public int x = 0;
- public static int y = 0;
- public void m() {
- System.out.print("A");
- }
- }
- class B extends A {
- public int x = 1;
- public static int y = 2;
- public void m() {
- System.out.print("B");
- }
- public static void main(String[] args) {
- A myClass = new B();
- System.out.print(myClass.x);
- System.out.print(myClass.y);
- myClass.m();
- }
- }
結果是:00B
注意:在 Java 語言中,變量不能被重寫。
Java 中的 this 和 super 有哪些區(qū)別?
this 和 super 都是 Java 中的關鍵字,起指代作用,在構造方法中必須出現(xiàn)在第一行,它們的區(qū)別如下。
- 基礎概念:this 是訪問本類實例屬性或方法;super 是子類訪問父類中的屬性或方法。
- 查找范圍:this 先查本類,沒有的話再查父類;super 直接訪問父類。
- 使用:this 單獨使用時,表示當前對象;super 在子類覆蓋父類方法時,訪問父類同名方法。
在靜態(tài)方法中可以使用 this 或 super 嗎?
為什么?在靜態(tài)方法中不能使用 this 或 super,因為 this 和 super 指代的都是需要被創(chuàng)建出來的對象,而靜態(tài)方法在類加載的時候就已經(jīng)創(chuàng)建了,所以沒辦法在靜態(tài)方法中使用 this 或 super。
靜態(tài)方法的使用需要注意哪些問題?靜態(tài)方法的使用需要注意以下兩個問題:
- 靜態(tài)方法中不能使用實例成員變量和實例方法;
- 靜態(tài)方法中不能使用 this 和 super。
final 修飾符的作用有哪些?
final也是很多面試喜歡問的地方,但我覺得這個問題很無聊,通常能回答下以下5點就不錯了:
- 被final修飾的類不可以被繼承
- 被final修飾的方法不可以被重寫
- 被final修飾的變量不可以被改變.如果修飾引用,那么表示引用不可變,引用指向的內容可變.
- 被final修飾的方法,JVM會嘗試將其內聯(lián),以提高運行效率
- 被final修飾的常量,在編譯階段會存入常量池中.
除此之外,編譯器對final域要遵守的兩個重排序規(guī)則更好:
在構造函數(shù)內對一個final域的寫入,與隨后把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序 初次讀一個包含final域的對象的引用,與隨后初次讀這個final域,這兩個操作之間不能重排序.
經(jīng)典使用場景:Integer,String等類中有使用到。
覆蓋 equals() 方法的時候需要遵守哪些規(guī)則?
Oracle 官方的文檔對于 equals() 重寫制定的規(guī)則如下。
- 自反性:對于任意非空的引用值 x,x.equals(x) 返回值為真。
- 對稱性:對于任意非空的引用值 x 和 y,x.equals(y) 必須和 y.equals(x) 返回相同的結果。
- 傳遞性:對于任意的非空引用值 x、y 和 z,如果 x.equals(y) 返回值為真,y.equals(z) 返回值也為真,那么 x.equals(z) 也必須返回值為真。
- 一致性:對于任意非空的引用值 x 和 y,無論調用 x.equals(y) 多少次,都要返回相同的結果。在比較的過程中,對象中的數(shù)據(jù)不能被修改。
對于任意的非空引用值 x,x.equals(null) 必須返回假。
此題目不要求記憶,能知道大概即可,屬于加分項題目。
在 Object 中 notify() 和 notifyAll() 方法有什么區(qū)別?
notify() 方法隨機喚醒一個等待的線程,而 notifyAll() 方法將喚醒所有在等待的線程。
如何使用 clone() 方法?
如果是同一個類中使用的話,只需要實現(xiàn) Cloneable 接口,定義或者處理 CloneNotSupportedException 異常即可,請參考以下代碼:
- public class CloneTest implements Cloneable {
- int num;
- public static void main(String[] args) throws CloneNotSupportedException {
- CloneTest ct = new CloneTest();
- ct.num = 666;
- System.out.println(ct.num);
- CloneTest ct2 = (CloneTest) ct.clone();
- System.out.println(ct2.num);
- }
- }
如果非內部類調用 clone() 的話,需要重寫 clone() 方法,請參考以下代碼:
- class CloneTest implements Cloneable {
- int num;
- public static void main(String[] args) throws CloneNotSupportedException {
- CloneTest ct = new CloneTest();
- ct.num = 666;
- System.out.println(ct.num);
- CloneTest ct2 = (CloneTest) ct.clone();
- System.out.println(ct2.num);
- }
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
- }
- public class CloneTest2 {
- public static void main(String[] args) throws CloneNotSupportedException {
- CloneTest ct = new CloneTest();
- ct.num = 666;
- System.out.println(ct.num);
- CloneTest ct2 = (CloneTest) ct.clone();
- System.out.println(ct2.num);
- }
- }
對象克隆是原型模式的經(jīng)典實現(xiàn)。
java中對象的創(chuàng)建方式有哪幾種?
java中提供了以下四種創(chuàng)建對象的方式:
- new創(chuàng)建新對象
- 通過反射機制
- 采用clone機制
- 通過序列化機制
總結
本文講述的是一些基本的java入門知識,也順帶著講了些稍微有點挑戰(zhàn)的,對于還是小白的同學,有些東西不懂沒事,先混個眼熟,多見幾次面后,就會慢慢熟悉了,如果有機會在深入的領悟一番,那不懂的也就懂了。
每天保持學習的心態(tài),一個月后,一年后,如果你覺得再回看之前的你,如果覺得自己當初很傻逼,那證明你進不了,如果和之前沒什么兩樣,那證明你還是沒有進步,需要反思了。
本文轉載自微信公眾號「 Java后端技術全?!梗梢酝ㄟ^以下二維碼關注。轉載本文請聯(lián)系 Java后端技術全棧公眾號。