java内部类外部访问
在内部类使用this表示的是对内部类自身的引用,如果想要获取外部类的引用,应当使用Outer.this,所以访问外部类的方法:
Outer.this.xxxx()
② 深入理解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..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//wOC1xLPJ1LGx5MG/b3V0ZXJGaWVsZKOsIM/Cw++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 内部类 能被外部使用吗
1、内部类是指在一个外部类的内部再定义一个类。类名不需要和文件夹相同。
2、内部类可以是静态static的,也可用public,default,protected和private修饰。(而外部顶级类即类名和文件名相同的只能使用public和default)。
3、内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现outer.class和outer$inner.class两类。 所以内部类的成员变量/方法名可以和外部类的相同。
4、内部类具有:成员内部类、局部内部类、嵌套内部类、匿名内部类。
以下以成员内部类示例:
成员内部类,就是作为外部类的成员,可以直接使用外部类的所有成员和方法,即使是private的。同时外部类要访问内部类的所有成员变量/方法,则需要通过内部类的对象来获取。
要注意的是,成员内部类不能含有static的变量和方法。 因为成员内部类需要先创建了外部类,才能创建它自己的 ,了解这一点,就可以明白更多事情,在此省略更多的细节了。
在成员内部类要引用外部类对象时,使用outer.this来表示外部类对象;
而需要创建内部类对象,可以使用outer.inner obj = outerobj.new inner();
示例代码:
public class Outer {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer. new Inner();
inner.print( "Outer.new" );
inner = outer.getInner();
inner.print( "Outer.get" );
}
public Inner getInner() {
return new Inner();
}
public class Inner {
public void print(String str) {
System.out.println(str);
}
}
}
④ 深入理解Java中为什么内部类可以访问外部类的成员
一般来说,外部类调用内部类的方法分为以下几种情况:
1.使用static可以声明一个内部类, 可以直接在外部调用
// 定义外部类
class Outer
{
// 定义外部类的私有属性
private static String info = "hello world";
// 使用static定义内部类
static class Inner
{
// 定义内部类的方法
public void print()
{
// 直接访问外部类的私有属性
System.out.println(info);
}
}
// 定义外部类的方法
public void fun()
{
// 通过内部类的实例化对象调用方法
new Inner().print();
}
}
public class InnerClassDemo03
{
public static void main(String args[])
{
// 通过Outer.Inner创建内部类的实例,并调用它的print方法
new Outer.Inner().print() ;
}
}
2.不使用statc声明一个内部类 ,使外部调用
//定义外部类
class Outer
{
//定义外部类的私有属性
private String info = "hello world";
//定义内部类
class Inner
{
//定义内部类的方法
public void print()
{
//直接访问外部类的私有属性
System.out.println(info);
}
};
//定义外部类的方法
public void fun()
{
//通过内部类的实例化对象调用方法
new Inner().print();
}
};
public class InnerClassDemo04
{
public static void main(String args[])
{
//外部类实例化对象
Outer out = new Outer();
//实例化内部类对象
Outer.Inner in = out.new Inner();
//调用内部类的方法
in.print();
}
}
3.在方法中定义内部类 ,使外部调用
//定义外部类
class Outer
{
//定义外部类的私有属性
private String info = "hello world";
//定义外部类的方法
public void fun(final int temp)
{
//在方法中定义的内部类
class Inner
{
//定义内部类的方法
public void print()
{
//直接访问外部类的私有属性
System.out.println("类中的属性:" + info);
System.out.println("方法中的参数:" + temp);
}
}
//通过内部类的实例化对象调用方法
new Inner().print();
}
}
public class InnerClassDemo05
{
public static void main(String args[]){
//调用外部类的方法
new Outer().fun(30);
}
}
⑤ Java内部类怎么直接调用外部类啊
publicclassOuter{
intx;
Strings="hello";
publicvoidtest(){
System.out.print("test");
}
publicclassInner{
ints=20;
publicvoidtestInner(){
//可以直接使用外部类的成员变量和成员方法
x=0;
test();
//如果外部类的成员变量和内部类变量重名,可以这样调用外部类的变量
Outer.this.s="test";
//当然你可以new外部类对象这也是没问题的
Outero=newOuter();
o.x=30;
o.test();
}
}
}
⑥ java里面怎样通过多个外部方法访问一个内部类
外部类访问内部类要创建内部类的对象才能访问内部类的成员;
内部类可以直接访问外部类的成员
package com.test;
public class Outer {
public class Inner{
int a;
int b;
}
public void fun(){
int a = new Inner().a;
}
public void fun2(){
int b = new Inner().b;
}
}
⑦ Java在外界如何调用局部内部类
内部类和成员变量一样,通过外部类的实例调用内部类。
可以先创建外部类实例。
用外部类实例调用内部类构造方法创建内部类实例。
再用这个内部类实例调用内部类成员方法。
Aa=newA();
A.Bb=a.newB();
b.do1();//do是关键字,所以这里在do后面加了个1,否则无法编译。
⑧ JAVA 中外部类可以访问非静态内部类的私有属性
1、创建一个Bean1类,并建好两个私有变量和构造方法。
⑨ java内部类可以访问外部类的静态方法吗
内部类
就相当于一个外部类的
成员变量
,所以可以
直接访问
外部变量
,外部类不能直接访问内部类变量,必须通过创建内部类实例的方法访问,
new
InnerClass
(32).m就是创建内部类实例访问内部类成员变量。
你
想不通
的肯定是指内部类的私有变量
怎么可以
被外部类访问吧,按常规,私有变量m只能在InnerClass里被访问,
但你要注意,内部类就相当于一个外部类的成员变量,举个例子。
class
Outer{
private
int
m;
private
class
Inner{
private
int
n;
private
int
k;
}
}
m和类Inner都是成员变量,他们之间是平等的,唯一不同的就是Inner它是包装了几个成员变量比如n,k,也就是说m
n
k是平等的,区别在于访问n
k要通过Inner,就是要建立Inner实例访问nk,这样解释够
明白了吧