概述
一、内部类
在某些情况下,我们把一个类放在另一个类的内部定义,这个定义在其它类内部的类就被称为内部类(有的地方也称为嵌套类),包含内部类的外部类也被称为外部类(有的地方也叫作宿主类)。Java 从 JDK 1.1 开始引入内部类。
内部类的分类
内部类可以分为:成员内部类和局部内部类,其中成员内部类又分为:静态内部类和非静态内部类
1.1 非静态内部类
定义非静态内部类的语法格式如下:
public class OuterClass
{
[访问修饰符] class 类名{
......
}
}
定义静态内部类的语法格式如下:
public class OuterClass
{
[访问修饰符] static class 类名{
......
}
}
静态内部类和非静态内部类的访问修饰符可以是所有——public、protected、默认、private,且内部类可以无限嵌套。
示例:
package outclasspackage;
public class OuerClass {
public class InnerClass1{
protected class InnerClass1_1{
class InnerClass1_1_1{
private class InnerClass1_1_1_1{
// ... 非晶态内部类可以无限嵌套, 它的访问权限修饰符可以是所有——public、protected、默认、private
}
}
}
}
public static class InnerStaticClass1{
protected static class InnerStaticClass1_1{
static class InnerStaticClass1_1_1{
private static class InnerClass1_1_1_1{
// ... 静态内部类可以无限嵌套, 它的访问权限修饰符可以是所有——public、protected、默认、private
}
}
}
}
}
我们经常看到同一个 Java 源文件中定义了多个类,那种情况并不是内部类,他们依然是两个互相独立的类。例如下面的程序:
另外除共有类之外的其他类的访问权限修饰符这能使用默认,private 和 protected 不是不被允许的。
内部类成员可以直接访问外部类的私有数据,因为内部类被当做其外部类的类成员,同一个类的成员之间可以互相访问。但外部类不能访问内部类的实现细节,例如内部类的属性。
示例:
package cowpackage;
public class Cow {
private double weight;
// 外部类的两个重载构造器
public Cow()
{
}
public Cow(double weight)
{
this.weight = weight;
}
// 定义一个非静态内部类
private class CowLeg
{
// 非静态内部类的两个属性
private double length;
private String color;
// 非静态内部类的两个重载的构造器
public CowLeg(){}
public CowLeg(double length, String color)
{
this.length = length;
this.color = color;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
// 非静态内部类的实例方法
private void info()
{
/*
* 在非静态内部类的方法内访问某个变量时,系统优先在该方法内查找是否存在该名字的局部变量,如果存在该名字的局部变量,就使用该变量;
* 如果不存在,则到该方法所在的内部类中查找是否存在该名字的属性,如果存在则使用该属性;
* 如果不存在,则到该内部类所在的外部类中查找是否存在该名字的属性,如果存在则使用该属性。
* 如果依然不存在,系统将出现编译错误:提示找不到该变量。
* 因此如果外部类属性、内部类属性与内部类里方法的局部变量同名,则可通过如下方法区分调用哪个属性:
* 访问外部类属性:外部类类名.this.外部类成员属性
* 访问内部类属性:this.内部类成员属性 或者 外部类类名.内部类类名.this.内部类成员属性
* 访问内部类局部变量:内部类局部变量名
*/
System.out.println("当前牛腿颜色是:" + color + ", 高:" + this.length);
// 直接访问外部类的 private 属性:weight
System.out.println("本牛腿所在的奶牛体重:" + Cow.this.weight);
}
}
public void test()
{
CowLeg cl = new CowLeg(1.12, "黑白相间");
cl.info();
}
public static void main(String[] args) {
Cow cow = new Cow(378.9);
cow.test();
}
}
编译上面的程序,生成两个 class 文件,一个是 Cow.class,另一个是Cow$CowLeg.class,前者是 Cow 的 class 文件,后者是内部类 CowLeg 的 class 文件。即成员内部类(包含静态内部类、非静态内部类)的 class 文件总是这种形式:OuterClass$InnerClass.class。
另外,不允许在外部类的静态成员中直接使用非静态内部类。例如:
非静态内部类中不允许定义任何静态成员:例如:
从编译器的提示信息我们可以看出来有三种解决上面编译错误的方案:
(1) 将 STR 变成常量,即用 static final 修饰。
(2) 去掉 static 修饰符
(3) 将 STR 所在的内部类修改为静态内部类。
1.2 静态内部类
使用 static 关键字来修饰一个内部类,则这个内部类是和外部类类相关的,属于整个外部类,二不是单独属于外部类的某个对象。使用 static 修饰的内部类被称为类的内部类,有的地方也称之为静态内部类。
static 只可以用来修饰内部类,不允许用来修饰外部类。
静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规则,所以静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。
外部类依然不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类的对象作为调用者来访问静态内部类的实例成员。
除此之外,Java 还允许在接口里定义内部类,接口里定义的内部类默认使用 public static 修饰,也就是说接口中的内部类只能是静态内部类。接口中也可以定义静态内部接口、静态内部类、静态内部抽象类。
示例:在接口中定义静态内部接口、静态内部类、静态内部抽象类,并且使用他们的成员属性、静态属性、静态方法的示例:
package mypackage;
public interface OuterInterface {
/**
* 接口内的静态类
* 其中 public、static 是默认并且是唯一的选择,所以可以省略 public、static 两个关键字
* @author Administrator
*
*/
public static class InnerClass{
public String innerClassStr = "innerClassStr";
public static String innerClassStaticStr = "innerClassStaticStr";
public String InnerClassFunction(){
return "InnerClassFunction";
}
// ...
}
/**
* 接口中的静态接口
* 同理,其中 public、static 是默认并且是唯一的选择,所以可以省略 public、static 两个关键字
* @author Administrator
*
*/
public static interface InnerInterface{
public static String InnerInterfaceStaticFinalStr = "InnerInterfaceStaticFinalStr";
// ...
}
/**
* 接口中的静态抽象类
* 同理,其中 public、static 是默认并且是唯一的选择,所以可以省略 public、static 两个关键字
*/
public static abstract class AbstractClass{
public static String abstractClassStaticStr = "abstractClassStaticStr";
public static final String abstractClassStaticFinalStr = "abstractClassStaticFinalStr";
public final String abstractClassFinalStr = "abstractClassFinalStr";
public String bstractClassFunction(){
return "bstractClassFunction";
}
// ...
}
}
class SubClass extends OuterInterface.AbstractClass{
// ...
}
class MainClass{
public static void main(String[] args) {
System.out.println(OuterInterface.InnerClass.innerClassStaticStr);
System.out.println(new OuterInterface.InnerClass().innerClassStr);
System.out.println(new OuterInterface.InnerClass().InnerClassFunction());
System.out.println(OuterInterface.InnerInterface.InnerInterfaceStaticFinalStr);
System.out.println(OuterInterface.AbstractClass.abstractClassStaticStr);
System.out.println(OuterInterface.AbstractClass.abstractClassStaticFinalStr);
OuterInterface.AbstractClass abstractClass = new SubClass();
System.out.println(abstractClass.abstractClassFinalStr);
System.out.println(abstractClass.bstractClassFunction());
}
}
1.3 使用内部类
分三种情况讨论内部类的用法:
(1)在外部类内部使用内部类
在前面的例子中可以看出来在外部类中使用内部类与使用普通类没有太大的区别,一样可以声明内部类的引用,通过 new 关键字调用内部类构造器来创建内部类实例。
唯一存在的不同是:不要再外部类的静态成员(包括静态方法和静态初始化块)中使用非静态内部类,因为静态成员不允许访问非静态成员。
(2)在外部类以外使用非静态内部类
如果需要在外部类以外的地方访问内部类(包括静态内部类和非静态内部类两种),则内部类不能使用 private 访问控制权限,private 修饰的内部类只能在外部类内部使用。对于使用其他访问控制修饰符的内部类,则能在访问控制修饰符对应的访问权限内使用。
默认:省略访问控制修饰符的内部类,只能被与外部类处于同一个包中其他类所访问。
protected:可被与外部类处于同一个包中的其他类和外部类的子类访问。
public:可在任何地方被访问
声明内部类变量的语法:
packagename.OuterClass.InnerClass varName;
声明非静态内部类的语法:
OuterInstance.new InnerConstructor()
示例:
Out 类:
package mypackage;
public class Out
{
class In
{
public In(String msg)
{
System.out.println(msg);
}
public In()
{
// 空构造器
}
}
}
MainClass 类:
package mypackage;
public class MainClass
{
/**
* @param args
*/
public static void main(String[] args)
{
Out.In in = new Out().new In("测试消息");
// 把这行代码拆开来等于
Out.In in2;
Out out = new Out();
in2 = out.new In("测试信息");
}
}
当创建一个子类时,子类的构造器总会调用父类的构造器,因此在创建非静态内部类的子类时,必须保证让子类构造器可以调用非静态内部类的构造器,调用非静态内部类的构造器时,必须存在一个外部类的对象。
SubClass 类:
非静态内部类 In 类的构造器必须使用外部类对象来调用。
从上面的代码中可以看出,如果需要创建 SubClass 对象时,必须先创建一个 Out 对象。因为创建子类必须先创建父类,如果父类是内部类,则必须先创建外部类。
非静态内部类 In 对象和 SubClass 对象都必须保留有指向 Outer 对象的引用,区别是创建两种对象时传入 Out 对象的方式不同:当创建非静态内部类 In 类的对象时,必须通过 Outer 对象来调用 new 关键字;当创建 SubClass 类的对象时,必须将 Outer 对象作为参数传给 SubClass 的构造器。也就是说非静态内部类的子类不能存在没有入参的构造器。
(3)在外部类以外使用静态内部类
因为静态内部类是和外部类类相关的,因此创建内部类对象时无需创建外部类的对象。语法如下:
new OuterClass.InnerConstructor();
(4)局部内部类
如果把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法里有效。因此,局部内部类不能在外部类以外的地方使用,那么局部内部类也不能使用访问控制符和 static 修饰符修饰。
如果需要用局部内部类定义变量,创建实例或派生子类,都只能在局部内部类所在方法内进行。
示例:
public class LocalInnerClass
{
public static void main(String[] args)
{
// 定义局部内部类
class InnerBase
{
int a;
}
// 定义局部内部类的子类
class InnerSub extends InnerBase
{
int b;
}
// 创建局部内部类的对象
InnerSub is = new InnerSub();
is.a = 5;
is.b = 8;
System.out.println("InnerSub 对象的 a 和 b 属性是:" + is.a + "," + is.b);
}
}
编译上面的程序,会看到生成三个 class 文件:LocalInnerClass.class、LocalInnerClass$1InnerBase.class 和 LocalInnerClass$1InnerSub.class,这表明局部内部类的 class 文件总是遵循如下命名格式:OutClass$NInnerClass.class,注意到局部内部类的 class 文件的文件名比成员内部类的 class 文件的文件名多了一个数字,这是因为同一个类里不能有两个同名的成员内部类,而同一个类里则可能有两个以上同名的局部内部类(处于不同方法中),所以 Java 为局部内部类的 class 文件名中增加了一个数字,加以区分。
(5)匿名内部类
匿名内部类适合创建那种只使用一次的类。定义匿名内部类的格式如下:
new 父类构造器(实参列表) | 实现接口()
{
// 匿名内部类的类体部分
}
从上面的定义可以看出,匿名内部类必须继承一个父类或实现一个接口,但最多只能实现一个父类,或实现一个接口。
关于匿名内部类还有如下两条规则:
-
匿名内部类不能是抽象类,因为系统在创建匿名内部类的时候,会立即创建匿名内部类的对象。因此不允许将匿名内部类定义成抽象类
-
匿名内部类不能定义构造器,因为匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义实例初始化块,可以通过实例初始化块来完成构造器需要完成的工作。
示例:
interface Product
{
public double getPrice();
public String getName();
}
public class TestAnonymous
{
public void test(Product p)
{
System.out.println("购买了一个" + p.getName() + ",花掉了" + p.getPrice());
}
public static void main(String[] args)
{
TestAnonymous ta = new TestAnonymous();
//调用test方法时,需要传入一个Product参数,此处传入其匿名实现类的实例
ta.test(new Product()
{
public double getPrice()
{
return 567.8;
}
public String getName()
{
return "AGP显卡";
}
});
}
}
如果不写作匿名内部类形式,则相当于如下代码:
class AnonymousProduct implements Product
{
@Override
public String getName()
{
return "AGP 显卡";
}
@Override
public double getPrice()
{
return 567.8;
}
}
ta.test(new AnonymousProduct());
由于匿名内部类不能是抽象类,所以匿名类必须实现它的抽象父类或者接口中的所有抽象方法。而成员内部类、静态内部类和局部内部类都可以是抽象类。
当通过实现接口来创建匿名内部类时,匿名内部类也不能显示创建构造器,因此匿名内部类只有一个隐式的无参数构造器,故 new 接口名后的括号里不能传入参数值。
但如果通过继承父类来创建匿名内部类,匿名内部类将拥有和父类相似的构造器,这里的相似指的是拥有相同的形参列表。
示例:
abstract class Device
{
protected String name;
public abstract double getPrice();
public Device(){}
public Device(String name)
{
this.name = name;
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
}
public class AnonymousInner
{
public void test(Device d)
{
System.out.println("购买了一个" + d.getName() + ",花掉了" + d.getPrice());
}
public static void main(String[] args)
{
AnonymousInner ai = new AnonymousInner();
// 拥有有参数的构造器创建 Device 匿名实现类的对象
ai.test(new Device("电子示波器"){
@Override
public double getPrice() {
return 67.8;
}
});
// 调用无参数的构造器创建 Device 匿名实现类的对象
Device d = new Device()
{
// 成员初始化块
{
this.name = "流川枫";
System.out.println("正在初始化匿名实现类……");
}
@Override
public double getPrice() {
return 56.2;
}
// 重写父类的实例方法
@Override
public String getName()
{
return this.name + "haha";
}
};
ai.test(d);
}
}
关于匿名内部类方法外部类的成员属性和局部属性的访问控制见如下示例所示:
当是成员匿名类时:
当是局部匿名类时:
(6)闭包 (Closure) 和回调
闭包 (Closure) 是一种能被调用的对象,它保存了创建它的作用域的信息。
Java 并不显式地支持闭包,但对于非静态内部类而言,它不仅记录了其外部类的详细信息,还保留了一个创建非静态内部类对象的引用,并且可以直接调用外部类的 private 成员,因此可以把非静态内部类当成面向对象领域的闭包。
通过这种仿必报的非静态内部类,可以实现回调。
最后
以上就是无私帆布鞋为你收集整理的第六课时:面向对象(5)—— 2010年05月22日的全部内容,希望文章能够帮你解决第六课时:面向对象(5)—— 2010年05月22日所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复