首页 > 编程语言 >java内部类详解

java内部类详解

时间:2024-07-24 12:39:54浏览次数:19  
标签:java 内部 void public 详解 println 方法 class

24-07-22 java内部类

目录

什么是内部类

简单来说,一个类的内部嵌套了另一个类结构,被嵌套的结构被称为内部类,嵌套其他类的类我们称为外部类。

在开始学习之前,我们先来回想一下一下类的五大成员,分别是属性方法构造器代码块内部类,而接下来我们就将开始学习它的第五大成员——内部类

内部类的分类

我们可以通过内部类的位置与是否有类名,是否是static类型将内部类分为4种,分别是:

  1. 局部内部类
  2. 匿名内部类
  3. 成员内部类
  4. 静态成员类

其中,匿名内部类是本章的重点与难点。

局部内部类

  1. 局部内部类是指定义在类方法中或者代码块中的类结构,并且它是有类名的。
class Outer01 {
    public void m1() {
        class Inner01{

        }
    }
}
  1. 局部内部类可以直接访问外部类所有的成员,包括私有的,如下,内部类的m2方法可以直接调用外部类的私有属性n1与私有方法m
class Outer01 {
    private int n1 = 100;
    private void m3(){}
    public void m1() {
        class Inner01 {
            public void m2() {
                System.out.println("外部类中的n1=" + n1);
                m3();
            }
    	}
  	}
}
  1. 局部内部类是不能添加访问修饰符的,因为它是包含在方法中的,而方法本身就是一个类成员的地位,但是我们可以给它加final,来指定它允不允许继承,因为有可能在一个外部类的方法里面有多个内部类,这些类可以存在继承关系。
public void m1() {
        final class Inner01 {
            public void m2() {
                System.out.println("外部类中的n1=" + n1);
                m3();
            }
        }
        class Inner02 extends Inner01 {//报错,因为Inner02类不能继承Inner01类
            
        }
    }
  1. 局部内部类的作用域仅仅在定义它的方法或者代码块中
class Outer01 {
    public void m1() {
        class Inner01 {
            public void m2() {
                System.out.println("局部内部类作用域仅仅在定义它的方法或者代码块中");
            }
        }
    }
    Inner01 inner01 =new Inner();//报错,在类的方法外无法创建内部类对象
}
  1. 外部类如何调用内部类的方法:外部类在方法中,可以创建局部内部类的对象,通过对象调用方法即可。
class Outer01 {
    private void m3() {
        System.out.println("加油");
    }
    public void m1() {
        class Inner01 {
            public void m2() {
                System.out.println("黄暄");
                m3();
            }
        }
        Inner01 inner01 = new Inner01();
        inner01.m2();
    }
}

在主方法中调用m1方法,首先通过创建的inner01对象调用m2方法,在m2方法中输出黄暄,同时调用外部类私有成员函数m3,输出加油。

总而言之,局部内部类的本质依然是个类,只不过是定义在方法体或者代码块中,它的作用域也在它的方法体或者代码块中,内部类可以直接调用外部类的所有成员,外部类也可以通过在方法体或者代码块中创建内部类的实例对象来调用内部类的方法。

  1. 需要注意的是,在外部其他类中无法创建内部类的对象,因为局部内部类是在类的方法体中定义的,别说是外部其他类了,就算是外部类中也无法创建内部类的对象,因为内部类的作用域只在定义它的方法体或者代码块中。

  2. 如果外部类与局部内部类中的成员重名时,遵循就近原则,如果想在内部类中访问外部类的同名成员,那么可以使用外部类名+this+成员。

public class innerClass01 {
    public static void main(String[] args) {
        Outer01 outer01 = new Outer01();
        outer01.m1();
    }
}


class Outer01 {
    private int n1 = 50;
    public void m1() {
        class Inner01 {
            private int n1 = 100;
            public void m2() {
                System.out.println("内部类的n1=" + n1 );
                System.out.println("外部类的n1=" + Outer01.this.n1);
            }
        }
        Inner01 inner01 = new Inner01();
        inner01.m2();
    }
}
//输出:
//内部类的n1 = 100;
//外部类的n1 = 50;

如何理解这个this呢,我们可以把Outer01.this看成一个对象,哪个对象呢,就是在主方法中创建的outer01,简单来说,就是哪个对象调用了m1方法,,Outer01.this就是哪个对象。我们可以使用hashcode值来检验它们是不是同一个对象(哈希值在一定程度上可以反映地址,地址如果一样,那么一定是同一个对象)

System.out.println("outer的hashcode"+outer01);
System.out.println("Outer01.this的hashcode"+Outer01.this);

Outer01.this的hashcodecom.huangxuan.innerclass.Outer01@3d075dc0
outer的hashcodecom.huangxuan.innerclass.Outer01@3d075dc0

我们可以发现它们的hashcode确实相等,因此它们是同一个对象。

匿名内部类AnonymousClass(重点

匿名内部类的特点:

  • 匿名内部类本质仍然是一个类
  • 它也是定义在方法体或者结构体中的
  • 匿名内部类没有名字(系统会自动分配)
  • 匿名内部类同时还是一个对象
  1. 基于接口的匿名内部类。

通常我们要想实现一个接口要写一个类来实现该接口并创建对象,如下:

public class innerClass01 {
    public static void main(String[] args) {
        Outer02 outer02 = new Outer02();
        outer02.m1();
    }
}

class Outer02 {
    private int n1 = 100;
    public void m1() {
        A a = new A();
        a.cry();
    }
}
interface IA {
    public void cry();
}
class A implements IA {
    @Override
    public void cry() {
        System.out.println("通过A类实现IA接口");
    }
}

但是这种写法的问题在于,如果我只想要使用一次cry方法,但是我专门的为此定义一个类来实现它的接口,未免有点大题小作了,特别繁琐,匿名内部类就是用来解决这种问题的,我们能不能不用专门创建一个类来重写接口中的cry方法呢,当然可以。

class Outer02 {

    private int n1 = 100;
    public void m1() {
        IA ia = new IA() {
            @Override
            public void cry() {
                System.out.println("匿名内部类");
            }
        };
        ia.cry();
    }

}

interface IA {
    void cry();
}

通常来讲接口是不能够实例化的,但是在匿名内部类这里有点不同,我们思考一个问题,ia的编译类型与运行类型分别是上面,我们可以发现,ia的编译类型是ia,而ia的运行类型是就是这个匿名内部类,我们透过底层可以发现其实存在一个匿名内部类,形如

class xxxx implements IA {
    @Override
    public void cry() {
        System.out.println("匿名内部类");
    }
}

这与我们刚刚说的第一种方法一样,我们其实也可以通过某些方法来看到xxxx,也就是匿名内部类的类名。

System.out.println("ia的运行类型为"+ia.getClass());

ia的运行类型为class com.huangxuanhsp.innerclass.Outer02$1

我们可以看到原来这个匿名内部类的类名就是外部类类名+$n;也就是说在底层创建了这个匿名内部类立刻创建了一个实例,并且把地址返回给IA ia;

需要注意的是匿名内部类使用一次就失效了,我们无法再次根据这个类名再次实例化对象。

  1. 基于类的匿名内部类

我们添加一个Father类,类中包括一个含参数的构造器与一个test方法,我们尝试在Outer02中使用匿名内部类重写Father类里的test方法,如下:

class Outer02 {

    private int n1 = 100;
    public void m1() {
        Fatehr1 father1 = new Fatehr1("huangxuan"){
            @Override
            public void test() {
                System.out.println("我是新的test方法");
            }
        };
        father1.test();
    }

}

class Fatehr1 {
    public Fatehr1(String name) {
        System.out.println("我是构造函数");
    }

    public void test() {
        System.out.println("我是原本的test方法");
    }
}

其实可以理解为在底层发生了下面的事

class Outer02$3 extends Fatehr1 {
    @Override
    public void test() {
        System.out.println("我是新的test方法");
    }
}

这样一来,匿名内部类通过所谓的继承father类来达到重写test方法的目标,不过需要注意的是,father构造函数不能被重写

还有就是如果father类是一个抽象类,那么在你的匿名内部类中就一定要实现方法,不能为空例如我们写下面的一个抽象类。

abstract class Father2 {
    abstract void test2();
}

如果我们不在匿名内部类中重写test方法,那么就会出现下面的提示

image-20240724000614211

由此可见,对于抽象的匿名内部类,一定要实现抽象类中的方法。

  1. 通过上面的学习,我们可以发现匿名内部类在语法上既具有创建对象的特点,也具有定义类的特点,下面我们学习它的其他语法。
class Outer03 {
    public void m1() {
        Father3 father3 = new Father3(){
            @Override
            public void test3() {
                System.out.println("我是第一种方法的test3");
            }
        };
        //第一种方法,通过创建对象调用方法
        father3.test3();
        //第二种方法,直接调用方法
        new Father3(){
            @Override
            public void test3() {
                System.out.println("我是第二种方法的test3");
            }
        }.test3();
        new Father3(){
            @Override
            public void test4() {
                super.test4();
            }
        }.test4();
    }
}

class Father3 {
    public void test3() {
        System.out.println("我是原来的test3");
    }
    public void test4() {
        System.out.println("我是原来的test4");
    }
}

由于匿名内部类返回的是一个实例对象,所以我们可以直接在后面调用方法而不需要额外将对象传递给另一个对象。还有我们在匿名内部类中重写test4的方法,重写内容为调用父类的test4方法,兜兜转转结构最终还是调用最初的test4的方法。

我是第一种方法的test3
我是第二种方法的test3
我是原来的test4

  1. 匿名内部类可以直接方法外部类的使用成员,包括私有的.
  2. 匿名内部类不可以添加访问修饰符
  3. 匿名内部类的作用域只在定义它的方法体或者代码快中.
  4. 外部其它类不能方法匿名内部类.
  5. 如果外部类与匿名内部类的成员重名的话,同样遵循就近原则,与上面相同,如果想要访问外部类的同名成员,我们同样可以使用外部类的类名+this+同名成员,上面已经讲过,不过多赘述.
  6. 匿名内部类的实践

其实匿名内部类充当实参直接传递时最简洁高效.

public class innerClass01 {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.m(new IT() {
            @Override
            public void show() {
                System.out.println("这个匿名内部类将返回一个对象作为参数");
            }
        });
    }
}

 class Outer04{
    public void m(IT il) {
        il.show();
    }
 }
interface IT {
    public void show();
}

由于匿名内部类返回的是一个对象,这个对象的编译类型是IT,运行类型是Outer04$1,这个运行类型中顺便重写了show方法.

成员内部类

成员内部类是定义在外部类的成员位置,并且没有static修饰的类

  1. 同样,成员内部类可以直接访问所有类成员,包括私有的
public class MemberInner {
    public static void main(String[] args) {
        Outer05 outer05 = new Outer05();
        outer05.t1();
    }
}

class Outer05 {
    private int n1 = 100;
    public String name = "小明";
    class Inner05{
        public void say() {
            System.out.println(n1);
            System.out.println(name);
        }
    }
    public void t1() {
        Inner05 inner05 = new Inner05();
        inner05.say();
    }
}

从上面我们可以看到,如果想要调用外部成员类的私有方法,那么可以在外部类中写一个方法,在方法中定义一个外部成员类对象然后调用这个对象的方法,最后在主方法中调用我们定义的方法就可以成功调用外部成员类的方法。

  1. 外部成员类可以添加访问修饰符,因为它的地位本身就是一个类成员
public class Inner05{
        public void say() {
            System.out.println(n1);
            System.out.println(name);
        }
    }
  1. 外部类要访问成员内部类需要先创建对象,再访问。
  2. 外部其他类也可以访问内部成员类,有三种方式
  • 我们先创建一个外部类对象,然后通过这个对象new一个内部类的对象
Outer05.Inner05 inner05 = outer05.new Inner05();
inner05.say();
  • 在外部类方法中写一个方法,返回一个内部类对象,然后就可以在主方法中直接调用内部类方法,这种方法与上面最开始提到的类似,只不过之前是在方法中完成创建对象然后调用,现在是返回对象然后在主方法中调用。
outer05.getInner05().say();

public Inner05 getInner05() {
        return new Inner05();
    }
  • 第三种方法其实只不过是把第一种方式给一体化了,也就是把创建外部类对象与借助外部类对象创建内部类对象这个两步的过程合为一步了
Outer05.Inner05 inner051 = new Outer05().new Inner05();
inner051.say();
  1. 如果外部类与内部成员类重名时,遵循就近原则,如果外部成员类想要访问外部类重名成员,那么可以使用外部类名+this+重名成员来访问。
class Outer05 {
    private int n1 = 100;
    public String name = "小明";
    public class Inner05{
        private int n1 =99;
        public void say() {
            System.out.println(n1);
            System.out.println(Outer05.this.n1);
        }
    }

静态内部类

静态内部类与成员内部类很相似,同样是定义在成员位置,只不过前面加了一个static修饰,所有用法略有不同。

  1. 和静态方法只能访问静态成员一样,静态内部类只能访问静态成员,包括私有的,而不能访问所有的非静态成员

    image-20240724112949167

  2. 可以添加任意的访问修饰符(public,protect,默认,private)

  3. 与内部成员类一样,静态内部类的作用域也是整个外部类体

  4. 静态成员类与外部类的相互调用与前面成员内部类的方法相同,这里不过多赘述。

  5. 外部其他类调用静态成员类

与成员内部类不同的是,由于静态成员类本质只是一个类的静态成员,所以我们可以直接通过类来调用静态成员类,同样有三种方法。

  • 直接通过外部类来创建内部类对象
Outer06.Inner06 inner06 = new Outer06.Inner06();
inner06.say();
  • 与成员内部类一样,在外部类方法中写一个方法,返回一个内部类对象,然后就可以在主方法中直接调用内部类方法
Outer06 outer06 = new Outer06();
outer06.get().say();

public Inner06 get() {
         return new Inner06();
    }
  • 基于第二种方法,我们可以更进一步简化,因为静态成员可以由类直接引用,所以我们可以写一个静态方法来返回静态内部类对象,直接使用Outer类来调用。
 Outer06.get1().say();
 
 public static Inner06 get1() {
         return new Inner06();
    }
  1. 如果外部类与静态内部类重名时,还是遵循就近原则,如果外部成员类想要访问外部类重名成员,那么可以使用外部类名+重名成员来访问,注意这里不需要this了,因为类可以直接调用静态成员。
class Outer06{
    private static int n2 = 200;
     static class Inner06{ 
        public int n2 = 99;
        public void say() {
            System.out.println(n2);
            System.out.println(Outer06.n2);
        }
    }
}

到这里,java四种内部类已经讲解完毕,欢迎补充

标签:java,内部,void,public,详解,println,方法,class
From: https://www.cnblogs.com/huangxuanblogs/p/18320629

相关文章

  • 【数据结构】队列详解和模拟实现
    大家好,今天我们学习队列,本篇博客主要涉及一般队列,环形队列和双端队列,每个队列应用场景均有所差异,下面我们一起来看看吧。队列(Queue)一,概念队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的特性入队列:进行插入操作的一端称为队尾(Ta......
  • GitHub 详解教程
    1.引言GitHub是一个用于版本控制和协作的代码托管平台,基于Git构建。它提供了强大的功能,使开发者可以轻松管理代码、追踪问题、进行代码审查和协作开发。2.Git与GitHub的区别Git是一个分布式版本控制系统,用于跟踪文件的更改历史。GitHub是一个基于Git的在线平台,......
  • Druid出现DruidDataSource - recyle error - recyle error java.lang.InterruptedExce
    原文链接: https://www.cnblogs.com/zhoading/p/14040939.htmlhttps://www.cnblogs.com/lingyejun/p/9064114.html 一、问题回顾线上的代码之前运行的都很平稳,突然就出现了一个很奇怪的问题,看错误信息是第三方框架Druid报出来了,连接池回收连接时出现的问题。1234......
  • Java面试题总结(持续更新)
    1、this关键字和super关键字的区别及联系this关键字用在本类中。在类的内部,可以在任何方法中使用this引用当前对象。this关键字是用来解决全局变量和局部变量之间的冲突。this()可以调用同类中重载的构造方法,并且需要放在第一行。super关键字用在子类中。在子类中可以通......
  • 在 Kubernetes 中设置 Pod 优先级及其调度策略详解
    个人名片......
  • QListWidget实现内部拖动排序功能
    1.需求将QListWidget有很多的任务,需要拖动任务,手动进行排序,并且需要保存拖动之后的顺序,软件重新打开要是修改之后的顺序;(1)继承QListWidget,重写一个QListWidget自定义类#ifndefDROPLISTWIDGET_H#defineDROPLISTWIDGET_H#include<QListWidget>#include<QDropEvent>clas......
  • JavaScript中的new map()和new set()使用详细(new map()和new set()的区别)
    简介:newMap():在JavaScript中,newMap()用于创建一个新的Map对象。Map对象是一种键值对的集合,其中的键是唯一的,值可以重复。newSet():在JavaScript中,newSet()是用来创建一个新的Set对象的语法。Set对象是一种集合,其中的值是唯一的,没有重复的值。newSet()可以用......
  • Python Match Case:检查未知长度的可迭代内部的类型
    我想使用匹配大小写检查一个未知长度的迭代(假设为list)仅包含给定类型(假设为float)(还有其他情况,只有这个给我带来了问题)。case[*elems]ifall([isinstance(elem,float)foreleminelems]):returnnum这个似乎可行,但确实很不Pythony。看来应该有更简单的方法。......
  • Aspose项目实战!pdf、cells for java
    Aspose实战使用:Excel与PDF转换工具类在这篇博客中,我将分享如何使用Aspose库来实现Excel文件与PDF文件之间的转换。我会重点分析一个工具类AsposeOfficeUtil,这个类封装了多个与Excel和PDF相关的操作方法,帮助开发者高效地进行文件转换和数据处理。此外,还将提......
  • JAVA 打印菱形的程序(Program to print the Diamond Shape)
    给定一个数字n,编写一个程序来打印一个有2n行的菱形。例子://JAVACodetoprint //thediamondshapeimportjava.util.*;classGFG{     //Printsdiamondpattern  //with2nrows  staticvoidprintDiamond(intn)  {    i......