11 异常处理

来源:互联网 发布:淘宝店标图片 编辑:程序博客网 时间:2024/06/06 04:30

创建MIDI音乐播放器

创建音效应用程序,包括BeatBox Drum播放机,涉及创建Swing GUI,网络连接,连接到输入,输出设备

JavaSoundAPI

放在J2SE类函数库的一组类与接口,分为两部分:MIDI和取样。

MIDI

Music Instrument Digital Interface,不同电子发声装置沟通的标准协议,本身不具备声音,带的是有MIDI播放功能装置的指令,当作乐谱.MIDI数据表示执行的动作,仅仅其指导作用,如播放中央,音量大小和长度。对BeatBox来说,只会使用内建,纯软件的乐器音效,称为synthesizer或software synth。

Sequencer

首先要取得Sequencer对象,此对象将MIDI数据送到产生音乐的装置中,本应用中sequencer指的是播放的装置

import javax.sound.midi.*;public class MusicTest1{   public void play(){   Sequencer sequencer = MidiSystem.getSequencer(); //对象起将MIDI信息组合成乐曲的作用   System.out.printIn('got a sequencer')     } public static void main(String[] args){   MusicTest1 mt =new MusicTest1();   mt.play();}}


但因为有异常情况必须处理,上述代码无法通过编译


异常处理机制


JAVA的异常处理机制是中简洁,轻量化的执行期间例外状况处理方式。依赖于可能产生异常的方法,预先准备处理程序。


方法的声明中存在throws语句代表该方法可能抛出异常.编译器要确定你了解所调用的方法有风险,所以要把有风险的程序代码包含在try\catch块中。catch块摆放异常状况的处理程序。

import javax.sound.midi.*;public class MusicTest1{   public void play(){   try{  Sequencer sequencer = MidiSystem.getSequencer(); //对象起将MIDI信息组合成乐曲的作用  System.out.printIn('got a sequencer')     }catch(MidiUnavailableException ex){     System.out.printIn("Bummer")     }} public static void main(String[] args){  MusicTest1 mt =new MusicTest1();  mt.play();}}


异常是种Exception类型的对象


Exception类型的对象可以是任何它的子类的实例。因为它是对象,所以catch住的也是对象。


try{  //危险动作}catch(Exception ex){ //ex是Exception类型的引用变量  //尝试恢复}

抛出异常的方法和抓住异常的方法


在编写可能会抛出异常的方法,都必须声明有异常


//有风险,会抛出异常的程序public void takeRisk() throws BadException{//必须声明会抛出异常  if(abandonALLHope){    throw new BadException();//创建异常对象并抛出    }}//调用该方法的程序public void crossFingers(){ try{   anObject.takeRisk();     }catch(BadException ex){       ex.printStackTrace();//列出有用信息      } }


方法可以抓住其他方法抛出的异常。异常总是丢回调用方法。


会抛出异常的方法必须声明它有可能这么做。


编译器会核对每件事,除了RuntimeExceptions(不检查异常,可以自己抛出与抓住它们,但没必要)。编译器保证:


1.若抛出异常,则一定要使用throw来声明。

2.若调用会抛出异常的方法,则必定使用try\catch块。

3.方法通过throw关键字抛出异常,如:

throw new BadException();//创建异常对象并抛出


try\catch块的流程控制


方法若不能成功地完成try块,就会把异常丢回调用方的方法。即执行try块的某程序抛出异常,则跳过try块剩下程序,直接到catch块执行,再继续try\catch块下方的内容


finally


finally块用来存放不管有无异常都得执行的程序

try{  //危险动作}catch(Exception ex){ //ex是Exception类型的引用变量  //尝试恢复}finally{  // 无论如何要执行}

执行过程


方法若不能成功完成try块,则跳过try块剩下程序,去到catch块执行,再执行finally内容,然后继续try\catch\finally块下方的内容。若完成try块内容,则再执行finally内容,然后继续try\catch\finally块下方的内容。

注意:try和catch块有return指令,finally块仍然会执行。流程跳到finally然后再回到return指令。


抛出多个异常


方法可以抛出多个异常,但该方法声明中必须含有全部可能的检查异常(若两个以上的异常有共同的父类时,可以只声明该父类即可)


多个异常时catch块出现的先后顺序有影响


public class Laundry{   public void doLaundry() throws PantsException, LingerieException{  //CODE   } }public class Foo{   public void go(){    Laundry laundry = new Laundry();  try{   laundry.doLaundry(); //根据抛出的不同异常对应不同catch块   }catch(PantsException pex){   //code1   }catch(LingerieException lex){  //code2   } }}


异常的多态性


异常也是对象,除了可以被抛出也与对象无区别。因此异常也能以多态方式引用,即子类对象引用可以赋值给父类对象引用,好处在于只用声明父类,而不必明确声明每个可能抛出的异常。对于catch块,可以不用对每个可能的异常处理,只要少数的catch块处理所有的异常。


1.以异常的父型声明会抛出的异常


public void doLaundry() throws ClothingException{  //可抛出任何该异常的子类


2.以抛出的异常父型来catch所有子类的异常


try{

  laundry.doLaundry();

    }catch(ClothingException cex){

     //plans

   }


3.用super来处理所有异常但不代表应该这么做


try{

  laundry.doLaundry();

    }catch(Exception cex){  //捕获所有异常,会搞不清出错处

     //plans

 

4.为每个需单独处理的异常(相同处理方法的异常用父类异常捕获,不同的处理方法需单独编写)编写不同catch块


5.多个catch块时要从小排到大(小大的概念指继承树层次,下层自然比上层小)


不要大篮子在上,小篮子在下(同一层次无所谓,必定代表不同异常)会无法通过编译,因为catch块无法类同重载调出最符合的项目。JVM对于catch块的处理流程是从上往下寻找第一个符合范围的异常处理块。


沿着继承层次向下走,异常类会越来越有特定的去向,catch的篮子越来越小,这是多态的常态现象。


6.若不想处理异常,可通过duck来避开。


调用危险方法,要包含在try\catch块中,但也可以把其duck掉,来让调用你的方法程序来catch该异常。该方法要求会抛出异常(不用你亲自抛出)。


7.ducking只是在踢皮球

使用ducker时,当方法抛出异常,方法会从栈上取出,异常被抛给栈上的方法,即调用方,若调用方为ducker,则此ducker也从栈上取出,异常再被抛给栈上方的方法,如此一路下去。


ducking只是在踢皮球,但早晚要处理这件事,若main()也duck掉异常,则见下例

public class Washer{

   Laundry laundry = new Laundry();

   public void foo() throws ClothingException{

   laundry.doLaundry();

   }

  

   public static void main(String[] args) throws ClothingException{

   Washer a = new Washer();

   a.foo();

   }

 }


上述程序可通过编译,但会运行出错

1)抛出ClothingException

main()调用foo(),foo()调用doLaundry(),doLaundry抛出ClothingException


2)foo()已经duck掉异常

doLaundry()从stack上被取走,异常抛给foo()


3)main()也duck掉异常

foo()也被取走,只剩下JVM


4)JVM驾崩


两种满足编译器的有风险方法的调用方式


1.try\catch块


2.声明(duck掉)

把方法声明为会抛出异常


void foo throws ClothingException{//声明会抛出异常,但无try\catch块

    laundry.doLaundry();          //所以会duck掉,异常给调用方

}


代表调用foo()的程序必须要处理或同样声明异常


public static void main(String[] args) throws ClothingException{//同样声明

   Washer a = new Washer();

   a.foo();  //要不就用try\catch块处理异常

   }

 }


回到音乐播放程序


利用Midi.getSequencer()创建出sequencer对象,方法要求检查异常,则包含在try\catch块中。


异常处理规则


1.catch与finally不能没有try


2.try与catch之间不能有程序


try{

   }

  int a=1; //error

   }catch(Exception ex)


3.try一定有有catchfinally


try{

   x.doStuff();

   }finally{

   }


4.只带有finally的try必须要声明异常。


void go() throws FooException{

try{

   x.doStuff();

   }finally{

   }

}


总结:

1.方法可在运行期间碰到问题抛出异常,而异常是Exception类型对象。


2.可能会抛出异常的方法必须声明为throws Exception


3.如果不打算处理异常,还可以将异常给ducking来通过编译。


实际发出声音

回顾:MIDI数据保存该演奏那些音乐的指令,所以要将其数据传递到某个MIDI装置,再将数据转化为声音。


类比:

Sequencer:CD播放机

sequence:单曲CD

Track:单曲CD的歌曲信息,包括乐曲的音符等信息(Midi Event)

Midi Event:如同乐谱上的音符,也可表示更换乐器的指令


public void play(){

 try{

   Sequencer player = MidiSystem.getSequencer();

   player.open();  //取得Sequencer并打开

   Sequence seq = new Sequence(Sequence.PPQ,4); //创建新的Sequence

   Track track = seq.createTrack(); //从Sequence中创建新的Track

   ShortMessage a = new ShortMessage();  //创建Message

   a.setMessage(144,1,44,100);           //置入指令:发出44音 

  MidiEvent noteOn = new MidiEvent(a,1);//用Message创建MidiEvent,第一拍启动a

   track.add(noteOn);                    //将MidiEvent加到Track中

......                      //Track带有全部的MidiEvent对象

   track.add(noteOff);       //填入MidiEvent

   player.setSequence(seq); //将Sequence送到Sequencer上

   player.starter();

   ......


制作MidiEvent(乐曲信息)


MidiEvent类似于乐谱,指定何时开始播放某个音符(NOTE ON事件)以及何时停止(NOTE OFF 事件)。MIDI指令放在Message对象中,MidiEventMessage加上发音时机组成,发音时机比如第四拍执行指令。MidiEvent指定何时做,Message描述做什么。


Track带有全部的MidiEvent对象,Sequence会根据事件时间组织它们,然后Sequencer根据此顺序播放,同一时间可以执行多个操作,如多个乐器的和声。


Message

Message代表做什么,即要sequencer实际执行的指令。要创建MIDI的Message,要用ShortMessage的实例调用setMessage(),传入该信息的四个参数。

信息的格式

a.setMessage(144,1,44,100)

144作为第一个参数代表信息类型,后面三个参数根据信息类型决定其意义,其中1代表频道,不同频道对应不同乐器,44代表音高,100代表音道,取零值几乎听不到。

144代表打开,即NOTE ON,128代表关闭,即NOTE OFF。


展望

12章:创建GUI,能根据MIDI的音乐节奏画出随机的字符,学习事件处理

13章:独立的BeatBox

14章:存储和还原播放进度



书海拾荒

1.任何继承过RuntimeExceptions的类都不会受编译器关于是否声明它会抛出RuntimeExceptions的检查,同样地,不会管调用方是否认识到运行期间的异常,所以诸如NullPointerException和DivideByZero都不要处理


2.编译器不处理RuntimeExceptions,不需要包含在try\catch块,是因为此类异常多是逻辑错误所致,在开发时可以避免,try\catch块是来处理真正的异常。


3.Exception类可以catch所有异常,包括运行期间(unchecked)的异常。