面向对象三大特性
面向对象程序设计(简称 OOP)是当今主流的程序设计范型,而Java 是完全面向对象的, 所以我们必须熟悉 OOP 才能够编写 Java 程序。
面向对象有三大特性,分别为:封装、继承、多态。
1、封装(也称数据隐藏):
封装也称为信息隐藏,是利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其它部分只有通过包裹在数据外面的被授权的操作来与这个抽象数据类型交流与交互。也就是说,用户无需知道对象内部方法的实现细节,但可以根据对象提供的外部接口(对象名和参数)访问该对象。
(1) 特点:
1、降低代码耦合性与提高代码的可维护性
封装可以对外界隐藏自己的内部域及方法,只对外界开放一些接口来与外部进行交互。
比如这里有一个能实现一个数组排序的一个类,只留一个接口来输入要排序的数组。我们在使用它的时候,与他的交互只有输入数组这个操作,并没有涉及到他的内部实现。
原来的那个代码是用冒泡排序实现的,但是现在我想用快速排序来实现,于是我就改写了那段代码。修改之后的代码使用方法没有改变,还是输入一个数组。
即我们可以随意改变类的内部,只要保证他的功能不变,那这个类的改变就对它的使用者没有任何影响,这就降低了它与其他代码的耦合性,同时也提高了代码的可维护性。
2、防止类的域被外部代码随意修改和访问,以保证数据完备性。
3、提高了安全性
隐藏实现的细节,使用者只需要知道如何使用,不需要知道也无从知道细节。
(2)如何实现
1、限制域及方法的访问权限(private关键字)
2、对外提供公开的set/get方法
3、对域的合法值进行判断(可以通过在set/get方法中加入判断域是否合法的语句)
2、继承
基于已存在的类构造一个新类。
(1)特点
继承已存在的类就是复用(继承)这些类的方法和域。在此基础上,还可以添加一些新的方法和域, 以满足新的需求。
(2)如何实现
1、由父类派生子类
子类可以继承父类的所以域与方法,而自己又可以添加新的域与方法。(如果子类不添加任何域与方法也是允许的)
public class Father {//父类
public int i = 2,j = 3;
public void hello() {
System.out.println("Hello");
}
}
public class Son extends Father {//子类
int sum = 0;
int getSum(int m,int n) {
sum = m +n;
return sum;
}
}
public class Son extends Father {
//如果子类不添加任何域与方法也是允许的
}
2、父类中的private类型的域不能被子类的方法访问。
虽然子类继承了父类的所有域,但是无法通过子类的方法访问父类的私有域。如果非要访问父类的私有域的话,就必须借助父类中公共的接口。
public class Father {//这是父类
private int num = 1;
public int getNum() {
return num;
}
}
public class Son extends Father {
int i = num;//这样写是不行的,因为父类的私有域只能由在父类中被访问
public class Son extends Father {
int i = getNum();//这里借助了父类中公共的接口,所以这个是可行的
}
3、覆盖方法
如果子类有与父类中名字相同的方法,那么子类中的方法将会覆盖(重写)父类中的方法。
public class Father {
public int i = 3,j = 4;
int getSum() {
return i +j;
}
}
public class Son extends Father {
int sum,m = 1,n = 2;
int getSum() {//getSum方法被重写了,也就是说子类的对象在调用getSum方法时,是调用重写后的方法
sum += n + m;
return sum;
}
}
如果想调用父类中的getSum方法,可以用super关键字
public class Son extends Father {
int sum,m = 1,n = 2;
int getSum() {
sum = super.getSum();//这个getSum是父类中的那个
sum += n + m;
return sum;
}
}
4、子类的构造器
因为子类无法访问父类的私有域,所以无法通过子类的构造器来对父类的私有域进行初始化。于是我们需要调用父类的构造器来对父类的私有域进行初始化。
注意:如果子类需要调用父类构造器,那么调用父类构造器的语句必须放在子类构造器的第一行!!!
public class Father {
private int length,height,weight;
private double square;
public Father(int length,int height,int weight) {
this.height = height;
this.length = length;
this.weight = weight;
}
}
public class Son extends Father {
private int i,j;
public Son(int length,int height,int weight) {
super(length,height,weight);//用super关键字来调用父类构造器
i = 0;//初始化子类的域
j = 1;
}
}
(super关键字的2个用途::一是调用父类的方法,二是调用父类的构造器。)
5、继承层次
由一个公共超类派生出来的所有类的集合被称为继承层次。在继承层次中, 从某个特定的类到其祖先的路径被称为该类的继承链。
public class Father {
private int length,height,weight;
private double square;
public Father(int length,int height,int weight) {
this.height = height;
this.length = length;
this.weight = weight;
}
}
public class Son extends Father {
int sum,num;
public Son(int length,int height,int weight,int sum,int num) {
super(length,height,weight);//调用Fathe类构造器
this.sum = sum;
this.num= num;
}
}
public class Daughter extends Father {
private String name;
public Daughter(int length,int height,int weight,String name) {
super(length,height,weight);
this.name = name;
}
}
public class GrandSon extends Son {
private int score;
public GrandSon(int length,int height,int weight,int sum,int num,int score) {
super(length,height,weight,sum,num);//调用Son类的构造器
this.score = score;
}
}
继承层次:
通常, 一个祖先类可以拥有多个子孙继承链。 例如, 可以由Father 类还可以派生出子类 Daughter,它与Son 类没有任何关系。必要的话,可以将这个过程一直延续下去。
3、多态
多态分为继承的多态与方法的多态。方法的多态即相同的方法名,传给它不同数量或者不同类型的参数,会产生不同的行为。继承的多态即不同的对象调用同一个方法会产生不同的行为。
如何实现
1、方法的多态
方法的多态其实是方法重载:如果多个方法有相同的名字、 不同的参数,便产生了重载。编译器必须挑选出具体执行哪个方法,它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法,这个过程叫做重载解析。
这个是构造方法的重载:
public class Father {
private int length = 1,height = 1,weight = 1;
private double square = 1,width = 1;
public Father(int length,int height,int weight) {
this.height = height;
this.length = length;
this.weight = weight;
}
public Father(double width) {
this.width = width;
}
}
public class Main {
public static void main(String args[]) {
Father testOne = new Father(10.2);//参数列表不同,调用的构造方法的行为也不同
Father testTwo = new Father(10,20,30);
}
}
当然,不仅仅只有构造方法可以重载:
public class Circle {
double square,radius,height,volume;
public void setData(double radius) {
this.radius = radius;
square = radius*radius*3.14;
}
public void setData(double radius,double height) {
this.radius = radius;
this.height = height;
square = radius * radius * 3.14;
volume = square * height;
}
}
public class Main {
public static void main(String args[]) {
Circle test[]= new Circle[2];
test[0].setData(5);//参数列表不同,调用方法的行为也不同
test[1].setData(5.3,2.4);
}
}
这里我们介绍一个概念:静态绑定
静态绑定也叫编译时绑定。如果我们有多个相同名字的方法,编译器通过参数列表来确定对象所调用的方法是哪个,那么我们就称这个过程叫静态绑定。静态绑定的主要体现就是方法的重载。同时,对于那些private 方法、 static 方法、 final 方法或者构造器, 编译器将可以准确地知道应该调用哪个方法。
与静态绑定对应的还有动态绑定,它的概念我们将在下面介绍。
2、继承的多态
实现多态有三个必要条件:继承、重写、向上转型。
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够调用父类的方法和子类的方法。
我们先上一个例子,用例子来说明这个动态绑定是如何操作的。
public class Vehicle {
public void soundA() {
System.out.println("We are not sure·····");
soundB();
}
public void soundB() {
System.out.println("We are not sure");
}
}
public class Motorbike extends Vehicle {//(继承)
//子类重写父类方法
public void soundA(String a) {
System.out.println("The sound of a "+a);
}
public void soundB() {
System.out.println("boom!!!");
}
}
public class Tricycle extends Vehicle{//(继承)
//子类重写父类方法
public void soundA(String a) {
System.out.println("The sound of a "+a);
}
public void soundB() {
System.out.println("ta ta ta·····");
}
}
public class Plane extends Vehicle{//(继承)
//子类重写父类方法
public void soundA(String a) {
System.out.println("The sound of a "+a);
}
public void soundB() {
System.out.println("It's really noisy······");
}
}
这个是主类:
public class TestEvent {
public static void main(String args[]) {
//将子类的引用赋给父类的对象(向上转型)
Vehicle p = new Motorbike();
Vehicle s = new Plane();
Vehicle m = new Tricycle();
p.soundA();
s.soundA();
m.soundA();
}
}
运行结果如下
We are not sure·····
boom!!!
We are not sure·····
It's really noisy······
We are not sure·····
ta ta ta·····
从运行结果中我们可以发现,调用soundA方法时,先运行了父类中的soundA方法,然后又运行了子类中的soundB方法。
分析:在这个程序中子类Plane重载了父类的soundA方法,重写了soundB方法。
由于重载后的soundA就变成了子类中特有的方法。向上转型后,通过父类引用变量无法调用子类特有方法。所以Vehicle类型的对象s无法调用子类Plane中的soundA方法。通过父类引用变量调用的方法只能是子类覆盖(重写)或继承父类的方法,而不是父类的方法。所以这里调用了子类plane从父类那里继承来的soundA。
而子类重写了soundB方法,所以s对象在调用soundB方法时会调用子类Plane重写后的那个。
动态绑定的概念:
动态绑定也叫运行时绑定。方法可以在沿着继承链的多个类中实现,子类可以重写父类的方法。不同的对象调用同一个方法时,由JVM决定运行时调用哪个方法。
上面的方法的调用就涉及了动态绑定。