带你深入学习java内部类_匿名类,细到不能再细~

简介: 带你深入学习java内部类_匿名类,细到不能再细~

内部类

内部类初识

内部类顾名思义就在类的内部中的类!


我们知道,类中可以有两种重要的成员,成员变量(字段/属性)和方法(行为),实际上java还允许类有一种成员——内部类!

java支持在一个类中定义另一个类,这样的类就称为内部类,而包含内部类的类称为内部类的外嵌类!


内部类是类的第5大成员

类的5大成员:


属性,方法,构造器,代码块,内部类


内部类最大特点:

可以访问类中的私有属性,体现类和类的包含关系!


内部类是java一个重点和难点,并且在java底层代码中有很多内部类的使用!


嵌套:


嵌套指的是一种类之间的关系,而不是对象之间的关系。外嵌类对象并不会包含内部类类型的子对象!


基础语法

class 外嵌类类名 {
    class 内部类类名 {
    } 
}

外嵌类和内部类的之间的关系:


1.外嵌类中的成员变量内部类中仍然有效,外嵌类方法可以在内部类中的方法中调用!

class People{
    protected String name;
    protected int age;
    public void speak(){
        System.out.println(name+":speak()");
    }
    class Child{
        //内部类可以访问外嵌类中的成员变量!
        String name = People.this.name;
        public void speak1(){
            speak(); //在内部类方法中调用外嵌类中的方法!
        }
        public void speak(){
            People.this.speak();//当内部类中的方法和外嵌类方法名相同时
                 //通过类名.this.方法名调用外嵌类同名方法
            // speak();
        }
    }
}

2.内部类的类体中不能声明类变量和类方法

//内部类中不含类变量和类方法!
class People{
    class Child{
        static String sex; //error
       public static void eat(){  //error
           System.out.println("eat::()!");
       }
    }
}

image.png

因为我们知道stataic修饰的类变量和类方法属于类,类加载时便一起加载了,而外嵌类加载完并不会加载内部类,而static类型的变量和方法在类加载时会初始化,这两者就会导致内部类未加载,但其成员却初始化了,这是矛盾的,所以内部类中的变量和方法不能被static关键字修饰!


3.外嵌类的类体中可以用内部类声明的对象作为外嵌类的成员。

class People{
    //外嵌类中声明内部类对象
    Child child = new Child();
    public void speak(){
        System.out.println();
    }
    class Child{
        String sex;
       public void eat(){
           System.out.println("eat::()!");
       }
    }
}

4.内部类仅提供他的外嵌类使用,其他类不可以用某个类的内部类声明对象

class People{
    Child child = new Child();
    public void speak(){
        child.eat(); //内部类对象只能在外嵌类中使用!
        System.out.println();
    }
    class Child{
       public void eat(){
           System.out.println("eat::()!");
       }
    }
}
public class Test_1 {
    public static void main(String[] args) {
        People.Child child = new People.Child();//error
        //内部类无法在其他类中创建对象 
    }
}

image.png



5.内部类可以用protected和private修饰,而一般类只能用public修饰!

class People{
    public void speak(){
        System.out.println();
    }
    private class Child{ //内部类可用private和protect修饰
       public void eat(){
           System.out.println("eat::()!");
       }
    }
}
 protect class A{ //error 一般类只能用public 修饰
}

image.png


6.当外嵌类和内部类中成员变量或方法同名时,相互使用或调用语法规则

//在外嵌类中调用内部类中的同名的方法!
class People{
    public void eat(){
        System.out.println("People eat::()!");
    }
    public void speak(){
        //People的eat
        eat();
        this.eat();
        People.this.eat();
        System.out.println("======");
        //Child的eat
        new Child().eat();
    }
    private class Child{ 
       public void eat(){
           System.out.println("Child eat::()!");
       }
    }
}
public class Test_1 {
    public static void main(String[] args) {
        People people = new People();
        people.speak();//调用谁的eat()?
    }
}

image.png

在内部类中可以通过类名.this.方法名调用外嵌类中同名方法!


7.内部类的字节码文件名字和通常的类不同,内部类字节码文件名字格式是:外嵌类类名$内部类类名.class

image.png

内部类分类

我们刚刚大概了解了内部类的语法和使用,可能有点绕,没有捋清楚,不要急,bug郭带你仔细学一遍!

根据内部类在外部类中的位置可以分为4种:


局部内部类,定义在外部类中的局部范围(方法和代码块中)

匿名内部类(学习的重点和难点)也通常定义在局部

成员内部类(定义在外部类的成员位置)

静态内部类(用stataic修饰的内部类)

我会一一给大家详细介绍这四种内部类的使用方法和细节!


局部内部类

在外嵌类的局部位置,通常在方法中或代码块中!


局部内部类特点:

1.定义在外部类局部(方法或者代码块中)

2.作用域在局部,只能在他的作用域中使用(方法或者代码块中)

3.本质还是一个类(可以被继承)

4.属于局部变量,不能用限定符修饰(可用finall修饰后不可被继承)

5.内部类可以直接访问外部类中的属性和方法(包括private修饰)

6.外部类访问局部内部类,只能创建内部类对象访问(且只能在该局部中)

7.当外部类和内部类重名时,遵守就近原则


//局部内部类
class Outer{//外部类
    private int m = 10; //属性
    private static String s = "Outer";
    public void f1(){ //方法
        System.out.println("Outer::f1");
    }
    public void f2(){//方法
        /*
        局部内部类:
        1.定义在外部类局部(方法或者代码块中)
        2.作用域在局部,只能在他的作用域中使用(方法或者代码块中)
        3.本质还是一个类(可以被继承)
        4.属于局部变量,不能用限定符修饰(可用finall修饰后不可被继承)
        5.内部类可以直接访问外部类中的属性和方法(包括private修饰)
        6.外部类访问局部内部类,只能创建内部类对象访问(且只能在该局部中)
        7.当外部类和内部类重名时,遵守就近原则
         */
        class Inner {//内部类
            private int a = 12;//属性
            public void f2(){//方法
                System.out.println("Inner::f2");
                f1(); //局部内部类直接访问外部类成员和方法
                System.out.println("Outer:private m="+m+" Inner::a="+a);
            }
        }
        //外部类只能通过创建外部类对象访问内部类
        Inner inner = new Inner();
        inner.f2();//方法同名就近原则
        class Inner1 extends Inner {//继承Inner类
        }
    }
}
public class Test_1{
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.f2();
    }
}

image.png


因为有前面学习的基础,bug郭就不一一列举代码了,可以自行尝试!


匿名类

我们是否想过一个问题,内部类既然只能在外嵌类中使用,显然当我们只需要使用该内部类一次时,常规方法创建一个内部类比较麻烦,价值不高,有其他方法解决该问题嘛?

那就是匿名类!

匿名类是不能有名字的类,它们不能被引用,只能在创建时用 new 语句来声明它们。


匿名类特点:

1.本质是一个类

2.没有名字的类

3.属于内部类

4.还是个对象


匿名类语法格式:

new 类名或接口(参数列表){
    //类体
};

匿名类使用演示:

//代码演示
class Outer2{ //外部类
    private int a = 1;
    void f1(IA ia){ //方法,参数实现向上转型
      ia.eat(); //调用重写方法
    }
}
interface IA{
    void eat();
}
//传统方式
class A implements IA{//我们要创建一个类实现IA接口才能创建对象
    @Override//在类中重写接口中的方法
    public void eat() {
        System.out.println("传统eat...");
    }
}
public class Test_2 {
    public static void main(String[] args) {
        Outer2 outer2 = new Outer2();
        outer2.f1(new A()); //传统方式需要创建A对象
        outer2.f1(new IA() {//匿名类方式,在new的时候重写方法即可
            //类体
            @Override
            public void eat() {
                System.out.println("匿名eat...");
            }
        });
    }
}

image.png

与子类有关的匿名类

假如没有显式地声明一个类的子类,但又想用这个子类创建对象,那么该如何实现这一目的呢?


java允许用户直接使用一个类的子类的类体创建一个子类的对象,也就是说,在创建子类对象时,除了使用父类的构造方法外还有类体,此类体被认为是一个子类去掉类声明后的类体,称作匿名类。匿名类就是一个子类,由于无名可以,所以不可能用匿名类声明对象,当可以直接用匿名类创建一个对象。


语法规则:

//1.方式一
new 类名(){
    //匿名类类体
}
//2.方式二 
//使用了父类提供的(带参数的)构造方法
new 类名(参数){
    //匿名类类体
}

匿名类特点:

1.匿名类可以继承父类的方法,也可也重写父类的方法。

2.在使用匿名类时,必然存在某个类中直接用匿名类创建对象,因此匿名类一定是内部类。

3.匿名类可以访问外嵌类中的成员变量和方法。在匿名类的类体中不可以声明stataic成员变量或方法。

4.由于匿名类是一个子类,且没有类名,所以在用匿名类创建对象时要直接使用父类的构造方法。

5.尽管匿名类创建对象没有经过类声明的步骤,但匿名对象的引用可以传递给一个匹配的参数。

//实例一
abstract class Bank{
    int money;
    public Bank(){
        money = 100;
    }
    public Bank(int money){
        this.money = money;
    }
    public abstract void output();
}
class ShowBank{
    void showMess(Bank bank){ //参数是Bank类型
        bank.output();
    }
}
public class Test_2 {
    public static void main(String[] args) {
        ShowBank showBank = new ShowBank();
        showBank.showMess(new Bank() { //向参数传递Bank的匿名子类对象
            @Override
            public void output() {
                money += 100;
                System.out.println("bug郭 银行:"+money);
            }
        });
        showBank.showMess(new Bank(500){向参数传递Bank的匿名子类对象
            @Override
            public void output() {
                money += 100;
                System.out.println("bug郭 银行:"+money);
            }
        });
    }
}

image.png

可以看到与子类有关的匿名类的使用,当我们只需要使用子类对象一次时,我们可以使用匿名类对象!


与接口有关的匿名类

与接口有关的匿名类和与子类有关的匿名类类似!

当某个方法的参数是接口类型时,那么可以使用接口名和类体组合创建一个匿名对象传递方法的参数,类体必须重写接口中的全部方法。


interface SpeakHello{
    void speak();
}
class HelloMachine{
    public void turnOn(SpeakHello hello){
        hello.speak();
    }
}
public class Test_3 {
    public static void main(String[] args) {
        HelloMachine machine = new HelloMachine();
        machine.turnOn(new SpeakHello() { //和接口有关的匿名类
            @Override
            public void speak() {
                System.out.println("hello,you are welcom!");
            }
        });
        machine.turnOn(new SpeakHello() {//和接口有关的匿名类
            @Override
            public void speak() {
                System.out.println("你好,欢迎光临!");
            }
        });
    }
}

image.png


匿名类小测试

题目:

1有一个铃声接口 Bell,里面有个 ring 方法。

2.有一个手机类Cellphone,具有闹钟功能 alarmClock,参数是Bell类型

3.测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了

4.再传入另一个匿名内部类(对象),打印:小伙伴上课了

答案:

public class Test_3{
    public static void main(String[] args) {
        CellPhone cellPhone = new CellPhone();
        cellPhone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("懒猪起床了");
            }
        });
        cellPhone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("小伙伴上课了");
            }
        });
    }
}
interface Bell{ //接口
    void ring();//方法
}
class CellPhone {//类
    public void alarmClock(Bell bell) {//形参是 Bell 接口类型
        System.out.println(bell.getClass());
        bell.ring();//动态绑定
    }
}

image.png


匿名类深入了解

匿名类真的就没有名字嘛?

错,只是我们不知道名字而已,就像匿名信一样,写信者肯定有名字的!其实在java底层jdk中我们java匿名类系统会给他定义一个名字的!

image.png

我们可以看jdk系统自动给匿名类取名为:调用匿名类的类类名$1


总结:

我们什么时候可以用到匿名类呢?

当我们只需要使用一次该类的子类对象或者使用一次该接口对象时我们就可以使用匿名类!

匿名类在创建对象时,构建!

采用 new 类名(接口名){匿名类体};实现!


匿名类通常当做实参直接传递,简洁高效!


用Lambda表达式代替匿名类

我们先来了解一下什么是Lambda表达式~


函数接口和Lambda表达式

函数接口

如果一个接口中有且只有一个abstract方法,称这样的接口是单接口。从JDK 8开始,java使用Lambda表达式,并将单接口称为函数接口。


Lambda表达式

下面add()是一个通常的方法(函数):


int add(int a,int b){
  retunrn a + b;
}

Lambda表达式就是一个用匿名方法(函数),用Lambda表达式表达同样功能的匿名方法:

//省略函数名
(int a,int b)->{
return a+b;
}

(a,b)->{
return a+b;
}

即Lambda表达式就是只写参数列表和方法体的匿名方法(参数列表和方法体之间用符号->连接)

//语法规则
(参数列表)->{
  //方法体
}

Lambda表达式的值

由于Lambda表达式过于简化,所以必须有特殊上下文,编译器才能推断出Lambda表达式到底是哪个方法,才能计算值,Lambda表达式的值就是方法的入口地址。因此,java中的Lambda表达式主要用在单接口,即函数接口!


使用举例:

interface ShowMessage{ //函数接口
    void show(String str);
}
public class Test_4 {
    public static void main(String[] args) {
        //接口变量中存放Lambda表达式的值
       ShowMessage sm = (s)->{ //Lambda表达式(匿名方法)
           System.out.println("java yyds!");
           System.out.println(s);
           System.out.println("hond on!");
       };
       sm.show("bug郭"); //接口回调Lambda表达式实现接口方法
    }
}

image.png

因为是匿名方法,当我们要使用该接口创建一个对象时,就可以通过Lambda表达式实现接口方法,创建对象,接口回调该方法!


大概了解了Lambda表达式,我们就可以用Lambda表达式代替匿名类了

就是在使用匿名类时,当我们要实现接口函数(单接口中的方法)时,用Lambda表达式非常便利!

interface ShowMessage{ //函数接口
    void show();
}
class Test{
    public void test(ShowMessage message){
        message.show();
    }
}
public class Test_4 {
    public static void main(String[] args) {
        Test test = new Test();
        test.test(()->{ //向形参message传递Lambda表达式的值
            System.out.println("java yyds!");
        });
    }
}

image.png


成员内部类

顾名思义:成员内部类应该是在外部类成员位置上


特点:

1.定义在外部类成员位置上

2.可以使用访问修饰限定符修饰(成员可以,变量不行)

3.没有stataic修饰


//代码演示
 class Outer{//外部类
    private int a = 10;
    public void f1(){
        System.out.println("Outer:f1");
    }
    class Inner{//成员内部类
        private int b = 14;
        protected void f2(){
            System.out.println("inner::f2");
        }
    }
    public Inner getInner(){ //实例方法获取内部类对象
        return new Inner();
    }
}
public class Test_1 {
    public static void main(String[] args) {
        //创建成员内部类对象
        //1.直接创建 语法: 外部类.成员内部类 对象名 = new 外部类().new 成员内部类()
        Outer.Inner inner = new Outer().new Inner();
        inner.f2();
        //2.方法返回值接收
        Outer outer = new Outer();
        Outer.Inner inner1 = outer.getInner();
        inner1.f2();
    }
}

image.png

成员内部类和其他内部类语法相差不大注意一点即可!

因为成员内部类是定义在外部类成员位置,所以能够在其他类中创建成员内部类对象!

语法为:

创建成员内部类对象

1.直接创建:外部类.成员内部类 对象名 = new 外部类().new 成员内部类()


2.在外部类创建一个方法返回值接收内部类对象


静态内部类

静态内部类和成员内部类类似。只是它用static修饰而已

我们来了解一下:

静态内部类特点:


放在外部类的成员位置

使用static 修饰

可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员

可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员

作用域 :同其他的成员,为整个类体

//静态内部类
class Outer1{ //外部类
    private int a = 10;
    public void f1(){
        System.out.println("Outer:f1");
    }
    static class Inner1{//成员内部类
        private int b = 14;
        protected void f2(){
            System.out.println("inner::f2");
        }
    }
    public Inner1 getInner(){ //实例方法获取内部类对象
        return new Inner1();
    }
    public static Inner1 getINner1(){//静态方法获取内部类对象
        return new Inner1();
    }
}
public class Test_2 {
    public static void main(String[] args) {
        //获取静态内部类对象
        //1.直接创建
        Outer1.Inner1 inner = new Outer1.Inner1();
        inner.f2();
        //2.方法返回值接收
        //1).静态方法返回
         Outer1.Inner1 inner1= Outer1.getINner1(); //类名.方法
        inner1.f2();
        //2).实例方法返回
        Outer1 outer2 = new Outer1();
        Outer1.Inner1 inner11 =  outer2.getInner(); //对象.方法
        inner11.f2();
    }
}

image.png


总结

内部类中的来说可以简单的分为两种:

1.定义在外部类局部位置的:局部内部类和匿名类

2.定义在外部类成员位置的:成员内部类和静态内部类

我们只要搞清楚他们的的作用域还有编译和运行时怎样即可

并且java匿名类是一个重点和难点

结合了之前我们学习的知识点:继承,多态等知识!

目录
相关文章
|
2月前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
98 43
Java学习十六—掌握注解:让编程更简单
|
1月前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
2月前
|
存储 SQL 小程序
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
这篇文章详细介绍了Java虚拟机(JVM)的运行时数据区域和JVM指令集,包括程序计数器、虚拟机栈、本地方法栈、直接内存、方法区和堆,以及栈帧的组成部分和执行流程。
41 2
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
|
1月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
2月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
59 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
2月前
|
存储 算法 Java
带你学习java的数组军队列
带你学习java的数组军队列
39 0
|
2月前
|
Java 大数据 开发工具
java学习——环境准备(1)
java学习——环境准备(1)
43 0
|
Java
java匿名类和匿名对象及this的其他用法
/* 匿名内部类:就是内部类的简写格式。 必须前提:内部类必须继承或者实现一个类或者接口。 匿名内部类其实就是一个匿名 子类对象。 格式:new 父类对象 or 接口(){ 子类内容;(覆盖父类的, 而且可以增加自己的方法) }//相当于将 继承父类 和 new 的过程 写到...
827 0
|
8天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
38 6