java面向对象学习--内部类

什么是内部类

还有一种类,它被定义在另一个类的内部,所以称为内部类(Nested Class)。Java的内部类分为好几种,通常情况用得不多,但也需要了解它们是如何使用的。

内部类只能依附于一个类,哪怕是实例化也要依附另外一个类

// inner class
public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer("Nested"); // 实例化一个Outer
        Outer.Inner inner = outer.new Inner(); // 实例化一个Inner
        inner.hello();
    }
}

class Outer {
    private String name;

    Outer(String name) {
        this.name = name;
    }

    class Inner {
        void hello() {
            System.out.println("Hello, " + Outer.this.name);
        }
    }
}

Inner Class和普通Class相比,除了能引用Outer实例外,还有一个额外的“特权”,就是可以修改Outer Class的private字段,因为Inner Class的作用域在Outer Class内部,所以能访问Outer Class的private字段和方法。

匿名类(Anonymous Class)

// Anonymous Class
public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer("Nested");
        outer.asyncHello();
    }
}

class Outer {
    private String name;

    Outer(String name) {
        this.name = name;
    }

    void asyncHello() {
//这个run是定义在 java.lang.Runnable 接口中。所以Runnable是一个接口
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello, " + Outer.this.name);
            }
        };
        new Thread(r).start();
    }
}

关于Thread(r).start()的解释

这一行代码 new Thread(r).start(); 是 Java 多线程编程中最核心的启动动作。我们可以把它拆成两部分来看:

  1. new Thread(r) —— “雇佣一个员工并分配任务”
    Thread 类:它是 Java 提供的“线程”类。你可以把它想象成一个“工人”或一个“执行引擎”
    参数 r:就是你刚才定义的那个 Runnable 对象。Runnable 的字面意思是“可运行的任务”。
    组合在一起:这一步的意思是:创建一个新线程,并把我们写好的任务(r)交给它。
    这时候,线程虽然创建了,但它还没动,只是处于“就绪”状态。
  2. .start() —— “下达开工指令”
    这是最关键的一步。
    当你调用 start() 方法时,Java 虚拟机会向操作系统申请:“请给我分配一个新的 CPU 线程!”
    一旦申请成功,系统会立即开启一条全新的执行路径。
    自动调用 run():在新线程启动后,它会自动去执行你刚才在 Runnable 里覆写的那个 run() 方法。
    你可能会问,为什么不用r.run()
    如果直接写 r.run();:
    这只是一个普通的函数调用。代码会在当前线程(通常是主线程 main)里按顺序执行。程序会停下来等 run() 执行完了,才继续走后面的代码。这叫同步。
    使用 new Thread(r).start();:
    主线程(main)会瞬间执行完这一行,然后直接往下走,不管那个新线程。
    与此同时,新线程会在后台独立运行 run() 里的代码。这叫异步。

这个多线程有什么用?

比如一个网页:你点了一个文件下载,这个时候其他事你都做不了,如果没有多线程的话,
还有一个例子:你的电脑都是多核的,就好比有八个灶台,你只用一个,效率就低下了
看代码

void asyncHello() {
    Runnable r = new Runnable() {
        @Override
        public void run() {
            // 假设这里有一行需要运行 5 秒钟的代码
            System.out.println("Hello, " + Outer.this.name);
        }
    };
    new Thread(r).start(); // 开启新线程
    System.out.println("主线程结束了"); 
}

如果没有多线程的话,就是先打出hello...然后才有主线程结束了这个,但是是不是就是有了多线程就一定是先出现主线程结束了,再出现hello呢,答案是大概率是,因为这就像一个赛跑

java8及其以上高级写法

 void asyncHello() {
//这个run是定义在 java.lang.Runnable 接口中。所以Runnable是一个接口
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello, " + Outer.this.name);
            }
        };
        new Thread(r).start();
    }
//对于上面的代码,有一种更高级的写法,就是 Lambda 表达式
void asyncHello() {
    // 效果完全一样,不再需要显式写 run() 了,因为编译器知道 Runnable 只有这一个方法
    new Thread(() -> System.out.println("Hello, " + this.name)).start();
}
/*
对于这个写法,这个就有很多讲究了:
1.为什么没有了上面的Runnable实例r了,也没有方法run了?
Thread类的构造函数,如果去看Thread的API的话,会发现她有很多重载函数
Thread() (无参)
Thread(String)
Thread(Runnable target) (最常用的,就是你用的这种)
Thread(Runnable target, String name) (还可以给线程起个名字)
你这个Thread是这样写的Thread().start()里面就只有一个东西被传入了,所以排除了第一个和第四个
然后你传入的很明显不是字符串,所以指挥使第二个,也就是说,默认你传入的就是Runnable这个接口了,因为只能传入这个接口
然后你括号里面写的是这样的() -> System.out.println("Hello, " + this.name)可以知道你覆写的就是Runnable这个接口的run方法,Runnable这个接口就一个抽象方法就是run,因为run方法本身没有任何参数所以
前面那个括号是空的就很合理了
如果这个接口的唯一的方法是void a(String words){},那就要写成(words) -> System.out.println("Hello, " + this.name)注意,不是写成String,接口里定义了 void say(String words),当你写 Lambda 的时候,编译器已经通过“对暗号”知道了这个参数一定是 String。既然大家都心知肚明,Java 就允许你省略掉类型,直接写变量名。
如果是这样就不对了,例如
interface Study {
    void read();
    void write();
}
然后你Thread那个括号里是这样写的,这样就错了
Study s = () -> System.out.println("Doing something...")
天知道你要覆写的是哪一个方法,只能给你算一卦了,算错了你又不开心
*/

接上文,如果一个接口有多个抽象方法,那么怎么办

1.返璞归真,直接写匿名函数的形式

Study s = new Study() {
    @Override
    public void read() {
        System.out.println("我在读 Java 说明书");
    }

    @Override
    public void write() {
        System.out.println("我在写代码笔记");
    }
};

2.default大法好

interface Study {
    void read(); 
    
    // 用 default 修饰,给它一个默认的动作
    default void write() {
        System.out.println("默认不写东西");
    }
}

然后就可以用这个了

// 这个 Lambda 自动指向了唯一的抽象方法 read()
Study s = () -> System.out.println("我只读,不写");

3.哪来这么多奇技淫巧

interface Readable {
    void read();
}

interface Writable {
    void write();
}

顺便提一嘴,这种只有一个抽象方法的接口就是SAM接口
直接拆分成两个只有独立抽象方法的接口,然后美美用上面的写法

好像扯的太远了,我们继续讲匿名类

和内部类一样,匿名类也可以访问所在类的private字段
除了接口外,匿名类也完全可以继承自普通类。观察以下代码:

// Anonymous Class
import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        HashMap<String, String> map1 = new HashMap<>();
        HashMap<String, String> map2 = new HashMap<>() {}; // 匿名类!
        HashMap<String, String> map3 = new HashMap<>() {
            {
                put("A", "1");
                put("B", "2");
            }
        };
        System.out.println(map3.get("A"));
    }
}

静态内部类

// Static Nested Class
public class Main {
    public static void main(String[] args) {
        Outer.StaticNested sn = new Outer.StaticNested();
        sn.hello();
    }
}

class Outer {
    private static String NAME = "OUTER";

    private String name;

    Outer(String name) {
        this.name = name;
    }

    static class StaticNested {
        void hello() {
            System.out.println("Hello, " + Outer.NAME);
        }
    }
}

可以访问类的private,且不再依附于外部类

posted @ 2026-04-21 18:55  Time_q  阅读(2)  评论(0)    收藏  举报