目录
1. 什么是内部类
- 什么是内部类: 将一个类定义在另一个类里面. 里面的那个类就叫内部类, 又叫内置类或嵌套类.
2. 内部类的作用
-
作用1:
如果类A类B相互独立, 并不是嵌套关系, 那么类A想访问类B中的成员, 必须在类A中new出类B的对象, 才能通过类B的对象访问类B的成员方法;
如果把类B放在类A中, 类B就相当于是类A的成员, 那么类B就能直接访问类A中的其他成员;
因此内部类的出现就是为了方便访问; -
作用2:
描述一类事物的时候用到了另一类事物, 可以用内部类; 例如描述人类, 需要用到心脏类:class Person{ private Integer age; class XinZang{ } }
3. 内部类的特点
-
内部类能直接访问外部类的成员, 包括私有成员;
外部类向访问内部类的成员, 必须先new内部类的对象;
(就像孙悟空能直接访问铁扇公主的内脏, 铁扇公主不能直接访问孙悟空的内脏. ) -
内部类定义在外部类的成员位置, 那么该内部类相当于外部类的成员, 因此能被成员修饰符修饰;
-
private修饰的成员内部类, 只能在外部类中调用. 就像私有的成员不能在其他类中访问一样;
-
内部类能放在成员位置, 能放在局部位置(方法中), 甚至能放在for循环中;
-
内部类, 外部类编译后会生成两个独立的
.class
文件:package cn.king.demo01; public class Demo01 { public static void main(String[] args) { } } class Wai { private int num1 = 10; class Nei { private int num2 = 20; public void fun1() { // 内部类能直接访问外部类的任何成员 System.out.println(num1); System.out.println(num2); } } public void fun1() { System.out.println(num1); // 外部类必须new内部类的对象才能访问内部类的成员 Nei nei = new Nei(); // -- 外部类访问内部类的成员变量 System.out.println(nei.num2); // -- 内部类访问外部类的成员方法 nei.fun1(); } } /* * 一个xxx.java文件编译后, 有几个class就生成几个xx.class文件. * 该文件编译后, 将生成3个xx.class文件: * Demo01.class * Wai$Nei.class * Nei.class */
4. 内部类的分类
- java中的内部类分为4种,分别是成员内部类, 局部内部类, 静态内部类, 匿名内部类.
4.1 成员内部类
4.1.1 定义成员内部类的语法格式
class Out{
class In{ } // 内部类定义在外部类的成员位置.
}
4.1.2 在第三类中访问成员内部类的3种方式
-
第三类就是内部类和外部类之外的另一个类. 例如下面的Demo01;
-
方式1. 无论内部类的修饰符是什么, 此法都能使用.
这种访问方式相对来说常见
/**
* 在外部类成员方法fun1()中new成员内部类的对象, 并通过此对象访问内部类成员,
* 那么我们在第三类的方法中new外部类的对象, 调用fun1()方法即可访问内部类的成员.
*/
class Out {
private int a = 60;
private class In {
public void fun1() {
System.out.println(a);
}
}
public void fun1() {
In in = new In();
in.fun1();
}
}
public class Demo01 {
public static void main(String[] args) {
Out out = new Out();
out.fun1(); //
}
}
- 方式2. 只能用于内部类没被封装的情况.
一般内部类会被封装起来,因此这种格式不常见
/**
* 在第三类的方法中创建内部类的对象, 通过内部类的对象访问内部类的成员,
* 语法格式如下: 外部类名.内部类名 引用变量名 = new 外部类名().new 内部类名();
*/
class Out {
private int a = 60;
class In {
public void fun1() {
System.out.println(a);
}
}
}
public class Demo01 {
public static void main(String[] args) {
Out.In in = new Out().new In();
in.fun1(); // 60
}
}
- 方式3. 只能用于内部类没被封装的情况.
一般内部类会被封装起来,因此这种格式不常见
/**
* 在外部类成员方法中定义返回内部类对象的方法.
*/
class Out {
private int a = 60;
class In {
public void fun1() {
System.out.println(a);
}
}
public In getIn() {
return new In();
}
}
public class Demo01 {
public static void main(String[] args) {
Out out = new Out();
Out.In in = out.getIn();
in.fun1(); // 60
}
}
- 小结在第三类中访问内部类
- 方式1: 在外部类成员方法中new成员内部类的对象, 通过该对象访问内部类的成员.
在第三类成员方法中, 只需要创建外部类的对象即可.
使用该方式, 成员内部类的访问修饰符可以是任意的. - 方式2: 在第三类的成员方法中使用下面的格式创建内部类的对象, 使用内部类的对象访问内部类的成员.
外部类名.内部类名 引用变量名 = new 外部类名().new 内部类名();
此方式只能用于内部类没被private修饰的情况. - 方式3: 在外部类中定义返回内部类对象的成员方法getIn().
在第三类成员方法中, 创建外部类的对象并调用外部类的getIn()方法获取内部类的对象, 使用内部类的对象访问内部类的成员.
此方式只能用于内部类没有被private修饰的情况.
- 方式1: 在外部类成员方法中new成员内部类的对象, 通过该对象访问内部类的成员.
4.2 局部内部类
4.2.1 定义局部内部类的语法格式
class Out{
public void fun1(){
// 内部类定义在外部类的局部位置
class In{
}
}
}
4.2.2 在局部内部类中访问外部类的成员变量 & 在第三类中访问局部内部类的方式
class Out {
private int a = 60;
// 调用Out.fun1() 就相当于调用了 Out.In.fun2();
public void fun1() {
class In {
public void fun2() {
// 局部内部类中访问外部类的成员, 就像方法访问方法所在类的成员一样.
System.out.println(a);
}
}
new In().fun2();
}
}
public class Demo01 {
public static void main(String[] args) {
Out out = new Out();
out.fun1(); // 60
}
}
4.2.3 在局部内部类中访问外部类的局部变量
package cn.king.demo01;
/**
* 局部内部类访问外部类的局部变量a,变量a必须被声明成final类型.
*
* 原因:
* 因为局部变量会随着方法的调用完毕而消失,这个时候,局部内部类的实例对象并没有立马从堆内存中消失,
* 还要使用那个变量。为了让数据还能继续被使用,就用fianl修饰,这样,
* 在堆内存里面存储的其实是一个常量值,并不是变量。
*/
class Out {
public void fun1() {
final int b = 60;
class In {
public void fun2() {
// 局部内部类访问与该内部类平级的局部变量
System.out.println(b);
}
}
new In().fun2();
}
}
public class Demo01 {
public static void main(String[] args) {
new Out().fun1(); // 60
}
}
- 注意, 局部内部类不能使用static关键字修饰. 就像局部变量不能用static关键字修饰一样.
4.3 静态内部类
4.3.1 定义静态内部类的语法格式
class Out{
// 成员内部类加上static就叫做静态内部类. 类似成员变量加上static就叫静态变量.
static class In{
}
}
4.3.2 在第三类中访问静态内部类的2种方式
- 方式1. 在第三类中访问静态内部类的成员方法.
class Out {
private static int b = 60;
static class In {
public void fun1() {
System.out.println(b);
}
}
}
/**
* 在第三类中通过下面的格式创建静态内部类的对象:
* 外部类名.内部类名 对象名=new 外部类名.内部类名();
* 通过该对象访问静态内部类的成员方法.
*/
public class Demo01 {
public static void main(String[] args) {
Out.In in = new Out.In();
in.fun1(); // 60
}
}
- 方式2. 在第三类中访问静态内部类的静态方法.
class Out {
private static int b = 60;
static class In {
public static void fun1() {
System.out.println(b);
}
}
}
// 在第三类的成员方法中 通过内部类类名调用内部类中的静态成员
public class Demo01 {
public static void main(String[] args) {
Out.In.fun1(); // 60
}
}
- 可见, 如果内部类是静态的, 那么它的访问方式和访问一个普通的类的方式相同.
- 注意
- static一般不加private,静态为了让人访问,私有为了不让人访问,两者在一起显然冲突,所以静态一般加public;
- static不能修饰外部类,只能修饰成员内部类。成员内部类就像外部类的成员变量, 因此能加static;
- 方法中不能加static,因为静态是随着类的加载而加载的, 方法是类加载之后才调用的,刚加载类时并没有调方法,更无法加载方法中的静态. 因此局部内部类不能加static关键字.
- 静态内部类访问的外部类数据必须用静态修饰,静态的只能访问静态的.
4.4 匿名内部类
4.4.1 匿名内部类简介
-
什么是匿名内部类: 匿名内部类是内部类的简写形式.
-
匿名内部类的本质: 匿名内部类的本质是继承了某类或实现了某接口的子类对象.
-
匿名内部类的作用: 匿名内部类的作用就是简化书写.
-
使用匿名内部类的前提: 该内部类必须继承一个类或实现一个接口, 注意不需要写extends或implements.
-
匿名内部类的使用场景: 适用于该类的对象只使用一次的情况.
-
匿名内部类的格式
new 父类名或实现的接口名(){};
new 父类名或实现的接口名(){ // 重写该类方法; // 如果想连续调用该对象的多个方法, 那么返回值就写return this; }.方法().方法()……
4.4.2 匿名内部类如何连续调用多个方法(两种方式)
- 方式1
package cn.king.demo01;
/**
* 匿名内部类如何连续调用多个方法.
* 方式1: 方法执行完毕之后,返回该对象的引用.
* "return this"才是this关键字最迷人的地方.
*/
abstract class A {
public abstract void fun1();
public abstract A fun2();
}
public class Demo01 {
public void fun() {
new A() {
@Override
public void fun1() {
System.out.println("fun1的逻辑被执行");
}
@Override
public A fun2() {
System.out.println("fun2的逻辑被执行");
return this;
}
}.fun2().fun1();
}
}
- 方式2
package cn.king.demo01;
/**
* 匿名内部类如何连续调用多个方法.
* 方式2: 给匿名对象一个名字
*/
abstract class A {
public abstract void fun1();
public abstract void fun2();
}
public class Demo01 {
public void fun() {
A a = new A() {
@Override
public void fun1() {
System.out.println("fun1的逻辑被执行");
}
@Override
public void fun2() {
System.out.println("fun2的逻辑被执行");
}
};
a.fun1();
a.fun2();
}
}
4.4.3 匿名内部类是向上转型
package cn.king.demo01;
abstract class A {
public abstract void fun1();
}
public class Demo01 {
public void fun() {
A a = new A() {
@Override
public void fun1() {
System.out.println("重写父类的方法");
}
public void fun2() {
System.out.println("匿名内部类独有的方法");
}
};
a.fun1(); // 输出: 重写父类的方法
//a.fun2(); // 报错
/*
* fun2()在父类中没有, 是子类中特有的,
* 匿名内部类是向上转型, 因此不能访问子类独有的方法fun2();
* 向上转型就是父类引用指向子类对象.
*/
}
}
4.4.4 匿名内部类简化内部类书写的实例
- 使用内部类
package cn.king.demo01;
abstract class A {
public abstract void fun1();
}
class Out {
int num = 60;
class In extends A {
@Override
public void fun1() {
System.out.println(num);
}
}
public void fun1() {
new In().fun1();
}
}
public class Demo01 {
public static void main(String[] args) {
new Out().fun1(); // 60
}
}
- 使用匿名内部类. 上面的代码和下面的代码功能完全相同. 都是继承父类 覆盖方法 new子类对象 调用方法.
package cn.king.demo01;
abstract class A {
public abstract void fun1();
}
class Out {
int num = 60;
public void fun1() {
new A() {
@Override
public void fun1() {
System.out.println(num);
}
}.fun1();
}
}
public class Demo01 {
public static void main(String[] args) {
new Out().fun1(); // 60
}
}
- 可见, 匿名内部类简化内部类的书写, 简化的是在第三类中访问内部类.
4.4.5 匿名内部类的实际使用场景
- 当函数的参数是接口类型,且接口中的方法不超三个,可以使用匿名内部类作为实参进行传递;
- 不使用匿名内部类
package cn.king.demo01;
interface Inter {
public abstract void fun1();
}
class InterImpl implements Inter {
@Override
public void fun1() {
System.out.println("fun1()被调用");
}
}
class Foo {
// 方法的形参是接口,我们必须传递该接口的实现类对象.
public void fun(Inter inter) {
inter.fun1();
}
}
public class Demo01 {
public static void main(String[] args) {
Foo foo = new Foo();
foo.fun(new InterImpl());
}
}
- 使用匿名内部类. 下面的代码功能等同于上面的代码
package cn.king.demo01;
interface Inter {
public abstract void fun1();
}
class Foo {
// 方法的形参是接口,我们必须传递该接口的实现类对象.
public void fun(Inter inter) {
inter.fun1();
}
}
public class Demo01 {
public static void main(String[] args) {
Foo foo = new Foo();
foo.fun(new Inter() {
@Override
public void fun1() {
System.out.println("fun1()被调用");
}
});
}
}
- 使用Lambda表达式. Lambda表达式能代替匿名内部类简化书写. 下面的代码功能等同于上面的代码.
package cn.king.demo01;
interface Inter {
public abstract void fun1();
}
class Foo {
// 方法的形参是接口,我们必须传递该接口的实现类对象.
public void fun(Inter inter) {
inter.fun1();
}
}
public class Demo01 {
public static void main(String[] args) {
Foo foo = new Foo();
foo.fun(() -> System.out.println("fun1()被调用"));
}
}
4.4.6 使用匿名内部类的注意事项
- 由于匿名内部类是接口的实现类对象(父类的子类对象), 因此使用匿名内部类时, 我们必须继承一个类或实现一个接口, 二者不可兼得, 注意不需要写extends或implements.
- 匿名内部类中不能定义构造方法.
- 匿名内部类中不能定义静态变量, 不能定义静态方法, 不能定义类.
- 匿名内部类不能是public、protected、private、static.
- 匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法
- 匿名内部类的大括号中可以有类能有的所有内容,匿名内部类是内部类的简写,是类的简写.
但是匿名内部类的本质是接口的实现类对象.
4.5 在外部类中new内部类对象
- 如果想在外部类中创建内部类的对象, 必须在
内部类定义之后的位置(内部类所在的括号或内部类所在括号的下级括号中)
创建该内部类的对象.
class Out {
// 成员内部类
class In {
}
// 在成员位置创建成员内部类的对象
In in = new In();
// 在局部局部创建成员内部类的对象
public void fun1() {
In in = new In();
}
// 在局部位置创建成员内部类的对象
public void fun2() {
if (true) {
In in = new In();
}
}
}
class Out {
public void fun1() {
// 局部内部类
class In {
}
// 在局部位置创建局部内部类
In in = new In();
}
}
4.6 内部类和外部类中存在同名成员时的访问方式
package cn.king.demo01;
class Out {
int a = 30;
class In {
int a = 60;
public void fun1() {
int a = 90;
System.out.println(a); // 90
System.out.println(this.a); // 60
System.out.println(Out.this.a); // 30
}
}
// 调用内部类中的fun1()方法
public void invokeFun1ForInner() {
new In().fun1();
}
}
public class Demo01 {
public static void main(String[] args) {
new Out().invokeFun1ForInner();
}
}