总结:
在使用匿名内部类(new <类或接口> <类的主体>)时,要记住以下几个原则:
·匿名内部类不能有构造方法。
·匿名内部类不能定义任何静态成员、方法和类。
·匿名内部类不能是public,protected,private,static。
·只能创建匿名内部类的一个实例。
·一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
·因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
匿名内部类是没有名字的内部类,不能继承其它类,但一个内部类可以作为一个接口,由另一个内部类实现.
一、由于匿名内部类没有名字,所以它没有构造函数。因为没有构造函数,所以它必须完全借用父类的构造函数来实例化,换言之:匿名内部类完全把创建对象的任务交给了父类去完成。
二、在匿名内部类里创建新的方法没有太大意义,但它可以通过覆盖父类的方法达到神奇效果,如上例所示。这是多态性的体现。
三、因为匿名内部类没有名字,所以无法进行向下的强制类型转换,持有对一个匿名内部类对象引用的变量类型一定是它的直接或间接父类类型。
体会:
感觉匿名内部类要更常用些。所以总结了匿名内部类的一些注意。这个假期感觉学了好多。这是第八天感觉已经比在学校一个学期还学的多了。
1.内部类的形式是怎样的?
//外部类
class Out {
private int age = 12;
//内部类
class In {
public void print() {
System.out.println(age);
}
}
}
public class Demo {
public static void main(String[] args) {
Out.In in = new Out().new In();
in.print();
//或者采用下种方式访问
/*
Out out = new Out();
Out.In in = out.new In();
in.print();
*/
}
}
从上面的例子不难看出,内部类其实严重破坏了良好的代码结构,但为什么还要使用内部类呢?
因为内部类可以随意使用外部类的成员变量(包括私有)而不用生成外部类的对象,这也是内部类的唯一优点
如同心脏可以直接访问身体的血液,而不是通过医生来抽血
程序编译过后会产生两个.class文件,分别是Out.class和Out$In.class
其中$代表了上面程序中Out.In中的那个 .
Out.In in = new Out().new In()可以用来生成内部类的对象,这种方法存在两个小知识点需要注意
1.开头的Out是为了标明需要生成的内部类对象在哪个外部类当中
2.必须先有外部类的对象才能生成内部类的对象,因为内部类的作用就是为了访问外部类中的成员变量
在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。下面就先来了解一下这四种内部类的用法。
1.成员内部类
成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Circle { double radius = 0;
public Circle(double radius) { this.radius = radius; }
class Draw { //内部类 public void drawSahpe() { System.out.println("drawshape"); } } } |
这样看起来,类Draw像是类Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Circle { private double radius = 0; public static int count =1; public Circle(double radius) { this.radius = radius; }
class Draw { //内部类 public void drawSahpe() { System.out.println(radius); //外部类的private成员 System.out.println(count); //外部类的静态成员 } } } |
不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
1 2 | 外部类.this.成员变量 外部类.this.成员方法 |
虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Circle { private double radius = 0;
public Circle(double radius) { this.radius = radius; getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问 }
private Draw getDrawInstance() { return new Draw(); }
class Draw { //内部类 public void drawSahpe() { System.out.println(radius); //外部类的private成员 } } } |
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class Test { public static void main(String[] args) { //第一种方式: Outter outter = new Outter(); Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建
//第二种方式: Outter.Inner inner1 = outter.getInnerInstance(); } }
class Outter { private Inner inner = null; public Outter() {
}
public Inner getInnerInstance() { if(inner == null) inner = new Inner(); return inner; }
class Inner { public Inner() {
} } } |
内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。比如上面的例子,如果成员内部类Inner用private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。我个人是这么理解的,由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。
2.局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class People{ public People() {
} }
class Man{ public Man(){
}
public People getWoman(){ class Woman extends People{ //局部内部类 int age =0; } return new Woman(); } } |
注意,局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
3.匿名内部类
匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。下面这段代码是一段Android事件监听代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | scan_bt.setOnClickListener(new OnClickListener() {
@Override public void onClick(View v) { // TODO Auto-generated method stub
} });
history_bt.setOnClickListener(new OnClickListener() {
@Override public void onClick(View v) { // TODO Auto-generated method stub
} }); |
这段代码为两个按钮设置监听器,这里面就使用了匿名内部类。这段代码中的:
1 2 3 4 5 6 7 8 | new OnClickListener() {
@Override public void onClick(View v) { // TODO Auto-generated method stub
} } |
就是匿名内部类的使用。代码中需要给按钮设置监听器对象,使用匿名内部类能够在实现父类或者接口中的方法情况下同时产生一个相应的对象,但是前提是这个父类或者接口必须先存在才能这样使用。当然像下面这种写法也是可以的,跟上面使用匿名内部类达到效果相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | private void setListener() { scan_bt.setOnClickListener(new Listener1()); history_bt.setOnClickListener(new Listener2()); }
class Listener1 implements View.OnClickListener{ @Override public void onClick(View v) { // TODO Auto-generated method stub
} }
class Listener2 implements View.OnClickListener{ @Override public void onClick(View v) { // TODO Auto-generated method stub
} } |
这种写法虽然能达到一样的效果,但是既冗长又难以维护,所以一般使用匿名内部类的方法来编写事件监听代码。同样的,匿名内部类也是不能有访问修饰符和static修饰符的。
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
4.静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class Test { public static void main(String[] args) { Outter.Inner inner = new Outter.Inner(); } }
class Outter { public Outter() {
}
static class Inner { public Inner() {
} } } |
2.为什么要有“内部类”?
内部类产生对象必须有一个重要的前提条件,那就是它的外部类对象必须先创建,通过外部类的对象构建内部类对象,有两种方法:外部类有一个public方法生产内部类的对象;其二是通过外部类对象实现。这要求内部类Inner是public或者protected访问权限。内部类的另一个重要特性是:它可以不受限制的访问其所在外部类的域。java可以实现多个接口,implements inerfaceA, interfaceB。但是不允许多重继承类,如果你确实需要利用例外的类来操控本类的成员,可以利用内部类满足这个需要。
1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,
2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
3.方便编写事件驱动程序
4.方便编写线程代码
3.利用内部类可以方便实现哪些功能?
无论是方法内部类还是匿名内部类,它们的共同点同2中提到的一样,可以不受限制的访问外部类的域和方法。同时,可根据限制条件让内部类隐藏或向上转型为接口类型,从而消除方法返回的具体类型的限制。匿名内部类可以隐藏接口的具体实现,使得类的使用者只关注要实现的目标。
4.内部类的实现机制?
内部类可以无条件访问外部类的成员,那具体究竟是如何实现的呢?下面通过反编译字节码文件看看究竟。事实上,编译器在进行编译的时候,会将成员内部类单独编译成一个字节码文件,下面是Outter.java的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class Outter { private Inner inner = null; public Outter() {
}
public Inner getInnerInstance() { if(inner == null) inner = new Inner(); return inner; }
protected class Inner { public Inner() {
} } } |
编译之后,出现了两个字节码文件:
反编译Outter$Inner.class文件得到下面信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | E:\Workspace\Test\bin\com\cxh\test2>javap -v Outter$Inner Compiled from "Outter.java" public class com.cxh.test2.Outter$Inner extends java.lang.Object SourceFile: "Outter.java" InnerClass: #24= #1 of #22; //Inner=class com/cxh/test2/Outter$Inner of class com/cxh/tes t2/Outter minor version: 0 major version: 50 Constant pool: const #1 = class #2; // com/cxh/test2/Outter$Inner const #2 = Asciz com/cxh/test2/Outter$Inner; const #3 = class #4; // java/lang/Object const #4 = Asciz java/lang/Object; const #5 = Asciz this$0; const #6 = Asciz Lcom/cxh/test2/Outter;; const #7 = Asciz <init>; const #8 = Asciz (Lcom/cxh/test2/Outter;)V; const #9 = Asciz Code; const #10 = Field #1.#11; // com/cxh/test2/Outter$Inner.this$0:Lcom/cxh/t est2/Outter; const #11 = NameAndType #5:#6;// this$0:Lcom/cxh/test2/Outter; const #12 = Method #3.#13; // java/lang/Object."<init>":()V const #13 = NameAndType #7:#14;// "<init>":()V const #14 = Asciz ()V; const #15 = Asciz LineNumberTable; const #16 = Asciz LocalVariableTable; const #17 = Asciz this; const #18 = Asciz Lcom/cxh/test2/Outter$Inner;; const #19 = Asciz SourceFile; const #20 = Asciz Outter.java; const #21 = Asciz InnerClasses; const #22 = class #23; // com/cxh/test2/Outter const #23 = Asciz com/cxh/test2/Outter; const #24 = Asciz Inner;
{ final com.cxh.test2.Outter this$0;
public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter); Code: Stack=2, Locals=2, Args_size=2 0: aload_0 1: aload_1 2: putfield #10; //Field this$0:Lcom/cxh/test2/Outter; 5: aload_0 6: invokespecial #12; //Method java/lang/Object."<init>":()V 9: return LineNumberTable: line 16: 0 line 18: 9
LocalVariableTable: Start Length Slot Name Signature 0 10 0 this Lcom/cxh/test2/Outter$Inner;
} |
第11行到35行是常量池的内容,下面逐一第38行的内容:
final com.cxh.test2.Outter this$0;
这行是一个指向外部类对象的指针,看到这里想必大家豁然开朗了。也就是说编译器会默认为成员内部类添加了一个指向外部类对象的引用,那么这个引用是如何赋初值的呢?下面接着看内部类的构造器:
publiccom.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
从这里可以看出,虽然我们在定义的内部类的构造器是无参构造器,编译器还是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的Outter this&0 指针便指向了外部类对象,因此可以在成员内部类中随意访问外部类的成员。从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对Outter this&0引用进行初始化赋值,也就无法创建成员内部类的对象了。
5.内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?
如果不是静态内部类,完全可以。那没有什么限制!
在静态内部类下,不可以访问外部类的普通成员变量,而只能访问外部类中的静态成员,
编程题:
1.定义一个乐器(Instrument)接口,其中有抽象方法
void play();
在InstrumentTest类中,定义一个方法
void playInstrument(Instrument ins);
并在该类的main方法中调用该方法。
要求:分别使用下列内部类完成此题。
成员内部类
局部内部类
匿名类
package day0801;
public interface Instrument {
public void play();
}
package day0801;
public class InstrumentTest1 {
/**
* 成员内部类
* @author jiale
* 乐器1类Instrument1实现接口Instrument
*/
class Instrument1 implements Instrument{
@Override
public void play() {
// TODO自动生成的方法存根
System.out.println("开始演奏!");
}
}
void playInstrument(Instrument ins){
ins.play();
}
public static void main(String[] args) {
// TODO自动生成的方法存根
InstrumentTest1 i = new InstrumentTest1();
i.playInstrument(i.new Instrument1());
}
}
package day0801;
public class InstrumentTest2 {
/**
* 局部内部类
* @param args
*/
void playInstrument(){
class Instrument2 implements Instrument{
@Override
public void play() {
// TODO自动生成的方法存根
System.out.println("开始演奏!");
}
}
new Instrument2().play();
}
public static void main(String[] args) {
// TODO自动生成的方法存根
InstrumentTest2 i=new InstrumentTest2();
i.playInstrument();
}
}
package day0801;
public class InstrumentTest3 {
void playInstrument(Instrument ins){
ins.play();
}
public static void main(String[] args) {
// TODO自动生成的方法存根
InstrumentTest3 i = new InstrumentTest3();
/**
* 匿名内部类
* 隐含实现接口
*/
i.playInstrument(new Instrument(){
@Override
public void play() {
// TODO自动生成的方法存根
System.out.println("开始演奏!");
}
});
}
}
2.定义一个Father和Child类,并进行测试。
要求如下:
1)Father类为外部类,类中定义一个私有的String类型的属性name,name的值为“zhangjun”。
2)Child类为Father类的内部类,其中定义一个introFather()方法,方法中调用Father类的name属性。
3)定义一个测试类Test,在Test类的main()方法中,创建Child对象,并调用introFather ()方法。
package day0802;
public class Father {
private String name = "zhangjun";
/**
* 内部类
* @author jiale
* 调用外部类的name属性
*/
class Child{
public void introFather(){
System.out.println(name);
}
}
}
package day0802;
import day0802.Father.Child;
public class demo02 {
public static void main(String[] args) {
// TODO自动生成的方法存根
Father dady = new Father();
Father.Child kid = dady.new Child(); //也可以Child kid = dady.new Child();
// Father.Child kid = new Father().new Child();
kid.introFather(); }
}
运行结果:
zhangjun