Java 面向对象的几个概念

本文主要记录 Java 面向对象中几个容易混淆的概念。主要包括重写 (override) 与重载 (overload),多态,抽象类与接口。

继承

在 Java 中,类的继承是单一继承,一个子类只能拥有一个父类。通过 extends 关键字实现类的继承。所有 Java 的类均是由 java.lang.Object 类继承而来的,所以 Object 是所有类的祖先类,而除了 Object 外,所有类必须有一个父类。

通过 instanceof 关键字可以判断一个对象是不是一个类的实例。见下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//A.java
class A{
}

//B.java
class B extends A{
public static void main(String[] args){
A a = new A();
B b = new B();
System.out.println(a instanceof A);
System.out.println(b instanceof B);
System.out.println(a instanceof B);
System.out.println(b instanceof A);
}
}

上述代码的输出为

1
2
3
4
true
true
false
true
也就是说子类的对象也是父类的一个实例。

重写 (override) 与重载 (overload)

重写 (override)

重写 (override) 是子类对父类的允许访问的方法的实现过程进行重新编写,返回值和形参都不能改变。

重写有以下几点规则

  • 参数列表和返回类型必须完全与被重写方法相同;
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
  • 子类只能重写有访问权限的父类方法,在此基础上声明为 final 的方法不能被重写。
  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以
  • 构造方法不能被重写。 如果不能继承一个方法,则不能重写这个方法

详见下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}

class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
}

public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象
a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
}
}
在上面的例子中可以看到,尽管b属于Animal类型,但是它运行的是Dog类的move方法。 这是由于在编译阶段,只是检查参数的引用类型。 然而在运行时,Java 虚拟机 (JVM) 指定对象的类型并且运行该对象的方法。 因此在上面的例子中,之所以能编译成功,是因为 Animal 类中存在 move 方法,然而运行时,运行的是特定对象的方法。而这就是一个典型的多态例子。

再看看下面的例子能更好地理解上面的话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}

class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
public void bark(){
System.out.println("狗可以吠叫");
}
}

public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象
a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
b.bark();
}
}
以上实例编译运行结果如下:
1
2
3
4
5
TestDog.java:30: cannot find symbol
symbol : method bark()
location: class Animal
b.bark();
^
该程序将抛出一个编译错误,因为 b 的引用类型 Animal 没有 bark 方法

当需要在子类中调用父类的被重写方法时,要使用 super 关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}

class Dog extends Animal{
public void move(){
super.move(); // 应用super类的方法
System.out.println("狗可以跑和走");
}
}

public class TestDog{
public static void main(String args[]){
Animal b = new Dog(); // Dog 对象
b.move(); //执行 Dog类的方法
}
}

以上实例编译运行结果如下:

1
2
动物可以移动
狗可以跑和走

重载 (overload)

重载 (overloading) 是在一个类里面,方法名字相同,而参数不同,返回类型可以相同也可以不同。

重载有以下几点规则

  • 被重载的方法必须改变参数列表
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符,没有限制权限只能变大或变小的限制
  • 方法能够在同一个类中或者在一个子类中被重载。

见下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Overloading {

public int test(){
System.out.println("test1");
return 1;
}

public void test(int a){
System.out.println("test2");
}

//以下两个参数类型顺序不同
public String test(int a,String s){
System.out.println("test3");
return "returntest3";
}

public String test(String s,int a){
System.out.println("test4");
return "returntest4";
}

public static void main(String[] args){
Overloading o = new Overloading();
System.out.println(o.test());
o.test(1);
System.out.println(o.test(1,"test3"));
System.out.println(o.test("test4",1));
}
}

重写 (override) 与重载 (overload) 的区别

区别点重写 (override)重载 (overload)
参数列表不能改变必须改变
返回类型不能改变可以改变
范围只能在子类中重写可以在当前类或子类中重载
权限重写的方法的访问权限只能变大重载方法的访问权限无变化限制
异常可以减少或删除,不能抛出新的或者更广的异常无添加减少的限制

多态

从字面上的意思解释,多态是同一个行为具有多个不同表现形态的能力。反映在 Java 面向对象中指的是同一方法(参数列表和返回类型都相同)有具有多种实现方式

因此,结合上面说到的内容,多态存在有以下三个必要条件:

  • 继承
  • 重写
  • 父类引用指向子类对象

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。

上面的重写所提到的例子就是一个典型的多态例子。

抽象类与接口

抽象类

抽象类不能实例化对象,抽象类的用途在于声明了一系列需要被继承并实现的抽象方法,然后被其他类继承并实现。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。

抽象类通过 abstract class 来定义,同时需要注意如果一个类包含抽象方法,那么该类一定要声明为抽象类;但是抽象类可以不包含抽象方法,也可以同时包含抽象方法和非抽象方法

见下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Employee.java
public abstract class Employee
{
private String name;
private String address;
private int number;
public abstract double computePay();
}

// seller.java
public class seller
{
public double computePay
{

}
}

继承抽象类后需要注意下面两点:

  • 如果一个类包含抽象方法,那么该类必须是抽象类。
  • 任何子类必须重写父类所有的抽象方法,否则需要声明自身为抽象类。

接口(interface)

接口(英文:Interface),在 JAVA 中是抽象方法的集合,接口通常以 interface 来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

接口有以下特性:

  • 接口是隐式抽象的,当声明一个接口的时候,不必使用 abstract 关键字。
  • 接口中每一个方法也是隐式抽象的,声明时同样不需要 abstract 关键子。
  • 接口中的方法都是公有的。

如下面就声明了一个接口:

1
2
3
4
interface Animal {
public void eat();
public void travel();
}

当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。 类使用 implements 关键字实现接口,且一个类可以实现多个接口

下面是实现上面的接口的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MammalInt implements Animal{

public void eat(){
System.out.println("Mammal eats");
}

public void travel(){
System.out.println("Mammal travels");
}

public int noOfLegs(){
return 0;
}

public static void main(String args[]){
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}

一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用 extends 关键字,见下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Sports.java
public interface Sports
{
public void setHomeTeam(String name);
public void setVisitingTeam(String name);
}

// Football.java
public interface Football extends Sports
{
public void homeTeamScored(int points);
public void visitingTeamScored(int points);
public void endOfQuarter(int quarter);
}

除此之外,接口还允许多继承,但是 Java 中是不允许类的多继承的。如下面的接口就继承了上面的两个接口

1
2
3
4
public interface Socer extends Sports, Football
{

}

接口与抽象类非常相似,两者的区别入下:

区别接口抽象类
继承 (实现) 的个数一个类可实现多个接口一个类仅能继承一个抽象类
内部是否可以含有实现的方法没有实现的方法可以有实现的方法

参考:http://www.runoob.com/java