面向对象主要有三大特性:继承和多态、封装。
一、抽象类
在了解抽象类之前,先来了解一下抽象方法。抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法的声明格式为:
abstract void fun();
抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。抽象类的声明格式如下:
public abstract class ClassName { abstract void fun(); }
下面要注意一个问题:在《JAVA编程思想》一书中,将抽象类定义为“包含抽象方法的类”,但是后面发现如果一个类不包含抽象方法,只是用abstract修饰的话也是抽象类。也就是说抽象类不一定必须含有抽象方法。个人觉得这个属于钻牛角尖的问题吧,因为如果一个抽象类不包含任何抽象方法,为何还要设计为抽象类?所以暂且记住这个概念吧,不必去深究为什么。
在面向对象领域由于抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能实例化的。同时,抽象类体现了数据抽象的思想,是实现多态的一种机制。它定义了一组抽象的方法,至于这组抽象方法的具体表现形式由派生类来实现。同时抽象类提供了继承的概念,它的出发点就是为了继承,否则它没有存在的任何意义。对于一个父类,如果它的某个方法在父类中实现出来没有任何意义,必须根据子类的实际需求来进行不同的实现,那么就可以将这个方法声明为abstract方法,此时这个类也就成为abstract类了。
使用抽象类时应注意一下几点:
1、包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法
2、如果一个非抽象类继承了抽象类,则非抽象类必须实现抽象父类的所有抽象方法
3、子类中的抽象方法不能与父类的抽象方法同名
4、抽象类不能创建实体,因为抽象类存在抽象方法,而抽象方法没有实体,创建对象后,抽象对象调用抽象方法是没有意义的
5、抽象类中一定有构造函数。主要为了初始化抽象类中的属性。通常由子类实现
6、final和abstract是否可以同时修饰一个方法,因为用final修饰后,修饰类代表不可以继承,修饰方法不可重写,abstract修饰类就是用来被继承的,修饰方法就是用来被重写的
abstract不能与private修饰同一个方法,因为privte成员对外是不可见的,只能在本类中使用,这样子类就无法重写抽象方法
abstract不能与static修饰同一个方法,static修饰的方法可以用类名调用,而对于abstract修饰的方法没有具体的方法实现,所有不能直接调用
抽象类与接口
接口,英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数。从这里,我们可以体会到Java语言设计者的初衷,它是对行为的抽象,而没有具体的实现,接口本身不是类。同时实现该接口的实现类必须要实现该接口的所有方法,通过使用implements关键字,他表示该类在遵循某个或某组特定的接口,同时也表示着“interface只是它的外貌,但是现在需要声明它是如何工作的”。
接口是抽象类的延伸,java为了了保证数据安全是不能多重继承的,也就是说继承只能存在一个父类,但是接口不同,一个类可以同时实现多个接口,不管这些接口之间有没有关系,所以接口弥补了抽象类不能多重继承的缺陷,但是推荐继承和接口共同使用,因为这样既可以保证数据安全性又可以实现多重继承。接口声明形式如下:
public interface InterfaceName { }
在使用接口过程中需要注意如下几个问题:
1、一个Interface的方所有法访问权限自动被声明为public。确切的说只能为public,当然你可以显示的声明为protected、private,但是编译会出错!
2、接口中定义的所有变量默认是public static final的,即静态常量既然是常量,那么定义的时候必须赋值,可以通过接口名直接访问:ImplementClass.name。
3、接口中定义的方法不能有方法体。接口中定义的方法默认添加public abstract
4、有抽象函数的不一定是抽象类,也可以是接口类。
5、由于接口中的方法默认都是抽象的,所以接口不能被实例化。
6、类实现接口通过implements实现,实现接口的非抽象类必须要实现该接口的所有方法,抽象类可以不用实现。
7、如果实现类要访问接口中的成员,不能使用super关键字。因为两者之间没有显示的继承关系,况且接口中的成员成员属性是静态的
8、接口没有构造方法。
9、不能使用new操作符实例化一个接口,但可以声明一个接口变量,该变量必须引用(refer to)一个实现该接口的类的对象。可以使用 instanceof 检查一个对象是否实现了某个特定的接口。
例如:if(anObject instanceof Comparable){}。
10、在实现多接口的时候一定要避免方法名的重复。
抽象类和接口的区别
1、语法层面上的区别
1)抽象类可以提供成员方法的实现细节(即普通方法),而接口中只能存在public abstract 方法;
2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口,Java是单继承,多实现。
2、设计层面上的区别
1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
2)抽象类所体现的是一种继承关系,而继承是一个 "is-a"的关系,而 接口 实现则是 "has-a"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系。比如:将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),对于不同种类的鸟直接继承Bird类即可,而鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
3)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
二、继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率。
继承的特点:
1、子类拥有父类非private的属性和方法
2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展
3、子类可以用自己的方式实现父类的方法(方法重写)
4、构造函数不能被继承
5、继承使用extends关键字实现
重写overriding
- 父类与子类之间的多态性,对父类的函数进行重新定义。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写。在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖;
- 若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法。那么子类的对象如果调用该函数,一定调用的是重写过后的函数。如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类;
-
子类重写父类的函数的时候,返回值类型必须是父类函数的返回值类型或该返回值类型的子类,不能返回比父类更大的数据类型;
- 子类函数的访问修饰权限不能少于父类的;
- 子类无法重写父类的private方法
子类对象查找属性或方法时的原则:就近原则。
如果子类的对象调用方法,默认先使用this进行查找,如果当前对象没有找到属性或方法,找当前对象中维护的super关键字指向的对象,如果还没有找到编译报错,找到直接调用。
重载 overloading
- 方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在,具有不同的参数个数/类型。重载是一个类中多态性的一种表现;
- Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。调用方法时通过传递给它们的不同参数个数和参数类型给它们的不同参数个数和参数类型给它们的不同参数个数和参数类型来决定具体使用哪个方法, 这就是多态性;
- 重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同,无法以返回型别作为重载函数的区分标准;
-
所有的重载函数必须在同一个类中
三、多态
- 多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)
- 实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
- 多态的作用:消除类型之间的耦合关系。
- 现实中,关于多态的例子不胜枚举。比方说按下 F1 键这个动作,如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;如果当前在 Word 下弹出的就是 Word 帮助;在 Windows 下弹出的就是 Windows 帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。
多态存在的三个必要条件
-
- 要有继承或实现,即父类引用变量指向了子类的对象或父类引用接受自己的子类对象;
- 要有重写;
- 父类引用指向子类对象。
多态弊端: 提高扩展性,但是只能使用父类引用指向父类成员。
注意:
在多态的情况下,字符类存在同名的成员(成员变量和成员函数)时,访问的是父类的成员,只有是同名的非静态成员函数时,才访问子类的成员函数;
多态用于形参类型时,可以接受多个类型的数据;
多态用于返回类型时,可以返回多个类型的数据,使用了多态的方法,定义的变量类型要与返回的类型一致。
以下面例子来分析多态
public class A { public String show(D obj) { return ("A and D"); } public String show(A obj) { return ("A and A"); } } public class B extends A{ public String show(B obj){ return ("B and B"); } public String show(A obj){ return ("B and A"); } } public class C extends B{ } public class D extends B{ } public class Test { public static void main(String[] args) { A a1 = new A(); A a2 = new B(); B b = new B(); C c = new C(); D d = new D(); System.out.println("1--" + a1.show(b)); System.out.println("2--" + a1.show(c)); System.out.println("3--" + a1.show(d)); System.out.println("4--" + a2.show(b)); System.out.println("5--" + a2.show(c)); System.out.println("6--" + a2.show(d)); System.out.println("7--" + b.show(b)); System.out.println("8--" + b.show(c)); System.out.println("9--" + b.show(d)); } }
输出结果为:
1--A and A 2--A and A 3--A and D 4--B and A 5--B and A 6--A and D 7--B and B 8--B and B 9--A and D
首先我们先看一句话:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被调用的方法必须是被子类重写的方法。这句话对多态进行了一个概括。其实在继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
对于前半句的意思就是:当父类变量引用子类对象时,在调用成员函数时,应该调用向子类的成员函数,但前提是此函数时被子类重写的函数。
A B C D的继承关系如下:
分析:
对于1和2,B和C属于A的子类,调用a1.show(b),a1.show(b),可以找到A.show(A boj),因为多态情况下,父类做形参时,可以接受其子类的实参。
对于3,直接就可以找到A.show(D odj)。
对于4,本来由于a2引用的是其子类B的一个对象,因此调用的成员函数应为B.show(B obj),但是由于B.show(B obj)不是重写的函数,因此不会调用B.show(B obj)。故将按照优先级,先看this.show(O),而类A里面没有找到show(B obj)方法,于是到A的super(超类)找,而A没有超类,因此转到第三优先级this.show((super)O),this仍然是a2,这里O为B,(super)O即(super)B即A,因此它到类A里面找show(A obj)的方法,类A有这个方法,但是由于a2引用的是类B的一个对象,且B覆盖了A的show(A obj)方法,因此最终锁定到类B的show(A obj),输出为"B and A”。
对于5,同样将按照优先级,先看this.show(O),而类A里面没有找到show(C obj)方法,于是到A的super(超类)找,而A没有超类,因此转到第三优先级this.show((super)O),this仍然是a2,这里O为C,由于A是C的超类,因此它到类A里面找show(A obj)的方法,类A有这个方法,但是由于a2引用的是类B的一个对象,且B覆盖了A的show(A obj)方法,因此最终锁定到类B的show(A obj),输出为"B and A”。
对于6,同样将按照优先级,先看this.show(O),而类A里面刚好找到了show(D obj)方法,输出为"D and A”.
对于7,可以直接调用this.show(O)。
对于8,同样将按照优先级,先看this.show(O),而类B里面没有找到show(C obj)方法,于是到B的super(超类)找,而类A里面没有找到show(C obj)方法,因此转到第三优先级this.show((super)O),this仍然是b,这里O为C,由于B是C的超类,因此它到类B里面找show(B obj)的方法,因此输出为"B and B”。
对于9,同样将按照优先级,先看this.show(O),而类B里面没有找到show(D obj)方法,于是到B的super(超类)找,而类A里面找到了show(D obj)方法,因此输出为"A and D”。
四、封装
封装是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其他对象只能通过包裹在数据外面的已经授权的操作来与这个封装的对象进行交流和交互。也就是说用户是无需知道对象内部的细节(当然也无从知道),但可以通过该对象对外的提供的接口来访问该对象。
使用封装有四大好处:
1、良好的封装能够减少耦合。
2、类内部的结构可以自由修改。
3、可以对成员进行更精确的控制。
4、隐藏信息,实现细节。