在Java中可以将一个类定义在另一个类或者方法的内部,前者称为内部类,后者称为外部类。内部类也是封装的一种体现。
public class OutClass {
class InnerClass{
}
}
// OutClass是外部类
// InnerClass是内部类
public class Out {
private int a;
int b;
public void fun(){
class Inner{
String name = "内部类";
}
}
}
注意:内部类和外部类虽然共用一个java源文件,但是经过编译后,内部类会形成单独的字节码文件:
1 实例内部类
实例内部类对比于静态内部类,就是不加static的成员内部类。
public class Out {
int a = 1;
int b = 2;
public int c = 3;
public void fun(){
System.out.println("这是外部类");
}
class Inner{
int a = 11;
int b = 22;
public int c = 33;
public void fun(){
System.out.println("内部类");
}
}
}
以上就是一个实例内部类的例子,那么我们要怎么实例化这个实例内部类呢?
Out out = new Out();
Out.Inner inner = out.new Inner();
//先实例化外部类 用过外部类引用实例化内部类
Out.Inner inner1 = new Out().new Inner();//直接new
思考一下在内部类中的字段是否能被static修饰呢?
答案是不可以 直接编译不通过 :
原因很简单 被static修饰后 成员将伴随类的加载而生成 但是要访问实例内部类中成员 必须要创建实例内部类的对象 所以就编译失败。当我们偏偏要想用static修饰的时候 该怎么办?我们可以用static final 修饰 public static final int A = 11;
此时A变量定义成一个常量。
现在我们的内部类声明好了 在内部类当中怎么访问外部类和自己的成员变量呢?
public class Out {
int a = 1;
int b = 2;
public int c = 3;
public void fun(){
System.out.println("这是外部类");
}
class Inner{
public static final int d = 11;
int e = 22;
public int f = 33;
public void fun(){
System.out.println("内部类");
//在内部类中可以直接访问 外部类的成员
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
System.out.println(e);
System.out.println(f);
}
}
}
思考:当外部类的成员变量名和内部类的相同时访问情况是什么?
public class Out {
int a = 1;
int b = 2;
public int c = 3;
public void fun(){
System.out.println("这是外部类");
}
class Inner{
public static final int a = 11;
int b = 22;
public int c = 33;
public void fun(){
System.out.println("内部类");
System.out.println(a);
System.out.println(b);
System.out.println(c);
}
}
}
此时会按照就近原则访问到的是我们的内部类的成员。那我们想要访问外部类的成员该怎么办? System.out.println(Out.this.a);
获取到外部类的this即可。
注意事项:
- 外部类中的任何成员都可以在实例内部类方法中直接访问
- 实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的约束
- 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名
称.this.同名成员 来访问 - 实例内部类对象必须在先有外部类对象前提下才能创建
- 实例内部类的非静态方法中包含了一个指向外部类对象的引用
- 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。
2 静态内部类
被static修饰的内部成员类称为静态内部类。
那么静态内部类该怎么实例化呢?
Out.InnerClass innerClass = new Out.InnerClass();
在内部类当中怎么访问外部类的成员变量呢?
public class Out {
private int a;
static int b;
public void methodA() {
a = 10;
System.out.println(a);
}
public static void methodB() {
System.out.println(b);
}
// 静态内部类:被static修饰的成员内部类
static class InnerClass {
public void methodInner() { // 在内部类中只能访问外部类的静态成员
a = 100; // 编译失败,因为a不是类成员变量
b =200;
methodA(); //编译失败,因为methodA()不是类成员方法
methodB();
}
}
}
注意:
- 在静态内部类中只能直接访问外部类中的静态成员
如果确实想访问,我们可以通过通过外部类的引用(先实例化外部类) - 创建静态内部类对象时,不需要先创建外部类对象
- 静态成员的应用就直接用类名 不能再乱加this 语法不允许
3 局部内部类
局部内部类就是定义在在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用,一般使用的非常少。
public class OutClass {
int a = 10;
public void method() {
int b = 10; // 局部内部类:定义在方法体内部 不能被public、static等访问限定符修饰
class InnerClass {
public void methodInnerClass() {
System.out.println(a);
System.out.println(b);
}
}
// 只能在该方法体内部使用,其他位置都不能用
InnerClass innerClass = new InnerClass();
innerClass.methodInnerClass();
}
public static void main(String[] args) {
// OutClass.InnerClass innerClass = null; 编译失败
}
}
注意:
- 局部内部类只能在所定义的方法体内部使用
- 不能被public、static等修饰符修饰
- 编译器也有自己独立的字节码文件,命名格式:外部类名字$内部类名字.class
4 匿名内部类
匿名内部类,就是没有名字的一种嵌套类。它是Java对类的定义方式之一。
我们为什么要使用匿名内部类呢?难道就是一个语法?一个花架子吗?当然不是了。
在实际开发中,我们常常遇到这样的情况:一个接口/类的方法的某个实现方式在程序中只会执行一次,但为了使用它,我们需要创建它的实现类/子类去实现/重写。此时可以使用匿名内部类的方式,可以无需创建新的类,减少代码冗余。
现在有一个IRun接口 接口中有一个run方法
public interface IRun {
public void run();
}
我们现在如果想使用这个接口 就必须创建一个类并且实现IRun接口 重写里面的run方法
public class IRunImpl implements IRun{
@Override
public void run() {
System.out.println("IRunImpl 实现了 IRun接口...");
}
}
然后实例化实现接口的类即可使用接口
public class Main {
public static void main(String[] args) {
IRunImpl iRun = new IRunImpl();
iRun.run();
}
}
但是如果实现类IRunImpl全程只使用一次,那么为了这一次的使用去创建一个类,未免太过麻烦。我们需要一个方式来帮助我们摆脱这个困境。匿名内部类则可以很好的解决这个问题。
我们使用匿名内部类对上面的例子进行优化
public class Main {
public static void main(String[] args) {
IRun iRun = new IRun(){
@Override
public void run() {
System.out.println("IRunImpl 实现了 IRun接口...利用了匿名内部类");
}
};
iRun.run();//利用接口的引用调用接口方法
}
}
或者匿名内部类直接作为参数
public interface Bell {
public void ring();
}
public class Cellphone {
public void alarmcolck(Bell bell){
bell.ring();
}
}
/**
* 匿名内部类直接作为参数
*/
public class Test {
public static void main(String[] args) {
Cellphone cellphone = new Cellphone();
cellphone.alarmcolck(new Bell(){
@Override
public void ring() {
System.out.println("懒猪起床了...");
}
});
cellphone.alarmcolck(new Bell(){
@Override
public void ring() {
System.out.println("小伙上课了...");
}
});
}
}
通常,我们也习惯用这样的方式创建并启动线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是一个线程");
}
}).start();
通过上面例子我们大概了解了 匿名内部类的使用方式和场景
注意:
- 匿名内部类,在【创建对象】的时候,只能使用唯一一次。
如果希望多次创建对象,而且类的内容一样的话,那么就需要使用单独定义的实现类了。 - 匿名对象,在【调用方法】的时候,只能调用唯一一次。
如果希望同一个对象,调用多次方法,那么必须给对象起个名字。 - 匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】
强调:匿名内部类和匿名对象不是一回事!!!