Java系列(十)__Java面向对象(4)


Java系列(十)__Java面向对象(4)

1、继承性

在之前只是针对于类的基本结构与封装性做了一个最简单的介绍。现在最为重要的代码模型:简单Java类,同时利用链表实现对象数组的保存。

         继承性是面向对象的第二大特征,而只有学过继承性之后才可以更好的解决参数统一的问题。 

1.1、问题的引出

         在解释继承性的作用之前,首先来观察这样的一个场景。现在要求定义两个类:人、学生,按照之前的简单Java类的定义原则,现在类定义如下:

Person.java:

Student.java:

class Person {

         private String name ;

         private int age ;

         public void setName(String name) {

                   this.name = name ;

         }

         public void setAge(int age) {

                   this.age = age ;

         }

         public String getName() {

                   return this.name ;

         }

         public int getAge() {

                   return this.age ;

         }

}

class Student {

         private String name ;

         private int age ;

         private String school ;

         public void setName(String name) {

                   this.name = name ;

         }

         public void setAge(int age) {

                   this.age = age ;

         }

         public void setSchool(String school) {

                   this.school = school ;

         }

         public String getName() {

                   return this.name ;

         }

         public int getAge() {

                   return this.age ;

         }

         public String getSchool() {

                   return this.school ;

         }

}

         现在最为明显的是部分的代码出现了重复的情况,而且从现实的角度来讲,学生和人什么关系?学生一定是人,但是学生和人相比,学生要比人表现的更加的具体,而人的范围更广泛,那么在这样具备联系的情况下,以上代码就出现的严重的重复问题。

1.2、继承的使用

         在Java里面可以使用extends关键字来实现继承关系,但是严格来讲表示的是“扩充、扩展的含义”,意思指的是扩充已有类的功能。而此时继承的定义格式如下:

class 子类 extends 父类 {}

         而在此时子类有时候也被称为派生类、而父类也会被称为超类(Super Class)。

范例:实现继承

class Person {

         private String name ;

         private int age ;

         public void setName(String name) {

                   this.name = name ;

         }

         public void setAge(int age) {

                   this.age = age ;

         }

         public String getName() {

                   return this.name ;

         }

         public int getAge() {

                   return this.age ;

         }

}

class Student extends Person {     

}

         此时Student就成为了Person的子类,虽然此时Student类之中没有编写任何的操作,那么也可以直接使用Person类之中定义的结构。

public class TestDemo {

         public static void main(String args[]) {

                   Student stu = new Student() ;

                   stu.setName("小金子") ;       // 继承而来

                   stu.setAge(30) ;   // 继承而来

                   System.out.println(stu.getName() + "," + stu.getAge()) ;

         }

}

         一旦使用了继承,那么子类将具备父类之中的全部功能。在程序之中,子类可以选择性对父类的功能进行扩充,如果不需要也可以不扩充任何的方法,即:子类至少会维持父类之中的全部操作。

范例:让子类扩充操作

class Person {

         private String name ;

         private int age ;

         public void setName(String name) {

                   this.name = name ;

         }

         public void setAge(int age) {

                   this.age = age ;

         }

         public String getName() {

                   return this.name ;

         }

         public int getAge() {

                   return this.age ;

         }

}

class Student extends Person {     

         private String school ;

         public void setSchool(String school) {

                   this.school = school ;

         }

         public String getSchool() {

                   return this.school ;

         }

}

public class TestDemo {

         public static void main(String args[]) {

                   Student stu = new Student() ;

                   stu.setName("小金子") ;       // 继承而来

                   stu.setAge(30) ;   // 继承而来

                   stu.setSchool("清华大学") ;  // 扩充的方法

                   System.out.println(stu.getName() + "," + stu.getAge() + "," + stu.getSchool()) ;

         }

}

         此时程序已经满足了最初的要求,但是代码没有重复了。所以在日后进行类功能扩充的时候,如果是相关的类,可以考虑使用继承性来解决。但是今天对于此部分的内容暂时不做过多的涉及,有一个基本认识就好。

1.3、继承限定

         虽然继承可以进行类功能的扩充,但是在继承之中也会存在有若干个限制。

限制一:在Java之中一个类只允许继承一个父类

         本概念实际上是针对于C++进行解释的,在C++之中一个子类可以同时继承多个父类,这种操作称为多重继承,而此类形式在Java之中是不允许的,即:如下的代码是会有错误的。

class A {}

class B {}

class C extends A , B{}         // 同时继承了两个父类

         之所以会存在这样的继承关系,其主要目的是为了让C类同时具备有A、B两个类的所有操作。那么现在既然多重继承无法使用,就采用多层继承。以下代码是正确的。

class A {}

class B extends A {}

class C extends B {}    // 同时继承了两个父类

         此时的C类就具备了A、B两个类的所有功能。但是一般在进行多层继承的时候千万别超过三层。

限制二:子类会继承父类之中的全部操作,但是对于所有的私有操作属于隐式继承,而所有的非私有操作属于显式继承。

范例:错误的访问父类中的私有操作

class Person {

         private String name ;     // 私有操作

}

class Student extends Person {

         public void set() {

                   this.name = "Hello" ;    // 子类访问父类私有操作

         }

}

         虽然不能访问,但是属性确实被继承下来了。

范例:观察属性的间接操作

class Person {

         private String name ;     // 私有操作

         public void setName(String name) {

                   this.name = name ;

         }

         public String getName() {

                   return this.name ;

         }

}

class Student extends Person {

}

public class TestJava {

         public static void main(String args[]) {

                   Student stu = new Student() ;

                   stu.setName("HELLO") ;      // 直接调用非私有

                   System.out.println(stu.getName()) ;

         }

}

         此时在操作Student类对象的时候使用setName()和getName()可以进行name属性的操作,那么就证明私有属性也被继承下来了,但是并不能直接访问,所以称为隐式继承,而所有的公共操作(public)可以被直接使用。

限制三:子类对象在进行对象实例化之前,会默认调用父类构造(默认执行父类的无参构造方法),而后再调用子类构造,为子类对象实例化。

范例:观察子类对象实例化

class Person {

         public Person() {         // 父类无参构造

                   System.out.println("*** Person类构造方法。") ;

         }

}

class Student extends Person {

         public Student() {

                   System.out.println("### Student类构造方法。") ;

         }

}

public class TestJava {

         public static void main(String args[]) {

                   Student stu =new Student() ;

         }

}

         而此时,就相当于在子类构造方法之中隐含了一个“super()”的语句。表示由子类调用父类的构造方法,如果没传递参数表示调用无参,这样的语句只能够放在子类构造方法的首行。

class Student extends Person {

         public Student() {

                  super() ;    // 不管写与不写,都存在

                   System.out.println("### Student类构造方法。") ;

         }

}

class Person {

         public Person(String name) {          // 父类无参构造

                   System.out.println("*** Person类构造方法。") ;

         }

}

class Student extends Person {

         public Student() {

                   super("加上参数") ;

                   System.out.println("### Student类构造方法。") ;

         }

}

public class TestJava {

         public static void main(String args[]) {

                   Student stu = new Student() ;

         }

}

         但是此时有一个疑问就出现了,发现this()也要放在构造方法首行,而super()同样要放在构造方法首行,如果说在构造方法首行里,编写了“this()”,那么就表示不能出现“super()”。那么是不是就表示可以不去调用父类构造,而实现直接调用子类构造呢?

范例:错误的操作

class Person {

         public Person() { // 父类无参构造

                   System.out.println("*** Person类构造方法。") ;

         }

}

class Student extends Person {

         private String school ;

         public Student() {

                  this("清华大学") ;

                   System.out.println("### Student类构造方法。") ;

         }

         public Student(String school) {

                  this() ;       // 不让super()出现

                   this.school = school ;

         }

}

public class TestJava {

         public static void main(String args[]) {

                   Student stu = new Student() ;

         }

}

         在使用this()调用本类构造方法的时候,一定要留一个出口,而这个出口就是给super准备的,即:子类构造执行之前一定要先调用父类构造。




2、覆写

既然现在存在了类继承的关系,那么就有可能出现子类定义了与父类相同的方法或者是属性的情况,那么这类情况统一都被称为覆写。

2.1、方法的覆写(核心)

         如果子类之中定义了和父类相同的方法名称的时候,就称为覆写。

范例:实现覆写

class Person {

         public void print() {

                   System.out.println("Person类,public void print(){}") ;

         }

}

class Student extends Person {

         public void print() {   // 方法名称相同、参数类型及个数相同、返回值相同

                   System.out.println("Student类,public void print(){}") ;

         }

}

public class TestJava {

         public static void main(String args[]) {

                   Student stu =new Student() ;

                   stu.print() ;

         }

}

         现在在Student类之中定义了和父类的print()方法名称相同,参数相同,返回值类型相同的方法后,那么就表示覆写了父类中的print()方法,而当实例化的是子类对象,那么所调用的方法一定是被覆写过的方法。

         如果说现在要想在子类之中调用已经被子类所覆写的方法,那么在方法前一定要使用“super.方法()”的形式。

class Student extends Person {

         public void print() {      // 方法名称相同、参数类型及个数相同、返回值相同

                   super.print() ;

                   System.out.println("Student类,public void print(){}") ;

         }

}

         有了继承的概念之后对于this和super的使用就非常重要了,一定要清楚:

                   · this:表示先从本类查找,如果本类没有则查找父类;

                   · super:表示不查找本类,而直接找到父类。

         如果是调用已经被子类所覆写过的父类方法时一定要加上“super”关键字。

         但是在进行方法覆写的时候还有一个非常重要的概念就是关于权限的问题,被子类覆写的方法不能拥有比父类更为严格的访问控制权限。

         关于访问控制权限我们已经接触过三类了:private < default(不写) < public。如果一个方法本身是public权限,那么在子类覆写的时候变为了private,则称为权限严格了。那么此时子类覆写时,权限只能够写public。如果父类之中的方法使用的是default权限,那么子类就可以使用default或者是public来表示。

范例:错误的覆写

class Person {

         public void print() {

                   System.out.println("Person类,public void print(){}") ;

         }

}

class Student extends Person {

         void print() {      // 方法名称相同、参数类型及个数相同、返回值相同

                   super.print() ;

                   System.out.println("Student类,public void print(){}") ;

         }

}

         以上是概念,但是从实际的角度而言,只要是方法99%都使用public声明,所以别去考虑权限大小问题。

面试题:请解释Overloading(重载)和Override(覆写)的区别?在Overloading时能否改变返回值类型?

No.

区别

重载

覆写

1

英文单词

Overloading

Override

2

定义

方法名称相同、参数的类型及个数不同

方法名称相同、参数的类型及个数相同、返回值相同

3

范围

发生在一个类之中

发生在继承关系类中

4

权限

没有权限要求

被覆写的方法不能拥有比父类更为严格的访问控制权限

         在方法重载时返回值类型可以不同,但是从代码的开发来讲,返回值类型一定要保持相同。

提问:如果现在父类定义的方法是private权限,子类覆写的方法是public权限,是否正确?

         从概念上来讲,此时的概念符合覆写要求,因为父类是private方法,而子类是public方法,权限变大了。

范例:观察问题

class Person {

         private void print() {

                   System.out.println("Person类,public void print(){}") ;

         }

         public void fun() {

                   this.print() ;

         }

}

class Student extends Person {

         public void print() {

                   System.out.println("Student类,public void print(){}") ;

         }

}

public class TestJava {

         public static void main(String args[]) {

                   Student stu = new Student() ;

                   stu.fun() ;

         }

}

         此时调用fun()方法的时候没有调用子类的print()(子类的print()就没用),而此时子类之中的print()只能够算是扩充的新功能,即:如果方法使用了private声明,那么是不会存在有覆写这一概念的,而实际之中也基本上不会出现此类操作。

2.2、属性的覆盖(无用)

         当子类定义了和父类相同的属性名称的时候就被称为属性的覆盖。

范例:观察属性覆盖

class Person {

         String msg = "今天你被吭了吗?" ;

}

class Student extends Person {

         int msg = 1000 ;

         public void print() {

                   System.out.println(super.msg) ;

                   System.out.println(this.msg) ;

         }

}

public class TestJava {

         public static void main(String args[]) {

                   Student stu = new Student() ;

                   stu.print() ;

         }

}

         一般而言属性都要求被封装,一旦属性被封装了,那么又何来覆盖呢?




3、final关键字

在Java之中final被称为终结器。指的是完结的含义,在程序之中可以用于定义类、方法、常量。

1、   使用final定义的类不能有子类,表示太监类。

final class Person {       // 不能有子类

}

class Student extends Person { // TestJava.java:3: 错误: 无法从最终Person进行继承

}

         你们自己写的代码之中很少会出现使用final定义类的情况,但是在研究系统类的时候会出现。

2、   使用final定义的方法不能被子类所覆写。

class Person {     // 不能有子类

         public final void print() {}

}

class Student extends Person {

         public void print() {}    // Student中的print()无法覆盖Person中的print()

}

         此类操作自己写代码之中也很少出现。

3、   使用final定义的变量就称为常量,常量必须使用final定义,而且声明时必须设置好内容,不能修改。

class Person {     // 不能有子类

         // 以后操作INFO就相当于操作“hello”

         final String INFO = "hello" ;      // 常量

         public final void print() {

                   INFO = "world" ;         // 错误的代码

         }

}

         如果在开发之中使用了“public static final”定义的常量,表示的是全局常量。

         public static final String INFO = "hello" ;   // 全局常量

         如果定义为常量的变量名称,每个字母都要求大写。




4、对象多态性

多态是面向对象的第三大主要特征,但并不意味着今天会把多态讲解清楚,今天只是一个开头,先认识什么叫多态,以及多态所带来的形式。

         如果说继承是为了解决代码重用的问题,那么多态就是为了解决参数的统一问题。而且多态一定要结合继承关系后才可以使用。以下所讲解的代码只是为了阐述多态的概念,实际之中没有任何的用处。

         在讲解多态性之前,首先来看一下下面的程序。

class Person {

         public void print() {

                   System.out.println("1、Person类,print(){}") ;

         }

         public void fun() {

                   this.print() ;

         }

}

class Student extends Person {

         public void print() {

                   System.out.println("2、Student类,print(){}") ;

         }

}

public class TestJava {

         public static void main(String args[]) {

                   Student stu =new Student() ;

                   stu.fun() ;

         }

}

         此时,Student子类覆写了Person类之中的print()方法,同时实例化的又是student类对象,所以一定调用被覆写过的方法。

         所谓的多态性指的是父子对象之间的转换操作,那么多态性一共有两种转换形式:

                   · 向上转型:父类 父类对象 = 子类实例,自动完成转换;

                   · 向下转型:子类 子类对象 = (子类) 父类实例,强制转换。

范例:向上转型

class Person {

         public void print() {

                   System.out.println("1、Person类,print(){}") ;

         }

}

class Student extends Person {

         public void print() {

                   System.out.println("2、Student类,print(){}") ;

         }

}

public class TestJava {

         public static void main(String args[]) {

                   Person per =new Student() ; // 向上转型,自动完成

                   per.print() ;

         }

}

         现在Student类覆写了print()方法,同时new的是Student子类,那么一定要找到被覆写过的方法。但是需要注意的是,如果此时子类扩充有新的操作方法,那么向上转型之后的对象是无法调用的。

class Person {

         public void print() {

                   System.out.println("1、Person类,print(){}") ;

         }

}

class Student extends Person {

         public void print() {

                   System.out.println("2、Student类,print(){}") ;

         }

         public void funStudent() {

                   System.out.println("3、Student类,funStudent(){}") ;

         }

}

public class TestJava {

         public static void main(String args[]) {

                   Person per = new Student() ; // 向上转型,自动完成

                   per.print() ;

                  per.funStudent() ;      // 错误: 找不到符号

         }

}

         因为Person类可以看见的方法只有一个print()而已,没有funStudent(),那么要想去调用funStudent()方法,则必须执行向下转型,将父类对象变为子类实例。

public class TestJava {

         public static void main(String args[]) {

                   Person per = new Student() ; // 向上转型,自动完成

                   per.print() ;

                  Student stu = (Student) per ;      // 向下转型,强制完成

                   stu.funStudent() ;          // 错误: 找不到符号

         }

}

阶段性总结:

         · 向上转型的主要特征,子类为父类对象实例化,调用的方法一定是子类所覆写过的方法,向上转型是找共性

         · 向下转型的主要特征,子类扩充了某些功能,而这些功能父类之中没有定义,则需要执行向下转型,以调用子类特有的功能,向下转型是找特性。

范例:请说出以下的程序结果

public class TestJava {

         public static void main(String args[]) {

                   Person per =new Person() ;

                   Student stu = (Student) per ;  //ClassCastException

                   stu.print() ;

                   stu.funStudent() ;

         }

}

         此时的程序出现了“ClassCastException”异常,表示的是类转换异常。指的是两个没有关系的类对象发生转换时所产生的问题。在本程序之中,发现实例化的是一个Person类对象。但是此时没有和任何的子类产生关系。在类的定义上,一个类并不能知道有那些子类,但是一个子类可以知道谁是它的父类,所以如果要向发生向下转型之前,必须首先建立好父子类之间的关系(向上转型),而后才可以发生向下转型。向下转型操作的前提:首先要发生向上转型。

         通过分析可以发现,向下转型实际上比向上转型危险,因为有可能会造成错误,所以为了更安全的进行转型操作,在Java之中专门提供有一个instanceof的关键字,利用此关键字就可以判断出某一个对象是否是某一个类的实例,语法:

对象 instanceof 类 è 返回boolean型数据

范例:观察instanceof的使用

public class TestJava {

         public static void main(String args[]) {

                   Person per = new Student() ;

                   System.out.println(per instanceof Person) ;

                   System.out.println(per instanceof Student) ;

                   if (perinstanceof Student) {

                            Student stu = (Student) per ;  // 向下转型

                            stu.funStudent() ;          // 调用子类自己扩充的操作

                   }

         }

}

疑问?这样的互相转型有什么意义呢?

         为了更好的解释此类问题,下面做一个程序,现在假设Person类下有学生、工人、教师三个子类,那么人一定可以说话,但是每个身份的人说的话一定不一样。要求定义一个方法,此方法可以接收Person类的所有引用。

class Person {

         public void say() {

                   System.out.println("1、我是一个纯粹的人。") ;

         }

}

class Student extends Person {

         public void say() {

                   System.out.println("2、我是一个学生。") ;

         }

}

class Worker extends Person {

         public void say() {

                   System.out.println("3、我是一个工人。") ;

         }

}

class Teacher extends Person {

         public void say() {

                   System.out.println("4、我是一个教师。") ;

         }

}

实现方式一:利用方法的重载完成

public class TestJava {

         public static void main(String args[]) {

                   fun(new Student()) ;

                   fun(new Worker()) ;

                   fun(new Teacher()) ;

         }

         public static void fun(Student stu) {

                   stu.say() ;

         }

         public static void fun(Worker wkr) {

                   wkr.say() ;

         }

         public static void fun(Teacher tea) {

                   tea.say() ;

         }

}

         此时,面对于当前要求的确是可以满足了,但是此类代码有一个缺陷,如果说Person类产生了5000个子类,而且还有可能随时增加更多的子类,那么就意味着fun()方法要重载5000次,而且重复编写相同的代码5000次,同时每当扩充子类的时候都必须去修改包含有fun()方法类的定义。这样的做法明显不好,而之所以会出现这样的情况,最重要的原因还在在于参数没有得到有效的统一。

实现方式二:利用对象的向上转型操作完成

         对象在进行向上转型时可以自动完成父类接收子类对象的功能,同时转型之后所调用的方法一定随实例化子类的不同,同一个方法一定执行不同的功能。

public class TestJava {

         public static void main(String args[]) {

                   fun(new Student()) ;

                   fun(new Worker()) ;

                   fun(new Teacher()) ;

         }

         public static void fun(Person per) {

                   per.say() ;

         }

}

         现在结果与之前完全一样,如果随意去扩充子类的话,fun()方法不需要做任何的修改就可以满足所有的功能。

思考题:现在假设有一个数组操作类,这个类操作之中需要接收一个数组(通过构造传递),而后在此类下有两个子类,一个子类在取得数据的时候会将传入的数组进行排序后返回,另外一个子类会将传递进去的数组以反转的形式返回。

class Array {

         private int data [] ;         // 保存数组

         public Array(int data[]) {

                   this.data = data ;  // 构造方法传递数组

         }

         public int [] getData() {

                   return this.data ;

         }

}

class SortArray extends Array {

         public SortArray(int data[]) {

                   super(data) ;

         }

         // 发现父类的getData()方法支持不足,但是又必须使用此方法

         public int [] getData() {

                   java.util.Arrays.sort(super.getData()) ;     // 排序

                   return super.getData() ;

         }

}

class ReverseArray extends Array {

         public ReverseArray(int data[]) {

                   super(data) ;

         }

         public int [] getData() {

                   int center = super.getData().length / 2 ;

                   int head = 0 ;

                   int tail = super.getData().length - 1 ;

                   for (int x = 0 ; x < center ; x ++) {

                            int temp = super.getData()[head] ;

                            super.getData()[head] = super.getData()[tail] ;

                            super.getData()[tail] = temp ;

                            head ++ ;

                            tail -- ;

                   }

                   return super.getData() ;

         }

}

public class TestJava {

         public static void main(String args[]) {

                   Array arr = new ReverseArray(new int [] {5,6,7,1,2,3}) ;

                   print(arr) ;

         }

         public static void print(Array temp) {

                   int result [] = temp.getData() ;

                   for (int x = 0 ; x < result.length ; x ++) {

                            System.out.print(result[x] + "、") ;

                   }

                   System.out.println() ;

         }

}

总结:关于继承性与多态性

         · 这两特特性一定是相辅相成的,不可以缺一;

         · 继承性解决的是一个类功能扩充的问题,而多态性解决的是参数统一的问题,有了多态性之后才应该发觉,父类的设计是相当重要的,而且子类不要轻易的扩充父类里面没有的方法,要按照父类已有的方法规格覆写方法。

总结:关于对象转型问题

         · 向上转型(90%),自动完成,子类对象自动的转换为父类对象,而后调用的是子类所覆写过的方法,利用对象的向上转型就可以很好的解决参数统一的问题;

         · 向下转型(1%),强制完成,不建议出现,当需要调用子类自己扩充方法的时候;

         · 不转型(9%),很多时候的一些类是不会进行转型操作,例如:String。




5、抽象类

5.1、基本概念

         在之前所学习的都属于普通类,普通类的特征是:类的结构很完整,而且可以直接产生实例化对象并使用。但是在普通类之中所定义的方法都是具备方法体的“{}”,但是如果说现在一个类之中的某些方法希望强制子类进行覆写,则可以将其定义为抽象方法,抽象方法使用abstract关键字声明,而且声明时没有方法体,而包含抽象方法的类就被称为抽象类,抽象类也要使用abstract关键字进行定义。

范例:定义抽象类

abstract class A {

         private String msg = "HELLO" ;    // 属性

         public void print() {      // 普通方法

                   System.out.println("HELLO WORLD .") ;

         }

         public abstract void fun() ;     // 抽象方法

}

         但是抽象类定义完成之后并不能像普通类那样直接使用关键字new进行对象实例化操作(但是可以声明抽象类对象)。因为一旦一个类的实例化对象产生了,那么就意味着可以通过此对象调用类中的方法,可是抽象方法没有方法体,所以一定无法进行调用,那么对于抽象类的使用原则如下:
                   · 抽象类一定要被子类所继承;

                   · 抽象类的子类(如果不是抽象类)则必须覆写抽象类之中全部的抽象方法(强制覆写);

                   · 抽象类可以通过子类对象的向上转型实现类对象实例化操作。

范例:使用抽象类

abstract class A {

         private String msg = "HELLO" ;    // 属性

         public void print() {      // 普通方法

                   System.out.println("HELLO WORLD .") ;

         }

         public abstract void fun() ;     // 抽象方法

}

class B extends A {      // B是一个普通类

         public void fun() {      // 覆写

                   System.out.println("输出数据。") ;

         }

}

public class TestDemo {

         public static void main(String args[]) {

                   A a =new B() ;   // 向上转型

                   a.fun() ;      // 调用的是被覆写过的方法

         }

}

         可以发现抽象类就是比普通类多了抽象方法的定义,但是在进行继承关系操作的时候,发现可以比普通类具备更强的制约性,即:抽象类的子类必须有明确的方法覆写要求,而普通类没有。

         抽象类的基本概念非常简单,但是对于抽象类还有几个小的注意事项。

问题一:请问抽象类在定义的时候能否使用final关键字?

         不能。因为抽象类必须有子类,而final定义的类不能有子类,所以这是矛盾的。

问题二:请问抽象类之中能否定义构造方法?

         能。因为抽象类只是比普通类多了抽象方法而已,其它的结构(属性、全局常量、普通方法)全都存在,而既然有属性了,那么一定要有构造方法。

问题三:如果一个抽象类没有抽象方法是否正确?那么此时的抽象类能否直接产生实例化对象?

         抽象类之中可以没有抽象方法,但是此时依然无法直接产生抽象类的实例化对象。

问题四:请问抽象类是否可以使用static定义?

         · 如果现在定义的是一个普通的外部类的话,那么无法使用static来定义抽象类;

         · 如果现在定义的是一个内部类,那么就可以使用static定义,因为使用static定义的内部类就成为了一个外部类。

abstract class A {

         public abstract void fun() ;

         abstract static class B {       // 内部抽象类

                   public abstract void print() ;

         }

}

class X extends A.B {

         public void print() {

                   System.out.println("Hello World .") ;

         }

}

public class TestDemo {

         public static void main(String args[]) {

                   A.B b = new X() ;

                   b.print() ;

         }

}

问题五:请问一个抽象类能否在此抽象类的内部提供实现此抽象类的子类呢?

         一个抽象类的内部可以直接定义一个内部类继承本抽象类。

abstract class A {

         public abstract void fun() ;

         private class X extends A {

                   public void fun() {

                            System.out.println("Hello World .") ;

                   }

         }

}

         那么既然可以,请问,现在该如何取得A类的实例化对象呢?此时A类是无法进行对象实例化的,但是只有static方法可以不受类实例化对象的控制。

abstract class A {

         public abstract void fun() ;

         private static class X extends A {

                   public void fun() {

                            System.out.println("Hello World .") ;

                   }

         }

         public static A getInstance() {

                   return new X() ;  // X是A的子类

         }

}

public class TestDemo {

         public static void main(String args[]) {

                   A a = A.getInstance() ;

                   a.fun() ;

         }

}

         一般而言使用此类代码只有一个目的:不希望用户关注到子类的存在。

5.2、抽象类应用 —— 模板设计模式(理解)

         首先抽象类一定不是一个具体的类,它只是一个半成品,而子类负责实现这些未实现的功能。

例如:现在做这样一个程序的分析,假设说现在有三个按钮,每按一个按钮就表示一种操作,而现在提供有三种操作:吃饭、睡觉、工作,而现在又有三类事物:

                   · 机器 = 吃饭、工作;

                   · 猪 = 吃饭、睡觉;

                   · 人 = 吃饭、睡觉、工作。

         那么现在该如何实现这样的类结构呢?


范例:代码实现

abstract class Action {  // 操作行为

         public static final int SLEEP = 1 ;

         public static final int EAT = 5 ;

         public static final int WORK = 10 ;

         public abstract void eat() ;

         public abstract void sleep() ;

         public abstract void work() ;

         public void command(int flag) {

                   switch(flag) {

                            case SLEEP :

                                     this.sleep() ;

                                     break ;

                            case EAT :

                                     this.eat() ;

                                     break ;

                            case WORK :

                                     this.work() ;

                                     break ;

                            case SLEEP + EAT + WORK :

                                     this.sleep() ;

                                     this.eat() ;

                                     this.work() ;

                                     break ;

                            case SLEEP + EAT :

                                     this.sleep() ;

                                     this.eat() ;

                                     break ;

                            case EAT + WORK :

                                     this.eat() ;

                                     this.work() ;

                                     break ;

                            default :

                                     System.out.println("错误:未知的命令。") ;

                   }

         }

}

         那么如果要想让三个不同的事物可以通过command()进行操作,那么就必须继承Action类,同时根据需要覆写方法。

class Robot extends Action {

         public void eat() {

                   System.out.println("机器人补充能量!") ;

         }

         public void sleep() {}

         public void work() {

                   System.out.println("机器人在工作!") ;

         }

}

class Pig extends Action {

         public void eat() {

                   System.out.println("猪在进食!") ;

         }

         public void sleep() {

                   System.out.println("猪在睡觉,长膘!") ;

         }

         public void work() {}

}

class Person extends Action {

         public void eat() {

                   System.out.println("人在吃饭!") ;

         }

         public void sleep() {

                   System.out.println("人在睡觉!") ;

         }

         public void work() {

                   System.out.println("人在工作和学习!") ;

         }

}

public class TestDemo {

         public static void main(String args[]) {

                   Action actA = new Robot() ;

                   actA.command(Action.WORK + Action.EAT) ;

                   Action actB = new Pig() ;

                   actB.command(Action.EAT + Action.SLEEP) ;

                   Action actC = new Person() ;

                   actC.command(Action.WORK + Action.EAT + Action.SLEEP) ;

         }

}

         如果要想正常的进行操作,就必须按照抽象类提供的要求进行实现,也就是说此时的抽象类提供给我们的是一个操作的模板。现在是根据模板来实现子类定义。在学习到Servlet技术的时候会继续讲解模板设计模式。





你可能感兴趣的:(Java系列(十)__Java面向对象(4))