内部类
一个类的内部又完整地嵌套了另一个类结构,被嵌套的类称为内部类,嵌套其他类的类称为外部类,是我们类的第五大成员,内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。
- 类的五大成员:属性、方法、构造器、代码块、内部类
- 内部类快速入门
//外部其他类
public class Journey {
public static void main(String[] args) {
}
}
//外部类
class Outer{
//属性
private int youth = 678;
//构造器
public Outer(int youth) {
this.youth = youth;
}
//方法
public void m1(){
System.out.println("m1()");
}
//代码块
{
System.out.println("代码块");
}
//内部类
class Inner{
}
}
内部类的分类
定义在外部类局部位置上(比如方法内)
-
局部内部类(有类名)
-
匿名内部类(没有类名,重点)
定义在外部类的成员位置上
-
成员内部类(没用static修饰)
-
静态内部类(使用static修饰)
局部内部类
说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
- 可以直接访问外部类的所有成员,包含私有的
//演示局部内部类的使用
public class Journey {
public static void main(String[] args) {
}
}
//外部类
class Outer{
private int youth=658;//属性
private void m2(){}//私有方法
public void m1(){//方法
//说明:局部内部类是定义在外部类的局部位置,通常在方法中
class Inner{//局部内部类(本质仍然是类)
// 1.可以直接访问外部类的所有成员,包含私有的,注意是所有!!!
public void f1(){
System.out.println("youth="+youth);//此处就访问了外部类的youth属性
m2();//此处访问了外部类的私有方法
}
}
}
}
-
不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用修饰符的,
但是可以用final修饰,因为局部变量也可以用final
class Outer{
private int youth=658;//属性
private void m2(){}//私有方法
public void m1(){//方法
//类本身是可以被public来修饰的,但是现在是局部内部类,类似于一个局部变量,此时不能再用public来修饰,会报错
// public class Inner{...}
//局部内部类前面可以加final,其意味着这个局部内部类是不能够再被继承的,即下面的原先继承Inner类的Inner03类处会报错
final class Inner{
public void f1(){
System.out.println("youth="+youth);//此处就访问了外部类的youth属性
m2();//此处访问了外部类的私有方法
}
}
// class Inner03 extends Inner{ } //报错:Cannot inherit from final 'null
}
}
- 作用域:仅仅在定义它的方法或代码块中
class Outer{
private int youth=658;//属性
private void m2(){}//私有方法
//3.局部内部类的作用域仅仅在定义它的 方法or代码块中
public void m1(){//方法
//life就是局部变量,其作用域仅仅体现在m1()方法体中,其与底下内部类Inner作用域相同,
// 只是内部类Inner是个类,而life此时是个字符串罢了
String life = "experience";
final class Inner{
public void f1(){
System.out.println("youth="+youth);//此处就访问了外部类的youth属性
m2();//此处访问了外部类的私有方法
}
}
}
{//代码块中也是可以搞一个局部内部类的,此时其作用域就是这个代码块
class Inner03{
}
}
}
- 局部内部类---访问--->外部类的成员【访问方式:直接访问】
//外部类
class Outer{
private int youth=658;//属性
private void m2(){}//私有方法
public void m1(){//方法
String life = "experience";
final class Inner{
public void f1(){
// 5.局部内部类可以直接访问外部类的成员,比如外部类中的属性youth和m2()方法
System.out.println("youth="+youth);//此处就访问了外部类的youth属性
m2();//此处访问了外部类的私有方法
}
}
}
}
- 外部类---访问--->局部内部类的成员【访问方式:创建对象,再访问(注意:必须在作用域内)】
public class Journey {
public static void main(String[] args) {
//演示外部类去访问局部
Outer outer = new Outer();
outer.m1();
}
}
//外部类
class Outer{
private int youth=658;//属性
private void m2(){
System.out.println("私有方法m2()被调用啦");
}//私有方法
public void m1(){//方法
String life = "experience";
final class Inner{
public void f1(){
// 5.局部内部类可以直接访问外部类的成员,比如外部类中的属性youth和m2()方法
System.out.println("youth="+youth);//进入到f1()之后就会去获取外部类中的属性youth
m2();//然后紧接着又调用外部类中的m2()方法
}
}
//6.外部类在方法中,可以创建Inner对象,然后调用方法即可
Inner inner = new Inner();
inner.f1();//程序执行到这儿就会去调用Inner 类中的f1()方法
}
}
//输出结果:
youth=658
私有方法m2()被调用啦
- 外部其他类---不能访问---局部内部类(因为局部内部类地位类似于一个局部变量)
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类.this.成员)去访问
//先看底下的外部类及局部内部类中的语句
public class Journey {
public static void main(String[] args) {
//演示
Outer outer = new Outer();
outer.m1();
}
}
//外部类
class Outer{
private int youth=658;//属性
private void m2(){
System.out.println("私有方法m2()被调用啦");
}//私有方法
public void m1(){//方法
String life = "experience";
final class Inner{
private int youth =6580;
public void f1(){
//如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,
// 则可以使用(外部类.this.成员)去访问
System.out.println("youth="+youth);//此处遵循就近原则访问局部内部类的重名youth属性,输出6580
System.out.println("尝试访问外部类的重名属性youth,得到其为"+Outer.this.youth);//此时输出658
/**
解读下这里的Outer.this.youth
Outer.this本质就是外部类的一个对象,即哪个对象调用了m1(),那么这个Outer.this就指向哪个对象
上面这句中的Outer.this就是指外部类Outer,因为是其对象调用了m1(),好好理解哈
*/
m2();//此处访问了外部类的私有方法,输出对应的语句
}
}
Inner inner = new Inner();
inner.f1();
}
}
//输出结果如下
youth=6580
尝试访问外部类的重名属性youth,得到其为658
私有方法m2()被调用啦
小结---一定要记住哟
- 局部内部类是定义在方法中/代码块
- 作用域在方法体或者代码块中
- 局部内部类本质上仍然是一个类
匿名内部类
1.匿名内部类本质上还是类
2.它是内部类
3.该类没有名字
4.匿名内部类同时还是一个对象
说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名<表面看是没有的,底层其实是有的,被隐藏起来了>
new 类/接口(参数列表){
类体
};
基于接口的匿名内部类的使用
//演示匿名内部类的使用
public class Journey {//外部其他类
public static void main(String[] args) {
Outer outer = new Outer();
outer.fighting();
}
}
//外部类
class Outer{
private int youth = 658;//属性
public void fighting(){//方法
//基于接口的匿名内部类
//1.需求:想使用Ifulture,并创建对象
//2.传统方式,写一个类,实现该接口,并创建对象
// Tiger tiger = new Tiger();
//上面这行也可以通过接口的多态来实现,接口类型指向实现了该接口的类的对象的实例 具体实现看下面10几行附近的<$$$$$$>标注处
//3-Q:此时出现了新的需求Tiger/Horse类只是使用一次,之后再也不会使用,而为此我要频繁的定义很多类,很繁琐
// 我如何才能既不去创建一个Tiger和Horse,又能输出对应的结果语句呢
// 4.此时可以使用匿名内部类来简化开发?!!!!! 以下就是匿名内部类的应用示例
/**
* 5.解读下下面这段代码,提问
* Q1:tiger的编译类型是什么? 看等号的左边-->Ifulture
* Q2:tiger的运行类型是什么? 运行类型是匿名内部类***** very important
* 讲下*****底层来理解为什么运行类型是匿名内部类,其底层其实是长得类似于下面所示的德行,其是隐藏起来的,我们是看不到的
* jdk底层看到你new了一个接口的语法,就会在底层给你创建一个匿名内部类,类的名字由系统给你分配,
* 匿名内部类之所以叫匿名内部类,不是因为这个类没有名字,而是你用完了就没有了,即我回头想再new Outer$1();是不可行的,其是不存在的
* 底层给你创建完,用掉之后就没有了,所以叫它匿名内部类,而创建了匿名内部类后,就会立刻创建对应的实例,将其地址返回给等待它的引用,
* 此例中引用tiger接收到之后,就可以反复地调用
* class XXXX implement Ifulture{
* @Override
* public void JustSmile() {
* System.out.println("心有猛虎,细嗅蔷薇");
* }
* }
* 其底层是有个XXXX的类的,其实现了Ifulture的方法,可以把"心有猛虎,细嗅蔷薇"输出,而XXXX这个名字在底层会被分配
* 那么这个XXXX的名字是咋分配的呢? 具体是啥呢?hhhh,其实XXXX是按照外部类$1的规则分配的,在此例中外部类的名称$1,即 Outer$1。
* 我们可以通过输出语句来验证,验证其确实是Outer.$1,即底层给此时这个匿名内部类分配的名字就是 类名 Outer.$1
* 而这个过程是底层运行的,用完一次就没有了,正好迎合了我们之前上面吐槽的点,
*
* 至此,你理解了为什么运行类型是匿名内部类了吧,也就是说运行类型此时是系统分配的Outer$1,
* hhhh,别打瞌睡,加油学ha
*6.问下,匿名内部类前面的new是啥含义,懂吗?
* jdk底层在创建匿名内部类 Outer$1后,立即马上就创建了Outer$1的实例,并且把地址返回给引用tiger了
*7.匿名内部类使用一次,就不能再使用了,注意,这里说的是匿名内部类,不是tiger哈,tiger可以反复调用,
* 上面所示的类的样子,调用一次,用完就死了,下次再用tiger调用,又会有个新的,用完又死了
*/
Ifulture tiger = new Ifulture(){//匿名内部类的应用实例,以输出 心有猛虎,细嗅蔷薇为例
@Override
public void JustSmile() {
System.out.println("心有猛虎,细嗅蔷薇");
}
};
//在这输出个语句来查看匿名内部类底层分配的名字
System.out.println("tiger的运行类型="+tiger.getClass());//此时得到的就是匿名内部类的底层真实的名称---跑代码可得
// tiger的运行类型=class Outer$1
// 顺便一提,getClass()就是用于获取对象的运行类型的
tiger.JustSmile();//此时,再跑代码,会发现可以输出 “心有猛虎,细嗅蔷薇”
// Ifulture tiger = new Tiger(); // <$$$$$$> 接口的多态,接口类型可以指向实现了该接口的类的对象实例
// tiger.JustSmile();//写完这步之后,在main方法中创建外部类对象实例,然后调用fighting()
}
}
interface Ifulture{//接口
public void JustSmile();
}
/* 如果用传统方式实现,就要像下面一样,需要一次性地输出一些语句,就要对应地定义许多类,操作繁琐,由此催生出了匿名内部类的诞生
class Tiger implements Ifulture{
@Override
public void JustSmile() {
System.out.println("心有猛虎,细嗅蔷薇");
}
}*/
/*class Horse implements Ifulture{
@Override
public void JustSmile() {
System.out.println("马儿呲着大嘴在笑你.....");
}
}*/
基于类(普通类&抽象类)的匿名内部类的使用
//演示匿名内部类的使用
public class Journey {//外部其他类
public static void main(String[] args) {
}
}
//外部类
class Outer{
private int youth = 658;//属性
public void fighting(){//方法
//演示基于类的匿名内部类的使用,有{}和没{}是不一样的哈
//下面这个语句是创建一个father对象的,此时没有{}
// new Father("Kerwin");
//而下面这是基于类的匿名内部类
/**
* 分析:提问
* Q1.father的编译类型是什么? 编译类型看=左边,即Father类
* Q2.father的运行类型是什么? 运行类型是Outer$2,是按顺序来编号的,当然如果下示的语句,没有{},那么其运行类型就是Father类喽
*
* 3.底层同样会创建匿名内部类,类似于这个德行
* class Outer$2 extends Father{ //此中的Outer$2这个系统分配的类名你是看不到的
@Override //当然,如果你的&&&&&&&&&&&&&&&&处什么也没有写,隐藏的这个类中也是空白的
* public void test() {
* System.out.println("匿名内部类重写了test方法");
* }
* }
*
* 4.同时也直接返回了 匿名内部类 Outer$2 的对象 给引用father
5.注意,要记住此中的 ("kerwin")的参数列表会传递给构造器,输出你在构造器中想输出的语句
*/
Father father = new Father("Kerwin"){
//&&&&&&&&&&&&&&&&&&&&&
@Override
public void test() {
System.out.println("匿名内部类重写了test方法");
}
};
System.out.println("father对象的运行类型是 "+father.getClass());//father对象的运行类型是 class Outer$2
//当然,如果你不加大括号,那么father.getClass()得到的结果就是Father喽
//接下来我们测试调用下test(),看能不能输出预期的结果
father.test();//输出了“匿名内部类重写了test方法”,和预期设想一致
/**
匿名内部类简言之
* 就是有个系统自动分配名字的类来帮你实现所需要实现的功能的对象,然后把这个对象的地址返回给你自己命名的对象名,
* 然后匿名内部类自我牺牲,但创建的对象还能被你命名的引用来调用,
* 匿名类可能是实现了接口的类或是实现继承重写的类
*/
//基于抽象类的匿名内部类
//此时就必须去实现抽象方法eat()
Animal animal = new Animal(){
/**
* animal的编译类型是Animal
* animal的运行类型是 匿名内部类,类名可以通过getClass来获取到,此时为Outer$3
*/
// ************* 注意,抽象类在这块儿必须实现哈,不同于普通类 *******************
@Override
void eat() {
System.out.println("小狗吃东西");
}
};
//尝试调用eat()运行下
animal.eat();
System.out.println(animal.getClass());
}
}
interface Ifulture{//接口
public void JustSmile();
}
/* 如果用传统方式实现,就要像下面一样,需要一次性地输出一些语句,就要对应地定义许多类,操作繁琐,由此催生出了匿名内部类的诞生
class Tiger implements Ifulture{
@Override
public void JustSmile() {
System.out.println("心有猛虎,细嗅蔷薇");
}
}*/
/*class Horse implements Ifulture{
@Override
public void JustSmile() {
System.out.println("马儿呲着大嘴在笑你.....");
}
}*/
class Father{
public Father(String name) {//构造器
System.out.println("Father类的构造器被调用,接收到name是\t"+name);
}
public void test(){//方法
}
}
//上面的Father类不是一个抽象类,所以其中的test()方法不实现也没关系,但像下面的抽象类Animal,写成基于类的匿名内部类就必须要实现喽
abstract class Animal{//抽象类
abstract void eat();
}
class Father{
public Father(String name) {//构造器
}
public void test(){//方法
}
}
匿名内部类细节
匿名内部类的语法比较特别,请大家注意,因为匿名内部类既是一个类的定义,同时它也是一个对象,因此,从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法
public class Youth {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.f1();
}
}
class Outer05{
private int youth =658;
public void f1(){
//1.创建一个基于类的匿名内部类
A a = new A(){
@Override
public void fighting() {
System.out.println("匿名内部类----习惯成自然就好了");//**
}
};
a.fighting();//此处涉及到动态绑定,其真实的运行类型是Outer$1,所以会进到**处,而不是*处
//当然,如果你没在匿名内部类里重写,就会按照继承关系,输出原先的“享受无法回避的痛苦”
//2.也可以直接调用,新写的匿名内部类,和上面的不同,这种比上面那种还懒,连接收都不想接收
//直接new了,重写之后,接一个.fighting(); hhhhh懒到极致了属于是
//.fighting 前面这一堆,其实就是个对象,之前不说了,匿名内部类既是一个类的定义,同时也是一个对象嘛.....
//不管.fighting 前写的多么复杂,其实最后就是一个匿名内部类的对象嘛hh,.fighting跟它的运行类型绑定,最后输出重写的后的结果噻
//.fighting()中如果之前写了可以传参的方法,也这儿也是对应的可以完善好匿名内部类之后,直接.fighting(参数)
new A(){
@Override
public void fighting() {
System.out.println("直接调用,输出了重写了之后的fighting方法");
}
@Override
public void fighting(String name) {
System.out.println("在匿名内部类中重写了fighting的有参方法,参数是"+name);
}
}.fighting();
}
}
class A{//类
public void fighting(){
System.out.println("享受无法回避的痛苦!!");//*
}
public void fighting(String name){
System.out.println("fighting的有参方法,传进来的参数是"+name);
}
}
- 匿名内部类可以直接访问外部类的所有成员,包含私有的
public class Youth {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.f1();
}
}
class Outer05{
private int youth =658;
public void f1(){
//创建一个基于类的匿名内部类
A a = new A(){
@Override
public void fighting() {
System.out.println("匿名内部类----我要访问外部类的所有成员\t"+youth);//此时访问外部类的私有属性
}
};
a.fighting();
}
}
class A{//类
public void fighting(){
System.out.println("享受无法回避的痛苦!!");//*
}
public void fighting(String name){
System.out.println("fighting的有参方法,传进来的参数是"+name);
}
}
- 不能添加访问修饰符,因为它的地位就是一个局部变量
A a = new A(){
...
}
//这时候对于之前写的匿名内部类,不要在new 之前加public 等..eg A a = public new A(){}//哐哐给你报错,懂?
- 作用域:仅仅在定义它的方法或代码块中
- 匿名内部类---访问---外部类成员【访问方式:直接访问】
-
外部其他类---不能访问--->匿名内部类(因为 匿名内部类地位是一个局部变量)
-
如果外部类和内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
public class Youth {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.f1();
System.out.println("main方法中 outer05 hashcode ="+outer05);
}
}
class Outer05{
private int youth =658;
public void f1(){
//创建一个基于类的匿名内部类
A a = new A(){
private int youth = 5888;
@Override
public void fighting() {
//遵循就近原则,外部类和匿名内部类成员重名时,此时访问匿名内部类中的youth即5888
System.out.println("匿名内部类----我要访问匿名内部类中的重名成员\t"+youth);
//如果,想访问外部类的同名成员,则应当按照外部类.this.成员去访问
System.out.println("我要访问外部类中的重名成员"+Outer05.this.youth);//此时输出658
//此中的Outer.this就是调用f1()的对象
System.out.println("Outer05.this hashcode="+Outer05.this);
}
};
a.fighting();
}
}
class A{//类
public void fighting(){
System.out.println("享受无法回避的痛苦!!");//*
}
public void fighting(String name){
System.out.println("fighting的有参方法,传进来的参数是"+name);
}
}
//最后的输出结果 由之后两句可以看出此例中的Outer05.this就是outer
匿名内部类----我要访问匿名内部类中的重名成员 5888
我要访问外部类中的重名成员658
Outer05.this hashcode=Outer05@1b6d3586
main方法中 outer05 hashcode =Outer05@1b6d3586
匿名内部类的最佳实践
- 当作实参传递,简洁高效
public class Youth {
public static void main(String[] args) {
//当作实参直接传递,简洁高效----modern 写法
f1(new IA() {//可以通过new 选中IA{....}即可快捷生成,传递的是一个匿名内部类,其本质是个对象
@Override
public void show() {
System.out.println("这是你变好的痕迹hhh");
}
});
//传统写法 先写个类(Attitude)去实现IA, 然后f1(new 所写的类)----->这种方式叫做硬编码(不推荐),
// 除非要反复地用Attitude类,否则就显得很死板了,如果只是用一次,上面的modern写法就很nice了
f1(new Attitude());
}
//静态方法,形参是接口类型
public static void f1(IA ia){
ia.show();
}
}
interface IA{
void show();//等同于public abstract void show()
}
class Attitude implements IA{
@Override
public void show() {
System.out.println("这是传统写法下,重写的show方法");
}
}
- 情景实操
public class Journey {
public static void main(String[] args) {
//测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
//再传入另一个匿名内部类(对象),打印: 小伙伴上课了
CellPhone cellPhone = new CellPhone();
//剖析下,具体的实现过程
/**
* 1.cellPhone.alarmClock()中()内传递的是实现了Bell接口的匿名内部类,名字就类似于Journey$1
* 2.这个匿名内部类中重写了ring()方法
* 3.然后将其整体传给了Bell类型的bell,即:
* Bell bell = new Bell() {
*
* @Override
* public void ring() {
* System.out.println("懒猪起床了");
* }
* }
* 此时,其编译类型是Bell,但运行类型是匿名内部类,可以在CellPhone类中
* 加一行打印语句.getClass.可以查到对应的运行类型
*/
cellPhone.alarmClock(new Bell() {//采用modern写法,直接在()里new 快捷生成,然后在重写的方法的方法体中,输入指定的输出语句
@Override
public void ring() {
System.out.println("懒猪起床了");
}
});
cellPhone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("小伙伴去上课啦!!!");
}
});
}
}
interface Bell{//接口
//方法
void ring();//等同于public abstract void ring();
// public abstract void run();
}
class CellPhone {//类
//注意!!bell接收之后,编译类型不变,是Bell,但运行类型是每次都随着一个个匿名内部类的诞生毁灭而不断变化的,
// 此例中的运行类型会按照系统分配的隐藏类名Journey$1、Journey$2、Journey$3....不断变化
public void alarmClock(Bell bell){//形参是Bell接口类型
//打印出来,查看其运行类型
System.out.println(bell.getClass());
bell.ring();//注意!!形参传进来了之后,这里会实现动态绑定,动态绑定机制是跟运行类型绑定的哈
// 这个动态绑定会导致重新回到匿名内部类的ring(),因为传入形参的运行类型是依次是Journey$1类、Journey$2类,可不就要回去嘛
//回去之后,就对应地执行其重写方法,依次输出 "懒猪起床了" "小伙伴上课啦"
}
}
感谢看到这里的铁子(或许可以加们?),我是Java小菜鸟kerwin,想坚持日更Java相关的知识点,倘若我的文章对正在学习Java的小伙伴有所帮助,那我可太开心啦hhh,文章中若有不当之处,还烦请诸位大佬指出,也欢迎各位在评论区或者私信我相关的文章改进建议,那就明天再见啦,铁子们,晚安~~
标签:Java,内部,void,视图,class,youth,匿名,CSDN,public From: https://blog.csdn.net/Kerwin_D/article/details/141113935