Java面向对象编程扩展
静态类、内部类和匿名类是Java为我们提供的几个出色的工具。如果使用得当,可以让程序更易理解和维护。我们可以根据它们各自不同的特点来决定何时选用:
1、避免创建类的实例,可以直接使用静态类。
2、使相关的类都能存在于同一个源代码中,这要归功于内部类
3、避免一个程序产生大量非常小的类,这要归功于匿名类
下面主要讲解的是:静态类、匿名类、内部类和异常类的概念和使用
1、Java静态类:静态方法、静态变量、静态代码块、静态内部类
静态类,即定义了静态方法、静态变量、静态代码块的类,或静态内部类,这些静态方法、静态变量不需要实例化即可直接引用,而静态代码块则是用在系统初始化时。
静态类与非静态类的区别
先看代码:
public class HelloWorld{
Public static void main(String[] args){
System.out.println(“Hello,world!”);
}
}
这段简单的代码定义了一个静态的方法名为main,这个就意味着告诉Java编译器,这个 方法不需要创建一个此类的对象即可使用。 它只输出简单的”Hello , world”,然而,它却展示static关键字的主要用法.
静态类,即定义了静态方法、静态变量、静态代码块的类,或静态内部类。静态类和非静态类的区别如下:
静态类不能被实例化,类的成员主要是静态成员和静态方法
非静态类一般需要实例化后才可以操作,不过接口和抽象类不能直接实例化。
静态的类都是在运行时静态地加载到内存中(一开始运行首先从静态的类开始),所以 也不需要对它们进行初始化,也没有实例,因此在类的内部也不能用this
如果是静态内部类的话,静态内部类不能操作访问外部数据,静态内部类的实例生成不需要从外部生成相关类。
静态方法:
通常,在一个类中定义一个方法为static,那就是说,无须本类的对象即可调用此方法。调用一个静态方法就是“类名.方法名”,静态方法的使用很简单。
例如:
public class StaticClass {
//定义一个静态方法
public static void inc(String str){
System.out.println(str);
}
public static void main(String[] args) {
StaticClass.inc("这就一个静态类!"); //引用静态类
}
}
一般来说,静态方法常常为应用程序中的其它类提供一些实用工具所用,在Java的类库中大量的静态方法正是出于此目的而定义的。
静态变量:
静态变量与静态方法类似。所有此类实共享此静态变量,也就是说在类装载时,只分配一块存储空间,所有此类的对象都可以操控引块存储空间。
例如:
public class StaticClass {
//定义静态变量
public static int i = 0 ;
//定义静态方法
public static void inc(){
i++;
}
}
//测试静态类,修改静态变量
public class StaticTest {
public static void main(String[] args) {
StaticClass v1,v2;
v1 = new StaticClass();
v2 = new StaticClass();
System.out.println("v1.i="+v1.i+" v2.i="+v2.i);
v1.inc();
System.out.println("v1.i="+v1.i+" v2.i="+v2.i);
}
}
运行结果:
v1.i=0 v2.i=0
v1.i=1 v2.i=1
由此可见,在调用了v1.inc()方法后,修改了静态变量i的值,同时v2变量的i值也发生了变化。这个说明每位共享一块存储区。
静态代码块:
Static变量有点类似于C中的全局变量的概念,值得探讨的是静态变量的初始化问题。修改上面的程序:
例如
public class StaticClass {
// 定义静态变量
public static int i = 0;
public StaticClass() {
i = 15;
}
public StaticClass(int n) {
i = n;
}
// 定义静态方法
public static void inc() {
i++;
}
}
//测试静态类,修改静态变量
public class StaticTest {
// 初始化静态构造函数
StaticClass v = new StaticClass(10);
// 访问静态类
static StaticClass v1, v2;
// 静态代码块
static {
System.out.println("初始化:v1.c=" + v1.i + " v2.i=" + v2.i);
v1 = new StaticClass(27);
System.out.println("初始化:v1.c=" + v1.i + " v2.i=" + v2.i);
v1 = new StaticClass(15);
System.out.println("初始化:v1.c=" + v1.i + " v2.i=" + v2.i);
}
public static void main(String[] args) {
StaticTest test = new StaticTest();
System.out.println("test.i=" + test.v.i);
System.out.println("v1.i=" + v1.i + " v2.i=" + v2.i);
v1.inc();
System.out.println("v1.i=" + v1.i + " v2.i=" + v2.i);
System.out.println("test.i=" + test.v.i);
}
}
从输出的结果可以看出,静态代码块在系统启动时就执行了输出。在static{后面跟着一段代码,这是用来显式地初始化静态变量,这段代码只会初始化一次,且在类被第一次装载时}
另外,static定义的变量会优先于任何其它非static变量,不论其出现的顺序如何。例子中v是在v1和v2的前面,但结果却是v1和v2的初始化在v前面。在涉及到继承的时候,会先初始化父类的static变量,然后是子类。
静态内部类:
Static在类的内部定义静态变量、静态方法和静态代码块的使用情形,除此之外,它还可以修饰class的定义。
通常一个普通类不允许声明为静态的,只有一个内部类可以。这时这个声明为静态的内部类可以直接作为一个普通类来使用,而不需要实例化一个外部类。
例如:
public class Tests {
//定义一个静态内部类
public static class InnerClass{
//静态内部类构造函数
InnerClass(){
System.out.println("InnerClass");
}
//静态内部定义的方法
public void print(String string){
System.out.println(string);
}
}
public static void main(String[] args) {
Tests.InnerClass ci = new Tests.InnerClass();//应用静态内部类
ci.print("this is String");
}
}
输出结果:
InnerClass
this is String
静态内部类和普通类的用法是一样的
2、Java匿名类:匿名类的定义、使用实例、处理Swing事件
package Test;
public class A {
public void f() {
/*
* class B extends Thread{ //定义一个内部类 public void run(){}//具体实现 } class C
* implements Runnable{//定义一个内部接口
*
* @Override public void run() {//具体实现 // TODO Auto-generated method
* stub
*
* } } B b = new B(); C c = new C();
*/
// 但是在方法中再定义内部类往往显得累赘,于是上面的代码就可以用匿名类重写
Thread bs = new Thread() { // 匿名内部类
public void run() {
}// 具体实现
};
Runnable cs = new Runnable() {// 匿名内部接口
@Override
public void run() {// 具体实现
// TODO Auto-generated method stub
}
};
}
}
匿名内部类通常用在GUI编程添加事件监听器中,目的的简化代码编写。
定义:从技术上说,匿名类可被视为非静态的内部类,所以它们具有和方法内部声明的非静态内部类一样的权限和限制。如果要执行的任务需要一个对象,但却不值得创建全新的对象(原因可能是所需的类过于简单,或者是由于它只在一个方法内部使用),匿名类就显得非常有用。
匿名类是不能有名称的类,所以没办法引用它们。必须在创建时,作为new语句的一部分来声明它们。这就要采用另一种形式的new语句。
New <类或接口><类的主体>
注意:匿名类的声明是在编译时进行的,实例化在进行时进行。这意味着for循环中的一个new语句会创建相同匿名类的几个实例,而不是创建几个不同匿名类的一个实例。
实例:定义一个接口ITest,并定义几个接口函数pring1():
package Test;
public interface ITest {
void print1();
}
然后新建一个类MyClass,添加一个函数getTest()用来返回的ITest的实例。在该函数中使用new ITest的语句声明一个匿名的内部类,然后使用{}来添加该内部类的主体
package Test;
public class MyClass {
public ITest getTest() {
return new ITest() { // 声明匿名类
public void print1() {// 内部类实现ITest接口的函数
System.out.println("Hello world");
}
};
}
public static void main(String[] args) {
MyClass c = new MyClass();
ITest test = c.getTest();
test.print1();
}
}
在main()函数中,即可以创建类MyClass的实例C,通过该实例调用getTest()即可返回ITEST 的实例test。ITest也可以是一个类,但是外部调用的方法必须在这个类或接口中声明,外部不能调用匿名类内部的方法。
3、Java内部类:内部类的含义、使用实例、局部内部类、引用外部类对象。
Java 内部类(Inner Class)
内部类的定义在另一个类的内部的类。可能很多人不太熟悉Java内部类(Inner Class),实际上类似的概念在C++里也有,那就是嵌套类(Nested Class)。
内部肉的含义:
简单地说,“内部类”是在另一个类的内部声明的类。Java语言规范允许定义以下几种内部类。
在另一个类或者一个接口中声明一个类。
在另一个接口或者一个类中声明一个接口。
在一个方法中声明一个类。
类和接口声明可嵌套任意深度。
内部类是在一个类的内部嵌套定义的类,它可以是其他类的成员,也可以在一个语句块的内部定义,还可以在表达式内部匿名定义。
内部类有如下特性
一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称,名字不能与包含它的类名相同.
可以使用包含它的类的静态和实例成员变量,也可以使用它所在方法的局部变量。
可以定义为abstract
可以声明为private或protected
若被声明为static,就变成了顶层类,不能再使用局部变量.
若想在Inner Class中声明任何static成员,则该Inner Class 必须声明为static
下面从以下几个方面来介绍:
1)内部类的可视范围
内部类的可视范围是它的直接外嵌类(这一点和嵌套静态类不同,嵌套类在其外嵌类之外也是可视的),也就是说内部类可以直接引用外嵌类中的任何成员。因为内部类与外嵌类的实例相关,所以内部类拥有两个this指针,一个指向内部类的实例本身,一个指向外嵌类实例
2)使用内部类的好处
封装性好:直接通过操作等到内部类内容,你甚至边这两个内部类的名字都没有看见!一个内部类好象可以访问创建它的外部类对象的内容,甚至包括私有变量。
在Java中的内部类和接口加在一起,可以解决常被C++程序员抱怨Java中存在的一个问题-----没有多继承。实际上,C++的多继承设计起来很复杂,而Java通过内部类加上接口,可以很好的实现多继承的效果。
3)内部类的用途
内部类的一个主要用途是用于GUI的事件处理程序。
内部类的使用实例:
下面通过发功邮件的例子来演示内部类的使用。
定义两个接口,一个是邮件内容接口,一个是邮件接收地址接口IAddress
public interface IContent {
String getContent();
}
public interface IAddress {
String getAddress();
}
然后定义一个邮件类Mail,在它内部定义两个内部类Content和Address,分别实现上面的接口IContent 和IAddress:
public class Mail {
// 邮件内容
private class Content implements IContent {
private String content;
public Content(String content) {
this.content = content;
}
@Override
public String getContent() {
// TODO Auto-generated method stub
return content;
}
}
//接受地址内部类
protected class Address implements IAddress{
private String address;
public Address(String address) {
this.address = address;
}
@Override
public String getAddress() {
// TODO Auto-generated method stub
return address;
}
}
public IContent getContent(String s){
return new Content(s);
}
public IAddress getAddress(String s){
return new Address(s);
}
}
测试类:
public class TestMail {
public static void main(String[] args) {
Mail mail = new Mail();
IContent content = mail.getContent("这是邮件正文");
IAddress address = mail.getAddress("lzb_box@189.com");
}
}
类Content和Address被定义在类Main内部,并且分别由Protected和private修饰符来控制访问级别。Content代表着Mail的邮件内容,而Address代表着Mail的接收地址。它们分别实现了两个接口IContent和IAddress。在后面的main方法里,直接用IContent content和IAddress address 进行操作,你甚至连两个内部类的名字都没有看见!这样,内部类的第一个好处就体现出来了----隐藏你不想让别人知道的操作,即封装性。
同时,在外部类作用范围之外得到内部类对象的第一个方法,那就是利用其外部类的方法创建并返回。上例中的getContent()和getAddress()方法就是这么做的。
局部内部类:
前面定义的内部类都是在类的内部定义的。除此之处Java内部类也可以是局部的,即它可以定义在一个方法,或者在一个代码块之内。
修改上面的例子:
package Test;
public class Mail {
//取得邮件内容实例
public IContent getContent(String s){
//邮件内容内部类
class Content implements IContent{
private String content;
private Content(String content){
this.content = content;
}
@Override
public String getContent() {
// TODO Auto-generated method stub
return content;
}
}
return new Content(s);
}
//取得接受地址实例
public IAddress getAddress(String s){
//接受地址内部类
class Address implements IAddress{
private String address;
public Address(String address) {
this.address = address;
}
@Override
public String getAddress() {
// TODO Auto-generated method stub
return address;
}
}
return new Address(s);
}
}
当内部类定义放在函数内部时,class前不能够使用private 、protected和public 修饰符,因为此时的类仅仅在该代码块作用可见,不需要对外部开放可视权限。
package Test;
public class Mail {
// 取得邮件内容实例
public IContent getContent(String s) {
if (s != null) {
// 邮件内容内部类
class Content implements IContent {
private String content;
private Content(String content) {
this.content = content;
}
@Override
public String getContent() {
// TODO Auto-generated method stub
return content;
}
}
return new Content(s);
} else {
return null;
}
}
// 取得接受地址实例
public IAddress getAddress(String s) {
if (s != null) {
// 接受地址内部类
class Address implements IAddress {
private String address;
public Address(String address) {
this.address = address;
}
@Override
public String getAddress() {
// TODO Auto-generated method stub
return address;
}
}
return new Address(s);
} else {
return null;
}
}
}
不能在if之外创建这个内部类的对象,因为这已经超出了它的作用域。不过在编译的时候,内部类Content、Address和其他类一样同时被编译,只不过它有自己的作用域,超出了这个范围就无效,除此之外它和其他内部类并没有区别。
内部类引用外部类对象:
根据内部类的含义可知,内部类的对象还可以引用外部类的对象。为了具体说明如何引用外部对象,对上面的例子稍作修改
public class Mail {
// 取得邮件内容实例
private String lzbbox = "lzb_box@189.com";
// 邮件内容内部类
private class Content implements IContent {
private String content;
private Content(String content) {
this.content = content;
}
@Override
public String getContent() {
// TODO Auto-generated method stub
return content;
}
}
// 接收地址内部类
protected class Address implements IAddress {
private String address = lzbbox;
public Address(String address) {
this.address = address;
}
@Override
public String getAddress() {
// TODO Auto-generated method stub
return address;
}
}
//取得邮件内容实例
public IContent getContent(String s){
return new Content(s);
}
//取得地址实例
public IAddress getAddress(String s){
return new Address(s);
}
}
在这里我们给Mail类增加了一个private成员变量lzbbox,意义是黑夜的接收地址,在内部类Address中黑夜引用了该类的变量。
由此可见,一个内部类对象可以访问创建它的外部类对象的内容,甚至包括私有变量!这是一个非常有用的特性,为我们在设计时提供了更多的思路和捷径。要想实现这个功能,内部类对象就必须有指向外类对象的引用。Java编译器在创建内部类对象时,隐式地把其外部类对象的引用也传了进去并一起保存着。这样就使得内部类对象始终可以访问其外部类对象,同时这也是为什么在外部类作用范围之外要创建内部类对象必须先创建其外部类对象的原因。
Java还可以用下面的格式表达外部类的引用:
外部类名.this.外部变量名
对于上面的变量lzbbox,如果使用与ASSRES内部同名的变量address,那么在内部需要使用Mail.this.address;来引用它,例如:
public class Mail {
// 取得邮件内容实例
private String address = "lzb_box@189.com";
// 邮件内容内部类
private class Content implements IContent {
private String content;
private Content(String content) {
this.content = content;
}
@Override
public String getContent() {
// TODO Auto-generated method stub
return content;
}
}
// 接收地址内部类
protected class Address implements IAddress {
private String address = Mail.this.address;
public Address(String address) {
this.address = address;
}
@Override
public String getAddress() {
// TODO Auto-generated method stub
return address;
}
}
//取得邮件内容实例
public IContent getContent(String s){
return new Content(s);
}
//取得地址实例
public IAddress getAddress(String s){
return new Address(s);
}
}
在创建非静态内部类对象时,一定要先创相应的外部类对象,因为非静态内部类对象有着指向其外部类对象的引用。
下面介静态内部类和非静态内部类。
和普通的类一样,内部类也可以有静态。不过和非静态内部类相比,区别就在于静态内部类没有了指向外部的引用。
除此之外,在任何非静态内部类中,都不能有静态数据,静态方法或者另外一个静态的内部类(内部类的嵌套可以不止一层)。不过静态内部类中却可以拥有这一切,这是两者的第二个区别。