首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【JavaSE】类与对象 && this引用 && 封装 && static && 代码块 && 内部类

【JavaSE】类与对象 && this引用 && 封装 && static && 代码块 && 内部类

原创
作者头像
lirendada
发布2026-03-07 15:52:11
发布2026-03-07 15:52:11
1020
举报
文章被收录于专栏:JavaJava

Ⅰ. 类的定义与使用

  • 类名注意采用大驼峰定义。
  • 一般一个文件只定义一个类
  • main 方法所在的类一般要使用 public 修饰(注意:Eclipse 软件默认会在 public 修饰的类中找 main 方法)
  • public 修饰的类必须要和文件名相同
  • 不要轻易去修改 public 修饰的类的名称,如果要修改,通过开发工具修改,即在 idea 中点击源文件的 Refactor 中的 Rename 选项。
代码语言:javascript
复制
class WashMachine {
    public String brand;  // 品牌
    public String type;   // 型号
    public double weight; // 重量
    public double lenght; // 长
    public double weidth; // 宽
    public double height; // 高
    public String color;  // 颜色
    
    public void WashClothes(){
        System.out.println("洗衣功能");
    }
    public void dryClothes(){
        System.out.println("脱水功能");
    }
    public void SetTime(){
        System.out.println("定时功能");
    }
}

采用 Java 语言将洗衣机类在计算机中定义完成,经过 javac 编译之后形成 .class 文件,在 JVM 的基础上计算机就可以识别了。

Ⅱ. 类的实例化

假设以洗衣机的代码为例来进行实例化:

代码语言:javascript
复制
public static void main(String[] args) {
    WashMachine wmach = new WashMachine();
    wmach.WashClothes();
    wmach.dryClothes();
    wmach.SetTime();
}

// 运行结果
洗衣功能
脱水功能
定时功能

🍁注意事项:

  • new 关键字用于创建一个对象的实例
  • 使用 . 来访问对象中的属性和方法
  • 类中只有成员变量占用空间,而方法不占用类的空间,并且方法是共享的。
  • 只有实例化出的对象才占用物理空间
  • 成员变量未初始化的话:
    • 如果是类类型,那么默认初始值为 null
    • 如果是内置类型,那么默认初始值为各内置类型的默认初始值,比如 int0booleanfalse 等等。
代码语言:javascript
复制
// 该例子为上面的洗衣机例子
public static void main(String[] args) {
    WashMachine wmach = new WashMachine();
    
    System.out.println(wmach.brand);
    System.out.println(wmach.height);
}

// 运行结果
null
0.0

Ⅲ. this 引用

一、概念

this 引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成

☘️引用的三种使用场景:

  1. this.成员变量
  2. this.成员方法
  3. this():访问构造方法

有了 this 引用的概念,我们来解决上面遗留的问题:

代码语言:javascript
复制
public class TestDate {
    public int year;
    public int month;
    public int day;
    
    // 这里赋值的显式强调要赋值的那个是this的而不是形参
    public void setDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
    
    // 也可以这样直接指明变量是this的,不指明也行,编译器默认添加
    public void printDate() {
        System.out.println(this.year + "-" + this.month + "-" + this.day);
    }
    
    public static void main(String[] args) {
        TestDate d1 = new TestDate();
        d1.printDate();
        
        d1.setDate(2022, 10, 7);
        d1.printDate();
    }
}

// 运行结果
0-0-0
2022-10-7

注意:this 引用的是调用成员方法的对象

二、特性

  1. this的类型:哪个对象调用就是哪个对象的引用类型。
  2. this只能在成员方法中使用
  3. 在成员方法中,this 只能引用当前对象,不能再引用其他对象
  4. this是成员方法第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this 负责来接收。
  5. 一般情况下,一般建议把 this 写上,比较规范。

在代码层面来简单演示,注意:下图右侧中的 Date 类也是可以通过编译的:

Ⅳ. 对象的构造以及初始化

一、构造方法

① 概念

构造方法(构造器)是一个特殊的成员方法,名字必须与类名相同。

在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次

代码语言:javascript
复制
public class Date {
    public int year;
    public int month;
    public int day; 
    
    // 构造方法:
    //     名字与类名相同,没有返回值类型,设置为void也不行
    //     一般情况下使用public修饰
    //     在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次
    public Date(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
        System.out.println("Date(int,int,int)方法被调用了");
    }
    
    public void printDate() {
        System.out.println(year + "-" + month + "-" + day);
    }
    
    public static void main(String[] args) {
        // 此处创建了一个Date类型的对象,并没有显式调用构造方法
        Date d = new Date(2021,6,9); // 输出Date(int,int,int)方法被调用了
        d.printDate(); // 2021-6-9
    }
}

② 特性

  1. 构造方法名必须与类名相同
  2. 没有返回值类型
  3. 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)
  4. 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)
  5. 如果用户没有显式定义,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的。
    1. 注意:一旦用户定义了任何构造方法,编译器则不再生成默认构造方法
  6. 绝大多数情况下使用 public 来修饰构造方法,特殊场景下会被 private 修饰(后序讲单例模式时会遇到)
  7. 构造方法中,可以通过 this() 调用其他构造方法来简化代码。
    代码语言:javascript
    复制
    public class Date {
        public int year;
        public int month;
        public int day; 
        
        // 无参构造方法--内部给各个成员赋值初始值,该部分功能与三个参数的构造方法重复 
        // 此处可以在无参构造方法中通过this调用带有三个参数的构造方法 
        // 但是this(1900,1,1);必须是构造方法中第一条语句
        public Date(){ 
            //System.out.println(year); 注释取消掉,编译会失败
            this(1900, 1, 1); 
        }
        
        // 带有三个参数的构造方法
        public Date(int year, int month, int day) {
            this.year = year; 
            this.month = month; 
            this.day = day; 
        } 
    }

☘️注意事项:

  • 显式调用 this(...) 的话,必须是构造方法中第一条语句
  • this() 不能在普通方法中使用,只能在构造方法中使用
  • 构造方法的调用不能形成环,也就是不能互相调用,因为会死循环,如下所示:
    代码语言:javascript
    复制
    public Date() { 
        this(1900,1,1); 
    }
    public Date(int year, int month, int day) { 
        this(); 
    }
    /*无参构造器调用三个参数的构造器,而三个参数构造器有调用无参的构造器,形成构造器的递归调用 
    编译报错:Error:(19, 12) java: 递归构造器调用
    */

二、默认初始化

为什么局部变量在使用时必须要初始化,而成员变量可以不用呢❓❓❓

要搞清楚这个过程,就需要知道 new 关键字背后所发生的一些事情:

代码语言:javascript
复制
Date d = new Date(2021,6,9);

在程序层面只是简单的一条语句,在 JVM 层面需要做好多事情,下面简单介绍下:

  1. 检测对象对应的类是否加载了,如果没有加载则加载
  2. 为对象分配内存空间
  3. 处理并发安全问题。比如多个线程同时申请对象,JVM 要保证给对象分配的空间不冲突
  4. 初始化所分配的空间。即对象空间被申请好之后,对象中包含的成员已经设置好了初始值
  5. 设置对象信息
  6. 调用构造方法,给对象中各个成员赋值

三、就地初始化🙅

在声明成员变量时,就直接给出了初始值:

代码语言:javascript
复制
public class Date {
    // 就地初始化
    public int year = 1900;
    public int month = 1;
    public int day = 1;
    public static void main(String[] args) {
        Date d1 = new Date();
    }
}

注意:代码编译完成后,编译器会将所有给成员初始化的这些语句添加到各个构造函数中。

Ⅴ. 封装

封装,简单来说就是套壳屏蔽细节,将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互

一、访问限定符

Java 中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用

Java 中提供了四种访问限定符:(包的概念下面会讲解)

☘️说明

  • default 权限是在什么都不写时的默认权限,并不需要指明的写出来!
  • 访问权限除了可以限定类中成员的可见性,也可以控制类的可见性。
代码语言:javascript
复制
public class Computer {
    private String cpu;    // cpu
    private String memory; // 内存
    public String screen;  // 屏幕
    String brand;          // 品牌---->default属性
    
    public Computer(String brand, String cpu, String memory, String screen){
        this.brand = brand;
        this.cpu = cpu;
        this.memory = memory;
        this.screen = screen;
    }
    
    public void Boot(){
        System.out.println("开机~~~");
    }
    public void PowerOff(){
        System.out.println("关机~~~");
    }
    public void SurfInternet(){
        System.out.println("上网~~~");
    }
}

public class TestComputer {
    public static void main(String[] args) {
        Computer p = new Computer("HW", "i7", "8G", "1314");
        System.out.println(p.brand);  // default属性:只能被本包中类访问
        System.out.println(p.screen); // public属性: 可以任何其他类访问
        //System.out.println(p.cpu); // private属性:只能在Computer类中访问,不能被其他类访问
    }
}
 
// 运行结果
HW
* 13*14

注意:一般情况下成员变量为 private,成员方法为 public

二、包

① 包的概念

为了更好的管理类,把多个类收集在一起成为一组,称为软件包。有点类似于目录。比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类。

Java 中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。

此外,包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可

② 导入包中的类

Java 中已经提供了很多现成的类供我们使用,例如 Date 类,使用 java.util.Date 即可使用:

代码语言:javascript
复制
public static void main(String[] args) {
    java.util.Date d = new java.util.Date(); // 显式导入类
    System.out.println(d.getTime());
}
 
// 运行结果
1665288560216

但是这种写法比较麻烦一些,可以使用 import 语句导入包

代码语言:javascript
复制
import java.util.Date;
public class Test {
    public static void main(String[] args) {
        Date date = new Date(); // 不需要显式导入类
        System.out.println(date.getTime());
    }
}

如果需要使用 java.util 中的其它类,可以使用 import java.util.* 来导入这些类

代码语言:javascript
复制
import java.util.*; // 导入util下所有类
public class Test {
    public static void main(String[] args) {
        Date date = new Date(); 
        System.out.println(date.getTime());
    }
}

但是因为不止 java.util 包中有 Date 类,所以我们最好在导包中的类的时候显式指定是哪个包的,否则还是容易出现冲突,这是个良好的编程习惯!如下所示:

代码语言:javascript
复制
import java.util.*;
import java.sql.*;
public class Test {
    public static void main(String[] args) {
        // util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
        Date date = new Date();
        System.out.println(date.getTime());
    }
}

// 编译出错
Error:(5, 9) java: 对Date的引用不明确
java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配

解决方法:使用完整的类名

代码语言:javascript
复制
import java.util.*;
import java.sql.*;
public class Test {
    public static void main(String[] args) {
        java.util.Date date = new java.util.Date(); // 指定util下的Date
        System.out.println(date.getTime());
    }
}

注意事项:Java 中的 import 不会将包含的文件都导入,而是用到哪个类则引入哪个

还有一种不太常见的用法:可以使用 import static 导入包中静态的方法和字段,但是不建议这么做!

代码语言:javascript
复制
import static java.lang.Math.*;
public class Test {
    public static void main(String[] args) {
        double x = 30;
        double y = 40;
        
        // 静态导入的方式写起来更方便一些.
        // double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
        double result = sqrt(pow(x, 2) + pow(y, 2));
        System.out.println(result);
    }
}

// 运行结果
50.0

③ 自定义包

  • 在文件的最上方加上一个 package 语句指定该代码在哪个包中。
  • 包名需要尽量指定成唯一的名字,通常会用公司的域名的颠倒形式(例如 com.liren.demo1
  • 包名要和代码路径相匹配。例如创建 com.liren.demo1 的包,那么会存在一个对应的路径 com/liren/demo1 来存储代码。
  • 如果一个类没有 package 语句,则该类被放到一个默认的包也就是 src 目录中。

操作步骤

  1. IDEA 中先新建一个包:右键 src -> 新建 -> 包。
  2. 在弹出的对话框中输入包名,例如 com.liren.demo1
  3. 在包中创建类,右键包名 -> 新建 -> 类,,然后输入类名即可。
  4. 此时可以看到我们的磁盘上的目录结构已经被 IDEA 自动创建出来了。
  5. 同时我们也看到了,在新创建的 Test.java 文件的最上方,就出现了一个 package 语句。

④ 包的访问权限控制

和上面是类似的访问限定符是一样的,只不过这里有了包的概念,但是都是大同小异的!

值得注意的是,一个包中是不能 import 默认包中的类的,但是可以 import 其他自定义或者已经存在的包中的类,这是因为从 J2SE 1.4 开始,Java 编译器不再支持 import 进未命包名的类、接口了,所以养成类都放到包里的习惯挺重要的

其实 非默认包是可以调用到默认包里的类的,这里要用到反射(反射后面会讲)。

java 中常见的包

⑥ 包的优缺点

  • 优点:
    • 提供了命名空间,避免了类名冲突。
    • 通过 Package 的访问修饰符来控制类的可见性。
  • 缺点:
    • Package 只能通过文件系统的目录结构来组织,无法在运行时动态创建或删除。
    • Package 的可见性受限于包含它的文件所在的目录,无法跨越多个目录

Ⅵ. static 成员

Java 中被 static 修饰的成员称为静态成员,其不属于某个具体的对象,而是由所有该类的对象所共享

静态成员不依赖于类的特定实例,被类的所有实例共享,就是说 static 修饰的方法或者变量不需要依赖于对象来进行访问,只要这个类被加载,JVM 就可以根据类名找到它们。

调用静态成员的语法形式如下:

代码语言:javascript
复制
类名.静态成员

🍁注意事项:

  • static 修饰的成员变量和方法从属于整个类,而普通变量和方法从属于独立的类对象。
  • 静态方法不能调用非静态成员
  • 不允许定义局部的静态变量,因为静态变量是与类相关联的,而不是与对象相关联的,所以在方法内部定义静态变量没有意义!

一、修饰成员变量

  • 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中。
  • 如果静态成员变量没有初始化的话,则为该类型的默认值
  • 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
  • 类变量、静态变量都存储在方法区当中。
  • 生命周期伴随类的一生,即随类的加载而创建,随类的卸载而销毁
代码语言:javascript
复制
public class TestStatic {
    public int age;
    static int age1 = 10;
    
    public static void main(String[] args) {
        TestStatic s1 = new TestStatic();
        TestStatic s2 = new TestStatic();
        TestStatic s3 = new TestStatic();
        
        System.out.println(s1.age1); // 通过s1对象访问
        System.out.println(s2.age1); // 通过s2对象访问
        System.out.println(s3.age1); // 通过s3对象访问
        
        System.out.println(TestStatic.age1); // 通过类名访问
    }
}

//运行结果
10
10
10
10

二、成员变量初始化

静态成员变量一般不会放在构造方法中来初始化,因为构造方法中初始化的是与对象相关的实例属性!

静态成员变量的初始化分为两种:「就地初始化」 和「静态代码块初始化」:

  1. 就地初始化:在定义时直接给出初始值
代码语言:javascript
复制
public static int count = 1;    // 定义静态变量count

2. 静态代码块初始化(下面讲)

三、修饰成员方法

  • 静态成员方法是类的方法,不是某个对象所特有的!
  • 「静态变量」一般是通过「静态方法」来访问的
  • 可以通过对象调用,但更推荐类名调用
  • 不能在静态方法中访问任何非静态成员变量 ,因此在静态方法中不能使用 this 关键字
  • 静态方法中不能调用任何非静态方法,因为非静态方法有 this 参数,在静态方法中调用时候无法传递 this 引用。
  • 静态方法不能重写也不能实现多态。(后续多态会详细讲解)
代码语言:javascript
复制
public class TestStatic {
    public static int count = 1; // 定义静态变量count
    
    public int method1() {
        count++;    // 访问静态变量count并赋值
        System.out.println("在静态方法 method1()中的 count="+count);         
        return count;
    }
    
    public static int method2() {
        count += count;    // 访问静态变量count并赋值
        System.out.println("在静态方法 method2()中的 count="+count);         
        return count;
    }
    
    public static void PrintCount() {
        count += 2;
        System.out.println("在静态方法 PrintCount()中的 count="+count);     
    }
    
    public static void main(String[] args) {
        TestStatic sft = new TestStatic();
        
        // 1. 通过实例对象调用实例方法
        System.out.println("method1() 方法返回值 intro1="+sft.method1());
        
        // 2. 直接调用静态方法
        System.out.println("method2() 方法返回值 intro1="+method2());
        
        // 3. 通过类名调用静态方法,打印 count
        TestStatic.PrintCount();
    }
}

// 运行结果
在静态方法 method1()中的 count=2
method1() 方法返回值 intro1=2
在静态方法 method2()中的 count=4
method2() 方法返回值 intro1=4
在静态方法 PrintCount()中的 count=6

Ⅶ. 代码块

使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字,又可分为以下四种:

  • 普通代码块
  • 构造块
  • 静态块
  • 同步代码块(后续多线程部分再谈)

一、普通代码块

普通代码块:定义在方法中的代码块,这种用法较少见!

代码语言:javascript
复制
public class TestStatic {
    public static void main(String[] args) {
        {
            // 直接使用{}定义,普通方法块
            int x = 10 ;
            System.out.println("x1 = " +x);
        }
        int x = 100;
        System.out.println("x2 = " +x);
    }
}

二、构造(实例)代码块

构造(实例)代码块:定义在类中的代码块(不需要加修饰符),一般用于初始化实例成员变量

实例代码块只有在对象创建后才会被执行,此外它先于类的构造方法执行,且每开辟一个新对象,实例代码块都会重新执行,这和后面讲的静态代码块是不同的!

代码语言:javascript
复制
public class Student {
    // 实例成员变量
    private String name;
    private String gender;
    private int age;
    private double score;

    public Student() {
        System.out.println("I am Student init()!");
    }

    // 实例代码块
    {
        this.name = "liren";
        this.age = 12;
        this.gender = "man";
        System.out.println("I am instance init()!");
    }

    public void show(){
        System.out.println("name: "+name+" age: "+age+" sex: "+gender);
    }

    public static void main(String[] args) {
        Student st1 = new Student();
        st1.show();
        System.out.println("----------------");
        Student st2 = new Student();
        System.out.println("----------------");
        Student st3 = new Student();
        System.out.println("----------------");
    }
}

// 运行结果
I am instance init()!
I am Student init()!
name: liren age: 12 sex: man
----------------
I am instance init()!
I am Student init()!
----------------
I am instance init()!
I am Student init()!
----------------

三、静态代码块

静态代码块指 Java 类中的 static{} 代码块,主要用于初始化类的静态变量,提升程序性能。它是Java 虚拟机在加载类时执行的,而不是创建对象之后才执行的(就类似于单例模式中的饿汉模式),这和上面的构造代码块是不一样的!

💄注意事项:

  • 静态代码块不管生成多少个对象,其只会执行一次
  • Java 虚拟机在加载类时执行静态代码块,所以很多时候会将一些只需要进行一次的初始化操作都放在 static 代码块中进行
  • 静态代码块类似于一个方法,但它不可以存在于任何方法体中
  • 类中可以有多个静态初始化块
  • 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行
  • 静态代码块与静态方法一样,静态代码块中不能直接访问类的实例变量和实例方法,而需要通过类的实例对象来访问。
代码语言:javascript
复制
public class Student {
    private String name;
    private String gender;
    private int age;
    private double score;
    private static String classRoom;

    // 实例代码块
    {
        this.name = "liren";
        this.age = 12;
        this.gender = "man";
        System.out.println("I am instance init()!");
    }

    // 静态代码块
    static {
        classRoom = "liren306";
        System.out.println("I am static init()!");
    }
    
    public Student(){
        System.out.println("I am Student init()!");
    }

    public static void main(String[] args) {
        System.out.println("--------------------------");
        Student s1 = new Student();
        
        System.out.println("--------------------------");
        Student s2 = new Student();
    }
}

// 运行结果
I am static init()!
--------------------------
I am instance init()!
I am Student init()!
--------------------------
I am instance init()!
I am Student init()!

Ⅷ. 内部类

在类内部可定义成员变量和方法,且在类内部也可以定义另一个类。如果在类 Outer 的内部再定义一个类 Inner,此时类 Inner 就称为内部类(或称为嵌套类),而类 Outer 则称为外部类(或称为宿主类)。

当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。内部类也是一种封装的体现~

内部类也可以分为多种形式,与变量非常类似,如图所示:(这个下面会讲)

💄注意事项:

  • 「内部类」与「外部类」不能重名
  • 「内部类」拥有「外部类」的所有元素的访问权限
  • 「内部类」可以很好地实现细节的隐藏,因为一般来说「非内部类」是不允许有 privateprotected 权限的,但「内部类」可以。
  • 虽然「内部类」和「外部类」共用同一个 java 源文件,但是经过编译之后,内部类会形成单独的字节码文件
  • 如果内部类声明成静态的,就不能随便访问外部类的非静态成员变量,但是仍然能访问外部类的静态成员变量。
代码语言:javascript
复制
public class OutClass {
    public int data1 = 1;
    int data2 = 2;
    private int data3 = 3;

    // 这里以后面要讲的实例内部类举例子
    class InnerClass {
        public int data4 = 4;
        int data5 = 5;
        private int data6 = 6;

        public int getSum() {
            return data4 + data5 + data6;
        }
    }

    // 方法3:在外部类中可以直接通过内部类的类名访问内部类
    InnerClass inAndOut = new InnerClass();
    public void printInSum() {
        System.out.println(inAndOut.getSum());
    }

    // 由于main是静态方法,所以无法向上边这种方法一样访问内部类
    // 但是我们可以通过内部类的完整类名: 外部类名.内部类名 -》访问内部类
    public static void main(String[] args) {
        // 方法1:直接通过OutClass()的类名来获取内部类
        OutClass.InnerClass in1 = new OutClass().new InnerClass();
        System.out.println(in1.getSum());

        // 方法2:通过OutClass()的对象来获取内部类(推荐)
        OutClass out = new OutClass();
        OutClass.InnerClass in2 = out.new InnerClass();
        System.out.println(in2.getSum());

        // 方法3:在外部类中可以直接通过内部类的类名访问内部类(看上面)
        out.printInSum();
    }
}

// 运行结果
15
15
15

一、实例内部类(非静态内部类)

实例内部类:没有用 static 修饰的内部类,有的地方也称为非静态内部类。

代码语言:javascript
复制
public class Outer { 
    class Inner {
        // 实例内部类
    }
}

☘️特点

  1. 在「外部类的静态方法」和「外部类以外的类」中,必须通过「外部类实例」才能创建「内部类实例
    代码语言:javascript
    复制
    public class OutClass {
        // 实例内部类,不需要创建外部类实例
        class InnerClass {
            InnerClass in = new InnerClass(); 
        }
    
        // 在外部类中直接定义,不需要创建外部类实例
        InnerClass in = new InnerClass(); 
    
        // 在非静态方法中定义,不需要创建外部类实例
        public void method1() {
            InnerClass in = new InnerClass(); 
        }
    
        // 在静态方法中定义,需要创建外部类实例
        public static void method2() {
            InnerClass in = new OutClass().new InnerClass(); 
        }
    }
    
    class OtherClass {
        // 需要创建外部类实例,需要创建外部类实例
        OutClass.InnerClass in1 = new OutClass().new InnerClass();
    }
  2. 实例内部类可以访问外部类的所有成员(如果有多层嵌套,则内部类可以访问所有外部类的成员)
  3. 在「外部类」中不能直接访问「内部类成员」,而必须通过「内部类实例」去访问。如果类 A 包含内部类 B,类 B 中包含内部类 C,则在类 A 中不能直接访问类 C,而应该通过类 B 的实例去访问类 C
  4. 在「实例内部类」方法中访问同名的成员时,优先访问自己的,如果要访问「外部类」同名的成员,必须:外部类名称.this.同名成员 来访问。比如「实例内部类B 与「外部类A 包含有同名的成员 t,则在类 Btthis.t 都表示 B 中的成员 t,而 A.this.t 表示 A 中的成员。
  5. 在「实例内部类」中不能定义 static 成员,除非使用 final 修饰。
  6. 实例内部类」所处的位置与「外部类成员」位置相同,因此也受 publicprivate 等访问限定符的约束。

二、静态内部类

静态内部类是指使用 static 修饰的内部类,如下所示:

代码语言:javascript
复制
public class Outer {
    static class Inner {
        // 静态内部类
    }
}

☘️特点

  1. 与实例内部类相反,在创建「静态内部类」的实例时,不需要先创建「外部类」的实例。
  2. 「静态内部类」中可以定义静态成员、实例成员。「外部类以外的类」需要通过完整的类名访问静态内部类中的静态成员,需要通过「静态内部类实例」访问静态内部类中的实例成员。
  3. 静态内部类可以直接访问外部类的「静态成员」,但如果要访问外部类的「实例成员」,则需要通过外部类的实例去访问
代码语言:javascript
复制
public class OutClass {
    // 静态内部类
    static class InnerClass {
        int a = 100;        // 实例变量a
        static int b = 250; // 静态变量b
    }

    public static void main(String[] args) {
        InnerClass in = new InnerClass(); // 不需要创建外部类的实例
    }
}
class OtherClass {
    public static void main(String[] args) {
        // 不需要创建外部类的实例
        OutClass.InnerClass in = new OutClass.InnerClass();
        int a1 = in.a;    // 通过对象访问实例成员
        int b1 = in.b;    // 通过对象访问实例成员
        int b2 = OutClass.InnerClass.b;    // 通过类名访问静态成员
    }
}

三、局部内部类(很少用,了解即可)

局部内部类是指在方法中定义的类。该种内部类只能在其定义的位置使用,一般使用的非常少,此处简单了解下语法格式。

代码语言:javascript
复制
public class Test {
    public void method() {
        class Inner {
            // 局部内部类
        }
    }
}

☘️特点:

  1. 局部内部类」与局部变量一样,不能使用访问控制修饰符(publicprivateprotected)和 static 修饰符修饰
  2. 局部内部类只在当前方法中有效
    代码语言:javascript
    复制
    public class Test {
        Inner i = new Inner();                    // ❌编译出错
        Test.Inner ti = new Test.Inner();         // ❌编译出错
        Test.Inner ti2 = new Test().new Inner();  // ❌编译出错
        
        public void method() {
            class Inner{
                // 局部内部类
            }
            Inner i = new Inner(); // 编译正确
        }
    }
  3. 在「局部内部类」中不能定义 static 成员
  4. 在「局部内部类」中可以访问「外部类」的所有成员
  5. 在「局部内部类」中只可以访问「当前方法」中 final 类型的参数与变量。如果方法中的成员与外部类中的成员同名,则可以使用 外部类名.this.变量名 的形式访问外部类中的成员。
    代码语言:javascript
    复制
    public class Test {
        int a = 0;
        int d = 0;
        public void method() {
            int b = 0;
            final int c = 0;
            final int d = 10;
            class Inner {
                int a2 = a;    // 访问外部类中的成员
                // int b2 = b;    // ❌编译出错
                int c2 = c;    // 访问方法中的成员
                int d2 = d;    // 访问方法中的成员
                int d3 = Test.this.d;    // 访问外部类中的成员
            }
            Inner i = new Inner();
            System.out.println(i.d2);    // 输出10
            System.out.println(i.d3);    // 输出0
        }
        public static void main(String[] args) {
            Test t = new Test();
            t.method();
        }
    }

四、匿名类与匿名内部类

匿名类是指没有类名的内部类必须在创建时使用 new 语句来声明类

代码语言:javascript
复制
new 类或接口名() {
    // 类的主体
};

这种形式的 new 语句声明一个新的匿名类,它对一个给定的类进行扩展,或者实现一个给定的接口。使用匿名类可使代码更加简洁、紧凑,模块化程度更高。匿名类有两种实现方式:

  1. 继承一个类,重写其方法。
  2. 实现一个或多个接口,实现其方法。
代码语言:javascript
复制
public class Out {
    void show() {
        System.out.println("调用 Out 类的 show() 方法");
    }
}

public class TestAnonymousInterClass {
    // 在这个方法中构造一个匿名内部类
    private void show() {
        Out anonyInter = new Out() {
            void show() {
                System.out.println("调用匿名类中的 show() 方法"); // 重写方法
            }
        };
        anonyInter.show();
    }
    
    public static void main(String[] args) {
        TestAnonymousInterClass test = new TestAnonymousInterClass();
        test.show();
    }
}

// 运行结果
调用匿名类中的 show() 方法

☘️特点:

  1. 「匿名类」和「局部内部类」一样,可以访问外部类的所有成员但如果匿名类位于一个方法中,则匿名类只能访问方法中 final 类型的局部变量和参数。(其实从 Java8 开始添加了 Effectively final 功能,在 Java 8 及以后的版本中代码第 6 行不会出现编译错误)
    代码语言:javascript
    复制
    public static void main(String[] args) {
        int a = 10;
        final int b = 10;
        Out anonyInter = new Out() {
            void show() {
                // System.out.println("调用了匿名类的 show() 方法"+a);    // ❌编译出错
                System.out.println("调用了匿名类的 show() 方法"+b);    // 编译通过
            }
        };
        anonyInter.show();
    }

2. 「匿名类」中允许使用「非静态代码块」进行成员初始化操作。

代码语言:javascript
复制
Out anonyInter = new Out() {
    int i; 
    
    // 非静态代码块,进行成员初始化
    {    
        i = 10;    
    }
    
    public void show() {
        System.out.println("调用了匿名类的 show() 方法"+i);
    }
};

3. 「匿名类」的「非静态代码块」会在父类的构造方法之后被执行。

4. 「匿名对象」可用于简化代码比如打印对象的属性,如下所示:

代码语言:javascript
复制
class Person {
    public int age;
    public String name;
}

public class Test {
    public static void main(String[] args) {
        Person p = new Person();
        System.out.println(Person.age);
        System.out.println(Person.name);
        
        // 直接使用匿名对象打印
        System.out.println(new Person().age);
        System.out.println(new Person().name);
    }
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Ⅰ. 类的定义与使用
  • Ⅱ. 类的实例化
  • Ⅲ. this 引用
    • 一、概念
    • 二、特性
  • Ⅳ. 对象的构造以及初始化
    • 一、构造方法
      • ① 概念
      • ② 特性
    • 二、默认初始化
    • 三、就地初始化🙅
  • Ⅴ. 封装
    • 一、访问限定符
    • 二、包
      • ① 包的概念
      • ② 导入包中的类
      • ③ 自定义包
      • ④ 包的访问权限控制
      • ⑤ java 中常见的包
      • ⑥ 包的优缺点
  • Ⅵ. static 成员
    • 一、修饰成员变量
    • 二、成员变量初始化
    • 三、修饰成员方法
  • Ⅶ. 代码块
    • 一、普通代码块
    • 二、构造(实例)代码块
    • 三、静态代码块
  • Ⅷ. 内部类
    • 一、实例内部类(非静态内部类)
    • 二、静态内部类
    • 三、局部内部类(很少用,了解即可)
    • 四、匿名类与匿名内部类
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档