JAVA 内部类
内部类
一、基本介绍
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员
【思考:类的五大成员是哪些?[属性、方法、构造器、代码块、内部类]】
内部类最大的特点:就是可以直接访问私有属性,并且可以体现类与类之间的包含关系
注意:
- 内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类.
基本语法:
class Outer{//外部类
class Inner{//内部类
}
}
class Ohter{}//外部其他类
二、内部类的分类
1.定义在外部类局部位置上 (比如说方法体内):
- 局部内部类(有类名)
- 匿名内部类(没有类名,重点!!!!),用的非常的多
2.定义在外部类的成员位置上:
- 成员内部类(没有static修饰)
- 静态内部类(使用static修饰)
2.1局部内部类
说明:局部内部类是定义在外部类的局部位置,通常在方法中,并且有类名
- 可以访问外部类的所有私有属性,即其他属性
- 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。
- 但是可以使用final修饰符,因为局部变量也可以使用final修饰符
- 作用域:仅仅在定义它的方法或者代码块中.
- 局部内部类可以直接访问外部类的成员
- 外部其它类不能访问局部内部类
- 如果外部类和局部内部类的成员重名时,默认遵守就近原则,如果想要访问外部类的成员,则可以使用
外部类名.this.成员
去访问- 它的本质其实就是外部类的对象,即哪个对象调用了内部类所在的方法或者代码块,就指向那个
记住:
- 位置:局部内部类是定义在一个类的方法/代码块中
- 作用域:在方法体或者代码块中
- 本质:本质仍然是个类,特别的一点就是可以访问外部类的所有成员,包括私有的
package com.hspedu.innerclass;
/**
* 演示局部内部类的使用
*/
public class LocalInnerClass {
public static void main(String[] args) {
Outer01 outer01 = new Outer01();
outer01.m1();
/*
内部类Inner01 f1() 方法..
n1=100
外部类的m2()方法..
*/
System.out.println("outer01 hashcode=" +outer01.hashCode() );
}
}
class Outer01{ //外部类
private int n1 = 100;
private void m2(){
System.out.println("外部类的m2()方法..");
}
private void f1(){
System.out.println("外部类Outer01的 f1()..");
}
public void m1(){
//1.局部内部类是定义在外部类的局部位置,通常在方法中
//3.不能添加访问修饰符,但是可以使用final修饰
// 加了final就这个类就不能被继承了
//4.仅仅在定义它的方法或者代码块中
class Inner01{ //局部内部类(本质上还是一个类)
//2.可以访问外部类的私有属性,以及其他成员
private int n1 = 800;
public void f1(){
//5.局部内部类可以直接访问外部类的成员 比如访问了 外部的n1,m2()
//7.如果外部类和局部内部类的成员重名时,默认遵守就近原则,
// 如果想要访问外部类的成员,则可以使用外部类名.this.成员去访问
System.out.println("内部类Inner01 f1() 方法..");
System.out.println("内部类的 n1=" + n1);
//Outer02.this 本质就是外部类的对象,
//即哪个对象调用了m1,Outer01.this就指向了哪个对象
System.out.println("外部类的 n1=" + this.n1);
System.out.println("Outer01.this hashcode=" + Outer01.this.hashCode());
m2();
}
}
//6.外部类在方法中,可以创建inner01这个对象,然后调用方法即可
Inner01 inner01 = new Inner01();
inner01.f1();
f1();
class Inner02 extends Inner01{
}
}
}
2.2匿名内部类
基本介绍:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
-
本质是类
-
且是一个内部类
-
该类没有名字
-
同时还是个对象
基本语法:
new 类或接口(参数列表){
类体
};
2.2.1基于接口
使用匿名内部类的好处:
- 如果想要使用某个接口,就要让一个类实现这个接口,并创建对象,若只想使用一次,后面都不再使用,这样就显得很费事
- 故使用匿名类部类,能简化开发步骤
注意:
- 匿名内部类在底层是会分配一个名字的,并且立马实例化,把地址返回
- 匿名内部类使用一次,就不再使用了,在内存中找不到了
- 分配的类名为:
类名$1
2.2.2基于类
大部分和和基于接口的一致,不过需要注意
- 他的运行类型还是
外部类$数字
- 且如果有参数,这个参数是直接传给对象的构造器中,进行使用的
package com.hspedu.innerclass;
/**
* 演示匿名类的使用
*/
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.method();
}
}
class Outer02 {//外部类
private int n1 = 10;//属性
public void method() {
//基于接口的匿名内部类
//老韩解读
//1.需求:想使用IA接口,并创建对象
//2.传统方式,写一个类实现接口,并创建对象
//3.需求:Tiger这个对象只使用一次,后再不使用了
//4.可以使用匿名内部类来简化开发
//5.tiger的编译类型 是A 接口,
// 运行类型就是匿名内部类 XXXX = Outer02$1
/*
我们看底层,系统会分配类型 类名$1 用一次就没有了
class XXXX implement IA{
@Override
public void cry() {
System.out.println("老虎叫唤..");
}
}
*/
//7.jdk底层在创建匿名内部类 Outer02$1,立马创建Outer02$1实例,并把地址返回给tiger
//8.匿名内部类使用一次,就不再使用了,在内存中找不到了
A tiger = new A() {
@Override
public void cry() {
System.out.println("老虎叫唤..");
}
};
System.out.println("tiger的运行类型=" + tiger.getClass());
//tiger的运行类型=class com.hspedu.innerclass.Outer02$1
tiger.cry();
tiger.cry();
tiger.cry();
//A tiger = new Tiger();
//tiger.cry();
//基于类的匿名内部类
//分析
//1.father编译类型 Father
//2.father运行类型 Outer01$2
//3.底层
/*
class Outer02$2 extend Father{
public void test() {
System.out.println("匿名内部类重写了test方法..");
}
}
*/
//4. 同时也直接返回了 匿名内部类 Outer02$2的对象
//5. 注意:("jack") 会传递给Father构造器
Father father = new Father("jack") {
@Override
public void test() {
System.out.println("匿名内部类重写了test方法..");
}
};
System.out.println("father对象的运行类型=" + father.getClass());
//father对象的运行类型=class com.hspedu.innerclass.Outer02$2
father.test();
//基于抽象类的匿名内部类
Animal animal = new Animal(){
@Override
void eat() {
System.out.println("小狗吃东西..");
}
};
System.out.println("animal的运行类型=" + animal.getClass());
animal.eat();
}
}
interface A {//接口
public void cry();
}
/*class Tiger implements A {
@Override
public void cry() {
System.out.println("老虎叫唤..");
}
}
class Dog implements A{
@Override
public void cry() {
System.out.println("小狗叫唤..");
}
}*/
class Father {//类
String name;
public Father(String name) {//构造器
System.out.println("接收到name:" + name);
this.name = name;
}
public void test() {
}
}
class B extends Father{
public B(String name) {
super(name);
}
}
abstract class Animal{
abstract void eat();
}
2.2.3注意事项
1.匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法。
2.可以直接访问外部类的所有成员,包含私有的
3.不能添加访问修饰符,因为它是个局部变量
4.作用域:仅仅是在代码块中
5.匿名类可以直接访问外部类的成员
6.外部其他类不能访问匿名类
7.如果外部类和内部类的成员重名时,匿名内部类访问的话,默认遵守就近原则,如果想要访问外部类的成员,则需要使用外部类名.this.成员
去访问
package com.hspedu.innerclass;
public class AnonymousInnerClassDetail {
public static void main(String[] args) {
Outer03 outer03 = new Outer03();
outer03.f1();
System.out.println("outer03 hashcode=" + outer03.hashCode());
}
}
class Outer03{
private int n1 = 99;
public void f1(){
//创建一个匿名内部类
Person p = new Person(){
private int n1 = 200;
@Override
public void hi() {
System.out.println("匿名内部类重写了 hi方法");
System.out.println("匿名类中的 n1="+n1);//200
System.out.println("外部类中的 n1="+Outer03.this.n1);//99
//Outer03.this 就是调用f1 的那个对象
System.out.println("Outer03.this hashcode=" + Outer03.this.hashCode());
}
};
p.hi();//动态绑定,找到匿名类中的hi
//也可以直接调用,匿名内部类本身也是返回对象的,参数也可以穿
new Person(){
@Override
public void hi() {
System.out.println("匿名内部类重写了 hi方法,哈哈哈");
}
@Override
public void ok(String str) {
System.out.println("匿名类 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);
}
}
2.2.4匿名内部类的最佳实践
- 匿名内部类可以当做实参直接传递,简洁高效
package com.hspedu.innerclass;
public class InnerClassExercise {
public static void main(String[] args) {
CellPhone cellPhone = new CellPhone();
//1.这里把Bell 接口的匿名内部类传给了AlarmClock方法
//2.并重写了ring方法,通过动态绑定机制,调用的是匿名内部类中的ring方法
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) {
System.out.println(bell.getClass());
bell.ring();//动态绑定机制
}
}
2.3成员内部类
说明:成员内部类是定义在外部类的成员位置,并且没有static修饰
-
可以直接访问外部类的所有成员,包含私有的
-
可以添加任意的访问修饰符(public,protected,默认,private)
-
作用域:和外部的其他成员一样,为整个类体
-
成员内部类能直接访问外部类的成员
-
外部类 要使用 成员内部类,要创建成员内部类的对象,然后调用相关的属性或/方法
-
外部其他类 使用成员内部类
-
方法一:用外部类的对象去new一个成员内部类;
Outer04.Inner08 inner08 = outer04.new Inner08();
-
方法二:在外部类中编写一个方法,可以返回成员内部类inner08的实例
Outer04.Inner08 inner081 = outer04.getInner08Instance();
-
-
如果外部类和成员内部类的成员重名时,成员内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
package com.hspedu.innerclass;
public class MemberInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.t1();
//n1=10 name=张三
//外部其它类,使用成员内部类的方法
//方式一:用外部类的对象去new一个成员内部类;
// 相当于把new Inner08()当做outer04的成员
Outer04.Inner08 inner08 = outer04.new Inner08();//这就是个语法
inner08.say();
//方式二:在外部类中编写一个方法,可以返回成员内部类inner08的实例
Outer04.Inner08 inner081 = outer04.getInner08Instance();
inner081.say();
//方式三:
new Outer04().new Inner08();
}
}
class Outer04{//外部类
private int n1 = 10;
public String name = "张三";
public void hi(){
System.out.println("hi"
);
}
//注意成员内部类是定义在外部类的成员位置上。
//可以添加任意的访问修饰符(public,protected,默认,private)
class Inner08 {//成员内部类
private double sal = 88.8;
//作用域:外部类都可以使用这个成员内部类
public void say(){
//可以直接访问外部类的所有成员,包括私有的
System.out.println("n1=" + n1 + " name=" + name);
hi();
}
}
//该方法返回一个成员内部类的实例
public Inner08 getInner08Instance(){
Inner08 inner08 = new Inner08();
return inner08;
}
//写一个方法
public void t1(){
//使用成员内部类
//外部类 要使用 成员内部类,要创建成员内部类的对象,然后调用相关的属性或/方法
Inner08 inner08 = new Inner08();
inner08.say();
System.out.println(inner08.sal);
}
}
2.4静态内部类
说明:静态内部类是定义在外部类成员位置上的,且有static修饰
-
可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
-
可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
-
作用域:同其他的成员,为整个类体
-
静态内部类访问外部类的静态成员:直接访问即可
-
外部类访问静态内部类:创建对象再访问
-
外部其他类访问静态内部类:
-
方式一:因为静态内部类,是可以通过类名直接访问的(前提是满足访问权限)
Outer05.Inner10 inner10 = new Outer05.Inner10();
-
方法二:编写一个方法,返回静态内部类,通过外部类名.静态类名 对象名 接受即可,可以写成静态的
Outer05.Inner10 inner101 = outer05.getInner10Instance();
-
-
如果外部类和静态内部类的成员重名时,静态内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
package com.hspedu.innerclass;
public class StaticInnerClass {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.m1();
//方式一:因为静态内部类,是可以通过类名直接访问的(前提是满足访问权限)
Outer05.Inner10 inner10 = new Outer05.Inner10();
inner10.say();
//方式二:编写一个方法,返回静态内部类,通过外部类名.静态类名 对象名 接受即可
Outer05.Inner10 inner101 = outer05.getInner10Instance();
inner101.say();
}
}
class Outer05{
private int n1 = 10;
private static String name = "张三";
private static void cry(){};
//Inner10就是一个静态内部类
//1.它是外部类的成员位置上的
//2.使用了static 修饰
//3.可以访问外部类的静态类的所有成员,但不能访问非静态类的成员
//4.可以添加任意访问修饰符(public、protected、默认、private)
//5.作用域:同其他成员一样,为整个类体
public static class Inner10{
public void say(){
System.out.println(name);
cry();
}
}
public void m1(){
Inner10 inner10 = new Inner10();
inner10.say();
}
public Inner10 getInner10Instance(){
return new Inner10();
}
}