java内部类,java编辑器手机版
大家好,关于java内部类很多朋友都还不太明白,不过没关系,因为今天小编就来为大家分享关于java编辑器手机版的知识点,相信应该可以解决大家的一些困惑和问题,如果碰巧可以解决您的问题,还望关注下本站哦,希望对各位有所帮助!
Java内部类有几种
Java中的几种内部类:
成员内部类:作为外部类的一个成员存在,与外部类的属性、方法并列。当某个类除了他的外部类,不会被其他类使用时应该选择使用成员内部类。
局部内部类:局部内部类定义在外部类的某个代码块或方法块中。如果只会在某个方法或块中创建这个类的对象,就可以使用局部内部类。
匿名内部类:匿名内部类一般定义在需要传递接口或回调的的地方,一个匿名内部类一定是在new的后面,用其隐含实现一个接口或继承一个类。假如只需要创建这个类的一个对象不需要知道其实际类型(不需要使用到类名),那么就可以使用匿名内部类。
静态内部类:和成员内部类一样,作为外部类的一个成员存在,与外部类的属性、方法并列,只不过在声明类的时候加入了static关键字。有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。这时可以使用静态内部类,以便取消产生对外部类的引用。
java内部类,匿名内部类这些是什么求详细用法,还有语法。
一、内部类:
内部类是定义在另一个类中的类,使用它的原因主要有3个:
内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据;
内部类可以对同一个包中的其他类隐藏以来;
当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
【举例:《Java核心技术(卷I)》,6.4节——内部类,程序清单6-6】
importjava.awt.*;
importjava.awt.event.*;
importjava.util.*;
importjavax.swing.*;
importjavax.swing.Timer;
/**
*Thisprogramdemonstratesanonymousinnerclasses.
*@version1.102004-02-07
*@authorCayHorstmann
*/
publicclassInnerClassTest
{
publicstaticvoidmain(String[]args)
{
TalkingClockclock=newTalkingClock(1000,true);
clock.start();
//keepprogramrunninguntiluserselects"Ok"
JOptionPane.showMessageDialog(null,"Quitprogram?");
System.exit(0);
}
}
/**
*Aclockthatprintsthetimeinregularintervals.
*/
classTalkingClock
{
privateintinterval;
privatebooleanbeep;
/**
*Startstheclock.
*@paramintervaltheintervalbetweenmessages(inmilliseconds)
*@parambeeptrueiftheclockshouldbeep
*/
publicTalkingClock(intinterval,booleanbeep)
{
this.interval=interval;
this.beep=beep;
}
/**
*Startstheclock.
*/
publicvoidstart()
{
ActionListenerlistener=newTimePrinter();
Timert=newTimer(interval,listener);
t.start();
}
publicclassTimePrinterimplementsActionListener
{
publicvoidactionPerformed(ActionEventevent)
{
Datenow=newDate();
System.out.println("Atthetone,thetimeis"+now);
if(beep)Toolkit.getDefaultToolkit().beep();
}
}
}上述代码中,TimePrinter就是在TalkingClock类的内部定义的一个内部类,因此它可以访问外围类的数据域,包括interval和beep这种私有域。
二、局部内部类:
讨论匿名内部类之前,先看看局部内部类,它是在一个方法中定义的类。代码示例如下,这里将上例中的TimePrinter类放到了start()方法中进行定义:
publicvoidstart(intinterval,finalbooleanbeep)
{
classTimePrinterimplementsActionListener
{
publicvoidactionPerformed(ActionEventevent)
{
Datenow=newDate();
System.out.println("Atthetone,thetimeis"+now);
if(beep)Toolkit.getDefaultToolkit().beep();
}
}
ActionListenerlistener=newTimePrinter();
Timert=newTimer(interval,listener);
t.start();
}局部内部类不能用public或private访问说明符来声明,它的作用域被限定在声明这个局部内部类的块中。局部内部类可以对外部世界完全地隐藏起来。
局部内部类不仅能访问其外围类,还可以访问局部变量,不过这些局部变量必须被声明为final,如上述代码中start()方法的参数beep所示。
三、匿名内部类
将上面的局部内部类的代码修改一下,就可以定义一个匿名内部类,这种类没有类名。
publicvoidstart(intinterval,finalbooleanbeep)
{
ActionListenerlistener=newActionListener()
{
publicvoidactionPerformed(ActionEventevent)
{
Datenow=newDate();
System.out.println("Atthetone,thetimeis"+now);
if(beep)Toolkit.getDefaultToolkit().beep();
}
};
Timert=newTimer(interval,listener);
t.start();
}请参照局部内部类的代码,比较不同之处。TimePrinter这个类名被省略了,定义listener这个局部内部类时,在其后的new ActionListener()后面跟了一个大括号括起的语句块,也就是此匿名内部类的定义语句。匿名内部类除了具有内部类的优点外,还可以减少代码量。
【内容有些多,但愿能帮到你^_^】
深入理解Java中为什么内部类可以访问外部类的成员
内部类简介
虽然Java是一门相对比较简单的编程语言,但是对于初学者,还是有很多东西感觉云里雾里,
理解的不是很清晰。内部类就是一个经常让初学者感到迷惑的特性。即使现在我自认为Java学的不错了,
但是依然不是很清楚。其中一个疑惑就是为什么内部类对象可以访问外部类对象中的成员(包括成员变量和成员方法)?
早就想对内部类这个特性一探究竟了,今天终于抽出时间把它研究了一下。
内部类就是定义在一个类内部的类。定义在类内部的类有两种情况:一种是被static关键字修饰的,叫做静态内部类,
另一种是不被static关键字修饰的,就是普通内部类。在下文中所提到的内部类都是指这种不被static关键字修饰的普通内部类。
静态内部类虽然也定义在外部类的里面,但是它只是在形式上(写法上)和外部类有关系,
其实在逻辑上和外部类并没有直接的关系。而一般的内部类,不仅在形式上和外部类有关系(写在外部类的里面),在逻辑上也和外部类有联系。
这种逻辑上的关系可以总结为以下两点:
1内部类对象的创建依赖于外部类对象;
2内部类对象持有指向外部类对象的引用。
上边的第二条可以解释为什么在内部类中可以访问外部类的成员。就是因为内部类对象持有外部类对象的引用。但是我们不禁要问,为什么会持有这个引用?接着向下看,答案在后面。
通过反编译字节码获得答案
在源代码层面,我们无法看到原因,因为Java为了语法的简介,省略了很多该写的东西,也就是说很多东西本来应该在源代码中写出,但是为了简介起见,不必在源码中写出,编译器在编译时会加上一些代码。现在我们就看看Java的编译器为我们加上了什么?
首先建一个工程TestInnerClass用于测试。在该工程中为了简单起见,没有创建包,所以源代码直接在默认包中。在该工程中,只有下面一个简单的文件。
?
1
2
3
4
5
6
7
8
9
public class Outer{
int outerField= 0;
class Inner{
void InnerMethod(){
int i= outerField;
}
}
}
该文件很简单,就不用过多介绍了。在外部类Outer中定义了内部类Inner,并且在Inner的方法中访问了Outer的成员变量outerField。
虽然这两个类写在同一个文件中,但是编译完成后,还是生成各自的class文件:
这里我们的目的是探究内部类的行为,所以只反编译内部类的class文件Outer$Inner.class。在命令行中,切换到工程的bin目录,输入以下命令反编译这个类文件:
?
1
javap-classpath.-v Outer$Inner
-classpath.说明在当前目录下寻找要反编译的class文件
-v加上这个参数输出的信息比较全面。包括常量池和方法内的局部变量表,行号,访问标志等等。
注意,如果有包名的话,要写class文件的全限定名,如:
?
1
javap-classpath.-v com.baidu.Outer$Inner
反编译的输出结果很多,为了篇幅考虑,在这里我们省略了常量池。下面给出除了常量池之外的输出信息。
?
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
{
final Outer this$0;
flags: ACC_FINAL, ACC_SYNTHETIC
Outer$Inner(Outer);
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield#10// Field this$0:LOuter;
5: aload_0
6: invokespecial#12// Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this LOuter$Inner;
void InnerMethod();
flags:
Code:
stack=1, locals=2, args_size=1
0: aload_0
1: getfield#10// Field this$0:LOuter;
4: getfield#20// Field Outer.outerField:I
7: istore_1
8: return
LineNumberTable:
line 7: 0
line 8: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LOuter$Inner;
8 1 1 i I
}</init>
首先我们会看到,第一行的信息如下:
?
1
final Outer this$0;
这句话的意思是,在内部类Outer$Inner中,存在一个名字为this$0,类型为Outer的成员变量,并且这个变量是final的。
其实这个就是所谓的“在内部类对象中存在的指向外部类对象的引用”。但是我们在定义这个内部类的时候,并没有声明它,
所以这个成员变量是编译器加上的。
虽然编译器在创建内部类时为它加上了一个指向外部类的引用,但是这个引用是怎样赋值的呢?毕竟必须先给他赋值,它才能指向外部类对象。下面我们把注意力转移到构造函数上。下面这段输出是关于构造函数的信息。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Outer$Inner(Outer);
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield#10// Field this$0:LOuter;
5: aload_0
6: invokespecial#12// Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this LOuter$Inner;</init>
我们知道,如果在一个类中,不声明构造方法的话,编译器会默认添加一个无参数的构造方法。但是这句话在这里就行不通了,因为我们明明看到,这个构造函数有一个构造方法,并且类型为Outer。所以说,
编译器会为内部类的构造方法添加一个参数,参数的类型就是外部类的类型。
下面我们看看在构造参数中如何使用这个默认添加的参数。我们来分析一下构造方法的字节码。下面是每行字节码的意义:
aload_0:
将局部变量表中的第一个引用变量加载到操作数栈。这里有几点需要说明。
局部变量表中的变量在方法执行前就已经初始化完成;局部变量表中的变量包括方法的参数;成员方法的局部变量表中的第一个变量永远是this;操作数栈就是
执行当前代码的栈。所以这句话的意思是:将this引用从局部变量表加载到操作数栈。
aload_1:
将局部变量表中的第二个引用变量加载到操作数栈。这里加载的变量就是构造方法中的Outer类型的参数。
putfield#10// Field this$0:LOuter;
使用操作数栈顶端的引用变量为指定的成员变量赋值。这里的意思是将外面传入的Outer类型的参数赋给成员变量this$0。
这一句putfield字节码就揭示了,指向外部类对象的这个引用变量是如何赋值的。
下面几句字节码和本文讨论的话题无关,只做简单的介绍。下面几句字节码的含义是:使用this引用调用父类(Object)的构造方法然后返回。
用我们比较熟悉的形式翻译过来,这个内部类和它的构造函数有点像这样:(注意,这里不符合Java的语法,只是为了说明问题)
?
1
2
3
4
5
6
7
8
class Outer$Inner{
final Outer this$0;
public Outer$Inner(Outer outer){
this.this$0= outer;
super();
}
}
说到这里,可以推想到,在调用内部类的构造器初始化内部类对象的时候,编译器默认也传入外部类的引用。调用形式有点像这样:(注意,这里不符合java的语法,只是为了说明问题)
vcq9ysfP4M2stcShoyDU2sTasr/A4LXESW5uZXJNZXRob2S3vbeo1tCjrCC3w87KwcvN4rK/wOC1xLPJ1LGx5MG/b3V0ZXJGaWVsZKOsIM/Cw+a1xNfWvdrC673Syr7By7fDzsrKx8jnus69+NDQtcSjugo8YnI+Cgo8cHJlIGNsYXNzPQ=="brush:java;">
void InnerMethod();
flags:
Code:
stack=1, locals=2, args_size=1
0: aload_0
1: getfield#10// Field this$0:LOuter;
4: getfield#20// Field
Outer.outerField:I
7: istore_1
8: return
getfield#10// Field this$0:LOuter;
将成员变量this$0加载到操作数栈上来
getfield#20// Field Outer.outerField:I
使用上面加载的this$0引用,将外部类的成员变量outerField加载到操作数栈
istore_1
将操作数栈顶端的int类型的值保存到局部变量表中的第二个变量上(注意,第一个局部变量被this占用,
第二个局部变量是i)。操作数栈顶端的int型变量就是上一步加载的outerField变量。所以,这句字节码的含义就是:
使用outerField为i赋值。
上面三步就是内部类中是如何通过指向外部类对象的引用,来访问外部类成员的。
文章写到这里,相信读者对整个原理就会有一个清晰的认识了。下面做一下总结:
本文通过反编译内部类的字节码,说明了内部类是如何访问外部类对象的成员的,除此之外,我们也对编译器的行为有了一些了解,编译器在编译时会自动加上一些逻辑,这正是我们感觉困惑的原因。
关于内部类如何访问外部类的成员,分析之后其实也很简单,主要是通过以下几步做到的:
1编译器自动为内部类添加一个成员变量,这个成员变量的类型和外部类的类型相同,这个成员变量就是指向外部类对象的引用;
2编译器自动为内部类的构造方法添加一个参数,参数的类型是外部类的类型,在构造方法内部使用这个参数为1中添加的成员变量赋值;
3在调用内部类的构造函数初始化内部类对象时,会默认传入外部类的引用。
好了,文章到这里就结束啦,如果本次分享的java内部类和java编辑器手机版问题对您有所帮助,还望关注下本站哦!