一、内部类
package com.alice.innerclass_.innerclass01;
/*
一个类的内部又完整的嵌套了另外的一个类结构,被嵌套的类称内部类
inner class,嵌套其他类的类称为外部类outer class,是我们类的五大成员(思考:类的五大成员是哪些?),内部类
最大的特点是可以直接访问私有属性,并且可以体现类与类之间的包含关系。
基本语法:
class Outer { // 外部类
class Inner { // 内部类
}
}
class Other{} // 外部其他类
InnerClass01.java
类的五大成员:属性,方法,构造器,代码块,内部类
内部类的分类
定义在外部类局部位置上(比如方法内):
1、局部内部类(有类名)
2、匿名内部类(没有类名,重点!!!)
定义在外部类的成员位置上面:
1、成员内部类(没有使用static修饰)
2、静态内部类(使用static修饰)
*/
public class Innerclass01 { // 外部其他类
}
class Outer { // 外部类
private int n1 = 100; // 属性
public Outer(int n1) { // 构造器
this.n1 = n1;
}
public void m1() { // 方法
System.out.println("m1()");
}
{ // 代码块
System.out.println("代码块...");
}
class Inner {
// 内部类
}
}
二、局部内部类
package com.alice.innerclass_.innerclass01;
public class LocalInnerClass02 {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.m1();
}
}
class Outer02 {
// 外部类
private int n1 = 100;
private void m2() {
System.out.println("Outer02 m2()");
}
public void m1() {
// 局部内部类是定义在外部类的局部位置,通常是在方法中
// 不能添加访问修饰符,但是可以使用final修饰
final class Inner02 { // 也就是这里不能加上public,但是可以加上final,加上这个表示不能够再被继承
// 局部内部类的作用域仅仅是在定义它的方法或者代码块中的
// 可以直接访问外部类的所有成员,包含私有的
public void f1() {
// 局部内部类的本质还是一个类
System.out.println("n1=" + n1); // 局部内部类可以直接访问外部类的成员,比如下面的n1和m2
// 理论上来说,私有的属性只能本类访问,但是因为f1是Outer02中的,所以也能够访问
// 外部类的所有的成员
// 所以这里访问外部方法也是可以的
m2();
}
}
// class Inner03 extends Inner02 { // 因为这里Inner02添加了final,所以这里不能够被继承
//
// }
// 代码块也是可以定义局部内部类的
{
class Inner03 {}
}
// 外部类在方法中,可以创建Inner02对象,然后调用方法即可
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
/*
局部内部类的使用LocalInnerClass.java
说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
1、可以直接访问外部类的所有成员,包含私有的
2、不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的,但是可以使用final修饰,因为局部变量
也可以使用final
3、作用域:仅仅是定义它的方法或者代码块中。
4、局部内部类--访问--外部类的成员
访问的方式是直接方法
5、外部类访问局部内部类的成员,访问方式是创建对象再访问。并且必须是在作用域内。
小结:
局部内部类定义的位置,作用域,本质还是类。记住这三句话就好。
*/
package com.alice.innerclass_.innerclass01;
public class LocalInnerClass03 { // 外部其他类
// 外部其他类不能访问局部内部类,因为局部内部类的地位是一个局部变量
public static void main(String[] args) {
Outer03 outer03 = new Outer03();
outer03.m1();
System.out.println("outer03的哈希代码:" + outer03);
}
}
/*
*/
class Outer03 {
private int n1 = 100;
public void m1() {
final class Inner02 {
private int n1 = 800; // 局部内部类和外部类的属性重名了。
public void f1() {
System.out.println("n1 = " + n1);
// 这个时候n1是800还是100呢?这个时候是局部内部类的800
// 如果想要访问到外部的100呢?
System.out.println("访问外部类的n1 = " + Outer03.this.n1); // 如果直接Outer03.n1这样要求是一个静态的
// 这里使用外部类的对象访问
// 这里的Outer03.this其实就是外部类的一个对象,谁在调用,这个this就是谁outer03.m1();
// 哈希
System.out.println("Outer03.this的哈希代码:" + Outer03.this); // 说明这两个是相同的,同一个对象
System.out.println(this); // 这里的是inner02的
}
}
Inner02 inner02 = new Inner02();
inner02.f1();
System.out.println(inner02); // 这里是inner02
}
}
/*
6、外部其他类不能访问局部内部类,因为局部内部类的地方相当于是一个局部变量
7、如果外部类和局部内部类的成员重名时,默认遵循的是就近原则,如果想要访问外部类的成员,则可以使用
外部类名.this.成员去访问
*/
三、匿名内部类
package com.alice.innerclass_.innerclass01;
public class AnonymousInnerClass04 { // 外部其他类
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}
class Outer04 {//外部类
private int n1 = 10;
public void method() {
// 基于接口的匿名内部类
// 1、需求:想要使用接口IA,并创建对象
// 2、传统方式就是写一个类实现该接口,并且创建对象--->Test一实现接口IA
// IA tiger = new Tiger(); // 接口可以指向实现了该接口的类的对象实例
// tiger.cry(); // 这就是传统的写法
// // 3、现在有一个新的需求,就是我们的这里对象不想创建tiger,我们只使用一次就不用了
// // 需求是:希望写的tiger类只是使用一次,以后再也不使用了,如果说有这样的需求,tiger
// // 定义在这个方法有点浪费如果再来一个狗类实现IA--Test二狗类
// // 如果这样就会定义很多的类
// IA dog = new Dog();
// dog.cry();
// 而解决的办法可以使用匿名内部类
// 这里将小狗类,和老虎类全部注释
// 我们不创建这个类也可以显示老虎叫的一个使用
IA tiger = new IA() { // 接口不能直接new,但是这里添加了一个{},这里就相当于是在tiger中写的方法
@Override
public void cry() {
System.out.println("老虎叫唤");
}
};
tiger.cry(); // 这个时候相当于是使用老虎这个对象去调用这里实现的方法,和刚刚的效果是是一样的
// IA dog = new IA() {
// @Override
// public void cry() {
// System.out.println("小狗汪汪叫");
// }
// };
// dog.cry();
// 这里使用了匿名内部类来简化开发
// 我们来看这个匿名内部类有什么特点
// tiger的编译类型是什么,tiger的运行类型是什么?
// 编译类型看等号的左边,所以编译类型就是接口类型IA
// 运行类型是什么?运行类型就是匿名内部类
// 这个匿名内部类就是
/*
我们看底层就会发现这个匿名内部类是
class XXX implements IA {
@Override
public void cry() {
System.out.println("老虎叫唤");
}
}
底层有了一个类去实现了,这个底层的类的名字是什么呢!?
用外部类的名字加了一个$1
tiger的运行类型是:class com.alice.innerclass_.innerclass01.Outer04$1
*/
// 前面讲过打印出运行类名只需要使用Object的getClass()方法
System.out.println("tiger的运行类型是:" + tiger.getClass());
// 底层会分配一个类名,就是这个,用一次就没有了
// 注意前面在创建匿名内部类的时候使用了new,JDK底层创建了匿名内部类之后立刻,马上就创建了这个Outer04$1的实例
// 并且将这个地址返回给tiger这个变量,这个底层其实可以认为栈中有一个tiger指向了一个堆,堆中有一个对象实例,
// 这个对象实例的类型就是Outer04$1
// 匿名内部类使用一次就不能再使用,不是说tiger不能再使用,说的是Outer04$1
// 演示基于类的匿名内部类
Father father = new Father("jack") { // 这里的jack还是会传递给Father构造器
// 如果没有打大括号就是创建一个father对象,如果打了大括号就是一个匿名内部类
// 这里的编译类型是Father,运行类型是什么,运行类型不是Father
// Outer04$2,如果不带大括号就是Father
// 这里面就可以重写test方法了
@Override
public void test() {
System.out.println("匿名内部类重写了test方法");
}
};
// 这里可以打印出运行类型进行证明
System.out.println("father对象的运行类型 为" + father.getClass());
// father对象的运行类型 为class com.alice.innerclass_.innerclass01.Outer04$2
// 如果把之前写的dog匿名对象注释取消,这里就变成了$3了
// 底层会创建匿名内部类,长的样子也差不多
/*
class Outer04$2 extends Father {
}
*/
father.test(); // 而因为当前的运行类型是Outer04$2,所以会绑定到上面重写的匿名内部类的test
// 这是基于类的匿名内部类
// 同时也是直接返回了Outer04$2的对象
// 因为这里是一个普通的类Father,可以不用实现方法,如果是一个抽象类的就必须要实现方法了。
// 比如这里创建一个抽象类Animal
// 基于抽象类的匿名内部类
Animal animal = new Animal() {
@Override
void eat() {
System.out.println("小狗吃骨头");
}
};
// 这里的编译类型是Animal,运行类型是Outer04$3
System.out.println(animal.getClass());
animal.eat();
}
}
// Test一实现接口IA
//class Tiger implements IA {
//
// @Override
// public void cry() {
// System.out.println("老虎嗷嗷叫");
// }
//}
// Test二狗类
//class Dog implements IA {
//
// @Override
// public void cry() {
// System.out.println("小狗汪汪叫");
// }
//}
interface IA {
public void cry();
}
class Father {
public Father(String name) {
System.out.println("name = " + name);
}
public void test() {
}
}
abstract class Animal {
abstract void eat();
}
/*
匿名内部类非常重要,底层框架以及今后的开发中使用非常多
所以前面其实打了三个叹号,并且难以理解,一些书籍和视频没有将最精华的地方讲到
什么是匿名内部类?
1本质是一个类,
2还是一个内部类,
3还是一个匿名的,没有名字的,该类没有名字
匿名内部类其实还是有名字的,只是这个名字不是你能够直接看到的
而是java虚拟机给你取得一个名字,
所以匿名内部类是系统给你分配的
你看不到,我们可以在底层打印出来,但是不能直接看到。
4匿名内部类同时还是一个对象,
匿名内部类是定义在外部类的局部位置,比如方法中,代码块中,并且没有类名
基本语法:
new 类或者接口(参数列表){类体};
Anonymous这个单词是匿名的意思
*/
package com.alice.innerclass_.innerclass01;
public class AnonymousInnerClassDetail05 {
public static void main(String[] args) {
new Outer05().f1();
}
}
/*
2匿名内部类的语法比较特别,匿名内部类既是一个类的定义,同时它本身也是一个对象,因此
从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,
因此可以调用匿名内部类方法。
3可以直接访问外部类的所有成员,包含私有的
4不能添加访问修饰符,因为它的地位就是一个局部变量
5作用域:仅仅在定义它的方法或者代码块中。
6匿名内部类访问外部类成员,直接访问
7外部其他类不能访问匿名内部类,因为匿名内部类的地位是一个局部变量
8如果外部类和匿名内部类的成员重名的时候,匿名内部类访问,会默认遵守就近原则,如果想要访问外部类的成员
,则可以使用,外部类名.this.成员,去访问。和之前局部内部类同理
*/
class Outer05 {
private int n1 = 99;
public void f1() {
// 创建一个基于类的匿名内部类
Person p = new Person() { // 不能添加访问修饰符,因为它的地位就是一个局部变量
@Override
public void hi() {
System.out.println("匿名内部类重写了hi方法");
System.out.println("可以直接访问外部类的所有成员,hi方法 n1 = " + n1);
}
// 如果这里没有重写就会往上面找,也就是往父类找
// 执行的是Person hi()
};
p.hi(); // p的编译类型是Person,运行类型是Outer05$1
// 运行的时候会有动态绑定,找到匿名内部类重写了hi方法输出
// 因为匿名内部类也同时是一个对象,也可以直接调用
new Person() { // 这里又创建了一个匿名内部类,这里我们不想要使用变量接收,直接点hi
@Override
public void hi() {
System.out.println("匿名内部类重写了hi方法,哈哈。。。");
}
@Override
public void ok(String str) {
super.ok(str);
}
}.ok("jack");
}
}
class Person {
public void hi() {
System.out.println("Person hi()");
}
public void ok(String str) {
System.out.println("Person ok()" + str);
}
} // 另外抽象类还有接口都可以写...
package com.alice.innerclass_.innerclass01;
//public class AnonymousInnerClass06 {
// public static void main(String[] args) {
// f1(new IL() { // 直接传递的是一个匿名内部类,也是一个对象,传统的写法应该怎么写呢?
// @Override
// public void show() {
// System.out.println("这是一幅名画。。。");
// }
// // 这种方式可以比较随意,我们更改一下这里的匿名内部类只会影响到这个局部区域
// });
// // 传统的方式
// f1(new Picture()); // 将实现了这个接口的对象传过去,这个叫做硬编码,这种硬编码不推荐,如果反复用这个对象还好
//
// }
// // 静态方法,形参是一个接口类型
// public static void f1(IL il) {
// il.show(); // 当前编译类型就是IL可以点到show
// }
//}
//// 在什么时候我们会使用到匿名内部类呢?
//// 被当作实参直接传递的时候
//
//// 接口
//interface IL {
// // 接口中有一个方法,叫做
// void show();
//}
//// 传统的方式写一个类实现IL
//class Picture implements IL {
// // 如果在这里更改会影响到所有这里的对象实例
//
// @Override
// public void show() {
// System.out.println("这是一幅名画...");
// }
//}
/*
第二题
1、有一个铃声接口Bell,里面有一个ring方法
2、有一个手机类Cellphone,具有闹钟功能alarmClock,参数是Bell类型
3、测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
4、再传入另外一个匿名内部类(对象),打印:小伙伴上课了。
*/
public class AnonymousInnerClass06 {
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.ring();
}
}
// 到此匿名内部类完成。匿名内部类涉及到了继承,多态,动态绑定机制,内部类。。
四、成员内部类
package com.alice.innerclass_.innerclass01;
public class MemberInnerClass07 {
public static void main(String[] args) {
Outer01 outer01 = new Outer01();
outer01.t1();
// 在外部其他类中使用我们的成员内部类的三种方式
// 第一种方式是这样子
// 使用外部类的对象,去调用方法
// 这里使用外部类的对象创建了一个内部类的实例返回
Outer01.Inner01 inner01 = outer01.new Inner01();
inner01.say();
// 如何去理解?最容易理解的其实是new Inner01(),但是我们这里需要知道,并没有写在外面
// 所以这里的语法需要变化,这里当作一个成员来看,所以是outer01.new Inner01();
// 第二种方式是在外部类中写一个方法然后将这个Inner01的对象实例即可
Outer01.Inner01 inner08Instance = outer01.getInner01Instance();
inner08Instance.say();
}
}
class Outer01 {
// 外部类
private int n1 = 10;
public String name = "张三";
private void hi() {
System.out.println("hi()方法...");
}
public class Inner01 {
private double sal = 99.9;
// 可以直接访问外部类的所有成员,包含私有的
public void say() {
System.out.println("Outer01的n1 = " + n1 + " Outer01的name = " + name);
hi(); // 可以直接调用外部类的所有成员不管是不是私有的,方法还是属性
}
}
public void t1() {
// 使用成员内部类
Inner01 inner01 = new Inner01();
inner01.say();
System.out.println(inner01.sal); // 外部类访问内部类
}
public Inner01 getInner01Instance() { // 返回一个成员内部类的实例
return new Inner01();
}
}
/*
成员内部类是定义在外部类的成员位置,并且没有static修饰
1、可以直接访问外部类的所有成员,包括私有的
2、可以添加任意访问修饰符
public protected 默认 private,因为它的地位就是一个成员
作用域
3和外部类的其他成员一样,为整个类体
比如前面的案例,在外部类的成员方法中创建成员内部类对象,再调用方法
4成员内部类访问外部类,访问方法是直接访问
5外部类访问内部类,访问方式是创建对象之后再访问
6外部其他类访问成员内部类
7如果外部类和内部类的成员重名的时候,内部类访问的话,默认遵循就近原则,如果想要访问外部类的成员,则可以使用
外部类名.this.成员去访问,和以前一样。
成员内部类结束
*/
五、静态内部类
package com.alice.innerclass_.innerclass01;
public class StaticInnerClass08 {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
outer10.m1();
// 外部其他类访问静态内部类
// 方式一
Outer10.Inner10 inner10 = new Outer10.Inner10();
inner10.say();
// 通过类名访问的静态内部类是可以的。因为静态内部类是可以通过类名直接访问,前提是需要有这种访问权限
// 方式二
// 编写一个访问返回静态内部类的一个实例
Outer10.Inner10 inner101 = outer10.getInner10();
inner101.say();
Outer10.Inner10 inner10_ = Outer10.getInner10_();
inner10_.say();
}
}
class Outer10 {
private int n1 = 10;
private static String name = "张三";
private static void cry() {
}
// Inner10就是静态内部类
// 1、是放在外部类的成员位置
// 2、使用static修饰
public static class Inner10 { // 由前面的知识可以知道,静态的只能访问静态的。
private static String name = "贾宝玉";
public void say() {
// System.out.println(n1); // 这里n1是一个非静态的,使用静态的访问非静态会报错
System.out.println(name); // 所以访问静态的是可以的。如果静态内部类中也有同名的成员则会就近原则
System.out.println(Outer10.name); // 因为是一个static的,所以这里直接类名输出即可
cry(); // 访问静态方法也可以直接访问
}
}
public void m1() { // 外部类访问静态内部类可以创建对象然后再访问
Inner10 inner10 = new Inner10(); // 作用域为整个类体,因为是成员
inner10.say();
}
public Inner10 getInner10() {
return new Inner10();
}
public static Inner10 getInner10_() {
return new Inner10();
}
}
/*
静态内部类的使用
说明:静态内部类是定义在外部类的成员位置,并且又static修饰
1、可以直接访问外部类的所有静态成员,包含私有的,但是不能直接访问非静态成员
2、可以添加任意访问修饰符public,protected,默认,private,因为它的地位就是一个成员
3、作用域:同其他的成员,为整个类体
4、静态内部类想要访问外部类比如静态属性可以直接访问
5、外部类访问静态内部类可以创建对象,然后再访问
6、外部其他类如何访问静态内部类呢?
7、如果外部类和静态内部类的成员重名,静态内部类访问的时候,默认遵循的是就近原则,如果想要
访问到外部类成员,则可以使用外部类名.成员的方式访问。
小结:
内部类由四种:
局部内部类
匿名内部类
这两个内部类都是放到方法或者代码块中
还有成员内部类,静态内部类
重点需要掌握匿名内部类
成员内部类和静态内部类放到成员位置
*/
六、结束
package com.alice.innerclass_.innerclass01;
public class InnerClass09 {
public static void main(String[] args) {
Test t = new Test();
Test.Inner inner = t.new Inner();
System.out.println(inner.a);
}
}
/*
关于内部类的题目
*/
class Test {
public Test() {
Inner s1 = new Inner();
s1.a = 10;
Inner s2 = new Inner();
System.out.println(s2.a);
}
class Inner {
public int a = 5;
}
}
// 5 5,到此为止面向对象结束