内部类的定义与使用
内部类的基本概念
为什么要存在内部类
内部类与外部类的关系
内部类对象的创建
内部类分类
内部类的基本概念
概念:所谓内部类,就是在一个类的内部进行其他类结构的嵌套操作。
首先先来看一下内部类的简单实现:
class Outter{//外部类
private int num=152;
class Inner{//内部类
public void print(){//在内部类中定义一个普通方法
System.out.println(num);
//调用外部类num私有属性(实际上此时num相当于调用out.num(隐藏语句))
}
}
public void fun(){
//在内部类外部(外部类中)定义一个普通方法,方法中生成内部类对象,调用内部类普通方法
Inner inner=new Inner();
inner.print();
}
}
public class Test{
public static void main(String[] args){
Outter out=new Outter();//实例化外部类
out.fun();//调用外部类普通方法
}
}
**由输出结果我们可以看到,内部类可直接访问外部类的私有域。**但是从程序结构来说,引入内部类会使程序结构混乱。
**注意:当编译存在内部类程序时,.class文件不再是主类名称.class,而是外部类名称$内部类名称。**如下图:
上文代码也可修改至更加简洁,如下:
class Outter{
private int num=152;
class Inner{//内部类
public void print(){
System.out.println(num);
}
}
}
public class Test{
public static void main(String[] args){
Outter.Inner in=new Outter().new Inner();//Outter.Inner表示Inner为内部类,Outter为外部类,in为内部类实例化对象名称,new Outter().new Inner()表示先实例化外部类再实例化内部类。
in.print();//可直接调用内部类方法
}
}
若不存在内部类的话,一个类要访问另一个类的私有域,相对会复杂很多,如下:
class Outter{
private int num=152;
public int getNum(){
return this.num;
}
public void fun(){
Inner inner=new Inner(this);
//this表示当前对象(即哪个对象调用它就代表谁,此处是与此类无直接关系的类调用)
inner.print();
}
}
class Inner{
private Outter out;//定义私有变量(类比于私有成员变量定义,Outter为类类型,out为类名称)
public Inner(Outter out){
this.out=out;//引用传递
}
public void print(){
System.out.println(out.getNum());
}
}
public class Test{
public static void main(String[] args){
Outter out=new Outter();
out.fun();
}
}
由以上代码我们可以看出,若不存在继承和内部类,一个类要调用另一个类的私有域是极其复杂的;若只有继承,子类也不能直接调用父类的私有域,需要间接通过setter和getter方法调用;若存在内部类则内部类可直接调用外部类的私有域。
内部类为什么存在
除了上文说到的内部类可直接访问外部类的私有域这一大功能外,还能实现隐藏,最主要的功能还是内部类实现多继承操作。
假设不存在内部类,我们要进行多继承要怎么办呢?以现有的知识一定是多层继承,但多次继承的缺点是代码冗余且多层继承次数不宜过多。代码如下:
class Person{//父类
private String name;
private int age;
public Person(String name,int age){//父类有参构造
this.name=name;
this.age=age;
}
public String getName(){//getter方法,用于获取私有属性内容
return this.name;
}
public int getAge(){
return this.age;
}
public void print(){
System.out.println(this.name+"今年"+this.age+"寂寞的跳舞");
}
}
class Student extends Person{//Student子类
private String school;
public Student(String name,int age,String school){//子类有参构造
super(name,age);
this.school=school;
}
public String getSchool(){
return this.school;
}
public void print(){
System.out.println(this.getName()+"今年"+this.getAge()+"寂寞的唱歌"+"在"+this.school);//此处getter()方法表示获取从父类继承的私有属性的值,不可再用this,因继承后私有属性为隐式继承子类不可直接使用,必须间接通过setter()方法(修改内容)getter()(获取内容)使用。
}
}
class Worker extends Student{
private String sex;
public Worker(String name,int age,String school,String sex){
super(name,age,school);
this.sex=sex;
}
public void print(){//方法覆写
System.out.println(this.getName()+"性别"+this.sex+"今年"+this.getAge()+"寂寞的打游戏"+"在"+this.getSchool());
}
}
public class Test{
public static void main(String[] args){
Person per=new Person("张三",18);//实例化父类对象,调用print()方法
per.print();
Who(new Student("李四",20,"清华大学"));//向上转型
Who(new Worker("刘能",22,"北京大学","男"));
}
public static void Who(Person per){
per.print();
}
}
注意:
1.若要修改已有内容,只需在父类中使用setter方法即可。
class Person{//父类
private String name;
private int age;
public Person(String name,int age){
this.name=name;
this.age=age;
setAge(85);
//构造方法中调用setAge()方法即可改变所有类的年龄(前提是测试时存在向上转型而非各自实例化)
}
public void setAge(int age){
this.age=age;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
public void print(){
System.out.println(this.name+"今年"+this.age+"寂寞的跳舞");
}
}
2.父类中不存在无参构造时,必须在子类构造函数首行使用super(参数)语句明确指明调用父类哪个有参构造,且子类有参构造参数必须写要调用父类有参构造的参数,若不写,使用super(name,age)调用父类有参构造则会编译报错:提示错误为**“name可以在Person中访问private”**;此时使用super(“张三”,18)调用程序正常,但会破坏多层继承。建议子类有参构造参数书写父类有参构造参数,然后使用super(参数)语句直接调用。
由以上我们可以看出,使用多层继承有一定的局限性,且子类使用父类私有域时不方便。现在就一起感受一下内部类的多继承,代码如下:
class Person{//Person类
private String name="刘能";
public String getName(){
return name;
}
}
class Worker{//Worker类
private String sex="男";
public String getSex(){
return sex;
}
}
//在Student类继承之前,Person类与Worker类无任何联系,一旦有了共同子类Student,Person类与Worker类均为父类
class Student{
class StudentA extends Person{//内部类继承Person类
public void name(){
System.out.println(super.getName());
}
}
class StudentB extends Worker{//内部类继承Worker类
public void sex(){
System.out.println(super.getSex());
}
}
public void print1(){//实例化内部类
StudentA stuA=new StudentA();
stuA.name();
}
public void print2(){
StudentB stuB=new StudentB();
stuB.sex();
}
}
public class Test{
public static void main(String[] args){
Student stu=new Student();
//实例化外部类(因内部类依赖于外部类,只有外部类生成对象后内部类才生成对象)
stu.print1();
stu.print2();
}
}
以上代码也可写成以下样式:
class Person{
private String name="刘能";
public String getName(){
return name;
}
}
class Worker{
private String sex="男";
public String getSex(){
return sex;
}
}
class Student{
class StudentA extends Person{
public String name(){
return super.getName();
}
}
class StudentB extends Worker{
public String sex(){
return super.getSex();
}
}
public String print1(){
return new StudentA().name();
}
public String print2(){
return new StudentB().sex();
}
}
public class Test{
public static void main(String[] args){
Student stu=new Student();
System.out.println(stu.print1());
System.out.println(stu.print2());
}
}
学到这里,我们可以思考一个问题,我们一直知道private修饰符不可以修饰类(原因是如果类被private修饰,其他类均不知道有此类存在也可不使用,故定义此类无用),那么可以修饰内部类吗?
class Person{
private String name="刘能";
public String getName(){
return name;
}
}
class Worker{
private String sex="男";
public String getSex(){
return sex;
}
}
class Student{
private class StudentA extends Person{
public void name(){
System.out.println(super.getName());
}
}
private class StudentB extends Worker{
public void sex(){
System.out.println(super.getSex());
}
}
public void print1(){
StudentA stuA=new StudentA();
stuA.name();
}
public void print2(){
StudentB stuB=new StudentB();
stuB.sex();
}
}
public class Test{
public static void main(String[] args){
Student stu=new Student();
stu.print1();
stu.print2();
}
}
注意:
1.当private修饰外部类时
2.当修饰内部类时
可见,private可以修饰内部类,当内部类被private修饰时,表示该内部类只能在此外部类中使用,是封装的一种。
3.当内部类被private修饰时,内部类隐藏,对统一外部类中的其他类隐藏。故不可直接在主类中调用输出。
//错误代码
class Person{
private String name="刘能";
public String getName(){
return name;
}
}
class Worker{
private String sex="男";
public String getSex(){
return sex;
}
}
class Student{
private class StudentA extends Person{//该内部类此时对Student类中的其他类隐藏
public String name(){
return super.getName();
}
}
private class StudentB extends Worker{
public String sex(){
return super.getSex();
}
}
public void print1(){
StudentA stuA=new StudentA();
stuA.name();
}
public void print2(){
StudentB stuB=new StudentB();
stuB.sex();
}
}
public class Test{
public static void main(String[] args){
Student stu=new Student();
System.out.println(stu.print1());
System.out.println(stu.print2());
}
}
内部类作用:
1.内部类方法可以访问该类定义所在作用域中的数据,包括被private修饰的私有数据。
2.内部类可以对同一包中的其他类隐藏起来。
3.内部类可实现多继承。
4.定义回调函数时可使用匿名内部类实现。
内部类与外部类的关系
1.对于非静态内部类(成员内部类),内部类的创建依赖外部类的实例对象,在没有外部类实例之前无法创建内部类。
2.内部类是一个相对独立的实体,与外部类不是is-a关系。
3.内部类可直接访问外部类的私有域,外部类间接访问内部类私有域。
4.外部类可通过内部类引用间接访问内部类元素。(通过内部类对象访问)
//内部类直接访问外部类私有域
class A{
private int num=20;//外部类可含有私有域
class B{
private int num=100;//内部类也可拥有私有域
public void fun(){
System.out.println(num);//考虑作用域
}
}
}
public class Test{
public static void main(String[] args){
A.B b=new A().new B();
b.fun();
}
}
//外部类间接通过实例化内部类对象间接访问内部类私有域
class A{
class B{
private int num=100;
public void fun(){
System.out.println(num);
}
}
public void print(){//外部类中定义普通方法,方法中实例化内部类,调用内部类方法
B b=new B();
b.fun();
}
}
public class Test{
public static void main(String[] args){
A a=new A();
a.print();
}
}
内部类对象的创建
1.在外部类外部创建内部类对象(主方法)
2.在外部类内部创建内部类对象
内部类分类
成员内部类
静态内部类
方法内部类
匿名内部类
1.成员内部类(类比于普通方法)
class A{
class B{
private int num=100;
public void fun(){//成员内部类
System.out.println(num);
}
}
}
注意:(1)成员内部类不能拥有静态域(任何static修饰的成员),但可以访问外部类的静态域。(原因是内部类依赖于外部类,需先创建外部类对象后才可创建成员内部类,但静态域与对象无关,但内部类需依靠对象访问)
//内部类直接访问外部类静态域
class A{
public static final int MSG=200;//全局常量
public static void print(){//外部类静态方法
System.out.println("你今天真好看!");
}
class B{
private int num=100;
public void fun(){
System.out.println(num);
System.out.println(MSG);
print();
}
}
}
public class Test{
public static void main(String[] args){
A.B b=new A().new B();
b.fun();
}
}
当内部类定义静态域时,编译报错。
(2)成员内部类是依赖于外部类的,只有创建了外部类对象才可创建内部类。
2.静态内部类(类比于静态方法)
静态内部类定义在类中,用static修饰。
成员内部类与静态内部类的最大区别是静态内部类的创建不依赖于外部类,静态内部类对象可直接创建。(因非静态内部类在编译完成后会隐含的保留一个引用指向创建它的外部类,但是静态内部类没有)
外部类外部静态内部类对象创建:
Outter.Inner in=new Outter.Inner();
//外部类后无括号说明没有实例化对象,静态内部类后有括号说明在调用内部类无参构造,故直接产生了内部类对象
class A{
static class B{//静态内部类可拥有静态域
private static int num=100;
public static final int MSG=200;
public void fun(){
System.out.println(num);
System.out.println(MSG);
print();
}
public void print(){
System.out.println("你今天真好看");
}
}
}
public class Test{
public static void main(String[] args){
A.B b=new A.B();//可直接创建内部类对象
b.fun();
}
}
注意:(1)静态内部类能拥有普通域,但不能访问外部类普通属性。
那么我们思考一下,既然static可修饰内部类,那么能修饰外部类吗?
//问题代码
static class A{
private static int age=20;
static class B{
private static int num=100;
public static final int MSG=200;
public void fun(){
System.out.println(num);
System.out.println(MSG);
System.out.println(age);
print();
}
public void print(){
System.out.println("你今天真好看");
}
}
}
public class Test{
public static void main(String[] args){
A.B b=new A.B();
b.fun();
}
}
(2)静态内部类不依赖于外部类,可直接创建。
3.方法内部类(类比于方法中的局部变量)
方法内部类定义在外部类的方法中,局部内部类与成员内部类基本一致,只不过作用域不同。方法内部类的作用域只是该方法。方法内部类主要解决了想创建一个类来帮助我们解决问题但又不希望该类是公共时使用。
注意:
1.局部内部类不允许使用访问权限修饰符(public private protected均不可用)。
2.局部内部类对外完全隐藏,除了创建这个类的方法可以访问其余均不可访问。
3.局部内部类若想使用方法形参,则该形参必须用final声明(JDK8形参变为隐式final声明)。
//简单实用局部内部类
class A{
private int age=20;
public void print(){
class B{
private int num=100;
public int MSG=200;
public void fun(){
System.out.println(num);//可直接访问外部类私有域
System.out.println(MSG);
System.out.println(age);
}
}
new B().fun();//只能位于方法内部类的方法中,除了该方法其余均不可访问方法内部类
}
}
public class Test{
public static void main(String[] args){
A a=new A();
a.print();
}
}
4.匿名内部类
匿名内部类就是一个没有名字的内部类,它满足所有方法内部类的约束。除此之外,还有满足以下几个约束:
(1)匿名内部类没有访问修饰符。
(2)匿名内部类必须继承一个抽象类或实现一个接口。
(3)匿名内部类中不能存在静态域。
(4)匿名内部类中不能有构造方法,因为匿名内部类没有名字。
(5)匿名内部类也可以引用方法形参,但必须用final声明。
//匿名内部类的简单实现
interface A{//接口
void fun();//抽象方法
}
class B{
private int num=20;
public void print(){
new A(){//匿名内部类
public void fun(){//实现接口
System.out.println("你今天真好看!");
}
}.fun();//调用匿名内部类方法
}
}
public class Test{
public static void main(String[] args){
B b=new B();
b.print();
}
}