java中的内部类

1. 内部类概述

1.1 含义:如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类。

1.2 例如:身体和心脏的关系、汽车和发动机的关系

1.3 分类:

  1. 成员内部类
  2. 局部内部类(包含匿名内部类)

2. 成员内部类的定义

2.1 成员内部类的定义格式

修饰符 class 外部类名称{
    修饰符 class 内部类名称{
        //.....
    }
    //......
}

2.2 注意:

  1. 内部类使用外部类的成员,随便访问;但是外用内,需要内部类对象

2.3 代码演示:外部类和内部类大概长什么样子

  • 来一个 Body类 包含 Heart类 的,就是身体和心脏的包含关系嘛
//Body 类作为内部类
public class Body {

    //Heart 类作为成员内部类
    public class Heart{
        //内部类方法
        public void heartMethod(){
            System.out.println("我是心脏,内部类成员方法");
            System.out.println("我叫" + name); //可以调用外部类的 name
        }
    }

    //外部类成员变量
    private String name = "haha";

    //外部类方法
    public void bodyMethod(){
        System.out.println("我是身体,外部类成员方法");
        
    }

    //外部类的getter
    public String getName() {
        return name;
    }

    //外部类的setter
    public void setName(String name) {
        this.name = name;
    }
}
  • 从代码可以看到,内部类Heart是可以调用外部类的成员的,并且内部类Heart在文件夹中的点class文件是Body$Heart.class,在内部类中‘$’符号是有特殊含义的。

    [外链图片转存失败(img-dpOvovCk-1564136694827)(C:\Users\STF\AppData\Roaming\Typora\typora-user-images\1564056143553.png)]

3. 成员内部类的使用

3.1 两种方式:

  1. 间接方式:在外部类的方法当中,使用内部类;然后main只要调用外部类的方法,就能间接使用到内部类

  2. 直接方式,和这个平常用的【类名称 对象名 = new 类名称();】差不多,多了一层而已,它是:

    【外部类名称.内部类名称 对象名 = new 外部类名称().内部类名称();】

3.2 方式1:间接方式

  • 在外部类的方法当中,使用内部类;然后main只要调用外部类的方法,就能间接使用到内部类。把上面的Body类代码搬下来,只加多一句代码(有注释的那一行)
public class Body {

    public class Heart{
        public void heartMethod(){
            System.out.println("我是心脏,内部类成员方法");
            System.out.println("我叫" + name); //可以调用外部类的 name
        }
    }

    private String name = "haha";

    public void bodyMethod(){
        System.out.println("我是身体,外部类成员方法");
        //就是这句代码,在外部类方法中,新建了这个匿名内部类,并顺便调用内部类的方法heartMethod()
        new Heart().heartMethod();  
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 在main中调用外部类方法bodyMethod()完成对内部类的间接使用
public class Demo07Main {
    public static void main(String[] args) {
        Body body = new Body();
        body.bodyMethod(); //该方法里面新建了一个内部类,并调用了内部类的方法
    }
}
/*输出结果:
    我是身体,外部类成员方法
    我是心脏,内部类成员方法
    我叫haha
 */

3.3 方式2:直接公式

【外部类名称.内部类名称 对象名 = new 外部类名称().内部类名称();】

上面的Body类代码不变,在main中我们这样写

public class Demo07Main {
    public static void main(String[] args) {
        //公式:【外部类名称.内部类名称 对象名 = new 外部类名称().内部类名称();】
        Body.Heart heart = new Body().new Heart();
        heart.heartMethod();
    }
}

4. 内部类的同名变量访问

假如内部类和外部类都有一个成员变量叫num,那怎么区分它们呢?

看下面代码注释就明白了

  • 来一个Outer类,代表外部类;里面有一个Inner类,代表内部类

    public class Outer {
    
        int num = 10;  //外部类的成员变量
    
        public class Inner{
    
            int num = 20; //内部类的成员变量
    
            public void methodInner(){
                int num = 30;    //内部类方法的局部变量num = 30
                System.out.println(num);  //局部变量,就近原则
                System.out.println(this.num);  //内部类的成员变量num = 20
                System.out.println(Outer.this.num); //外部类的成员变量num = 10
            }
        }
    }
    
  • 在main中调用内部类Inner的方法methodInner,看看能不能正确地区分不同的num

    public class Demo07Main_ {
        public static void main(String[] args) {
            //【外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称()】
            Outer.Inner inner = new Outer().new Inner();
            //用内部类对象 inner 调用方法 innerMethod
            inner.methodInner();
        }
    }
    /*输出结果:
        30
        20
        10
     */
    

5. 局部内部类定义

如果一个类是定义在一个方法内部的,那么这就是一个局部内部类

5.1 “局部”的概念:只有当前所属的方法才能使用它,出了这个方法外面就不能用了,这个方法外面的是“外部”

5.2 定义格式:

修饰符 class 外部类名称{
    修饰符 返回值类型 外部类方法名称(参数列表){
        class 局部内部类名称{
            //....
        }
    }
}

5.3 和成员内部类有什么区别

成员内部类在方法外面,局部内部类在方法里面

局部内部类一旦走出了方法外面,就会失去作用,和局部变量一样

5.4 局部内部类里面的方法怎么使用

  • 局部内部类里面的方法怎么使用,通过外部类方法来启动它

    public class Outer {
    
        //这是一个外部类方法
        public void methodOutter(){
    
            //在外部类方法里面创建一个类Inner,class前面是没有关键字的
            class Inner{
    
                int num = 30;
    
                public void methodInner(){  //这个类里面也有一个方法,为局部内部类方法
                    System.out.println(num);
                }
            }
    
            //在这里新建一个局部内部类对象,启动它的methodInner方法
            //当 methodOutter 方法运行时,methodInner 方法也会被连带运行
            Inner inner = new Inner();
            inner.methodInner();
        }
    }
    
  • 在main中启动外部类方法,外部类方法里面的局部内部类方法也会被启动

    public class Demo07Main_ {
        public static void main(String[] args) {
            Outer outer = new Outer();
            outer.methodOutter();  //30,这是局部内部类里面的成员num
        }
    }
    

5.5 内部类的权限问题

权限排名:public > protected > (default) > private

定义一个类的时候,权限修饰符规则:

  1. 外部类:public / (default) 这两个都可以
  2. 成员内部类:public / protected / (default) / private
  3. 局部内部类:什么都不要写,因为只有成员才有权限,局部内部类不算外部类的成员,只有那个方法才能访问它。

6. 局部内部类的final问题

局部内部类如果希望访问所在方法的局部变量,那么这个局部变量必须是【有效的final】

备注:从java8+开始,只要局部变量事实不变,那么final关键字可以省略

请注意下面代码中的那个错误的写法,因为num = 20之后,又把它重新赋值num = 10,所以报错,为什么?

原因:生命周期的问题

  1. new出来的对象是在堆内存中的,所以inner对象是在堆内存中的
  2. 局部变量num是跟着方法走的,在栈内存当中,当方法运行结束了,局部变量就会立马出栈,然后消失
  3. 但是new出来的对象并不会随着方法的结束而消失,因为它是在堆中,会持续存在,直到垃圾回收
  4. 那问题来了,num消失了,inner对象还没消失,那inner里面的methodInner()方法还怎么访问num呀?inner对象会提前将num复制一遍存好,num就算消失也无碍,但前提是要保证num这个值事实不变,如果变了,inner对象也不知该存哪个好,所以报错。
public class Outer {
    
    public void methodOutter(){
        //final int num = 20;   正确写法
    /*  int num = 20;
        num = 10          错误写法,num = 20之后,不可以再重新赋值为10*/
    
        int num = 20;  //正确写法
        class Inner{
            public void methodInner(){  
                System.out.println(num);  //打印num
            }
        }
        Inner inner = new Inner();
        inner.methodInner();
    }
}

7. 匿名内部类(重点)

7.1 知识回顾:如何使用接口

一般要使用到接口的时候,我们会先写一个接口的实现类,然后重写实现类里面的抽象方法,最后创建实现类对象并通过对象去调用方法。用代码来回顾一下这个经典的流程

  • 创建一个接口叫MyInterface,里面有一个抽象方法叫method()

    public interface MyInterface {
        public abstract void method();
    }
    
  • 创建一个MyInterface接口的实现类叫 Impl ,重写实现类里面的抽象方法method

    public class Impl implements MyInterface {
        @Override
        public void method() {
            System.out.println("创建了实现类,并重写了抽象方法");
        }
    }
    
  • 最后在main中创建实现类对象,通过对象去调用重写的method方法,至此,完成对接口的使用

    public class Demo08Main {
        public static void main(String[] args) {
            Impl impl = new Impl();
            impl.method();  //创建了实现类,并重写了抽象方法
        }
    }
    

7.2 假如我们只想要使用一次接口,又不想像上面那么麻烦,就可以使用匿名内部类进行操作。

使用【匿名内部类】的时候,我们可以省略掉该类的定义

7.2.1 匿名内部类的定义格式:

//【匿名内部类】
接口名称 对象名 = new 接口名称(){
	//覆盖重写所有抽象方法
};  // 这个大括号{}里面的就是一个内部类,并且没有名字,俗称匿名内部类

或者我们省略了对象名称,直接new就行

//【匿名对象】
new 接口名称(){  // 这个大括号{}里面的就是一个内部类,并且没有名字,俗称匿名内部类
	//覆盖重写所有的抽象方法
}.方法名();

7.2.2 还是用上面的MyInterface接口,我们不需要再创建一个实现类,在main中重写接口的method方法

public class Demo08Main {
    public static void main(String[] args) {
        //方式1:使用匿名内部类的方法重写接口 MyInterface 中的method 方法, 【匿名内部类】
        MyInterface obj = new MyInterface() {
            @Override
            public void method() {
                System.out.println("实现了抽象方法method,接口对象不省略");
            }
        };
        //用 MyInterface 接口的对象调用 method 方法
        obj.method();


        //方式2:使用匿名内部类的方法重写接口 MyInterface 中的method 方法, 【匿名对象】
        new MyInterface(){
            @Override
            public void method() {
                System.out.println("实现了抽象方法method,省略接口的对象");
            }
        }.method();  //在这里直接调用重写的 method 方法
    }
}

7.2.3 注意:

  1. 【匿名内部类】,在【创建对象】的时候,只能创建一个对象,如果希望多次创建对象,而且类的内容一样的话,那么就必须使用单独定义的实现类了。

  2. 【匿名对象】,在【调用方法】的时候,只能调用唯一次方法。如果希望同一个对象,调用多次方法,那么就必须给对象起个名字。

  3. 【匿实名内部类】是省略了【实现类/子类名称】,但是【匿名对象】是省略了【对象名称】。强调;匿名内部类和匿名对象不是一回事,有区别!!!

8. 类作为成员变量

比如我创建一个武器类Weapon,然后再创建一个英雄类Hero,武器类是英雄类里面的一个成员变量

  • Weapon类

    public class Weapon {
        //武器代号
        private String code;
    
        public Weapon() {
        }
    
        public Weapon(String code) {
            this.code = code;
        }
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    }
    
  • Hero类

    //英雄类
    public class Hero {
        //英雄的名字
        private String name;
        //英雄使用的武器
        private Weapon weapon;
    
        public Hero() {
        }
    
        public Hero(String name, Weapon weapon) {
            this.name = name;
            this.weapon = weapon;
        }
    
        //给英雄一个攻击的功能
        public void attach(){
            System.out.println(this.getName() + "英雄正在用" + weapon.getCode() + "武器攻击对方" );
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Weapon getWeapon() {
            return weapon;
        }
    
        public void setWeapon(Weapon weapon) {
            this.weapon = weapon;
        }
    }
    
  • 在main中创立英雄对象和武器对象,并把武器对象装备到英雄对象上

    public class DemoHero {
        public static void main(String[] args) {
            //创建一个英雄对象
            Hero hero = new Hero();
            hero.setName("IronMan");
    
            //创建一个武器给英雄,叫AK-47吧
            Weapon weapon = new Weapon();
            weapon.setCode("AK-47");
    
            //给英雄装备上AK-47
            hero.setWeapon(weapon);
    
            //让英雄上战场
            hero.attach();  //IronMan英雄正在用AK-47武器攻击对方
        }
    }
    

9. 接口作为成员变量

接口作为成员变量和类作为成员变量,其实两个都差不多,也都没有复杂

创建一个英雄类Hero,和一个名为技能的接口Skill,Skill里面有各种技能(抽象方法),现在把接口Skill作为英雄类的成员变量,在main中创建英雄对象并通过它来使用技能。

  • Hero类

    public class Hero {
        //英雄的名字
        private String hero;
        //英雄使用的技能
        private Skil skil;
    
        public Hero() {
        }
    
        public Hero(String name, Skil skil) {
            this.hero = name;
            this.skil = skil;
        }
    
        //给英雄一个使用技能的方法
        public void useSkill() {
            System.out.println(this.getHero() + "英雄正在使用" + "技能");
            skil.use();
        }
    
        public String getHero() {
            return hero;
        }
    
        public void setHero(String hero) {
            this.hero = hero;
        }
    
        public Skil getSkil() {
            return skil;
        }
    
        public void setSkil(Skil skil) {
            this.skil = skil;
        }
    }
    
  • Skill接口

    public interface Skill {
        //使用技能,是一个抽象方法
        public abstract void use();
    }
    
  • Skill接口的实现类SkillImpl类

    public class SkillImpl implements Skill {
        @Override
        public void use() {
            System.out.println("Biu...Biu...Biu");
        }
    }
    
  • 在main中创建英雄对象,设置英雄对象的技能,使用技能

    public class Demo09Main {
        public static void main(String[] args) {
            //创建一个英雄
            Hero hero = new Hero();
            //英雄的名字叫艾希
            hero.setHero("艾希");
    
            //设置该英雄的技能,,这里把实现类对象当做接口传进去,编译器会自动向上转型
            hero.setSkill(new SkillImpl());
            //英雄开始使用技能
            hero.useSkill();
    
    
            System.out.println("------------------------");
    
    
            //我们还可以使用匿名内部类的写法重写Skill接口中的方法
            Skill skill = new Skill() {
                @Override
                public void use() {
                    System.out.println("再来Biu...Biu...Biu");
                }
            };
            //给英雄设置技能
            hero.setSkill(skill);
            //英雄开始使用技能
            hero.useSkill();
    
    
            System.out.println("------------------------");
    
    
            //我们还可以使用匿名对象的方法,不用创建一个SkillImpl的实现类
            hero.setSkill(new Skill() {
                @Override
                public void use() {
                    System.out.println("还来Biu...Biu...Biu");
                }
            });
            //英雄开始使用技能
            hero.useSkill();
        }
    }
    

10. 接口作为方法的参数

我们就以ArrayList作为例子

ArrayList<T>类是List<T>接口的实现类

请看一下代码,写一个方法叫addNames,参数正是List接口

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        //多态的写法,左边接口,右边实现类
        List<String> list = new ArrayList<>();

        //调用addNames方法
        list = addNames(list);

        //打印 list 中添加的名字
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        /* 输出结果:
            周杰伦
            权志龙
            贾斯汀比伯
         */
    }

    public static List<String> addNames(List<String> list){
        list.add("周杰伦");
        list.add("权志龙");
        list.add("贾斯汀比伯");
        return list;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值