标签:
还是先走一个小球吧,已经做了很多次了,我们开始思考,如果用户按了键盘上的一个键,那么子啊整个计算机系统中,谁最先知道这件事情呢?这个大家都可以猜出来没错就是键盘,不过后面的事情,键盘到底是通知给谁了?我们干脆跳过中间环节吧,一定会到操作系统对吧?如果你的程序要能够响应用户的输入,是不是操作系统要将这件事情通知给你的程序?系统怎么能够把一件事情通知到你的程序呢?其实之前我们遇到过类似和系统打交道的事情无论是开始的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
}
下一次我要用这个按键的监听去做一个打字母的游戏,不过我相信应该大家都有能力做出来,不光是这个,还有雷电和坦克大战这些经典的FC游戏。
今天就到这里,下期再见
by WhiteJavaCoder
标签:
原文地址:http://blog.csdn.net/mrinvincible/article/details/51161883