12.java基础-内部类

★内部类

把类定义在其他类的内部,这个类就被称为内部类。

举例:在类A中定义了一个类B,类B就是内部类。

内部类的访问特点

内部类可以直接访问外部类的成员,包括私有。
外部类要访问内部类的成员,必须创建对象。
package 成员内部类;

/*
	成员内部类的修饰符:
		private 为了保证数据的安全性
		static 为了方便访问数据
			注意:静态内部类访问的外部类数据必须用静态修饰。
*/
public class Outer {
	private int num = 10;
	private static int staticNum= 100;

	protected class Inner {
		public void show() {
			System.out.println(num);
			System.out.println(staticNum);
		}

		//非静态内部类中不能有静态成员变量和方法
		/*static int k = 0;
		public static void show3() {
			System.out.println("show3");
		}
		*/
	}

	//内部类用静态修饰是因为内部类可以看成是外部类的成员
	public static  class StaticInner {

		public void show() {
			//System.out.println(num);
			//被静态修饰的成员内部类只能访问外部类的静态成员
			System.out.println(staticNum);
		}

		//静态内部类中可以有静态成员变量和方法
		static int k =0;
		public static void show2() {
			//被静态修饰的成员内部类只能访问外部类的静态成员
			//System.out.println(num);
			System.out.println(staticNum);
		}
	}
}

class InnerClassDemo4 {
	public static void main(String[] args) {
		//成员内部类被静态修饰后的访问方式是:
		//格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
		//限定的静态内部类
		Outer.StaticInner staticInner = new Outer.StaticInner();
		staticInner.show();
		staticInner.show2();
		//show2()的另一种调用方式
		Outer.StaticInner.show2();

		//成员内部类的访问方式是:
		//格式:外部类名.内部类名 对象名 = new 外部类名().内部类名();
		//非静态内部类
		Outer.Inner oi = new Outer().new Inner();
		oi.show();
	}
}

思考一下:为什么被静态修饰的成员内部类只能访问外部类的静态成员?
首先回顾一下静态内部类的加载时机和加载过程

类的加载时机:
1.new 一个类的时候
2.调用类的静态变量
3.调用类的静态方法
4.调用类的静态内部类

静态内部类的加载时机:静态内部类的加载时机是在程序中调用静态内部类的时候加载的,和外部类的加载没有必然关系。如果在程序中单纯的使用外部类,并不会触发静态内部类的加载。

加载过程:在加载静态内部类的时候 发现外部类还没有加载,那么就会先加载外部类,加载外部类的静态变量、静态代码块、非静态变量等等,加载完外部类之后,再加载静态内部类(初始化静态变量和静态代码块等)只有这样才能保证,静态内部类一被加载就可以访问到外部的静态变量。

静态内部类优先于外部类的非静态变量加载,如果静态内部类加载完成,访问非静态变量,但是非静态变量还没加载完成,那么就会存在问题。
https://blog.csdn.net/xiangqianzou_liu/article/details/105441903

内部类位置:按内部类在类中位置

按照内部类在类中定义的位置不同,可以分为如下两种格式:

成员位置(成员内部类)
局部位置:方法中(局部内部类)
/*
	内部类位置
		成员位置:在成员位置定义的类,被称为成员内部类。	
		局部位置:在局部位置定义的类,被称为局部内部类。	
*/
class Outer {
	private int num = 10;

	//成员位置
	class MemberInner {
		private int i=0;
	}

	public void method() {
		Outer.MemberInner oi = new Outer().new MemberInner();
		Outer.MemberInner memberInner = new Outer.MemberInner();
		System.out.println(memberInner.i);
		System.out.println(oi.i);
		//局部位置
		class LocalInner {
			private int j=0;
		}
	}
	public static void main(String[] args) {
		new Outer().method();
	}
}

☆加载时机:第一次使用被加载

内部类是延时加载的,也就是说只会在第一次使用时加载。
不使用就不加载,所以可以很好的实现单例模式。

☆内部类注意事项

1.加载内部类会触发加载外部类

实例化外部类,调用外部类的静态方法、静态变量、静态内部类,会加载外部类。

2.加载外部类不会触发加载内部类

外部类初次加载,会初始化静态变量、静态代码块、静态方法。
但不会加载内部类和静态内部类,静态内部类和非静态内部类一样,都是在被调用时才会被加载。

3.静态成员内部类只能访问外部类的静态成员

内部类用静态修饰是因为内部类可以看成是外部类的成员。
被静态修饰的成员内部类只能访问外部类的静态成员。

直接调用外部类的静态内部类和外部类的静态变量,静态方法都会让外部类被加载。

如果只调用外部类的静态变量,静态方法时,是不会让静态内部类的被加载。

5.非静态内部类里面不能有静态成员和方法

package 内部类;

import java.util.Random;

public class OuterClass {
    public static long OUTER_DATE = System.currentTimeMillis();

    static {
        System.out.println("外部类静态块加载时间:" + 
                           			System.currentTimeMillis());
    }

    public OuterClass() {
        timeElapsed();
        System.out.println("外部类静态变量加载时间:" + OUTER_DATE);
        System.out.println("外部类构造函数时间:" + 
                           	System.currentTimeMillis());
    }

    static class InnerStaticClass {
        public static long INNER_STATIC_DATE = System.currentTimeMillis();
    }

    class InnerClass {
        public long INNER_DATE = 0;

        public InnerClass() {
            timeElapsed();
            INNER_DATE = System.currentTimeMillis();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        OuterClass outer = new OuterClass();

        Thread.sleep(1000);
        System.out.println("非静态内部类加载时间" 
                           		+ outer.new InnerClass().INNER_DATE);
        Thread.sleep(1000);
        System.out.println("静态内部类加载时间:" 
                          		 + InnerStaticClass.INNER_STATIC_DATE);
    }

    //单纯的为了耗时,来扩大时间差异
    private void timeElapsed() {
        for (int i = 0; i < 10000000; i++) {
            int a = new Random(100).nextInt(), 
            b = new Random(100).nextInt();
            a = a + b;
        }
    }
}
外部类静态块加载时间: 1627945301254
外部类静态变量加载时间:1627945301254
外部类构造函数时间:1627945301630
非静态内部类加载时间1627945302978
静态内部类加载时间:1627945303990
package 内部类;

import java.util.Random;

public class OuterClass2 {
    public static long OUTER_DATE = System.currentTimeMillis();

    static {
        System.out.println("外部类静态块加载时间:" + System.currentTimeMillis());
    }

    public OuterClass2() {
        timeElapsed();

        System.out.println("外部类构造函数时间:" + System.currentTimeMillis());
    }

    static class InnerStaticClass {
        public static long INNER_STATIC_DATE = System.currentTimeMillis();

        public static long show(){
            return INNER_STATIC_DATE;
        }
    }

    class InnerClass {
        public long INNER_DATE = 0;

        public InnerClass() {
            timeElapsed();
            INNER_DATE = System.currentTimeMillis();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("外部类静态变量加载时间:" + OuterClass2.OUTER_DATE);
        //不管把静态内部类注释还是非静态内部类注释 总是会加载外部类
        //System.out.println("非静态内部类加载时间" + new OuterClass2().new InnerClass().INNER_DATE);
        //OuterClass2.InnerStaticClass staticInner = new OuterClass2.InnerStaticClass();
        System.out.println("静态内部类加载时间" +   OuterClass2.InnerStaticClass.show());
    }

    //单纯的为了耗时,来扩大时间差异
    private void timeElapsed() {
        for (int i = 0; i < 10000000; i++) {
            int a = new Random(100).nextInt(), b = new Random(100).nextInt();
            a = a + b;
        }
    }
}
外部类静态块加载时间:1627945922547
外部类静态变量加载时间:1627945922547
静态内部类加载时间1627945922550


外部类静态块加载时间:1627946028821
外部类静态变量加载时间:1627946028820
外部类构造函数时间:1627946029235
非静态内部类加载时间1627946029671

6.内部类的加载过程

静态内部类和非静态内部类一样,都是在被调用时才会被加载。在加载静态内部类的过程中也会加载外部类。
非静态内部类的实例化依赖于外部类,所以内部类的实例化会导致外部类的实例化,自然也需要加载外部类。

https://www.cnblogs.com/maohuidong/p/7843807.html

成员内部类

外界如何创建对象

1.非静态内部类

Outer.MemberInner oi = new Outer().new MemberInner();

2.静态内部类

Outer.MemberInner memberInner = new Outer.MemberInner();

刚才我们讲解过了,成员内部类的使用,但是一般来说,在实际开发中是不会这样使用的。

因为一般内部类就是不让外界直接访问的。

成员内部类的常见修饰符:private&static

private 为了保证数据的安全性
static 为了让数据访问更方便

内部类用静态修饰是因为内部类可以看成是外部类的成员
被静态修饰的成员内部类只能访问外部类的静态成员

package 成员内部类;

/*
	成员内部类的修饰符:
		private 为了保证数据的安全性
		static 为了方便访问数据
			注意:静态内部类访问的外部类数据必须用静态修饰。
*/
public class Outer {
	private int num = 10;
	private static int num2 = 100;


	protected class Inner {

		public void show() {
			System.out.println(num);
			System.out.println(num2);
		}

		//非静态内部类中不能有静态成员变量和方法
		/*static int k = 0;
		public static void show3() {
			System.out.println("show3");
		}
		*/
	}

	//内部类用静态修饰是因为内部类可以看成是外部类的成员
	public static  class StaticInner {

		public void show() {
			//System.out.println(num);
			//被静态修饰的成员内部类只能访问外部类的静态成员
			System.out.println(num2);
		}

		//静态内部类中可以有静态成员变量和方法
		static int k =0;
		public static void show2() {
			//被静态修饰的成员内部类只能访问外部类的静态成员
			//System.out.println(num);
			System.out.println(num2);
		}
	}
}

class InnerClassDemo4 {
	public static void main(String[] args) {
		//成员内部类被静态修饰后的访问方式是:
		//格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
		// 限定的静态内部类
		Outer.StaticInner staticInner = new Outer.StaticInner();
		staticInner.show();
		staticInner.show2();
		//show2()的另一种调用方式
		Outer.StaticInner.show2();

		//成员内部类的访问方式是:
		//格式:外部类名.内部类名 对象名 = new 外部类名().内部类名();
		//非静态内部类
		Outer.Inner oi = new Outer().new Inner();
		oi.show();
	}
}

非静态内部类里面不能有静态成员和方法

非静态内部类不能有静态成员!因为成员内部类必须先实例化外部类对象然后再实例化成员内部类。

非static的内部类,在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法。因为还没有被加载,无法通过类名调用!

1.static类型的属性和方法,在类加载的时候就会存在于内存中。

2.要使用某个类的static属性或者方法,那么这个类必须要加载到jvm中。

基于以上两点,可以看出,如果一个非static的内部类如果具有static的属性或者方法.

那么就会出现一种情况:内部类未加载,但是却试图在内存中创建static的属性和方法,这当然是错误的。

原因:类还未加载,在方法区不存在,但却希望操作它的属性和方法。

java很多像这类不能共同存在的 一般都与他们的生命周期有关。
比如 静态成员和静态方法是随着类的加载而存在的,也就是说内部类的静态属性是随着类的加载的,但是内部类的实例,是创建后才存在的,也就是说其静态属性优先存在于他的类实例的存在,这显然是矛盾的。所以如果有静态成员变量也要把内部类设为静态的,这样他们的生命周期就是相同了。

如果内部类没有被static修饰,就需要实例化内部类才能调用,说明非static的内部类不是自动跟随主类加载的,而是被实例化的时候才会加载。
而static的语义,就是主类能直接通过内部类名来访问内部类中的static方法,而非static的内部类又是不会自动加载的,所以这时候内部类也要static,否则会前后冲突。

成员内部类面试题

//补齐程序(注意:内部类和外部类没有继承关系)
	class Outer {
		public int num = 10;
		class Inner {
			public int num = 20;
			public void show() {
				int num = 30;
				System.out.println(?);
				System.out.println(??);
				System.out.println(???);
			}
		}
	}
在控制分别输出:302010

public class Outer {
	public int num = 10;
	class Inner {
		public int num = 20;
		public void show() {
			int num = 30;
			System.out.println(num);
			System.out.println(this.num);
			System.out.println(new Outer().num);
			//System.out.println(Outer.this.num);
		}
	}

	public static void main(String[] args) {
		new Outer().new Inner().show();
	}
}

局部内部类和匿名内部类

局部内部类(有类名)
匿名内部类(无类名,重点)
局部内部类可以直接访问外部类的成员,可以创建内部类对象。
通过对象调用内部类方法,来使用局部内部类功能。

为什么局部内部类的局部变量必须被final修饰

这个局部变量包括形参和内部类方法中的局部变量

内部类和外部类是都是类级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。

所以为了防止方法执行完毕,引用了一个不存在的变量,就copy一份局部变量作为内部类的成员变量。

当方法执行完毕局部变量被回收之后,实际访问的是局部变量的复制品即局部内部类的成员变量。

设置为final,是为了保证一致性。

package 匿名内部类;

public interface InnerInterface {
    void printA();
}
package 匿名内部类;
/**
 *内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁;
 *所以为了防止方法执行完毕,引用了一个不存在的变量,就copy一份局部变量作为内部类的成员变量,
 *当局部变量没有之后,实际访问的是copy的复制品.
 */

public class OuterClass{

    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        //在调用完fun方法后,InnerInterface的printA方法中的a变量就要被销毁
        outerClass.print(100);
        System.out.println("方法执行完成之后,局部变量作用域终止了");
        //但是执行printA方法发现a变量仍然存在
        obj.printA();
        outerClass.show(100);
    }

    public static InnerInterface obj = null;
    /**
     * 匿名内部类
     * 局部变量a作用域只能是方法内部,但是匿名内部类不会随着方法执行完毕而被销毁,
     * 就copy了一份局部变量,同时为了保证一致性,设置为final。
     * */
    public void print(int a) {
        obj = new InnerInterface() {
            @Override
            public void printA() {
                System.out.println(" print final param " + a);
            }
        };
    }

    /**
     * 局部内部类
     * 同样拷贝一份作为InnerClass的成员变量
     * */
    public void show(final int b) {
        class InnerClass {
            public void printB() {
                System.out.println(" show final param " + b);
            }
        }
        new InnerClass().printB();
    }
}

因为局部变量会随着方法的调用完毕而消失。
这个时候,局部内部类对象obj并没有立马从堆内存中消失,obj还有引用指向它不能被立刻回收。
为了让数据还能继续被使用,就需要用final修饰,这样,在堆内存里面存储的其实是一个常量值。
通过反编译工具可以看一下。

根本原因就是作用域中变量的生命周期导致的

首先需要知道的一点是:内部类和外部类是同属于类级别,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。
这里就会产生问题:当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在(只有没有人再引用它时,才会死亡)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。

为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以访问它,实际访问
的是局部变量的”copy”。这样就好像延长了局部变量的生命周期。

我们可以通过反编译生成的.class文件来实验:先执行命令 javac 匿名内部类/*.java -encoding utf-8 进行编译
javap是 Java class文件分解器,可以反编译,也可以查看java编译器生成的字节码。
反编译
反编译

//匿名内部类
class 匿名内部类.InnerClassTest$1 implements 匿名内部类.InnerInterface {
  final int val$a;
  final 匿名内部类.InnerClassTest this$0;
  匿名内部类.InnerClassTest$1(匿名内部类.InnerClassTest, int);
  public void printA();
}
//成员内部类
class 匿名内部类.InnerClassTest$1InnerClass {
  final int val$b;
  final 匿名内部类.InnerClassTest this$0;
  匿名内部类.InnerClassTest$1InnerClass();
  public void printB();
}

可见方法中的局部变量实际上确实会复制为内部类的成员变量使用。

问题又出现了:将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变,怎么解决问题呢?

将局部变量设置为final,对它初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性。这实际上也是一种妥协。

若变量是final时:

若是基本类型,其值是不能改变的,就保证了copy与原始的局部变量的值是一样的;

若是引用类型,其引用是不能改变的,保证了copy与原始的变量引用的是同一个对象。

这就使得局部变量与内部类内建立的拷贝保持一致。

匿名内部类

就是内部类的简化写法。

前提:存在一个类或者接口。这里的类可以是具体类也可以是抽象类。

格式:
new 类名或者接口名() {重写方法;}

本质:
是一个继承了类或者实现了接口的子类匿名对象

package 匿名内部类;

/*
	匿名内部类
		就是内部类的简化写法。

	前提:存在一个类或者接口
		这里的类可以是具体类也可以是抽象类。
	
	格式:
		new 类名或者接口名(){
			重写方法;
		}
		
	本质是什么呢?
		是一个继承了类或者实现了接口的子类匿名对象。
*/
interface Inter {
	public abstract void show();
	public abstract void show2();
}

class Outer {
	public void method() {
		//一个方法的时候
		/*
		new Inter() {
			public void show() {
				System.out.println("show");
			}
		}.show();
		*/
		
		//二个方法的时候
		/*
		new Inter() {
			public void show() {
				System.out.println("show");
			}
			
			public void show2() {
				System.out.println("show2");
			}
		}.show();
		
		new Inter() {
			public void show() {
				System.out.println("show");
			}
			
			public void show2() {
				System.out.println("show2");
			}
		}.show2();
		*/
		
		//如果我是很多个方法,就很麻烦了
		//那么,我们有没有改进的方案呢?
		Inter i = new Inter() { //多态
			public void show() {
				System.out.println("show");
			}
			
			public void show2() {
				System.out.println("show2");
			}
		};
		
		i.show();
		i.show2();
	}
}

class InnerClassDemo6 {
	public static void main(String[] args) {
		Outer o = new Outer();
		o.method();
	}
}

匿名内部类面试题

//按照要求,补齐代码
	interface Inter { void show(); }
	class Outer { 
		//补齐代码
    }
	class OuterDemo {
	    public static void main(String[] args) {
		      Outer.method().show();
		  }
	}
要求在控制台输出”HelloWorldpackage digitalwallet;

import org.omg.PortableInterceptor.INACTIVE;

interface Inter {
    void show();
}

class Outer {

    public static Inter method() {
        return new Inter() {
            @Override
            public void show() {
                System.out.println("Hello World");
            }
        };
    }
}

class OuterDemo {
    public static void main(String[] args) {
        Outer.method().show();
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值