十、内部类
10.1 创建内部类
可以将一个类的定义放在另一个类的定义内部,这就是内部类。
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 readLable(){return label;};
}
public void ship(String dest){
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLable());
}
public static void main(String[] args) {
Parcell p = new Parcell();
p.ship("Tasmania");
}
}
更典型的内部类案例,外部类将有一个方法,该方法返回一个指向内部类的引用。
public class Pracel2 {
class Contents{
private int i = 11;
public int value(){return i;}
}
class Destination{
private String label;
Destination(String whereTo){
label = whereTo;
}
String readLable(){return label;}
}
public Destination to(String s){
return new Destination(s);
}
public Contents contends(){
return new Contents();
}
public void ship(String dest){
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLable());
}
public static void main(String[] args) {
Pracel2 p = new Pracel2();
p.ship("Tasmania");
Pracel2 q = new Pracel2();
//具体指明对象的类型OuterClassName.InnerClassName
Pracel2.Destination d = q.to("Broneo");
Pracel2.Contents c = q.contends();
}
}
练习1:编写一个名为Outer的类,它包含一个名为Inner的类。在Outer中添加一个方法,它返回一个Inner类型的对象。在main()中,创建并初始化一个指向某个Inner对象的引用。
public class Outer {
Outer(){
System.out.println("I am Outer");
}
Inner getInner(){
return new Inner();
}
class Inner{
Inner(){
System.out.println("I am Inner");
}
}
public static void main(String[] args) {
Outer o = new Outer();
Outer.Inner i = o.getInner();
}
}
//output
I am Outer
I am Inner
10.2 链接到外部类
当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了联系,所以他能访问其外围对象的所有成员,而不需要任何特殊条件。此外内部类还拥有其外围类的所有元素的访问权。
public interface Selector {
boolean end();
Object current();
void next();
}
public class Sequence {
private Object[] items;
private int next = 0;
public Sequence(int size){
items = new Object[size];
}
public void add(Object x){
if(next<items.length){
items[next++] = x;
}
}
public class SequenceSelector implements Selector{
private int i = 0;
//检查序列是否到了末尾
@Override
public boolean end() {
return i == items.length;
}
//访问当前对象
@Override
public Object current() {
return items[i];
}
//序列中的下一个对象
@Override
public void next() {
if (i<items.length){
i++;
}
}
}
public Selector selector(){
return new SequenceSelector();
}
public static void main(String[] args) {
Sequence sequence = new Sequence(10);
for (int i = 0;i<10;i++){
//sequence.add(Integer.toString(i));
sequence.add(new Integer(i));
Selector selector = sequence.selector();
while (!selector.end()){
System.out.println(selector.current()+" ");
selector.next();
}
}
}
}
练习2:创建一个类,它持有String,并且有一个显示这个String的toString()方法,将你的新类的若干个对象添加到一个Sequence对象中,然后显示他们。
import com.agree.shiZ.didaiqi.Selector;
import com.agree.shiZ.didaiqi.Sequence;
public class Test2 {
int id;
String s ;
Test2(int id,String s){
this.id = id;
this.s = s;
}
@Override
public String toString() {
return "Test2 s = "+s;
}
public static void main(String[] args) {
Sequence sequence = new Sequence(6);
for (int i = 0;i<6;i++){
sequence.add(new Test2(i,"lx "+i));
}
Selector selector = sequence.selector();
while (!selector.end()) {
System.out.println(selector.current() + " ");
selector.next();
}
}
}
//output
Test2 s = lx 0
Test2 s = lx 1
Test2 s = lx 2
Test2 s = lx 3
Test2 s = lx 4
Test2 s = lx 5
练习3:修改练习1,使得Outer类包含一个private String域(由构造器初始化),而Inner包含一个显示这个域的toString()方法。创建一个Inner类型的对象并显示它。
public class Outer {
private static String s= "静态啊";
Outer(){
System.out.println("I am Outer");
}
Inner getInner(){
return new Inner();
}
class Inner{
Inner(){
System.out.println("I am Inner");
}
@Override
public String toString() {
return s;
}
}
public static void main(String[] args) {
Outer o = new Outer();
Outer.Inner i = o.getInner();
System.out.println(i);
}
}
//output
I am Outer
I am Inner
静态啊
10.3 使用.this与.new
如果你需要生成对外部类对象的引用,可使用外部类的名字后面紧跟圆点和this。这样产生的引用自动地具有正确的类型。
public class Outer{
class Inner{
Outer getOut(){
return Outer.this;
}
}
}
如果你想告知其他对象,去创建其某个内部类的对象,要实现此目的必须在new表达式中提供对其他外部类的引用,这就需要使用.new语法。(但是你这个内部类是个嵌套类的话(静态内部类)正常创建即可)
public class Outer{
class Inner{
}
public static void main(String[] args){
Outer o = new Outer();
Outer.Inner i = o.new Inner();
}
}
练习4:在Sequence.SequenceSelector类中增加一个方法,它可以生成对外部类Sequence的引用。
public class SequenceSelector implements Selector{
Sequence getSequence(){
return Sequence.this;
}
...
}
练习5:创建一个包含内部类的类,在另一个独立的类中,创建此内部类的实例。
Outer.Inner a = o.new Inner();
10.4 内部类与向上转型
内部类向上转型为实现的接口。
练习6:在第一个包中创建一个至少有一个方法的接口。然后在第二个包内创建一个类,在其中增加一protected的内部类以实现那个接口。在第三个包中,继承这个类,并在一个方法中返回该protected内部类的对象,在返回的时候向上转型为第一个包中的接口的类型。
public class Test61 {
protected class Test61Inner implements Test {
@Override
public void f() {
}
}
}
public interface Test {
void f();
}
public class Test6 extends Test61 {
public Test getInner(){
return new Test61Inner();//报错,需修改为public
}
}
练习7:创建一个含有private域和private方法的类。创建一个内部类,它有一个方法可用来修改外围类的域,并调用外围类的方法。在外围类的另一方法中,创建此内部类的对象,并且调用它的方法,然后说明对外围类对象的影响。
public class Test7 {
private int i =0;
private void f(){
System.out.println("外部lei"+i);
}
void getInner(){
TestInner t = new TestInner();
t.update();
}
class TestInner{
void update(){
i = 6;
f();
}
}
public static void main(String[] args) {
Test7 a = new Test7();
a.getInner();
}
}
//output更改了外部类的域
外部lei6
练习8:确定外部类是否可以访问其内部类的private元素
class Parcel4{
private class PContents implements Contents{
private int i =11;
public int value(){return i;}
}
public Contents contents(){
return new PContents();
}
public void show(){
PContents s =new PContents();
System.out.println(s.i);
}
}
public class TestParcel {
public static void main(String[] args){
Parcel4 p = new Parcel4();
Contents c = p.contents();
p.show();
}
}
//output 11
10.5 在方法和作用域内部类
在方法的作用域内创建一个完整的类,称作局部内部类。
1.你实现了某类接口,于是可以创建返回对其的引用
2.你要解决一个复杂的问题,想创建一个类去辅助,但是又不希望这个类时共用的 。
定义在方法和作用域的内部类,外部类访问不到。
练习9:创建一个至少有一个方法的接口。在某个方法定义一个内部类以实现此接口,这个方法返回对此接口的引用。
public interface Test9 {
void f();
}
public class Test9L implements Test9 {
@Override
public void f() {
}
Test9 test(){
class Inner implements Test9{
@Override
public void f() {
}
}
return new Inner();
}
}
练习10:重复前一个练习,但将内部类定义在某个方法的一个作用域内。
public class Test9L implements Test9 {
int i = new Random().nextInt(10);
@Override
public void f() {
}
Test9 test(){
if(i<8){
class Inner implements Test9{
@Override
public void f() {
}
}
Inner a = new Inner();
}
return null;
}
}
练习11:创建一个private内部类,让它实现一个public接口。写一个方法,它返回一个指向此private内部类的实例的引用,并将此引用向上转型为该接口类型。通过尝试向下转型,说明此内部类被完全隐藏了。
public class Test11 {
Test9 get11Inner(){
Test9 t = new Test11Inner();
return t;
}
private class Test11Inner implements Test9{
@Override
public void f() {
System.out.println("向下");
}
}
public static void main(String[] args) {
Test11 a = new Test11();
Test9 b = a.get11Inner();
//(Test11Inner)b.f();//报错 无法通过向下转型
}
}
10.6 匿名内部类
实现接口返回的是自己引用。
在匿名内部类中使用了默认的构造方法来生成Contents,如果你想用有参数的构造器,此时匿名内部类继承的是一个具体实现的基类,包含着带参数的构造方法。
内部类希望使用一个在其外部定义的对象(String或者其他对象),那么编译器会要求参数引用时final;(final 数据类型 参数名)必须是传递给内部类供其使用的。
内部类中包含实例初始化{ },其中传递的参数必须是final,参数在实例初始化中用到。
可通过实例初始化完成匿名内部类的初始化。
匿名内部类可扩展可实现接口,但只能实现一个接口。
public interface Contents {
void f();
}
public class Test9daican {
Test9daican(int x){
System.out.println(x);
}
}
public class Parcel7 {
public Contents contents() {
//通过new表达式返回的引用
return new Contents() {
@Override
public void f() {
System.out.println("匿名内部类");
}
};//匿名内部类等同于以下这个内部类
}
//带参数的构造方法基类;等同于My带参
public Test9daican daican(int x){
return new Test9daican(x){
};
}
class MyContents implements Contents{
@Override
public void f() {
}
public Contents contents(){
return new MyContents();
}
}
class Mydaican extends Test9daican{
Mydaican(int x) {
super(x);
}
public Test9daican daican(int x){
return new Mydaican(x);
}
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
Test9daican t = p.daican(2);
}
}
10.6.1 再访工厂方法
这种内部类的工厂模式就没有必要去创建作为工厂的具体类名。你需要单一的工厂对象的时候本例添加了static关键字。
public interface Service {
void method1();
void method2();
}
public interface ServiceFactory {
Service getService();
}
public class Implementation implements Service {
private Implementation(){}
@Override
public void method1() {
System.out.println("Implementation method1()");
}
@Override
public void method2() {
System.out.println("Implementation method2()");
}
public static ServiceFactory factory = new ServiceFactory() {
@Override
public Service getService() {
return new Implementation();
}
};
}
public class Implementation2 implements Service{
private Implementation2(){}
@Override
public void method1() {
System.out.println("Implementation2 method1");
}
@Override
public void method2() {
System.out.println("Implementation2 method2");
}
public static ServiceFactory factory = new ServiceFactory() {
@Override
public Service getService() {
return new Implementation2();
}
};
}
public class Factories {
public static void serviceConsumer(ServiceFactory factory){
Service s = factory.getService();
s.method1();
s.method2();
}
public static void main(String[] args) {
serviceConsumer(Implementation.factory);
serviceConsumer(Implementation2.factory);
}
}
10.7 嵌套类
如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static。这通常被称为嵌套类。
嵌套类意味着:1.要创建嵌套类对象的时候不需要外部类对象
2.不能从嵌套类对象中访问非静态的外部类对象
10.7.1 接口内部的类
嵌套类可以作为接口的一部分,每个类中都可以写main()方法
public interface ClassInInterface {
void howdy();
class Test implements ClassInInterface{
@Override
public void howdy() {
System.out.println("aaaaa");
}
public static void main(String[] args) {
Test t = new Test();
t.howdy();
}
}
}
10.7.2 从多层嵌套类中访问外部类成员
一个内部类被嵌套多少层并不重要,它能透明的访问所有它所嵌入的外围类的所有成员。
public class A {
private void aTest(){
}
class B{
private void bTest(){
}
class C{
void cTest(){
aTest();
bTest();
}
}
}
public static void main(String[] args) {
A a = new A();
A.B b = a.new B();
A.B.C c = b.new C();
c.cTest();
}
}
10.8 为什么需要内部类
每个内部类都能独立的继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有任何影响。
内部类可以更好的实现多重继承。
1.一个内部类的对象能够访问创建它的对象的实现,包括私有数据。
2.对于同一个包中的其他类来说,内部类能够隐藏起来。
3.匿名内部类可以很方便的定义回调。
4.使用内部类可以非常方便的编写事件驱动程序
一个类中以某种方式实现两个接口,可选择单一类也可使用内部类。
interface A{}
interface B{}
class X implements A,B{}
class Y implements A{
B makeB(){
return new B(){
};
}
}
public class MultiInterfaces{
static void takesA(A a){}
static void takesB(B b){}
public static void main(String[] args){
X x = new X();
Y y = new Y();
takesA(x);
takesA(y);
takesB(x);
takesB(y.makeB());
}
}
如果拥有的是抽象的类或具体的类,而不是接口,。
class D{}
abstract class E{}
class Z extends D{
E makeE(){
return new E(){};
}
}
public class MultiInterfaces{
static void takesD(D d){}
static void takesE(E e){}
public static void main(String[] args){
Z z= new Y();
takesD(z);
takesE(z.makeE());
}
10.8.1 闭包与回调
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。
由此可以看出内部类是面向对象的闭包,它不仅包含了外围类的对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有成员,包括private成员。
通过回调,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。
回调可在运行时动态的决定需要调用什么方法。
或者说我自己活太多,或者有其他的事需要做,自己不想做,就把这个活分配给别人去完成,并把完成的结果告诉我就行了。
回调函数就是别人要调用我的时候,我要告诉别人我这个方法的地址在那里,然后别人直接找这个地址就行,不用再去向系统申请内存地址然后去找这个方法在哪里。
10.8.2 内部类与控制框架
应用 程序框架就是被设计用以解决某类特定问题的一个类或一组类。
要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法。在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决方案。
控制框架是一类特殊的应用程序框架,它用来解决响应事件的需求,被称作事件驱动系统。java Swing库就是一个控制框架。
10.9 内部类的继承
内部类的构造器必须连接到指向其外部类对象的引用,继承内部类的时候,指向外部类对象的引用必须被初始化。不能采用默认的构造器必须在导出类中构造器传递一个外部类对象的引用,还必须添加 对象名.super()。
public class WithInner {
class Inner{
}
}
public class InheritInner extends WithInner.Inner {
InheritInner(WithInner wi){
wi.super();
}
public static void main(String[] args) {
WithInner w = new WithInner();
InheritInner I = new InheritInner(w);
}
}
10.10 内部类可以被覆盖吗
单纯的继承外部类重写内部类是不不会被覆盖的,除非重写的内部类继承到到具体的内部类(外部类.内部类)。
class Egg{
class Yolk{
void f(){
print("Egg Yolk");
}
}
}
class BigEgg extends Egg{
class Yolk{
void f(){
print("BigEgg Yolk");
}
}
}
class BigEgg2 extends Egg{
class Yolk extends Egg.Yolk{
void f(){
print("BigEgg2 Yolk");
}
}
public static void main(String[] args){
Egg e = new Egg();
Egg.Yolk a = e.new Yolk();
a.f();
BigEgg d = new BigEgg();
BigEgg.Yolk b = d.new Yolk();
b.f();
BigEgg2 c = new BigEgg2();
BigEgg2.Yolk z = c.new Yolk();
z.f();
}
}
//output
Egg Yolk
Egg Yolk
BigEgg2 Yolk
10.11 局部内部类
在代码块里创建的内部类。
我们需要一个已命名的构造器或者需要重载构造器,而匿名内部类只能用于实例初始化。(需要不止一个内部类对象)
10.11 内部类标识符
内部类也产生一个.class文件,类文件命名外部类名字加上$再加上内部类的名字,内部类是匿名的会产生一个数字作为标识符
LocalInnerClass$1.class
LocalInnerClass$Inner.class