概念
一个类的定义出现在另外一个类,那么这个出现的类就叫内部类(Inner),内部类所在的类叫做外部类(Outer)。
类里面的内容可以写成员变量、成员方法、构造方法、静态成员、构造块和静态代码块、内部类。
说明类体的内容越来越复杂,我们可以往里面写的内容越来越多。
思考🤔:为什么我们需要内部类呢?在什么情况下可以把一个类写在另外一个类的内部呢?
应用
- 当一个类对应提供的内容仅仅是为某一个类单独服务时,那就可以将这个类定义为所服务类中的内部类。
- 既然是内部类,那也是外部类的一部分「相当于都是一家人」,所以也可以访问外部类的私有的成员变量,不需要创建对象实例,然后实例名.变量名或方法名引用,或调get和set方法。
分类
普通内部类
- 直接将一个类的定义放在另外一个类的类体中。
内部类可以用public修饰
静态内部类
- 使用
static
修饰的内部类,属于类层级。{可直接调用,不用实例化}
局部内部类
- 直接将一个类的定义放在方法体的内部
局部变量,就是定义在方法内的变量。同样,局部内部类,就是定义在方法内的类。
匿名内部类
- 就是指没有名字的内部类
没有名字的内部类,开发中用的比较多,也比较不好理解
普通内部类
和我们讲的成员方法和成员变量是一个平行关系
格式
访问修饰符 class 外部类的类名 {
访问修饰符 class 内部类的类名 {
内部类的类体;
}
}
内部类和平时写的类体没什么区别;只不过是放在类里面了。
调用方式
外部类名 外部类实例名 = new 外部类名();
外部类名.内部类名 内部类实例名 = 外部类实例名.new 内部类名();
特点
- 普通内部类和普通类一样,定义成员变量、成员方法、构造方法
- 普通内部类权限修饰符都可以用
- 创建调用普通内部类,需要外部类实例对象来创建内部类对象
- 内部类访问外部类的成员变量/方法,并且这些成员变量名和方法名有同名,那需要使用this关键字
- final可修饰内部类
示例
- 对应编写一个普通内部类
public class InnerDemo {
//成员变量
private int id = 1;
//定义普通内部类
public class NormalInner{
private String name = "inner";
public NormalInner(String name) {
this.name = name;
System.out.println("普通内部类NormalInner的有参构造方法");
}
public NormalInner() {
System.out.println("普通内部类NormalInner的无参构造方法");
}
public void show(){
System.out.println("外部类InnerDemo的成员变量id值是:"+ id);
System.out.println("普通内部类NormalInner的成员变量name值是:"+name);
}
//内部类不能写main方法
/*public static void main(String[] args) {
}*/
}
}
- 内部类不能添加对应的static的main方法
- 内部类可以直接访问外部类的成员变量
- 内部类有构造方法
调用
我们发现对应的内部类声明,不能直接声明,如果直接声明
外部类.内部类
会报错,告诉我们需要把内部类使用static关键字进行修饰。那我们只想要调用的话,只能是先声明对应的外部类,然后再根据外部类的实例名.new 内部类
才可以进行调用
public class Test {
public static void main(String[] args) {
//声明InnerDemo
InnerDemo innerDemo = new InnerDemo();
//指向内部类
InnerDemo.NormalInner normalInner = innerDemo.new NormalInner();
// InnerDemo.NormalInner normalInner = new InnerDemo.NormalInner();
//调用内部类的show()方法
normalInner.show();
}
}
普通内部类NormalInner的无参构造方法
外部类InnerDemo的成员变量id值是:1
普通内部类NormalInner的成员变量name值是:inner
虽然现在我们在外面可以调用成功,但是我们对应内部类主要是为了外部类去使用的,所以我们内部类修饰符用private调用
面试考点
形参变量、内部类成员变量、外部类成员变量,三个都同名的情况下,怎么调用??
源代码:
public class InnerDemo {
//成员变量
private int id = 1;
//定义普通内部类
public class NormalInner{
private int id = 2;
public void show2(int id){
System.out.println("形参变量id:"+ id);//局部优先元素
System.out.println("外部类InnerDemo的成员变量id值是:"+ InnerDemo.this.id);//外部类名.this.成员变量名
System.out.println("普通内部类NormalInner的成员变量id值是:"+ this.id);
}
}
}
调用方式:
public class Test {
public static void main(String[] args) {
//声明InnerDemo
InnerDemo innerDemo = new InnerDemo();
//指向内部类
InnerDemo.NormalInner normalInner = innerDemo.new NormalInner();
//调用内部类的方法
normalInner.show2(6);
}
}
形参变量id:6
外部类InnerDemo的成员变量id值是:1
普通内部类NormalInner的成员变量id值是:2
静态内部类
一说静态,大家是不是就会想到我们前面说的static关键字。如果加上static关键字,就代表着它修饰的是类层级,对应类层级的内容就可以直接
类名.
调用
格式
访问修饰符 class 外部类的类名 {
访问修饰符 static class 内部类的类名 {
内部类的类体;
}
}
静态内部类和普通内部类就是多了一个static关键字
调用方式
外部类名.内部类名 内部类实例名 = new 外部类名.内部类名();
特点
- 静态内部类 不能直接访问外部类的非静态成员
- 静态内部类 可以直接创建对象
- 如果静态内部类访问外部类中与本类内同名的成员变量或方法时,需要使用
类名.
的方式访问
示例
- 对应编写一个静态内部类
public class StaticInnerDemo {
private int id = 1;
private static int sid = 2;
/**
* 定义静态内部类
*/
public static class StaticInner{
private int sinId = 3;
private static int sid = 4;
public StaticInner() {
System.out.println("静态内部类StaticInner的构造方法");
}
public void show(){
System.out.println("外部类StaticInnerDemo的静态成员变量sid值是:"+ sid);
System.out.println("静态内部类StaticInner的成员变量sinId值是:"+ sinId);
//访问外部类的id是否可以访问
// System.out.println("外部类StaticInnerDemo的成员变量id值是:"+ id);//不能访问Error:静态上下文中不能访问非静态成员,此时还没有创建对象
//不能在静态区域访问非静态资源
}
public void show2(int sid){//就近原则
System.out.println("形参变量sid:"+ sid);//局部优先元素 形参值
//内部类的成员id
System.out.println("静态内部类StaticInner的静态成员变量sid值是:"+ StaticInner.sid);
//外部类的成员id
System.out.println("外部类StaticInnerDemo的静态成员变量sid值是:"+ StaticInnerDemo.sid);
}
}
}
- 静态内部类和普通内部类一样,都可以创建成员变量和方法
- 静态内部类有构造方法
- 静态内部类可以直接访问外部类的静态变量
调用
我们发现对应的静态内部类声明,可以直接
外部类.内部类
声明,因为static修饰的内容都是类层级的,可以直接调用。
public class Test {
public static void main(String[] args) {
System.out.println("-------静态内部类-------");
//1.声明静态内部类的引用指向该类型的对象
StaticInnerDemo.StaticInner staticInner = new StaticInnerDemo.StaticInner();
//2.内部类调用方法
staticInner.show();
System.out.println("-------静态内部类静态成员变量-------");
staticInner.show2(9);
}
}
-------静态内部类-------
静态内部类StaticInner的构造方法
外部类StaticInnerDemo的静态成员变量sid值是:4
静态内部类StaticInner的成员变量sinId值是:3
-------静态内部类静态成员变量-------
形参变量sid:9
静态内部类StaticInner的静态成员变量sid值是:4
外部类StaticInnerDemo的静态成员变量sid值是:2
可以看到对应的静态内部类和外部类的成员变量(static修饰)同一个名字的情况下,调用的时候需要说明对应
类名.变量名
面试考点
内部类、外部类方法,同名的情况下,怎么调用??
源代码:
public class StaticInnerDemo {
//...
public void show(){
System.out.println("外部类的show方法");
}
/**
* 定义静态内部类
*/
public static class StaticInner{
//...
public void show(){
System.out.println("内部类的show方法");
}
public void show2(int sid){
//...
show();
}
}
}
内部类
show2()
调用的是内部类的show()
方法,如果想要调用外部类的show()
方法,需要用类名.的方式去访问
StaticInnerDemo.show()
这个时候会报错,对应的外部类的show方法添加static
关键字可以解决
调用方式:
如果是不想方法上添加static调用,则只能
new StaticInnerDemo().show()
这样调用
总结
- 有static就是类层级,类名.调用
- 没有static就是对象层级,引用.调用
都是static关键字修饰的那几条
局部内部类
看到这个词大家应该会联想到我们前面说的局部变量,什么是局部变量呢?就是在方法体内定义的变量就是局部变量
而且局部变量只在方法体内生效,出了方法体这个变量就失效了。
局部内部类又叫方法内部类,顾名思义写在方法体的内部就叫局部内部类。
格式:
访问修饰符 class 外部类的类名 {
访问修饰符 返回值类型 成员方法名(形参列表) {
class 内部类的类名 {
内部类的类体;
}
}
}
局部内部类没有访问权限修饰符,只有一个class+类名
类的定义挪到了方法体里面
调用方式
方法内调用:
局部内部类类名 局部内部类实例名 = new 局部内部类类名();
局部内部类实例名.内部类方法名();
特点
- 局部内部类只能在该方法的内部可以使用。
- 局部内部类可以在方法体内部直接创建对象。
- 局部内部类不能使用访问控制符和static关键字修饰符。
- 局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。
示例
- 对应编写一个局部内部类
public class AreaInnerDemo {
private int aid = 2;
public void show(){
//定义局部内部类 局部内部类:只在当前方法体的内部使用 不能用访问修饰符
class AreaInner{
private int id = 1;
public AreaInner() {
System.out.println("局部内部类AreaInner的构造方法");
}
public void testArea(){
System.out.println("局部内部类AreaInner的id的值:"+id);
System.out.println("AreaInnerDemo的aid的值:"+aid);
}
}
//声明局部内部类的引用指向局部内部类的对象
AreaInner areaInner = new AreaInner();
areaInner.testArea();
}
}
调用
我们发现对应的局部内部类声明,可以直接
外部类实例名.内部类所在方法
声明,因为局部内部类是声明在方法里面的,直接调用方法就会调用该局部内部类。
public class Test {
public static void main(String[] args) {
System.out.println("-------局部内部类-------");
AreaInnerDemo areaInnerDemo = new AreaInnerDemo();
areaInnerDemo.show();
}
}
-------局部内部类-------
局部内部类AreaInner的构造方法
局部内部类AreaInner的id的值:1
AreaInnerDemo的aid的值:2
可以看到对应的局部内部类只能在方法内调用,在外面调用方法的时候,其实是不知道里面有局部内部类的
匿名内部类
在讲匿名内部类的时候,我们先来讲一个概念,什么叫回调模式
回调模式的概念
- 回调模式是指——如果一个方法的参数是接口类型,则在调用该方法时,
需要创建并传递一个实现此接口类型的对象;而该方法在运行时会调用
到参数对象中所实现的方法(接口中定义的)。
创建接口
该接口为了当方法参数提供
public interface AnonyInterface {
public abstract void show();
}
创建接口实现类
public class AnonyInterfaceImpl implements AnonyInterface{
@Override
public void show() {
System.out.println("接口实现类的show方法");
}
}
调用
public class AnonyInterfaceTest {
//假设已有下面方法
/**
*
* @param anonyInterface
* 形参是接口体的引用,调用接口的show方法
* 方法的参数是接口类型,则在调用该方法时,需要创建并传递一个实现此接口类型的对象
* 该方法在运行时会调用到参数对象中所实现的方法
*/
//接口类型的引用指向实现类型的对象,形成了多态
public static void test(AnonyInterface anonyInterface){
//编译阶段调用父类版本,运行调用实现类重写的版本
anonyInterface.show();
}
public static void main(String[] args) {
// AnonyInterfaceTest.test(new AnonyInterface() );//接口不能实例化
AnonyInterfaceTest.test(new AnonyInterfaceImpl());
}
}
分享
- 当接口/类类型的引用作为方法的形参时,实参的传递方式有两种:
- 自定义类实现接口/继承类并重写方法,然后创建该类对象作为实参传递;
- 使用上述匿名内部类的语法格式得到接口/类类型的引用即可;
匿名内部类
格式(重点)
接口/父类类型 引用变量名 = new 接口/父类类型() { 方法的重写 };
package top.testeru.innerp;
/**
* @Package: top.testeru.innerp
* @author: testeru.top
* @Description: 测试已有的抽象方法
* @date: 2022年02月21日 11:35 AM
*/
public class AnonyInterfaceTest {
public static void main(String[] args) {
AnonyInterface anonyInterface = new AnonyInterface() {
@Override
public void show() {
System.out.println("匿名内部类,虽然很抽象~");
}
};
AnonyInterfaceTest.test(anonyInterface);
}
}
new完这个对象,内存空间就可以销毁了,对应的不占空间
这是Java8 以前
//Java8以后lambda表达式,简化代码
(参数列表)->{方法体}
AnonyInterface anonyInterface1 = ()->System.out.println("匿名内部类lambda,虽然很抽象~");
AnonyInterfaceTest.test(anonyInterface1);