什么是内部类:
只要声明在其他类里面的类都叫做内部类。
内部类分为几种
根据它声明的位置不同分为四种:
- (1)成员内部类:类中方法外
-
A:静态内部类
-
B:非静态成员内部类
- (2)局部内部类:方法体中
-
A:有名字的局部内部类
-
B:匿名内部类
回忆以往知识:
- 变量根据声明的位置不同,分为:
- (1)成员变量
-
类中方法外
-
A:静态变量
-
B:实例变量
- (2)局部变量
-
方法()中,方法体{},代码块{}中
为什么要声明内部类?
- 类的概念:一类具有相同特性(属性、方法)的事物的抽象描述。
- (1)当在描述一类事物的时候,发现这个事物的内部,仍然有一个完整的结构,需要用一个类来描述。
- (2)这个内部类的事物只为它的外部类服务,脱离了这个外部类就没有存在的意义
- 那么这个时候,就可以考虑使用内部类来声明这个内部的结构。
一、静态内部类
- 1、语法格式:
【修饰符】 class 外部类{
【修饰符】 static class 静态内部类{
}
}
- 2、特点
-
(1)静态内部类它也是一个类
- 它的字节码文件名:外部类名$静态内部类名.class
- 例如:Outer$Inner.class
-
(2)静态内部类也可以有自己的成员:
- 没有限制
- 所有类型的成员在静态内部类中都可以声明。
-
(3)静态内部类是否可以使用外部类的成员?
- 有限制。
- 静态内部类不能使用外部类的非静态成员。
-
(4)在外部类中使用静态内部类是否有限制?
- 没有限制。
- 说明:如果你要使用静态内部类的静态方法等,那么直接使用"静态内部类名."即可
- 如果你要使用静态内部类的非静态方法等,那么直接使用"静态内部类对象."即可
-
(5)在外部类的外面 使用静态内部类是否有限制?
- 没有限制
- 说明:如果你要使用静态内部类的静态方法等,那么直接使用"静态内部类名."即可
- 如果你要使用静态内部类的非静态方法等,那么直接使用"静态内部类对象."即可
- 只不过在外面使用时,要注意静态内部类的全名称是“包名.外部类名.静态内部类名",可以使用import语句简化使用
- 声明内部类对象:例如Inner是Outer的内部类,我现在要在外部类外面生命Inner的对象
- Inner inner = new Outer.Inner();(Inner是静态的,所有对象共享一个所以new Outer.后面没有加new)
-
二、非静态内部类
1、语法格式:
【修饰符】 class 外部类{
【修饰符】 class 非静态内部类{
}
}
2、特点
-
(1) 非静态内部类也是类,也有自己的字节码文件
- 外部类名$非静态内部类名.class
-
(2) 非静态内部类中声明成员,是否有限制
- 有限制:不能有静态的成员
-
(3)非静态内部类中使用外部类的成员是否有限制
- 没有限制
-
(4)在外部类中使用非静态内部类是否有限制
- 有限制
- 静态方法等成员中不能使用非静态内部类
-
(5)在外部类的外面使用非静态内部类
- 可以使用,只不过需要外部类的对象,才能使用非静态内部类
- 声明非静态内部类对象:例如Inner是Outer的内部类,我现在要在外部类外面生命Inner的对象
- Inner inner = new Outer.new Inner();(Inner是非静态的,所以new Outer.后面要加new,注意区分静态和非静态的区别)
三、局部内部类
声明在类的方法体中
- 1、语法格式
【修饰符】 class 外部类{
【修饰符】 返回值类型 方法名(【形参列表】){
【修饰符】 class 局部内部类名{
}
}
}
- 2、特点
- (1)局部内部类也是类,也是有字节码文件
- 外部类名$编号局部内部类名.class
- (2)局部内部类中声明类的成员,是否有限制
- 不能有静态成员
- 总结: 除了静态内部类中可以有静态成员,其他的内部类中统统都不能声明静态成员。
- (3)在局部内部类中使用外部类的成员,是否有限制
- 有限制,但是限制和局部内部类无关,看所在的方法,所在的方法是静态的,就不能使用非静态的
- (4)在外部类中,使用局部内部类是否有限制?
- 局部内部类有作用域
- (5)在外部类的外面能不能使用局部内部类?
- 不能
- (6)在外部类的外面是否可以获取局部内部类的对象?
- 可以,但是只能用父类或父接口的变量接收这个对象
- (7)在局部内部类中可以使用外部类的作用域范围内的局部变量,但是这个局部变量必须是final的。
- JDK1.8之前,必须手动加final,JDK1.8之后默认自动加final
- 为什么?
- 如果没有final,那么就是普通的局部变量,是在栈中存储,那么方法调用结束后,就内存自动释放了, 而我们方法中的局部内部类的对象可能被返回到外面使用,如果通过这个对象调用方法,又要使用这个局部变量,就无法访问了。所以要用final修饰,使得它称为常量,值存储到方法区的常量池中。
例如:
- (1)局部内部类也是类,也是有字节码文件
public class TestLocalInner {
public static void main(String[] args) {
Outer out = new Outer();
Object obj = out.test();
//此时的输出为class 包名.Outer$Inner
System.out.println(obj.getClass());//获取对象的运行时类型
//但此时如果我们想要调用Inner里面的method方法,那么我们需要对obj向下转型
//但又发现我们根本无法获取MyInter类的类型,所以这样是无法调用MyInter类中的方法的
//obj.method();
//这里我们采用了接口(继承父类也可以)的方式来实现调用,重写接口中的方法
//接口对象在调用method方法时会自动调用重写后的方法
MyInter my = out.test();
my.method();
}
}
class Outer{
public MyInter test(){
final int num = 10;//外部类Outer的test5方法的局部变量
class Inner implements MyInter{
@Override
public void method() {
System.out.println("num = " + num);
}
}
return new Inner();
}
}
interface MyInter{
void method();
}
四、匿名内部类
匿名: 没有名字,即这里说的是类没有名字
内部类: 在另一个类中声明的类,称为内部类。
匿名对象: 对象没名字
匿名内部类的语法格式:
-
我们声明一个类的目的,是为了创建对象,然后通过对象调用方法什么的。
-
那么这里这个类都没有名字,那么怎么创建对象?
- (1)因为是匿名内部类,那么必须在声明类的同时创建对象。现在不创建,后面没法创建了。
- (2)因为是匿名内部类,那么只能借助它父类或父接口的名字。
-
语法格式:
- A:调用父类的无参构造器
new 父类(){ 写类的成员 }
- B:调用父类的有参构造
new 父类(实参列表){ 写类的成员 }
- C:用Object的无参构造,因为接口没有构造器
new 父接口(){ 写类的成员 }
public class TestAnonymous {
public static void main(String[] args) {
//这样写可以调用test()方法
new Object(){
public void test(){
System.out.println("hello 匿名内部类");
}
}.test();
//下面这样不行
Object obj = new Object(){
public void test(){
System.out.println("hello 匿名内部类");
}
};
//obj.test();//报错object类中没有test()方法
}
}
通过上面的例子我们发现,匿名内部类好像并不能达到我们想要的结果,那我们用它做什么呢?
其实匿名内部类的使用是针对父类和接口方法的重写,我们在看下面这个例子
public class TestAnonymous {
public static void main(String[] args) {
//这里我们想要重写父类的方法,按照一般的办法我们应该写一个子类继承父类,然后在子类中重写父类的方法
//但如果我这个方法只用一次,那么我们是没有必要为此去创建一个新的子类的
//我们可以利用匿名内部类来重写父类的方法
Father f = new Father(){
public void method(){
System.out.println("子类的方法");
}
};
//此时调用的会是重写后的方法,输出"子类的方法"
f.method();
//那么此时f的类型又是什么呢?
System.out.println(f.getClass());
//class 包名.TestAnonymous$1
//TestAnonymous是我们当前类的名,$代表f是内部类
//后面是1那是因为我们没有给这个类取名字,系统默认为数字1,如果还有第二个匿名内部类,那么他会被命名为2,以此类推
}
}
class Father{
public void method(){
System.out.println("父类的方法");
}
}
同样,我们改用接口也可以
public class TestAnonymous2 {
public static void main(String[] args) {
Flyable f1 = new Flyable(){
@Override
public void fly() {
System.out.println("震动翅膀飞");
}
};
//此时调用的会是实现接口后的方法,输出"震动翅膀飞"
f1.fly();
System.out.println(f1.getClass());
}
}
interface Flyable{
void fly();
}