内部类与静态内部类
目录
前言
如果你是一个急性子,没什么耐性的人,可以只看下句,自己去品味理解:
内部类:就是我是你的一部分,我了解你,我知道你的全部,没有你就没有我。(所以内部类对象是以外部类对象存在为前提的)
静态内部类:就是我跟你没关系,自己可以完全独立存在,但是我就借你的壳用一下,来隐藏自己。
如果还不知道静态和普通成员的区别,就先学static吧。
静态成员:属于这个类,数据存放在class文件中,程序运行之前就已经存进去数据了。
普通成员:属于这个类的对象,程序运行后生成堆栈中。
先来看一下官方说法,根据Oracle官方的说法:
Terminology: Nested classes are divided into two categories: static and non-static. Nested classes that are declared static
are called static nested classes. Non-static nested classes are called inner classes.
一个称为静态嵌套类(静态内部类),一个称为内部类。那么两者到底有什么区别呢?很多JDK源码用到了静态内部类。HashMap、ThreadLocal、AQS的sync等。那么接下来学习一下内部类吧!
内部类
内部类是定义在另外一个类中的类,主要原因有:
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据
- 内部类可以对同一个包的其他类隐藏
静态内部类和非静态内部类最大的区别是:非静态内部类编译后隐式保存着外部类的引用(就算外部类对象没用了也GC不掉),但是静态内部类没有。
1.1 非静态内部类
1.1.1 定义
内部类定义语法格式如下:
class OuterClass {
...
class NestedClass {
...
}
}
我们直接先看来一个例子吧,在Human类里定义了一个HumanLeg非静态内部类,并且在HumanLeg类的实例方法中直接访问外部类的private访问权限的实例变量和类变量。
/**
* 人类 - 外部类
*
* @author GrimMjx
*/
public class Human {
private static final int eyes = 2;
private static void count() {
System.out.println("I can count number");
}
private int teeth = 10;
private void say() {
System.out.println("Hello world");
}
/**
* 人腿 - 非静态内部类
*/
public class HumanLeg {
private Double length;
public HumanLeg(Double length) {
this.length = length;
}
public void test() {
say();
count();
System.out.println("I have " + eyes + " eyes");
System.out.println("I have " + teeth + " teeth");
System.out.println("My leg has " + length.toString() + "cm long");
}
}
public static void main(String[] args) {
Human human = new Human();
HumanLeg humanLeg = human.new HumanLeg(100D);
humanLeg.test();
}
}
运行结果:
Hello world
I can count number
I have 2 eyes
I have 10 teeth
My leg has 100.0cm long
由此看出,非静态内部类可以直接访问外部类的实例变量、类变量、实例方法、类方法。这是因为在非静态内部类对象里,保存了一个它所寄生的外部类对象的引用(非静态内部类实例必须寄生在外部类实例里)。也就是说,非静态内部类对象总有一个隐式引用,指向了创建它的外部类对象。我们来画一张示意图来理解一下。
另外,还有一些要注意的点
- 非静态内部类的成员只是在非静态内部类范围是可知的,并不能被外部类直接使用,如果要访问非静态内部类的成员必须显示创建非静态内部类对象来调用访问!
- 根据静态成员不能访问非静态成员的规则,外部类的静态方法不能访问非静态内部类。
- 非静态内部类不允许定义静态成员。如下面例子所示:
/**
* @author GrimMjx
*/
public class Test {
class Inner{
static {
}
}
// 静态成员无法访问非静态成员
public static void main(String[] args) {
new Inner();
}
}
1.1.2 内部类的特殊语法规则
如果非静态内部类方法访问某个变量,其顺序为
- 该方法是否有该名字的成员变量 - 直接用该变量名
- 内部类中是否有该名字的成员变量 - 使用this.变量名
- 外部类中是否有该名字的成员变量 - 使用外部类的类名.this.变量名
接下来看一个例子:
/**
* @author GrimMjx
*/
public class Outer {
private int i = 1;
public class Inner {
private int i = 2;
public void print() {
int i = 3;
System.out.println(i);
System.out.println(this.i);
System.out.println(Outer.this.i);
}
}
public static void main(String[] args) {
Outer outer = new Outer();
Inner inner = outer.new Inner();
inner.print();
}
}
运行结果:
3
2
1
1.2 静态内部类
如果用static来修饰一个内部类,那么就是静态内部类。这个内部类属于外部类本身,但是不属于外部类的任何对象。因此使用static修饰的内部类称为静态内部类。静态内部类有如下规则:
- 静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。
- 外部类可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象访问其实例成员。
/**
* 静态内部类测试外部类。
*
* @author GrimMjx
*/
public class StaticInnerTest {
private int x = 1;
private static int y = 2;
public void test(){
System.out.println(new InnerClass().a);
System.out.println(InnerClass.b);
}
static class InnerClass {
private int a = 3;
private static int b = 4;
public void test(){
//无法访问
// System.out.println(x);
System.out.println(y);
}
}
public static void main(String[] args) {
StaticInnerTest staticInnerTest = new StaticInnerTest();
staticInnerTest.test();
InnerClass innerClass = new InnerClass();
innerClass.test();
}
}
只要你坚持,一步一步来,总归会成功的。切忌,学技术急不来,快就是稳,稳就是快。技术有限,接收指正。如果您觉得写的可以,请点个推荐。
内部类包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类
-
成员内部类
1.成员内部类定义为位于另一个类的内部,成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括
private
成员和静态成员)。class Outer{ private double a = 0; public static int b =1; public Outer(double a) { this.a = a; } class Inner { //内部类 public void fun() { System.out.println(a); System.out.println(b); } } }
2.当成员内部类拥有和外部类同名的成员变量或者方法时,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:外部类.
this
.成员变量3.在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。
4.成员内部类是依附外部类而存在的,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
class Outter{ private double a = 0; public static int b =1; public Outter(){} public Outter(double a) { this.a = a; Inner inner = new Inner(); inner.fun(); //调用内部类的方法 } class Inner { //内部类 int b = 2; public void fun() { System.out.println(a); System.out.println(b); //访问内部类的b System.out.println(Outter.this.b);//访问外部类的b } } } public class Main{ public static void main(String[] args) { Outter outter = new Outter(); Outter.Inner inner = outter.new Inner(); //创建内部类的对象 } }
-
局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。
class Outter { private int outter_a = 1; private static int static_b = 2; public void test1(){ int inner_c =3; class Inner { private void fun(){ System.out.println(outter_a); System.out.println(static_b); System.out.println(inner_c); } } Inner inner = new Inner(); //创建局部内部类 inner.fun(); } public static void test2(){ int inner_d =3; class Inner { private void fun(){ System.out.println(outter_a); //编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量 System.out.println(static_b); System.out.println(inner_d); } } Inner inner = new Inner(); inner.fun(); } }
-
匿名内部类
匿名内部类只没有名字的内部类,在日常开发中使用较多。使用匿名内部类的前提条件:必须继承一个父类或实现一个接口。
interface Person { public void fun(); } class Demo { public static void main(String[] args) { new Person() { public void fun() { System.out.println("hello,word"); } }.fun(); } }
-
静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字
static
。静态内部类是不需要依赖于外部类的,并且它不能使用外部类的非static
成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static
成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。class Outter { int a = 1; static int b = 2; public Outter() { } static class Inner { public Inner() { System.out.println(a);//报错,静态内部类不能访问非静态变量 System.out.println(b); } } } public class Main{ public static void main(String[] args) { Outter.Inner inner = new Outter.Inner(); } }
-
内部类的优点:
-
内部类不为同一包的其他类所见,具有很好的封装性;
-
匿名内部类可以很方便的定义回调。
-
每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
-
内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
-
-
局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上
final
?public class Main { public static void main(String[] args) { } public void fun(final int b) { final int a = 10; new Thread(){ public void run() { System.out.println(a); System.out.println(b); }; }.start(); } }
对于变量
a
可以从生命周期的角度理解,局部变量直接存储在栈中,当方法执行结束后,非final
的局部变量就被销毁,而局部内部类对局部变量的引用依然存在,如果局部内部类要调用没有final
修饰的局部变量时,就会造成生命周期不一致出错。对于变量
b
,其实是将fun
方法中的变量b
以参数的形式对匿名内部类中的拷贝(变量b
的拷贝)进行赋值初始化。在run
方法中访问的变量b
根本就不是test
方法中的局部变量b
,而是一个拷贝值,所以不存在生命周期不一致的问题,但如果在run
方法中修改变量b
的值会导致数据不一致,所以需要加final
修饰。