Java面向考點復習
1.Action event 事件監聽方法(滑鼠等事件)

- Event(事件):用戶對組件的一次操作稱為一個事件,以類的形式出現,例如,鍵盤操作對應的事件類是 KeyEvent,
- Event Source(事件源):事件發生的場所,通常就是各個組件,例如按鈕 Button,
- Event Handler(事件處理者):接收事件物件并對其進行處理的物件事件處理器,通常就是某個Java類中負責處理事件的成員方法,
授權模型(Delegation Model): 事件源可以把在其自身上所有可能發生的事件分別授權給不同的事件處理者來處理,
事件處理者也稱為監聽器,監聽器時刻監聽事件源上所有發生的事件型別,一旦該事件型別與自己所負責處理的事件型別一致,就馬上進行處理,授權模型把事件的處理委托給外部的處理物體進行處理,實作了將事件源和監聽器分開的機制,
動作事件監聽器
- 事件名稱:ActionEvent,
- 事件監聽介面: ActionListener,
- 事件相關方法:addActionListener() 添加監聽,removeActionListener() 洗掉監聽,
- 涉及事件源:JButton、JList、JTextField 等,
自定義事件監聽器必須繼承 ActionListener 類,并重寫父類的 actionPerformed() 方法,在 actionPerformed() 方法內撰寫按鈕被單擊后執行的功能,
以下示例代碼展示了內部類和匿名內部類實作監聽的方式,除此之外還可以直接在主類實作ActionListener介面以及外部類實作介面在主類中創建物件共4種方式實作監聽
package Review;
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
public class ActionListenerDemo extends JFrame
{
JLabel label;
JButton button1;
int clicks = 0;
public ActionListenerDemo()
{
setTitle("動作事件監聽器示例");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100,100,400,200);
JPanel contentPane=new JPanel();
contentPane.setBorder(new EmptyBorder(5,5,5,5));
contentPane.setLayout(new BorderLayout(0,0));
setContentPane(contentPane);
label=new JLabel(" ");
label.setFont(new Font("楷體",Font.BOLD,16)); //修改字體樣式
contentPane.add(label, BorderLayout.SOUTH);
button1=new JButton("我是普通按鈕"); //創建JButton物件
button1.setFont(new Font("黑體",Font.BOLD,16)); //修改字體樣式
// button1.addActionListener(new ActionListener()
// {
// public void actionPerformed(ActionEvent e)
// {
// label.setText("按鈕被單擊了 "+(clicks++)+" 次");
// }
// });
button1.addActionListener(new button1ActionListener());
contentPane.add(button1);
}
//處理按鈕單擊事件的匿名內部類
class button1ActionListener implements ActionListener
{
@Override
public void actionPerformed(ActionEvent e)
{
label.setText("按鈕被單擊了 "+(clicks++)+" 次");
}
}
public static void main(String[] args)
{
ActionListenerDemo frame=new ActionListenerDemo();
frame.setVisible(true);
}
}
焦點事件監聽器
如將游標離開文本框時彈出對話框,或者將焦點回傳給文本框等,
- 事件名稱:FocusEvent,
- 事件監聽介面: FocusListener,
- 事件相關方法:addFocusListener() 添加監聽,removeFocusListener() 洗掉監聽,
- 涉及事件源:Component 以及派生類,
FocusEvent 介面定義了兩個方法,分別為 focusGained() 方法和 focusLost() 方法,其中 focusGained() 方法是在組件獲得焦點時執行,focusLost() 方法是在組件失去焦點時執行,
核心代碼:
txtfield1=new JTextField();
txtfield1.addFocusListener(new FocusListener()
{
@Override
public void focusGained(FocusEvent arg0)
{
// 獲取焦點時執行此方法
label.setText("文本框獲得焦點,正在輸入內容");
}
@Override
public void focusLost(FocusEvent arg0)
{
// 失去焦點時執行此方法
label.setText("文本框失去焦點,內容輸入完成");
}
});
contentPane.add(txtfield1);
考試涉及內容:
(1)監聽鍵盤事件
通過addKeyListener,添加KeyAdapter物件(如果添加KeyListener物件就需要重寫keyTyped、keyPressed、keyReleased三個方法),重寫keyPressed即可
package Review;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class KeyBoardListener
{
public static void main(String[] args)
{
Frame f = new Frame();
f.setBounds(200, 200, 200, 200);
f.addKeyListener(new KeyListener(){
@Override
public void keyTyped(KeyEvent e) {
System.out.println("鍵盤輸入");
}
@Override
public void keyPressed(KeyEvent e) {
System.out.println("鍵盤敲擊");
int code = e.getKeyCode();
System.out.println(code);
}
@Override
public void keyReleased(KeyEvent e) {
System.out.println("鍵盤釋放");
}
});
f.setVisible(true);
}
}
(2)監聽滑鼠事件
package Review;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class MouseListenerDEMO
{
public static void main(String[] args)
{
Frame f = new Frame();
f.setBounds(200, 200, 200, 200);
f.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("滑鼠點擊");
}
@Override
public void mousePressed(MouseEvent e) {
System.out.println("滑鼠按下");
}
@Override
public void mouseReleased(MouseEvent e) {
System.out.println("滑鼠抬起");
}
@Override
public void mouseEntered(MouseEvent e) {
System.out.println("滑鼠進入");
}
@Override
public void mouseExited(MouseEvent e) {
System.out.println("滑鼠離開");
}
});
f.setVisible(true);
}
}
2.char型別存盤需要位元組數
基礎知識:Unicode 相當于一張表,建立了字符與編號之間的聯系,而UTF-XX是這個編號的存盤方式
Unicode 為世界上所有字符都分配了一個唯一的數字編號,這個編號范圍從 0x000000 到 0x10FFFF (十六進制),有 110 多萬,每個字符都有一個唯一的 Unicode 編號,
Unicode字符集規定的標準編碼方案是UCS-2(UTF-16),用兩個位元組表示一個Unicode字符
① 對于編號在 U+0000 到 U+FFFF 的字符(常用字符集),直接用兩個位元組表示,
② 編號在 U+10000 到 U+10FFFF 之間的字符,需要用四個位元組表示,
Java為應對這種情況,考慮到向前兼容的要求,Java用兩個char來表示那些需要4位元組的字符,所以,java中的char是占用兩個位元組,只不過有些字符需要兩個char來表示,
3.字串常量池
每當我們創建字串常量時,JVM會首先檢查字串常量池,如果該字串已經存在常量池中,那么就直接回傳常量池中的實體參考,如果字串不存在常量池中,就會實體化該字串并且將其放到常量池中,由于String字串的不可變性我們可以十分肯定常量池中一定不存在兩個相同的字串,
String a="AA";
String b="AA";
String c=new String("AA");
a、b和字面上的AA都是指向JVM字串常量池中的"AA"物件,他們指向同一個物件,
new關鍵字一定會產生一個物件AA,同時這個物件是存盤在堆中,所以上面這一句應該產生了兩個物件:保存在方法區中字串常量池的AA和保存堆中AA,但是在Java中根本就不存在兩個完全一模一樣的字串物件,故堆中的AA應該是參考字串常量池中AA,所以c、堆AA、池AA的關系應該是:c—>堆AA—>池AA,
雖然a、b、c、c是不同的參考,但是從String的內部結構我們是可以理解上面的,String c = new String(“AA”);雖然c的內容是創建在堆中,但是他的內部value還是指向JVM常量池的AA的value,它構造AA時所用的引數依然是AA字串常量,所以a==b是true,因為記憶體地址是一樣的,a==c是false,因為c的記憶體地址是在堆中new的是新的地址

對于字串而言,==比較的是存盤地址,顯然相同地址的字串內容相同,但相同內容字串可以在多個地址,+和substring等方式產生的字串并不共享,例如"Hello".substring(0,3) == "Hel"就有可能為false
4.向上轉型&動態鏈接
從一個大范圍類到小范圍類是可以強轉的,如:
Son son = new Son();
Father father = (Father)son;
反過來
Father father = new Father();
Son son = (Son)father;
就不行
向上轉型:
因為子類是對父類的一個改進和擴充,所以一般子類在功能上較父類更強大,屬性較父類更獨特, 定義一個父型別別的參考指向一個子類的物件既可以使用子類強大的功能,又可以抽取父類的共性, 所以,父型別別的參考可以呼叫父類中定義的所有屬性和方法,而對于子類中定義而父類中沒有的方法,父類參考是無法呼叫的;
動態鏈接:
當父類中的一個方法只有在父類中定義而在子類中沒有重寫的情況下,才可以被父型別別的參考呼叫; 對于父類中定義的方法,如果子類中重寫了該方法,那么父型別別的參考將會呼叫子類中的這個方法,
5.例外種類 例外處理

(1)throw
在指定方法中拋出指定的例外,必須寫在方法內部
throw new xxxException("例外產生的原因");
對方法傳遞過來的引數必須先進行合法性校驗,比如引數是陣列卻傳過來了null就要拋出空指標例外(NullPointerException)
注:NullPointerException是一個運行期例外,不需要自己處理,交給JVM處理即可,但對于編譯例外就必須通過throws或是用try…catch處理
Objects類自帶一個檢測是否為空指標的方法requireNonNull,可以直接使用
(2)throws
在方法宣告時使用
修飾符 回傳值型別 方法名(引數串列) throws AAAException,BBBException…
{
throw new AAAException("產生原因");
throw new BBBException("產生原因");
}
1.throws后需要宣告方法內拋出的全部例外物件(有子父類關系則只用宣告父類)
2.呼叫了拋出例外的方法就必須通過throws交給呼叫者->JVM去處理,或是用try…catch自己處理(即throws等需要在方法宣告處與呼叫處宣告兩次)
(3)try…catch
可執行后續代碼
try{
可能產生例外的代碼
}
catch(例外類名 變數名){
對例外物件的處理(一般會選擇記錄到日志)
}
...
//可以有多個catch,但子類例外變數必須在前面
執行catch后會繼續執行后續代碼(但是try中產生例外后的代碼不會執行),而throws交給JVM會進行中斷處理
(4)Throwable中的三個例外處理方法
1.getMessage() 回傳Throwable的簡短描述
2.toString() 回傳Throwable的詳細訊息字串
3.printStackTrace() 列印例外物件
(5)finally
放在catch代碼塊后面,不管是否有例外都會執行
finally一般用于資源釋放/回收,注意finally中的return一定會執行,為了避免最好不要在finally中回傳
(6)子父類例外
父類方法宣告拋出的例外子類在重寫時只能拋出相同例外或其子類例外或不拋出例外
父類方法不拋出例外子類重寫時只能捕獲不能拋出(即只能try…catch)
6.靜態工廠模式

public class ProductFactory {
public static IProduct product=null;
public static IProduct getProduct(String productType) throws InstantiationException, IllegalAccessException, ClassNotFoundException{
// if(productType==null||productType.trim().equals("")){//默認創建ProductA
// product=new ProductA();
// }else if(productType.trim().equals("ProductA")){
// product=new ProductA();
// }else if(productType.trim().equals("ProductB")){
// product=new ProductB();
// }
//靜態工廠一般使用類的反射來構建物件,像上面的構建也可以,
if(productType.trim().equals("ProductA")){
product=(IProduct)Class.forName("org.test.design.sf.ProductA").newInstance();
}else if(productType.trim().equals("ProductB")){
product=(IProduct)Class.forName("org.test.design.sf.ProductB").newInstance();
}
return product;
}
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
IProduct product_a=ProductFactory.getProduct("ProductA");
product_a.work();
IProduct product_b=ProductFactory.getProduct("ProductB");
product_b.work();
}
7.值傳遞&參考傳遞
首先,Java中所謂的參考傳遞本質仍然是值傳遞,
先來看定義:
- 值傳遞:將實際引數復制一份傳遞到函式中,函式對引數的修改不會影響實際引數
- 參考傳遞:將實際引數的地址傳遞到函式中,函式對引數的所有修改直接體現于實際引數
在java的值傳遞中,是將實際引數參考的地址復制一份給形參, 換句話說,形參是一個新的參考,指向實際引數所在記憶體空間,舉個最簡單的例子,對于傳統意義上的參考傳遞而言,在函式內使用new會使原參考指向另一片記憶體空間,而在java中,會復制(或者說臨時創建)一個新的參考指過去,那么new的時候自然就是這個參考指向new的空間,而實際引數的參考不變,對new出來的物件的修改不作用到實際引數上,

Java中的傳遞,是值傳遞,而這個值,對于基本資料型別是值的復制,對參考資料型別實際上是物件的參考地址的復制,值得注意的是,在大多數語言中參考傳遞都是通過這樣值傳遞的方式進行的,C++中直接傳參考反而較為少見
易錯示例:
public class Example{
String str=new String("good");
char[]ch={'a','b','c'};
public void change(String str,char ch[])
{
str="test ok"; ch[0]='g';
}
public static void main(String args[]){
Example ex=new Example();
ex.change(ex.str,ex.ch);
System.out.print(ex.str+" and ");
System.out.print(ex.ch);
}
}
結果為good and gbc,注意String是不可變的(見3.字串常量池)
8.常用類
Math(主要看回傳值和引數數量)
public static double sin (double radians)
public static double toRadians (double degree)
public static double toDegrees (double radians)
public static double asin (double a)
public static double exp ( double x )
public static double log ( double x )
public static double log10 (double x )
public static double pow ( double a, double b )
public static double sqrt ( double x )
public static double ceil ( double x ) 【向上取整】
public static double floor ( double x ) 【向下取整】
public static double rint ( double x ) 【取最接近的整數,如果有兩個同樣接近的整數(.5),就兩個中隨機取一個 】
public static int round ( float x ) Return (int)Math.floor (x + 0.5 )
public static long round ( double x ) Return (long)Math.floor ( x + 0.5)
abs 回傳一個數(int、 long、 float、 double)的絕對值
public static double random()
Date
1、public Date()——分配 Date 物件并初始化此物件,以表示分配它的時間(精確到毫秒),
2、public Date(long date)——根據給定的毫秒值創建日期物件,
3、public long getTime()——日期轉毫秒值
通過getTime方法可以將一個日期型別轉換為long型別的毫秒值
4、public void setTime(long time)——毫秒值轉日期
當然也可以通過建構式public Date(long date)將毫秒值轉為日期型別,
通常我們會比較2個日期的大小,Date類提供以下方法用來比較
1、public boolean before(Date when)——測驗此日期是否在指定日期之前,當且僅當此Date物件表示的瞬間比when表示的瞬間早,才回傳true;否則回傳false,
2、public boolean after(Date when)——測驗此日期是否在指定日期之后,當且僅當此Date物件表示的瞬間比when表示的瞬間晚,才回傳true;否則回傳false,
3、public int compareTo(Date anotherDate)——比較兩個日期的順序,
如果引數Date等于此Date,則回傳值0;如果此Date在Date引數之前,則回傳小于0的值;如果此Date在Date引數之后,則回傳大于0的值,
9.instanceof關鍵字
instanceof 嚴格來說是Java中的一個雙目運算子,用來測驗一個物件是否為一個類的實體,用法為:
boolean result = obj instanceof Class
其中 obj 為一個物件,Class 表示一個類或者一個介面,當 obj 為 Class 的物件,或者是其直接或間接子類,或者是其介面的實作類,結果result 都回傳 true,否則回傳false,如果 obj 為 null,那么將回傳 false,
10.非訪問修飾符
static 修飾符
靜態變數:
static 關鍵字用來宣告獨立于物件的靜態變數,無論一個類實體化多少物件,它的靜態變數只有一份拷貝, 靜態變數也被稱為類變數,區域變數不能被宣告為 static 變數,
靜態方法:
static 關鍵字用來宣告獨立于物件的靜態方法,靜態方法從引數串列得到資料,然后計算這些資料,靜態方法不能訪問非靜態的資料和方法,因為這兩項都依賴于具體的實體,而靜態方法在物件實體化之前就已經被JVM裝載,而類中的實體變數和實體物件必須在物件開辟堆記憶體之后才能使用,
優點:
1.屬于類級別的,所有不需要創建物件就可以直接使用;
2.全域唯一,記憶體中唯一,靜態變數可以唯一標識某些狀態;
3.初始化在類加載時候,常駐在記憶體中,呼叫快捷方便,
final 修飾符
final 變數:
final 表示"最后的、最終的"含義,變數一旦賦值后,不能被重新賦值,被 final 修飾的實體變數必須顯式指定初始值,
final 修飾符通常和 static 修飾符一起使用來創建類常量,
final 方法:
父類中的 final 方法可以被子類繼承,但是不能被子類重寫,
宣告 final 方法的主要目的是防止該方法的內容被修改,
abstract 修飾符
抽象類:
抽象類不能用來實體化物件,宣告抽象類的唯一目的是為了將來對該類進行擴充,
一個類不能同時被 abstract 和 final 修飾,如果一個類包含抽象方法,那么該類一定要宣告為抽象類,否則將出現編譯錯誤,
抽象類可以包含抽象方法和非抽象方法,
抽象方法
抽象方法是一種沒有任何實作的方法,該方法的的具體實作由子類提供,
抽象方法不能被宣告成 final 和 static,
任何繼承抽象類的子類必須實作父類的所有抽象方法,除非該子類也是抽象類,
如果一個類包含若干個抽象方法,那么該類必須宣告為抽象類,抽象類可以不包含抽象方法,
synchronized 修飾符
synchronized 關鍵字宣告的方法同一時間只能被一個執行緒訪問,
transient 修飾符
序列化的物件包含被 transient 修飾的實體變數時,java 虛擬機跳過該特定的變數,
該修飾符包含在定義變數的陳述句中,用來預處理類和變數的資料型別,
volatile 修飾符
volatile 修飾的成員變數在每次被執行緒訪問時,都強制從共享記憶體中重新讀取該成員變數的值,而且,當成員變數發生變化時,會強制執行緒將變化值回寫到共享記憶體,這樣在任何時刻,兩個不同的執行緒總是看到某個成員變數的同一個值,
11.多執行緒實作方式(主要兩種)
(1)創建Thread類的子類
1.創建Thread類的子類
2.重寫run方法,設定執行緒任務
3.創建當前類物件
4.呼叫父類的start方法,開啟新執行緒,執行run方法
先執行優先級高的執行緒,若優先級相同則隨機選擇一個執行
(2)使用Runnable介面實作類
1.創建Runnable介面實作類
2.重寫run方法,設定執行緒任務
3.創建當前類物件
4.創建Thread類物件,引數為該介面實作類物件,呼叫start方法
(3)區別
1.實作Runnable介面可以再繼承其它類或實作其它介面,避免了局限性
2.實作Runnable介面把設定執行緒任務和開啟新執行緒進行分離(解耦)
plus:實作Callable介面
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 一、創建執行執行緒的方式三:實作Callable介面,相較于實作Runnable介面的方式,方法可以有回傳值,并且可以拋出例外
* 二、執行Callable方式,需要FutureTask實作類的支持,用于接收運算結果
*/
public class TestCallable {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
// 1.執行Callable方式,需要FutureTask實作類的支持,用于接收運算結果
FutureTask<Integer> result = new FutureTask<>(td);
new Thread(result).start();
// 2.接收執行緒運算后的結果
Integer sum;
try {
//等所有執行緒執行完,獲取值,因此FutureTask可用于閉鎖
sum = result.get();
System.out.println("-----------------------------");
System.out.println(sum);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100000; i++) {
System.out.println(i);
sum += i;
}
return sum;
}
}
12.裝飾模式寫出IO流
Java的java.io包中囊括了整個流的家族,輸出流和輸入流的譜系如下所示:


以InputStream為例,它是一個抽象類:
public abstract class InputStream implements Closeable {
...
...
}
并定義有抽象方法
public abstract int read() throws IOException;
該抽象方法由具體的子類去實作,通過InputStream的族譜圖可以看到,直接繼承了InputStream,并且提供某一特定功能的子類有:
- ByteArrayInputStream
- FileInputStream
- ObjectInputStream
- PipedInputStream
- SequenceInputStream
- StringBufferInputStream
這些子類都具有特定的功能,比如說,FileInputStream代表一個檔案輸入流并提供讀取檔案內容的功能,ObjectInputStream提供了物件反序列化的功能,
InputStream這個抽象類有一個子類與上述其它子類非常不同,這個子類就是FilterInputStream,可參見上圖中的InputStream族譜圖,
翻開FilterInputStream的代碼,我們可以看到,它內部又維護了一個InputStream的成員物件,并且它的所有方法,都是呼叫這個成員物件的同名方法,
換句話說,FilterInputStream它什么事都不做,就是把呼叫委托給內部的InputStream成員物件,
FilterInputStream的又有其子類,分別是:
- BufferedInputStream
- DataInputStream
- LineNumberInputStream
- PushbackInputStream
以BufferedInputStream為例,這個類提供了提前讀取資料的功能,也就是緩沖的功能,可以看看它的read方法:
public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
可以看到,當pos>=count時,意即需要提前緩沖一些資料的時候到了,那么就會呼叫fill()將緩沖區加滿,以便后續讀取,
BufferedInputStream就是一個裝飾者,它能為一個原本沒有緩沖功能的InputStream添加上緩沖的功能,
比如我們常用的FileInputStream,它并沒有緩沖功能,我們每次呼叫read,都會向作業系統發起呼叫索要資料,假如我們通過BufferedInputStream來裝飾它,那么每次呼叫read,會預先向作業系統多拿一些資料,這樣就不知不覺中提高了程式的性能,如以下代碼所示:
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("/home/user/abc.txt")));
同理,對于其它的FilterInputStream的子類,其作用也是一樣的,那就是裝飾一個InputStream,為它添加它原本不具有的功能,OutputStream以及家屬對于裝飾器模式的體現,也以此類推,
13.Layout 界面編程
BorderLayout
BorderLayout(邊框布局管理器)是 Window、JFrame 和 JDialog 的默認布局管理器,邊框布局管理器將視窗分為 5 個區域:North、South、East、West 和 Center,其中,North 表示北,將占據面板的上方;Soufe 表示南,將占據面板的下方;East表示東,將占據面板的右側;West 表示西,將占據面板的左側;中間區域 Center 是在東、南、西、北都填滿后剩下的區域

邊框布局管理器并不要求所有區域都必須有組件,如果四周的區域(North、South、East 和 West 區域)沒有組件,則由 Center 區域去補充,如果單個區域中添加的不只一個組件,那么后來添加的組件將覆寫原來的組件,所以,區域中只顯示最后添加的一個組件,
BorderLayout 布局管理器的構造方法如下所示,
BorderLayout():創建一個 Border 布局,組件之間沒有間隙,BorderLayout(int hgap,int vgap):創建一個 Border 布局,其中 hgap 表示組件之間的橫向間隔;vgap 表示組件之間的縱向間隔,單位是像素,
package Review;
import javax.swing.JButton;
import javax.swing.JFrame;
import java.awt.*;
public class BorderLayoutDemo
{
public static void main(String[] agrs)
{
JFrame frame=new JFrame("Java第三個GUI程式"); //創建Frame視窗
frame.setSize(400,200);
frame.setLayout(new BorderLayout()); //為Frame視窗設定布局為BorderLayout
JButton button1=new JButton ("上");
JButton button2=new JButton("左");
JButton button3=new JButton("中");
JButton button4=new JButton("右");
JButton button5=new JButton("下");
frame.add(button1,BorderLayout.NORTH);
frame.add(button2,BorderLayout.WEST);
frame.add(button3,BorderLayout.CENTER);
frame.add(button4,BorderLayout.EAST);
frame.add(button5,BorderLayout.SOUTH);
frame.setBounds(300,200,600,300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
GridLayout
GridLayout(網格布局管理器)為組件的放置位置提供了更大的靈活性,它將區域分割成行數(rows)和列數(columns)的網格狀布局,組件按照由左至右、由上而下的次序排列填充到各個單元格中,
GridLayout 的構造方法如下,
GridLayout(int rows,int cols):創建一個指定行(rows)和列(cols)的網格布局,布局中所有組件的大小一樣,組件之間沒有間隔,GridLayout(int rows,int cols,int hgap,int vgap):創建一個指定行(rows)和列(cols)的網格布局,并且可以指定組件之間橫向(hgap)和縱向(vgap)的間隔,單位是像素,
GridLayout 布局管理器總是忽略組件的最佳大小,而是根據提供的行和列進行平分,該布局管理的所有單元格的寬度和高度都是一樣的,
package Review;
import javax.swing.*;
import java.awt.*;
public class Main {
public static void main(String[] args) {
JFrame jf = new JFrame("測驗視窗");
jf.setSize(200, 250);
jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
jf.setLocationRelativeTo(null);
// 創建 3 行 3 列 的網格布局
GridLayout layout = new GridLayout(3, 3);
// 設定 水平 和 豎直 間隙
layout.setHgap(10);
layout.setVgap(10);
JPanel panel = new JPanel(layout);
JButton btn01 = new JButton("按鈕01");
JButton btn02 = new JButton("按鈕02");
JButton btn03 = new JButton("按鈕03");
JButton btn04 = new JButton("按鈕04");
JButton btn05 = new JButton("按鈕05");
JButton btn06 = new JButton("按鈕06");
JButton btn07 = new JButton("按鈕07");
JButton btn08 = new JButton("按鈕08");
panel.add(btn01);
panel.add(btn02);
panel.add(btn03);
panel.add(btn04);
panel.add(btn05);
panel.add(btn06);
panel.add(btn07);
panel.add(btn08);
jf.setContentPane(panel);
jf.setVisible(true);
}
}
14.泛型
<泛型型別名>
1.泛型類
在類名后加,方法的回傳值也為E
創建物件時要制定泛型型別,即與集合的創建方式相同
2.泛型方法
泛型型別名加在方法修飾符與回傳型別之間
3.泛型介面
與泛型類相似,介面的實作類要制定泛型型別(比如implements interface)
如果是泛型類實作泛型介面就可以不用指定
4.泛型通配符
? 代表任意資料型別
適用于作為引數傳遞時,接收一個任意通配泛型形參做迭代,獲取的物件只能說Object型別
5.泛型限定
1)泛型上限限定
? extends E
使用的泛型只能是E及其子類
比如E是Number,使用的就可以是Integer
2)泛型下限限定
? super E
使用的泛型只能是E及其父類
package day2;
import java.util.*;
public class day2 {
public void setName(E name)
{
this.name = name;
}
public E getName()
{
return this.name;
}
public static <M> void printVar(M m)
{
System.out.println(m);
}
//泛型通配符
public static void printArray(ArrayList<?> arr)
{
Iterator<?> it = arr.iterator();
while(it.hasNext())
{
Object o = it.next();
System.out.println(o);
}
}
private E name;
public static void main(String args[]) {
//迭代器
Collection<String> coll = new ArrayList<>();
coll.add("AAA");
coll.add("BBB");
coll.add("CCC");
coll.add("XXX");
Iterator<String> it = coll.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
for (Iterator<String> it2 = coll.iterator(); it2.hasNext(); )
{
System.out.println(it2.next());
}
//泛型類
day2<String> obj1 = new day2<String>();
obj1.setName("AAA");
System.out.println(obj1.name);
day2<Integer> obj2 = new day2<Integer>();
obj2.setName(111);
System.out.println(obj2.name);
day2<Character> obj3 = new day2<Character>();
obj3.setName('X');
System.out.println(obj3.name);
//泛型方法
day2.printVar("靜態泛型方法");
//泛型介面
MyInterfaceImpl1 impl1 = new MyInterfaceImpl1();
impl1.mth("泛型類實作泛型介面");
new MyInterface<String>() {
@Override
public void mth(String s) {
System.out.println(s);
}
}.mth("匿名物件實作泛型介面");
System.out.println();
//泛型通配符方法
day2.printArray((ArrayList)coll);
}
}
package day2;
//泛型介面
public interface MyInterface<E> {
public abstract void mth(E e);
}
package day2;
//泛型介面實作類
public class MyInterfaceImpl1<E> implements MyInterface<E>{
@Override
public void mth(E e)
{
System.out.println(e);
}
}
15.多型
1、向上轉型實作多型
public class TestMainClass {
public static void main(String[] args) {
A[] oArray = new B[6];
int sum1=0, sum2=0;
for (int i=0; i<oArray.length; i++) {
oArray[i] = new B(i);
sum1 += oArray[i]._value;
sum2 += ((B)oArray[i])._value;
}
System.out.println("Sum1 = " + sum1);
System.out.println("Sum2 = " + sum2);
}
}
class A {
public int _value = 1;
public A() {
_value = 2;
}
}
class B extends A {
public int _value = 3;
public B(int value) {
_value = value;
}
}
Sum1 = 15 Sum1 = 12
Sum2 = 18 Sum2 = 15
易錯點
對于new B(0);這個物件的value是通過B的有參構造器賦值,故value為0
但當將其存入A類參考的陣列時,由于父類參考指向子類物件只能訪問父類資料成員、子類重寫方法與父類其它方法(下面會解釋為什么),所以oArray[i]訪問的永遠都是2 (因為B類構造方法呼叫前會默認呼叫父類預設構造方法),或許可以理解為oArray[]中放的都是A類參考指向B類物件(getClass獲得的一定是B),因此oArray[i] = new B(i);這句話干的事情是:第i個A類參考現在指向這個新的B,那么顯然當我們試圖使用oArray[i]去訪問這個物件時只能訪問A的資料成員和B中的重寫方法及A的其它方法,而剛才的賦值是隱式呼叫父類構造方法給value賦了2,放到子類物件的super空間的value域(見下圖);子類構造方法賦了B類物件的value屬性,放在子類內容的value域,

而對于sum2 += ((B)oArray[i])._value;親測這個強轉并不會呼叫構造方法,那么它干的事情就是把oArray[i]從一個裝A類參考的陣列轉成一個裝B類參考的陣列,指向的還是B類物件,那么此時物件的資料成員就可以訪問了(值在第一步已經賦進去了,只是A類參考訪問不到而已),所以是0加到5,
關于為什么父類參考訪問不到子類資料成員,我的理解是多型中資料成員和方法的系結方式是不同的,對于方法而言

如果創建的是一個B類物件,那么里面就會有父類的同名方法,通過虛擬方法表在被訪問時映射過來,但是對于資料成員而言,其實使用的是靜態系結 例如Father father = new Son(); 在編譯期間 father就是個Father物件,系統不知道實際上它是一個 Son物件,這得在運行期間由JVM判斷(父類的靜態方法和資料成員遵循相同的規則),因此自然是編譯期它是什么型別就訪問什么型別的資料成員,
2、介面回呼實作多型
介面回呼是指:可以把使用某一介面的類創建的物件的參考賦給該介面宣告的介面變數,那么該介面變數就可以呼叫被類實作的介面的方法,實際上,當介面變數呼叫被類實作的介面中的方法時,就是通知相應的物件呼叫介面的方法,這一程序稱為物件功能的介面回呼,
interface People{
void peopleList();
}
class Student implements People{
public void peopleList(){
System.out.println("I’m a student.");
}
}
class Teacher implements People{
public void peopleList(){
System.out.println("I’m a teacher.");
}
}
public class Example{
public static void main(String args[]){
People a;
a=new Student();
a.peopleList();
a=new Teacher();
a.peopleList();
}
}
介面回呼父類方法更加的簡潔,是抽象方法不需要有方法體;
都是編譯時顯示父類方法行為特征,運行時顯示子類行為特征,
(Thinking in Java)
使用介面的核心原因:為了能夠向上轉型為多個基型別,即利用介面的多實作,可向上轉型為多個介面基型別,
從實作了某介面的物件,得到對此介面的參考,與向上轉型為這個物件的基類,實質上效果是一樣的,
這兩個概念是從兩個方面來解釋一個行為,介面回呼的概念,強調使用介面來實作回呼物件方法使用權的功能,而向上轉型則牽涉到多型和運行期系結的范疇,
16.介面和抽象類
-
介面不能用于實體化物件,
介面中所有的方法必須是抽象方法,
介面不能包含成員變數,除了 static 和 final 變數,
介面方法默認為public abstract
-
抽象類和介面的區別
抽象類中的方法可以有方法體,就是能實作方法的具體功能,但是介面中的方法不行,
抽象類中的成員變數可以是各種型別的,而介面中的成員變數只能是 public static final 型別的,
介面中不能含有靜態代碼塊以及靜態方法,而抽象類是可以有靜態代碼塊和靜態方法,
一個類只能繼承一個抽象類,而一個類卻可以實作多個介面, -
成員變數:
抽象類可有成員變數,也可以有常量
介面只能有常量(因為他無法給成員賦值,只能一開始就初始化)
成員方法:
抽象類可有抽象方法,也可有非抽象方法
介面只能有抽象方法,而且方法有默認修飾符public abstract
17.多載的順序
public class Test{
public static void say(Object arg){
System.out.println("object");
}
public static void say(int arg){
System.out.println("int");
}
public static void say(char arg){
System.out.println("char");
}
public static void say(long arg){
System.out.println("long");
}
public static void say(char... arg){
System.out.println("char...");
}
public static void say(Character arg){
System.out.println("Character");
}
public static void say(Serializable arg){
System.out.println("Serializable");
}
public static void main(String[] args){
say('a');
}
}
輸出結果為char
這也很好解釋,優先肯定匹配型別相同的
如果我們注釋掉say(char arg)
輸出結果為int
這是因為此時發生了一次型別轉換,'a’除了可以代表一個字串,還可以代表數字97,因此引數型別為int的多載也是合適的,
繼續注釋say(int arg)
輸出結果為long
這個時候發生了兩次自動型別轉換,'a’轉型為整數97之后進一步轉型為97L,匹配的引數型別為long的多載,
代碼中沒有寫float,double的多載,實際上自動型別轉換還能繼續發生多次,按照char->int->long->float->double的順序進行匹配,但不會匹配到byte和short型別的多載,因為char轉byte和short的轉型是不安全的,
我們繼續注釋say(long arg)
輸出結果為Character
這次發生了一次自動裝箱
繼續注釋掉say(Character arg)
輸出結果為Seriablizable
這是因為java.long.Seriablizable是java.long.Character類實作的介面,當自動裝箱之后發現找不到裝箱類,但是找到裝箱類實作的介面型別,所以緊接著又發生了一次自動轉型,
繼續注釋say(Seriablizable arg)
結果就變成了Object
這是因為char裝箱成Character之后轉型父型了,因為Object是所有類的父類,
繼續注釋say(Object arg)
結果為Char...
可見陣列引數多載的優先級最低,
18.網路編程
服務端實作:
public class ServerTCP {
public static void main(String[] args) throws IOException {
System.out.println("服務端啟動 , 等待連接 .... ");
ServerSocket ss = new ServerSocket(6666);
Socket server = ss.accept();
InputStream is = server.getInputStream();
byte[] b = new byte[1024];
int len = is.read(b);
String msg = new String(b, 0, len);
System.out.println(msg);
OutputStream out = server.getOutputStream();
out.write("我很好,謝謝你".getBytes());
out.close();
is.close();
server.close();
}
}
客戶端實作:
public class ClientTCP {
public static void main(String[] args) throws Exception {
System.out.println("客戶端 發送資料");
Socket client = new Socket("localhost", 6666);
OutputStream os = client.getOutputStream();
os.write("你好么? tcp ,我來了".getBytes());
InputStream in = client.getInputStream();
byte[] b = new byte[100];
int len = in.read(b);
System.out.println(new String(b, 0, len));
in.close();
os.close();
client.close();
}
}
19.IO流輸入輸出抽象類 檔案輸入輸出
一、位元組流
輸入流是將資源資料讀入到緩沖Buffer中,輸出流是將緩沖Buffer中的資料按照指定格式寫出到一個指定的位置(從記憶體寫到硬碟)
java程式–>JVM–>OS–>OS的寫資料方法–>寫入
| 輸入流 | 輸出流 | |
|---|---|---|
| 位元組流 | InputStream | OutputStream |
| 字符流 | Reader | Writer |
1.OutputStream
public void close():關閉此輸出流并釋放與此流相關聯的任何系統資源,public void flush():重繪此輸出流并強制任何緩沖的輸出位元組被寫出,public void write(byte[] b):將 b.length位元組從指定的位元組陣列寫入此輸出流,public void write(byte[] b, int off, int len):從指定的位元組陣列寫入 len位元組,從偏移量 off開始輸出到此輸出流,public abstract void write(int b):將指定的位元組輸出流,
FileOutputStream
FileOutputStream(String name);
FileOutputStream(File file);
構造方法作用:
-
創建一個FileOutputStream物件
-
根據引數(檔案/檔案路徑)創建一個空檔案
-
讓FileOutputStream物件指向該檔案
使用:
-
創建一個FileOutputStream物件,指定目的地
-
呼叫write,將資料寫入檔案
//將指定的位元組輸出流
public abstract void write(int b);
- 釋放資源
例:write(97);首先會轉為二進制1100001,存到硬碟中
任何文本編輯器(如記事本)打開檔案時都會查詢編碼表,將位元組轉換為字符,即引數寫入97會得到a而非97
如果使用其它兩個write一次性寫入多個位元組,那么:
- 如果第一個位元組是正的0-127,會查詢ASCII碼表
- 如果第一個位元組是負數,會將一二兩個位元組合并去查GBK(系統默認碼表)
資料追加續寫
public FileOutputStream(File file, boolean append): 創建檔案輸出流以寫入由指定的 File物件表示的檔案,public FileOutputStream(String name, boolean append): 創建檔案輸出流以指定的名稱寫入檔案,
這兩個構造方法,引數中都需要傳入一個boolean型別的值,true 表示追加資料,false 表示清空原有資料,這樣創建的輸出流物件,就可以指定是否追加續寫
public static void main(String[] args) throws IOException
{
FileOutputStream fos = new FileOutputStream("D:/test/fos.txt",true);
byte[] b = "abcde".getBytes();
fos.write(b,2,2);
fos.write(b);
fos.close();
}
2.InputStream
當完成流的操作時,必須呼叫close方法釋放系統資源
public void close():關閉此輸入流并釋放與此流相關聯的任何系統資源,public abstract int read(): 從輸入流讀取資料的下一個位元組,public int read(byte[] b): 從輸入流中讀取一些位元組數,并將它們存盤到位元組陣列b中 ,
FileInputStream
FileInputStream(File file): 通過打開與實際檔案的連接來創建一個 FileInputStream ,該檔案由檔案系統中的 File物件 file命名,FileInputStream(String name): 通過打開與實際檔案的連接來創建一個 FileInputStream ,該檔案由檔案系統中的路徑名 name命名,read(),每次可以讀取一個位元組的資料,提升為int型別,讀取到檔案末尾,回傳-1
FileInputStream fis = new FileInputStream("read.txt");
int b ;
while ((b = fis.read())!=-1) {
System.out.println((char)b);
}
fis.close();
read(byte[] b),每次讀取b的長度個位元組到陣列中,回傳讀取到的有效位元組個數,讀取到末尾時,回傳-1
public class FISRead {
public static void main(String[] args) throws IOException{
FileInputStream fis = new FileInputStream("read.txt"); //檔案中為abcde
int len ;
byte[] b = new byte[2];
while (( len= fis.read(b))!=-1) {
//len 每次讀取的有效位元組個數
System.out.println(new String(b,0,len));
}
fis.close();
}
}
注意一定要用System.out.println(new String(b,0,len));因為每次不一定都能讀取到兩個位元組,如果只讀了一個那就會有一個沒有重繪,用len獲取有效位元組數
3.輸入輸出搭配
public class Copy {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\test.jpg");
FileOutputStream fos = new FileOutputStream("test_copy.jpg");
byte[] b = new byte[1024];
int len;
while ((len = fis.read(b))!=-1)
{
fos.write(b, 0 , len);
}
fos.close();
fis.close();
}
}
二、字符流
1.Reader
public void close():關閉此流并釋放與此流相關聯的任何系統資源,public int read(): 從輸入流讀取一個字符,public int read(char[] cbuf): 從輸入流中讀取一些字符,并將它們存盤到字符陣列 cbuf中 ,
FileReader
FileReader(File file): 創建一個新的 FileReader ,給定要讀取的File物件,FileReader(String fileName): 創建一個新的 FileReader ,給定要讀取的檔案的名稱,
與位元組流基本相同,類比即可
2.Writer
void write(int c)寫入單個字符,void write(char[] cbuf)寫入字符陣列,abstract void write(char[] cbuf, int off, int len)寫入字符陣列的某一部分,off陣列的開始索引,len寫的字符個數,void write(String str)寫入字串,void write(String str, int off, int len)寫入字串的某一部分,off字串的開始索引,len寫的字符個數,void flush()重繪該流的緩沖,void close()關閉此流,但要先重繪它,
FileWriter
FileWriter(File file): 創建一個新的 FileWriter,給定要讀取的File物件,FileWriter(String fileName): 創建一個新的 FileWriter,給定要讀取的檔案的名稱,
關閉資源時,與FileOutputStream不同,如果不關閉,資料只是保存到緩沖區,并未保存到檔案,
因為內置緩沖區的原因,如果不關閉輸出流,無法寫出字符到檔案中,但是關閉的流物件,是無法繼續寫出資料的,如果我們既想寫出資料,又想繼續使用流,就需要flush 方法了,
flush:重繪緩沖區,流物件可以繼續使用,close:先重繪緩沖區,然后通知系統釋放資源,流物件不可以再被使用了,
public class FWWrite {
public static void main(String[] args) throws IOException {
// 使用檔案名稱創建流物件
FileWriter fw = new FileWriter("fw.txt");
// 寫出資料,通過flush
fw.write('刷'); // 寫出第1個字符
fw.flush();
fw.write('新'); // 繼續寫出第2個字符,寫出成功
fw.flush();
fw.write('關'); // 寫出第1個字符
fw.close();
fw.write('閉'); // 繼續寫出第2個字符,【報錯】java.io.IOException: Stream closed
fw.close();
}
}
三、位元組流和字符流的區別
- 位元組流操作的基本單元為位元組;字符流操作的基本單元為Unicode碼元,
- 位元組流默認不使用緩沖區;字符流使用緩沖區,
- 位元組流通常用于處理二進制資料,實際上它可以處理任意型別的資料,但它不支持直接寫入或讀取Unicode碼元;字符流通常處理文本資料,它支持寫入及讀取Unicode碼元,
位元組流直接操作檔案本身,不關也會寫進去
字符流操作時使用記憶體了緩沖區,在close()或flush()時會將緩沖區中內容輸出到檔案
20.多執行緒&等待喚醒機制

1.多執行緒
yield( ) :執行緒讓步,當一個執行緒使用了這個方法之后,它就會把自己CPU執行的時間讓掉,讓自己或者其它的執行緒運行,在大多數情況下,yield()將導致執行緒從運行狀態轉到可運行狀態,
sleep() 是執行緒類(Thread)的靜態方法,呼叫此方法會讓當前執行緒暫停執行指定的時間,將執行機會(CPU)讓給其他執行緒,但是物件的鎖依然保持,因此休眠時間結束后會自動恢復(執行緒回到就緒狀態),
wait() 是Object類的方法,呼叫物件的wait()方法導致當前執行緒放棄物件的鎖(執行緒暫停執行),進入物件的等待池(wait pool),只有呼叫物件的notify()方法(或notifyAll()方法)時才能喚醒等待池中的執行緒進入等待鎖定池(lock pool),如果執行緒重新獲得物件的鎖就可以進入就緒狀態
plus:wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在任何地方使用,
suspend() 方法的作用是將一個執行緒掛起(暫停),不會釋放鎖,
resume() 方法的作用則是將一個掛起的執行緒重新開始并繼續向下運行,
使用 suspend 和 resume 方法容易出現因為執行緒的暫停而導致資料不同步的問題,現已被棄用,
Thread.join()方法用于把指定的執行緒加入到當前執行緒中,把當前執行緒的CPU執行時間讓給另一個執行緒,比如在執行緒B中呼叫了執行緒A的Join()方法,直到執行緒A執行完畢后,才會繼續執行執行緒B,
2.等待喚醒機制
wait:執行緒不再活動,不再參與調度,進入 wait pool中,因此不會浪費 CPU 資源,也不會去競爭鎖了,這時的執行緒狀態即是 WAITING,等著別的執行緒通知(notify) 在這個物件上等待的執行緒從wait set 中釋放出來,重新進入到調度佇列(ready queue)中
notify:則選取所通知物件的 wait set 中的一個執行緒釋放,
notifyAll:則釋放所通知物件的 wait set 上的全部執行緒,
哪怕只通知了一個等待的執行緒,被通知執行緒也不能立即恢復執行,因為它當初中斷的地方是在同步塊內,而此刻它已經不持有鎖,所以它需要再次嘗試去獲取鎖(很可能面臨其它執行緒的競爭),成功后才能在當初呼叫 wait 方法之后的地方恢復執行,
如果能獲取鎖,執行緒就從 WAITING 狀態變成 RUNNABLE 狀態,否則,從 wait set 出來,又進入 entry set,執行緒就從 WAITING 狀態又變成 BLOCKED 狀態
1.wait方法與notify方法必須要由同一個鎖物件呼叫,因為對應的鎖物件可以通過notify喚醒使用同一個鎖物件呼叫的wait方法后的執行緒,
2.wait方法與notify方法是屬于Object類的方法的,因為:鎖物件可以是任意物件,而任意物件的所屬類都是繼承了Object類的,
3.wait方法與notify方法必須要在同步代碼塊或者是同步函式中使用,必須要通過鎖物件呼叫這2個方法,
設計思路上就是創建一個類,該類物件為鎖物件,故這個類應該要有某個標志狀態的屬性,兩個(或多個)生產者、消費者類均使用該類物件作為鎖,在狀態為某一特值時等待,在被喚醒后(就是wait后面)寫將要執行的陳述句,執行完后notify讓另一個執行緒進行類似流程操作即可,
21.資料共享機制
多執行緒共享資料
1.如果每個執行緒執行的代碼相同,可以使用同一個Runnable物件,這個Runnable物件中有那個共享資料,例如,賣票系統就可以這么做,
2.如果每個執行緒執行的代碼不同,這時候需要用不同的Runnable物件,例如銀行存取款
有三種方法來解決此類問題:
1.將共享資料封裝成另外一個物件,然后將這個物件逐一傳遞給各個Runnable物件,每個執行緒對共享資料的操作方法也分配到那個物件身上完成,這樣容易實作針對資料進行各個操作的互斥和通信
public class doThreadShareData {
//java.util.concurrent
public static void main(String[] args) {
ShareData shareData =new ShareData();
new Thread(new Runnable() {
@Override
public void run() {
shareData.deccreament();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
shareData.increment();
}
}).start();
}
}
class ShareData {
private int j = 0;
public synchronized void increment(){
j++;
}
public synchronized void deccreament(){
j--;
}
}
2.將Runnable物件作為一個類的內部類,共享資料作為這個類的成員變數,每個執行緒對共享資料的操作方法也封裝在外部類,以便實作對資料的各個操作的同步和互斥,作為內部類的各個Runnable物件呼叫外部類的這些方法,
3.使用Map
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class ThreadScopeShareData {
public static Map<Object, Integer> map = new HashMap<Object, Integer>();
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
public void run() {
int data = new Random().nextInt();
System.out.println(Thread.currentThread().getName() + " set data:" + data);
map.put(Thread.currentThread(), data);
//將值按照Thread去設值,取的時候也按Thread去取,以保證資料的共享,但又保證了物件的獨立.
new A().get();
new B().get();
}
}).start();
}
}
static class A {
public int get() {
data = map.get(Thread.currentThread());
System.out.println("a from thread:" + Thread.currentThread().getName() + " is " + data);
return data;
}
}
static class B {
public int get() {
int data = map.get(Thread.currentThread());
System.out.println("b from thread:" + Thread.currentThread().getName() + " is " + data);
return data;
}
}
}
22.Collection介面各種實作類的常用方法 Collections工具類
Collection
Collection<String> coll = new ArrayList<>();
//多型性
一、List(有序;允許重復;有索引值-可用for遍歷)
- Vector集合
- ArrayList集合(底層為陣列,查詢快,增刪慢)
- LinkedList集合(底層為鏈表,查詢慢,增刪快)
二、Set(無序;不允許重復;無索引值)
- TreeSet集合(底層為二叉樹,用于排序)
- HashSet集合(底層為哈希表+紅黑樹,存取無序)
1)LinkedHashSet集合(底層為哈希表+鏈表,存取有序)
三、所有單列集合有共性的方法
- add,remove,clear,isEmpty
- contains判斷集合中是否包含某個元素,回傳布林值
- toArray集合轉陣列
List
- 有序,存取順序一致
- 有索引值
- 允許元素重復
含索引值的方法
- add(int index, E element) 將元素添加至索引值對應位置,后面的元素后移
- get(int index) 回傳相應位置元素
- remove(int index) 移除指定位置元素并回傳該元素
- set(int index, E element) 用元素替換相應位置元素,回傳被替換的元素
一、ArrayList
底層為陣列,查詢快,增刪慢
1.長度可變化
2.泛型,集合中全部元素為同一型別,泛型只能是參考型別(例如類的物件),不能是基本型別(可以是String,不能說int這些,因為集合中存盤的是地址值,而基本資料型別沒有地址值),要裝基本型別就需要使用對應的包裝類,除了int對應Integer,char對應Character,其它都是基本型別的首字母大寫即可ArrayList<String> list = new ArrayList<>();3.直接列印得到的是內容(無內容列印中括號)而不是地址值(其它非基本型別直接列印一般為地址值,ArrayList重寫了toString方法)
4.添加內容使用add,注意添加內容型別必須與宣告的泛型相同,回傳值為添加成功與否的布林值
5.讀取/洗掉使用get和remove方法,引數為索引值
6.size方法獲取集合長度,為包含的元素個數
7.集合作為方法的引數或回傳值都是以地址值傳送
二、LinkedList
底層為雙向鏈表,增刪快,查找慢
有多個對首尾元素進行操作的方法
1.addFirst,addLast方法將元素插入首/尾
2.push方法將元素推入堆疊(等效于addFirst)
3.removeFirst,removeLast方法將首/尾元素移除并回傳
4.pop方法從堆疊中彈出元素(等效于removeFirst)
5.getFirst/getLast獲取首尾元素
三、Vector
與ArrayList類似,較少使用
Set
- 不允許重復
- 無索引值,不能使用for回圈遍歷
一、HashSet
哈希值為十進制邏輯地址(==比較的是物理地址),通過hashCode方法可獲得,String類重寫了hashCode方法
add方法會呼叫hashCode方法,如果出現相同哈希值會呼叫equals方法,相同則不再存盤
底層為哈希表(因此存盤的元素必須重寫hashCode和equals方法)
哈希表=陣列+鏈表(同組元素過多時轉為紅黑樹),陣列存盤各元素哈希值,哈希值相同的元素在一個分組下以鏈表形式存盤,多于8個時轉為紅黑樹存盤,查詢速度快
存取順序不一定一致
遍歷可以使用迭代器或for each回圈
二、LinkedHashSet
將HashSet的鏈表改為雙向鏈表,是有序的HashSet,仍然不允許重復
Collections集合方法
均為靜態方法,直接用Collections類名呼叫
一、添加多個元素到指定集合中
Collections.addAll(集合名,元素1、元素2...);
二、打亂集合
Collections.shuffle(集合名);
三、按默認規則排序(升序/自然順序)
Collections.sort(集合名);
被排序的集合元素型別必須重寫了Comparable介面下的compareTo方法
方法回傳當前資料成員-引數對應資料成員即為升序排序
package day3;
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Person p) {
return this.getAge() - p.getAge();
}
}
四、按傳入的比較器排序
在主類中重寫Comparator(在util包中)下的compare方法(對兩個引數物件進行比較,不涉及當前物件)
Collections.sort(list3, new Comparator<Person>()
{
@Override
public int compare(Person p1, Person p2)
{
return p2.getAge() - p1.getAge();
}
});
23.Map

1.雙列集合,為鍵值對形式,類似于python的字典
2.key不允許重復,value可重復
一、HashMap
1.實作了Map介面
2.底層為哈希表,查詢速度快
3.存取無序
1)LinkedHashMap
1.繼承了HashMap
2.底層為哈希表+鏈表
3.存取有序
2)存盤自定義型別元素
由于key不重復,故自定義類必須重寫hashCode和equals方法
二、HashTable
1.底層為哈希表,單執行緒(HashMap為多執行緒),速度慢
2.key和value均不能為null
三、常用方法
1.put(key,value) 將鍵值對插入集合,若key重復則回傳被替代的value,否則回傳null
2.remove(key) 洗掉指定鍵值對,若key存在回傳對應的value,不存在則回傳null
3.get(key) 獲得鍵對應的值,key不存在回傳null
4.containsKey(key) 判斷是否包含指定鍵,回傳布林值
四、遍歷Map
1.keySet() 將集合中全部鍵取出存盤到一個Set集合中,再遍歷Set集合通過get(key)方法遍歷value
2.Entry介面
在Map創建時會自動創建Entry物件記錄鍵和值,一個鍵值對對應一個Entry物件
(1) entrySet() 將Map內全部Entry物件取出放入Set
(2) getKey() 獲取key
(3) getValue() 獲取value
24.執行緒安全
1.執行緒安全問題產生情況
若有多個執行緒同時執行寫操作,一般都需要考慮執行緒同步,否則的話就可能影響執行緒安全,
private int ticket = 100;
@Override
public void run() {
while (true)
{
if(ticket > 0)
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "張票");
ticket--;
}
}
}
Runnable run = new RunnableImpl();
Thread thread1 = new Thread(run);
Thread thread2 = new Thread(run);
Thread thread3 = new Thread(run);
thread1.start();
thread2.start();
thread3.start();
三個執行緒搶奪CPU執行權,在sleep處失去執行權,這時判斷條件(ticket數量)會因為其它執行緒執行而改變,判斷條件在不一定成立的情況下向后執行,出現執行緒安全問題
解決思路:在一個執行緒涉及到訪問共享資料時,不管是否失去執行權其它執行緒必須等待
2.執行緒同步
解決訪問共享資源時產生的執行緒安全問題
(1)同步代碼塊
synchronized同步使相應區塊資源實行互斥訪問
synchronized(鎖物件)
{
//需要同步操作的代碼
}
鎖物件可以是任意物件,只需保證每個執行緒均使用同一物件即可(也就是放在方法外面宣告)
鎖物件只允許一個執行緒在同步代碼塊中執行
while (true)
{
synchronized (obj)
{
if(ticket > 0)
{
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "張票");
ticket--;
}
}
}
原理:執行到synchronized代碼塊時會檢查是否有鎖物件,如果有則獲取并執行,沒有就阻塞,執行完歸還(這就是為什么鎖物件只能有一個)
(2)同步方法
public synchronized void method(){}
把訪問共享資料的代碼放進去即可
@Override
public void run() {
while (true)
{
payTicket();
}
}
public synchronized void payTicket()
{
if(ticket > 0)
{
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "張票");
ticket--;
}
}
原理是一樣的,只不過鎖物件換成了this(執行緒實作類物件)
注意如果同步方法是靜態的,那么使用的鎖就必須是所在類的位元組碼檔案物件,也就是 類名.class,解釋來說,靜態是和類一起加載的,所以在靜態加載的時候是不可能有object的物件,所以鎖就必須用在靜態加在之前的一個物件,
(3)靜態同步方法
將方法和相關變數宣告為靜態也可實作同步,此時鎖物件是實作類的class檔案物件(如RunnableImpl.class)
(4)Lock鎖
//獲取鎖
void lock();
//釋放鎖
void unlock();
創建一個ReentrantLock(Lock介面實作類)物件,在可能產生安全問題的代碼前后分別呼叫lock和unlock
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true)
{
lock.lock();
if(ticket > 0)
{
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "張票");
ticket--;
}
lock.unlock();
}
}
考慮到總是要釋放鎖的,為了提高效率,更好的寫法是:
while (true)
{
lock.lock();
if(ticket > 0)
{
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "張票");
ticket--;
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
}
25.一些小細節
25.1 實作Runnable創建多執行緒的方式
應該是:
Runnable run = new RunnableImpl();
Thread thread1 = new Thread(run);
Thread thread2 = new Thread(run);
thread1.start();
thread2.start();
而不是:
Thread thread1 = new Thread(new RunnableImpl());
Thread thread2 = new Thread(new RunnableImpl());
thread1.start();
thread2.start();
25.2 靜態方法訪問的變數也必須是靜態的
25.3 UTF-8中一個中文是三位元組,GBK中一個中文是兩位元組
25.4 (輸出流)Windows系統里,每行結尾是 回車+換行 ,即\r\n
25.5 流操作完畢后,必須釋放系統資源,呼叫close方法
25.6 流的關閉原則:先開后關,后開先關
25.7 write(97)寫的是a(ASCII),想寫a要用write('a')
25.8 測驗類一定不要忘了main函式
25.9 如果==作用于基本資料型別的變數則直接比較其存盤的"值"是否相等;如果作用于參考型別的變數(String),則比較的是所指向的物件的地址(即是否指向同一個物件),如果沒有對equals方法進行重寫,則比較的是參考型別的變數所指向的物件的地址
25.10 父類的構造方法是不會被子類繼承的,但是子類的構造方法中會有一個隱式的super()來呼叫父類中的無引數構造方法,顯式呼叫時同理
25.11 continue:不再執行回圈體中continue陳述句之后的代碼,直接進行下一次回圈
25.12 catch (Exception e)能抓到所有例外,后面的catch不會再執行,事實上后面再抓子類例外是錯誤,IDEA根本就不通過(Error:(23, 9) java: 已捕獲到例外錯誤java.lang.ArithmeticException),但考試時或許可以忽略這一點
25.13 注意分辨println和print
25.14 追加寫入通過構造器第二個引數實作
OutputStream output = new FileOutputStream(orgFile,true);
25.15 執行緒鎖要宣告為靜態,一個物件一個鎖和沒有是一樣的,當然也可以直接把空字串作為物件鎖
25.16 檔案操作基本上所有代碼都是寫在main方法里的,不要忘了寫,更不要忘了拋例外
25.17 序列化應用于IO流讀取物件時,只需要物件類實作標記介面Serializable即可使用序列化輸入輸出流存取(輸出對應序列化,輸入對應反序列化),靜態優先于非靜態加載到記憶體,因此static修飾的成員不能被序列化(序列化的都是物件),被transient(瞬態關鍵字)修飾的成員也不能被序列化(作用主要就是為了防止成員被序列化)
25.18 注意,如果實作多個介面中有重名方法且它們僅僅是回傳型別不同(其他情況下這兩個方法被識別為不同,可以分別重寫),那么就不能同時實作兩個介面方法(回傳型別相同顯然實作前面那個就行,類只需要一個這樣的方法),可以通過兩個內部類分別實作來解決,繼承也是同理,子類如果有一個和父類只有回傳型別不同的方法就會報錯
25.19 父類參考指向子類物件,對于方法重寫,編譯看父類,運行看子類
25.20 has a是關聯關系,關聯分雙向關聯和單向關聯,雙向關聯是A,B類分別持有對方的參考(或是對方的屬性),單向關聯是一方持另一方的參考
25.21 構造方法中只能使用this(引數串列)呼叫構造方法,不能直接使用類名呼叫其他構造,這是因為構造方法非靜態,必須要用物件去呼叫(this就是當前物件)
25.22 float f = 1.1;是錯的,應該是float f = 1.1f;同樣int i = 4L也是錯的,long不能隱式轉換成int
25.23 注意i–和--i的區別,下面回圈體的陳述句會執行一次,–i則不會執行
int i = 5;
while (i-- > 4)
{
System.out.println("aaa");
}
同理return a++會回傳a之后再把a自增
25.24 final修飾類物件引數其屬性可修改,只是限制類的派生而已,但如果是基本資料型別則不可修改
void add(final A a)
{
a._value++;
}
25.25 靜態方法不能用this呼叫,直接方法名即可呼叫
25.26 子類重寫父類方法,訪問權限不能降低(protected可以重寫為public)
25.27 靜態代碼塊(或自由代碼塊)可以有多個,相互之間是平級的,按照順序執行
25.28 >>為符號擴展,>>>則是將高位補0,<<同理且不存在<<<
25.29 基礎資料型別的強制轉換遵循小到大為隱式轉換,大到小需要顯式指定

25.30 在一個類里面有main函式,那么main函式可以直接訪問這個類的private成員,因為main函式也是類函式(只是他是靜態的),可以訪問私有成員
25.31 Java與C++不同,不支持方法中的引數帶默認值,但是可以使用函式多載來實作該功能
(最后30分倆題一個題讓寫實作類,有陷阱,去做轉換,比如不支持小數比較,兩位小數進行比較時就先乘100再比)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/241307.html
標籤:java
上一篇:Java實作簡單的計算器
