剖析Java中的事件處理與異常處理機(jī)制
一、事件處理
其實,由事件處理這個名字自然就想到MFC中的消息響應(yīng)機(jī)制,就我的體會,它們應(yīng)該算是南桔北枳的情形吧,我懷疑Java中的事件處理這個"新瓶"應(yīng)是裝的MFC中的消息響應(yīng)這個"舊酒"。
所謂的"事件"即如鍵盤按鍵、鼠標(biāo)點擊等這類由動作或什么導(dǎo)致某個狀態(tài)改變并需要對這個改變作相應(yīng)響應(yīng)的這類改變。我們可以將Java中的事件分為按鈕、鼠標(biāo)、鍵盤、窗口、其它事件這幾大類。
事件處理模型
1. 基于繼承的事件處理模型(JDK1.0)
JDK1.0中,事件處理是基于繼承的,事件先發(fā)送到組件,然后沿容器層次向上傳播。沒有被組件處理的事件會自動地繼續(xù)傳播到組件的容器。——這個與MFC中的原始事件響應(yīng)順序或者多態(tài)的響應(yīng)機(jī)制似乎一致,而后面說的基于代理的事件處理機(jī)制似乎又與MFC的回調(diào)機(jī)制一致。
具體的處理方法
調(diào)用action()方法或handleEvent()方法來獲取程序運行時發(fā)生的事件,所有組件發(fā)生的事件都在此方法中處理。
2、基于代理的事件處理模型(JDK1。1)
在這個模型中,事件被直接送往產(chǎn)生這個事件的組件,
對于每一個組件注冊一個或多個稱為監(jiān)聽者的類,這些類包含事件處理器,用來接收和處理這個事件。
監(jiān)聽者就是實現(xiàn)了Listener接口的類。事件是只向注冊的監(jiān)聽者報告的對象。每個事件都有一個對應(yīng)的監(jiān)聽者接口。
在Button對象上用鼠標(biāo)進(jìn)行點擊時,將發(fā)送一個ActionEvent事件。這個ActionEvent事件會被使用addActionListener()方法進(jìn)行注冊的所有ActionListener的actionPerformed()方法接收。
基于代理的事件處理模型的特點
①事件不會被意外地處理。在層次模型中,一個事件可能傳播到容器,并在非預(yù)期的層次被處理。
②有可能創(chuàng)建并使用適配器(adapter)類對事件動作進(jìn)行分類。
③有利于把工作分布到各個類中。
重點學(xué)習(xí)這種事件處理模型
3、事件
事件處理的三要素。
(1)事件源 事件源是一個事件的產(chǎn)生者,如按鈕、窗口及文本域等。
(2)事件類型 Java中所有的事件都封裝成一個類,這些事件類被集中在java.awt.event包,所有的事件類均繼承了AWTEvent類和一個方法——getSouce()方法,該方法返回發(fā)生事件的對象。
(3)事件監(jiān)聽器 不同的類型事件發(fā)生后,由事件監(jiān)聽器接收事件并調(diào)用相應(yīng)的事件處理方法。所有的事件監(jiān)聽器實際上都是一個java.awt.event包中的接口,引入了java.util.EventListener接口。不同事件類型的監(jiān)聽器具有不同的方法。
事件處理步驟
① 程序加入java.awt.event包:
Import java.awt.event;
② 給所需的事件源對象注冊事件監(jiān)聽器:
事件源對象.addXXXListener(XXXListener);
③ 實現(xiàn)相應(yīng)的方法。如果某個監(jiān)聽器接口包含多個方法,則需要實現(xiàn)所有的方法
例:b2.addActionListener(this)
4、事件Adapters(適配器)
實現(xiàn)每個Listener接口的所有方法的工作量是非常大的,為了方便起見,Java語言提供了Adapters類,用來實現(xiàn)含有多個方法的類。
你定義的Listener可以繼承Adapter類,而且只需重寫你所需要的方法。
例如,窗口事件對應(yīng)的監(jiān)聽器為WindowListener,它必須實現(xiàn)多個方法,包括windowOpened()、windowClosed()、windowClosing()、WindowIconfied()、WindowDeiconfied()、WindowActivated()、WindowDeactivated(),這樣加大了不必要的編程工作量。
如果繼承了WindowAdapter,就只需實現(xiàn)其中某一個或幾個方法,不需要實現(xiàn)所有的方法。后面很多的例子都只實現(xiàn)windowClosing()一個方法,目的是在關(guān)閉窗口時退出系統(tǒng)。
4.1 按鈕事件的處理
點擊按鈕所發(fā)生的事件為動作事件
動作事件對應(yīng)的事件類是ActionEvent類
動作事件對應(yīng)的事件監(jiān)聽器為: ActionListener
監(jiān)聽器的主要方法:
actionPerformed(ActionEvent e)發(fā)生動作事件時被調(diào)用
實現(xiàn)動作事件的操作工程:
第一步,注冊動作事件監(jiān)聽器addActionListener(ActionListener)。
第二步,實現(xiàn)ActionListener接口的方法:actionPerformed(ActionEvent e)
4.2鼠標(biāo)事件的處理
觸發(fā)鼠標(biāo)事件的事件源通常是一個容器,當(dāng)鼠標(biāo)進(jìn)入、離開容器,或者在容器中單擊鼠標(biāo)、拖動鼠標(biāo)等操作時,都發(fā)生鼠標(biāo)事件
鼠標(biāo)事件對應(yīng)的事件類是MouseEvent類
MouseEvent類中方法:
getX()獲取鼠標(biāo)的X坐標(biāo)
getY()獲取鼠標(biāo)的Y坐標(biāo)
getPoint()獲取鼠標(biāo)的位置
鼠標(biāo)事件對應(yīng)的事件監(jiān)聽器有兩個:MouseListener(或者M(jìn)ouseAdapter)對應(yīng)鼠標(biāo)事件,MouseMotionListener(或者M(jìn)ouseMotionAdapter)對應(yīng)鼠標(biāo)移動事件。
MouseListener(或者M(jìn)ouseAdapter)的主要方法
MousePressed(MouseEvent e)鼠標(biāo)按下時的處理方法
MouseReleased(MouseEvent e)鼠標(biāo)釋放時的處理方法
MouseEntered(MouseEvent e)鼠標(biāo)進(jìn)入時的處理方法
MouseExited(MouseEvent e)鼠標(biāo)離開時的處理方法
MouseClicked(MouseEvent e)鼠標(biāo)點擊時的處理方法
MouseMotionListener(或者M(jìn)ouseMotionAdapter)的主要方法
MouseMoved(MouseEvent e)鼠標(biāo)移動時的處理方法
MouseDraged(MouseEvent e)鼠標(biāo)拖動時的處理方法
4.3 鍵盤事件的處理
在具有鍵盤焦點的組件中按下或釋放鍵盤等操作時,都發(fā)生鍵盤事件
鍵盤事件對應(yīng)的事件類是KeyEvent類
KeyEvent類主要方法:
getKeyCode()獲得按下或釋放的鍵代碼
getKeyText()獲得按下或釋放的鍵的字符串
鍵盤事件對應(yīng)的事件監(jiān)聽器為:KeyListener或KeyAdapter
主要方法:
KeyPressed(KeyEvent e)按下鍵盤時的處理方法
KeyReleased(KeyEvent e)釋放鍵盤時的處理方法
4.4 窗口事件的處理
有Window及其擴(kuò)展類(Frame、Dialog)等才能激發(fā)窗口事件,表示窗口處于激活/無效狀態(tài)、圖標(biāo)/非圖標(biāo)狀態(tài)或打開/關(guān)閉狀態(tài)等
窗口事件對應(yīng)的類為WindowEvent,監(jiān)聽器為WindowListener(或WindowAdapter)
主要方法:
windowOpened(WindowEvent e)打開窗口的事件處理
windowClosed(WindowEvent e)關(guān)閉窗口的事件處理
windowClosing(WindowEvent e)正在關(guān)閉窗口的事件處理
WindowActivated(WindowEvent e)激活狀態(tài)的事件處理
WindowDeactivated(WindowEvent e)無效狀態(tài)的事件處理
4.5 其它事件的處理
4.5.1 復(fù)選框、單選鈕事件處理
事件類是ItemEvent :
選項事件對應(yīng)的事件監(jiān)聽器為: ItemListener
方法:
itemStateChanged (ItemEvent e)發(fā)生選項事件時被調(diào)用
4.5.2 滾動條事件處理
調(diào)整事件對應(yīng)的事件類是AdjustmentEvent類 :
調(diào)整事件對應(yīng)的事件監(jiān)聽器為: AdjustmentListener
方法:
adjustmentValueChanged (AdjustmentEvent e)發(fā)生調(diào)整事件時被調(diào)用
4.5.3 下拉列表的事件處理
事件類是ItemEvent :
選項事件對應(yīng)的事件監(jiān)聽器為: ItemListener
方法:
itemStateChanged (ItemEvent e)生下拉列表發(fā)生了動作時被調(diào)用
(可見,下拉列表的事件處理與事件類型、事件監(jiān)聽器及方法與復(fù)選框、單選框的事件處理的事件類型、事件監(jiān)聽器及方法一樣)
4.5.4 菜單事件的處理
菜單事件一般是當(dāng)我們點擊某個菜單項時所發(fā)生的事件。
菜單項有兩種:
MenuItem 動作事件
CheckboxMenuItem,選項事件
MenuItem的事件處理
第一步,給所有的MenuItem菜單項注冊動作事件監(jiān)聽器addActionListener(ActionListener)。
第二步,實現(xiàn)ActionListener接口的方法:actionPerformed(ActionEvent e)。在該方法中用e.getSource()獲取用戶所選的菜單項,并進(jìn)行相應(yīng)的處理。
CheckboxMenuItem的事件處理
第一步,給所有的CheckMenuItem菜單項注冊選項事件監(jiān)聽器addItemListener(ItemListener)。
第二步,實現(xiàn)ItemListener接口的方法:itemStateChanged(ItemEvent e)。在該方法中用e.getSource()獲取用戶所選的菜單項,e.getItem()獲取用戶所選的菜單項的標(biāo)簽,e.getStateChange()獲取是否選中,并進(jìn)行相應(yīng)的處理。
二、異常處理
任何好的編程語言和編程人員都不會忽視對異常的處理,作為比較熱門的面向?qū)ο缶幊痰恼Z言——Java,異常處理機(jī)制自然也是其重要特色之一。
一般解釋異常,都將其說為:編程中的錯誤。但是,實際上這個錯誤可是非常頻繁,有多種,如:編譯錯誤、運行錯誤(具體上又分為:系統(tǒng)運行錯誤和邏輯運行錯誤,這個什么系統(tǒng)運行錯誤,自己倒很少將其算作是編程中的錯誤了,之前。)
在JAVA中,異常是一個類,它繼承自Throwable類。每個異常類代表了運行錯誤(注意:是運行錯誤)。異常類中包含了該運行錯誤的信息及處理錯誤的方法等內(nèi)容。
Java的異常處理機(jī)制:
每當(dāng)Java程序運行過程中發(fā)生一個可識別的運行錯誤時,(即該錯誤有一個異常類與之相對應(yīng)時),系統(tǒng)都會產(chǎn)生一個相應(yīng)的該異常類的對象,(注意:叫做產(chǎn)生一個異常類對象。)即產(chǎn)生一個異常。
一旦一個異常對象產(chǎn)生了,系統(tǒng)中就一定要有相應(yīng)的機(jī)制來處理它,確保不會產(chǎn)生死機(jī)、死循環(huán)或其他對操作系統(tǒng)的損害,從而保證了整個程序運行的安全性
異常和異常類:
Error:由Java虛擬機(jī)生成并拋出,Java程序不做處理.
Runtime Exception(被0除等系統(tǒng)錯誤,數(shù)組下標(biāo)超范圍):由系統(tǒng)檢測, 用戶的Java 程序可不做處理,系統(tǒng)將它們交給缺省的異常處理程序(注意:有缺省的異常處理).
Exception(程序中的問題,可預(yù)知的): Java編譯器要求Java程序必須捕獲或聲明所有的非運行時異常
用戶自己產(chǎn)生異常
Exception類
構(gòu)造函數(shù):
public Exception();
public Exception(String s);可以接受字符串參數(shù)傳入的信息,該信息通常是對該異常所對應(yīng)的錯誤的描述。
Exception類從父親Throwable那里還繼承了若干方法,其中常用的有:
1)public String toString();
toString()方法返回描述當(dāng)前Exception 類信息的字符串。
2)public void printStackTrace();
printStackTrace()方法沒有返回值,它的功能是完成一個打印操作,在當(dāng)前的標(biāo)準(zhǔn)輸出(一般就是屏幕)上打印輸出當(dāng)前例外對象的堆棧使用軌跡,也即程序先后調(diào)用執(zhí)行了哪些對象或類的哪些方法,使得運行過程中產(chǎn)生了這個例外對象。
系統(tǒng)定義的運行異常
這些子類有些是系統(tǒng)事先定義好并包含在Java類庫中的,稱為系統(tǒng)定義的運行異常
用戶自定義的異常
對于某個應(yīng)用所特有的運行錯誤,則需要編程人員根據(jù)程序的特殊邏輯在用戶程序里自己創(chuàng)建用戶自定義的異常類和異常對象
用戶定義的異常通常采用Exception作為異常類的父類
但是這里有一個還未弄懂的問題:發(fā)生一個錯誤,系統(tǒng)怎么知道是可識別的?又是怎么產(chǎn)生相應(yīng)異常類對象?異常類對象怎么就知道去用相應(yīng)方法解決?每個處理相應(yīng)異常的異常類對象難道就只有一個異常處理方法?————————————原來,由用戶自定義的異常,是通過語句throw才拋出異常。
創(chuàng)建用戶自定義異常時,一般需要完成如下的工作:
1)聲明一個新的異常類,使之以Exception類或其他某個已經(jīng)存在的系統(tǒng)異常類或用戶異常為父類。
2)為新的異常類定義屬性和方法,或重載父類的屬性和方法,使這些屬性和方法能夠體現(xiàn)該類所對應(yīng)的錯誤的信息。
異常的拋出
Java程序在運行時如果引發(fā)了一個可以識別的錯誤,就會產(chǎn)生一個與該錯誤相對應(yīng)的異常類的對象,把這個過程叫做異常的拋出,
實際是相應(yīng)異常類對象的實例的拋出。
根據(jù)異常類的不同,拋出異常的方式有系統(tǒng)自動拋出和用戶拋出兩種:
1、系統(tǒng)自動拋出
所用的系統(tǒng)定義的運行錯誤異常都是由系統(tǒng)自動地拋出
2、用戶拋出
用戶自定義的異常不可能依靠系統(tǒng)自動拋出,而必須由用戶用Java語句拋出,在Java語句中,throw語句用來明確地拋出一個“異?!?
用throw語句拋出的格式
返回類型 方法名(參數(shù)列表) throws 要拋出的異常類名列表{
…
throw 異常類實例;//注意這里
…
}
注意:
1)一般當(dāng)程序中滿足某個條件時才拋出異常;
往往把throw語句放在if語句的if分支中,
if(I>100)
throw (new MyException());
2)含有throw的語句的方法,應(yīng)當(dāng)在方法頭定義中增加如下的部分:
throws 要拋出的異常類名列表
這樣做主要是為了通知欲調(diào)用這個方法的上層方法,準(zhǔn)備接受和處理它在運行中可能會拋出的異常
如果方法中的throw語句不止一個,則應(yīng)該在方法頭throws中列出所有可能的異常
3)Java語言要求所有用throws關(guān)鍵字聲明的類和用throw拋出的對象必須是Throwable類或其子類。如果你試圖拋出一個不是可拋出(Throwable)對象,Java編譯器將會報錯
異常處理:
主要考慮如何捕捉異常,捕捉異常后程序如何跳轉(zhuǎn),以及如何寫異常處理語句
1.try…catch…finally 塊
1)try
在try語句的{ }中包含了可能會拋出一個或多個異常的一段程序代碼
這些代碼實際上指定了它后面的catch塊所能捕捉的異常的范圍。
Java程序運行到try塊中的語句時如果產(chǎn)生了異常,就不再繼續(xù)執(zhí)行該try塊中其他的語句,而是直接進(jìn)入catch塊中尋找第一個與之匹配的異常類型并進(jìn)行處理。
2)catch塊
catch語句的參數(shù)類似于方法的定義,包括一個異常類型和一個異常對象。
異常類型必須為Throwable類的子類,它指明了catch語句所處理的異常類型;
異常對象則由Java運行時系統(tǒng)在try所指定的程序代碼塊中拋出的大括號中包含異常對象的處理的方法代碼。
catch語句可以有多個,分別處理不同類型的異常。
Java運行時系統(tǒng)從上到下分別對每個catch語句處理的異常類型進(jìn)行檢測,直到找到與之相匹配的catch語句為止。
這里,類型匹配指catch中的異常類型與生成的異常對象的類型完全一致或者是異常對象的父類,因此,catch語句的排序順序應(yīng)該是從特殊到一般。(考慮為什么?)
3)finally塊
finally語句可以說是為異常處理事件提供的一個清理機(jī)制,一般用來關(guān)閉文件或釋放其他系統(tǒng)資源
在try-catch-finally語句中可以沒有finally部分的語句。
如果沒有finally部分,則當(dāng)try指定的程序代碼拋出一個異常時,其他的程序代碼就不會被執(zhí)行;
如果存在finally部分,則不論try塊中是否發(fā)生了異常,是否執(zhí)行過catch部分的語句,都要執(zhí)行finally部分的語句。
可見,finally部分的語句為異常處理提供了一個統(tǒng)一的出口。
多異常處理
一個try塊可能會產(chǎn)生多種不同的異常,如果希望能采取不同的方法來處理這些例外,就需要使用多異常處理機(jī)制。
多異常處理是通過在一個try塊后面定義若干個catch塊來實現(xiàn)的,每個catch塊用來接收和處理一種特定的異常對象
通過catch塊的參數(shù)來判斷一個異常對象是否應(yīng)為本catch塊接收和處理的異常。
被哪個catch塊獲取,根據(jù)異常對象與catch塊的異常參數(shù)的匹配情況:當(dāng)它們滿足下面三個條件的任何一個時,認(rèn)為異常對象和參數(shù)匹配:
1)異常對象與參數(shù)屬于相同的例外類。
2)異常對象屬于參數(shù)例外類的子類。
3)異常對象實現(xiàn)了參數(shù)所定義的接口。
如果try塊產(chǎn)生的異常對象被第一個catch塊所接收,則程序的流程將直接跳轉(zhuǎn)到這個catch語句塊中,語句塊執(zhí)行完后就退出當(dāng)前方法,try塊中尚未執(zhí)行的語句和其他的catch塊將被忽略
如果try塊產(chǎn)生的異常對象與第一個catch塊不匹配,系統(tǒng)將自動轉(zhuǎn)到第二個catch塊進(jìn)行匹配,如果第二個仍不匹配,就轉(zhuǎn)向第三個、第四個……直到找到一個可以接收該異常對象的catch塊,完成流程的跳轉(zhuǎn)。
如果try塊產(chǎn)生的異常對象被第一個catch塊所接收,則程序的流程將直接跳轉(zhuǎn)到這個catch語句塊中,語句塊執(zhí)行完后就退出當(dāng)前方法,try塊中尚未執(zhí)行的語句和其他的catch塊將被忽略
如果try塊產(chǎn)生的異常對象與第一個catch塊不匹配,系統(tǒng)將自動轉(zhuǎn)到第二個catch塊進(jìn)行匹配,如果第二個仍不匹配,就轉(zhuǎn)向第三個、第四個……直到找到一個可以接收該異常對象的catch塊,完成流程的跳轉(zhuǎn)。
若try塊中所有語句的執(zhí)行都沒有引發(fā)異常,則所有的catch塊都會被忽略而不執(zhí)行。
注意:
1)catch塊中的語句應(yīng)根據(jù)異常的不同執(zhí)行不同的操作
所以在處理多異常時應(yīng)注意認(rèn)真設(shè)計各catch塊的排列順序。一般地處理較具體和較常見的異常的catch塊應(yīng)放在前面,而可以與多種異常相匹配的catch塊應(yīng)放在較后的位置。
/*嘗試用戶運行錯誤的異常處理: 問題是這樣的,當(dāng)輸入的用戶工資初值少于800則是錯誤的,當(dāng)然工資的變化如果超過20%,則也是錯誤的 */ import java.awt.*; import java.applet.*; import java.awt.event.*; public class UserExceptionApplet extends Applet implements ActionListener{ Label prompt1=new Label("請輸入雇員姓名和工資初值:"); Label prompt2=new Label("請輸入欲修改的工資"); TextField name,isal,nsal; String msg; Employee Emp; Button okBtn=new Button("OK"); Button cancelBtn=new Button("Cancel"); public void init(){ name=new TextField(5); isal=new TextField(5); nsal=new TextField(5); add(prompt1); add(name); add(isal); add(prompt2); add(nsal); add(okBtn); okBtn.addActionListener(this); cancelBtn.addActionListener(this); add(cancelBtn); } public void paint(Graphics g){ g.drawString(msg,0,80); } public void CreateEmp(String empName,double sa){ try{ Emp=new Employee(empName,sa); msg=new String(Emp.toString()); } catch(IllegalSalaryException ise){ msg=new String(ise.toString()); } } public void ChangeEmpSal(double changeSal){ try{ Emp.setEmpSalary(changeSal); msg=new String(Emp.toString()); } catch(IllegalSalaryException illSal){ msg=new String(illSal.toString()); } catch(IllegalSalaryChangeException illSalChange){ msg=new String(Emp.toString()+illSalChange.toString()); } } public void actionPerformed(ActionEvent e){ String empName; double empSal,changeSal; Object obj=e.getSource(); if(obj==okBtn){ empName=new String(name.getText()); if(empName==null){ msg=new String("請先輸入雇員姓名工資并創(chuàng)建之"); } if(nsal.getText()==null){ empSal=Double.valueOf(isal.getText()).doubleValue(); CreateEmp(empName,empSal); } else{ changeSal=Double.valueOf(nsal.getText()).doubleValue(); ChangeEmpSal(changeSal); } } if(obj==cancelBtn){ naem.setText(""); isal.setText(""); nsal.setText(""); } repaint(); } } class Employee{ String m_EmpName; double m_EmpSalary; Employee(String name,double initsalary)throws IllegalSalaryException{ m_EmpName=name;//看這里有問題沒,參考代碼為m_EmpName=new String(name); if(initsalary<800){ throw(new IllegalSalaryException(this,initsalary));//throw語句 } m_EmpSalary=initsalary; } public String getEmpName(){ return m_EmpName; } public double getEmpSalary(){ return m_EmpSalary; } public boolean setEmpSalary(double newSal) throws IllegalSalaryException,IllegalSalaryChangeException{ if(newSal<800) throw(new IllegalSalaryException(this,newSal)); else if(getEmpSalary()==0.0){ m_EmpSalary=newSal; return true; } else if(Math.abs(newSal-getEmpSalary())/getEmpSalary()>=0.2) throw(new IllegalSalaryChangeException(this,newSal-getEmpSalary())); else{ m_EmpSalary=newSal; return true; } } public String toString(){ String s; s="姓名:"+m_EmpName+"工資: "+m_EmpSalary; return s; } } class IllegalSalaryException extends Exception{ private Employee m_ConcernedEmp; private double m_IllegalSalary; IllegalSalaryException(Employee emp,double isal){ super("工資低于最低工資"); m_ConcernedEmp=emp; m_IllegalSalary=isal; } public String toString(){ String s; s="為雇員提供的工資不合法:雇員:"+m_ConcernedEmp.getEmpName()+"非法工資:"+m_IllegalSalary+"低于最低工資數(shù)額800元"; return s; } } class IllegalSalaryChangeException extends Exception{ private Employee m_ConcernedEmp; private double m_IllegalSalaryChange; IllegalSalaryChangeException(Employee emp,double csal){ super("工資變動太大"); m_ConcernedEmp=emp; m_IllegalSalaryChange=csal; } public String toString(){ String s; s="為雇員提供的工資變動不合法:雇員:"+m_ConcernedEmp.getEmpName()+"非法變動工資變化:"+m_IllegalSalaryChange+"高于原工資的20%"; return s; } }
相關(guān)文章
SpringData @Query和@Modifying注解原理解析
這篇文章主要介紹了SpringData @Query和@Modifying注解原理解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08SpringBoot統(tǒng)一數(shù)據(jù)返回的幾種方式
在Web應(yīng)用程序開發(fā)中,統(tǒng)一數(shù)據(jù)返回格式對于前后端分離項目尤為重要,本文就來介紹一下SpringBoot統(tǒng)一數(shù)據(jù)返回的幾種方式,具有一定的參考價值,感興趣的可以了解一下2024-07-07