【1】内部类定义

我们所说的内部类,官方的叫法是嵌套类(Nested Classes)。嵌套类包括静态内部类(Static Nested Classes)和内部类(Inner Classes)。而内部类分为成员内部类,局部内部类(Local Classes)和匿名内部类(Anonymous Classes)。

内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两个类,分别为outer.class和outer$inner.class类。所以内部类的成员变量/方法名可以和外部类的相同。

当你访问外部类的私有数据成员时,JDK会在外部类中创建包级访问权限的(package-acess)成员函数以便内部类来访问外部类的私有成员。这种结果会导致一个安全漏洞。通常情况下,我们应当尽量避免使用内部类。

只有当内部类仅仅只和外部类的上下文相关或者内部类需要被设置为私有的且只能被外部类访问的这些情况下,我们才使用内部类。内部类主要被用来实现类似Iterators、Comparators这些辅助类。它们被使用在外部类的上下文中。


【2】内部类特点

内部类是指在一个外部类的内部再定义一个类,内部类作为外部类的一个成员并且依附于外部类而存在的。

注意这里明确说明了是在类的内部作为其成员,而不仅仅只是和外部类在同一个class文件中!

如下所示:枚举类Operator和OO均为SearchFilter的内部类
Java内部类 - 局部/匿名/成员/静态内部类_java
如下所示,class BB 和class SearchFilter在同一个class 文件中,但是二者并无关系。唯一需要注意的是,一个class文件只能有一个public类型的外部类!
Java内部类 - 局部/匿名/成员/静态内部类_内部类_02

编译后的class文件显示如下:
Java内部类 - 局部/匿名/成员/静态内部类_外部类_03
可以在一个外部类中定义多个内部类,这些内部类在逻辑上是一个整体,与外部类之间是从属关系。在内部类中可以访问外部类定义的私有成员。

其特点如下:

  • 非静态内部类能够访问其外部类的所有属性和方法;
  • 静态内部类能够访问其外部类的static属性和static方法
  • 内部类能够隐藏起来,不被同一个包中的其他类访问。如果一个类只对某个类提供使用,那么可以将其定义为内部类;
  • 匿名内部类可以方便的用在回调方法中,典型应用是图形编程中的事件处理;
  • 外部类不能直接访问内部类的成员, 但是可以通过内部类的实例访问内部类的私有成员;
  • 局部类不仅可以访问外部类的所有成员,还可以访问方法体的局部变量,但必须是final修饰的局部变量;
  • 非静态内部类不能够定义静态字段和方法,但是静态内部类则可以定义静态字段和方法也可以定义非静态字段和方法。
  • 内部类可以声明为抽象类,因此可以被其他的内部类继承,也可以声明为final;
  • 和外部类不同,内部类可以声明为private或protected,外部类只能用public和default;
  • 内部类可以声明为static,但此时就不能再使用外层封装类的非static成员变量;
  • 非static的内部类的成员不能声明为static的,只有在顶层类或static的内部类才可以声明static成员。
  • 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
  • 匿名内部类可以很方便的定义回调。

内部类示例代码如下:

public class Outer{
private int size;
//此处为成员内部类-非静态内部类
public class Inner{
public void duStuff(){
//内部类可以访问外部类的私有属性
size++;
}
}
//方法定义
public void testInner(){
//调用内部类中的方法
Inner i = new Inner();
i.duStuff();
}
}

在上述代码中,在Outer类中定义了属性size、内部类Inner和方法testInner(),在方法中调用内部类,直接通过"对象.方法"的形式即可调用。

如果在其他类中需要调用Inner类中的方法,则可以使用以下格式:

//方法一
Outer.Inner in = new Outer().new Inner();
in.doStuff();

//方法二
Outer o = new Outer();
Outer.Inner in = o.new Inner();
in.doStuff();

内部类可以通过static修饰,称为静态内部类,在静态内部类中调用外部类成员,外部类成员也要求用static修饰。

如果内部类用static修饰,则可以使用以下格式调用其方法。

Outer.Inner in = new Outer.Inner();
in.doStuff();

当外部类和内部类成员变量相同时,在内部类中需要使用“外部类.this.attr”来调用外部类的变量。

示例如下:

public class A{

private int i = 100;

class B {
private int i = 10;
public void show(){
//若不显示指明,默认为内部类成员
System.out.println(i);
//该处打印外部类的成员变量
System.out.println(A.this.i);
}
}
}

输出结果:10 100

【3】局部内部类与静态内部类

二者区别

  • 创建静态内部类的对象可以直接通过外部类调用静态内部类的构造器;创建非静态的内部类的对象必须先创建外部类的对象,通过外部类的对象调用内部类的构造器。
  • 在静态内部类中调用外部类成员,成员也要求用static修饰。
  • 非静态内部类能访问外部类的一切成员, 包括私有成员。外部类虽然不能直接访问内部类的成员, 但是可以通过内部类的实例访问内部类的私有成员。
  • 局部内部类定义在方法体内,只能在该方法或条件的作用域内才能使用,退出这些作用域就无法引用。
  • 局部类不仅可以访问外部类的所有成员,还可以访问方法体的局部变量,但必须是final修饰的局部变量。

示例如下:

public class TestInnerClass {
public static void main(String[] args) {
//创建静态内部类的对象:可以直接通过外部类调用静态内部类的构造器
Person.Dog d = new Person.Dog();

//创建非静态的内部类的对象:必须先创建外部类的对象,
//通过外部类的对象调用内部类的构造器
Person p = new Person();
Person.Bird b = p.new Bird();//new p.Bird();
b.info();
b.setName("杜鹃");
}
}

class Person{
String name = "韩梅梅";
int age;
//成员内部类(非static的)
class Bird{
String name = "黄鹂";
int id;
public Bird(){
}
public void setName(String name){
System.out.println(name);//杜鹃
System.out.println(this.name);//黄鹂
System.out.println(Person.this.name);//韩梅梅
}
public void info(){
//调用外部类的成员方法
show();
}
}
//成员内部类(静态内部类)
static class Dog{

}
/*成员方法*/
public void show(){
System.out.println("我是show()方法");
}

public void method1(){
/*局部内部类*/
class A{

}
}
}

运行结果:

我是show()方法
杜鹃
黄鹂
韩梅梅

【4】局部内部类与匿名内部类的使用

示例如下:

public class TestInnerClass1 {

}
class OuterClass{
//局部内部类
//如下的使用方式较少
public void method1(){
class InnnerClass{
}
}
//常常使用一个方法,使其返回值为某个类或接口的对象。
//而这个类或接口在方法内部创建

//使用方式一
public Comparable getComparable(){
//1.创建一个实现Comparable接口的类:局部内部类
class MyComparable implements Comparable{
public int compareTo(java.lang.Object o) {
return 0;
}
}
}
//使用方式二
public Comparable getComparable1(){
//返回一个实现Comparable接口的匿名内部类的对象
return new Comparable(){
public int compareTo(java.lang.Object o) {
return 0;
}
};
/*
new Thread(new Runnable() {
public void run() {
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
});
*/
}
}

为什么匿名内部类和局部内部类只能访问final变量?

参考博文:浅谈匿名内部类和局部内部类只能访问final变量


【5】静态内部类和非静态内部类

非静态嵌套类(即内部类)对它嵌套的外部类的成员拥有全部的访问权限。

而静态嵌套类并不包含对外部类的实例引用,这就使得静态嵌套类不能够调用外部类实例的非静态方法或者访问外部类的非静态字段。

在声明成员字段和方法上,非静态嵌套类不能够定义静态字段和方法。但是,静态内部类则可以定义静态字段和方法也可以定义非静态字段和方法。

非静态内部类的实例是通过使用外部类的对象引用来创建的,也就在说在内部类中已经定义了外部类实例。但是静态嵌套类实例的创建过程中并不涉及到外部类的引用,这就是说它并不拥有外部类的实例。

示例如下:

public class OuterClass {

class InnerClass {
// static int x; not allowed here
}

static class StaticInnerClass {
static int x; // allowed here
}
}

class Test {
public static void main(String... str) {
OuterClass oc = new OuterClass();
OuterClass.InnerClass obj1 = oc.new InnerClass();// need of inclosing
// instance
OuterClass.StaticInnerClass obj2 = new OuterClass.SIC();
// no need of reference of object of outer class
}
}

【6】成员变量与局部变量

成员变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中。

局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放。

作用域

成员变量:针对整个类有效。
局部变量:只在某个范围内有效。(一般指的就是方法,语句体内)

存储位置

成员变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中。
局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放。

生命周期

成员变量:随着对象的创建而存在,随着对象的消失而消失
局部变量:当方法调用完,或者语句结束后,就自动释放。

初始值

成员变量:有默认初始值。
局部变量:没有默认初始值,使用前必须赋值。

使用原则

在使用变量时需要遵循的原则为:就近原则 首先在局部范围找,有就使用;接着在成员位置找。