专栏首页小菜良记打好Java基础,从使用内部类开始!

打好Java基础,从使用内部类开始!

大家好,我是小菜,一个渴望在互联网行业做到蔡不菜的小菜。可柔可刚,点赞则柔,白嫖则刚!「死鬼~看完记得给我来个三连哦!」

“本文主要介绍 Java中内部类的用法 如有需要,可以参考 如有帮助,不忘 「点赞」 ❥ 微信公众号已开启,「小菜良记」,没关注的小伙伴记得关注哦! ”

今天又周五了呀,正在想明天周六有啥安排的时候,一声惊讶声打断了我

小蔡小菜,你看看这组代码,好灵活啊

听到领桌小王的惊讶,我扭头看了下他的屏幕,这不就是内部类么。用的好当然就灵活啦,只是我们平常没怎么用。

内部类用的好真的好灵活呀,我对这一块还不是很熟悉,看来还得多学习学习!小菜,看你的样子好像挺了解的,你能给我讲讲吗?

看着小王如饥似渴的眼色,我不由有点心虚,内心活动也是极其复杂:我平时也没咋用,只是有个大概的了解,讲出来不就献丑了,连忙声道:

好说好说,不过今天都周五了,也不差这一时半会,咱们还是想想明天有啥活动,等下周来我再给你好好讲讲!

小王仿佛被我忽悠过去了,也没看到我眼神中的慌乱,答应了下来。

好在有惊无险,周末还能有啥安排,赶紧把内部类安排上!

初识

比起面向对象编程中其他的概念来,接口和内部类更深奥复杂,比如 C++ 就没有这些。将两者结合起来,可以解决 C++ 中用多重继承所能解决的问题,然后,多重继承在 C++ 中被证明是相当难以使用的,相比较而言,Java 的接口和内部类就容易理解多了!

一、内部类如何创建

内部类,顾名思义就是类中类,将类定义在外围类里面:

public class Animal {

    class Monkey{
        private String name = "monkey";

        public String getName() {
            return name;
        }
    }

    class Pig {
        private String color;

        Pig(String color) {
            this.color = color;
        }

        String getColor() {
            return color;
        }
    }

    public void getAnimal(String note) {
        Monkey monkey = new Monkey();
        Pig pig = new Pig(note);
        System.out.println(pig.getColor());
    }

    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.getAnimal("pink");
    }
}
/* OUTPIT:
pink
*/

因为MonkeyPig两个类是定义在 Animal 类中,因此使用起这两个内部类跟使用普通类没什么区别。下面这组代码相信小伙伴也不陌生:

public class Animal {

    class Monkey{
    }

    class Pig {
    }

    public Monkey getMonkey() {
        return new Monkey();
    }

    public Pig getPig() {
        return new Pig();
    }

    public static void main(String[] args) {
        Animal animal = new Animal();
        Animal.Monkey monkey = animal.getMonkey();
        Animal.Pig pig = animal.getPig();
    }
}

通过定义方法,来返回执行内部类的引用。不知道细心的小伙伴有没有注意到内部类的引用有点奇怪:Animal.Monkey。这也是内部类的区别之一,如果要在外部类的非静态方法之外获取某个内部类的对象,需要「具体指明这个对象的类型」OuterClassName.InnerClassName

二、内外相连

内部类存在于外部类里层,因此也具有一定特权:内部类可以访问外围对象的所有成员,不需要任何特殊条件,此外,内部类还拥有外部类的所有元素的访问权。

public class OuterArray {
    private Integer[] ints;
    private int next = 0;

    public OuterArray(int size) {
        ints = new Integer[size];
    }

    public void add(int x) {
        if (next < ints.length) {
            ints[next++] = x;
        }
    }

    class InnerArray {
        private int i = 0;

        public boolean end() {
            return i == ints.length;
        }

        public int current() {
            return ints[i];
        }

        public void next() {
            if (i < ints.length) {
                i++;
            }
        }
    }

    public static void main(String[] args) {
        OuterArray outerArray = new OuterArray(10);
        for (int i = 0; i < 10; i++) {
            outerArray.add(i);
        }
        InnerArray innerArray = outerArray.new InnerArray();
        while (!innerArray.end()) {
            System.out.print(innerArray.current()+" ");
            innerArray.next();
        }
    }
}

上组代码中我们可以看到,InnerArray可以访问到OuterArray中的每一个属性,就像自己拥有它们一样,这带来了很大的方便。

三、new 和 this

这两个关键字我们肯定都不陌生了,我们平时用到最多的肯定就是new一个对象出来。

public class OuterClass {

    class InnerClass {
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
    }
}

当我们需要OuterClass对象的时候,我们顺手就来了个new OuterClass(),但是如果我们需要的是InnerClass对象,那么又该如何处理呢?答案便是:

InnerClass inner = outer.new InnerClass();

可能觉得有点奇怪,为什么此处的new需要以OuterClass对象引用,这是因为内部类对象会暗暗地连接到创建它的外部类对象上,因此必须使用外部类的对象来创建内部类对象。如果你创建的是「嵌套类」(静态内部类),那么它就不需要对外部类对象的引用。

this关键字是用来生成对外部类对象的引用,这样产生的引用自动具有正确的类型:

public class OuterClass {

    class InnerClass {
        public OuterClass getOuterClass() {
            return OuterClass.this;
        }
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        InnerClass inner = outer.new InnerClass();
        OuterClass outerClass = inner.getOuterClass();
    }
}

四、局部内部类

我们上面看到的内部类都是定义在外部类中,这也是内部类的典型用处。但是,我们也可以在一个方法里面或者任意的作用域里面定义内部类。这种也被称为局部内部类

public class OuterClass {

    public Animal getPig(String color) {
        class Pig extends Animal {
            @Override
            public void getAnimal(String color) {
                super.getAnimal(color);
            }
        }
        return new Pig();
    }

    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        Animal pink = outerClass.getPig("pink");
    }
}

Pig类是getPig()方法的一部分,而不是OuterClass的一部分,所以在getPig()之外不能访问Pig类。

五、匿名内部类

在了解什么是匿名内部类之前,我们先看一组代码:

public class OuterClass {

    public Animal animal() {
        return new Animal(){
            private String name = "monkey";
            @Override
            public String toString() {
                return "animal{" +
                        "name='" + name + '\'' +
                        '}';
            }
        };
    }

    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        System.out.println(outerClass.animal());
    }
}
/* OUTPUT:
animal{name='monkey'}
*/

animal()这个方法将返回值的生成与表示这个返回值的类定义结合在一起。而且这个类是匿名的,它没有名字,正常形式应该是这样的:

public class OuterClass {

    class Monkey extends Animal {
        private String name = "monkey";

        @Override
        public String toString() {
            return "animal{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    
    public Animal animal() {
        return new Monkey();
    }
}

匿名类再访工厂:

public interface Service {
    void method1();
}
interface ServiceFactory{
    Service getService();
}

class Implementation1 implements Service {

    private Implementation1(){}

    @Override
    public void method1() {
        System.out.println("Implementation1.method1()");
    }

    public static ServiceFactory factory = new ServiceFactory() {
        @Override
        public Service getService() {
            return new Implementation1();
        }
    };
}

class Factories{
    public static void main(String[] args) {
        ServiceFactory factory = Implementation1.factory;
        Service service = factory.getService();
        service.method1();
    }
}

通过内部类获取外部类的实现,这样子Implementation1的构造器都可以是private的,并且没有任何必要去创建作为工厂的具体类,这样所产生的语法也更具有实际意义,也可以运用在单例模式中。

六、嵌套类

如果不需要内部类对象与外围类之间有联系,就可以将内部类声明为static,这通常称为嵌套类。普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象,然而,当内部类是static的时候,就意味着:

  • 要创建嵌套类的对象,并不需要其外围类的对象
  • 不能从嵌套类的对象中访问非静态的外围类对象
public class NestClass {
    
    static class InnerNestClass{
    }

    public static InnerNestClass get() {
        return new InnerNestClass();
    }

    public static void main(String[] args) {
        InnerNestClass innerNestClass = get();
    }
}

main()方法中没有任何NestClass对象是必须的,而是使用选取static成员的普通语法来调用方法。

接口内部类

正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分。你放到接口中的任何类都自动是publicstatic的。因为类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外部类的接口:

public interface ClassInterface {

    void test();

    class Test implements ClassInterface {

        @Override
        public void test() {
            System.out.println("接口中的嵌套类");
        }

        public static void main(String[] args) {
            new Test().test();
        }
    }
}

如果你想要的创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便,尽管在 Java 8 之后可以使用 default 来默认实现接口方法。

七、继承内部类

内部类作为一种类,被继承当然也是被允许的。但是因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,那个指向外围类对象的引用必须被初始化,而在导出类中不再存在可连接的默认对象:

可以看到,通过这样继承是会报错的,解决方法便是:

class ExtendClass {
    class Inner{}
}

class WithInner extends ExtendClass.Inner {
    public WithInner(ExtendClass extendClass) {
        extendClass.super();
    }
}

因此我们需要记住,如果要继承一个内部类的时候,必须在构造器内使用外部类.super(),这样才能提供了必要的引用,然后程序才能编译通过。

八、覆盖内部类?

当子类继承父类时,子类可以覆盖父类的方法。那么问题来了,内部类能否被覆盖?我们通过看一组代码来找找答案:

public class Flower {

    class Bud{
        public Bud(){
            System.out.println("Flower.Bud");
        }
    }

    public Flower(){
        System.out.println("new Flower()");
        new Bud();
        test();
    }

    public void test() {
        System.out.println("Flower.test()");
    }
}

class Flower2 extends Flower{

    class Bud{
        public Bud(){
            System.out.println("Flower2.Bud");
        }
    }
    public void test() {
        System.out.println("Flower2.test()");
    }

    public static void main(String[] args) {
        new Flower2();
    }
}
/* OUTPUT
new Flower()
Flower.Bud
Flower2.test()
*/

从这个例子中我们可以看到,当继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化,这两个内部类是完全独立的两个实体,各自在自己的命名空间内。

九、为什么要使用内部类?

我们在回答这个问题之前先明白一件事情:

「每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响」

这句话很清楚的说明了内部类的能力,如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决,从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,为内部类有效地实现了"多重继承"

呼~ 终于把内部类复习的差不多了,乍看时间,今天都周末了呀!看来周末又没安排计划咯,不过这周过的还挺充实的,把基础巩固了一下,周一的时候还可以跟小王好好唠唠,想到这里,小菜不禁又陷入无限的幻想!

看完不赞,都是坏蛋

“今天的你多努力一点,明天的你就能少说一句求人的话! 我是小菜,一个和你一起学习的男人。 ? 微信公众号已开启,「小菜良记」,没关注的同学们记得关注哦! ”

本文分享自微信公众号 - 小菜良记(gh_0df6fdc8d3e4),作者:蔡不菜丶

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-09-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【Spring-IOC】你需要掌握的注解版开发!

    大家好,我是小菜,一个渴望在互联网行业做到蔡不菜的小菜。可柔可刚,点赞则柔,白嫖则刚!死鬼~看完记得给我来个三连哦!

    蔡不菜丶
  • 伙计,来跟我一起学SpringBoot! 【第一弹】

    YAML(YAML Ain't Markup Language) 标记语言,以数据为中心,比 json 、xml 等更适合做配置文件

    蔡不菜丶
  • Quartz 是什么?一文带你入坑

    quartz 是一款开源且丰富特性的**“任务调度库”,能够集成与任何的java** 应用,下到独立应用,大到电子商业系统。quartz就是基于java实现的任...

    蔡不菜丶
  • 从源码的角度搞懂 Java 动态代理!

    最近,看了一下关于RMI(Remote Method Invocation)相关的知识,遇到了一个动态代理的问题,然后就决定探究一下动态代理。

    Java技术栈
  • Java-内部类

    如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象(在静态方法内部创建某个内部类的对象),那么必须像在main()方法中那样,具体地指明这个对象的类型...

    桑鱼
  • Java编程思想第五版(On Java8)(十一)-内部类

    内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。在...

    JavaEdge
  • Java 泛型进阶

    在 List<String> 中添加 Integer 将不会通过编译,但是List<Sring>与List<Integer>在运行时的确是同一种类型。

    yuxiaofei93
  • java学习day15---UDP,reflect,proxy

    1.UDP udp是一种不安全的协议 容易丢失数据 不需要建立连接 效率高 数据是以数据包的形式发送 数据 IP 端口

    曼路
  • The Clean Architecture in PHP 读书笔记(二)

    设计模式是对软件中通用问题的总结,有了设计模式,方便我们进行交流,譬如一说MVC,我们就知道是怎么回事了,不然我们必须巴拉巴拉一大堆话去描述,不易于传播、交流,...

    zhuanxu
  • SpringMVC 中的Annotated Controllers

    上面透漏一下信息:是一个GET响应 查找web下的index模版,通过model将数据传递给模版引擎渲染

    大话swift

扫码关注云+社区

领取腾讯云代金券