今天第一次学习内部类这个概念,觉得其中的内容很多很繁琐。还不清楚在什么情况下使用一个内部类比较合适,就先研究一下他的语法吧。
1.最普通的内部类,将类的定义置入一个用于封装它的类内部。
package c07;
public class Parcell {
class Contents{
private int i = 11;
public int value(){return i;}
}
class Destination{
private String label;
Destination(String whereTo){
label = whereTo;
}
String readLabel(){return label;}
}
public void ship(String dest){
Contents c= new Contents();
Destination d = new Destination(dest);
}
public static void mian(String args[]){
Parcell p = new Parcell();
p.ship("Tanzania");
}
}
若在ship()内部使用,则会发现内部类的使用看起来和其他类没有任何分别。在这里唯一的区别就是它的名字嵌套在Parcel1里面,但是这并不是内部类与普通类的唯一区别。
2.外部类拥有一个特殊的方法,它会返回指向一个内部类的句柄。
package c07;
public class Parcel2 {
class Contents{
private int i = 11;
public int value(){return i;}
}
class Destination{
private String label;
Destination(String whereTo){
label = whereTo;
}
String readLabel(){return label;}
}
public Destination to(String s){
return new Destination(s);
}
public Contents cont(){
return new Contents();
}
public void ship(String dest){
Contents c = cont();
Destination d = to(dest);
}
public static void main(String args[]){
Parcel2 p = new Parcel2();
p.ship("Tanzania");
Parcel2 q = new Parcel2();
Parcel2.Contents c =p.cont();
Parcel2.Destination d = q.to("Borneo");
}
}
在外部类中的方法cont()和to(String desst)分别可以返回内部类Contents和Destination的句柄,这是一种更典型的情况。
3.内部类和上溯造型
当我们准备上溯造型到一个基础类(特别是到一个接口)的时候,内部类就开始发挥其关键作用(用于实现的对象生成一个接口句柄具有与上溯造型置一个基础类相同的效果)。这是由于内部类随后可完全进入不可见或不可用状态——对任何人都如此。所以我们可以很方便地隐藏实施细节。我们得到地全部汇报就是一个基础类或者接口地句柄, 甚至有可能不知道准确地类型。就像下面这样:
package c07;
abstract class Contents{
abstract public int value();
}
interface Destination{
String readLabel();
}
public class Parcel3 {
private class PContents extends Contents{
private int i = 11;
public int value(){return i;}
}
protected class PDestination implements Destination{
private String label;
private PDestination(String whereTo){
label = whereTo;
}
public String readLabel(){
return label;
}
}
public Destination dest(String s){
return new PDestination(s);
}
public Contents cont(){
return new PContents();
}
}
class Test{
public static void main(String args[]){
Parcel3 p = new Parcel3();
Contents c = p.cont(); //只能通过方法来返回内部类的句柄,又通过上Destination d = p.dest("Tanzania"); 溯造型将句柄转换成内部类对应的接口或基本类
//! Parcel3.PContents c = p.new PContents(); (非法语句,类PContents是私有内部类,无法访问)
Destination d = p.dest("Tanzania");
//! Parcel3.PDistination p = p.new PDistination("Tanzania"); (等号左边合法,因为类PDistination是protected的对包内成员是“友好的; 但是等号右边非法,因为构建器PDistination("String dest")是private的。) }
}
}
4.方法和作用域中的内部类
在以下两种情况下我们会在一个方法甚至一个任意的作用于内创建内部类:
(1)准备实现某种形式的接口,使得自己能创建和返回一个句柄;
(2)要解决一个问题,并希望创建一个类辅助自己的程序方案,同时不愿把它公开
①.在一个方法内定义的类
package c07.innerscopes;
//在一个方法内定义的类, 对类的访问权限只局限于方法体中,不可从方法dest(String s)外部访问。
public class Parcel4 {
public Destination dest(String s){
class PDestination implements Destination{
private String label;
private PDestination(String whereTo){
label = whereTo;
}
public String readLabel(){ return label; }
}
return new PDestination(s);
}
public static void main(String args[]){
Parcel4 p = new Parcel4();
Destination d = p.dest("Tanzania");
}
}
PDestination类属于dest()的一部分,而不是Parcel4的一部分(同时注意可以为相同目录内每个类内部的一个内部类使用类标识符PDestination,这样做不会发生命名的冲突)。因此,PDestination不可从dest()的外部访问。
②.在任意作用于嵌套一个内部类:
package c07.innerscopes;
//在任意作用域内嵌套一个内部类,在定义它的那个作用域之外它是不可使用的。
public class Parcel5 {
private void internalTracking(boolean b){
if(b){
class TrackingSlip{
private String id;
TrackingSlip(String s){
id = s;
}
String getSlip() { return id; }
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlip();
}
//! TrackingSlip ts = new TrackingSlip("x");(非法语句,此处在内部类TrackingSlip定义的作用于之外(既在if之后的花括号之外),已脱离了内部类所在的作用域。)
}
public void track(){ internalTracking(true); }
public static void main(String args[]){
Parcel5 p =new Parcel5();
p.track();
}
}
} }
③.普通匿名类(使用默认构建器)
package c07.innerscopes;
//一个匿名类,用于实现一个接口,在匿名内部类中,Contents是用一个默认构建器创建的。
public class Parcel6 {
public Contents cont(){
return new Contents(){ //创建从Contents衍生出来的匿名类的一个对象。
private int i = 11;
public int value(){ return i; }
}; //必须加一个分号,标志用于包含匿名类的表达式的结束
}
public static void main(String args[]){
Parcel6 p = new Parcel6();
Contents c = p.cont();
}
}
④.匿名类(基础类需要含有自变量的一个构建器)
package c07.innerscopes;
//基础类(Contents)需要含有自变量的一个构建器
public class Parcel7 {
public Wrapping wrap(int x){
return new Wrapping(x){ //将自变量x传递给基础类构建器
public int value(){
return super.value() * 47;//匿名类无构造器,故这里用super
}
}; //必须加一个分号,标志用于包含匿名类的表达式的结束
}
public static void main(String args[]){
Parcel7 p = new Parcel7();
Wrapping w = p.wrap(10);
System.out.println(w.value());
}
}
以下是类Wrapping定义:
public class Wrapping {
private int i;
public Wrapping(int x){ i = x; }
public int value(){ return i; }
}
⑤.匿名类(用字段进行初始化)
package c07.innerscopes;
//匿名类用于执行字段初始化
public class Parcel8 {
public Destination dest(final String dest){ //参数必须为final
return new Destination(){
private String label = dest;
public String readLabel(){ return label; }
};
}
public static void main(String args[]){
Parcel8 p = new Parcel8();
Destination d = p.dest("Tanzania");
System.out.println(d.readLabel());
}
}
若试图定义一个匿名内部类,并想使用在匿名内部类外部定义的一个对象(比如上例中的dest参数),则编辑器要求外部对象为final属性,这正是我们将dest()的自变量设为final的原因。(注意跟⑤进行区分,一个是用带参数的构造器进行初始化,这个是在匿名类中用的字段初始化进行的)
⑥.一个匿名类,通过实例初始化进行构建(匿名类内部类不可拥有构建器)
package c07.innerscopes;
//匿名类通过实例初始化进行构建(匿名内部类不可拥有构建器)
public class Parcel9 {
public Destination dest(final String dest, final float price){
return new Destination(){
private int cost;
{
cost = Math.round(price);
if(cost > 100)
System.out.println("Over budget!");
}
private String label = dest;
public String readLabel(){ return label; }
};
}
public static void main(String args[]){
Parcel9 p = new Parcel9();
Destination d = p.dest("Tanzania", 101.395f);
}
}
在实例初始化模块中,我们可看到代码不能作为类初始化模块(即if 语句)的一部分执行。所以实际上,一
个实例初始化模块就是一个匿名内部类的构建器。当然,它的功能是有限的;我们不能对实例初始化模块进
行过载处理,所以只能拥有这些构建器的其中一个。
5.链接到外部类
创建自己的内部类时,那个类的对象同时拥有指向封装对象(这些对象封装或生成了内部类)的一个链接。所以它们能访问那个封装对象的成员—— 毋需取得任何资格。除此以外,内部类拥有对封装类所有元素的访问权限(注释②)。下面这个例子阐示了这个问题:
package c07.innerscopes;
interface Selector{
boolean end();
Object current();
void next();
}
public class Sequence {
private Object[] o;
private int next = 0;
public Sequence(int size){
o = new Object[size];
}
public void add(Object x){
if(next < o.length){
o[next] = x;
next ++;
}
}
private class SSelector implements Selector{
int i = 0;
public boolean end(){
return i == o.length; //链接到外部类私有成员o
}
public Object current(){
return o[i];
}
public void next(){
if(i < o.length) i++;
}
}
public Selector getSelector(){
return new SSelector();
}
public static void main(String args[]){
Sequence s = new Sequence(10);
for(int i = 0; i < 10; i++)
s.add(Integer.toString(i));
Selector s1 = s.getSelector();
while(!s1.end()){
System.out.println((String)s1.current());
s1.next();
}
}
}
从表面看, SSelector 似乎只是另一个内部类。但不要被表面现象迷惑。请注意观察 end(), current()以及
next(),它们每个方法都引用了 o。 o 是个不属于 SSelector 一部分的句柄,而是位于封装类里的一个
private 字段。然而,内部类可以从封装类访问方法与字段,就象已经拥有了它们一样。这一特征对我们来
说是非常方便的。
6.static内部类
为正确理解 static 在应用于内部类时的含义,必须记住内部类的对象默认持有创建它的那个封装类的一个对
象的句柄。然而,假如我们说一个内部类是 static 的,这种说法却是不成立的。 static 内部类意味着:
(1) 为创建一个 static 内部类的对象,我们不需要一个外部类对象。
(2) 不能从 static 内部类的一个对象中访问一个外部类对象。
但在存在一些限制:由于 static 成员只能位于一个类的外部级别,所以内部类不可拥有 static 数据或
static 内部类。
倘若为了创建内部类的对象而不需要创建外部类的一个对象,那么可将所有东西都设为static。为了能正常
工作,同时也必须将内部类设为 static。如下所示:
package c07.innerscopes;
abstract class Content{
abstract public int value();
}
public class Parcel10 {
private static class PContents extends Content{
private int i = 11;
public int value(){ return i; }
}
protected static class PDestination implements Destination{
private String label;
private PDestination(String whereTo){
label = whereTo;
}
public String readLabel(){ return label; }
}
public static Destination dest(String s){
return new PDestination(s);
}
public static Content cont(){
return new PContents();
}
public static void main(String args[]){
Content c = cont();
Destination d = dest("Tanzania");
}
}
在 main()中,我们不需要 Parcel10 的对象;相反,我们用常规的语法来选择一个 static 成员,以便调用将
句柄返回 Contents 和 Destination 的方法。
通常,我们不在一个接口里设置任何代码,但 static 内部类可以成为接口的一部分。由于类是“静态”的,
所以它不会违反接口的规则—— static 内部类只位于接口的命名空间内部:
//: IInterface.java
// Static inner classes inside interfaces
interface IInterface {
static class Inner {
int i, j, k;
public Inner() {}
void f() {}
}
} ///:
7.引用外部类对象
若想生成外部类对象的句柄,就要用一个点号以及一个 this 来命名外部类。举个例子来说,在
Sequence.SSelector 类中,它的所有方法都能产生外部类 Sequence 的存储句柄,方法是采用 Sequence.this
的形式。结果获得的句柄会自动具备正确的类型(这会在编译期间检查并核实,所以不会出现运行期的开
销)。
有些时候,我们想告诉其他某些对象创建它某个内部类的一个对象。为达到这个目的,必须在 new 表达式中
提供指向其他外部类对象的一个句柄,就象下面这样:
public class Parcell {
class Contents{
private int i = 11;
public int value(){return i;}
}
class Destination{
private String label;
Destination(String whereTo){
label = whereTo;
}
String readLabel(){return label;}
}
public void ship(String dest){
Contents c= new Contents();
Destination d = new Destination(dest);
}
public static void mian(String args[]){
Parcell p = new Parcell();
p.ship("Tanzania");
}
}
8.从内部类继承
class WithInner{
class Inner{}
}
public class InheritInner extends WithInner.Inner{
//InheritInner(){}; 非法语句!!
InheritInner(WithInner wi){
wi.super();
}
public static void main(String args[]){
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
从中可以看到, InheritInner 只对内部类进行了扩展,没有扩展外部类。但在需要创建一个构建器的时候,
默认对象已经没有意义,我们不能只是传递封装对象的一个句柄。此外,必须在构建器中采用下述语法:
enclosingClassHandle.super();
它提供了必要的句柄,以便程序正确编译。
9.覆盖内部类
package c07;
class Egg2{
protected class Yolk{
public Yolk(){
System.out.println("Egg2.Yolk()");
}
public void f(){
System.out.println("Egg2.Yolk.f()");
}
}
private Yolk y = new Yolk();
public Egg2(){
System.out.println("nNew Egg2");
}
public void insertYolk(Yolk yy){y = yy;}
public void g(){y.f();}
}
public class BigEgg2 extends Egg2{
public class Yolk extends Egg2.Yolk{
public Yolk(){
System.out.println("BigEgg2.Yolk()");
}
public void f(){
System.out.println("BigEgg2.Yolk.f()"); }
}
public BigEgg2(){insertYolk(new Yolk());}
public static void main(String args[]){
Egg2 e2 = new BigEgg2();
e2.g();
}
}
输出结果如下:
Egg2.Yolk()
nNew Egg2
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk.f()
现在, BigEgg2.Yolk 明确地扩展了 Egg2.Yolk,而且覆盖了它的方法。方法 insertYolk()允许 BigEgg2 将它自己的某个 Yolk 对象上溯造型至 Egg2 的 y 句柄。所以当 g()调用 y.f()的时候,就会使用 f()被覆盖版本。对 Egg2.Yolk()的第二个调用是 BigEgg2.Yolk 构建器的基础类构建器调用。调用
g()的时候,可发现使用的是 f()的被覆盖版本。