likes
comments
collection
share

掌握Java内部类和枚举

作者站长头像
站长
· 阅读数 10

内部类

概述

什么是内部类:

将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,类B则称为外部类。

为什么要用内部类:

那么为什么要声明内部类呢,总的来说是为了遵循高内聚、低耦合的面向对象开发的原则,便于代码的维护和扩展。

另外,当一个事物的内部还有一部分需要一个完整的结构进行描述,而这个内部的完整结构又只是为外部事物提供服务,不在其他的地方单独使用,那么整个内部的完整结构最好使用内部类,且内部类因为在外部类的里面,还可以直接访问外部类的私有成员。

内部类的分类:

掌握Java内部类和枚举

根据内部类声明的位置,可以分为成员内部类和局部内部类。

  • 成员内部类:可以分为静态成员内部类和非静态成员内部类
  • 局部内部类:可以分为有名字的局部内部类和匿名的内部类

成员内部类

如果成员内部类中不使用外部类的非静态成员,那么通常会将内部类声明为静态内部类,否则声明为非静态内部类。

语法格式如下:

[修饰符] class 外部类{
    [其他修饰符] [static] class 内部类{
    }
}

成员内部类的使用特征:

  • 成员内部类作为类的成员的角色
    • 和外部类不同,内部类还可以声明为privateprotected
    • 可以调用外部类的结构
    • 内部类可以声明为static,但此时就不能再使用外部类的非静态的成员变量了
  • 成员内部类作为类的角色
    • 可以在内部定义属性、方法、构造器等结构
    • 可以声明为abstract类,因此可以被其他的内部类继承
    • 可以声明为final
    • 编译后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)

注意:

  1. static的成员内部类中的成员不能声明为static,只有在外部类或static的成员内部类中才可以声明static成员。
  2. 外部类访问成员内部类的成员时,需要内部类.成员内部类对象.成员的方式。
  3. 成员内部类可以直接使用外部类的所有成员,包括私有的数据。
  4. 当想在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的。

静态成员内部类

静态成员内部类使用static修饰,特点如下:

  • 和其他类一样,只是定义在外部类中的另一个完整的类结构
    • 可以继承想要继承的父类,实现想要实现的接口,和外部类的父类和父接口无关
    • 可以在静态内部类中声明属性、方法、构造器等结构,包括静态成员
    • 可以使用abstract修饰,因此也可以被其他类继承
    • 可以使用final修饰,表示不能被继承
    • 编译后有自己的独立的字节码文件,只不过是在内部类名前面冠以外部类名和$符号
  • 和外部类不同的是,内部类可以允许使用四种权限修饰符
    • 外部类只允许使用public缺省
  • 可以在静态内部类中使用外部类的静态成员
    • 在静态内部类中不能使用外部类的非静态成员
    • 在内部类中有变量与外部类的静态成员变量同名时,可以使用外部类名.进行区分
  • 在外部类的外面不需要通过外部类的对象就可以创建静态内部类的对象(通常应避免这样使用)

提示: 严格的讲,静态内部类不是内部类,而是类似于C++的嵌套类的概念,外部类仅仅是静态内部类的一种命名空间的限定名形式而已,所以接口中的内部类通常都不叫内部类,因为接口中的内部成员都是隐式是静态的(即public static)。

非静态成员内部类

没有static修饰的成员内部类叫做非静态内部类,特点如下:

  • 和其他类一样,只是定义在外部类中的另一个完整的类结构
    • 可以继承想要继承的父类,实现想要实现的接口,和外部类的父类和父接口无关
    • 可以在非静态内部类中声明属性、方法、构造器等结构,但不允许声明静态成员,可以继承父类的静态成员,且可以声明静态常量
    • 可以使用abstract修饰,因此可以被其他类继承
    • 可以使用final修饰,表示不能被继承
    • 编译后有自己的独立的字节码文件,只不过是在内部类名前冠以外部类名和$符号
  • 和外部类不同的是,可以允许使用四种权限修饰符
    • 外部类只能用public缺省
  • 可以在非静态内部类中使用外部类的所有成员,哪怕是私有的
  • 在外部类的静态成员中不可以使用非静态内部类
    • 如同静态方法中不能访问本类的非静态成员变量和非静态方法一样
  • 在外部类的外面必须通过外部类的对象才能创建非静态内部类的对象(通常应避免这样用)
    • 如果要在外部类的外面使用非静态内部类的对象,通常在外部类中提供一个方法来返回这个非静态内部类的对象比较合适
    • 因此在非静态内部类的方法中有两个this对象,一个是外部类的this对象,一个是内部类的this对象

创建成员内部类对象

实例化静态内部类:

外部类名.静态内部类名 变量 = 外部类名.静态内部类名();
变量.非静态成员();

实例化非静态内部类:

外部类名 变量1 = new 外部类();
外部类名.非静态内部类名 变量 = 变量1.new 非静态内部类名();
变量.非静态成员();

示例

下面看个例子:

//测试类
public class TestMemberInnerClass {
    public static void main(String[] args) {
        Outer.outMethod();
        System.out.println("------");
        Outer out = new Outer();
        out.outFun();
        System.out.println("######");
        Outer.Inner.inMethod();
        System.out.println("------");
        Outer.Inner inner = new Outer.Inner(); //实例化静态内部类
        inner.inFun();
        System.out.println("######");
        Outer outer = new Outer(); //实例化非静态内部类
	//Outer.Nei nei = outer.new Nei();
        Outer.Nei nei = out.getNei();
        nei.inFun();
    }
}
//外部类
class Outer{
    private static String a = "外部类的静态a";
    private static String b  = "外部类的静态b";
    private String c = "外部类对象的非静态c";
    private String d = "外部类对象的非静态d";
    //静态内部类
    static class Inner{
        private static String a ="静态内部类的静态a";
        private String c = "静态内部类对象的非静态c";
        public static void inMethod(){
            System.out.println("Inner.inMethod");
            System.out.println("Outer.a = " + Outer.a);
            System.out.println("Inner.a = " + a);
            System.out.println("b = " + b);
	    //System.out.println("c = " + c); //不能访问外部类和自己的非静态成员
	    //System.out.println("d = " + d); //不能访问外部类的非静态成员
        }
        public void inFun(){
            System.out.println("Inner.inFun");
            System.out.println("Outer.a = " + Outer.a);
            System.out.println("Inner.a = " + a);
            System.out.println("b = " + b);
            System.out.println("c = " + c);
	    //System.out.println("d = " + d); //不能访问外部类的非静态成员
        }
    }
    //非静态内部类
    class Nei{
        private String a = "非静态内部类对象的非静态a";
        private String c = "非静态内部类对象的非静态c";
        public void inFun(){
            System.out.println("Nei.inFun");
            System.out.println("Outer.a = " + Outer.a);
            System.out.println("a = " + a);
            System.out.println("b = " + b);
            System.out.println("Outer.c = " + Outer.this.c);
            System.out.println("c = " + c);
            System.out.println("d = " + d);
        }
    }
    //外部类的静态方法
    public static void outMethod(){
        System.out.println("Outer.outMethod");
        System.out.println("a = " + a);
        System.out.println("Inner.a = " + Inner.a);
        System.out.println("b = " + b);
	//System.out.println("c = " + c);
	//System.out.println("d = " + d);
        Inner in = new Inner();
        System.out.println("in.c = " + in.c);
    }
    //外部类的非静态方法
    public void outFun(){
        System.out.println("Outer.outFun");
        System.out.println("a = " + a);
        System.out.println("Inner.a = " + Inner.a);
        System.out.println("b = " + b);
        System.out.println("c = " + c);
        System.out.println("d = " + d);
        Inner in = new Inner();
        System.out.println("in.c = " + in.c);
    }
    
    public Nei getNei(){
        return new Nei();
    }
}

局部内部类

有名字的局部内部类

语法格式如下:

【修饰符】 class 外部类{
    【修饰符】 返回值类型  方法名(【形参列表】){
final/abstract】 class 内部类{
    	}
    }    
}

局部内部类的特点如下:

  • 和外部类一样,只是定义在外部类的某个方法中的另一个完整的类结构
    • 可以继承想继承的父类,实现想实现的父接口,和外部类的父类和父接口无关
    • 可以在局部内部类中声明属性、方法、构造器等结构,但不包括静态成员,除非是从父类继承的或静态常量
    • 可以使用abstract修饰,因此可以被同一个方法在它后面的其他内部类继承
    • 可以使用final修饰,表示不能被继承
    • 编译后有自己独立的字节码文件,只不过是在内部类名前面冠以外部类名和$符号编号等(这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类)
  • 和成员内部类不同的是,它前面不能有修饰符等
  • 局部内部类如同局部变量一样有作用域
  • 局部内部类中是否能访问外部类的非静态成员,取决于所在的方法
  • 局部内部类中还可以使用所在方法的局部常量,即用final声明的局部变量
    • JDK1.8之后,如果某个局部变量在局部内部类中被使用了,会自动加final
    • 考虑到生命周期问题,在局部内部类中使用外部类方法的局部变量要加上final

示例:

//测试类
public class TestLocalInner {
    public static void main(String[] args) {
        Runner runner = Outer.getRunner();
        runner.run();
        System.out.println("------");
        Outer.outMethod();
        System.out.println("------");
        Outer out = new Outer();
        out.outTest();
    }
}
//外部类
class Outer{
    private static String a = "外部类的静态变量a";
    private String b = "外部类对象的非静态变量b";
    //外部类的静态方法
    public static void outMethod(){
        System.out.println("Outer.outMethod");
        final String c = "局部变量c";
        //局部内部类
        class Inner{
            public void inMethod(){
                System.out.println("Inner.inMethod");
                System.out.println("out.a = " + a);
		//System.out.println("out.b = " + b);//错误的,因为outMethod是静态的
                System.out.println("out.local.c = " + c);
            }
        }
        Inner in = new Inner();
        in.inMethod();
    }
    //外部类的非静态方法
    public void outTest(){
        //局部内部类
        class Inner{
            public void inMethod(){
                System.out.println("out.a = " + a);
                System.out.println("out.b = " + b);//可以,因为outTest是非静态的
            }
        }
        Inner in = new Inner();
        in.inMethod();
    }
    //外部类的静态方法
    public static Runner getRunner(){
        //局部内部类实现接口
        class LocalRunner implements Runner{
            @Override
            public void run() {
                System.out.println("LocalRunner.run");
            }
        }
        return new LocalRunner();
    }
}
//接口
interface Runner{
    void run();
}

匿名的局部内部类

在开发中,需要用到一个抽象类的子类的对象或一个接口的实现类的对象时,且只创建一个对象,而且逻辑代码也不复杂,原先的步骤就是:

  • 编写类,继承这个父类或实现这个接口
  • 重写父类或父接口的方法
  • 创建这个子类或实现类的对象

如果这个子类或实现类是一次性的,这样做就显得有些多余,这个时候就可以使用匿名内部类的方式来实现了,匿名内部类避免了给类命名的问题。

//是否需要实参,取决于让匿名内部类调父类的哪个构造器,如果是无参构造器就不用
new 父类(【实参列表】){
    重写方法...
}
//没有参数是因为此时匿名内部类的父类是Object类,只有一个无参构造器
new 父接口(){
    重写方法...
}

提示: 匿名内部类是没有名字的类,因此在声明类的同时就创建好了唯一的对象。

匿名内部类是一种特殊的局部内部类,只不过没有名字而已,所有局部内部类的限制都适用于匿名内部类,比如下面:

  • 在匿名内部类中是否可以使用外部类的非静态成员变量,看所在方法是否静态
  • 在匿名内部类中如果需要访问当前方法的局部变量,该局部变量需要加final

示例:

//使用匿名内部类的对象直接调用方法
interface A{
    void a();
}
public class Test{
    public static void main(String[] args){
    	new A(){
		@Override
		public void a() {
			System.out.println("aaaa");
		}
    	}.a();
    }
}
//通过父类或父接口的变量多态引用匿名内部类的对象
interface A{
    void a();
}
public class Test{
    public static void main(String[] args){
    	A obj = new A(){
			@Override
			public void a() {
				System.out.println("aaaa");
			}
    	};
    	obj.a();
    }
}
//匿名内部类的对象作为实参
interface A{
    void method();
}
public class Test{
    public static void test(A a){
    	a.method();
    }
    public static void main(String[] args){
    	test(new A(){
			@Override
			public void method() {
				System.out.println("aaaa");
			}
    	});
    }   
}

枚举类

概述

有很多这样的例子,某些类的对象是有限的几个,比如星期、性别、月份、季节等,这时候就需要用到枚举类了。

枚举类型本质上也是一种类,只不过是这个类的对象是固定的几个,而不是能随意让用户创建。

若枚举只有一个对象,则可以作为一种单例模式的实现方式。

语法格式

在JDK5.0之前,枚举类的实现需要程序员自己定义枚举类型,在JDK5.0之后,Java支持enum关键字来快速定义枚举类型。

定义枚举类(JDK5.0之前)

在JDK5.0之前声明枚举类的步骤如下:

  • 私有化类的构造器,保证不能在类的外部创建其对象
  • 在类的内部创建枚举类的实例,声明为public static final,然后对外暴露这些常量对象
  • 对象如果有实例变量,应该声明为private final(建议不是必须),并在构造器中初始化

示例:

class Season{
    private final String SEASONNAME; //季节的名称
    private final String SEASONDESC; //季节的描述
    private Season(String seasonName,String seasonDesc){
        this.SEASONNAME = seasonName;
        this.SEASONDESC = seasonDesc;
    }
    public static final Season SPRING = new Season("春天", "春暖花开");
    public static final Season SUMMER = new Season("夏天", "夏日炎炎");
    public static final Season AUTUMN = new Season("秋天", "秋高气爽");
    public static final Season WINTER = new Season("冬天", "白雪皑皑");
    @Override
    public String toString() {
        return "Season{" +
                "SEASONNAME='" + SEASONNAME + '\'' +
                ", SEASONDESC='" + SEASONDESC + '\'' +
                '}';
    }
}
class SeasonTest{
    public static void main(String[] args) {
        System.out.println(Season.AUTUMN);
    }
}

定义枚举类(JDK5.0之后)

JDK5.0之后用enum关键字声明枚举,格式如下:

【修饰符】 enum 枚举类名{
    常量对象列表
}

【修饰符】 enum 枚举类名{
    常量对象列表;
    其他成员列表;
}

示例:

//定义枚举类
public enum Week {
    MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY;
}

enum方式定义的要求和特点:

  • 使用enum定义的枚举类默认继承了java.lang.Enum类,因此不能再继承其他类,同时说明枚举本质上还是类
  • 枚举类的常量对象列表必须在枚举类的首行,因为是常量所以建议大写
  • 列出的实例系统会自动添加public static final修饰
  • 如果常量对象列表后面没有其他代码,那么;可以省略,否则不可省略
  • 编译器给枚举类默认提供的是private的无参构造,如果枚举类需要无参构造就不需要声明,写常量对象列表时也不用加参数
  • 如果枚举类需要有参构造,需要手动定义,有参构造的private可以省略,调用有参构造的方法就是在常量对象名后面加(实参列表)就可以。
  • 枚举类默认继承的是java.lang.Enum类,因此不能再继承其他的类
  • JDK5.0之后switch提供支持枚举类型,case后面可以写枚举常量名,无需添加枚举类作为限定

示例:

//定义枚举类
public enum SeasonEnum {
    SPRING("春天","春风又绿江南岸"),
    SUMMER("夏天","映日荷花别样红"),
    AUTUMN("秋天","秋水共长天一色"),
    WINTER("冬天","窗含西岭千秋雪");

    private final String seasonName;
    private final String seasonDesc;
    private SeasonEnum(String seasonName, String seasonDesc) {
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }
    public String getSeasonName() {
        return seasonName;
    }
    public String getSeasonDesc() {
        return seasonDesc;
    }
}

提示: 开发中,当需要定义一组常量时,强烈建议使用枚举类。

枚举中常用方法

枚举中常用方法如下:

String toString() //默认返回的是常量名(对象名),可以继续手动重写该方法
static 枚举类型[] values() //返回枚举类型的对象数组,该方法是一个静态方法,可以很方便地遍历所有的枚举值
static 枚举类型 valueOf(String name) //可以把一个字符串转为对应的枚举类对象,要求字符串必须是枚举类对象的名字,如果不是会报运行时异常IllegalArgumentException
String name() //得到当前枚举常量的名称,建议优先使用toString()
int ordinal() //返回当前枚举常量的次序号,默认从0开始

示例:

public class TestEnumMethod {
    public static void main(String[] args) {
        Week[] values = Week.values();
        for (int i = 0; i < values.length; i++) {
            System.out.println((values[i].ordinal()+1) + "->" + values[i].name());
        }
        System.out.println("------");
        Scanner input = new Scanner(System.in);
        System.out.print("请输入星期值:");
        int weekValue = input.nextInt();
        Week week = values[weekValue-1];
        System.out.println(week);
        System.out.print("请输入星期名:");
        String weekName = input.next();
        week = Week.valueOf(weekName);
        System.out.println(week);
        input.close();
    }
}

实现接口的枚举类

枚举的本质还是类,那么就可以实现接口。

  • 和普通Java类一样,枚举类可以实现一个或多个接口
  • 若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可
  • 若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法

语法格式:

//枚举类可以像普通的类一样,实现多个接口,但必须实现里面所有的抽象方法
enum A implements 接口1,接口2{
	//抽象方法的实现
}
//如果枚举类是常量可以继续重写抽象方法
enum A implements 接口1,接口2{
    常量名1(参数){
        //抽象方法的实现或重写
    },
    常量名2(参数){
        //抽象方法的实现或重写
    },
    ...
}

示例:

//定义接口
interface Info{
    void show();
}
//使用enum关键字定义枚举类
enum Season1 implements Info{
	//创建枚举类中的对象,声明在enum枚举类的首位
	SPRING("春天","春暖花开"){
		public void show(){
			System.out.println("春天在哪里");
		}
	},
	SUMMER("夏天","夏日炎炎"){
		public void show(){
			System.out.println("夏天在哪里");
		}
	},
	AUTUMN("秋天","秋高气爽"){
		public void show(){
			System.out.println("秋天在哪里");
		}
	},
	WINTER("冬天","白雪皑皑"){
		public void show(){
			System.out.println("冬天在哪里");
		}
	};
	//声明每个对象拥有的属性(private final修饰)
	private final String SEASON_NAME;
	private final String SEASON_DESC;
	//私有化类的构造器
	private Season1(String seasonName,String seasonDesc){
		this.SEASON_NAME = seasonName;
		this.SEASON_DESC = seasonDesc;
	}
	public String getSEASON_NAME() {
		return SEASON_NAME;
	}
	public String getSEASON_DESC() {
		return SEASON_DESC;
	}
}

今天的内容就到这里,喜欢的话点个关注吧,下篇见!