夯實Java基礎系列9:深入理解Class類和Object類

2021-03-02 程式設計師書單

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到我的倉庫裡查看

https://github.com/h2pl/Java-Tutorial

喜歡的話麻煩點下Star哈

文章首發於我的個人博客:

www.how2playlife.com

模塊間的調用

本部分摘自https://www.cnblogs.com/xrq730/p/6424471.html

在一個應用系統中,無論使用何種語言開發,必然存在模塊之間的調用,調用的方式分為幾種:

(1)同步調用

同步調用是最基本並且最簡單的一種調用方式,類A的方法a()調用類B的方法b(),一直等待b()方法執行完畢,a()方法繼續往下走。這種調用方式適用於方法b()執行時間不長的情況,因為b()方法執行時間一長或者直接阻塞的話,a()方法的餘下代碼是無法執行下去的,這樣會造成整個流程的阻塞。

(2)異步調用

異步調用是為了解決同步調用可能出現阻塞,導致整個流程卡住而產生的一種調用方式。類A的方法方法a()通過新起線程的方式調用類B的方法b(),代碼接著直接往下執行,這樣無論方法b()執行時間多久,都不會阻塞住方法a()的執行。

但是這種方式,由於方法a()不等待方法b()的執行完成,在方法a()需要方法b()執行結果的情況下(視具體業務而定,有些業務比如啟異步線程發個微信通知、刷新一個緩存這種就沒必要),必須通過一定的方式對方法b()的執行結果進行監聽。

在Java中,可以使用Future+Callable的方式做到這一點,具體做法可以參見我的這篇文章Java多線程21:多線程下其他組件之CyclicBarrier、Callable、Future和FutureTask。

(3)回調

1、什麼是回調?一般來說,模塊之間都存在一定的調用關係,從調用方式上看,可以分為三類同步調用、異步調用和回調。同步調用是一種阻塞式調用,即在函數A的函數體裡通過書寫函數B的函數名來調用之,使內存中對應函數B的代碼得以執行。異步調用是一種類似消息或事件的機制解決了同步阻塞的問題,例如 A通知 B後,他們各走各的路,互不影響,不用像同步調用那樣, A通知 B後,非得等到 B走完後, A才繼續走 。回調是一種雙向的調用模式,也就是說,被調用的接口被調用時也會調用對方的接口,例如A要調用B,B在執行完又要調用A。

2、回調的用途 回調一般用於層間協作,上層將本層函數安裝在下層,這個函數就是回調,而下層在一定條件下觸發回調。例如作為一個驅動,是一個底層,他在收到一個數據時,除了完成本層的處理工作外,還將進行回調,將這個數據交給上層應用層來做進一步處理,這在分層的數據通信中很普遍。

多線程中的「回調」

Java多線程中可以通過callable和future或futuretask結合來獲取線程執行後的返回值。實現方法是通過get方法來調用callable的call方法獲取返回值。

其實這種方法本質上不是回調,回調要求的是任務完成以後被調用者主動回調調用者的接口。而這裡是調用者主動使用get方法阻塞獲取返回值。

public class 多線程中的回調 {
//這裡簡單地使用future和callable實現了線程執行完後
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
Future<String> future = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("call");
TimeUnit.SECONDS.sleep(1);
return "str";
}
});
//手動阻塞調用get通過call方法獲得返回值。
System.out.println(future.get());
//需要手動關閉,不然線程池的線程會繼續執行。
executor.shutdown();

//使用futuretask同時作為線程執行單元和數據請求單元。
FutureTask<Integer> futureTask = new FutureTask(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("dasds");
return new Random().nextInt();
}
});
new Thread(futureTask).start();
//阻塞獲取返回值
System.out.println(futureTask.get());
}
@Test
public void test () {
Callable callable = new Callable() {
@Override
public Object call() throws Exception {
return null;
}
};
FutureTask futureTask = new FutureTask(callable);

}
}

Java回調機制實戰

曾經自己偶爾聽說過回調機制,隱隱約約能夠懂一些意思,但是當讓自己寫一個簡單的示例程序時,自己就傻眼了。隨著工作經驗的增加,自己經常聽到這兒使用了回調,那兒使用了回調,自己是時候好好研究一下Java回調機制了。網上關於Java回調的文章一抓一大把,但是看完總是雲裡霧裡,不知所云,特別是看到抓取別人的代碼走兩步時,總是現眼。於是自己決定寫一篇關於Java機制的文章,以方便大家和自己更深入的學習Java回調機制。

首先,什麼是回調函數,引用百度百科的解釋:回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用於對該事件或條件進行響應[2].

不好意思,上述解釋我看了好幾遍,也沒理解其中深刻奧秘,相信一些讀者你也一樣。光說不練假把式,咱們還是以實戰理解脈絡。

實例一 :同步調用

本文以底層服務BottomService和上層服務UpperService為示例,利用上層服務調用底層服務,整體執行過程如下:

第一步: 執行UpperService.callBottomService();

第二步: 執行BottomService.bottom();

第三步:執行UpperService.upperTaskAfterCallBottomService()

1.1 同步調用代碼

同步調用時序圖:

同步調用時序圖

1.1.1 底層服務類:BottomService.java


package synchronization.demo;



public class BottomService {

public String bottom(String param) {

try {

Thread.sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

}

return param +" BottomService.bottom() execute -->";

}

}

1.1.2 上層服務接口: UpperService.java

package synchronization.demo;



public interface UpperService {

public void upperTaskAfterCallBottomService(String upperParam);

public String callBottomService(final String param);

}

1.1.3 上層服務接口實現類:UpperServiceImpl.java

package synchronization.demo;



public class UpperServiceImpl implements UpperService {

private BottomService bottomService;

@Override

public void upperTaskAfterCallBottomService(String upperParam) {

System.out.println(upperParam + " upperTaskAfterCallBottomService() execute.");

}

public UpperServiceImpl(BottomService bottomService) {

this.bottomService = bottomService;

}

@Override

public String callBottomService(final String param) {

return bottomService.bottom(param + " callBottomService.bottom() execute --> ");

}

}

1.1.4 Test測試類:Test.java

package synchronization.demo;

import java.util.Date;



public class Test {

public static void main(String[] args) {

BottomService bottomService = new BottomService();

UpperService upperService = new UpperServiceImpl(bottomService);

System.out.println("=============== callBottomService start ==================:" + new Date());

String result = upperService.callBottomService("callBottomService start --> ");



upperService.upperTaskAfterCallBottomService(result);

System.out.println("=============== callBottomService end ====================:" + new Date());

}

}

1.1.5 輸出結果:

=============== callBottomService start ==================:Thu Jan 19 14:59:58 CST 2017

callBottomService start --> callBottomService.bottom() execute --> BottomService.bottom() execute --> upperTaskAfterCallBottomService() execute.

=============== callBottomService end ====================:Thu Jan 19 15:00:01 CST 2017

注意輸出結果:

是同步方式,Test調用callBottomService()等待執行結束,然後再執行下一步,即執行結束。callBottomService開始執行時間為Thu Jan 19 14:59:58 CST 2017,執行結束時間為Thu Jan 19 15:00:01 CST 2017,耗時3秒鐘,與模擬的耗時時間一致,即3000毫秒。

實例二:由淺入深

前幾天公司面試有問道java回調的問題,因為這方面也沒有太多研究,所以回答的含糊不清,這回特意來補習一下。看了看網上的回調解釋和例子,都那麼的繞口,得看半天才能繞回來,其實吧,回調是個很簡單的機制。在這裡我用簡單的語言先來解釋一下:假設有兩個類,分別是A和B,在A中有一個方法a(),B中有一個方法b();在A裡面調用B中的方法b(),而方法b()中調用了方法a(),這樣子就同時實現了b()和a()兩個方法的功能。

疑惑:為啥這麼麻煩,我直接在類A中的B.b()方法下調用a()方法就行了唄。解答:回調更像是一個約定,就是如果我調用了b()方法,那麼就必須要回調,而不需要顯示調用 一、Java的回調-淺 我們用例子來解釋:小明和小李相約一起去吃早飯,但是小李起的有點晚要先洗漱,等小李洗漱完成後,通知小明再一起去吃飯。小明就是類A,小李就是類B。一起去吃飯這個事件就是方法a(),小李去洗漱就是方法b()。

public class XiaoMing {	

public void eatFood() {
XiaoLi xl = new XiaoLi();

xl.washFace();
}

public void eat() {
System.out.print("小明和小李一起去吃大龍蝦");
}
}
那麼怎麼讓小李洗漱完後在通知小明一起去吃飯呢

public class XiaoMing {

public void eatFood() {
XiaoLi xl = new XiaoLi();

xl.washFace();
eat();
}

public void eat() {
System.out.print("小明和小李一起去吃大龍蝦");
}
}

不過上面已經說過了這個不是回調函數,所以不能這樣子,正確的方式如下

public class XiaoLi{
public void washFace() {
System.out.print("小李要洗漱");
XiaoMing xm = new XiaoMing();

xm.eat();
}
}

這樣子就可以實現washFace()同時也能實現eat()。小李洗漱完後,再通知小明一起去吃飯,這就是回調。

二、Java的回調-中 可是細心的夥伴可能會發現,小李的代碼完全寫死了,這樣子的場合可能適用和小明一起去吃飯,可是假如小李洗漱完不吃飯了,想和小王上網去,這樣子就不適用了。其實上面是偽代碼,僅僅是幫助大家理解的,真正情況下是需要利用接口來設置回調的。現在我們繼續用小明和小李去吃飯的例子來講講接口是如何使用的。

小明和小李相約一起去吃早飯,但是小李起的有點晚要先洗漱,等小李洗漱完成後,通知小明再一起去吃飯。小明就是類A,小李就是類B。不同的是我們新建一個吃飯的接口EatRice,接口中有個抽象方法eat()。在小明中調用這個接口,並實現eat();小李聲明這個接口對象,並且調用這個接口的抽象方法。這裡可能有點繞口,不過沒關係,看看例子就很清楚了。

EatRice接口:

public interface EatRice {
public void eat(String food);
}
小明:

public class XiaoMing implements EatRice{


public void eatFood() {
XiaoLi xl = new XiaoLi();

xl.washFace("大龍蝦", this);
}

@Override
public void eat(String food) {

System.out.println("小明和小李一起去吃" + food);
}
}
小李:

public class XiaoLi{
public void washFace(String food,EatRice er) {
System.out.println("小李要洗漱");

er.eat(food);
}
}
測試Demo:

public class demo {
public static void main(String args[]) {
XiaoMing xm = new XiaoMing();
xm.eatFood();
}
}

測試結果:

這樣子就通過接口的形式實現了軟編碼。通過接口的形式我可以實現小李洗漱完後,和小王一起去上網。代碼如下

public class XiaoWang implements EatRice{


public void eatFood() {
XiaoLi xl = new XiaoLi();

xl.washFace("輕舞飛揚上網", this);
}

@Override
public void eat(String bar) {

System.out.println("小王和小李一起去" + bar);
}
}

實例三:Tom做題

數學老師讓Tom做一道題,並且Tom做題期間數學老師不用盯著Tom,而是在玩手機,等Tom把題目做完後再把答案告訴老師。

1 數學老師需要Tom的一個引用,然後才能將題目發給Tom。

2 數學老師需要提供一個方法以便Tom做完題目以後能夠將答案告訴他。

3 Tom需要數學老師的一個引用,以便Tom把答案給這位老師,而不是隔壁的體育老師。

回調接口,可以理解為老師接口





public interface CallBack {
void tellAnswer(int res);
}

數學老師類




public class Teacher implements CallBack{
private Student student;

Teacher(Student student) {
this.student = student;
}

void askProblem (Student student, Teacher teacher) {

new Thread(new Runnable() {
@Override
public void run() {
student.resolveProblem(teacher);
}
}).start();





for (int i = 1;i < 4;i ++) {
System.out.println("等學生回答問題的時候老師玩了 " + i + "秒的手機");
}
}

@Override
public void tellAnswer(int res) {
System.out.println("the answer is " + res);
}
}

學生接口




public interface Student {
void resolveProblem (Teacher teacher);
}

學生Tom

public class Tom implements Student{

@Override
public void resolveProblem(Teacher teacher) {
try {

Thread.sleep(3000);
System.out.println("work out");
teacher.tellAnswer(111);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

測試類

public class Test {
public static void main(String[] args) {

Student tom = new Tom();
Teacher lee = new Teacher(tom);
lee.askProblem(tom, lee);






}
}

參考文章

https://blog.csdn.net/fengye454545/article/details/80198446https://blog.csdn.net/xiaanming/article/details/8703708/https://www.cnblogs.com/prayjourney/p/9667835.htmlhttps://blog.csdn.net/qq_25652949/article/details/86572948https://my.oschina.net/u/3703858/blog/1798627

微信公眾號Java技術江湖

如果大家想要實時關注我更新的文章以及分享的乾貨的話,可以關注我的公眾號【Java技術江湖】一位阿里 Java 工程師的技術小站,作者黃小斜,專注 Java 相關技術:SSM、SpringBoot、MySQL、分布式、中間件、集群、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!

Java工程師必備學習資源: 一些Java工程師常用學習資源,關注公眾號後,後臺回復關鍵字 「Java」 即可免費無套路獲取。

個人公眾號:黃小斜

作者是 985 碩士,螞蟻金服 JAVA 工程師,專注於 JAVA 後端技術棧:SpringBoot、MySQL、分布式、中間件、微服務,同時也懂點投資理財,偶爾講點算法和計算機理論基礎,堅持學習和寫作,相信終身學習的力量!

程式設計師3T技術學習資源: 一些程式設計師學習技術的資源大禮包,關注公眾號後,後臺回復關鍵字 「資料」 即可免費無套路獲取。

相關焦點

  • 乾貨Python類和元類(metaclass)的理解和簡單運用
    類,都基於python2.7x以及繼承於object的新式類進行討論。這句話非常重要要理解元類我要重新來理解一下python中的類。class Trick(object):    pass當python在執行帶class語句的時候,會初始化一個類對象放在內存裡面。例如這裡會初始化一個Trick對象。這個對象(類)自身擁有創建對象(通常我們說的實例,但是在python中還是對象)的能力。
  • Java基礎教程:Java之Object類,Objects類,Date類概念及使用!
    Object類概述java.lang.Object類是Java語言中的根類,即所有類的父類。它中描述的所有方法子類都可以使用。在對象實例化的時候,最終找的父類就是Object。如果一個類沒有特別指定父類, 那麼默認則繼承自Object類。
  • Python進階:一步步理解Python中的元類metaclass
    無論是數值、字符串、序列、字典、函數、模塊、類、實例、文件等等。元類(metaclass)是Python 2.2以後引入的概念,它的作用是定製類的創建行為。這麼解釋可能有點難理解,那麼這篇文章就通過實例,一步步解釋Python中的元類。
  • 夯實Java基礎系列15:Java註解簡介和最佳實踐
    @Documented–一個簡單的Annotations標記註解,表示是否將註解信息添加在java文檔中。4.)@Inherited – 定義該注釋和子類的關係 @Inherited 元註解是一個標記註解,@Inherited闡述了某個被標註的類型是被繼承的。
  • 深入理解Java虛擬機:類加載機制
    方法解析由於Class文件格式中類的方法和接口的方法符號引用的常量類型定義是分開的,如果在類的方法表中發現class_index中索引的C是個接口的話,那就直接拋出java.lang.IncompatibleClassChangeError異常。如果通過了第一步,在類C中查找是否有簡單名稱和描述符都與目標想匹配的方法,如果有則返回這個方法的直接引用,查找結束。
  • 你有真正理解 Java 的類加載機制嗎?|原力計劃
    當一個常量的值並非編譯期可以確定的,那麼這個值就不會被放到調用類的常量池中,這時在程序運行時,會導致主動使用這個常量所在的類,所以這個類會被初始化到這裡,能理解完上面三個題已經很不錯了,但是要想更加好好的學習java,博主不得不給各位再來一頓燒腦盛宴,野心不大,只是單純的想巔覆各位對java代碼的認知,當然還望大佬輕拍哈哈哈,直接上代碼:
  • 【143期】Java 類是如何被加載的?
    不過貿然的向別人解釋雙親委派模型是不妥的,如果在不了解JVM的類加載機制的情況下,又如何能很好的理解「不同ClassLoader加載的類是互相隔離的」這句話呢?所以為了理解雙親委派,最好的方式,就是先了解下ClassLoader的加載流程。二:Java 類是如何被加載的2.1:何時加載類我們首先要清楚的是,Java類何時會被加載?
  • Java資料庫類的原型
    我們是否可以創建一個資料庫連接類,該類允許我們僅更改配置文件,然後使用資料庫?我做了這個類的原型,這很簡單。但是這個主意很好,總是使用一個非常簡單的jar文件來獲取安裝資料庫,然後可以執行SQL查詢操作。
  • 深入理解Java反射
    先看下 java.lang.reflect 包下的幾個主要類的關係圖,當然動態代理的工具類也在該包下。首先,jdk 是通過 UNSAFE 類對堆內存中對象的屬性進行直接的讀取和寫入,要讀取和寫入首先需要確定屬性所在的位置,也就是相對對象起始位置的偏移量,而靜態屬性是針對類的不是每個對象實例一份,所以靜態屬性的偏移量需要單獨獲取。
  • Java基礎知識中的類
    Java類的內容一、類的定義和聲明Java程式語言是面向對象的,處理的最小的完整單元為對象。而現實生活中具有共同特性的對象的抽象就稱之為類。類由類聲明和類體構成,類體又由變量和方法構成。下面給出一個例子來看一下類的構成。
  • 深入理解Django時區及naive datetime object和aware datetime object的區別
    如果你要在Django視圖和模板中正確使用DateTime類型的數據,除了要閱讀Django基礎(32):按日期與時間欄位查詢數據及模板中日期時間類型數據的格式化及比較,還必須要對timezone非常了解,才能避免如RuntimeWarning: DateTimeField Article.pub_date received a naive datetime 這種新手必犯的錯誤。
  • 別翻了,這篇文章絕對讓你深刻理解java類的加載以及ClassLoader源碼分析
    點進文章的盆友不如先來做一道非常常見的面試題,如果你能做出來,可能你早已掌握並理解了java的類加載機制,若結果出乎你的意料,那就很有必要來了解了解java的類加載機制了。這篇文章將通過對Java類加載機制的講解,讓各位熟練理解java類的加載機制。
  • 類(class)及對象(object)間的關聯
    大家好,從這講開始我們深入地講解OOP中對象的相關內容,當然主要是針對類模塊的內容進行講解。對於OOP的概念,我們並不陌生,是Object Oriented Programming的一種簡稱,翻譯過來就是面向對象編程或者面向對象的程序設計,這種設計基本原則是電腦程式由單個能夠起到子程序作用的單元或對象組合而成。是以對象為核心,程序由一系列對象組成。
  • java並發編程-原子類
    而 java.util.concurrent.atomic 下的類,就是具有原子性的類,可以原子性地執行添加、遞增、遞減等操作。比如之前多線程下的線程不安全的 i++ 問題,到了原子類這裡,就可以用功能相同且線程安全的 getAndIncrement 方法來優雅地解決。
  • Java 基礎與提高幹貨系列—Java 反射機制
    正文Java反射機制定義Java反射機制是指在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。用一句話總結就是反射可以實現在運行時可以知道任意一個類的屬性和方法。
  • 我如何理解Java中抽象類和接口
    不賣關子,我個人對這兩個的理解:類是具體實例的抽象,比如一個json字符串的抽象;而抽象類就是類的抽象;接口就是抽象類的抽象,接口更像是一種協議  聽我慢慢道來~ 吐槽  首先,我必須吐槽一下這種面試,我認為面試官凡事問出這種類似「說說抽象類和接口的區別」,「說說進程和線程的區別」等等問題,都是不負責的表現。  為什麼呢?
  • 關於Java中Runtime.class.getClass()的細節分析
    其中一個比較詭異的邏輯Runtime.class.getClass(),有朋友問它的結果為什麼是java.lang.Class。對於這個問題,有Java語言基礎的同學一般會回答『對象的類型本來就是Class,而Class也是對象,它的類型當然也是Class』,道理沒錯,但仔細想想,這還真是一個挺有意思的問題。
  • 再來說說Java嵌套類(nested class)
    其實我們使用的內部類是嵌套類(nested class)的一種,而nested class 共有四種:static nested class 靜態嵌套類inner class 內部類(非靜態)local class 本地類(定義在方法內部)anonymous class 匿名類  靜態嵌套類的行為更接近普通的類,另外三個是真正的內部類。區別在於作用域的不同。
  • 深入分析Java虛擬機堆和棧及OutOfMemory異常產生原因
    前言JVM系列文章如無特殊說明,一些特性均是基於Hot Spot虛擬機和JDK1.8版本講述。Java程序執行流程從我們寫好的.java文件到最終在JVM上運行時,大致是如下一個流程:一個java類在經過編譯和類加載機制之後,會將加載後得到的數據放到運行時數據區內,這樣我們在運行程序的時候直接從JVM內存中讀取對應信息就可以了。
  • python基礎知識 ——類
    說到類,就不得不提面向過程編程和面向對象編程。下面用兩個例子說明這兩種編程方式在編碼上的區別,如圖5-3-9和圖5-3-10所示。▲圖5-3-10 案例2(1) 在面向對象編程中最重要的概念是類(Class) 和實例(Instance) 。(2) 類是抽象的模板,可以將其理解為一個基礎模板,比如Student類。