2023年将会持续于B站、CSDN等各大平台更新,可加入粉丝群与博主交流:838681355,为了老板大G共同努力。【商务合作请私信或进群联系群主】
 
继承机制的使用可以复用一些定义好的类,减少重复代码的编写。多态机制可以动态调整对象的调用,降低对象之间的依存关系。
封装将类的某些信息隐藏在类内部,不允许外部程序直接访问,只能通过该类提供的方法来实现对隐藏信息的操作和访问。封装的特点:
1.  只能通过规定的方法访问数据。
2.  隐藏类的实例细节,方便修改和实现。实现封装的具体步骤如下:
1.  修改属性的可见性来限制对属性的访问,一般设为 private 。
2.  为每个属性创建一对赋值(setter)方法和取值(getter)方法,一般设为 public ,用于属性的读写。
3.  在赋值和取值方法中,加入属性控制语句(对属性值的合法性进行判断)。下面以一个员工类的封装为例介绍封装过程。一个员工的主要属性有姓名、年龄、联系电话和家庭住址。假设员工类为 Employee ,示例如下:public  class  Employee  { private  String  name;  private  int  age;  private  String  phone;  private  String  address;  public  String  getName ( )  { return  name; } public  void  setName ( String  name)  { this . name =  name; } public  int  getAge ( )  { return  age; } public  void  setAge ( int  age)  { if  ( age <  18  ||  age >  40 )  { System . out. println ( "年龄必须在18到40之间!" ) ; this . age =  20 ;  }  else  { this . age =  age; } } public  String  getPhone ( )  { return  phone; } public  void  setPhone ( String  phone)  { this . phone =  phone; } public  String  getAddress ( )  { return  address; } public  void  setAddress ( String  address)  { this . address =  address; } } 
如上述代码所示,使用 private  关键字修饰属性,这就意味着除了 Employee  类本身外,其他任何类都不可以访问这些属性。但是,可以通过这些属性的 setXxx ( )  方法来对其进行赋值,通过 getXxx ( )  方法来访问这些属性。
在 age 属性的 setAge ( )  方法中,首先对用户传递过来的参数 age 进行判断,如果 age 的值不在 18  到 40  之间,则将 Employee  类的 age 属性值设置为 20 ,否则为传递过来的参数值。
编写测试类 EmployeeTest ,在该类的 main ( )  方法中调用 Employee  属性的 setXxx ( )  方法对其相应的属性进行赋值,并调用 getXxx ( )  方法访问属性,代码如下:public  class  EmployeeTest  { public  static  void  main ( String [ ]  args)  { Employee  people =  new  Employee ( ) ; people. setName ( "王丽丽" ) ; people. setAge ( 35 ) ; people. setPhone ( "13653835964" ) ; people. setAddress ( "河北省石家庄市" ) ; System . out. println ( "姓名:"  +  people. getName ( ) ) ; System . out. println ( "年龄:"  +  people. getAge ( ) ) ; System . out. println ( "电话:"  +  people. getPhone ( ) ) ; System . out. println ( "家庭住址:"  +  people. getAddress ( ) ) ; } } 
Java  中的继承就是在已经存在类的基础上进行扩展,从而产生新的类。已经存在的类称为父类、基类或超类,而新产生的类称为子类或派生类。在子类中,不仅包含父类的属性和方法,还可以增加新的属性和方法。Java  中子类继承父类的语法格式如下:修饰符 class  class_name extends  extend_class { } 其中,class_name 表示子类(派生类)的名称;extend_class 表示父类(基类)的名称;extends  关键字直接跟在子类名之后,其后面是该类要继承的父类名称。例如:public  class  Student  extends  Person { } 继承的优缺点
在面向对象语言中,继承是必不可少的、非常优秀的语言机制,它有如下优点:*  实现代码共享,减少创建类的工作量,使子类可以拥有父类的方法和属性。*  提高代码维护性和可重用性。*  提高代码的可扩展性,更好的实现父类的方法。
自然界的所有事物都是优点和缺点并存的,继承的缺点如下:*  继承是侵入性的。只要继承,就必须拥有父类的属性和方法。*  降低代码灵活性。子类拥有父类的属性和方法后多了些约束。*  增强代码耦合性(开发项目的原则为高内聚低耦合)。当父类的常量、变量和方法被修改时,需要考虑子类的修改,有可能会导致大段的代码需要重构。示例: public  class  People  { public  String  name;  public  int  age;  public  String  sex;  public  String  sn;  public  People ( String  name,  int  age,  String  sex,  String  sn)  { this . name =  name; this . age =  age; this . sex =  sex; this . sn =  sn; } public  String  toString ( )  { return  "姓名:"  +  name +  "\n年龄:"  +  age +  "\n性别:"  +  sex +  "\n身份证号:"  +  sn; } } 
创建 People  类的子类 Student  类,并定义 stuNo 和 department 属性,代码如下:public  class  Student  extends  People  { private  String  stuNo;  private  String  department;  public  Student ( String  name,  int  age,  String  sex,  String  sn,  String  stuno,  String  department)  { super ( name,  age,  sex,  sn) ;  this . stuNo =  stuno; this . department =  department; } public  String  toString ( )  { return  "姓名:"  +  name +  "\n年龄:"  +  age +  "\n性别:"  +  sex +  "\n身份证号:"  +  sn +  "\n学号:"  +  stuNo +  "\n所学专业:"  +  department; } } 
由于 Student  类继承自 People  类,因此,在 Student  类中同样具有 People  类的属性和方法,这里重写了父类中的 toString ( )  方法。
由于子类不能继承父类的构造方法,因此,如果要调用父类的构造方法,可以使用 super  关键字。super  可以用来访问父类的构造方法、普通方法和属性。
super  关键字的功能:*  在子类的构造方法中显式的调用父类构造方法*  访问父类的成员方法和变量。
super  关键字可以在子类的构造方法中显式地调用父类的构造方法,其中,parameter- list 指定了父类构造方法中的所有参数。super (  )  必须是在子类构造方法的方法体的第一行。基本格式如下:super ( parameter- list) ; 如果一个类中没有写任何的构造方法,JVM  会生成一个默认的无参构造方法。在继承关系中,由于在子类的构造方法中,第一条语句默认为调用父类的无参构造方法(即默认为 super ( ) ,一般这行代码省略了)。所以当在父类中定义了有参构造方法,但是没有定义无参构造方法时,编译器会强制要求我们定义一个相同参数类型的构造方法。示例: 
声明父类 Person ,类中定义两个构造方法。示例代码如下:public  class  Person  { public  Person ( String  name,  int  age)  { } public  Person ( String  name,  int  age,  String  sex)  { } } 
子类 Student  继承了 Person  类,使用 super  语句来定义 Student  类的构造方法。示例代码如下:public  class  Student  extends  Person  { public  Student ( String  name,  int  age,  String  birth)  { super ( name,  age) ;  } public  Student ( String  name,  int  age,  String  sex,  String  birth)  { super ( name,  age,  sex) ;  } } 
从上述 Student  类构造方法代码可以看出,super  可以用来直接调用父类中的构造方法,使编写代码也更加简洁方便。
编译器会自动在子类构造方法的第一句加上super ( ) ; 来调用父类的无参构造方法,必须写在子类构造方法的第一句,也可以省略不写。通过 super  来调用父类其它构造方法时,只需要把相应的参数传过去。
当子类的成员变量或方法与父类同名时,可以使用 super  关键字来访问。如果子类重写了父类的某一个方法,即子类和父类有相同的方法定义,但是有不同的方法体,此时,我们可以通过 super  来调用父类里面的这个方法。
使用 super  访问父类中的成员与 this  关键字的使用相似,只不过它引用的是子类的父类,语法格式如下:使用 super  访问父类的属性和方法时不用位于第一行。 super . member示例:  super 调用成员属性
当父类和子类具有相同的数据成员时,JVM  可能会模糊不清。我们可以使用以下代码片段更清楚地理解它。class  Person  { int  age =  12 ; } class  Student  extends  Person  { int  age =  18 ; void  display ( )  { System . out. println ( "学生年龄:"  +  super . age) ; } } class  Test  { public  static  void  main ( String [ ]  args)  { Student  stu =  new  Student ( ) ; stu. display ( ) ; } } 
this  指的是当前对象的引用,super  是当前对象的父对象的引用。下面先简单介绍一下 super  和 this  关键字的用法。
super  关键字的用法:super . 父类属性名:调用父类中的属性super . 父类方法名:调用父类中的方法super ( ) :调用父类的无参构造方法super ( 参数) :调用父类的有参构造方法如果构造方法的第一行代码不是 this ( )  和 super ( ) ,则系统会默认添加 super ( ) 。
this  关键字的用法:this . 属性名:表示当前对象的属性this . 方法名( 参数) :表示调用当前对象的方法关于 Java  super  和 this  关键字的异同,可简单总结为以下几条。*  子类和父类中变量或方法名称相同时,用 super  关键字来访问。可以理解为 super  是指向自己父类对象的一个指针。在子类中调用父类的构造方法。*  this  是自身的一个对象,代表对象本身,可以理解为 this  是指向对象本身的一个指针。在同一个类中调用其它方法。*  this  和 super  不能同时出现在一个构造方法里面,因为 this  必然会调用其它的构造方法,其它的构造方法中肯定会有 super  语句的存在,所以在同一个构造方法里面有相同的语句,就失去了语句的意义,编译器也不会通过。*  this (  )  和 super (  )  都指的是对象,所以,均不可以在 static  环境中使用,包括 static  变量、static  方法和 static  语句块。*  从本质上讲,this  是一个指向对象本身的指针,  然而 super  是一个 Java  关键字。
将一个类型强制转换成另一个类型的过程被称为类型转换。本节所说的对象类型转换,是指存在继承关系的对象,不是任意类型的对象。当对不存在继承关系的对象进行强制类型转换时,会抛出 Java  强制类型转换(java. lang.  ClassCastExceptionJava  语言允许某个类型的引用变量引用子类的实例,而且可以对这个引用变量进行类型转换。Java  中引用类型之间的类型转换(前提是两个类是父子关系)主要有两种,分别是向上转型(upcasting)和向下转型(downcasting)。
父类引用指向子类对象为向上转型,语法格式如下:fatherClass obj =  new  sonClass ( ) ; 其中,fatherClass 是父类名称或接口名称,obj 是创建的对象,sonClass 是子类名称。
向上转型就是把子类对象直接赋给父类引用,不用强制转换。使用向上转型可以调用父类类型中的所有成员,不能调用子类类型中特有成员,最终运行效果看子类的具体实现。 
与向上转型相反,子类对象指向父类引用为向下转型,语法格式如下:sonClass obj =  ( sonClass)  fatherClass; 其中,fatherClass 是父类名称,obj 是创建的对象,sonClass 是子类名称。
向下转型可以调用子类类型中所有的成员,不过需要注意的是如果父类引用对象指向的是子类对象,那么在向下转型的过程中是安全的,也就是编译是不会出错误。但是如果父类引用对象是父类本身,那么在向下转型的过程中是不安全的,编译不会出错,但是运行时会出现我们开始提到的 Java  强制类型转换异常,一般使用 instanceof  运算符来避免出此类错误。
Animal  类表示动物类,该类对应的子类有 Dog  类,使用对象类型表示如下:Animal  animal =  new  Dog ( ) ;     Dog  dog =  ( Dog )  animal;  
Java  编译器允许在具有直接或间接继承关系的类之间进行类型转换。对于向下转型,必须进行强制类型转换;对于向上转型,不必使用强制类型转换。示例: 
对于一个引用类型的变量,Java  编译器按照它声明的类型来处理。如果使用 animal 调用 str 和 eatMethod ( )  方法将会出错,如下:animal. str =  "" ;     animal. eatMethod ( ) ;     
如果要访问 Cat  类的成员,必须通过强制类型转换,如下:( ( Cat ) animal) . str =  "" ;     ( ( Cat ) animal) . eatMethod ( ) ;     
把 Animal  对象类型强制转换为 Cat  对象类型,这时上面两句编译成功。对于如下语句,由于使用了强制类型转换,所以也会编译成功,例如:Cat  cat =  ( Cat ) animal;     
类型强制转换时想运行成功就必须保证父类引用指向的对象一定是该子类对象,最好使用 instanceof  运算符判断后,再强转,例如:Animal  animal =  new  Cat ( ) ; if  ( animal instanceof  Cat )  { Cat  cat =  ( Cat )  animal;  . . . } 
子类的对象可以转换成父类类型,而父类的对象实际上无法转换为子类类型。因为通俗地讲,父类拥有的成员子类肯定也有,而子类拥有的成员,父类不一定有。因此,对于向上转型,不必使用强制类型转换。例如:Cat  cat =  new  Cat ( ) ; Animal  animal =  cat;     
如果两种类型之间没有继承关系,那么将不允许进行类型转换。例如:Dog  dog =  new  Dog ( ) ; Cat  cat =  ( Cat ) dog;     
Java  允许同一个类中定义多个同名方法,只要它们的形参列表不同即可。如果同一个类中包含了两个或两个以上方法名相同的方法,但形参列表不同,这种情况被称为方法重载(overload)。
方法重载的要求是两同一不同:同一个类中方法名相同,参数列表不同。至于方法的其他部分,如方法返回值类型、修饰符等,与方法重载没有任何关系。
使用方法重载其实就是避免出现繁多的方法名,有些方法的功能是相似的,如果重新建立一个方法,重新取个方法名称,会降低程序可读性。示例: 
在比较数值时,数值的个数和类型是不固定的,可能是两个 int  类型的数值,也可能是两个 double  类型的数值,或者是两个 double 、一个 int  类型的数值;在这种情况下就可以使用方法的重载来实现数值之间的比较功能。具体实现代码如下:public  class  OverLoading  { public  void  max ( int  a,  int  b)  { System . out. println ( a >  b ?  a :  b) ; } public  void  max ( double  a,  double  b)  { System . out. println ( a >  b ?  a :  b) ; } public  void  max ( double  a,  double  b,  int  c)  { double  max =  ( double )  ( a >  b ?  a :  b) ; System . out. println ( c >  max ?  c :  max) ; } public  static  void  main ( String [ ]  args)  { OverLoading  ol =  new  OverLoading ( ) ; System . out. println ( "1 与 5 比较,较大的是:" ) ; ol. max ( 1 ,  5 ) ; System . out. println ( "5.205 与 5.8 比较,较大的是:" ) ; ol. max ( 5.205 ,  5.8 ) ; System . out. println ( "2.15、0.05、58 中,较大的是:" ) ; ol. max ( 2.15 ,  0.05 ,  58 ) ; } } 
在子类中如果创建了一个与父类中相同名称、相同返回值类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写(override),又称为方法覆盖。当父类中的方法无法满足子类需求或子类具有特有功能的时候,需要方法重写。
子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。在重写方法时,需要遵循下面的规则:1.  参数列表必须完全与被重写的方法参数列表相同。2.  返回的类型必须与被重写的方法的返回类型相同(Java1 .5  版本之前返回值类型必须一样,之后的 Java  版本放宽了限制,返回值类型必须小于或者等于父类方法的返回值类型)。3.  访问权限不能比父类中被重写方法的访问权限更低(public > protected > default > private )。4.  重写方法一定不能抛出新的检査异常或者比被重写方法声明更加宽泛的检査型异常。例如,父类的一个方法声明了一个检査异常 IOException ,在重写这个方法时就不能抛出 Exception ,只能拋出 IOException  的子类异常,可以抛出非检査异常。另外还要注意以下几条:1.  重写的方法可以使用 @Override  注解来标识。2.  父类的成员方法只能被它的子类重写。3.  声明为 final  的方法不能被重写。4.  声明为 static  的方法不能被重写,但是能够再次声明。5.  构造方法不能被重写。6.  子类和父类在同一个包中时,子类可以重写父类的所有方法,除了声明为 private  和 final  的方法。7.  子类和父类不在同一个包中时,子类只能重写父类的声明为 public  和 protected  的非 final  方法。8.  如果不能继承一个方法,则不能重写这个方法。示例: 
下面编写 Java  程序,在父类 Animal  中定义 getInfo ( )  方法,并在子类 Cat  中重写该方法, 实现猫的介绍方式。父类 Animal  的代码如下:public  class  Animal  { public  String  name;  public  int  age;  public  Animal ( String  name,  int  age)  { this . name =  name; this . age =  age; } public  String  getInfo ( )  { return  "我叫"  +  name +  ",今年"  +  age +  "岁了。" ; } } 
子类 Cat  的代码如下:public  class  Cat  extends  Animal  { private  String  hobby; public  Cat ( String  name,  int  age,  String  hobby)  { super ( name,  age) ; this . hobby =  hobby; } public  String  getInfo ( )  { return  "喵!大家好!我叫"  +  this . name +  ",我今年"  +  this . age +  "岁了,我爱吃"  +  hobby +  "。" ; } public  static  void  main ( String [ ]  args)  { Animal  animal =  new  Cat ( "小白" ,  2 ,  "鱼" ) ; System . out. println ( animal. getInfo ( ) ) ; } } 
多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为. 
对面向对象来说,多态分为编译时多态和运行时多态。Java  实现多态有 3  个必要条件:继承、重写和向上转型。1.  继承:在多态中必须存在有继承关系的子类和父类。2.  重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。3.  向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。
示例: 1 )创建 Figure  类,在该类中首先定义存储二维对象的尺寸,然后定义有两个参数的构造方法,最后添加 area ( )  方法,该方法计算对象的面积。代码如下:public  class  Figure  { double  dim1; double  dim2; Figure ( double  d1,  double  d2)  { this . dim1 =  d1; this . dim2 =  d2; } double  area ( )  { System . out. println ( "父类中计算对象面积的方法,没有实际意义,需要在子类中重写。" ) ; return  0 ; } } 2 )创建继承自 Figure  类的 Rectangle  子类,该类调用父类的构造方法,并且重写父类中的 area ( )  方法。代码如下:public  class  Rectangle  extends  Figure  { Rectangle ( double  d1,  double  d2)  { super ( d1,  d2) ; } double  area ( )  { System . out. println ( "长方形的面积:" ) ; return  super . dim1 *  super . dim2; } } 3 )创建继承自 Figure  类的 Triangle  子类,该类与 Rectangle  相似。代码如下:public  class  Triangle  extends  Figure  { Triangle ( double  d1,  double  d2)  { super ( d1,  d2) ; } double  area ( )  { System . out. println ( "三角形的面积:" ) ; return  super . dim1 *  super . dim2 /  2 ; } } 4 )创建 Test  测试类,在该类的 main ( )  方法中首先声明 Figure  类的变量 figure,然后分别为 figure 变量指定不同的对象,并调用这些对象的 area ( )  方法。代码如下:public  class  Test  { public  static  void  main ( String [ ]  args)  { Figure  figure;  figure =  new  Rectangle ( 9 ,  9 ) ; System . out. println ( figure. area ( ) ) ; System . out. println ( "===============================" ) ; figure =  new  Triangle ( 6 ,  8 ) ; System . out. println ( figure. area ( ) ) ; System . out. println ( "===============================" ) ; figure =  new  Figure ( 10 ,  10 ) ; System . out. println ( figure. area ( ) ) ; } } 
从上述代码可以发现,无论 figure 变量的对象是 Rectangle  还是 Triangle ,它们都是 Figure  类的子类,因此可以向上转型为该类,从而实现多态。
严格来说 instanceof  是 Java  中的一个双目运算符,由于它是由字母组成的,所以也是 Java  的保留关键字。在 Java  中可以使用 instanceof  关键字判断一个对象是否为一个类(或接口、抽象类、父类)的实例,语法格式如下所示。( obj是一个对象, Class 表示一个类或接口obj是class 类( 或接口) 的实例或者子类实例时, 结果result返回true , 否则返回 false ) boolean  result =  obj instanceof  Class 
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,那么这样的类称为抽象类。抽象方法的 3  个特征如下:*  抽象方法没有方法体*  抽象方法必须存在于抽象类中*  子类重写父类时,必须重写父类所有的抽象方法( abstract 表示该类或该方法是抽象的: class_name表示抽象类的名称: method_name表示抽象方法名称: parameter- list表示方法参数列表) 
在 Java  中抽象类的语法格式如下: 
< abstract > class < class_name> { < abstract > < type> < method_name> ( parameter- iist) ; 
} 抽象类的定义和使用规则如下:*  抽象类和抽象方法都要使用 abstract  关键字声明。*  如果一个方法被声明为抽象的,那么这个类也必须声明为抽象的。而一个抽象类中,可以有 0 ~ n 个抽象方法,以及 0 ~ n 个具体方法。*  抽象类不能实例化,也就是不能使用 new  关键字创建对象。
抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更彻底,则可以提炼出一种更加特殊的“抽象类”——接口(Interface )。接口是 Java  中最重要的概念之一,它可以被理解为一种特殊的类,不同的是接口的成员没有执行体,是由全局常量和公共的抽象方法所组成。 
Java  接口的定义方式与类基本相同,不过接口定义使用的关键字是 interface ,接口定义的语法格式如下: [ public ]  interface  interface_name [ extends  interface1_name[ ,  interface2_name, …] ]  { [ public ]  [ static ]  [ final ]  type constant_name =  value;     [ public ]  [ abstract ]  returnType method_name ( parameter_list) ;     } *  public  表示接口的修饰符,当没有修饰符时,则使用默认的修饰符,此时该接口的访问权限仅局限于所属的包;*  interface_name 表示接口的名称。接口名应与类名采用相同的命名规则,即如果仅从语法角度来看,接口名只要是合法的标识符即可。如果要遵守 Java  可读性规范,则接口名应由多个有意义的单词连缀而成,每个单词首字母大写,单词与单词之间无需任何分隔符。*  extends  表示接口的继承关系;*  interface1_name 表示要继承的接口名称;*  constant_name 表示变量名称,一般是 static  和 final  型的;*  returnType 表示方法的返回值类型;*  parameter_list 表示参数列表,在接口中的方法是没有方法体的。
接口对于其声明、变量和方法都做了许多限制,这些限制作为接口的特征归纳如下:*  具有 public  访问控制符的接口,允许任何类使用;没有指定 public  的接口,其访问将局限于所属的包。*  方法的声明不需要其他修饰符,在接口中声明的方法,将隐式地声明为公有的(public )和抽象的(abstract )。*  在 Java  接口中声明的变量其实都是常量,接口中的变量声明,将隐式地声明为 public 、static  和 final ,即常量,所以接口中定义的变量必须初始化。*  接口没有构造方法,不能被实例化。示例: public  interface  A  { publicA ( ) { …}     } 一个接口不能够实现另一个接口,但它可以继承多个其他接口。子接口可以对父接口的方法和常量进行重写。例如:public  interface  StudentInterface  extends  PeopleInterface  { int  age =  25 ;     void  getInfo ( ) ;     } 例如,定义一个接口 MyInterface ,并在该接口中声明常量和方法,如下:public  interface  MyInterface  {     String  name;     int  age =  20 ;     void  getInfo ( ) ;     } 
接口的主要用途就是被实现类实现,一个类可以实现一个或多个接口,继承使用 extends  关键字,实现则使用 implements  关键字。因为一个类可以实现多个接口,这也是 Java  为单继承灵活性不足所作的补充。类实现接口的语法格式如下:< public > class  < class_name> [ extends  superclass_name]  [ implements  interface1_name[ ,  interface2_name…] ]  { 
} 对以上语法的说明如下:*  public :类的修饰符;*  superclass_name:需要继承的父类名称;*  interface1_name:要实现的接口名称。
实现接口需要注意以下几点:1.  实现接口与继承父类相似,一样可以获得所实现接口里定义的常量和方法。如果一个类需要实现多个接口,则多个接口之间以逗号分隔。2.  一个类可以继承一个父类,并同时实现多个接口,implements  部分必须放在 extends  部分之后。3.  一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法);否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类。
示例: 
1 )创建一个名称为 IMath  的接口,代码如下:public  interface  IMath  { public  int  sum ( ) ;     public  int  maxNum ( int  a, int  b) ;     } 2 )定义一个 MathClass  类并实现 IMath  接口,MathClass  类实现代码如下:public  class  MathClass  implements  IMath  { private  int  num1;     private  int  num2;     public  MathClass ( int  num1, int  num2)  { this . num1 =  num1; this . num2 =  num2; } public  int  sum ( )  { return  num1 +  num2; } public  int  maxNum ( int  a, int  b)  { if ( a >=  b)  { return  a; }  else  { return  b; } } } 
在实现类中,所有的方法都使用了 public  访问修饰符声明。无论何时实现一个由接口定义的方法,它都必须实现为 public ,因为接口中的所有成员都显式声明为 public 。3 )最后创建测试类 NumTest ,实例化接口的实现类 MathClass ,调用该类中的方法并输出结果。该类内容如下:public  class  NumTest  { public  static  void  main ( String [ ]  args)  { MathClass  calc =  new  MathClass ( 100 ,  300 ) ; System . out. println ( "100 和 300 相加结果是:"  +  calc. sum ( ) ) ; System . out. println ( "100 比较 300,哪个大:"  +  calc. maxNum ( 100 ,  300 ) ) ; } } 
在类内部可定义成员变量和方法,且在类内部也可以定义另一个类。如果在类 Outer  的内部再定义一个类 Inner ,此时类 Inner  就称为内部类(或称为嵌套类),而类 Outer  则称为外部类(或称为宿主类)。
内部类可以很好地实现隐藏,一般的非内部类是不允许有 private  与 protected  权限的,但内部类可以。内部类拥有外部类的所有元素的访问权限。
内部类可以分为:实例内部类、静态内部类和成员内部. 内部类的特点如下:*  内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的. class 文件,但是前面冠以外部类的类名和$符号。*  内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否为 private  的。*  内部类声明成静态的,就不能随便访问外部类的成员变量,仍然是只能访问外部类的静态成员变量。示例: public  class  Test  { public  class  InnerClass  { public  int  getSum ( int  x, int  y)  { return  x +  y; } } public  static  void  main ( String [ ]  args)  { Test. InnerClass  ti =  new  Test ( ) . new  InnerClass ( ) ; int  i =  ti. getSum ( 2 , 3 ) ; System . out. println ( i) ;     } } 有关内部类的说明有如下几点。*  外部类只有两种访问级别:public  和默认;内部类则有 4  种访问级别:public 、protected 、 private  和默认。*  在外部类中可以直接通过内部类的类名访问内部类。InnerClass  ic =  new  InnerClass ( ) ;     *  在外部类以外的其他类中则需要通过内部类的完整类名访问内部类。Test. InnerClass  ti =  newTest ( ) . new  InnerClass ( ) ;     内部类与外部类不能重名。
实例内部类是指没有用 static  修饰的内部类,有的地方也称为非静态内部类。示例: public  class  Outer  { class  Inner  { } } 
特点: 
1 )在外部类的静态方法和外部类以外的其他类中,必须通过外部类的实例创建内部类的实例。public  class  Outer  { class  Inner1  { } Inner1  i =  new  Inner1 ( ) ;  public  void  method1 ( )  { Inner1  i =  new  Inner1 ( ) ;  } public  static  void  method2 ( )  { Inner1  i =  new  Outer ( ) . new  inner1 ( ) ;  } class  Inner2  { Inner1  i =  new  Inner1 ( ) ;  } } class  OtherClass  { Outer. Inner  i =  new  Outer ( ) . new  Inner ( ) ;  } 2 )在实例内部类中,可以访问外部类的所有成员。public  class  Outer  { public  int  a =  100 ; static  int  b =  100 ; final  int  c =  100 ; private  int  d =  100 ; public  String  method1 ( )  { return  "实例方法1" ; } public  static  String  method2 ( )  { return  "静态方法2" ; } class  Inner  { int  a2 =  a +  1 ;  int  b2 =  b +  1 ;  int  c2 =  c +  1 ;  int  d2 =  d +  1 ;  String  str1 =  method1 ( ) ;  String  str2 =  method2 ( ) ;  } public  static  void  main ( String [ ]  args)  { Inner  i =  new  Outer ( ) . new  Inner ( ) ;  System . out. println ( i. a2) ;  System . out. println ( i. b2) ;  System . out. println ( i. c2) ;  System . out. println ( i. d2) ;  System . out. println ( i. str1) ;  System . out. println ( i. str2) ;  } } 
提示:如果有多层嵌套,则内部类可以访问所有外部类的成员。3 )在外部类中不能直接访问内部类的成员,而必须通过内部类的实例去访问。如果类 A  包含内部类 B ,类 B  中包含内部类 C ,则在类 A  中不能直接访问类 C ,而应该通过类 B  的实例去访问类 C 。4 )外部类实例与内部类实例是一对多的关系,也就是说一个内部类实例只对应一个外部类实例,而一个外部类实例则可以对应多个内部类实例。
如果实例内部类 B  与外部类 A  包含有同名的成员 t,则在类 B  中 t 和 this . t 都表示 B  中的成员 t,而 A . this . t 表示 A  中的成员 t。public  class  Outer  { int  a =  10 ; class  Inner  { int  a =  20 ; int  b1 =  a; int  b2 =  this . a; int  b3 =  Outer . this . a; } public  static  void  main ( String [ ]  args)  { Inner  i =  new  Outer ( ) . new  Inner ( ) ; System . out. println ( i. b1) ;  System . out. println ( i. b2) ;  System . out. println ( i. b3) ;  } } 5 )在实例内部类中不能定义 static  成员,除非同时使用 final  和 static  修饰。
静态内部类是指使用 static  修饰的内部类。示例: public  class  Outer  { static  class  Inner  { } } 
特点: 
1 )在创建静态内部类的实例时,不需要创建外部类的实例。public  class  Outer  { static  class  Inner  { } } class  OtherClass  { Outer. Inner  oi =  new  Outer. Inner ( ) ; } 2 )静态内部类中可以定义静态成员和实例成员。外部类以外的其他类需要通过完整的类名访问静态内部类中的静态成员,如果要访问静态内部类中的实例成员,则需要通过静态内部类的实例。public  class  Outer  { static  class  Inner  { int  a =  0 ;     static  int  b =  0 ;     } } class  OtherClass  { Outer. Inner  oi =  new  Outer. Inner ( ) ; int  a2 =  oi. a;     int  b2 =  Outer. Inner . b;     } 3 )静态内部类可以直接访问外部类的静态成员,如果要访问外部类的实例成员,则需要通过外部类的实例去访问。public  class  Outer  { int  a =  0 ;     static  int  b =  0 ;     static  class  Inner  { Outer  o =  new  Outer ; int  a2 =  o. a;     int  b2 =  b;     } } 
局部内部类是指在一个方法中定义的内部类。示例: public  class  Test  { public  void  method ( )  { class  Inner  { } } } 
特点: 
1 )局部内部类与局部变量一样,不能使用访问控制修饰符(public 、private  和 protected )和 static  修饰符修饰。
2 )局部内部类只在当前方法中有效。public  class  Test  { Inner  i =  new  Inner ( ) ;     Test. Inner  ti =  new  Test. Inner ( ) ;     Test. Inner  ti2 =  new  Test ( ) . new  Inner ( ) ;     public  void  method ( )  { class  Inner { } Inner  i =  new  Inner ( ) ; } } 3 )局部内部类中不能定义 static  成员。
4 )局部内部类中还可以包含内部类,但是这些内部类也不能使用访问控制修饰符(public 、private  和 protected )和 static  修饰符修饰。
5 )在局部内部类中可以访问外部类的所有成员。
6 )在局部内部类中只可以访问当前方法中 final  类型的参数与变量。如果方法中的成员与外部类中的成员同名,则可以使用 < OuterClassName > . this . < MemberName > public  class  Test  { int  a =  0 ; int  d =  0 ; public  void  method ( )  { int  b =  0 ; final  int  c =  0 ; final  int  d =  10 ; class  Inner  { int  a2 =  a;     int  c2 =  c;     int  d2 =  d;     int  d3 =  Test . this . d;     } Inner  i =  new  Inner ( ) ; System . out. println ( i. d2) ;     System . out. println ( i. d3) ;     } public  static  void  main ( String [ ]  args)  { Test  t =  new  Test ( ) ; t. method ( ) ; } } 
匿名类是指没有类名的内部类,必须在创建时使用 new  语句来声明类。这种形式的 new  语句声明一个新的匿名类,它对一个给定的类进行扩展,或者实现一个给定的接口。使用匿名类可使代码更加简洁、紧凑,模块化程度更高,语法如下: new  < 类或接口> ( )  { } ; 
匿名类有两种实现方式:*  继承一个类,重写其方法。*  实现一个接口(可以是多个),实现其方法。示例: public  class  Out  { void  show ( )  { System . out. println ( "调用 Out 类的 show() 方法" ) ; } } public  class  TestAnonymousInterClass  { private  void  show ( )  { Out  anonyInter =  new  Out ( )  { void  show ( )  { System . out. println ( "调用匿名类中的 show() 方法" ) ; } } ; anonyInter. show ( ) ; } public  static  void  main ( String [ ]  args)  { TestAnonymousInterClass  test =  new  TestAnonymousInterClass ( ) ; test. show ( ) ; } } 
程序的输出结果如下:调用匿名类中的 show ( )  方法
从输出结果可以看出,匿名内部类有自己的实现。
特点: 
1 )匿名类和局部内部类一样,可以访问外部类的所有成员。如果匿名类位于一个方法中,则匿名类只能访问方法中 final  类型的局部变量和参数。public  static  void  main ( String [ ]  args)  { int  a =  10 ; final  int  b =  10 ; Out  anonyInter =  new  Out ( )  { void  show ( )  { System . out. println ( "调用了匿名类的 show() 方法" + b) ;     } } ; anonyInter. show ( ) ; } 从 Java  8  开始添加了 Effectively  final  功能,在 Java  8  及以后的版本中代码第 6  行不会出现编译错误,详情可点击《Java8 新特性之Effectively  final 》进行学习。2 )匿名类中允许使用非静态代码块进行成员初始化操作。Out  anonyInter =  new  Out ( )  { int  i;  {     i =  10 ;     } public  void  show ( )  { System . out. println ( "调用了匿名类的 show() 方法" + i) ; } } ; 3 )匿名类的非静态代码块会在父类的构造方法之后被执行。
Java  中局部内部类和匿名内部类访问的局部变量必须由 final  修饰,以保证内部类和外部类的数据一致性。但从 Java  8  开始,我们可以不加 final  修饰符,由系统默认添加,当然这在 Java  8  以前的版本是不允许的。Java  将这个功能称为 Effectively  final  功能。编写同样的代码,分别在 Java  7  和 Java  8  下运行,代码如下:public  class  Test  { public  static  void  main ( String [ ]  args)  { String  name =  "语言网" ; new  Runnable ( )  { @Override public  void  run ( )  { System . out. println ( name) ; } } } } 因为系统会默认添加 final  修饰符,所以在图 2  和图 3  中可以在匿名内部类中直接使用非 final  变量,而 final  修饰的局部变量不能在被重新赋值,所以图 3  中出现编译错误。也就是说从 Java  8  开始,它不要求程序员必须将访问的局部变量显式的声明为 final  的。只要该变量不被重新赋值就可以。一个非 final  的局部变量或方法参数,其值在初始化后就从未更改,那么该变量就是 effectively final 。在 Lambda  表达式中,使用局部变量的时候,也要求该变量必须是 final  的,所以 effectively final  在 Lambda  表达式上下文中非常有用。Lambda  表达式在编程中是经常使用的,而匿名内部类是很少使用的。那么,我们在 Lambda  编程中每一个被使用到的局部变量都去显示定义成 final  吗?显然这不是一个好方法。所以,Java  8  引入了 effectively final  新概念。总结一下,规则没有改变,Lambda  表达式和匿名内部类访问的局部变量必须是 final  的,只是不需要程序员显式的声明变量为 final  的,从而节省时间。
Lambda  表达式(Lambda  expression)是一个匿名函数,基于数学中的λ演算得名,也可称为闭包(Closure )。
Lambda  表达式是推动 Java  8  发布的重要新特性,它允许把函数作为一个方法的参数(函数作为参数传递进方法中)
示例: 
先定义一个计算数值的接口,代码如下。public  interface  Calculable  { int  calculateInt ( int  a,  int  b) ; } Calculable  接口只有一个方法 calculateInt,参数是两个 int  类型,返回值也是 int  类型。实现方法代码如下:public  class  Test { public  static  Calculable  calculate ( char  opr)  { Calculable  result; if  ( opr ==  '+' )  { result =  new  Calculable ( )  { @Override public  int  calculateInt ( int  a,  int  b)  { return  a +  b; } } ; }  else  { result =  new  Calculable ( )  { @Override public  int  calculateInt ( int  a,  int  b)  { return  a -  b; } } ; } return  result; } } 
方法 calculate 中 opr 参数是运算符,返回值是实现 Calculable  接口对象。代码第 13  行和第 23  行都采用匿名内部类实现 Calculable  接口。代码第 16  行实现加法运算。代码第 26  行实现减法运算。public  static  void  main ( String [ ]  args)  { int  n1 =  10 ; int  n2 =  5 ; Calculable  f1 =  calculate ( '+' ) ; Calculable  f2 =  calculate ( '-' ) ; System . out. println ( n1 +  "+"  +  n2 +  "="  +  f1. calculateInt ( n1,  n2) ) ; System . out. println ( n1 +  "-"  +  n2 +  "="  +  f1. calculateInt ( n1,  n2) ) ; } 
代码第 5  行中 f1 是实现加法计算 Calculable  对象,代码第 7  行中 f2 是实现减法计算 Calculable  对象。代码第 9  行和第 12  行才进行方法调用。上述代码中列出了两种输出方式,下面简单介绍一下 Java  中常见的输出函数:*  printf 主要继承了C 语言中 printf 的一些特性,可以进行格式化输出。*  print 就是一般的标准输出,但是不换行。*  println 和 print 基本没什么差别,就是最后会换行。
lambda表达式示例: 修改之后的通用方法 calculate 代码如下:public  static  Calculable  calculate ( char  opr)  { Calculable  result; if  ( opr ==  '+' )  { result =  ( int  a,  int  b)  ->  { return  a +  b; } ; }  else  { result =  ( int  a,  int  b)  ->  { return  a -  b; } ; } return  result; } 
代码第 10  行和第 15  行用 Lambda  表达式替代匿名内部类,可见代码变得简洁。通过以上示例我们发现,Lambda  表达式是一个匿名函数(方法)代码块,可以作为表达式、方法参数和方法返回值。Lambda  表达式标准语法形式如下:
( 参数列表)  ->  { 
} -> 被称为箭头操作符或 Lambda  操作符,箭头操作符将 Lambda  表达式拆分成两部分:1.  左侧:Lambda  表达式的参数列表。2.  右侧:Lambda  表达式中所需执行的功能,用{  } 包起来,即 Lambda  体。
Java  Lambda  表达式的优缺点
优点:*  代码简洁,开发迅速*  方便函数式编程*  非常容易进行并行计算*  Java  引入 Lambda ,改善了集合操作(引入 Stream  API )
缺点:*  代码可读性变差*  在非并行计算中,很多计算未必有传统的 for  性能要高*  不容易进行调试
Lambda  表达式实现的接口不是普通的接口,而是函数式接口。如果一个接口中,有且只有一个抽象的方法(Object  类中的方法不包括在内),那这个接口就可以被看做是函数式接口。这种接口只能有一个方法。如果接口中声明多个抽象方法,那么 Lambda  表达式会发生编译错误:
The  target type of this  expression must be a functional interface 
这说明该接口不是函数式接口,为了防止在函数式接口中声明多个抽象方法,Java  8  提供了一个声明函数式接口注解 @FunctionalInterface ,示例代码如下。@FunctionalInterface public  interface  Calculable  { int  calculateInt ( int  a,  int  b) ; } 在接口之前使用 @FunctionalInterface  注解修饰,那么试图增加一个抽象方法时会发生编译错误。但可以添加默认方法和静态方法。@FunctionalInterface  注解与 @Override  注解的作用类似。Java  8  中专门为函数式接口引入了一个新的注解 @FunctionalInterface 。该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。提示:Lambda  表达式是一个匿名方法代码,Java  中的方法必须声明在类或接口中,那么 Lambda  表达式所实现的匿名方法是在函数式接口中声明的。
Lambda  表达式一种常见的用途就是作为参数传递给方法,这需要声明参数的类型声明为函数式接口类型。示例代码如下:public  static  void  main ( String [ ]  args)  { int  n1 =  10 ; int  n2 =  5 ; display ( ( a,  b)  ->  { return  a +  b; } ,  n1,  n2) ; display ( ( a,  b)  ->  a -  b,  n1,  n2) ; } public  static  void  display ( Calculable  calc,  int  n1,  int  n2)  { System . out. println ( calc. calculateInt ( n1,  n2) ) ; } 上述代码第 19  行定义 display 打印计算结果方法,其中参数 calc 类型是 Calculable ,这个参数即可以接收实现 Calculable  接口的对象,也可以接收 Lambda  表达式,因为 Calculable  是函数式接口。 代码第 7  行和第 9  行两次调用 display 方法,它们第一个参数都是 Lambda  表达式。 
Lambda  表达式可以访问所在外层作用域定义的变量,包括成员变量和局部变量。
成员变量包括实例成员变量和静态成员变量。在 Lambda  表达式中可以访问这些成员变量,此时的 Lambda  表达式与普通方法一样,可以读取成员变量,也可以修改成员变量。public  class  LambdaDemo  { private  int  value =  10 ; private  static  int  staticValue =  5 ; public  static  Calculable  add ( )  { Calculable  result =  ( int  a,  int  b)  ->  { staticValue++ ; int  c =  a +  b +  staticValue; return  c; } ; return  result; } public  Calculable  sub ( )  { Calculable  result =  ( int  a,  int  b)  ->  { staticValue++ ; this . value++ ; int  c =  a -  b -  staticValue -  this . value; return  c; } ; return  result; } } LambdaDemo  类中声明一个实例成员变量 value 和一个静态成员变量 staticValue。此外,还声明了静态方法 add(见代码第 8  行)和实例方法 sub(见代码第 20  行)。add 方法是静态方法,静态方法中不能访问实例成员变量,所以代码第 13  行的 Lambda  表达式中也不能访问实例成员变量,也不能访问实例成员方法。sub 方法是实例方法,实例方法中能够访问静态成员变量和实例成员变量,所以代码第 23  行的 Lambda  表达式中可以访问这些变量,当然实例方法和静态方法也可以访问。当访问实例成员变量或实例方法时可以使用 this ,如果不与局部变量发生冲突情况下可以省略 this 。
对于成员变量的访问 Lambda  表达式与普通方法没有区别,但是访问局部变量时,变量必须是 final  类型的(不可改变)。示例代码如下:public  class  LambdaDemo  { private  int  value =  10 ; private  static  int  staticValue =  5 ; public  static  Calculable  add ( )  { int  localValue =  20 ; Calculable  result =  ( int  a,  int  b)  ->  { int  c =  a +  b +  localValue; return  c; } ; return  result; } public  Calculable  sub ( )  { final  int  localValue =  20 ; Calculable  result =  ( int  a,  int  b)  ->  { int  c =  a -  b -  staticValue -  this . value; return  c; } ; return  result; } } 上述代码第 10  行和第 23  行都声明一个局部变量 localValue,Lambda  表达式中访问这个变量,如代码第 14  行和第 25  行。不管这个变量是否显式地使用 final  修饰,它都不能在 Lambda  表达式中修改变量,所以代码第 12  行和第 26  行如果去掉注释会发生编译错误。Lambda  表达式只能访问局部变量而不能修改,否则会发生编译错误,但对静态变量和成员变量可读可写。
方法引用可以理解为 Lambda  表达式的快捷写法,它比 Lambda  表达式更加的简洁,可读性更高,有很好的重用性。如果实现比较简单,复用的地方又不多,推荐使用 Lambda  表达式,否则应该使用方法引用。Java  8  之后增加了双冒号:: 运算符,该运算符用于“方法引用”,注意不是调用方法。“方法引用”虽然没有直接使用 Lambda  表达式,但也与 Lambda  表达式有关,与函数式接口有关。 方法引用的语法格式如下:ObjectRef :: methodName  
其中,ObjectRef  是类名或者实例名,methodName 是相应的方法名。
注意:被引用方法的参数列表和返回值类型,必须与函数式接口方法参数列表和方法返回值类型一致,示例代码如下。public  class  LambdaDemo  { public  static  int  add ( int  a,  int  b)  { return  a +  b; } public  int  sub ( int  a,  int  b)  { return  a -  b; } } LambdaDemo  类中提供了一个静态方法 add,一个实例方法 sub。这两个方法必须与函数式接口方法参数列表一致,方法返回值类型也要保持一致。public  class  HelloWorld  { public  static  void  main ( String [ ]  args)  { int  n1 =  10 ; int  n2 =  5 ; display ( LambdaDemo :: add ,  n1,  n2) ; LambdaDemo  d =  new  LambdaDemo ( ) ; display ( d:: sub ,  n1,  n2) ; } public  static  void  display ( Calculable  calc,  int  n1,  int  n2)  { System . out. println ( calc. calculateInt ( n1,  n2) ) ; } } 
代码第 18  行声明 display 方法,第一个参数 calc 是 Calculable  类型,它可以接受三种对象:Calculable  实现对象、Lambda  表达式和方法引用。代码第 6  行中第一个实际参数LambdaDemo :: add 是静态方法的方法引用。代码第 9  行中第一个实际参数d:: sub ,是实例方法的方法引用,d 是 LambdaDemo  实例。提示:代码第 6  行的LambdaDemo :: add 和第 9  行的d:: sub 是方法引用,此时并没有调用方法,只是将引用传递给 display 方法,在 display 方法中才真正调用方法。
异常(exception)是在运行程序时产生的一种异常情况。
Java  中的异常又称为例外,是一个在程序执行期间发生的事件,它中断正在执行程序的正常指令流。在 Java  中一个异常的产生,主要有如下三种原因:1.  Java  内部错误发生异常,Java  虚拟机产生的异常。2.  编写的程序代码中的错误所产生的异常,例如空指针异常、数组越界异常等。3.  通过 throw  语句手动生成的异常,一般用来告知该方法的调用者一些必要信息。
示例: 
为了更好地理解什么是异常,下面来看一段非常简单的 Java  程序。下面的示例代码实现了允许用户输入 1 ~ 3  以内的整数,其他情况提示输入错误。import  java. util.  Scanner ; public  class  Test01  { public  static  void  main ( String [ ]  args)  { System . out. println ( "请输入您的选择:(1~3 之间的整数)" ) ; Scanner  input =  new  Scanner ( System . in) ; int  num =  input. nextInt ( ) ; switch  ( num)  { case  1 : System . out. println ( "one" ) ; break ; case  2 : System . out. println ( "two" ) ; break ; case  3 : System . out. println ( "three" ) ; break ; default : System . out. println ( "error" ) ; break ; } } } 正常情况下,用户会按照系统的提示输入 1 ~ 3  之间的数字。但是,如果用户没有按要求进行输入,例如输入了一个字母“a”,则程序在运行时将会发生异常,运行结果如下所示。
请输入您的选择:(1 ~ 3  之间的整数)
a
Exception  in thread "main"  java. util.  InputMismatchExceptionjava. util.  Scanner. throwFor ( Unknown  Source ) at java. util.  Scanner. next ( Unknown  Source ) at java. util.  Scanner. nextInt ( Unknown  Source ) at java. util.  Scanner. nextInt ( Unknown  Source ) at text. text. main ( text. java: 11 ) 
在 Java  中所有异常类型都是内置类 java. lang.  ThrowableThrowable  位于异常类层次结构的顶层。Throwable  类是所有异常和错误的超类,下面有 Error  和 Exception  两个子类分别表示错误和异常: *  Exception  类用于用户程序可能出现的异常情况,它也是用来创建自定义异常类型类的类。*  Error  定义了在通常环境下不希望被程序捕获的异常。一般指的是 JVM  错误,如堆栈溢出。
异常类型 说明 ArithmeticException 算术错误异常,如以零做除数 ArraylndexOutOfBoundException 数组索引越界 ArrayStoreException 向类型不兼容的数组元素赋值 ClassCastException 类型转换异常 IllegalArgumentException 使用非法实参调用方法 lIIegalStateException 环境或应用程序处于不正确的状态 lIIegalThreadStateException 被请求的操作与当前线程状态不兼容 IndexOutOfBoundsException 某种类型的索引越界 NullPointerException 尝试访问 null 对象成员,空指针异常 NegativeArraySizeException 再负数范围内创建的数组 NumberFormatException 数字转化格式异常,比如字符串到 float 型数字的转换无效 TypeNotPresentException 类型未找到 
 
 
异常类型 说明 ClassNotFoundException 没有找到类 IllegalAccessException 访问类被拒绝 InstantiationException 试图创建抽象类或接口的对象 InterruptedException 线程被另一个线程中断 NoSuchFieldException 请求的域不存在 NoSuchMethodException 请求的方法不存在 ReflectiveOperationException 与反射有关的异常的超类 
 
Error (错误)和 Exception (异常)都是 java. lang.  ThrowableJava  代码中只有继承了 Throwable  类的实例才能被 throw  或者 catch 。如下是常见的 Error  和 Exception :1 )运行时异常(RuntimeException ):NullPropagation :空指针异常;ClassCastException :类型强制转换异常IllegalArgumentException :传递非法参数异常IndexOutOfBoundsException :下标越界异常NumberFormatException :数字格式异常2 )非运行时异常:ClassNotFoundException :找不到指定 class  的异常IOException :IO  操作异常3 )错误(Error ):NoClassDefFoundError :找不到 class  定义异常StackOverflowError :深递归导致栈被耗尽而抛出的异常OutOfMemoryError :内存溢出异常
堆栈溢出错误: class  StackOverflow  { public  static  void  test ( int  i)  { if  ( i ==  0 )  { return ; }  else  { test ( i++ ) ; } } } public  class  ErrorEg  { public  static  void  main ( String [ ]  args)  { StackOverflow . test ( 5 ) ; } } 
Java  的异常处理通过 5  个关键字来实现:try 、catch 、throw 、throws  和 finally 。try  catch  语句用于捕获并处理异常,finally  语句用于在任何情况下(除特殊情况外)都必须执行的代码,throw  语句用于拋出异常,throws  语句用于声明可能会出现的异常。Java  的异常处理机制提供了一种结构性和控制性的方式来处理程序执行期间发生的事件。异常处理的机制如下:*  在方法中用 try  catch  语句捕获并处理异常,catch  语句可以有多个,用来匹配多个异常。*  对于处理不了的异常或者要转型的异常,在方法的声明处通过 throws  语句拋出异常,即由上层的调用方法来处理。示例: 
以下代码是异常处理程序的基本结构:try  { 逻辑程序块}  catch ( ExceptionType1  e)  { 处理代码块1 }  catch  ( ExceptionType2  e)  { 处理代码块2 throw ( e) ;     }  finally  { 释放资源代码块} 
在 Java  中通常采用 try  catch  语句来捕获异常并处理: ( 在语法中,把可能引发异常的语句封装在 try  语句块中,用以捕获可能发生的异常。catch  后的(  ) 里放匹配的异常类,指明 catch  语句可以处理的异常类型,发生异常时产生异常类的实例化对象。) try  { }  catch ( ExceptionType  e)  { } 在上面语法的处理代码块中,可以使用以下3 个方法输出相应的异常信息: *  printStackTrace ( )  方法:指出异常的类型、性质、栈层次及出现在程序中的位置。*  getMessage ( )  方法:输出错误的性质。*  toString ( )  方法:给出异常的类型与性质。
示例: 
编写一个录入学生姓名、年龄和性别的程序,要求能捕捉年龄不为数字时的异常。在这里使用 try  catch  语句来实现,具体代码如下:import  java. util.  Scanner ; public  class  Test02  { public  static  void  main ( String [ ]  args)  { Scanner  scanner =  new  Scanner ( System . in) ; System . out. println ( "---------学生信息录入---------------" ) ; String  name =  "" ;  int  age =  0 ;  String  sex =  "" ;  try  { System . out. println ( "请输入学生姓名:" ) ; name =  scanner. next ( ) ; System . out. println ( "请输入学生年龄:" ) ; age =  scanner. nextInt ( ) ; System . out. println ( "请输入学生性别:" ) ; sex =  scanner. next ( ) ; }  catch  ( Exception  e)  { e. printStackTrace ( ) ; System . out. println ( "输入有误!" ) ; } System . out. println ( "姓名:"  +  name) ; System . out. println ( "年龄:"  +  age) ; } } 
如果 try  代码块中有很多语句会发生异常,而且发生的异常种类又很多。那么可以在 try  后面跟有多个 catch  代码块。多 catch  代码块语法如下:try  { }  catch ( ExceptionType  e)  { }  catch ( ExceptionType  e)  { }  catch ( ExceptionType  e)  { . . . } 
示例: public  class  Test03  { public  static  void  main ( String [ ]  args)  { Date  date =  readDate ( ) ; System . out. println ( "读取的日期 = "  +  date) ; } public  static  Date  readDate ( )  { FileInputStream  readfile =  null ; InputStreamReader  ir =  null ; BufferedReader  in =  null ; try  { readfile =  new  FileInputStream ( "readme.txt" ) ; ir =  new  InputStreamReader ( readfile) ; in =  new  BufferedReader ( ir) ; String  str =  in. readLine ( ) ; if  ( str ==  null )  { return  null ; } DateFormat  df =  new  SimpleDateFormat ( "yyyy-MM-dd" ) ; Date  date =  df. parse ( str) ; return  date; }  catch  ( FileNotFoundException  e)  { System . out. println ( "处理FileNotFoundException..." ) ; e. printStackTrace ( ) ; }  catch  ( IOException  e)  { System . out. println ( "处理IOException..." ) ; e. printStackTrace ( ) ; }  catch  ( ParseException  e)  { System . out. println ( "处理ParseException..." ) ; e. printStackTrace ( ) ; } return  null ; } } 
使用 try - catch - finally  语句时需注意以下几点:1.  异常处理语法结构中只有 try  块是必需的,也就是说,如果没有 try  块,则不能有后面的 catch  块和 finally  块;2.  catch  块和 finally  块都是可选的,但 catch  块和 finally  块至少出现其中之一,也可以同时出现;3.  可以有多个 catch  块,捕获父类异常的 catch  块必须位于捕获子类异常的后面;4.  不能只有 try  块,既没有 catch  块,也没有 finally  块;5.  多个 catch  块必须位于 try  块之后,finally  块必须位于所有的 catch  块之后。6.  finally  与 try  语句块匹配的语法格式,此种情况会导致异常丢失,所以不常见。try  catch  finally  语句块的执行情况可以细分为以下 3  种情况:*  如果 try  代码块中没有拋出异常,则执行完 try  代码块之后直接执行 finally  代码块,然后执行 try  catch  finally  语句块之后的语句。*  如果 try  代码块中拋出异常,并被 catch  子句捕捉,那么在拋出异常的地方终止 try  代码块的执行,转而执行相匹配的 catch  代码块,之后执行 finally  代码块。如果 finally  代码块中没有拋出异常,则继续执行 try  catch  finally  语句块之后的语句;如果 finally  代码块中拋出异常,则把该异常传递给该方法的调用者。*  如果 try  代码块中拋出的异常没有被任何 catch  子句捕捉到,那么将直接执行 finally  代码块中的语句,并把该异常传递给该方法的调用者。
除非在 try  块、catch  块中调用了退出虚拟机的方法System . exit ( int  status) ,否则不管在 try  块或者 catch  块中执行怎样的代码,出现怎样的情况,异常处理的 finally  块总会执行。finally  语句可以与前面介绍的 try  catch  语句块匹配使用,语法格式如下:try  { }  catch ( ExceptionType  e)  { }  finally  { } 对于以上格式,无论是否发生异常(除特殊情况外),finally  语句块中的代码都会被执行。此外,finally  语句也可以和 try  语句匹配使用,其语法格式如下:try  { }  finally  { } 
1.  try  语句中声明的资源被隐式声明为 final ,资源的作用局限于带资源的 try  语句。
2.  可以在一条 try  语句中声明或初始化多个资源,每个资源以; 隔开即可。
3.  需要关闭的资源必须实现了 AutoCloseable  或 Closeable  接口。Closeable  是 AutoCloseable  的子接口,Closeable  接口里的 close ( )  方法声明抛出了 IOException ,因此它的实现类在实现 close ( )  方法时只能声明抛出 IOException  或其子类;AutoCloseable  接口里的 close ( )  方法声明抛出了 Exception ,因此它的实现类在实现 close ( )  方法时可以声明抛出任何异常。
下面示范如何使用自动关闭资源的 try  语句。public  class  AutoCloseTest  { public  static  void  main ( String [ ]  args)  throws  IOException  { try  ( BufferedReader  br =  new  BufferedReader ( new  FileReader ( "AutoCloseTest.java" ) ) ; PrintStream  ps =  new  PrintStream ( new  FileOutputStream ( "a.txt" ) ) )  { System . out. println ( br. readLine ( ) ) ; ps. println ( "C语言中文网" ) ; } } } 上面程序中粗体字代码分别声明、初始化了两个 IO  流,BufferedReader  和 PrintStream  都实现了 Closeable  接口,并在 try  语句中进行了声明和初始化,所以 try  语句会自动关闭它们。自动关闭资源的 try  语句相当于包含了隐式的 finally  块(这个 finally  块用于关闭资源),因此这个 try  语句可以既没有 catch  块,也没有 finally  块。Java  7  几乎把所有的“资源类”(包括文件 IO  的各种类、JDBC  编程的 Connection  和 Statement  等接口)进行了改写,改写后的资源类都实现了 AutoCloseable  或 Closeable  接口。如果程序需要,自动关闭资源的 try  语句后也可以带多个 catch  块和一个 finally  块。Java  9  再次增强了这种 try  语句。Java  9  不要求在 try  后的圆括号内声明并创建资源,只需要自动关闭的资源有 final  修饰或者是有效的 final  ( effectively final ) ,Java  9  允许将资源变量放在 try  后的圆括号内。上面程序在 Java  9  中可改写为如下形式。public  class  AutoCloseTest  { public  static  void  main ( String [ ]  args)  throws  IOException  { final  BufferedReader  br =  new  BufferedReader ( new  FileReader ( "AutoCloseTest.java" ) ) ; final  PrintStream  ps =  new  PrintStream ( new  FileOutputStream ( "a. txt" ) ) ; try  ( br;  ps)  { System . out. println ( br. readLine ( ) ) ; ps. println ( "C语言中文网" ) ; } } } 
Java  中的异常处理除了捕获异常和处理异常之外,还包括声明异常和拋出异常。实现声明和抛出异常的关键字非常相似,它们是 throws  和 throw 。可以通过 throws  关键字在方法上声明该方法要拋出的异常,然后在方法内部通过 throw  拋出异常对象。
当一个方法产生一个它不处理的异常时,那么就需要在该方法的头部声明这个异常,以便将该异常传递到方法的外部进行处理。使用 throws  声明的方法表示此方法不处理异常。throws  具体格式如下:returnType method_name ( paramList)  throws  Exception  1 , Exception2 , …{ …} returnType 表示返回值类型;method_name 表示方法名;paramList 表示参数列表;Exception  1 ,Exception2 ,… 表示异常类。使用 throws  声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由向上一级的调用者处理;如果 main 方法也不知道如何处理这种类型的异常,也可以使用 throws  声明抛出异常,该异常将交给 JVM  处理。JVM  对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因。
创建一个 readFile ( )  方法,该方法用于读取文件内容,在读取的过程中可能会产生 IOException  异常,但是在该方法中不做任何的处理,而将可能发生的异常交给调用者处理。在 main ( )  方法中使用 try  catch  捕获异常,并输出异常信息。首先在定义 readFile ( )  方法时用 throws  关键字声明在该方法中可能产生的异常,然后在 main ( )  方法中调用 readFile ( )  方法,并使用 catch  语句捕获产生的异常: import  java. io.  FileInputStream ; 
import  java. io.  IOException ; public  class  Test04  { public  void  readFile ( )  throws  IOException  { FileInputStream  file =  new  FileInputStream ( "read.txt" ) ;  int  f; while  ( ( f =  file. read ( ) )  !=  - 1 )  { System . out. println ( ( char )  f) ; f =  file. read ( ) ; } file. close ( ) ; } public  static  void  main ( String [ ]  args)  { Throws  t =  new  Test04 ( ) ; try  { t. readFile ( ) ;  }  catch  ( IOException  e)  { System . out. println ( e) ; } } } 
与 throws  不同的是,throw  语句用来直接拋出一个异常,后接一个可拋出的异常类对象,其语法格式如下:throw  ExceptionObject ; 其中,ExceptionObject  必须是 Throwable  类或其子类的对象。如果是自定义异常类,也必须是 Throwable  的直接或间接子类。例如,以下语句在编译时将会产生语法错误:throw  new  String ( "拋出异常" ) ;     
示例: 在某仓库管理系统中,要求管理员的用户名需要由 8  位以上的字母或者数字组成,不能含有其他的字符。当长度在 8  位以下时拋出异常,并显示异常信息;当字符含有非字母或者数字时,同样拋出异常,显示异常信息。代码如下:在 validateUserName ( )  方法中两处拋出了 IllegalArgumentException  异常,即当用户名字符含有非字母或者数字以及长度不够 8  位时。在 main ( )  方法中,调用了 validateUserName ( )  方法,并使用 catch  语句捕获该方法可能拋出的异常。import  java. util.  Scanner ; public  class  Test05  { public  boolean  validateUserName ( String  username)  { boolean  con =  false ; if  ( username. length ( )  >  8 )  { for  ( int  i =  0 ;  i <  username. length ( ) ;  i++ )  { char  ch =  username. charAt ( i) ;  if  ( ( ch >=  '0'  &&  ch <=  '9' )  ||  ( ch >=  'a'  &&  ch <=  'z' )  ||  ( ch >=  'A'  &&  ch <=  'Z' ) )  { con =  true ; }  else  { con =  false ; throw  new  IllegalArgumentException ( "用户名只能由字母和数字组成!" ) ; } } }  else  { throw  new  IllegalArgumentException ( "用户名长度必须大于 8 位!" ) ; } return  con; } public  static  void  main ( String [ ]  args)  { Test05  te =  new  Test05 ( ) ; Scanner  input =  new  Scanner ( System . in) ; System . out. println ( "请输入用户名:" ) ; String  username =  input. next ( ) ; try  { boolean  con =  te. validateUserName ( username) ; if  ( con)  { System . out. println ( "用户名输入正确!" ) ; } }  catch  ( IllegalArgumentException  e)  { System . out. println ( e) ; } } } 
*  throws  用来声明一个方法可能抛出的所有异常信息,表示出现异常的一种可能性,但并不一定会发生这些异常;throw  则是指拋出的一个具体的异常类型,执行 throw  则一定抛出了某种异常对象。
*  通常在一个方法(类)的声明处通过 throws  声明方法(类)可能拋出的异常信息,而在方法(类)内部通过 throw  声明一个具体的异常信息。
*  throws  通常不用显示地捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法; throw  则需要用户自己捕获相关的异常,而后再对其进行相关包装,最后将包装后的异常信息抛出。
多 catch  代码块虽然客观上提高了程序的健壮性,但是也导致了程序代码量大大增加。如果有些异常种类不同,但捕获之后的处理是相同的,例如以下代码。try { }  catch  ( FileNotFoundException  e)  { }  catch  ( IOException  e)  { }  catch  ( ParseException  e)  { } 3  个不同类型的异常,要求捕获之后的处理都是调用 methodA 方法。为了解决这种问题,Java  7  推出了多异常捕获技术,可以把这些异常合并处理。上述代码修改如下:try { }  catch  ( IOException  |  ParseException  e)  { } 注意:由于 FileNotFoundException  属于 IOException  异常,IOException  异常可以捕获它的所有子类异常。所以不能写成 FileNotFoundException  |  IOException  |  ParseException  。使用一个 catch  块捕获多种类型的异常时需要注意如下两个地方。*  捕获多种类型的异常时,多种异常类型之间用竖线| 隔开。*  捕获多种类型的异常时,异常变量有隐式的 final  修饰,因此程序不能对异常变量重新赋值。下面程序示范了 Java  7  提供的多异常捕获。public  class  ExceptionTest  { public  static  void  main ( String [ ]  args)  { try  { int  a =  Integer . parseInt ( args[ 0 ] ) ; int  b =  Integer . parseInt ( args[ 1 ] ) ; int  c =  a /  b; System . out. println ( "您输入的两个数相除的结果是:"  +  c) ; }  catch  ( IndexOutOfBoundsException  |  NumberFormatException  |  ArithmeticException  e)  { System . out. println ( "程序发生了数组越界、数字格式异常、算术异常之一" ) ; e =  new  ArithmeticException ( "test" ) ; }  catch  ( Exception  e)  { System . out. println ( "未知异常" ) ; e =  new  RuntimeException ( "test" ) ; } } } 
上面程序中第一行粗体字代码使用了IndexOutOfBoundsException | NumberFormatException | ArithmeticException 来定义异常类型,这就表明该 catch  块可以同时捕获这 3  种类型的异常。捕获多种类型的异常时,异常变量使用隐式的 final  修饰,因此上面程序的第 12  行代码将产生编译错误;捕获一种类型的异常时,异常变量没有 final  修饰,因此上面程序的第 17  行代码完全正确。
自定义异常的语法形式为:< class > < 自定义异常名> < extends > < Exception > IntegerRangeException  类继承自 Exception  类,在该类中包含两个构造方法。 创建一个名称为 IntegerRangeException  的自定义异常类:class  IntegerRangeException  extends  Exception  { public  IntegerRangeException ( )  { super ( ) ; } public  IntegerRangeException ( String  s)  { super ( s) ; } } 
示例: 
编写一个程序,对会员注册时的年龄进行验证,即检测是否在 0 ~ 100  岁。1 )这里创建了一个异常类 MyException ,并提供两个构造方法。MyException  类的实现代码如下:public  class  MyException  extends  Exception  { public  MyException ( )  { super ( ) ; } public  MyException ( String  str)  { super ( str) ; } } 2 )接着创建测试类,调用自定义异常类。代码实现如下:
import  java. util.  InputMismatchException ; 
import  java. util.  Scanner ; public  class  Test07  { public  static  void  main ( String [ ]  args)  { int  age; Scanner  input =  new  Scanner ( System . in) ; System . out. println ( "请输入您的年龄:" ) ; try  { age =  input. nextInt ( ) ;     if ( age <  0 )  { throw  new  MyException ( "您输入的年龄为负数!输入有误!" ) ; }  else  if ( age >  100 )  { throw  new  MyException ( "您输入的年龄大于100!输入有误!" ) ; }  else  { System . out. println ( "您的年龄为:" + age) ; } }  catch ( InputMismatchException  e1)  { System . out. println ( "输入的年龄不是数字!" ) ; }  catch ( MyException  e2)  { System . out. println ( e2. getMessage ( ) ) ; } } } 3 )运行该程序,当用户输入的年龄为负数时,则拋出 MyException  自定义异常,执行第二个 catch  语句块中的代码,打印出异常信息。在该程序的主方法中,使用了 if …else  if …else  语句结构判断用户输入的年龄是否为负数和大于 100  的数,如果是,则拋出自定义异常 MyException ,调用自定义异常类 MyException  中的含有一个 String  类型的构造方法。在 catch  语句块中捕获该异常,并调用 getMessage ( )  方法输出异常信息。提示:因为自定义异常继承自 Exception  类,因此自定义异常类中包含父类所有的属性和方法。
假设在某仓库管理系统的登录界面中需要输入用户名和密码,其中用户名只能由 6 ~ 10  位数字组成,密码只能有 6  位,任何不符合用户名或者密码要求的情况都视为异常,并且需要捕获并处理该异常。示例: 
1 )编写自定义异常类 LoginException ,该类继承自 Exception 。在 LoginException  类中包含两个构造方法,分别为无参的构造方法和含有一个参数的构造方法,代码如下:public  class  LoginException  extends  Exception  { public  LoginException ( )  { super ( ) ; } public  LoginException ( String  msg)  { super ( msg) ; } } 2 )创建测试类 Test08 ,在该类中定义 validateLogin ( )  方法,用于对用户名和密码进行验证。当用户名或者密码不符合要求时,使用自定义异常类 LoginException  输出相应的异常信息。validateLogin ( )  方法的定义如下:public  boolean  validateLogin ( String  username, String  pwd)  { boolean  con =  false ;     boolean  conUname =  false ;     try  { if  ( username. length ( )  >=  6  &&  username. length ( )  <=  10 )  { for  ( int  i =  0 ; i <  username. length ( ) ; i++ )  { char  ch =  username. charAt ( i) ;     if  ( ch >=  '0'  &&  ch <=  '9' )  {     conUname =  true ;     }  else  {     conUname =  false ; throw  new  LoginException ( "用户名中包含有非数字的字符!" ) ; } } }  else  {     throw  new  LoginException ( "用户名长度必须在6〜10位之间!" ) ; } if  ( conUname)  {     if  ( pwd. length ( )  ==  6 )  {     con= true ;     }  else  {     con =  false ; throw  new  LoginException ( "密码长度必须为 6 位!" ) ; } } }  catch ( LoginException  e)  {     System . out. println ( e. getMessage ( ) ) ; } return  con; } 3 )在 Test08  类中添加 main ( )  方法,调用 validateLogin ( )  方法,如果该方法返回 true ,则输出登录成功的信息。main ( )  方法的定义如下:public  static  void  main ( String [ ]  args)  { Scanner  input =  new  Scanner ( System . in) ; System . out. println ( "用户名:" ) ; String  username =  input. next ( ) ; System . out. println ( "密码:" ) ; String  password =  input. next ( ) ; Test08  lt =  new  Test08  ( ) ; boolean  con =  lt. validateLogin ( username, password) ;     if  ( con)  { System . out. println ( "登录成功!" ) ; } } 
异常对象的 printStackTrace() 方法用于打印异常的跟踪栈信息,根据 printStackTrace() 方法的输出结果,开发者可以找到异常的源头,并跟踪到异常一路触发的过程。测试 printStackTrace 的例子程序。class SelfException extends RuntimeException {SelfException() {}SelfException(String msg) {super(msg);}}public class PrintStackTraceTest {public static void main(String[] args) {firstMethod();}public static void firstMethod() {secondMethod();}public static void secondMethod() {thirdMethod();}public static void thirdMethod() {throw new SelfException("自定义异常信息");}}上面程序中 main 方法调用 firstMethod,firstMethod 调用 secondMethod,secondMethod 调用 thirdMethod,thirdMethod 直接抛出一个 SelfException 异常。运行上面程序,会看到如下所示的结果。Exception in thread "main" Test.SelfException: 自定义异常信息at Test.PrintStackTraceTest.thirdMethod(PrintStackTraceTest.java:26)at Test.PrintStackTraceTest.secondMethod(PrintStackTraceTest.java:22)at Test.PrintStackTraceTest.firstMethod(PrintStackTraceTest.java:18)at Test.PrintStackTraceTest.main(PrintStackTraceTest.java:14)
上面运行结果的第 2 行到第 5 行之间的内容是异常跟踪栈信息,从打印的异常信息我们可以看出,异常从 thirdMethod 方法开始触发,传到 secondMethod 方法,再传到 firstMethod 方法,最后传到 main 方法,在 main 方法终止,这个过程就是 Java 的异常跟踪栈。在面向对象的编程中,大多数复杂操作都会被分解成一系列方法调用。这是因为实现更好的可重用性,将每个可重用的代码单元定义成方法,将复杂任务逐渐分解为更易管理的小型子任务。由于一个大的业务功能需要由多个对象来共同实现,在最终编程模型中,很多对象将通过一系列方法调用来实现通信,执行任务。所以,面向对象的应用程序运行时,经常会发生一系列方法调用,从而形成“方法调用栈”,异常的传播则相反:只要异常没有被完全捕获(包括异常没有被捕获,或异常被处理后重新抛出了新异常),异常从发生异常的方法逐渐向外传播,首先传给该方法的调用者,该方法调用者再次传给其调用者……,直至最后传到 main 方法,如果 main 方法依然没有处理该异常,则 JVM 会中止该程序,并打印异常的跟踪栈信息。很多初学者一看到上面运行结果的异常提示信息,就会惊慌失措,其实结果中的异常跟踪栈信息非常清晰,它记录了应用程序中执行停止的各个点。异常跟踪栈信息的第一行一般详细显示异常的类型和异常的详细消息,接下来是所有异常的发生点,各行显示被调用方法中执行的停止位置,并标明类、类中的方法名、与故障点对应的文件的行。一行行地往下看,跟踪栈总是最内部的被调用方法逐渐上传,直到最外部业务操作的起点,通常就是程序的入口 main 方法或 Thread 类的 run 方法(多线程的情形)。关于多线程可以参考教程的《Java多线程编程》一章,也可忽略本节关于多线程的内容,学习完多线程在了解本节内容。下面例子程序示范了多线程程序中发生异常的情形。public class ThreadExceptionTest implements Runnable {public void run() {firstMethod();}public void firstMethod() {secondMethod();}public void secondMethod() {int a = 5;int b = 0;int c = a / b;}public static void main(String[] args) {new Thread(new ThreadExceptionTest()).start();}}运行上面程序,会看到如下运行结果。Exception in thread "Thread-0" java.lang.ArithmeticException: / by zeroat Test.ThreadExceptionTest.secondMethod(ThreadExceptionTest.java:14)at Test.ThreadExceptionTest.firstMethod(ThreadExceptionTest.java:8)at Test.ThreadExceptionTest.run(ThreadExceptionTest.java:4)at java.lang.Thread.run(Unknown Source)
多线程异常的跟踪栈,从发生异常的方法开始,到线程的 run 方法结束。从上面的运行结果可以看出,程序在 Thread 的 run 方法中出现了 ArithmeticException 异常,这个异常的源头是 ThreadExcetpionTest 的 secondMethod 方法,位于 ThreadExcetpionTest.java 文件的 14 行。这个异常传播到 Thread 类的 run 方法就会结束(如果该异常没有得到处理,将会导致该线程中止运行)。前面已经讲过,调用 Exception 的 printStackTrace() 方法就是打印该异常的跟踪栈信息,也就会看到上面两个示例运行结果中的信息。当然,如果方法调用的层次很深,将会看到更加复杂的异常跟踪栈。提示:虽然 printStackTrace() 方法可以很方便地用于追踪异常的发生情况,可以用它来调试程序,但在最后发布的程序中,应该避免使用它。应该对捕获的异常进行适当的处理,而不是简单地将异常的跟踪栈信息打印出来。
如果要生成简单的日志记录,可以使用全局日志记录器并调用其 info 方法,代码如下:Logger . getGlobal ( ) . info ( "打印信息" ) ; 
JDK Logging 把日志分为如下表 7 个级别,等级依次降低。 级别 SEVERE WARNING INFO CONFIG FINE FINER FINEST 调用方法 severe() warning() info() config() fine() finer() finest() 含义 严重 警告 信息 配置 良好 较好 最好 
 
Logger  的默认级别是 INFO ,比 INFO  级别低的日志将不显示。Logger  的默认级别定义在 jre 安装目录的 lib 下面。# Limit  the message that are printed on the console to  INFO  and above. java. util. logging.  ConsoleHandler. level =  INFO 所以在默认情况下,日志只显示前三个级别,对于所有的级别有下面几种记录方法:logger. warning ( message) ; logger. fine ( message) ; 同时,还可以使用 log 方法指定级别,例如:logger. log ( Level . FINE ,  message) ; 
可以使用 setLevel 方法设置级别,例如logger. setLevel ( Level . FINE ) ; 可以将 FINE  和更高级别的都记录下来。另外,还可以使用 Level . ALL  开启所有级别的记录,或者使用 Level . OFF  关闭所有级别的记录。注意:如果将记录级别设计为 INFO  或者更低,则需要修改日志处理器的配置。默认的日志处理器不会处理低于 INFO  级别的信息。public  class  Test  { private  static  Logger  log =  Logger . getLogger ( Test . class . toString ( ) ) ; public  static  void  main ( String [ ]  args)  { log. finest ( "finest" ) ; log. finer ( "finer" ) ; log. fine ( "fine" ) ; log. config ( "config" ) ; log. info ( "info" ) ; log. warning ( "warning" ) ; log. severe ( "server" ) ; } } 
可以通过编辑配置文件来修改日志系统的各种属性。在默认情况下,配置文件存在于 jre 安装目录下“jre/ lib/ logging. properties”。要想使用另一个配置文件,就要将 java. util. logging. config. file 特性设置为配置文件的存储位置,并用下列命令启动应用程序。java - Djava . util. logging. config. file =  configFile MainClass 日志管理器在 JVM  启动过程中初始化,这在 main 执行之前完成。如果在 main 中调用System . setProperty ( "java.util.logging.config.file" , file) ,也会调用LogManager . readConfiguration ( ) 来重新初始化日志管理器。要想修改默认的日志记录级别,就需要编辑配置文件,并修改以下命令行。. level= INFO 可以通过添加以下内容来指定自己的日志记录级别Test. Test . level= FINE 也就是说,在日志记录器名后面添加后缀 . level。在稍后可以看到,日志记录并不将消息发送到控制台上,这是处理器的任务。另外,处理器也有级别。要想在控制台上看到 FINE  级别的消息,就需要进行下列设置。java. util. logging.  ConsoleHandler. level= FINE 注意:在日志管理器配置的属性设置不是系统属性,因此,用 - Dcom . mycompany. myapp. level= FINE 启动应用程序不会对日志记录器产生任何影响。