`

Java学习系列(五)Java面向对象之抽象类、接口、内部类、枚举类详解

 
阅读更多

抽象类、接口常常与设计模式紧密相连。掌握抽象类、接口等其实很简单。下面以说明+实例的方式来讲,这样更容易理解。

抽象类

先看一个关键字“abstract”,我们知道它是抽象的意思。所谓抽象,说的直白一点就是同一件事情,有不同的实现。比如呼吸这个动作,人需要肺呼吸,而鱼需要鳃呼吸。代码实现如下:

Java代码收藏代码
  1. abstractclassCommonMode{
  2. abstractvoidbreathe(Stringname);
  3. }
  4. classFishextendsCommonMode{
  5. @Override
  6. voidbreathe(Stringname){
  7. System.out.println("鱼呼吸用"+name);
  8. }
  9. }
  10. classPersonextendsCommonMode{
  11. @Override
  12. voidbreathe(Stringname){
  13. System.out.println("人呼吸用"+name);
  14. }
  15. }
  16. publicclassTest{
  17. publicstaticvoidmain(String[]args){
  18. CommonModefish=newFish();
  19. CommonModeperson=newPerson();
  20. fish.breathe("鳃");
  21. person.breathe("肺");
  22. }
  23. }

这样我们就不难理解所谓”抽象化”,就是指从具体问题中,提取出具有共性的模式,再使用通用的解决方法加以处理。需要注意的是abstract修饰符只能修饰类或方法。修饰类时该类就成了抽象类,抽象类用四个字就可以概括:有得有失。所谓“得”就是增加了一个可以包含抽象方法(由子类实现)的功能,所谓“失”就是不能创建实例,而其它的功能普通类有的抽象类都有(如:定义普通方法、初始化快、内部类等),需要补充的是抽象类的构造器主要是提供其子类的构造器调用。抽象方法:使用abstract修饰,它没有方法体,而且它必须由子类重写,由于子类必须重写抽象父类的方法,所以abstract不能与final(有final修饰的方法,意味着不能被重写)同时出现,而且子类继承了(抽象)父类,意味着abstract不能与final同时出现;除此之外,abstract还不能与static、private(private意味着不能被子类访问)同时存在。注意子类要实现抽象类的所有抽象方法,否则子类也是抽象类。抽象类的作用主要是与“模板模式”联系在一起,以后会在设计模式中讲到,这里就不细说了。为加深理解,再举一例:

Java代码收藏代码
  1. abstractclassParentClass{
  2. //定义抽象方法
  3. abstractvoidlearn(Stringname);
  4. //抽象父类的构造器
  5. ParentClass(){
  6. System.out.println("父类无参构造器被调用!");
  7. }
  8. {
  9. System.out.println("实例初始化块被调用!");
  10. }
  11. static{
  12. System.out.println("类初始化块被调用!");
  13. }
  14. //定义内部类(下面会讲到)
  15. classA{
  16. }
  17. //定义普通方法
  18. publicvoidinfo(){
  19. System.out.println("抽象父类中的info方法被调用!");
  20. }
  21. }
  22. classSubClassextendsParentClass{
  23. //当执行这个方法时,会调用抽象父类的构造器
  24. publicSubClass(){}
  25. @Override
  26. voidlearn(Stringname){
  27. System.out.println("我在学习"+name);
  28. }
  29. publicvoidtest(){
  30. System.out.println("子类test方法被调用!");
  31. }
  32. }
  33. publicclassAbstractClassTest{
  34. publicstaticvoidmain(String[]args){
  35. //说明:newSubClass()会创建一个SubClass类的对象存放在堆内存中,
  36. //并让pc这个引用变量指向这个刚创建的对象,这个就是我们常听到的父类引用指向子类对象。
  37. //当我们只写newSubClass();来创建子类对象,我们从打印语句中我们可以看到:父类无参构造器被调用!
  38. //这说明父类构造器主要是提供其子类的构造器调用
  39. ParentClasspc=newSubClass();
  40. //因为子类的实例完全可以当成父类的对象使用,并且子类SubClass重写了抽象父类的learn方法,所以当然可以调用
  41. pc.learn("Java");
  42. pc.info();
  43. //当我们写pc.test();我们会看到编译器会报错,因为抽象父类ParentClass中根本没有test方法被定义
  44. }
  45. }

接口(interface)

接口的作用非常丰富,接口往往是和设计模式结合在一起的。接口是从多个相似的类中提取出来的一种规范,接口可认为是一种彻底的抽象类。接口定义的语法格式如下:

[修饰符] interface 接口名{
//可以有属性定义、抽象方法(有方法的话只能是抽象的)、内部类/内部接口/内部枚举定义
}

修饰符:public/省略;接口名:每个单词首字母大写,推荐接口用“形容词”。field默认有3个修饰符:public static final;抽象方法默认有2个修饰符:public abstract;内部类/内部接口/内部枚举定义默认也有2个修饰符:public static;注意属性声明时必须指定初始值,原因是final修饰的类变量只能在声明时、静态初始化块中指定初始值,但接口不包含初始化块,所以属性声明时必须指定初始值,并且推荐其属性变量所有字母全部大写。下面通过实例说明:

Java代码收藏代码
  1. interfaceIdentifiable{
  2. voidinsert(Stringname);
  3. }
  4. interfaceA{
  5. //接口里的属性声明必须指定初始值
  6. intVALUE=100;
  7. //接口里面可以有内部接口
  8. interfaceB{
  9. intINNERVALUE=20;
  10. }
  11. }
  12. //一个接口可以有N个直接父接口
  13. interfaceIRechargeableextendsIdentifiable,A{
  14. voidcharge(Stringname);
  15. }
  16. classUsbimplementsIdentifiable{
  17. @Override
  18. publicvoidinsert(Stringname){
  19. System.out.println("插入"+name);
  20. }
  21. }
  22. //子类可以实现多个接口
  23. classDataLineimplementsIdentifiable,IRechargeable{
  24. @Override
  25. publicvoidinsert(Stringname){
  26. System.out.println("插入"+name);
  27. }
  28. @Override
  29. publicvoidcharge(Stringname){
  30. System.out.println("数据线为"+name+"充电");
  31. }
  32. }
  33. publicclassInterfaceTest{
  34. publicstaticvoidmain(String[]args){
  35. Identifiableusb=newUsb();
  36. IRechargeabledl=newDataLine();
  37. usb.insert("U盘");
  38. dl.insert("数据线");
  39. dl.charge("手机");
  40. System.out.println(IRechargeable.VALUE);
  41. System.out.println(A.B.INNERVALUE);
  42. }
  43. }

一般我们在功要实现的功能少时用抽象类;功能多时用接口。
内部类

内部类(在界面编程点击处理事件中经常使用)也称寄生类,实质就是把一个类作为类的成员放在类里面定义。听起来有点绕,下面通过实例来说明:

Java代码收藏代码
  1. //在外部类里面访问非静态内部类
  2. publicclassOuter{
  3. intvalue=1;
  4. classInner{
  5. intvalue=2;
  6. publicvoidinfo(){
  7. intvalue=3;
  8. //由于info方法里面的局部变量,所以此时输出的value为3
  9. System.out.println("value="+value);
  10. //由于this在方法中代表调用该方法的对象,所以value为该Inner类对象的属性值2
  11. System.out.println("value="+this.value);
  12. //由于该方法所在的类时没有static修饰的内部类,
  13. //所以该类必须寄生在“外部类”的实例里,这里的Outer.this就代表了外部类的实例
  14. //关于this,你只要记住一点就行:方法是谁的,就用谁来调用
  15. System.out.println("value="+Outer.this.value);
  16. //比如下面的调用:Outer.this内部类寄生在外部类的实例里面,
  17. //既然Outer.this代表外部类(宿主)的实例,那么当然可以访问外部类的成员(包括field、方法)。
  18. Outer.this.test2();
  19. }
  20. }
  21. publicvoidtest(){
  22. newInner().info();
  23. }
  24. publicvoidtest2(){
  25. System.out.println("外部类的成员方法test2被调用!");
  26. }
  27. publicstaticvoidmain(String[]args){
  28. Outerouter=newOuter();
  29. outer.test();
  30. }
  31. }

Java代码收藏代码
  1. //在外部类里面访问静态内部类
  2. publicclassOuter{
  3. intvalue=1;
  4. staticclassInner{
  5. intvalue=2;
  6. publicvoidinfo(){
  7. intvalue=3;
  8. System.out.println("value="+this.value);
  9. }
  10. }
  11. publicstaticvoidmain(String[]args){
  12. newInner().info();
  13. }
  14. }
Java代码收藏代码
  1. //在外部类外面访问非静态内部类
  2. classOut{
  3. intvalue=1;
  4. classIn{
  5. intvalue=2;
  6. publicvoidinfo(){
  7. intvalue=3;
  8. System.out.println("value="+value);
  9. System.out.println("value="+this.value);
  10. System.out.println("value="+Out.this.value);
  11. }
  12. }
  13. }
  14. publicclassTest{
  15. publicstaticvoidmain(String[]args){
  16. //注意:没有static修饰的内部类,必须寄生在“外部类”的实例里
  17. Out.Inin=newOut().newIn();
  18. in.info();
  19. }
  20. }
Java代码收藏代码
  1. //在外部类外面访问静态内部类
  2. classOut{
  3. intvalue=1;
  4. staticclassIn{
  5. intvalue=2;
  6. publicvoidinfo(){
  7. intvalue=3;
  8. System.out.println("value="+value);
  9. System.out.println("value="+this.value);
  10. //静态内部类也属于静态成员,因此它不能访问外部类的非静态成员(包括属性、方法)。所以下面写法是错的
  11. //System.out.println("value="+Out.this.value);
  12. }
  13. }
  14. }
  15. publicclassTest{
  16. publicstaticvoidmain(String[]args){
  17. //注意:有static修饰的内部类属于外部类本身,这是只需将外部类看成是内部类的包名即可
  18. Out.Inin=newOut.In();
  19. in.info();
  20. }
  21. }

还有一种非静态内部类派生子类:由于子类的构造器必须调用父类构造器一次,因此必须在子类构造器中使用宿主对象来调用它的构造器。这个用的比较少,这里就不细谈了。
下面讲一下匿名内部类:当程序创建匿名内部类时,会立即创建匿名内部类(实现类)的实例。

定义语法格式如下:
new 接口() | 父类构造器(参数){
//类体部分。
};

使用规则:1.匿名内部类必须显式的继承一个父类,或实现一个接口。2.匿名内部类必须实现接口或抽象类中所有的抽象方法。3.匿名内部类不能有构造器而且程序以后无法再访问它,因为它没有类名。实例说明如下:

Java代码收藏代码
  1. interfaceIdentifiable{
  2. voidinsert(Stringname);
  3. }
  4. publicclassTest{
  5. publicstaticvoidmain(String[]args){
  6. //此处相当于创建了Identifiable匿名的实现类,并创建了匿名内部类的实例
  7. //将实现类的实例赋值给接口变量,这是典型的“向上转型”
  8. Identifiableidf=newIdentifiable(){
  9. @Override
  10. publicvoidinsert(Stringname){
  11. System.out.println("插入"+name);
  12. }
  13. };
  14. idf.insert("U盘");
  15. }
  16. }

局部类:在当前方法里面定义,它只在该方法里有效,你可以把它当做一个局部变量,这个很少用,了解即可。

枚举类:枚举类是一种实例数固定的类,既然是实例固定,那当然不能创建实例。定义语法格式如下:

修饰符 enum 枚举名{
//立即在第一行列出该枚举的所有实例(但实际上是创建枚举实例,会默认调用无参构造器)。
}

--修饰符 public|省略| abstract|final(这两个必须出现一个),默认final;构造器无论是否使用private修饰,默认总是private。下面举例说明:

Java代码收藏代码
  1. publicenumTest{
  2. //列出所有枚举值,也就是该枚举类的所有可能的实例;
  3. //下面相当于:TestMALE=newTest("男");
  4. MALE("男"),FEMALE("女");
  5. privateStringname;
  6. Test(Stringname){
  7. this.name=name;
  8. }
  9. {
  10. System.out.println("实例初始化快被调用~~");
  11. }
  12. classA{}
  13. publicvoidinfo(){
  14. System.out.println("普通的info方法~"+this.name);
  15. }
  16. publicstaticvoidmain(String[]args){
  17. //Test.FEMAIL会调用构造方法,枚举类中有几个实例就调用几次构造方法
  18. //通过上一章,我们知道初始化快会在构造方法被调用之前调用(其实就是把初始化快放在了构造方法的第一行)
  19. //运行一下,我们会看到“实例初始化快被调用”被执行了两次
  20. Test.MALE.info();
  21. }
  22. }


下面再看一个实现接口的枚举:

Java代码收藏代码
  1. interfaceDirectionable{
  2. voidpointDirection();
  3. }
  4. //实现接口的枚举,如果直接实现所有的抽象方法,则此时枚举类就不再是抽象枚举。
  5. publicenumTestimplementsDirectionable{
  6. //下面这4个实例,相当于是publicstaticfinal修饰的
  7. EAST,WEST,SOUTH,NORTH;
  8. @Override
  9. publicvoidpointDirection(){
  10. System.out.println("指向"+this);
  11. }
  12. publicstaticvoidmain(String[]args){
  13. Test.SOUTH.pointDirection();
  14. }
  15. }

看了上了两个实例,枚举类是不是很简单呢,呵呵。下面再看一个难一点的。

Java代码收藏代码
  1. //抽象枚举需要创建匿名内部类
  2. publicenumTest{
  3. //当枚举类是抽象类时,还需要立即创建匿名内部类的实例
  4. //这里的ADD和ADD()其实是一样的,都是创建实例并实现抽象方法。
  5. ADD{
  6. @Override
  7. publicdoubleeval(doublem,doublen){
  8. returnm+n;
  9. }
  10. },SUB{
  11. @Override
  12. publicdoubleeval(doublem,doublen){
  13. returnm-n;
  14. }
  15. },MULTI{
  16. @Override
  17. publicdoubleeval(doublem,doublen){
  18. returnm*n;
  19. }
  20. },DIV{
  21. @Override
  22. publicdoubleeval(doublem,doublen){
  23. returnm/n;
  24. }
  25. };
  26. //如果枚举里面已经有了抽象方法,该枚举类默认就有了abstract修饰,此时该枚举类就没有了final修饰
  27. publicabstractdoubleeval(doublem,doublen);
  28. publicstaticvoidmain(String[]args){
  29. System.out.println(Test.MULTI.eval(9,9));
  30. }
  31. }

结束语:

今天就到这,明天开始学习Jar命令打包、正则表达式、国际化。

分享到:
评论

相关推荐

    【Java面试+Java学习指南】 一份涵盖大部分Java程序员所需要掌握的核心知识

    抽象类和接口 代码块和代码执行顺序 Java自动拆箱装箱里隐藏的秘密 Java中的Class类和Object类 Java异常 解读Java中的回调 反射 泛型 枚举类 Java注解和最佳实践 JavaIO流 多线程 深入理解内部类 javac和javap Java8...

    Java开发详解.zip

    020609_【第6章:面向对象(高级)】_抽象类与接口的应用笔记.pdf 020610_〖第6章:面向对象(高级)〗_实例分析:宠物商店笔记.pdf 020611_【第6章:面向对象(高级)】_Object类笔记.pdf 020612_【第6章:面向对象...

    Java工程师面试复习指南

    抽象类和接口 代码块和代码执行顺序 Java自动拆箱装箱里隐藏的秘密 Java中的Class类和Object类 Java异常 解读Java中的回调 反射 泛型 枚举类 Java注解和最佳实践 JavaIO流 多线程 深入理解内部类 javac和javap Java8...

    【05-面向对象(下)】

    •接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。 接口与抽象类的区别 •接口里只能包含抽象方法,不同包含已经提供实现的方法;抽象类则完全可以包含普通...

    Java 基础核心总结 +经典算法大全.rar

    continue 语句面向对象 类也是-种对象对象的创建 属性和方法 构造方法 方法重载 方法的重写 初始化 类的初始化 成员初始化 构造器初始化初始化顺序 数组初始化 对象的销毁 对象作用域 this 和 super 访问控制权限...

    Java基础知识点总结.docx

    五、 封装(面向对象特征之一)★★★★ 23 六、 继承(面向对象特征之一)★★★★ 25 七、 接口(面向对象特征之一)★★★★ 28 八、 多态(面向对象特征之一)★★★★ 30 九、 java.lang.Object 31 十、 异常★...

    疯狂JAVA讲义

    2.3 Java的面向对象特征 36 2.3.1 一切都是对象 37 2.3.2 类和对象 37 2.4 本章小结 37 第3章 数据类型和运算符 38 3.1 注释 39 3.1.1 单行注释和多行注释 39 3.1.2 文档注释 40 学生提问:API文档是什么? ...

    Java基础最全笔记文档

    2. 权限修饰符、常量、枚举、抽象类 3. 多态、内部类、常用API 4. 日期与时间、日期类、包装类、正则表达式、Arrays 类、常见算法、Lambda 表达式 5. Collection集合、数据结构、List集合、泛型、Set集合、可变参数 ...

    Java开发技术大全 电子版

    第2篇Java面向对象编程 第3章对象和类98 3.1面向对象的基本概念98 3.1.1对象98 3.1.2类99 3.1.3消息101 3.1.4面向对象的4个基本特征101 3.2类与对象104 3.2.1类的基本结构104 3.2.2类的声明104 3.2.3创建...

    整理后java开发全套达内学习笔记(含练习)

    nested [java] 嵌套的 ['nestid] '如:内部类(nested classes) Object [java] 对象 ['ɒbdʒekt] Overload [java] 方法的重载(不同参数列表的同名方法) [,әuvә'lәud] Override [java] 方法的覆盖(覆盖父类的...

    Scala程序设计(第2版)

    8.8 调用父类构造器(与良好的面向对象设计) 226 8.9 嵌套类型 230 8.10 本章回顾与下一章提要 232 第9章 特征 233 9.1 Java 8中的接口 233 9.2 混入trait 234 9.3 可堆叠的特征 238 9.4 ...

    asp.net知识库

    泛型技巧系列:避免基类及接口约束 New Article 不该用Generics实现Abstract Factory的理由 C#2.0-泛型 C#2.0-extern C#2.0-可空类型 C#2.0-分部类 C#2.0-迭代器 C#2.0 的新增功能学习 泛型的序列化问题 .NET 2.0 ...

Global site tag (gtag.js) - Google Analytics