目录
(1):在实例内部类中可以直接访问外部类中任意访问限定符修饰的成员
(2)在内部类中如果外部类和实例内部类具有相同名称成员时,优先访问的是内部类自己的
(3)如果要访问外部类同名成员时候,必须:外部类名称.this.同名成员名字
(一)概述
什么是内部类?
定义:把类定义在其他类的内部,那么这个类就称作内部类。它体现了一种代码的隐藏机制和访问控制机制。
什么时候使用内部类?
内部类与所在外部类有一定的关系,且往往只由该外部类调用此内部类,没有其他类要调用此内部类,没必要专门用一个Java文件来存放这个类的时候,用内部类。
如下代码是java源码中定义在HashMap类中的静态内部类Node。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
(二)内部类的分类
根据内部类定义的位置不同可分为两类
1:成员内部类 :根据是否有static修饰又分为
1.1:实例内部类:未被static修饰的成员内部类
1.2 静态内部类:被static修饰的成员内部类
2:局部内部类:可分为普通局部内部类 和 匿名内部类(特殊的局部内部类)
如下代码中InnerClass1为实例内部类,InnerClass2为静态内部类 InnerClass3为普通局部内部类,匿名内部类实现了Runnable接口,并重写了run方法,可见匿名内部类非常适合用于简单接口的实现和抽象类的实例化。
public class OutClass {
class InnerClass1{
//实例内部类
}
static class InnerClass2{
//静态内部类
}
public void method(){
class InnerClass3{
//局部内部类
}
}
Thread runnable = new Thread(new Runnable() {
//匿名内部类非常适合用于简单的接口实现或抽象类的实例化可以使代码更加简洁和易读。
@Override
public void run() {
System.out.println("这是一个匿名内部类");
}
});
}
(三)实例内部类特性
(1):在实例内部类中可以直接访问外部类中任意访问限定符修饰的成员
如下图实例内部类InnerClass中的 innerMethod方法可直接访问外部类OutClass中的任意限定符修饰的成员
(2)在内部类中如果外部类和实例内部类具有相同名称成员时,优先访问的是内部类自己的
如下图中外部类OutClass和内部类InnerClass中都定义了相同名称的成员c,在调用内部类的innerMehod方法时,优先访问的是内部类的成员c。
注意:优先访问内部类的前提是在内部类中,如果是调用的外部类中的方法,那么优先访问的是外部类中的成员变量(可以理解为就近原则),并不是所有情况都是优先访问内部类中的变量
(3)如果要访问外部类同名成员时候,必须:外部类名称.this.同名成员名字
在如下代码中,通过OutClass.this.c成功在内部类中调用了外部类中的变量c(运行结果在代码下)
public class OutClass {
int c = 200;//外部类中的c为200
public void OutMethod(){
System.out.println(c);
}
class InnerClass {// 实例内部类:未被static修饰
int c = 100;//内部类中的c为100
public void innerMethod() {
System.out.println("调用外部类为200,内部类100 结果是 :"+ OutClass.this.c);
//借助OutClass.this.c在内部类中调用了外部变量c
}
}
//主方法
public static void main(String[] args) {
OutClass outClass = new OutClass();
OutClass.InnerClass innerClass = outClass.new InnerClass();
innerClass.innerMethod();//调用内部类中的innerMethod方法
}
}
在内部类中调用外部类变量运行结果
(4):如何创建实例内部类的对象
当要访问外部类是只需要正常new就能得到外部类对象,但实例内部类在外部类的内部,那么该如何创建内部类的对象呢?
普通内部类定义与外部类成员定义位置相同,因此创建实例内部类对象时必须借助外部类
如下图主方法中,有两种形式创建出了内部类对象(实际为一种,只是不同写法)
OutClass.InnerClass innerClass1 = new OutClass().new InnerClass();
解析:
OutClass.InnerClass找到InnerClass类
innerClass1为定义名称
newOutClass().new InnerClass():先通过newOutClass() 创建出OutClass类的对象,再借助创建的OutClass类的对象通过new InnerClass() 创建出InnerClass类的对象。
第二种方法与第一种相同,只是将newOutClass替换为创建好的outClass对象。
public class OutClass {
int c = 200;//外部类中的c为200
class InnerClass {// 实例内部类:未被static修饰
int c = 100;//内部类中的c为100
public void innerMethod() {
System.out.println("调用外部类为200,内部类100 结果是 :"+ OutClass.this.c);
//借助OutClass.this.c在内部类中调用了外部变量c
}
}
//主方法
public static void main(String[] args) {
// 创建实例内部类对象
OutClass.InnerClass innerClass1 = new OutClass().new InnerClass();
// 上述语法比较怪异,也可以先将外部类对象先创建出来,然后再创建实例内部类对象
OutClass outClass = new OutClass();
OutClass.InnerClass innerClass2 = outClass.new InnerClass();
innerClass2.innerMethod();
}
}
(5)实例内部类小结
1. 外部类中的任何成员都可以在实例内部类方法中直接访问
2. 实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的约束
3. 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名 称.this.同名成员 来访问
4. 实例内部类对象必须在先有外部类对象前提下才能创建
5. 实例内部类的非静态方法中包含了一个指向外部类对象的引用(第一条成立原因)
6. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。
(四)静态内部类的特性
(1)静态内部类只能直接访问外部类的静态成员
可以看到下图中内部类InnerClass中的innerMethod方法在访问没有static修饰的外部类变量a与MehodA()方法时报错,说明静态的内部类只能访问外部类的静态成员。
那么为什么呢?
(2):为什么静态内部类只能访问外部类静态成员
因为静态的内部类不持有对其外部类实例的引用。
因为非静态成员变量和方法属于外部类的具体实例,而静态内部类没有与外部类实例相关联,所以它无法访问任何非静态的成员。如果静态内部类需要访问外部类的非静态成员,它必须通过外部类的一个实例来进行访问。
关于静态关键字static的讲述可看:java的static关键字
如下代码中innerMethod方法借助OutClass的实例来访问到了外部类中的非静态成员变量
(3)静态内部类对象的创建
下方代码的主方法中创建一个实例对象直接new就行了不需要借助外部类对象
不需要借助外部类对象的原因还是,静态的内部类不持有对其外部类实例的引用,因此就可以在没有外部类实例的情况下创建静态内部类实例
public class OutClass {
private int a;
static int b;
public void methodA(){
a = 10;
System.out.println(a);
}
public static void methodB(){
System.out.println(b);
}
static class InnerClass {//静态内部类,被static修饰
public void innerMethod(OutClass outClass) {
outClass.a = 100;//借助外部类实例来访问外部类非静态成员
b =200;
outClass.methodA();
methodB();
}
}
//主方法
public static void main(String[] args) {
//创建一个外部类实例对象
OutClass outClass = new OutClass();
//创建一个内部类实例对象(不需要借助外部类)
InnerClass innerClass = new InnerClass();
innerClass.innerMethod(outClass);
}
}
(4)静态内部类小结
1.在静态内部类中能直接访问的只有外部类中的静态成员
2.要想访问外部类的非静态成员需要借助外部类实例
3.静态内部类的创建,不需要依赖外部类实例。
(五):普通局部内部类
由于局部内部类使用非常少,只需要明白语法格式(上方(二)内部类分类代码中),并有一些注意事项
1. 局部内部类只能在所定义的方法体内部使用(使用非常少原因)
2. 不能被public、static等修饰符修饰
3. 编译器也有自己独立的字节码文件,命名格式:外部类名字$数字内部类名字.class
(六)匿名内部类
(1)匿名内部类的目的
假如我们要重写一个Test接口中的eat方法
interface Test{//定义一个test接口
void eat();
}
按照正常的逻辑来说,我们必须创建一个类去实现 Test接口,并重写 Test接口中的方法,然后再创建Test接口的实现类对象,然后才能调用Test 接口中的方法。
如下OutClass实现Test接口并调用。
interface Test{//定义一个test接口
void eat();
}
//内部类博客
public class OutClass implements Test {//OutClass实现Test接口
@Override
public void eat() {//重写接口中的eat方法
System.out.println("吃饭!");
}
//主方法
public static void main(String[] args) {
OutClass outClass = new OutClass();//实例化对象
outClass.eat();//调用方法
}
}
(2)用匿名内部类如何实现接口重写方法
匿名内部类的作用,实际上就是将上述过程简化,来达到调用接口中方法的目的
如下代码是我们将上述过程用匿名内部类的过程实现了一遍
interface Test{//定义一个test接口
void eat();
}
//内部类博客
public class OutClass {
//主方法
public static void main(String[] args) {//匿名内部类属于局部内部类所以要定义在方法中
Test test = new Test() {//匿名内部类
@Override
public void eat() {
System.out.println("吃饭!");
}
};
test.eat();//调用eat方法
}
}
(3)匿名内部类的特性
1.定义和实例化同时进行:匿名内部类在定义的同时就被实例化,因此不需要单独的实例化语句。
2.访问外部类的成员:如果匿名内部类是在一个方法中定义的,它可以访问该方法的局部变量,但这些变量必须是 final 的或者是 effectively final (实际没有被修改的变量)的变量
如下图val被修改后匿名内部类不能正常访问,这涉及到变量捕获的概念
具体可看文章——>一文看懂变量捕获
(4)匿名内部类小结
1:匿名内部类没有类名,所以只能使用一次
2:匿名内部类定义的同时就被实例化
3:访问的外部变量必须是final 或者effectively final (实际没有被修改的变量)
4:匿名内部类非常适合用于简单接口的实现和抽象类的实例化。