還是先走一個小球吧,已經做了很多次了,我們開始思考,如果用戶按了鍵盤上的一個鍵,那麼子啊整個計算機系統中,誰最先知道這件事情呢?這個大家都可以猜出來沒錯就是鍵盤,不過後面的事情,鍵盤到底是通知給誰了?我們干脆跳過中間環節吧,一定會到操作系統對吧?如果你的程序要能夠響應用戶的輸入,是不是操作系統要將這件事情通知給你的程序?系統怎麼能夠把一件事情通知到你的程序呢?其實之前我們遇到過類似和系統打交道的事情無論是開始的main,還是重畫的paint,抑或是線程的run都是和系統打交道的,系統和程序交流的唯一辦法就是你准備一個方法,它會在合適的時候來調用。難道我們要一個個地記住方法嗎?Java的設計者提供了一個很好的解決方案——接口
我們現在來認識接口,其實在前面的線程裡我們已經使用了接口Runnable,但是那個時候我們沒有仔細講接口是什麼,為什麼一定要使用接口。
我是個對象,我是個男人類的對象,可以這麼說,男人是從一大堆相似的對象中抽象出來的一個概念,男人是人類的一種,應該是人類的子類,也就是說,人類是從男人和女人中抽象出來的,人類是動物類的子類,那麼動物類就是人類的抽象。我們現在要寫出一個類——動物類,你想想動物類該怎麼寫,我想至少得有吃,喝,拉,撒,睡這些方法,那麼我們就定義這些方法,具體到”吃”這個方法,你覺得有辦法描述清楚動物的”吃”嗎?是不是沒法描述,因為不同的動物的吃有很大差別。
class Animal{
public void eat(){
//描述如何吃
}
}
注釋的位置是不是很難辦,我確切地知道作為一個動物一定要有吃的方法,但是我現在沒有辦法描述,既然這個樣子就干脆不描述了。
class Animal{
public void eat();
}
如果你是在eclipse上這麼寫,你會發現編輯器馬上就會報錯,因為沒有方法的主體,可是不知道怎麼寫主體呀,那你可以在這個方法前說明這個方法是”抽象”的。
class Animal{
public abstract void eat();
}
Animal類的這個地方也立刻報錯了,它提示如果類裡面有抽象方法,那麼這個類也需要說明成抽象的。其實到現在都很好理解,我們抽象,抽象抽象到一定程度,得到了一個叫做動物的類,可是這個類太抽象了,很多東西我沒法描述,那麼我就告訴Java編輯器,這是抽象的,我不具體寫內容了。
abstract class Animal{
public abstract void eat();
}
又有一個問題,抽象類能產生對象嗎?如果你是Java編輯器,別人給了你一個代碼,代碼裡將抽象的類變成對象,你有辦法嗎?所以抽象類沒法生成對象。
我們再想想動物類裡面的其他辦法,你會發現所有的方法都沒法描述,不但方法沒法描述,而且根本找不到聲明變量的需要,我們管這樣的類叫做純抽象類,在Java裡純抽象類會被替換成另一個名字叫做接口。
interface Animal{
public void eat();
}
class Person implements Animal{
}
我們用一個人的類來實現動物的接口,代碼寫到這兒,你就會發現Person下面有紅色波浪線,這個類就出錯了,把鼠標放在Person上面,提示告訴你,必須實現接口Animal裡面抽象的方法eat。這有什麼用?
舉個例子,還是我老師和我說的,假設我現在是一個班的老師,班裡有20個學生,我覺得天天上課太辛苦了,而且錢還賺的少,我現在想出了一個好辦法,我出去接一個軟件開發的活,報價10萬,根據之前的標准,大約有10萬行的代碼量,我的課改成了實訓項目,也都不用上課了,每個學生平均5000行代碼,各自回家去完成,一個學期結束,完成的學生這門課就通過了,而且不但賺了講課的錢,還把外包的軟件的錢給賺了,這個計劃看上去很好,但是有一個問題,就是到最後,我需要將20個人寫的10萬行代碼給合並起來才能拿去售賣,這樣我難道要看懂10萬行代碼嗎?不,這樣看上去太累了,於是我規定,學生們要給自己寫的5000行代碼寫上注釋,每個人最多有200行標注上是給我看的,其他的我不用看就能合並,這個規定在Java裡不需要,所有要給我看的代碼前要寫上public,不需要我看的寫上private,這下子明白了,定義共有,私有的主要目的是為了降低項目經理的工作量,其實在面向對象裡,這個項目項目經理不會看一行,也許每個人交給我的就是5個類,20個人也就是100個類,只需要寫個主程序,new成對象,然後調用就好了。做起來也不是那麼輕松,項目經理不但要知道Java的類怎麼用,是不是還要學習項目組成員的類,於是就產生了很多技術來簡化項目經理的工作,比如沒有構造方法,每個類的項目經理就要多學一個方法。
現在還有一個問題,就是學生干活,那老師沒事兒干,老師要等所有的學生將類都上交了才能寫主程序,這有點耽誤時間,能不能學生和老師同時寫程序呢?現在看來不行,因為在大家上交所有的代碼前,老師並不知道每個類的情況,主要有什麼方法,看起來老師需要事先規定好每個類所包含的方法,然後將描述的文檔交給學生。不過這也有隱患,萬一學生沒有按照文檔的規定完成怎麼辦?這時候接口出場了,老師可以給每個學生分發5個接口,讓大家去實現,實現接口就意味著必須實現裡面的抽象方法,這樣接口就成了項目經理和項目組成員之間的強制性約定了,這時候在回過頭來想想接口這個詞是不是特別符合。
接口不僅僅有這個用處,我這兒還有一個例子。假設我現在接了一個企業的管理軟件項目,規劃財務,生產和銷售系統,但是這個單位的資金不能到位這麼多,現在決定先開發財務和銷售系統,生產系統留著,等到資金到位後再開發。那麼我該怎麼辦?可能我會開發一個菜單,上面有三個選項:財務,生產和銷售,我會將目前不開發的生產系統設置成灰色的,等錢到位了,我再開發成可用的狀態,用戶點擊”生產”,將會產生一個方法調用,調用到我未來開發的生產系統模塊的方法上,也就是說,我要現在就做好這個方法調用,雖然這個方法現在還不存在,我如何確保若干年後如果我不在這個軟件公司了,別人也可以准確無誤的寫出那個被調用的方法呢?我可以在開發這個菜單的時候,留下一個接口,未來開發生產系統的人只要實現這個接口,那麼他的程序就能融入到已有的程序裡。接口就成了前人和後人的約定,怎麼樣?接口這個詞是不是用的特別到位呢?好像目前還找不出比這個詞更適合的詞匯了。
回到任務中去,我要寫一段代碼來響應用戶的鍵盤輸入,如何准確無誤的寫出來一個方法讓系統調用呢?其實系統的調用早就寫好了,現在知道了,系統會提供一個借口給我,我只需要實現這個借口就好了。看起來線程的Runnaable也起到了這個作用,而裡面實現方法就是run。
鍵盤的借口是KeyListener,放在java.awt.event裡面,導入的時候寫
import java.awt.event.*;
這個時候細心的人會發現我之前不是已經導入 了
import java.awt.*;
嗎?不行,我現在告訴你,這個導入就對一層有效果,Eclipse有輔助的工具幫你導入。不過在此還是提醒一下,過度的依賴工具不是一件好事情,自始至終都用記事本來寫代碼的才是真正的編程,因為這樣可以確保每一個字母都是自己碼出來的,會對代碼建立感覺。
import java.awt.*;
import java.awt.event.*;
public class MyBall {
public static void main(String[] args) {
// TODO Auto-generated method stub
Frame w = new Frame();
w.setSize(300, 400);
MyOval mp = new MyOval();
w.add(mp);
w.show();
}
}
class MyOval extends Panel implements KeyListener{
public void paint(Graphics g){
g.fillOval(30, 30, 20, 20);
}
代碼寫到這裡,你會發現MyPanel下面有紅色波浪線,說明有錯誤,我們知道是因為有未實現的抽象方法,這些方法都是我們想要的。在這一行的前面,你能看見一個你能看見一個紅色的小叉圖標,用鼠標點一下,會彈出一個窗口,有一個選項”添加未實現的方法”
就是第一個Add unimplemented methods,就選它,用鼠標點擊,你會看到下面一下子就蹦出了好多的代碼
仔細一看,是3個方法,有KeyPressed(鍵按下),(鍵抬起),KetType(鍵按下),KeyReleased(鍵抬起),KeyType比較特別,前兩個是比較底層的方法,能夠識別一個鍵的動作,但是我們知道鍵盤上有很多組合鍵,比如“SHIFT+A”組合鍵,這就需要KeyType來識別了。我還是把代碼放上來好了
class MyOval extends Panel implements KeyListener{
public void paint(Graphics g){
g.fillOval(30, 30, 20, 20);
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
現在有了鍵盤的輸入方法,但是還差一步,系統並不只帶你能接收,或者說系統不知道你的那個對象要去處理鍵盤輸入,就好像現在有一件事情發生了,我的手機號碼變了,其實對我沒有什麼影響,我想找到誰都可以找的到,只要我有別人的號碼,但是反過來,對別人有影響啊,我是事件的出發者,你是事件的處理者,你因為這件事情,要將手機裡的聯系人中我的聯系方式更新,問題是,如果我沒有你的號碼,我就通知不了你,所以這裡就需要一步——注冊事件。
我們知道實現接口的類是MyPanel,類不管用,它的對象才可能是處理者,那麼mp是處理者,誰是事件的發出者呢?按說應該是鍵盤,但是程序以外的事情我們不關心,這個事件第一個抵達的是窗體,所以我們需要
public class MyBall {
public static void main(String[] args) {
// TODO Auto-generated method stub
Frame w = new Frame();
w.setSize(300, 400);
MyOval mp = new MyOval();
w.add(mp);
//注冊事件
w.addKeyListener(mp);
w.show();
}
不過這裡又出現了一個問題,目前我們的程序裡有兩個對象,其中一個是窗體,窗體上面放了一個面板,大多數情況下窗體是程序的基礎,鍵盤事件會到達窗體,可是如果鼠標在窗體中間的面板上點一下,鍵盤事件就接收不到了,因為當前的有效對象變成了面板,為了避免珍重情況產生,我們加一條語句。代碼變成額這樣
public class MyBall {
public static void main(String[] args) {
// TODO Auto-generated method stub
Frame w = new Frame();
w.setSize(300, 400);
MyOval mp = new MyOval();
w.add(mp);
//注冊事件
w.addKeyListener(mp);
mp.addKeyListener(mp);
w.show();
}
新加的這句話好像有一點怪異,看上去好像我們將mp加到mp上了,這是因為mp具有兩個身份,前面用mp是因為這是Panel的對象,後面用mp是因為它是鍵盤事件的處理者。我們現在集中精力來看看如何處理接收到的事件吧。在我看來,事件處理分三步
1.實現接口
2.注冊事件
3.編寫事件處理程序
先來認識十分重要的語句。還記得前面幾次的”Hello World!”嗎?其實就是在控制台上打印一句話,雖然打印到控制台上很無聊,但是我們還是要學一下。雖然這句話今天看來幾乎沒有任何實用價值了,因為現在大家都用圖形界面,沒有人會將信息通過控制台顯示給用戶看,但是打印這句話能夠幫助我們確定程序是按照預想的方式運行的,我們將在不確定的地方加上這句話,看看運行以後顯示的是不是想要的結果。還有一個用處,這句話能夠幫助我麼探索不知道的內容,後面我將頻繁的使用這種探索方式。
什麼是控制台?有可能是我們偶爾能夠看到的黑色Dos框,不過因為使用的是Eclipse,所以控制台集成到這個軟件的一個窗口裡了,這個窗口就是我們每次關閉程序的窗口。
這條語句是:
System.out.println("需要打印的內容");
我們把這條語句放到代碼中,然後運行,見到窗體後,按動鍵盤,如果有一串a顯示在控制台上,就說說明我們的鍵盤事件機制沒有問題。
附上代碼
class MyOval extends Panel implements KeyListener{
public void paint(Graphics g){
g.fillOval(30, 30, 20, 20);
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
System.out.println("aaaaaaaa");
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
還是老規矩,練習20遍,搞定之後繼續下一步,下一步的問題是鍵盤上有這麼多的按鍵,到底按哪一個啊,不能總顯示一堆a吧,注意到接收的方法的參數了沒有?既然有參數就說嘛有人往裡面傳參值,誰傳的值,當然是調用者了,調用者是系統,那麼就是系統傳值進去,通常你需要的信息都在裡面,打印一下試試。
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
System.out.println(e);
}
結果是什麼,是不是一大堆亂七八糟的結果,別著急,靜下心來看看,裡面有你按得字母,不過這個太氣人了,這麼多東西也沒法用呀。
我們看這個e是什麼類型的東西,當然不是KeyEvent,這不是基本的數據類型,一看就知道是個對象,第一個字母還是大寫,既然是對象,我們拿鼠標點一點,彈出來的都是我們可以用的方法,方法名字也起的夠貼切的,你仔細研究一下就會發現這麼多的方法,其實是有規律的,要得到信息就以get開頭,要修改對象就用set開頭,對不對?
即便是小學生也能發現有個getKeyChar,直譯”得到鍵字符”,既然猜到了,咱們試試。
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
System.out.println(e.getKeyChar());
}
怎麼樣?運行後就是你按的那個按鍵吧,很快你就會發現問題,如果我們用WASD來控制小球還好,但是用上下左右的箭頭呢?你現在按箭頭看看能打印什麼出來,是不是一堆的問號?其實箭頭並不對應字符,這次嘗試失敗,我們看看還有什麼get方法可以用,實在不行就挨個試試,反正也不多我想你能找到getKeyCode,並且能夠試出箭頭的編號是什麼。
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
System.out.println(e.getKeyCode());
}
左37,上38,右39,下40,看來是順時針排列的,現在你能用方向控制小球移動了吧。
下面我來附上本次所有的代碼
mport java.awt.*;
import java.awt.event.*;
public class MyBall {
public static void main(String[] args) {
// TODO Auto-generated method stub
Frame w = new Frame();
w.setSize(300, 400);
MyOval mp = new MyOval();
w.add(mp);
//注冊事件
w.addKeyListener(mp);
mp.addKeyListener(mp);
w.show();
}
}
class MyOval extends Panel implements KeyListener{
int x=30;
int y=30;
public void paint(Graphics g){
g.fillOval(30, 30, 20, 20);
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if(e.getKeyCode()==37){
x--;
}
if(e.getKeyCode()==38){
y--;
}
if(e.getKeyCode()==39){
x++;
}
if(e.getKeyCode()==40){
y++;
}
repaint();
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}