本文介绍了什么是内部类,Java中的内部类:实例内部类. 静态内部类.局部内部类.匿名内部类的语法和注意事项,以及每个内部类的运用场景,以及简单介绍了匿名内部类更简洁更特殊的语法->lambda表达式
Java内部类学习笔记
- 一.什么是内部类?
- 二. 内部类的分类
- 1.实例内部类
- 2.静态内部类
- 3.局部内部类
- 4.匿名内部类
- 简单介绍一下lambda表达式:
- 三.各个内部类的运用场景
- 四.总结
一.什么是内部类?
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么这个内部的完整结构最好使用内部类
比如 : 一辆汽车, 汽车整体是一个完整的结构,但在其内部又可以细分为多个内部结构 如汽车内的发动机,其可以看成独立的内部结构,而此发动机内部结构又是为汽车提供能源的…
因此,Java这种面向对象的编程语言,用来描述这种类与类的包含关系,采取了内部类的形式:
可以将一个类A定义在另一个类B或者一个方法的内部,有了这层定义关系
前者A称为内部类,后者B称为外部类。
内部类可以看成是外部类的成员,其能被访问权限修饰符和static修饰,故内部类也是封装的一种体现…
语法如下:
public class OutClass {
class InnerClass{
}
} // OutClass是外部类
// InnerClass是内部类
首先定义一个外部类, 在类里面 再定义一个类. OutClass为外部类,InnerClass即为内部类
public class A{
}
class B{
}
一般一个java源文件中只写一个类, 如果还有类一般是内部类
上面的类A 类B是定义在同一个java源文件中,但并不是内部类
注意:
- 定义在class 类名{}花括号外部的,即使是在一个文件里,都不能称为内部类
- 内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件
二. 内部类的分类
内部类是定义在一个类中的, 但由于在类中不同位置,或者不同修饰符,不同用法 可分为多种形式: 实例内部类 静态内部类 局部内部类 匿名内部类
1.实例内部类
实例内部类即作为外部类的一个实例成员,定义在一个类里面, 方法的外面,不被static修饰
示例:
class OuterClass1{
public int num1=1;
static int num2=2;
public class InnerClass{ // 内部类 是外部类的一个成员 作为类的一部分 可以和另一个类的内部类同名 是属于不同类里的 内部类是一个成员可以用public等修饰符
public int num1=1; // 内部类 和外部类虽是包含关系 但是 是两个不同的类,分别都可以有同名的成员
static final int num3=3; //语法规定 非静态/实例 内部类 里面不能写 静态成员变量和成员方法 但是静态成员变量可以加final变为静态常量
//static int num; 报错
// static void func1(){ 语法报错
//
// }
//静态内部类里可以有静态属性和方法 非静态内部类 不能有静态属性或方法 但可以有静态常量(因为静态常量在编译时就产生,可做内部类里常量使用) 非静态内部类使用就得实例化
//在加载外部类时不会加载实例内部类故无法加载实例内部类里的静态成员 所以非静态内部类里不能有静态成员 否则没有实例化没有加载 通过类名无法访问
//加载外部类会加载静态内部类 静态内部类里可以有静态成员 可以通过类名访问
int num4 =4;
final int num5=5;
//可以有非静态成员变量和成员方法 或 静态 非静态的final常量
public void func(){ //非静态内部类里的非静态方法
InnerClass innerClass=new InnerClass(); //在外部类里或者非静态内部类里 可以不用外部类类名.内部类类名 而是直接使用内部类名 来创建非静态内部类引用变量
//创建内部类对象时 在外部类的实例部分中(实例代码块和成员方法等)只要能用this表示当前对象引用时 可以直接使用new 内部类() 来实例 因为隐式包含了当前外部类对象
System.out.println("非静态内部类里的静态常量:"+num3); //静态常量 直接用名也可以访问
System.out.println("非静态内部类里的非静态变量:"+num4);
System.out.println("非静态内部类里的非静态常量:"+this.num5);
System.out.println("非静态内部类里调用外部类的非静态变量:"+OuterClass1.this.num1);//非静态内部类里调用外部类的非静态变量用外部类对象this
//因为非静态内部类对象出现一定要有外部类对象和内部类对象
// 此时在内部类方法里有两个this 单独this表示当前内部类对象的this 表示当前对象的引用 也有外部类名.this 表示外部类对象的引用
System.out.println("非静态内部类里调用外部类的静态变量:"+OuterClass1.num2);
}
}
//静态内部类和非静态内部类 一般静态内部类比较常用 非静态比较麻烦 用内部类就是多了层关系 表示内部类是为外部类服务的
}
注意事项:
内部类和外部类虽然是包含关系,但是是两个不同的类, 其内部可以分别有同名的方法或成员, 在实例内部类里调用和外部类同名的静态成员时, 为了避免冲突,需要加上外部类名.成员,而如果调用的是外部类的实例成员时,需要加上 外部类名.this .外部类成员 (即在实例内部类对象里有两个this引用, 一个内部类对象自己的,一个对应外部类对象的) ,而在外部类或外部类对象里不能直接调用实例内部类的成员
除非外部类里实例了内部类成员进行调用
java语法不支持在实例内部类定义 static 的静态成员变量和成员方法, 因为加载外部类时不会加载实例内部类,故其内的静态部分不会被加载, 拿到内部类引用无法调用静态的成员, 但 可以static和final同时修饰,表示是一个静态常量(常量在编译时就会被加载),
实例内部类对象的创建
//实例内部类的调用
OuterClass1 outerClass1=new OuterClass1();
OuterClass1.InnerClass innerClass1 =outerClass1.new InnerClass();
// 给非静态内部类创建对象 需要先实例化外部类对象然后 外部类名.内部类名 引用变量= 外部类实例化的对象.new 内部类();可见创建非静态内部类比静态麻烦,创建时多了外部类对象调用
innerClass1.func();
因为实例内部类 是外部类对象的成员, 要拿到实例内部类的引用 需要通过外部类类名.内部类类名 对应创建一个内部类引用变量, 但是创建实例内部类对象, 需要先创建一个外部类对象, 再通过外部类对象.new 实例内部类名() ;
即要得到实例内部类对象 得先有外部类对象
实例内部类里不能定义静态的成员变量和成员方法
实例内部类和外部类可以同名的成员, 实例内部类对象里有当前对象this引用 和外部类对象this引用
创建实例内部类对象 必须先有外部类对象
2.静态内部类
静态内部类 定义在在外部类里面 ,方法的外面, 被static关键字修饰的类 ,即属于外部类的一个静态成员
示例:
class OuterClass{ //外部类 里面有完整结构 里面有内部类 内部类只是外部类的一个成员 内部类里可以有自己的结构 可以同名 与外部类不冲突!!!
public int num1=1;
static int num2=2;
static class InnerClass{ // OuterClass里的一个静态类 这个类属于外部类的一部分 但是这个类里可以有自己完整的结构
int num1=1;
static int num2=2;
int num3=3;
static int num4=4;
// 一个类内部是一个完整结构 不管其和其他类是否是包含关系 还是继承关系 只要是不同的两个类 两个类就可以有同名的变量
void func(){
InnerClass innerClass=new InnerClass(); // 在外部类里 可以不用外部类名 直接使用内部类名 创建内部类对象
System.out.println("static("+num2+","+num4+")");
// 当前最近一层类是 InnerClass 其变量是属于此类的 不属于再外层的OuterClass (跟多层包类似) 类里的变量最接近的哪层类就是哪层类自身的
System.out.println("内部类非静态变量:"+num1); //在非静态方法中 可以直接调用当前对象的静态或非静态
System.out.println("内部类静态变量:"+this.num2);//静态内部类 成员方法里可以this表示静态内部类对象引用
OuterClass outerClass=new OuterClass();
System.out.println("外部类对象内的非静态变量:"+outerClass.num1);
System.out.println("外部类的静态变量:"+OuterClass.num2);
//System.out.println("外部类对象内的非静态变量:"+OuterClass.this.num1); 错误
// 静态内部类里不存在两个this 因为外部类不用实例化就可以实例化静态内部类 在内部类方法里访问外部类的非静态必须先实例化外部类访问
}
@Override
public String toString() { //内部类本身也是一个类 继承 object 可以重写其方法
return "InnerClass{" +
"num1=" + num1 +
", num3=" + num3 +
'}';
}
}
void func(){ //外部类 内部类 都有自己的结构 可以有同名方法和同名属性
System.out.println("外部类非静态变量:"+num1); //在非静态方法中 可以直接调用当前对象的静态或非静态
System.out.println("外部类静态变量:"+num2);
InnerClass innerClass=new InnerClass(); // 在当前外部类里 可以直接用静态类名访问 静态类 实例化类对象
System.out.println("内部类对象内的非静态变量:"+innerClass.num3); // 有了内部类对象才可以调用内部类非静态成员变量
System.out.println("内部类的静态变量:"+InnerClass.num4); //此时在外部类里面 使用内部类静态成员变量 只需要静态类名 调用即可
}
}
注意事项:
静态内部类 是外部类的一个静态成员, 但是其一般是由外部类里使用的类用来描述一个内部结构,外部类和静态内部类 是可以有同名的成员变量.成员方法, 在静态内部类中,不能再访问到外部类对象的实例成员,但是可以访问到外部类的静态成员:外部类名.外部类静态成员 (因为静态内部类的实例不需要先实例外部类对象,要访问到外部类的非静态成员需要实例外部类对象)
静态内部类里可以有静态成员和非静态成员, 以及常量, 因为在加载外部类的时候静态内部类也会被加载,其静态内部类的静态成员都会被加载, 在外部类里也可以直接通过获取静态内部类名.访问静态成员
在没创建静态内部类对象时访问不了其实例成员
静态内部类对象的创建
OuterClass.InnerClass innerClass=new OuterClass.InnerClass(); // 静态内部类是外部类一部分 需要拿到外部类类名.静态类名才能得到这个类 实例化内部类对象需要 new 外部类名.内部类名()
System.out.println(innerClass);
innerClass.func();
OuterClass outerClass=new OuterClass(); //实例化一个外部类 对象 里面有一个静态成员是一个类
outerClass.func();
获取静态内部类 需要用 外部类名.静态内部类名 ,以此创建一个静态内部类引用
而获取静态内部类对象 需要 new 外部类名.静态内部类名(); 获取静态内部类对象
创建静态内部类对象不需要先创建外部类对象,只需拿到类名即可
3.局部内部类
局部内部类 是定义在某个代码块里面的一个类,其作用域也是在其代码的局部区域内, 故称为局部内部类
示例:
class OuterClass2{ // 局部内部类 在一个类里 有代码块(静态或非静态) 构造方法 成员方法 这些内部都可以定义一个类 这些类都被成为局部内部类
//局部内部类只在 对应的 代码块 方法体内有效 能够被访问使用, 所以不同的区域内 是不同的类 可以同名 出了指定局部就不能再使用
//不能被public 不能static 只能实例化后使用且只能在当前包内 几乎不使用
int num1=1;
class InnerClass{ // 一个外部类里 不能同时存在相同名字的两个内部类 不论静态非静态 但是有内部类 可以存在同名的局部内部类
}
// static class InnerClass{ // 存在实例内部类 不能再有同名的静态内部类
//
// }
{//实例代码块
class InnerClass{
int num1=1;
}
}
static {//静态代码块
class InnerClass{
int num2=2;
}
}
void func(){//成员方法
class InnerClass{
int num3=3;
}
}
static void func1(){
class InnerClass{
int num4=4;
}
}
OuterClass2(){ // 构造方法
class InnerClass{
int num5=5;
}
}
//不同局部区域的局部内部类 可以同名
}
注意事项:
局部内部类可以定义在代码块内,且只能在定义的代码块里使用的类, 且其不能被访问权限修饰符修饰
一般很少使用, 不同代码块的局部内部类可以同名, 但是同一个外部类里 静态内部类 实例内部类不能同名
- 局部内部类只能在所定义的方法体内部使用
- 不能被public、static等修饰符修饰
- 编译器也有自己独立的字节码文件,命名格式:外部类名字$数字内部类名字.class
- 几乎不会使用
4.匿名内部类
匿名内部类 是在一个类里定义的一个没有名字的类,因为其没有名字的原因,此类只能使用一次且必须配合继承其它类或者实现其它接口使用…
示例:
interface IA{
void func();
}
class AA implements IA{ //此处和下面创建匿名内部类过程是等价的
@Override
public void func() {
System.out.println("创建一个普通类 实现IA接口重写func方法 实例化此普通类调用此func方法");
}
}
public class Note{
//匿名内部类
new OuterClass(){
void func1(){
System.out.println("创建一个匿名内部类继承 OuterClass 重写一个func1方法 并且实例化匿名内部类对象 调用func1方法");
}
}.func1();
OuterClass outerClass2= new OuterClass(){
void func(){
System.out.println(" 创建一个匿名内部类 继承OuterClass 类 重写func方法 并且实例化一个对象给OuterClass类引用接受 再调用此子类对象重写的func");
}
};
outerClass2.func();
IA ia=new AA(); //向上转型
ia.func(); //动态绑定
new IA(){
@Override
public void func() {
System.out.println("创建一个匿名内部类拓展IA接口,重写IA内抽象方法 并实例化此内部类对象调用 func");
}
}.func();
}
//创建匿名内部类 一定是要继承某个类或者某个接口 new 类名/接口名 (){}; {}内写继承或拓展后需要重写或者自己的结构最后以分号结尾 在创建完后会实例化对象 如果要想执行对象内容在分号前通过.执行
//匿名内部类只在当前创建实例化后只执行一次 匿名只使用一次
//多用于拓展接口
}
匿名内部类创建类的写法和其他内部类有所不同:
匿名内部类创建需要先实现或者继承某个类, 确定要实现某个接口 后 即可通过 new 接口名(){};
创建一个类实现此接口, {}里写的即是匿名内部类的成员, 可以有自己的成员,但主要是用来重写 实现接口里的方法的, 大部分使用场景就是为了能直接调用重写的方法…
当我们有一个接口或者有一个类 其内部有一个或多个方法需要被重写使用时, 可以自定义一个类,此类继承或实现接口 后 在其内可以重写类和接口的方法, 然后实例调用这些重写的方法
如: 实现对象的比较 可以创建一个比较器对象实现compare接口 重写其比较规则后 此类即可以作为一个比较器对象使用(具备比较特性)
而如此定义一个类,它一般是应用在某个特殊的场景,比如比较对象大小而定义的比较器对象使用,这种对象一般只使用一次, 而如果显式定义一个类得到一个比较器对象 此类具备名字,单独的.class源文件,但一般作为比较器的类只用于某个特定场景得到一个比较器对象即可
此时则可以使用到匿名内部类,匿名内部类实现一个接口或者类然后在其内部是重写接口或类的方法,在定义完成后同时会实例一个此类对象返回, 即在定义的同时实例对象,且此类没有名字只被使用一次,在实例化后也可以用其实现的接口或者继承的类创建的引用接受(向上转型)
可见匿名内部类是某些特定场景下用于重写某个类或接口里的方法来使用的一个类,其匿名的特性使得此类只能在定义的同时使用一次
匿名内部类比普通的类实现此功能更加轻便简洁, 但是使用时需要明确重写的方法名,遵循重写的规则对方法进行重写
而在后面还有一种和匿名内部类类似且更为简洁的用法 -> lambda表达式
简单介绍一下lambda表达式:
在上面代码中华可以看到当匿名内部类用于实现IA接口重写其方法进行使用时,编译器提示这种写法可以用lambda表达式进行替代,
如下:
new IA(){
@Override
public void func() {
System.out.println("创建一个匿名内部类拓展IA接口,重写IA内抽象方法 并实例化此内部类对象调用 func");
}
}.func();
IA ia1=()->{ // 重写的方法无返回值 重写的方法内容为 输出字符串...
System.out.println("使用lambda表达式 代替匿名内部类 重写IA抽象方法 并实例化后给IA接口引用 接受后并调用");
};
ia1.func();
//匿名内部类和lambda表达式 在此处效果是等价的
可以看到lambda表达式 比匿名表达式写法更精简, 但是可读性不好, 且需要在特殊场景下才能使用(必须作用于实现函数式接口 (抽象方法只有一个的接口) 即用来重写此抽象方法) 故要在在非常熟悉要重写的方法时使用lambda表达式能比匿名表达式更快捷完成此功能…
三.各个内部类的运用场景
静态内部类和实例内部类都是在外部类里方法外定义的外部类
它们都可以作为外部类的一个成员(静态或非静态)
能被访问权限修饰符修饰,即可以实现封装, 当我们需要一个类只能在另一个类里或类实例的对象中被使用,而在其他类里无法访问此类 即可将其被private修饰,体现其封装性
静态内部类和实例内部类在java中被运用一个类里需要额外描述一个完整的结构时使用:
如写一个链表或者二叉树 哈希表 时 其内部都需要额外的节点结构, 而这个节点结构只服务于外部的链表类,此时可以在链表里封装一个节点类 ,可以借助内部类来完美地体现这种设计需求
当然不使用内部类,在链表类外 也可以定义一个节点类,采取组合关系也可以描述出一个链表, 但是这种写法没有很好地表达链表和节点的包含关系且不好封装,随便一个类都可以访问到节点类
故使用静态或者实例内部类可以更清晰地表达包含关系
而静态内部类一般比实例内部类更常用, 因为其语法比实例内部类更简洁不需要先有外部类对象,但实例内部类里可以直接访问外部类对象的成员, 看具体场景使用
局部内部类和匿名内部类都是定义在局部代码块区域内的类
局部内部类很少使用到, 但匿名内部类这种特性在java中使用很广泛, java中有很多提供的接口需要被实现重写其方法使用, 而匿名内部类就是为了更快捷方便的创建一个类实现某个接口重写实现其方法
四.总结
本文介绍了什么是内部类(用于描述实物的内在结构), java中的内部类(实例内部类 ,静态内部类 ,局部内部类,匿名内部类) 实例内部类和静态内部类更好地描述一个类的内部结构且表示其包含关系也具有封装特性, 局部内部类不常用, 匿名内部类能快捷构造一个类重写其实现的接口或继承的类的方法,且匿名的类只能使用一次,用于快速得到重写的方法进行使用 …以及简单介绍匿名内部类更简写 场景更特殊的用法->lambda表达式