正則表達式
正則表達式驗證網(wǎng)站
限定符
1、?
:表示前邊這個字符可以出現(xiàn)0次或者1次。例如下邊/used?
既可以匹配use
也可以匹配used
。
2、*
:匹配0個或者多個字符,*
號代表前邊這個字符可以出現(xiàn)0次或者多次。例如/ab*c
可以匹配ac、abc、abbbbc
3、+
:與*
號不同的是,+
需要前面這個字符出現(xiàn)1次或者多次。當(dāng)使用+
號時,/ab+c
就無法匹配ac
了,因為b至少的出現(xiàn)一次。
4、如果想要實現(xiàn)對字符出現(xiàn)次數(shù)更加精確的限定,可以使用花括號+數(shù)字,指定。
例如:
-
/ab{2,}c
:b出現(xiàn)2次以及以上 -
/ab{2,6}c
:b出現(xiàn)[2,6]次 -
/ab{6}c
:b出現(xiàn)6次
5、如果要匹配多個字符的重復(fù)出現(xiàn)次數(shù),可以使用小括號()
將字符擴起來。
例如/(ab)+
表示前面這個ab出現(xiàn)1次或者多次。
運算符與字符類
1、或運算符|
2、字符類。[abc]+
:由abc組成的字符
-
:按照字母順序的可以使用-
。
3、非運算符^
:除了列出字符之外的。
例如:/[^0-9]+
:非數(shù)字字符。
元字符
\d
:digit,數(shù)字字符,等同于[0-9]\D
:非數(shù)字字符\w
:word,單詞字符,英文、數(shù)字及其下劃線。\W
:非單詞字符\s
:space,空表字符,包含tab和換行符。\S
:非空白字符.
:代表任意字符,但不包含換行符^
:匹配行首的字符:例如/^a
,只匹配行首的a$
:匹配行尾的字符:例如/a$
,只匹配行尾的a
貪婪匹配與懶惰匹配
默認(rèn)的匹配規(guī)則,是貪婪匹配,即盡可能地匹配多個字符。
使用?
切換貪婪匹配為懶惰匹配。
案例
1、匹配16進制顏色rgb值。
- 首先都以
#
開頭。 - 16進制,因此,字符從0-9,a-f組成。
- 字符出現(xiàn)次數(shù)為6次,使用花括號,指定6次。
- 使用單詞結(jié)尾字符
\b
表示結(jié)束。
2、ipv4地址匹配。
三個點,與四個數(shù)字,數(shù)字范圍為[0,255],可以把點與數(shù)字放一塊,分為三個數(shù)字+點,和一個數(shù)字。
數(shù)字范圍分情況討論,3位數(shù),2位數(shù),1位數(shù)。
線程并發(fā)
多線程
多線程的創(chuàng)建
- 方法一:繼承Thread類,創(chuàng)建步驟:
- 定義子類MyThread繼承Thread,重寫run()方法
- 創(chuàng)建MyThread類對象
- 調(diào)用線程對象的start()方法啟動線程(啟動后執(zhí)行的是run()方法)
這種創(chuàng)建方式因為已經(jīng)繼承了Thread類,無法繼承其他類,不利于拓展。
- 方法二:聲明一個實現(xiàn)Runnable接口的類。
- 定義定義一個線程任務(wù)類MyRunnable實現(xiàn)Runnable接口,重寫run()方法。
- 創(chuàng)建MyRunnable對象
- 把MyRunnable對象交給Thread處理
- 調(diào)用Thread對象的start方法啟動
方法二只實現(xiàn)接口,可以繼續(xù)繼承類和實現(xiàn)接口,擴展性更強。但缺點是編程多一層包裝(runnable對象還需要再傳給thread構(gòu)造thread對象)
方法一實現(xiàn):
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; ++i) {
System.out.println("子線程執(zhí)行輸出:" + i);
}
}
}
Thread t1 = new MyThread();
t1.start();
方法二實現(xiàn):
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
});
for (int i = 0; i < 10; i++) {
System.out.println("主線程:" + i);
}
當(dāng)然,可以用lambda表達式簡寫。
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println( "子線程" + i);
}
});
for (int i = 0; i < 10; i++) {
System.out.println("主線程:" + i);
}
- 方式3
前兩種創(chuàng)建方式都存在一個問題:- 他們重寫的run()方法均不能直接返回結(jié)果
- 不適合需要返回線程執(zhí)行結(jié)果的業(yè)務(wù)場景
jdk5利用Callable、FutureTask接口實現(xiàn)上述功能。
創(chuàng)建步驟:
- 定義類實現(xiàn)Callable接口,重寫call方法,封裝要做的事情。
- 用FutureTask把Callable對象封裝成線程任務(wù)對象。
- 把線程任務(wù)對象交給Thread處理
- 調(diào)用Thread的start方法啟動線程,執(zhí)行任務(wù)。
- 線程執(zhí)行完畢后,通過FutureTask的get()方法去獲取任務(wù)執(zhí)行的結(jié)果。
方法三的實現(xiàn):
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo3 {
public static void main(String[] args) {
Callable<String> call1 = new MyCallable(100000);
FutureTask<String> f1 = new FutureTask<>(call1);
Thread t1 = new Thread(f1);
t1.start();
Callable<String> call2 = new MyCallable(100000);
FutureTask<String> f2 = new FutureTask<>(call2);
Thread t2 = new Thread(f2);
t2.start();
try {
// 如果f1沒有執(zhí)行完畢,那么get這里會等待,直至完成
String r1 = f1.get();
System.out.println("第一個結(jié)果:" + r1);
} catch (Exception e) {
e.printStackTrace();
}
// 如果f2沒有執(zhí)行完畢,那么get這里會等待,直至完成
try {
String r2 = f2.get();
System.out.println("第二個結(jié)果:" + r2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<String> {
private int n;
public MyCallable (int n) {
this.n = n;
}
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i <= n; i++) {
sum += i;
}
return "子線程執(zhí)行的結(jié)果是:" + sum;
}
}
Thread常用API
線程同步與通信
當(dāng)多線程訪問共享資源時,可能出現(xiàn)線程安全問題。解決線程安全問題的方法有線程同步和線程通信。
線程同步:單例模式的三種寫法
線程同步的思想是對共享資源加鎖,將多個線程實現(xiàn)先后依次訪問共享資源,這樣就解決了線程安全問題。
線程同步的方式:
- 同步代碼塊
- 同步方法
- Lock鎖
同步代碼塊
- 作用:把出現(xiàn)線程安全問題的核心代碼給上鎖。
- 原理:每次只能一個線程進入,執(zhí)行完畢后自動解鎖,其他線程才可以進來執(zhí)行。
synchronized(同步鎖對象){
操作共享資源的代碼
}
鎖對象規(guī)范:
- 建議使用共享資源作為鎖對象
- 對于實例方法建議使用this作為鎖對象。
- 對于靜態(tài)方法建議使用字節(jié)碼(類名.class)對象作為鎖對象。
同步代碼塊實現(xiàn)的懶漢單例模式:
核心:私有化構(gòu)造函數(shù),靜態(tài)化單例成員,在獲取單例的靜態(tài)方法中,如果檢測到實例未創(chuàng)建,使用synchronized
構(gòu)建同步代碼塊,因為是靜態(tài)方法,所以使用類的字節(jié)碼(類名.class)對象作為synchronized
的鎖對象。
class SingleInstance {
private SingleInstance() {
}
private static SingleInstance singleInstance;
public static SingleInstance getInstance() {
if (singleInstance == null) {
synchronized (SingleInstance.class) {
if (singleInstance == null) {
singleInstance = new SingleInstance();
}
}
}
return singleInstance;
}
}
同步方法
基于同步方法實現(xiàn)的懶漢單例:
核心:私有化構(gòu)造、靜態(tài)化單例對象
獲取實例的靜態(tài)方法加synchronized
修飾。
class Single {
private Single(){
}
private static Single single;
public static synchronized Single GetInstance() {
if (single == null) {
single = new Single();
}
return single;
}
}
同步方法的底層其實是隱式鎖對象,只是鎖的范圍是整個方法代碼,如果方法是實例方法,同步方法默認(rèn)用this
作為鎖對象。
從性能上講,同步代碼塊鎖的范圍更小,性能更高。
java中靜態(tài)內(nèi)部類不會自動初始化,只有在調(diào)用靜態(tài)內(nèi)部類的方法時才會加載靜態(tài)內(nèi)部類。
利用這種靜態(tài)內(nèi)部類,我們可以實現(xiàn)一個比使用同步代碼塊和同步方法,性能上更加優(yōu)秀的懶漢單例。
核心:私有化構(gòu)造函數(shù),構(gòu)建靜態(tài)內(nèi)部類,類中成員初始化時調(diào)用構(gòu)造函數(shù),使用static決定它的全局性 ,使用final,決定它只會初始化一次。在獲取單例的方法中,返回內(nèi)部類的成員。
class SingleByInner{
private SingleByInner() {
}
static class Inner {
private static final SingleByInner INSTANCE = new SingleByInner();
}
public static SingleByInner getInstance() {
return Inner.INSTANCE;
}
}
Lock鎖
- 為了更加清晰的表達如何加鎖和釋放鎖,JDK5之后提供了一個新的鎖對象Lock,更加靈活,方便。
- Lock實現(xiàn)提供比使用synchronize方法和語句可以獲得更廣泛的鎖定操作。
- Lock是接口不能直接實例化,這里采用它的實現(xiàn)類ReetrantLock來構(gòu)建Lock鎖對象。
線程通信
什么是線程通信、如何實現(xiàn)?
- 所謂線程通信就是線程間相互發(fā)送數(shù)據(jù),線程通信通常通過共享一個數(shù)據(jù)的方式實現(xiàn)。
- 線程間會根據(jù)共享數(shù)據(jù)的情況決定自己該怎么做,以及通知其他線程怎么做。
線程通信常見模型
- 生產(chǎn)者與消費者模式:生產(chǎn)者線程負(fù)責(zé)生成數(shù)據(jù),消費者線程負(fù)責(zé)消費數(shù)據(jù)。
- 要求:生產(chǎn)者生產(chǎn)完數(shù)據(jù)后,喚醒消費者,然后等待自己;消費者消費完數(shù)據(jù)后,喚醒生產(chǎn)者,然后等待自己。
線程通信的關(guān)鍵是object類的等待和喚醒方法上述所有方法應(yīng)該使用當(dāng)前同步鎖對象進行調(diào)用。
this.notifyAll(); // 喚醒所有線程
this.wait(); // 鎖對象,讓當(dāng)前線程進入等待。
經(jīng)典面試題:sleep()和wait()的區(qū)別是什么?
1、來自不同類:sleep()來著Thread,wait()來自O(shè)bject。
2、sleep()不會釋放鎖,wait()會釋放鎖。
3、使用范圍不同:wait,notify和notifyAll只能在同步控制方法或者同步代碼塊中使用,sleep可以在任何地方使用。
線程池
線程池是一個可以復(fù)用線程的技術(shù)。因為創(chuàng)建新線程的開銷很大,使用線程池可以重復(fù)利用線程,可提高程序性能。
獲取線程池對象
JDK5.0起提供了代表線程池的接口:ExecutorService
如何獲取線程池對象?
-
方式一:使用ExecutorService的實現(xiàn)類ThreadPoolExecutor自創(chuàng)建一個線程池對象,這種方法最為靈活。
-
方式二:使用Executor(線程池的工具類)調(diào)用方法返回不同特點的線程池對象。
ThreadPoolExecutor
臨時線程什么時候創(chuàng)建?
- 新任務(wù)提交時,核心線程都在忙,任務(wù)隊列也滿了,并且還可以創(chuàng)建臨時線程,此時才會創(chuàng)建臨時線程。
什么時候會開始拒絕任務(wù)? - 核心線程和臨時線程都在忙,任務(wù)隊列也滿了,新的任務(wù)過來時才會開始拒絕任務(wù)。
線程池處理runnable任務(wù)
ExecutorService pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
Runnable target = () -> {
try {
Thread.sleep(100000);
System.out.println(Thread.currentThread().getName() + "輸出");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
// 三個核心線程
pool.execute(target);
pool.execute(target);
pool.execute(target);
// 五個在等待隊列
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
// 等待隊列滿了,新加兩個臨時線程
pool.execute(target);
pool.execute(target);
// 拒絕任務(wù),拋出異常
pool.execute(target);
}
線程池處理callable任務(wù)
execute 執(zhí)行runnable類任務(wù),submit處理callable任務(wù)。
Executors
Executors是jdk內(nèi)置的線程池工具類,可以返回下邊四種類型的線程池:
定時器
Timer
Timer和TimerTask是用于在后臺線程中調(diào)度任務(wù)的java util類。簡單地說,TimerTask是要執(zhí)行的任務(wù),Timer是調(diào)度器。
1.自定義一個類繼承于TimerTask的類,并重寫其run()方法即可。
2.可以采取匿名類的形式,直接重寫其run()方法。
TimeTask有一抽象方法run(),其作用就是用來放我們處理的邏輯任務(wù)。
Timer有一schedule()方法,重載參數(shù)和另外兩個方法如下表:
可以看到schedule()可以接收Data參數(shù),指定某個時刻執(zhí)行,也可接收long類型的毫秒?yún)?shù),延遲多少毫秒執(zhí)行。
public static void usingTimer() {
Timer timer = new Timer("Timer");
long delay = 2000L;
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("Task performed on: " + new Date() + "n" +
"Thread's name: " + Thread.currentThread().getName());
}
}, delay, 3*1000); // 每3秒執(zhí)行一次定時任務(wù)
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(new Date());
}
},delay); // 只執(zhí)行一次
}
調(diào)度可重復(fù)執(zhí)行任務(wù)
有兩種方式:
- 固定延遲:schedule()方法還有兩個重載,每個重載都使用一個額外的
period
參數(shù)來表示以毫秒為單位的周期性。 - 固定頻率:有兩個scheduleAtFixedRate()方法,它們的周期也是以毫秒為單位的。
注意:1、如果一個任務(wù)的執(zhí)行時間超過了執(zhí)行周期,那么無論我們使用固定延遲還是固定速率,它都會延遲整個執(zhí)行鏈。
2、更有甚者如果定時調(diào)度器中一個定時任務(wù)出現(xiàn)異常,會同時影響其他任務(wù)的進行。
這就是使用Timer定時器的缺點所在,Timer本身是單線程的,處理多個任務(wù)按照順序執(zhí)行,存在延時與設(shè)置定時器的時間有出入??赡芤驗槠渲械哪硞€任務(wù)異常,影響后續(xù)任務(wù)。
取消定時器
1、調(diào)用Timer.cancel()方法。
2、在TimerTask的run()方法中使用cancle取消。
ScheduleExecutorService
ScheduleExecutorService內(nèi)部是線程池來處理定時任務(wù),如果某個任務(wù)失敗,那么這個線程會掛掉,不會影響其他線程的任務(wù)。
并發(fā)與并行
- 正在運行的程序(軟件)就是一個獨立的進程,線程是屬于進程的,多個線程其實是并發(fā)與并行同時進行的。
并發(fā)的理解:
- CPU同時處理線程的數(shù)量有限。
- CPU會輪詢?yōu)橄到y(tǒng)的每個線程服務(wù),由于CPU切換的速度很快,給我們感覺這些線程在同時執(zhí)行,這就是并發(fā)。
并行的理解:
- 在同一個時刻,多個線程同時執(zhí)行
線程的生命周期
線程的生命周期主要有以下6種狀態(tài):
- New(新創(chuàng)建)
- Runnable(可運行)
- Blocked(被阻塞)
- Waiting(等待)
- Timed Waiting(計時等待)
- Terminated(被終止)
New表示線程被創(chuàng)建,尚未啟動的狀態(tài),即new Thread(),新建一個線程但是還沒有執(zhí)行start()方法。當(dāng)執(zhí)行start()后,就進入了Runnable狀態(tài)。
Runnable
Java中的Runnable
狀態(tài)對應(yīng)操作系統(tǒng)線程狀態(tài)中的兩種狀態(tài)分別是Running
和Ready
,也就是說Java中處于Runnable
狀態(tài)的線程可能是正在執(zhí)行,也可能是在等待CPU分配資源。
阻塞狀態(tài)
Blocked(被阻塞)、Waiting(等待)、TimedWaiting(計時等待)這三種狀態(tài)統(tǒng)稱為阻塞狀態(tài)。
Blocked:沒獲得鎖被阻塞
從Runnable狀態(tài)進入到Blocked狀態(tài)只有一種途徑,那就是當(dāng)進入到synchronized代碼塊中時,未能獲得相應(yīng)的鎖。
相應(yīng)的,當(dāng)Blocked狀態(tài)的線程獲取到鎖時,此線程就會進入到Runnable狀態(tài)中參與CPUT資源的搶奪。
Waiting等待狀態(tài)
waiting狀態(tài)有三種情況:
- 當(dāng)線程中調(diào)用了沒有設(shè)置timeout參數(shù)的object.wait()方法。
- 當(dāng)線程調(diào)用了沒有設(shè)置timeout參數(shù)的thread.join()方法。
- 當(dāng)線程調(diào)用了LockSupport.park()方法。
Blocked與Waiting的區(qū)別
- Blocked是在等待其他線程釋放鎖
- Waiting則是在等待某個條件,比如join的線程執(zhí)行完畢,或者notify()/notifyAll().
Time Waiting計時等待狀態(tài)
Time Waiting狀態(tài)與Waiting狀態(tài)非常相似,其中的區(qū)別就在于是否有時間的限制,在Timed Waiting 狀態(tài)時會等待超時,之后由系統(tǒng)喚醒,或者也可以提前被通知喚醒如notify
。
進程狀態(tài)之間的轉(zhuǎn)換
Wait
和TimeWaiting
狀態(tài)被notify
或者notify_all
后,如果拿到鎖,那么進入Runnable
,如果沒有拿到鎖,則進入Blocked
。Runnable
執(zhí)行不帶時間參數(shù)的wait
進入waiting狀態(tài),執(zhí)行帶時間參數(shù)的wait
或者sleep
后進入TimeWaiting
狀態(tài)。
sleep和wait的區(qū)別
- 來自不同的對象:sleep來自Thread,wait來自O(shè)bject
- 使用場景不同:wait只能在同步代碼塊或者同步方法中使用,執(zhí)行wait前應(yīng)該使用notify或者notify_all喚醒其他線程。執(zhí)行wait后會釋放自己的鎖。sleep則沒有使用場景的限制,sleep執(zhí)行后并不會釋放鎖。
Junit單元測試框架
編寫Junit測試的步驟:
- 一般而言IDEA整合了Junit框架,不需要另外導(dǎo)入,但是如果IDEA沒有整合,需要手工導(dǎo)入Junit的兩個Jar包。
- 編寫測試方法:該測試方法必須是公共的無參數(shù)無返回值的非靜態(tài)方法
- 在測試方法上使用@Test注解:標(biāo)注該方法是一個測試方法。
- 在測試方法中完成被測試方法的預(yù)期正確性測試。
- 選中測試方法,選擇“Junit運行”,如果測試良好則是綠色,如果測試失敗,則是紅色。
例子:
對兩個業(yè)務(wù)代碼的正確性編寫單元測試:
業(yè)務(wù)用例:
public class UserService {
public String loginName(String loginName, String passWard) {
if ("admin".equals(loginName) && "123456".equals(passWard)) {
return "登錄成功";
} else {
return "用戶或者密碼有問題";
}
}
public void selectName() {
System.out.println(10 / 0);
}
}
測試案例:
public class TestUserService {
/**測試方法
* 1、必須是公開的,無參數(shù),無返回值的方法
* 2、測試方法必須使用@Test注解標(biāo)記
*/
@Test
public void testLoginName() {
UserService userService = new UserService();
String rs = userService.loginName("admin", "123456");
Assert.assertEquals("用戶或者密碼有問題", "登錄成功", rs);
}
@Test
public void testSelectNames() {
UserService userService = new UserService();
userService.selectName();
}
}
測試結(jié)果:
反射
反射的概述:
- 反射是指對于任何一個Class類,在“運行的時候”都可以直接得到這個類的全部成分。
- 在運行時,可以直接得到這個類的構(gòu)造對象:Constructor
- 在運行時,可以直接得到這個類的成員變量對象:Field
- 在運行時,可以直接得到這個類的成員方法對象:Method
- 這種運行時動態(tài)獲取類信息以及動態(tài)調(diào)用類中成分的能力稱為Java語言的反射機制
反射的關(guān)鍵:
反射的第一步都是先得到編譯后的Class類對象,然后就可以得到Class的全部成分。HelloWorld.java -> javac -> HelloWorld.class Class c = HelloWorld.class;
反射獲取Class類的全部成分
獲取Class類對象
一、反射第一步:獲取Class對象,有下邊三種方法:
- 在源代碼階段:Class類中的靜態(tài)方法:forName(String className)
- 在Class對象階段,通過類名.class
- 在Runtime運行時階段,通過對象.getClass()
回顧我們在編寫同步代碼塊實現(xiàn)的單例對象時。便是通過了上述的方法二,獲取到類對象來作為鎖的互斥資源:synchronized (SingleInstance.class){}
public static SingleInstance getInstance() {
if (singleInstance == null) {
synchronized (SingleInstance.class) {
if (singleInstance == null) {
singleInstance = new SingleInstance();
}
}
}
下邊我們編寫一個Student類,嘗試用上邊的三種方法獲取到Class對象:
// 1、Class.forName(全限名) 全限名:包名 + 類名
Class C1 = Class.forName("com.heima2.model.Student");
System.out.println(C1);
// 2、通過 類名.class
Class C2 = Student.class;
System.out.println(C2);
// 3、通過 對象.getClass
Student s = new Student();
Class C3 = s.getClass();
System.out.println(C3);
三個打印結(jié)果相同,因為編輯器只會編譯出一份類對象。
獲取構(gòu)造器(Constructor)、成員(Field)、成員函數(shù)(Method)
Constuctor
通過class對象的.getConstructors()
獲取全部構(gòu)造器
獲取每個構(gòu)造器,打印它的名字+構(gòu)造器的參數(shù)個數(shù)。
Student s = new Student();
Class C3 = s.getClass();
Constructor[] constructors = C3.getConstructors();
for (Constructor c : constructors) {
System.out.println(c.getName() + "-->" + c.getParameterCount());
}
使用.getConstructor(T ...)
方法按照參數(shù)獲取指定的構(gòu)造器并構(gòu)造對象:
Constructor con1 = C3.getConstructor(); // 獲取無參構(gòu)造器
Student sbycon1 = (Student) con1.newInstance();
Constructor con2 = C3.getConstructor(String.class, String.class); // 獲取有參構(gòu)造器
Student sbycon2 = (Student) con2.newInstance("張三", "01217");
注意:上邊兩種方法無法獲取到聲明為private的構(gòu)造器
如果要拿到private的構(gòu)造器,需要使用.getDeclaredConstructor()
方法,拿到私有構(gòu)造器后并不能直接構(gòu)建對象,需要打開權(quán)限:setAccessible(true);
Constructor con3 = C3.getDeclaredConstructor(Integer.class);
con3.setAccessible(true);
Student sbycon3 = (Student) con3.newInstance(12);
Field
Method
反射的作用
- 反射可以繞過編譯階段為集合添加數(shù)據(jù),此時集合的泛型將不能產(chǎn)生約束,可以為集合添加任意類型的元素。
- 泛型只是在編譯階段可以約束集合只能操作某種數(shù)據(jù)類型,在編譯成Class文件進入運行階段時,類型都是ArrayList,泛型相當(dāng)于被擦除了。
反射的另外一個重要作用是,做通用框架的底層實現(xiàn)基礎(chǔ)。
為了實現(xiàn)上述功能,我們需要利用反射構(gòu)造一個工具類:
這個工具類的主要思路就是接收一個對象,利用反射獲取對象的成員名字和成員的值,使用打印流將內(nèi)容保存到指定位置。
public class MybatisUtils {
public static void save(Object obj) {
try(PrintStream ps = new PrintStream(new FileOutputStream("F:\\java\\heima2\\src\\model\\data.txt", true))) {
Class c = obj.getClass();
ps.println("=============" + c.getSimpleName() + "=============");
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
String name = field.getName();
field.setAccessible(true);
String value = field.get(obj) + "";
ps.println(name + "=" + value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
測試代碼:
public class mainActivity {
public static void main(String[] args) throws Exception {
Student a = new Student("張三", "01217");
MybatisUtils.save(a);
Teacher t = new Teacher("李四", 24);
MybatisUtils.save(t);
}
}
結(jié)果:
注解
java注解是jdk5引入的一種注釋機制,java語言中的類、構(gòu)造器、方法、成員變量、參數(shù)等都可以被注解進行標(biāo)注,然后進行特殊處理。
自定義注解
格式:
public @interface 注解名稱 {
public 屬性類型 屬性名 () default 默認(rèn)值;
}
特殊屬性
- value屬性,如果只有一個value屬性的情況下,使用value屬性可以省略value的名稱
- 但是如果有多個屬性,且屬性沒有默認(rèn)值,那么value屬性是不能省略的
public @interface Book {
String value();
}
@Book("fa")
如果處理value屬性,其他屬性有默認(rèn)值,這種寫法也是正確的。
public @interface Book {
String value();
String name() default "aaa";
}
@Book("fa")
元注解
元注解:就是注解的注解
元注解有兩個:@Target
:約束自定義注解只能在哪些地方使用@Retention
:申明注解的生命周期
注解的解析
注解通常需要解析,判斷是否存在注解,存在就解析出內(nèi)容。
與注解解析相關(guān)的接口:
- Annotation:注解的頂級接口,注解都是Annotation類型的對象
- AnnotatedElement:該接口定義了與注解解析相關(guān)的解析方法
- 所有的類成分Class、Method、Field、Constructor都實現(xiàn)了AnnotatedElement接口,他們都擁有解析注解的功能。
注解解析案例
首先,我們編寫一個名為Book注解,添加元注解:@Target({ElementType.TYPE, ElementType.METHOD})
使得可以在類型和方法上添加注解@Retention(RetentionPolicy.RUNTIME)
使得注解一直存在
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
String name();
String[] authors();
double price();
}
然后我們編寫一個類BookStore,給他加上Book注解:
@Book(name = "《java虛擬機》", authors = {"a","b"}, price = 9.9)
public class BookStore {
@Book(name = "《C++prime》", authors = {"c","b"}, price = 19.9)
public void test() {
}
}
最后我們寫個測試案例:獲取并解析類上和方法上的這兩個注解:
public static void main(String[] args) throws NoSuchMethodException {
// a、先得到類對象
Class c = BookStore.class;
// 判斷是否存在注解,如果存在,取出注解內(nèi)容
if (c.isAnnotationPresent(Book.class)) {
Book bookFromClass = (Book) c.getDeclaredAnnotation(Book.class);
System.out.println("類:---" + c.getSimpleName() + "---的注解");
System.out.println(bookFromClass.name());
System.out.println(Arrays.toString(bookFromClass.authors()));
System.out.println(bookFromClass.price());
}
// 由對象獲取到方法,再獲取方法的注解
Method m = c.getDeclaredMethod("test");
if (m.isAnnotationPresent(Book.class)) {
System.out.println("方法:---" + m.getName() + "---的注解");
Book bookFromMethod = (Book) m.getDeclaredAnnotation(Book.class);
System.out.println(bookFromMethod.name());
System.out.println(Arrays.toString(bookFromMethod.authors()));
System.out.println(bookFromMethod.price());
}
}
模擬Junit的注解案例
首先自定義一個注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
在測試類中,我們給某些方法定義注解,在main函數(shù)中,我們通過反射獲取到類的方法,檢查是否有注解,如果有注解就,通過方法的invoke(Object,...)
方法執(zhí)行該方法。
public class AnnotationDemo {
@MyTest
public void test1() {
System.out.println("===test1===");
}
public void test2() {
System.out.println("===test2===");
}
@MyTest
public void test3() {
System.out.println("===test3===");
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
AnnotationDemo a = new AnnotationDemo();
Class c = a.getClass();
Method[] ms = c.getDeclaredMethods();
for (Method m : ms) {
if (m.isAnnotationPresent(MyTest.class)) {
m.invoke(a);
}
}
}
}
動態(tài)代理
代理:某些場景下,對象會找一個代理對象,來輔助自己完成一些工作。
在java中實現(xiàn)動態(tài)代理的步驟:
- 必須存在接口
- 被代理對象實現(xiàn)接口
- 使用Proxy類提供的方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
通過代理調(diào)用方法的執(zhí)行流程:
- 先走向代理
- 代理可以為方法額外做一些輔助工作
- 開始正在觸發(fā)對象方法的執(zhí)行
- 回到代理中,由代理負(fù)責(zé)返回結(jié)果給方法的調(diào)用者。
下邊是一個例子:
首先有一個接口Skill
:
public interface Skill {
void dance();
void sing();
}
被代理對象Star
實現(xiàn)了上述接口:
public class Star implements Skill{
private String name;
public Star(String name) {
this.name = name;
}
@Override
public void dance() {
System.out.println(name + "跳舞");
}
@Override
public void sing() {
System.out.println(name + "唱歌");
}
}
創(chuàng)建代理類,構(gòu)建一個創(chuàng)建代理的靜態(tài)方法,該方法返回的是接口對象Skill
,入?yún)⑹俏覀兊谋淮韺ο?code>Star
方法體就是利用Proxy類提供的newProxyInstance
代理實例方法,這個方法實際上是通過反射的方法,獲取到了被代理類實現(xiàn)的方法,并且在方法體中invoke
它。
public class StarAgentProxy {
public static Skill getProxy(Star s) {
return (Skill) Proxy.newProxyInstance(s.getClass().getClassLoader(), s.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("收首付款。。。");
Object rs = method.invoke(s);
System.out.println("收尾款。。。");
return rs;
}
});
}
}
動態(tài)代理的案例
第一步:編寫接口
public interface UserService {
void login(String userName, String passWard);
String deleteUser();
String selectUsers();
}
第二步:編寫被代理類實現(xiàn)上述接口:
package proxy2;
public class UserServiceImpl implements UserService{
@Override
public void login(String userName, String passWard) {
String rs = "登錄名和密碼錯誤";
if ("admin".equals(userName) && "123456".equals(passWard)) {
rs = "登錄成功";
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String deleteUser() {
try {
System.out.println("正在刪除用戶.....");
Thread.sleep(2500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "deleteUser";
}
@Override
public String selectUsers() {
try {
System.out.println("正在搜索用戶.....");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
return "selectUsers";
}
}
第三步,編寫代理類,返回接口,在代理中調(diào)用接口的方法,并對方法進行耗時分析。
public static UserService getProxy(UserService obj) {
return (UserService) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object rs = method.invoke(obj, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + "耗時" + (endTime - startTime) / 1000.0 + "s");
return rs;
}
});
}
第四步:
編寫測試用例,構(gòu)建被代理類對象,交由代理調(diào)用方法;
public static void main(String[] args) throws Exception {
UserServiceImpl userService = new UserServiceImpl();
UserService u = ProxyUtil.getProxy(userService);
u.login("admin", "123456");
u.deleteUser();
u.selectUsers();
}
動態(tài)代理的優(yōu)點
- 可以在不改變方法源碼的情況下,實現(xiàn)對方法功能的增強,提高了代碼的復(fù)用。
- 簡化了編程工作,提高了開發(fā)效率,同時提高了軟件系統(tǒng)的可拓展性。
- 可以為被代理對象的所有方法做代理。
- 非常的靈活,支持接口類型的實現(xiàn)類對象做代理,也可以直接為接口本身做代理。
XML
- XML是
可拓展標(biāo)記語言
(eXtensible Markup Language)的縮寫,它是一種數(shù)據(jù)表示格式,可以描述非常復(fù)雜的數(shù)據(jù)結(jié)構(gòu),常用于傳輸和存儲數(shù)據(jù)。
XML的幾個特點和使用場景
- 一是純文本,默認(rèn)使用UTF-8編碼,二是可嵌套
- 如果把XML內(nèi)容存為文件,那么它就是一個XML文件
- XML的使用場景,XML內(nèi)容經(jīng)常被當(dāng)成消息進行網(wǎng)絡(luò)傳輸,或者作為配置文件用于存儲系統(tǒng)的信息。
XML類似html語言,由可以嵌套的開閉標(biāo)簽構(gòu)成:
<?xml version="1.0" encoding="UTF-8" ?>
<student>
<name>張三</name>
<gender>男</gender>
<info>
<age>24</age>
<address>武漢</address>
</info>>
</student>
為了避免與標(biāo)記符’>’ '<'沖突,XML語言對于特殊字符有另外的表述方法,當(dāng)然也可以聲明一個區(qū)域,CDATA
,在這個區(qū)域的大于小于符號就不會與標(biāo)記符號產(chǎn)生沖突。在IDEA中可以直接輸入CD然后tab文章來源:http://www.zghlxwxcb.cn/news/detail-457471.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-457471.html
JVM
語言發(fā)展歷史
- C/C++
- 手動管理堆內(nèi)存:malloc/free / new / delete
- 可能導(dǎo)致的問題:申請了內(nèi)存,忘記釋放 -->
memory leak 內(nèi)存泄露
--> 內(nèi)存泄露越來越多時,可用的堆空間越來越小,很有可能進一步導(dǎo)致某次申請空間時,沒辦法分配 即out of memory 內(nèi)存溢出
- 由于編程時需要考慮底層的內(nèi)存分配,導(dǎo)致開發(fā)效率極低。
- Java Python Go
- 方便內(nèi)存管理的語言
- 自帶GC - Garbage Collector(垃圾回收器),程序運行時自動啟動垃圾回收線程,垃圾回收器負(fù)責(zé)回收在堆中申請的內(nèi)存
- 自帶垃圾收集器使得程序員業(yè)務(wù)開發(fā)時,不再需要關(guān)注底層的內(nèi)存問題,大大降低了程序員門檻,提高了開發(fā)的效率
- 由于需要額外的垃圾回收線程,執(zhí)行效率偏低,此外這些語言也沒有解決空指針問題,需要程序中額外判斷。
到了這里,關(guān)于java語法(二)線程并發(fā)、Juit單元測試、反射機制、注解、動態(tài)代理、XML解析、JVM的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!