Java内部类详解

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修饰的情况.

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();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值