内部类的概念
认识内部类
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称之为内部类inner class,嵌套该内部类的类称为外部类。就像双重for循环,外部for循环里面包含着另一个内层循环。内部类是类的第5大成员,[属性,方法,构造器,代码块,内部类]
内部类的最大特点就是可以直接访问外部类的私有属性,且可以体现类与类之间的包含关系
基本语法
成员内部类的基本语法演示:
class Outer{//外部类
class inner{//内部类
}
}
class other{}//其他类
代码演示:
public class Outer {//外部类
private int x = 21;
class inner{//内部类
public void innerMethod(){
System.out.println(x);//直接调用外部类的私有属性
}
}
}
class other{//其他类
}
内部类的分类
内部类分为4种:分别在局部位置和成员位置,根据不同的修饰或命名有不同的叫法
- 定义在外部类的局部位置上 (比如定义在方法体内或者代码块内)
- 局部内部类(有类名的)
- 匿名内部类(没类名的)
- 定义在外部类的成员位置上 (和构造方法代码块同一个水平位置上的)
- 成员内部类(没static修饰)
- 静态内部类(使用static修饰的)
局部内部类
- 局部内部类是定义在外部类的局部位置的类,比如外部类的方法体中,或者代码块中。且有类名
public class other {//其他类
}
class outer{//外部类
public void m1(){
class inner{//局部内部类
}
}
}
局部内部类的特点
- 局部内部类可以直接访问外部类的所有成员,包含私有的
- 不能添加访问修饰符,因为它相当于一个局部变量。局部变量是不能使用修饰符的,但是可以使用final修饰,防止别人继承它。
- 局部内部类的作用域仅仅在定义它的方法或者代码中。
- 局部内部类要访问外部类的成员,可以直接访问。不用带前缀
- 外部类访问局部内部类的成员,需要创建局部内部类的实例对象。且只能在局部内部类的作用域内访问
- 其他类不能直接访问或者创建局部内部类的对象访问局部内部类的成员。
- 如果外部类的成员和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用 外部类名.this.成员ming 去访问。this代表谁调用就指向谁,类名顾名思义
特点演示:
public class other {//其他类
public static void main(String[] args) {
new outer().m1();
}
}
class outer{//外部类
private int num = 21;
public void m1(){
final class inner{//局部内部类-2.无法访问修饰符,只能修饰final
private int num = 888;
public void m2inner(){
System.out.println(num);//1.局部内部类直接方法外部类的成员.4。如左
System.out.println("局部内部类的num"+num+"外部类的num"+outer.this.num);//7.外部类和内部类成员重名怎么区分调用
}
public int a = 1;
}
//5.外部类访问内部局部类的成员,需要创建内部局部类实例化对象。且需在作用域之内
new inner().m2inner();
}
//3,无法在局部内部类的作用域范围外创建
//new inner();
}
匿名内部类
匿名内部类本质就是基于接口或者其他类,实现或者继承,然后创建一个新类,且实例化返回一个对象
匿名内部类存在的位置和局部内部类相同,都在外部类的方法中或者代码块中。
匿名内部类的特点
- 本质上还是个类
- 也是一个内部类
- 匿名内部类没有名字,但是编译器会给一个名字,但是不重要
- 匿名内部类只能使用一次
- 匿名内部类实例化的对象可以重复使用
匿名内部类的语法
new 类或者接口(参数列表){
类体
};
举例说明及引出
在学习接口后,了解了各个实现了同一个接口的类,可以重写接口的方法。
比如现在有一个接口IA 他有一个抽象方法cry,老虎类实现了这个接口
外部类的m1方法中需要调用老虎类的cry方法
在未学习匿名内部类前应该是这样实现的
class outer{//外部类
public void m1(){
new tiger().cry();
}
}
interface IA{
void cry();
}
class tiger implements IA{
@Override
public void cry() {
System.out.println("老虎嗷嗷叫...");
}
}
但是因为就调用一次,但是还得去创建一个新类。有点浪费内存,所以可以使用匿名内部类实现。
语法解释:
public void m1(){
IA tiger = new IA() {
@Override
public void cry() {
System.out.println("老虎嗷嗷叫");
}
};
tiger.cry();
}
这一段就是在外部类的方法中使用了匿名内部类,在这一段代码中做了这么几件事:
- 底层创建了一个类,且实现了IA接口,只不过这个类,不同于正常类,有点虚幻
class 匿名 implement IA{}
- 在{}中的是这个类的类体,这里跟其他类一样,这个类在类体中实现了IA接口的cry方法
- 以此类为模板,实例化了一个对象。并且将此对象的地址给到 tiger
根据之前学到的知识,等号左边是编译类型,等号右边是运行类型。
根据上面的代码可以看到 编译类似是IA接口类型,但是运行类型却不能直接认为是IA接口。运行类型是底层创建的匿名内部类
因为这个匿名内部类,没有我们定义的类名。所以它只能使用一次,而我们将他实例化的对象地址给到了tiger。后面也无法再实例化一个对象了。所以说是匿名内部类只能使用一次,但是它已经实例化的对象,我们可以通过它的引用tiger,反复调用。
此致就解决了只需要使用一次还要创建一个类的问题。
完整代码:
public class other {//其他类
public static void main(String[] args) {
new outer().m1();
}
}
class outer{//外部类
public void m1(){
IA tiger = new IA() {//匿名内部类
@Override
public void cry() {
System.out.println("老虎嗷嗷叫");
}
};
tiger.cry();
tiger.cry();
tiger.cry();
}
}
interface IA{
void cry();
}
上面是基于接口举例的匿名内部类,下面用基于普通类的案例演示下
基于接口的匿名类,可以看作是匿名内部类实现了接口。那么基于普通类,可以看作是匿名内部类继承于普通类extends
public class other {//其他类
public static void main(String[] args) {
new outer().m1();
}
}
class outer{//外部类
public void m1(){
Father father = new Father(){//匿名内部类
};
}
}
class Father{
public Father() {
System.out.println("父类的构造器被调用");
}
}
通过运行后输出-父类的构造器被调用,这说明匿名内部类确实会和子类一样调用父类的构造器的。
匿名内部类的使用注意点
- 匿名内部类的语法看起来比较复杂,但是本质上其实就是一个类的定义且返回一个对象,也就是说匿名内部类的语法是定义类和返回一个对象。这两者是一体的
演示:类定义理解比较简单,返回一个对象可以这样理解,不管匿名内部类的语法多长多复杂,直接看作是一个临时对象即可。直接调用需要的东西
class outer{//外部类
public void m1(){
new Father(){
}.hi();//因为是定义和返回对象一体的,所以可以直接看作是临时对象,直接调用
}
}
class Father{
public Father() {
System.out.println("父类的构造器被调用");
}
public void hi(){
System.out.println("hi,");
}
}
其他特点和注意事项与局部内部类差不多
- 不能添加访问修饰符,因为它相当于一个局部变量
- 可以直接访问外部类的成员
- 作用域只在定义它的方法或代码块中‘
- 外部类其他类不能访问匿名内部类
- 外部类和匿名内部类的成员重名时,默认是就近原则。想访问外部类的成员可以 类名.this.成员
匿名内部类的实践
第一题
- 将匿名内部类 作为参数使用(因为匿名内部类即是类也是一个对象)
class outer{//外部类
public void test(IA ia){
ia.print();
}
public void m1(){
test(new IA() {//传入了一个匿名内部类
@Override
public void print() {
System.out.println("匿名内部类的print");
}
});
}
}
interface IA{
void print();
}
只是看起来理解性不太好,但是解决了只使用一次还要创建类的麻烦。
把这一部分看作是一个整体的参数就行
第二题
- 有一个铃声接口Bell 它有ring方法。
- 有一个手机类CallPhone,它有闹钟功能 AlarmClock方法,参数是Bell
- 测试手机类的闹钟功能,通过匿名内部类作为参数打印“快点起床”
- 再传入另一个匿名内部类打印“再不起床,扣积分”
比较简单,直接上代码
public class testinners2 {
public static void main(String[] args) {
new CallPhone().AlarmClock(new Bell() {//临时对象调用ALarm方法,传入匿名对象(实现了Bell接口)
@Override
public void ring() {
System.out.println("快点起床");
}
});
new CallPhone().AlarmClock(new Bell() {
@Override
public void ring() {
System.out.println("再不起床,扣积分");
}
});
}
}
interface Bell{
void ring();
}
class CallPhone{
public void AlarmClock(Bell bell){
bell.ring();
}
}
成员内部类
成员内部类定义在外部类的成员位置,和属性 方法 构造器是同级别,且没有static修饰
- 成员内部类和其他内部类一样可以直接访问外部类的所有成员。
- 因为成员内部类也是外部类的一个成员,跟属性方法一样,所以成员内部类可以添加任意访问修饰符
- 成员内部类的作用域和其他外部类的成员一样,是外部类的整个类体
- 外部类访问内部类,需要创建对象再访问
- 外部类和成员内部类成员重名,和前面一样,默认就近原则。区分直接外部类名.this.成员名
- 外部类其他类访问成员内部类,有两种方法
成员内部类演示:
public class other {//外部其他类
public static void main(String[] args) {
//外部其他类访问成员内部类 方法一:直接创建
outer1 ou2 = new outer1();
outer1.Inner2 inner3 = ou2.new Inner2();
inner3.innerme();
//外部其他类访问成员内部类 方法二:使用外部类返回成员内部类的对象
outer1.Inner2 inner4 = ou2.getinner2();
inner4.innerme();
}
}
class outer1{
private int num = 90;
public class Inner2{//成员内部类
public void innerme(){
System.out.println(num);//直接使用外部类的成员
}
}
//外部类访问成员内部类
public Inner2 getinner2(){
Inner2 inner2 = new Inner2();
inner2.innerme();
return inner2;
}
}
注意点:外部类其他类访问成员内部类的方法:
第一种:先创建外部类的对象 outer1 ou2 = new outer1();
然后通过类名.成员内部类名作为编译类型 且使用外部类的对象去创建成员内部类的对象 outer1.Inner2 inner3 = ou2.new Inner2();
第二种:就是外部类直接将创建成员内部类封装在方法里,返回创建好的对象,给个引用即可。
静态内部类
静态内部类也是定义在外部类的成员位置上,但是与成员内部类的区别就是静态内部类加了static修饰
- 静态内部类,因为是静态的所以要遵守静态只能直接访问静态的原则。它只能直接访问外部类的所有静态成员
- 同理静态内部类也是个成员,可以添加访问修饰符
- 作用域也是整个外部类体
- 外部类访问静态内部类,创建对象再访问
- 外部其他类访问静态内部类,有两种访问方式{直接创建和封装方法创建}
- 静态内部类和外部类成员重名时,默认就近原则,要区分可以使用外部类.属性名。因为只能调用静态所以可以直接类名.不用加this
静态内部类演示:
public class other {//外部其他类
public static void main(String[] args) {
//外部其他类访问静态内部类 第一种:直接创建对象
outer1.Inner5 inner88 = new outer1.Inner5();
inner88.print1();
//外部其他类访问静态内部类 第二种:使用外部类封装返回的对象
outer1.Inner5 inner99 = outer1.getInner5();
inner99.print1();
}
}
class outer1{
private static int num = 90;
public static class Inner5{//静态内部类
private static int num = 88;
public void print1(){
System.out.println(num +" "+outer1.num);//直接调用和重名问题
}
}
public void m1(){
Inner5 inner5 = new Inner5();
inner5.print1();
}
public static Inner5 getInner5(){
return new Inner5();
}
}
注意点:静态内部类和成员内部类在外部其他类的调用语法稍有不同,且都要注意修饰符范围
静态内部类也是静态,需要遵守静态只能直接调用静态的规定。
重点:匿名内部类要理解透彻,它既是一个类的定义同时也是一个对象。
内部类练习
- 下列代码会输出什么?
public class Test1 {
public Test1(){
inner s1 = new inner();
s1.a = 10;
inner s2 = new inner();
System.out.println(s2.a);
}
public static class inner{
public int a = 5;
}
public static void main(String[] args) {
Test1 t1 = new Test1();
inner s3 = new inner();
System.out.println(s3.a);
}
}
5
5