从面向对象的四大特性说起

说到面向对象(OOP)的特性很多人首先就想到了,封装、继承和多态,其实在维基百科的解释里“抽象”也应该被包含在内,自然而然本文的标题就变成了“从面向对象的四大特性说起”了。
为了描述得足够系统,对于基本概念的描述大部分来源于维基百科。

什么是封装?
封装是一种可以使类中的字段私有并能通过公有方法来访问私有字段的技术。如果一个字段被声明为私有,它就不能在类的外部被访问,从而隐藏了类内部的字段。基于这个原因,封装有时也被称为数据隐藏。

封装可以被认为是一种能够保护代码和数据被定义在类外的其它代码任意访问的屏障。访问数据和代码由一个接口严格控制。 封装的主要好处是修改我们实现的代码而又不会破坏其他人使用我们的代码。封装的这个特性使我们的代码具有可维护性、灵活性以及扩展性。

封装类中的属性或方法
封装属性:

private 属性类型 属性名

封装方法:

private 方法返回类型 方法名称(参数)

示例:

/* File name : EncapTest.java */
public class EncapTest{
    private String name;
    private String idNum;
    private int age;
    public int getAge(){
        return age;
    }
    public String getName(){
        return name;
    }
    public String getIdNum(){
        return idNum;
    }
    public void setAge(int newAge){
        age = newAge;
    }
    public void setName(String newName){
            name = newName;
    }
    public void setIdNum(String newId){
        idNum = newId;
    }
}

公有方法是从类外访问到类内字段的入口。通常情况下,这些方法被定义为 getters 和 setters 。因此想要访问类内变量的任何其他类要使用 getters 和 setters 方法。

EncapTest 类的变量可以像如下的方式访问:

/* File name : RunEncap.java */
public class RunEncap{
    public static void main(String args[]){
        EncapTest encap = new EncapTest();
        encap.setName("James");
        encap.setAge(20);
        encap.setIdNum("12343ms");
        System.out.println("Name : " + encap.getName());
        System.out.println("Age : "+ encap.getAge());
    }
}

结果:

Name : James
Age : 20

到底什么时候需要封装,什么时候不用封装?关于封装与否并没有一个明确的规定,不过从程序设计角度来说,一般说来设计较好的程序的类中的属性都是需要封装的。此时,要设置或取得属性值,则只能用 setXxx()、getXxx()方法,这是一个明确且标准的规定。

封装的优点
• 类中的字段可以被设置为只读或只写。
• 类可以完全控制它字段里面所存储的东西。
• 类的使用者不用知道类是如何存储数据的。类可以改变字段的数据类型而类的使用者不需要改变任何之前的
总结起来就是,封装让程序的调用变得可控并且安全,可以极大程度的阻止非法或者错误的调用。

什么是继承?
作为面向对象的编程语言,Java也提供了类的继承机制。利用继承机制,新建的类可以建立在原有类的基础之上,使用或者重写原有类的成员方法,访问原有类的成员变量。我们称新类为原有类子类,而原有类为新类的父类(superclass)。如果类A是类B的父类,而类B是类C的父类,我们也称C是A的子类,类C是从类A继承而来的。在Java中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,一个父类可以有多个子类。所有Java的类均是由java.lang.Object类继承而来的,所以Object是所有类的祖先类,而除了Object外,所有类必须有一个父类。通过extends关键字可以申明一个类是继承另外一个类而来的,一般形式如下:

类的继承格式

class 父类 {
}
class 子类 extends 父类 {
}

重写
如果一个类从它的父类继承了一个方法,如果这个方法没有被标记为 final ,就可以对这个方法进行重写。

重写的好处是:能够定义特定于子类类型的行为,这意味着子类能够基于要求来实现父类的方法。

在面向对象编程中, overriding 意味着去重写父类已经存在的方法。

示例:

class Animal{
public void move(){
System.out.println("Animals can move");
}
}
class Dog extends Animal{
public void move(){
System.out.println("Dogs can walk and run");
}
}
public class Test{
public static void main(String args[]){
Animal a = new Animal(); 
Animal b = new Dog(); 
a.move();
b.move();
}
}

结果:

Animals can move
Dogs can walk and run

在上面的例子中,你可以看到尽管 b 是 Animal 类型,但它运行了 dog 类的方法。

原因是:在编译时会检查引用类型。然而,在运行时,JVM 会判定对象类型到底属于哪一个对象。因此,在上面的例子中,虽然 Animal 有 move 方法,程序会正常编译。在运行时,会运行特定对象的方法。

示例:

class Animal{
public void move(){
System.out.println("Animals can move");
}
}
class Dog extends Animal{
public void move(){
System.out.println("Dogs can walk and run");
}
public void bark(){
System.out.println("Dogs can bark");
}
}
public class Test{
public static void main(String args[]){
Animal a = new Animal(); 
Animal b = new Dog(); 
a.move();
b.move();
b.bark();
}
}

这将产生如下结果:

Test.java:30: cannot find symbol symbol : method bark() location: class Animal b.bark();^

这个程序在编译时将抛出一个错误,因为 b 的引用类型 Animal 没有一个名字叫 bark 的方法。

方法重写规则
重写方法的参数列表应该与原方法完全相同。
返回值类型应该和原方法的返回值类型一样或者是它在父类定义时的子类型。
重写函数访问级别限制不能比原函数高。举个例子:如果父类方法声明为公有的,那么子类中的重写方法不能是私有的(private)或是保护的(protected)。
只有被子类继承时,方法才能被重写。
方法定义为 final,将导致不能被重写。
一个方法被定义为 static,将使其不能被重写,但是可以重新声明。
一个方法不能被继承,那么也不能被重写。
和父类在一个包中的子类能够重写任何没有被声明为 private 和 final 的父类方法。
和父类不在同一个包中的子类只能重写 non-final 方法或被声明为 public 或 protected 的方法
一个重写方法能够抛出任何运行时异常,不管被重写方法是否抛出异常。然而重写方法不应该抛出比被重写方法声明的更新更广泛的已检查异常。重写方法能够抛出比被重写方法更局限或更少的异常。
构造函数不能重写。
方法覆盖 (Overriding) 和方法重载 (Overloading)
Java 中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况(同名不同参)。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。

super 关键字
super是用在子类中,目的是访问直接父类中被屏蔽的变量或方法。

子类构造方法中要调用父类的构造方法:

super(参数列表)
子类引用父类成员变量:

super.成员变量名
子类成员方法覆盖了父类成员方法时,也就是子类和父类有完全相同的方法定义(但方法体可以不同):

super.方法名(参数列表)
示例:

class Animal{
public void move(){
System.out.println("Animals can move");
}
}
class Dog extends Animal{
public void move(){
super.move();
System.out.println("Dogs can walk and run");
}
}
public class Test{
public static void main(String args[]){
Animal b = new Dog(); 
b.move(); 
}
}

结果:

Animals can move
Dogs can walk and run

this关键字
在类的构造方法中,通过this调用另一个构造方法:

this(参数列表)

方法参数或者方法中的局部变量和成员变量同名的情况下,成员变量被屏蔽,此时要访问成员变量则需要用

this.成员变量名
的方式来引用成员变量。当然,在没有同名的情况下,可以直接用成员变量的名字,可不用this。

在方法中,需要引用当前对象时候。

this
其实这些用法总结都是从对“this是指向对象本身的一个指针”这句话的更深入的理解而来。

什么是多态?
多态是指对象能够有多种形态。在 OOP 中最常用的多态性发生在当父类引用指向子类对象时。

任何能够通过一个以上的 IS-A 测试的 Java 对象被认为是多态的。在 Java 中所有对象都是多态的,因为任何一个对象都会有一个他们自己类型的和 Object 类的 IS-A 关系。

重要的是知道,通过引用变量是唯一可以用来访问一个对象的方法。引用变量可以只有一个类型。引用变量一旦被声明是不能被改变的。

引用变量能够重新分配到其他提供的没有被声明为 final 的对象。引用变量的类型将决定它可以调用的对象的方法。

一个引用变量能够引用任何一个对象的声明类型或任何声明类型的子类型。一个引用变量可以声明为一个类或接口类型。

示例

让我们看下面的例子:

public interface Vegetarian{}
public class Animal{}
public class Deer extends Animal implements Vegetarian{}

现在 Deer 类是多态的,因为他有多个继承机制。针对上面的例子有以下说法:

• Deer 就是 Animal

• Deer 就是 Vegetarian

• Deer 就是 Deer

• Deer 就是 Object

当我们提供引用变量来引用 Deer 对象,下面的声明是合法的:

Deer d = new Deer();
Animal a = d;
Vegetarian v = d;
Object o = d;

所有的引用变量 d, a, v, o 在堆内存中引用同一个对象 Deer。

什么是抽象?
如果一个类没有包含足够的信息来描述一个具体的对象就是抽象类。

类名前加修饰符abstract;
可包含常规类能包含的任何成员,包括非抽象方法;
也可包含抽象方法:用abstract修饰,只有方法原型,没有方法的实现;
没有具体实例对象的类,不能使用new方法进行实例化只能用作超类;
只有当子类实现了抽象超类中的所有抽象方法,子类才不是抽象类,才能产生实例;
如果是子类有抽象方法未实现,则子类只能是抽象类。
//抽象类

/* File name : Employee.java */
public abstract class Employee{
private String name;
private String address;
private int number;
public Employee(String name, String address, int number){
System.out.println("Constructing an Employee");
this.name = name;
this.address = address;
this.number = number;
}
public double computePay(){
System.out.println("Inside Employee computePay");
return 0.0;
}
public void mailCheck(){
System.out.println("Mailing a check to " + this.name
+ " " + this.address);
}
public String toString(){
return name + " " + address + " " + number;
}
public String getName(){
return name;
}
public String getAddress(){
return address;
}
public void setAddress(String newAddress){
address = newAddress;
}
public int getNumber(){
return number;
}
}

Employee 类现在是抽象的,但它仍然有三个变量,七个方法,一个构造方法。

抽象方法
如果你想一个提供特定方法的类,但是你想要在他的子类中实际实现这个方法,你可以在父类中声明这个方法为抽象的。

abstract 关键字也被用来定义抽象方法。 抽象方法只包含一个方法名而没有方法体。 方法名之后直接跟一个分号,而不是花括号。

public abstract class Employee
{
private String name;
private String address;
private int number;
public abstract double computePay();
//Remainder of class definition
}

声明一个抽象方法有两个结果:

如果一个类中含有一个抽象方法,类必须也是抽象的。
任何一个子类必须覆盖这个抽象方法,或者继续将它声明为抽象方法。
子类继承一个抽象方法,必须要去覆盖他。如果不这样做的话,它们必须将其继续声明为抽象,或在它们的子类中去覆盖它们。最终,后代类不得不去实现抽象方法;否则你会一直有一个不能被实例化的抽象类。

如果 Salary 继承 Employee 类,则他必须如下要去实现 computerPay() 方法:

/* File name : Salary.java */
public class Salary extends Employee
{
private double salary; // Annual salary
public double computePay()
{
System.out.println("Computing salary pay for " + getName());
return salary/52;
}
//Remainder of class definition
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值