从零学编程--Java 内部类

简介: 从零学编程--Java 内部类

Java 内部类


前言


怎么解决浮躁?

去看历史,去看宇宙

人是多么渺小,浮躁又有什么用?

2021/8/23,编程的学习继续奥里给!!!


目标


  1. 什么是内部类
  2. 内部类的分类作用
  3. 内部类如何定义
  4. 如何实例化以及各自的特点
  5. 要注意区分不同类型内部类的异同
  6. 为什么需要内部类


1. 概念


在 Java 语言中,可以将一个类定义在另一个类里面或者一个方法里面,我们把这样的类称为内部类。

与之对应的,包含内部类的类被称为外部类。请阅读下面的代码:


// 外部类 Car
public class Car {
    // 内部类 Engine
    class Engine {
        private String innerName = "发动机内部类";
    }
}

代码中,Engine 就是内部类,而 Sky 就是外部类。


2. 分类


Java 中的内部类可以分为 4 种:

成员内部类

静态内部类

方法内部

匿名内部类


2.1 成员内部类


2.1.1 定义


成员内部类也称为普通内部类,它是最常见的内部类。可以将其看作外部类的一个成员。在成员内部类中无法声明静态成员。

如下代码中声明了一个成员内部类:

// 外部类 Car
public class Car {
    // 内部类 Engine
    private class Engine {
        private void run() {
            System.out.println("发动机启动了!");
        }
    }
}


我们在外部类 Sky 的内部定义了一个成员内部类 Engine,在 Engine 下面有一个 fly() 方法,其功能是打印输出一行字符串:“发动机启动了!”。

另外,需要注意的是,与普通的 Java 类不同,含有内部类的类被编译器编译后,会生成两个独立的字节码文件:


Car$Engine.class
Car.class


内部类 Engine 会另外生成一个字节码文件,其文件名为:外部类类名 $ 内部类类名.class


2.1.2 实例化


内部类在外部使用时,无法直接实例化,需要借助外部类才能完成实例化操作。关于成员内部类的实例化,有 3 种方法:

  1. 我们可以通过 new 外部类().new 内部类() 的方式获取内部类的实例对象:

实例演示

// 外部类 Car
public class Car {
    // 内部类 Engine
    private class Engine {
        private void run() {
            System.out.println("发动机启动了!");
        }
    }
    public static void main(String[] args) {
        // 1.实例化外部类后紧接着实例化内部类
        Engine engine = new Car().new Engine();
        // 2.调用内部类的方法
        engine.run();
    }
}

运行结果:


发动机启动了!
  1. 我们可通过先实例化外部类、再实例化内部类的方法获取内部类的对象实例:
public static void main(String[] args) {
    // 1.实例化外部类
    Car car = new Car();
    // 2.通过外部类实例对象再实例化内部类
    Engine engine = car.new Engine();
    // 3.调用内部类的方法
    engine.run();
}

编译执行,成功调用了内部类的 fly () 方法:


$javac Car.java
java Car
发动机启动了!
  1. 我们也可以在外部类中定义一个获取内部类的方法 getEngine(),然后通过外部类的实例对象调用这个方法来获取内部类的实例:

实例演示

package com.caq.oop.demo10;
public class Outer {
    private int id = 10;
    public void out(){
        System.out.println("这是外部类的方法");
    }
    //一个java类中可以有多个class类,但只能有一个public class
    public class In {
        public void in() {
            System.out.println("这是内部类的方法");
        }
    }
    public static void main(String[] args) {
        //实例化内部类,调用内部类的方法
        In In = new Outer().new In();
        In.in();
    }
}

运行结果:


这是内部类的方法

这种设计在是非常常见的,同样可以成功调用执行 fly() 方法。


2.1.2 成员的访问


成员内部类可以直接访问外部类的成员,例如,可以在内部类的中访问外部类的成员属性:

实例演示

// 外部类 Sky
public class Sky {
    String name;
    public Engine getEngine() {
        return new Engine();
    }
    // 内部类 Engine
    private class Engine {
        // 发动机的起动方法
        private void fly() {
            System.out.println(name + "的发动机启动了!");
        }
    }
    public static void main(String[] args) {
        // 实例化外部类
        Sky Sky = new Sky();
        // 为实例属性赋值
        Sky.name = "张三";
        // 获取内部类实例
        Engine engine = Sky.getEngine();
        // 调用内部类的方法
        engine.fly();
    }
}

观察 Enginefly() 方法,调用了外部类的成员属性 name,我们在主方法实例化 Sky 后,已经为属性 name 赋值。

运行结果:


大奔奔的发动机启动了!

相同的,除了成员属性,成员方法也可以自由访问。这里不再赘述。

还存在一个同名成员的问题:如果内部类中也存在一个同名成员,那么优先访问内部类的成员。可理解为就近原则。

这种情况下如果依然希望访问外部类的属性,可以使用外部类名.this.成员的方式,例如:

实例演示

// 外部类 Sky
public class Sky {
    String name;
    public Engine getEngine() {
        return new Engine();
    }
    // 汽车的跑动方法
    public void fly(String name) {
        System.out.println(name + "跑起来了!");
    }
    // 内部类 Engine
    private class Engine {
        private String name = "引擎";
        // 发动机的起动方法
        private void fly() {
            System.out.println("Engine中的成员属性name=" + name);
            System.out.println(Sky.this.name + "的发动机启动了!");
            Sky.this.fly(Sky.this.name);
        }
    }
    public static void main(String[] args) {
        // 实例化外部类
        Sky Sky = new Sky();
        // 为实例属性赋值
        Sky.name = "张三";
        // 获取内部类实例
        Engine engine = Sky.getEngine();
        // 调用内部类的方法
        engine.fly();
    }
}


运行结果:

Engine中的成员属性name=引擎
张三的发动机启动了!
大奔奔跑起来了!

请观察内部类 fly() 方法中的语句:第一行语句调用了内部类自己的属性 name,而第二行调用了外部类 Sky 的属性 name,第三行调用了外部类的方法 fly(),并将外部类的属性 name 作为方法的参数。


2.2 静态内部类


2.2.1 定义


静态内部类也称为嵌套类,是使用 static 关键字修饰的内部类。如下代码中定义了一个静态内部类:

public class Car1 {
    // 静态内部类
    static class Engine {
        public void run() {
            System.out.println("我是静态内部类的run()方法");
            System.out.println("发动机启动了");
        }
    }
}


2.2.2 实例化


静态内部类的实例化,可以不依赖外部类的对象直接创建。我们在主方法中可以这样写:

// 直接创建静态内部类对象
Engine engine = new Engine();
// 调用对象下run()方法
engine.run();


运行结果:

我是静态内部类的run()方法
发动机启动


2.2.2 成员的访问


在静态内部类中,只能直接访问外部类的静态成员。例如:

实例演示

public class Sky1 {
    String brand = "宝马";
    static String name = "外部类的静态属性name";
    // 静态内部类
    static class Engine {
        public void fly() {
            System.out.println(name);
        }
    }
    public static void main(String[] args) {
        Engine engine = new Engine();
        engine.fly();
    }
}


fly() 方法中,打印的 name 属性就是外部类中所定义的静态属性 name。编译执行,将会输出:

外部类的静态属性name

对于内外部类存在同名属性的问题,同样遵循就近原则。这种情况下依然希望调用外部类的静态成员,可以使用外部类名.静态成员的方式来进行调用。这里不再一一举例。

如果想要访问外部类的非静态属性,可以通过对象的方式调用,例如在 fly() 方法中调用 Sky1 的实例属性 brand

public void run() {
    // 实例化对象
    Car1 car1 = new Car1();
    System.out.println(car1.brand);
}


2.3 方法内部类


2.3.1 定义


方法内部类,是定义在方法中的内部类,也称局部内部类。

如下是方法内部类的代码:

实例演示

public class Sky2 {
  // 外部类的fly()方法
    public void fly() {
        class Engine {
            public void fly() {
                System.out.println("方法内部类的fly()方法");
                System.out.println("发动机启动了");
            }
        }
        // 在Sky2.fly()方法的内部实例化其方法内部类Engine
        Engine engine = new Engine();
        // 调用Engine的fly()方法
        engine.fly();
    }
    public static void main(String[] args) {
        Sky2 Sky2 = new Sky2();
        Sky2.fly();
    }
}

运行结果:

方法内部类的run()方法
发动机启动了

如果我们想调用方法内部类的 fly() 方法,必须在方法内对 Engine 类进行实例化,再去调用其 fly() 方法,然后通过外部类调用自身方法的方式让内部类方法执行。


2.3.2 特点


与局部变量相同,局部内部类也有以下特点:

  • 方法内定义的局部内部类只能在方法内部使用;
  • 方法内不能定义静态成员;
  • 不能使用访问修饰符。

也就是说,Sky2.getEngine() 方法中的 Engine 内部类只能在其方法内部使用;并且不能出现 static 关键字;也不能出现任何的访问修饰符,例如把方法内部类 Engine 声明为 public 是不合法的。


2.4 匿名内部类


2.4.1 定义


匿名内部类就是没有名字的内部类。使用匿名内部类,通常令其实现一个抽象类或接口。

实例演示

// 定义一个交通工具抽象父类,里面只有一个fly()方法
public abstract class Transport {
    public void fly() {
        System.out.println("交通工具fly()方法");
    }
    public static void main(String[] args) {
        // 此处为匿名内部类,将对象的定义和实例化放到了一起
        Transport Sky = new Transport() {
            // 实现抽象父类的fly()方法
            @Override
            public void fly() {
                System.out.println("汽车跑");
            }
        };
        // 调用其方法
        Sky.fly();
        Transport airPlain = new Transport() {
            // 实现抽象父类的fly()方法
            @Override
            public void fly() {
                System.out.println("飞机飞");
            }
        };
        airPlain.fly();
    }
}


运行结果:

汽车跑
飞机飞

上述代码中的抽象父类中有一个方法 fly(),其子类必须实现,我们使用匿名内部类的方式将子类的定义和对象的实例化放到了一起,通过观察我们可以看出,代码中定义了两个匿名内部类,并且分别进行了对象的实例化,分别为 SkyairPlain,然后成功调用了其实现的成员方法 fly()


2.4.2 特点


  • 含有匿名内部类的类被编译之后,匿名内部类会单独生成一个字节码文件,文件名的命名方式为:外部类名称$数字.class。例如,我们将上面含有两个匿名内部类的 Transport.java 编译,目录下将会生成三个字节码文件:


Transport$1.class
Transport$2.class
Transport.class

  • 匿名内部类没有类型名称和实例对象名称;
  • 匿名内部类可以继承父类也可以实现接口,但二者不可兼得;
  • 匿名内部类无法使用访问修饰符、staticabstract 关键字修饰;
  • 匿名内部类无法编写构造方法,因为它没有类名;
  • 匿名内部类中不能出现静态成员。


2.4.2 使用场景


由于匿名内部类没有名称,类的定义可实例化都放到了一起,这样可以简化代码的编写,但同时也让代码变得不易阅读。当我们在代码中只用到类的一个实例、方法只调用一次,可以使用匿名内部类。


3. 作用


3.1 封装性


内部类的成员通过外部类才能访问,对成员信息有更好的隐藏,因此内部类实现了更好的封装。


3.2 实现多继承


我们知道 Java 不支持多继承,而接口可以实现多继承的效果,但实现接口就必须实现里面所有的方法,有时候我们的需求只是实现其中某个方法,内部类就可以解决这些问题。

下面示例中的 SonClass,通过两个成员内部类分别继承 FatherClass1FatherClass2,并重写了方法,实现了多继承:

// FatherClass1.java
public class FatherClass1 {
    public void method1() {
        System.out.println("The FatherClass1.method1");
    }
}
// FatherClass2.java
public class FatherClass2 {
    public void method2() {
        System.out.println("The FatherClass2.method2");
    }
}
// SonClass.java
public class SonClass {
  // 定义内部类1
    class InClass1 extends FatherClass1 {
        // 重写父类1方法
        @Override
        public void method1() {
            super.method1();
        }
    }
    // 定义内部类2
    class InClass2 extends FatherClass2 {
        // 重写父类2方法
        @Override
        public void method2() {
            super.method2();
        }
    }
    public static void main(String[] args) {
        // 实例化内部类1
        InClass1 InClass1 = new SonClass().new InClass1();
        // 实例化内部类2
        InClass2 InClass2 = new SonClass().new InClass2();
        // 分别调用内部类1、内部类2的方法
        InClass1.method1();
        InClass2.method2();
    }
}

编译执行 SonClass.java,屏幕将会打印:

$ javac SubClass.java
$ java SonrClass
The SuperClass1.method1
The SuperClass1.method2


3.3 解决继承或实现接口时的方法同名问题


请阅读如下代码:

// First.java
public class First {
    public void test() {
    }
}
// Second.java
public interface Second {
    void test();
}
// Demo.java
public class Demo1 extends First implements Second {
    public void test() {
    }
}

此时,我们无法确定 Demo1 类中的 test() 方法是父类 First 中的 test 还是接口 Second 中的 test。这时我们可以使用内部类解决这个问题:

public class Demo2 extends First {
    // 重写父类方法
    @Override
    public void test() {
        System.out.println("在外部类实现了父类的test()方法");
    }
    // 定义内部类
    class InClass implements Second {
        // 重写接口方法
        @Override
        public void test() {
            System.out.println("在内部类实现了接口的test()方法");
        }
    }
    public static void main(String[] args) {
        // 实例化子类Demo2
        Demo2 demo2 = new Demo2();
        // 调用子类方法
        demo2.test();
        // 实例化子类Demo2的内部类
        InClass InClass = demo2.new InClass();
        // 调用内部类方法
    InClass.test();
    }
}

运行结果:

在外部类实现了父类的test()方法
在内部类实现了接口的test()方法

相关文章
|
8天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
14天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
7天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
6天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
9天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
15天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
12天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
14天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
36 2
|
15天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
15天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
42 1
下一篇
无影云桌面