内部类
一、概述
-
什么是内部类?
将一个类A定义在另一个类B的里面,里面的类A就称为内部类,B就称为外部类。 -
为什么要声明内部类?
遵循高内聚低耦合、便于代码维护。
具体来说,当一个事物的内部,还有一部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,不在其他地方单独使用,那么整个内部的完整结构最好使用内部类。而且内部类因为在外部类的里面,因此可以直接访问外部类的私有成员。 -
内部类有哪些形式?
根据内部类声明的位置(如同变量的分类),我们可以分为:-
成员内部类:
- 静态成员内部类
- 非静态成员内部类
-
局部内部类
-
有名字的局部内部类
-
匿名的内部类
-
-
二、成员内部类
1. 静态内部类
语法格式:
【修饰符】 class 外部类{
【其他修饰符】 static class 内部类{
}
}
特点:
- 和其他类一样,它只是定义在外部类中的另一个完整的类结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以在静态内部类中声明属性、方法、构造器等结构,包括静态成员
- 可以使用abstract修饰,因此它也可以被其他类继承
- 可以使用final修饰,表示不能被继承
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。
- 和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private
- 外部类只允许public或缺省的
- 只可以在静态内部类中使用外部类的静态成员
- 在静态内部类中不能使用外部类的非静态成员
- 在外部类的外面不需要通过外部类的对象就可以创建静态内部类的对象
- 如果在内部类中有变量与外部类的静态成员变量同名,可以使用“外部类名."进行区别
public class TestInner{
public static void main(String[] args){
Outer.Inner in= new Outer.Inner();
in.inMethod();
Outer.Inner.inTest();
Outer.Inner.inFun(3);
}
}
class Outer{
private static int a = 1;
private int b = 2;
protected static class Inner{
static int d = 4;//可以
void inMethod(){
System.out.println("out.a = " + a);//1
// System.out.println("out.b = " + b);//错误的无法从static上下文中引用非static字段
}
static void inTest(){
System.out.println("out.a = " + a);//1
}
static void inFun(int a){
System.out.println("out.a = " + Outer.a);//1
System.out.println("local.a = " + a);//3
}
}
}
2、 非静态成员内部类
语法格式:
【修饰符】 class 外部类{
【修饰符】 class 内部类{
}
}
特点:
-
和其他类一样,它只是定义在外部类中的另一个完整的类结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以在非静态内部类中声明属性、方法、构造器等结构,但是不允许声明静态成员,但是可以继承父类的静态成员,而且可以声明静态常量。
- 可以使用abstract修饰,因此它也可以被其他类继承
- 可以使用final修饰,表示不能被继承
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。
-
和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private
- 外部类只允许public或缺省的
-
还可以在非静态内部类中使用外部类的所有成员,哪怕是私有的
-
在外部类的静态成员中不可以使用非静态内部类哦
- 就如同静态方法中不能访问本类的非静态成员变量和非静态方法一样
-
在外部类的外面必须通过外部类的对象才能创建非静态内部类的对象
- 因此在非静态内部类的方法中有两个this对象,一个是外部类的this对象,一个是内部类的this对象
不能声明静态成员的原因:
JVM的类加载规则 :
- static类型的属性和方法,在类加载的时候就会存在于内存中。
- 要想使用某个类的static属性和方法,那么这个类必须要加载到JAVA虚拟机中。
- 非静态内部类并不随外部类一起加载,只有在实例化外部类之后才会加载。
现在考虑这个情况:在外部类并没有实例化,内部类还没有加载,这时候如果
调用内部类的静态成员或方法,内部类还没有加载,却试图在内存中创建该内
部类的静态成员,这明显是矛盾的。所以非静态内部类不能有静态成员变量或
静态方法。
package day09Innerclass;
public class TestInner{
public static void main(String[] args){
Outer1 out = new Outer1();
Outer1.Inner in= out.new Inner();
in.inMethod();
Outer1.Inner inner = out.getInner();
inner.inMethod();
}
}
class Father{
protected static int c = 3;
}
class Outer1{
private static int a = 1;
private int b = 2;
protected class Inner extends Father{
//static int d = 4;//错误不支持static的声明
int b = 5;
void inMethod(){
System.out.println("out.a = " + a);//1 //1
System.out.println("out.b = " + Outer1.this.b);//2 //2
System.out.println("in.b = " + b);//5 //5
System.out.println("father.c = " + c);//3 //3
}
}
public static void outMethod(){
//Inner in = new Inner();//错误的静态不能访问非静态
}
public Inner getInner(){
return new Inner();
}
}
3、局部内部类
语法格式:
【修饰符】 class 外部类{
【修饰符】 返回值类型 方法名(【形参列表】){
【final/abstract】 class 内部类{
}
}
}
通过调用方法来创建内部类对象
特点:
- 和外部类一样,它只是定义在外部类的某个方法中的另一个完整的类结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以在局部内部类中声明属性、方法、构造器等结构,但不包括静态成员,除非是从父类继承的或静态常量
- 可以使用abstract修饰,因此它也可以被同一个方法的在它后面的其他内部类继承
- 可以使用final修饰,表示不能被继承
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。
- 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
- 和成员内部类不同的是,它前面不能有权限修饰符等
- 局部内部类如同局部变量一样,有作用域
- 局部内部类中是否能访问外部类的静态还是非静态的成员,取决于所在的方法
- 局部内部类中还可以使用所在方法的局部常量,即用final声明的局部变量
- JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final
思考题
为什么在局部内部类中使用外部类方法的局部变量要加final呢?
public class TestInner{
public static void main(String[] args) {
A obj = Outer.method();
//因为如果c不是final的,那么method方法执行完,method的栈空间就释放了,那么c也就消失了
obj.a();//这里打印c就没有值可取了,所以把c声明为常量,存储在方法区中
}
}
interface A{
void a();
}
class Outer{
public static A method(){
final int c = 3;
class Sub implements A{
@Override
public void a() {
System.out.println("method.c = " + c);
}
}
return new Sub();
}
}
4、 匿名内部类
当我们在开发过程中,需要用到一个抽象类的子类的对象或一个接口的实现类的对象,而且只创建一个对象,而且逻辑代码也不复杂。那么我们原先怎么做的呢?
(1)编写类,继承这个父类或实现这个接口
(2)重写父类或父接口的方法
(3)创建这个子类或实现类的对象
这里,因为考虑到这个子类或实现类是一次性的,那么我们“费尽心机”的给它取名字,就显得多余。那么我们完全可以使用匿名内部类的方式来实现,避免给类命名的问题。
new 父类(【实参列表】){ 重写方法…}
//()中是否需要【实参列表】,看你想要让这个匿名内部类调用父类的哪个构造器,如果调用父类的无参构造,那么()中就不用写参数,如果调用父类的有参构造,那么()中需要传入实参
new 父接口(){
重写方法…
}
//()中没有参数,因为此时匿名内部类的父类是Object类,它只有一个无参构造
匿名内部类是没有名字的类,因此在声明类的同时就创建好了唯一的对象。
注意:
匿名内部类是一种特殊的局部内部类,只不过没有名称而已。所有局部内部类的限制都适用于匿名内部类。例如:
- 在匿名内部类中是否可以使用外部类的非静态成员变量,看所在方法是否静态
- 在匿名内部类中如果需要访问当前方法的局部变量,该局部变量需要加final
思考:这个对象能做什么呢?
(1)使用匿名内部类的对象直接调用方法
interface A{
void a();
}
public class Test{
public static void main(String[] args){
new A(){
@Override
public void a() {
System.out.println("aaaa");
}
}.a();
}
}
(2)通过父类或父接口的变量多态引用匿名内部类的对象
interface A{
void a();
}
public class Test{
public static void main(String[] args){
A obj = new A(){
@Override
public void a() {
System.out.println("aaaa");
}
};
obj.a();
}
}
(3)匿名内部类的对象作为实参
interface A{
void method();
}
public class Test{
public static void test(A a){
a.method();
}
public static void main(String[] args){
test(new A(){
@Override
public void method() {
System.out.println("aaaa");
}
});
}
}