内部类
一、相关概念
1、概念
内部类是指在一个外部类的内部再定义一个类
内部类作为外部类的一个成员,并且依附于外部类而存在的
内部类可为静态,可用protected和private修饰。而外部类不可以,外部类只能使用public和default修饰
2、分类
- 成员内部类
- 局部内部类
- 静态内部类
- 匿名内部类
3、目的、好处
使用内部类可以使程序更加的简洁,便于命名规范和划分层次结构
使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
public interface Father {
}
public interface Mother {
}
public class Son implements Father, Mother {
}
public class Daughter implements Father{
class Mother_ implements Mother{
}
}
其实对于以上实例我们确实是看不出来使用内部类存在何种优点,但是如果Father、Mother不是接口,而是抽象类或者具体类呢?这个时候我们就只能使用内部类才能实现多重继承了。
在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。
4、特性
使用内部类最大的优点就在于它能够非常好的解决多重继承的问题,但是如果我们不需要解决多重继承问题,那么我们自然可以使用其他的编码方式,但是使用内部类还能够为我们带来如下特性(摘自《Think in java》):
1)内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。
2)在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
3)创建内部类对象的时刻并不依赖于外围类对象的创建。
4)内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
5)内部类提供了更好的封装,除了该外围类,其他类都不能访问。
abstract class People {
abstract void run() ;
}
interface Machine {
void run() ;
}
//错误:此时run()不可直接实现
class Roboot extends People implements Machine {
}
//注意:当类与接口(或者是接口与接口)发生方法命名冲突的时候,此时必须使用内部类来实现。
//用接口不能完全地实现多继承,用接口配合内部类才能实现真正的多继承。
二、内部类分类 - 实现
1、成员内部类
与成员变量、成员方法类似,作为某个类的成员定义的类,叫成员内部类
// 定义外部类 - 外部类修饰符只能为:public 或 default(缺省- 默认不写)
class Outer {
//1.外部类成员
protected int age;
//2.成员内部类
// 修饰符可以为:与类成员一样,都可以使用四个修改符
[访问修饰符] class 内部类名 {
//3.内部类成员
}
[访问修饰符] 数据类型 方法名([参数列表]) {
内部类名 对象 = new 内部类名() ;
对象.成员...
}
}
说明
1)作为外部类的一个成员存在,与外部类的属性、方法并列
2)在内部类中,访问内部类的成员:this.成员
3)在内部类中,访问外部类的成员:外部类名.this.成员 或 直接访问成员
4)成员内部类中,不可以有静态成员(为什么?与成员方法中,不允许访问静态成员一样,成员内部类中不能存在任何static的变量和方法)
5)成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类。语法如下:
// 方法二:
// 外部类 外部类对象 = new 外部类() ;
OuterClass oc = new OuterClass();
// 外部类名称.内部类名称 内部类对象 = 外部类对象.new 内部类名称() ;
oc.InnerClass ic = oc.new InnerClass();
// 方法二:
// 外部类名称.内部类名称 内部类对象 = new 外部类名称().new 内部类名称() ;
OuterClass.InnerClass ic = new OuterClass().new InnerClass();
注意:内部类的修饰符不能为私有的!!
优点:
1)内部类作为外部类的成员,可以访问外部类的私有成员或属性。(即使将外部类的成员声明为private,但是对于处于其内部的内部类还是可见的。)
2)用内部类定义在外部类中不可访问的属性。这样就在外部类中实现了比外部类的private还要小的访问权限。注意:内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。对于一个名为Outer的外部类和其内部定义的名为Inner的内部类。编译完成后出现Outer.class和Outer$Inner.class两类。
/**
内部类只能在内部使用
第一:私有化内部类
第二:在外部类中,编写公开方法,创建并使用私有的内部类
第三:创建外部类对象,调用公开方法
*/
public class Outer {
private int count = 100 ;
//1、private修改内部类,则此类只能在类内部使用
private class Inner {
public void display() {
System.out.println("count="+count);
}
}
//2、在外部类中,编写公开方法,创建并使用私有的内部类
public void test() {
Inner inner = new Inner() ;
inner.display();
}
}
public class MainTest {
public static void main(String[] args) {
//3、创建外部类对象,通过调用公开方法来使用私有内部类
Outer outer = new Outer() ;
outer.test();
}
}
内部类对象保存了一个对外部类对象的引用
例子:
/**
内部类被外部使用
第一:在外部类中,非私有化内部类
第二:创建外部类对象
外部类 外部类对象 = new 外部类() ;
第三:创建内部类对象
外部类名称.内部类名称 内部类对象 = 外部类对象.new 内部类名称() ;
*/
//外部类
public class OuterClass {
private String name;
private int age;
private char sex ;
//省略setter/getter方法
//私有方法
private void menu() {
System.out.println("===用户信息===");
}
// 成员内部类:内部类作为外部类的成员,可以访问外部类的私有成员或属性
/**
语法一:
外部名名称.this.属性 [=值];
外部名名称.this.方法([实参列表]);
语法二:
属性 [=值] ;
方法([实参列表]);
注意:语法二中,如果内部类定义了与外部内同名的成员,则默认调用内部类的成员。
*/
public class InnerClass {
//定义与外部类相同名称的属性
int age = 20 ;
//成员类中,不允许存在静态成员
//static int staticA ;
//public static void staticMethod() {}
//内部类构造方法:访问外部内的私有成员
public InnerClass() {
OuterClass.this.name = "zing";
//注意:直接通过属性访问,则默认访问内部类的age属性。
//如果需要访问外部的属性必须通过外部类名.this.属性访问
//age = 18 ;
OuterClass.this.age = 18;
sex = '女';
}
//内部类成员方法:访问外部类的成员方法
public void display() {
OuterClass.this.menu();
System.out.println("姓名:" + getName());
System.out.println("性别:" + getSex());
System.out.println("年龄:" + getAge());
}
}
}
//测试类--主方法
public class MainTest {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
//语法:外部类名称.内部类名称 内部类对象 = 外部类对象.new 内部类名称() ;
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.display();
}
}
2、局部内部类
与局部变量类似,在方法内定义的类称为局部内部类;
语法:
//定义外部类
class Outer {
//1.外部类成员
//2.成员方法
[访问修饰符] 数据类型 方法名([参数列表]) {
//3.局部内部类 - 与局部变量一样,只能作用于当前方法- 不能使用修饰符!!
class 内部类名 {
//4.内部类成员
}
// 5.创建局部内部类对象:局部内部类名 对象 = new 局部内部类名() ;
// 6.调用属性或方法
}
}
说明
1)在方法中定义的内部类称为局部内部类;
2)与局部变量类似,在局部内部类前不允许加访问修饰符;外部类可以使用public 或 (default)修饰;成员内部类:可以使用public、protected、(default)、private;
3)局部内部类不仅可以访问外部类实例变量,还可以访问外部类的局部变量,但外部类的局部变量必须声明为final,JDK1.8+可以省略。原因:
- new出来的对象存放在堆内存中;
- 局部变量跟着方法走,存储在栈内存中;
- 方法运行结束后,立即出栈,局部变量随之消失;
- 而局部类new出来的对象在堆内存中持续存在,走到垃圾回收消失
4)在类外不可直接生成局部内部类(保证局部内部类对外是不可见的)。要想使用局部内部类时需要生成对象,对象调用方法,在方法中才能调用其局部内部类。
例子:
public class Outer {
//外部类成员变量
int count = 100 ;
public void test() {
//注意:JDK1.8中,会自动进行检测,当局部内部类访问局部变量时,会隐式在该局部变量加final关键字,声明为常量
int local1 = 200 ;
final int local2 = 300 ;
//local = 300 ; //在此,再次给local变量赋值,会报错,常量不允许重新赋值
//局部内部类
class Inner {
void display() {
//访问外部类的成员变量
System.out.println(count);
//访问方法的final局部变量,JDK1.8会自动声明为final
System.out.println(local1);
System.out.println(local2);
}
}
//实例化局部内部类对象,并调用方法
Inner inner = new Inner() ;
inner.display();
}
}
public class MainTest {
public static void main(String[] args) {
Outer outer = new Outer() ;
outer.test();
}
}
3、匿名内部类
匿名内部类是一种 特殊 的局部内部类,它是通过匿名类实现接口。
语法:
new 外部类().方法(new 接口|抽象类|父类(){
接口方法的实现();
或
重写抽象类或父类的方法() ;
}) ;
接口名称 对象名 = new 接口名称() {
// 注意:匿名内部类不能定义静态变量,可以定义成员变量
重写抽象方法;
}
//具体步骤看以下例子
要点:
匿名内部类是一种特殊的局部内部类,它是通过匿名类实现接口。
IA被定义为接口。IA I=new IA(){};
注:一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类,没有类名,根据多态,我们使用其父类名。因其为局部内部类,那么局部内部类的所有限制都对其生效。
匿名内部类是唯一一种无构造方法类。匿名内部类在编译的时候由系统自动起名Out$1.class。
如果一个对象编译时的类型是接口,那么其运行的类型为实现这个接口的类。
因匿名内部类无构造方法,所以其使用范围非常的有限。
例子:
//第一:定义一个接口、抽象类或父类
public interface IA {
abstract void fun1();
}
//第二:定义外部类
public class Outer {
public void test1() {
//局部内部类
class Inner implements IA {
@Override
public void fun1() {
System.out.println("匿名类实现fun1--方式一");
}
}
//第四:调用父类对象方法,传递接口实现对象或父类的子类对象(重写或实现方法)
new Outer().callInner(new Inner());
}
//匿名内部类
public void test2() {
new Outer().callInner(new IA() {
@Override
public void fun1() {
System.out.println("匿名类实现fun1--方式二");
}
});
}
//第三:定义一个方法,参数为父类对象引用或接口对象
public void callInner(IA a) {
a.fun1();
}
}
public class MainTest {
public static void main(String[] args) {
Outer outer = new Outer() ;
outer.test1();
outer.test2();
}
}
总结:接口的使用有以下两种用法,分别是:
- 传统用法 - 必须预告定义好相关的实体类
// 接口用法一:传统的用法,需要定义实现类
// 接口 对象 = new 实现类();
- 匿名内部类实现 - 直接 new 接口的同时,使用匿名内部类实现接口,只能使用一个,用完就没有了
- 接口使用的位置有以下三种情况:
// 接口用法二:匿名内部类实现
// 特点:一般只用一次,用完就丢
接口 对象 = new 接口(){
// 实现接口的方法
1.成员变量
2.成员方法
// 注意:匿名内部类不允许定义静态的成员,但是可以定义常量
3.接口抽象方法的实现;
}
// 接口用法三:接口作为方法参数的使用
xxx.方法名(new 接口名称(){
});
// 接口用法四:接口作为方法返回值的使用
访问修饰符 接口名称 方法名称(){
new return 接口名称(){
// 实例接口的方法
}
}
4、静态内部类
与静态变量、静态方法类似,作为某个类的静态成员定义的类,叫成员内部类
语法:
- 调用(类名.静态内部类 && 对象.静态内部类)
//定义外部类
class Outer {
//1.外部类成员
//2.成员内部类
[访问修饰符] static class 内部类名 {
//3.内部类成员
}
}
要点:
静态内部类定义在类中,任何方法外,用static定义
静态内部类只能访问外部类的静态成员
生成(new)一个静态内部类不需要外部类成员:这是静态内部类和成员内部类的区别
静态内部类的对象可以直接生成:Outer.Inner in=new Outer.Inner();而不需要通过生成外部类对象来生成。这样实际上使静态内部类成为了一个顶级类。
静态内部类不可用private来进行定义。
例子:
//外部类
public class Outer {
int count1 = 100 ;
static int count2 = 200 ;
void test() {
Inner inner = new Inner() ;
inner.display();
}
//静态内部类
public static class Inner {
void display() {
//错误:静态内部类不允许访问外部类的非静态成员
//System.out.println(count1);
System.out.println(count2);
}
}
}
public class MainTest {
public static void main(String[] args) {
//内部类只能在内部使用(内部类声明为private)
Outer outer = new Outer() ;
outer.test() ;
//内部类被外部使用,语法为:
//外部类.内部类 内部类对象 = new 外部类.内部类() ;
Outer.Inner inner = new Outer.Inner();
inner.display();
}
}