6Java面向对象的三个特征与含义

三大特征是:继承、封装和多态。

1)继承的概念

继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

生活中的继承:

兔子和羊属于食草动物类,狮子和豹属于食肉动物类。

食草动物和食肉动物又是属于动物类。

所以继承需要符合的关系是:is-a,父类更通用,子类更具体。

虽然食草动物和食肉动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特性。

 

类的继承格式

Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:

类的继承格式

class 父类 {

}

class 子类 extends 父类 {

}

为什么需要继承

  定义一个父类,然后子类继承这个类之后,就具有父类当中的属性和方法,子类就不会存在重复的代码,维护性也提高,代码也更加简洁,提高代码的复用性(复用性主要是可以多次使用,不用再多次写同样的代码)

继承类型

  需要注意的是 Java 不支持多继承,但支持多重继承。

继承的特性

子类拥有父类非 private 的属性、方法。

子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。

子类可以用自己的方式实现父类的方法。

Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 A 类继承 B 类,B 类继承 C 类,所以按照关系就是 C 类是 B 类的父类,B 类是 A 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。

提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。

继承关键字

  继承可以使用 extends implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。

extends关键字

Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。

使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。

super this 关键字

super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。

this关键字:指向自己的引用。

实例

 1 class Animal {
 2 
 3   void eat() {
 4 
 5     System.out.println("animal : eat");
 6 
 7   }
 8 
 9 }
10 
11  
12 
13 class Dog extends Animal {
14 
15   void eat() {
16 
17     System.out.println("dog : eat");
18 
19   }
20 
21   void eatTest() {
22 
23     this.eat();   // this 调用自己的方法
24 
25     super.eat();  // super 调用父类方法
26 
27   }
28 
29 }
30 
31  
32 
33 public class Test {
34 
35   public static void main(String[] args) {
36 
37     Animal a = new Animal();
38 
39     a.eat();
40 
41     Dog d = new Dog();
42 
43     d.eatTest();
44 
45   }
46 
47 }

输出结果为:

animal : eat

dog : eat

animal : eat

 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

final关键字

  final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写:

声明类:

  final class 类名 {//类体}

声明方法:

修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}

:实例变量也可以被定义为 final,被定义为 final 的变量不能被修改。被声明为 final 类的方法自动地声明为 final,但是实例变量并不是 final

 

构造器

子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。

如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。

实例

 1 class SuperClass {
 2 
 3   private int n;
 4 
 5   SuperClass(){
 6 
 7     System.out.println("SuperClass()");
 8 
 9   }
10 
11   SuperClass(int n) {
12 
13     System.out.println("SuperClass(int n)");
14 
15     this.n = n;
16 
17   }
18 
19 }
20 
21 // SubClass 类继承
22 
23 class SubClass extends SuperClass{
24 
25   private int n;
26 
27   SubClass(){ // 自动调用父类的无参数构造器
28 
29     System.out.println("SubClass");
30 
31   }  
32 
33   public SubClass(int n){
34 
35     super(300);  // 调用父类中带有参数的构造器
36 
37     System.out.println("SubClass(int n):"+n);
38 
39     this.n = n;
40 
41   }
42 
43 }
44 
45 // SubClass2 类继承
46 
47 class SubClass2 extends SuperClass{
48 
49   private int n;
50 
51   SubClass2(){
52 
53     super(300);  // 调用父类中带有参数的构造器
54 
55     System.out.println("SubClass2");
56 
57   }  
58 
59   public SubClass2(int n){ // 自动调用父类的无参数构造器
60 
61     System.out.println("SubClass2(int n):"+n);
62 
63     this.n = n;
64 
65   }
66 
67 }
68 
69 public class TestSuperSub{
70 
71   public static void main (String args[]){
72 
73     System.out.println("------SubClass 类继承------");
74 
75     SubClass sc1 = new SubClass();
76 
77     SubClass sc2 = new SubClass(100);
78 
79     System.out.println("------SubClass2 类继承------");
80 
81     SubClass2 sc3 = new SubClass2();
82 
83     SubClass2 sc4 = new SubClass2(200);
84 
85   }
86 
87 }

输出结果为:

------SubClass 类继承------

SuperClass()

SubClass

SuperClass(int n)

SubClass(int n):100

------SubClass2 类继承------

SuperClass(int n)

SubClass2

SuperClass()

SubClass2(int n):200

 

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

2Java 封装

在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。

要访问该类的代码和数据,必须通过严格的接口控制。

封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。

适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

封装的优点

1. 良好的封装能够减少耦合。

2. 类内部的结构可以自由修改。

3. 可以对成员变量进行更精确的控制。

4. 隐藏信息,实现细节。

实现Java封装的步骤

1. 修改属性的可见性来限制对属性的访问(一般限制为private),例如:

1 public class Person {
2 
3     private String name;
4 
5     private int age;
6 
7 }

这段代码中,将 name age 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。

 

2. 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问,例如:

 1 public class Person{
 2 
 3     private String name;
 4 
 5     private int age;
 6 
 7     public int getAge(){
 8 
 9       return age;
10 
11     }
12 
13     public String getName(){
14 
15       return name;
16 
17     }
18 
19     public void setAge(int age){
20 
21       this.age = age;
22 
23     }
24 
25     public void setName(String name){
26 
27       this.name = name;
28 
29     }
30 
31 }

采用 this 关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突。

以上实例中public方法是外部类访问该类成员变量的入口。

通常情况下,这些方法被称为gettersetter方法。

因此,任何要访问类中私有成员变量的类都要通过这些gettersetter方法。

 

(3)多态

Java 多态

多态是同一个行为具有多个不同表现形式或形态的能力。

多态就是同一个接口,使用不同的实例而执行不同操作多态性是对象多种表现形式的体现。

 

现实中,比如我们按下 F1 键这个动作:

如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;

如果当前在 Word 下弹出的就是 Word 帮助;

Windows 下弹出的就是 Windows 帮助和支持。

同一个事件发生在不同的对象上会产生不同的结果。

 

多态的优点

1. 消除类型之间的耦合关系

2. 可替换性

3. 可扩充性

4. 接口性

5. 灵活性

6. 简化性

多态存在的三个必要条件

继承

重写

父类引用指向子类对象

比如:

Parent p = new Child();

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

多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。

 

以下是一个多态实例的演示,详细说明请看注释:

 1 public class Test {
 2 
 3     public static void main(String[] args) {
 4 
 5       show(new Cat());  // 以 Cat 对象调用 show 方法
 6 
 7       show(new Dog());  // 以 Dog 对象调用 show 方法         
 8 
 9       Animal a = new Cat();  // 向上转型  
10 
11       a.eat();               // 调用的是 Cat 的 eat
12 
13       Cat c = (Cat)a;        // 向下转型  
14 
15       c.work();        // 调用的是 Cat 的 work
16 
17   }       
18 
19     public static void show(Animal a)  {
20 
21       a.eat();  
22 
23         // 类型判断
24 
25         if (a instanceof Cat)  {  // 猫做的事情
26 
27             Cat c = (Cat)a;  
28 
29             c.work();  
30 
31         } else if (a instanceof Dog) { // 狗做的事情
32 
33             Dog c = (Dog)a;  
34 
35             c.work();  
36 
37         }  
38 
39     }  
40 
41 }
42 
43 abstract class Animal {  
44 
45     abstract void eat();  
46 
47 }  
48 
49 class Cat extends Animal {  
50 
51     public void eat() {  
52 
53         System.out.println("吃鱼");  
54 
55     }  
56 
57     public void work() {  
58 
59         System.out.println("抓老鼠");  
60 
61     }  
62 
63 }  
64 
65 class Dog extends Animal {  
66 
67     public void eat() {  
68 
69         System.out.println("吃骨头");  
70 
71     }  
72 
73     public void work() {  
74 
75         System.out.println("看家");  
76 
77     }  
78 
79 }

执行以上程序,输出结果为:

吃鱼

抓老鼠

吃骨头

看家

吃鱼

抓老鼠

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

虚函数

虚函数的存在是为了多态。

Java 中其实没有虚函数的概念,它的普通函数就相当于 C++ 的虚函数,动态绑定是Java的默认行为。如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。

 

重写

我们将介绍在 Java 中,当设计类时,被重写的方法的行为怎样影响多态性。

我们已经讨论了方法的重写,也就是子类能够重写父类的方法。

当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。

要想调用父类中被重写的方法,则必须使用关键字 super

 

 1 public class Employee {
 2 
 3    private String name;
 4 
 5    private String address;
 6 
 7    private int number;
 8 
 9    public Employee(String name, String address, int number) {
10 
11       System.out.println("Employee 构造函数");
12 
13       this.name = name;
14 
15       this.address = address;
16 
17       this.number = number;
18 
19    }
20 
21    public void mailCheck() {
22 
23       System.out.println("邮寄支票给: " + this.name
24 
25        + " " + this.address);
26 
27    }
28 
29    public String toString() {
30 
31       return name + " " + address + " " + number;
32 
33    }
34 
35    public String getName() {
36 
37       return name;
38 
39    }
40 
41    public String getAddress() {
42 
43       return address;
44 
45    }
46 
47    public void setAddress(String newAddress) {
48 
49       address = newAddress;
50 
51    }
52 
53    public int getNumber() {
54 
55      return number;
56 
57    }
58 
59 }

假设下面的类继承Employee类:

 

 1 public class Salary extends Employee
 2 
 3 {
 4 
 5    private double salary; // 全年工资
 6 
 7    public Salary(String name, String address, int number, double salary) {
 8 
 9        super(name, address, number);
10 
11        setSalary(salary);
12 
13    }
14 
15    public void mailCheck() {
16 
17        System.out.println("Salary 类的 mailCheck 方法 ");
18 
19        System.out.println("邮寄支票给:" + getName()
20 
21        + " ,工资为:" + salary);
22 
23    }
24 
25    public double getSalary() {
26 
27        return salary;
28 
29    }
30 
31    public void setSalary(double newSalary) {
32 
33        if(newSalary >= 0.0) {
34 
35           salary = newSalary;
36 
37        }
38 
39    }
40 
41    public double computePay() {
42 
43       System.out.println("计算工资,付给:" + getName());
44 
45       return salary/52;
46 
47    }
48 
49 }

现在我们仔细阅读下面的代码,尝试给出它的输出结果:

public class VirtualDemo {

   public static void main(String [] args) {

      Salary s = new Salary("员工 A", "北京", 3, 3600.00);

      Employee e = new Salary("员工 B", "上海", 2, 2400.00);

      System.out.println("使用 Salary 的引用调用 mailCheck -- ");

      s.mailCheck();

      System.out.println("n使用 Employee 的引用调用 mailCheck--");

      e.mailCheck();

    }

}

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

Employee 构造函数

Employee 构造函数

使用 Salary 的引用调用 mailCheck --

Salary 类的 mailCheck 方法

邮寄支票给:员工 A ,工资为:3600.0

 

使用 Employee 的引用调用 mailCheck--

Salary 类的 mailCheck 方法

邮寄支票给:员工 B ,工资为:2400.0

 

例子解析

实例中,实例化了两个 Salary 对象:一个使用 Salary 引用 s,另一个使用 Employee 引用 e

当调用 s.mailCheck() 时,编译器在编译时会在 Salary 类中找到 mailCheck(),执行过程 JVM 就调用 Salary 类的 mailCheck()

因为 e Employee 的引用,所以调用 e mailCheck() 方法时,编译器会去 Employee 类查找 mailCheck() 方法 。

在编译的时候,编译器使用 Employee 类中的 mailCheck() 方法验证该语句, 但是在运行的时候,Java虚拟机(JVM)调用的是 Salary 类中的 mailCheck() 方法。

以上整个过程被称为虚拟方法调用,该方法被称为虚拟方法。

 

Java中所有的方法都能以这种方式表现,因此,重写的方法能在运行时调用,不管编译的时候源代码中引用变量是什么数据类型。

 

多态的实现方式

方式一:重写:

这个内容已经在上一章节详细讲过,就不再阐述,详细可访问:Java 重写(Override)与重载(Overload)

方式二:接口

1. 生活中的接口最具代表性的就是插座,例如一个三接头的插头都能接在三孔插座中,因为这个是每个国家都有各自规定的接口规则,有可能到国外就不行,那是因为国外自己定义的接口类型。

2. java中的接口类似于生活中的接口,就是一些方法特征的集合,但没有方法的实现。具体可以看 java接口 这一章节的内容。

方式三:抽象类和抽象方法

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。

父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。

Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

 

抽象类

Java语言中使用abstract class来定义抽象类。如下实例:

 1 public abstract class Employee
 2 
 3 {
 4 
 5    private String name;
 6 
 7    private String address;
 8 
 9    private int number;
10 
11    public Employee(String name, String address, int number)
12 
13    {
14 
15       System.out.println("Constructing an Employee");
16 
17       this.name = name;
18 
19       this.address = address;
20 
21       this.number = number;
22 
23    }
24 
25    public double computePay()
26 
27    {
28 
29      System.out.println("Inside Employee computePay");
30 
31      return 0.0;
32 
33    }
34 
35    public void mailCheck()
36 
37    {
38 
39       System.out.println("Mailing a check to " + this.name
40 
41        + " " + this.address);
42 
43    }
44 
45    public String toString()
46 
47    {
48 
49       return name + " " + address + " " + number;
50 
51    }
52 
53    public String getName()
54 
55    {
56 
57       return name;
58 
59    }
60 
61    public String getAddress()
62 
63    {
64 
65       return address;
66 
67    }
68 
69    public void setAddress(String newAddress)
70 
71    {
72 
73       address = newAddress;
74 
75    }
76 
77    public int getNumber()
78 
79    {
80 
81      return number;
82 
83    }
84 
85 }

注意到该 Employee 类没有什么不同,尽管该类是抽象类,但是它仍然有 3 个成员变量,7 个成员方法和 1 个构造方法。 现在如果你尝试如下的例子:

 1 public class AbstractDemo
 2 
 3 {
 4 
 5    public static void main(String [] args)
 6 
 7    {
 8 
 9       /* 以下是不允许的,会引发错误 */
10 
11       Employee e = new Employee("George W.", "Houston, TX", 43);
12 
13  
14 
15       System.out.println("n Call mailCheck using Employee reference--");
16 
17       e.mailCheck();
18 
19     }
20 
21 }

当你尝试编译AbstractDemo类时,会产生如下错误:

Employee.java:46: Employee is abstract; cannot be instantiated

      Employee e = new Employee("George W.", "Houston, TX", 43);

                   ^

 

继承抽象类

我们能通过一般的方法继承Employee类:

 1 public class Salary extends Employee
 2 
 3 {
 4 
 5    private double salary; //Annual salary
 6 
 7    public Salary(String name, String address, int number, double
 8 
 9       salary)
10 
11    {
12 
13        super(name, address, number);
14 
15        setSalary(salary);
16 
17    }
18 
19    public void mailCheck()
20 
21    {
22 
23        System.out.println("Within mailCheck of Salary class ");
24 
25        System.out.println("Mailing check to " + getName()
26 
27        + " with salary " + salary);
28 
29    }
30 
31    public double getSalary()
32 
33    {
34 
35        return salary;
36 
37    }
38 
39    public void setSalary(double newSalary)
40 
41    {
42 
43        if(newSalary >= 0.0)
44 
45        {
46 
47           salary = newSalary;
48 
49        }
50 
51    }
52 
53    public double computePay()
54 
55    {
56 
57       System.out.println("Computing salary pay for " + getName());
58 
59       return salary/52;
60 
61    }
62 
63 }

尽管我们不能实例化一个 Employee 类的对象,但是如果我们实例化一个 Salary 类对象,该对象将从 Employee 类继承 7 个成员方法,且通过该方法可以设置或获取三个成员变量。

 

 1 public class AbstractDemo
 2 
 3 {
 4 
 5    public static void main(String [] args)
 6 
 7    {
 8 
 9       Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00);
10 
11       Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00);
12 
13  
14 
15       System.out.println("Call mailCheck using Salary reference --");
16 
17       s.mailCheck();
18 
19  
20 
21       System.out.println("n Call mailCheck using Employee reference--");
22 
23       e.mailCheck();
24 
25     }
26 
27 }

以上程序编译运行结果如下:

Constructing an Employee

Constructing an Employee

Call mailCheck using  Salary reference --

Within mailCheck of Salary class

Mailing check to Mohd Mohtashim with salary 3600.0

Call mailCheck using Employee reference--

Within mailCheck of Salary class

Mailing check to John Adams with salary 2400.

 

抽象方法

如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。

Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。

抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。

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

 

声明抽象方法会造成以下两个结果:

如果一个类包含抽象方法,那么该类必须是抽象类。

任何子类必须重写父类的抽象方法,或者声明自身为抽象类。

继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。

如果Salary类继承了Employee类,那么它必须实现computePay()方法:

 1 public class Salary extends Employee
 2 
 3 {
 4 
 5    private double salary; // Annual salary
 6 
 7    public double computePay()
 8 
 9    {
10 
11       System.out.println("Computing salary pay for " + getName());
12 
13       return salary/52;
14 
15    }
16 
17    //其余代码
18 
19 }

 

抽象类总结规定

1. 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。

2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。

4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。

5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

 

4Java 重写(Override)与重载(Overload)

重写(Override)

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception IOException 的父类,只能抛出 IOException 的子类异常。

 

在面向对象原则里,重写意味着可以重写任何现有方法。实例如下:

 1 class Animal{
 2 
 3    public void move(){
 4 
 5       System.out.println("动物可以移动");
 6 
 7    }
 8 
 9 }
10 
11 class Dog extends Animal{
12 
13    public void move(){
14 
15       System.out.println("狗可以跑和走");
16 
17    }
18 
19 }
20 
21 public class TestDog{
22 
23    public static void main(String args[]){
24 
25       Animal a = new Animal(); // Animal 对象
26 
27       Animal b = new Dog(); // Dog 对象
28 
29       a.move();// 执行 Animal 类的方法
30 
31       b.move();//执行 Dog 类的方法
32 
33    }
34 
35 }

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

动物可以移动

狗可以跑和走

 

在上面的例子中可以看到,尽管 b 属于 Animal 类型,但是它运行的是 Dog 类的 move方法。

这是由于在编译阶段,只是检查参数的引用类型。

然而在运行时,Java 虚拟机(JVM)指定对象的类型并且运行该对象的方法。

因此在上面的例子中,之所以能编译成功,是因为 Animal 类中存在 move 方法,然而运行时,运行的是特定对象的方法。

思考以下例子:

 1 class Animal{
 2 
 3    public void move(){
 4 
 5       System.out.println("动物可以移动");
 6 
 7    }
 8 
 9 }
10 
11 class Dog extends Animal{
12 
13    public void move(){
14 
15       System.out.println("狗可以跑和走");
16 
17    }
18 
19    public void bark(){
20 
21       System.out.println("狗可以吠叫");
22 
23    }
24 
25 }
26 
27 public class TestDog{
28 
29    public static void main(String args[]){
30 
31       Animal a = new Animal(); // Animal 对象
32 
33       Animal b = new Dog(); // Dog 对象
34 
35  
36 
37       a.move();// 执行 Animal 类的方法
38 
39       b.move();//执行 Dog 类的方法
40 
41       b.bark();
42 
43    }
44 
45 }

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

TestDog.java:30: cannot find symbol

symbol  : method bark()

location: class Animal

                b.bark();

                 ^

该程序将抛出一个编译错误,因为b的引用类型Animal没有bark方法。

 

方法的重写规则

参数列表必须完全与被重写方法的相同。

返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。

访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected

父类的成员方法只能被它的子类重写。

声明为 final 的方法不能被重写。

声明为 static 的方法不能被重写,但是能够被再次声明。

子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private final 的方法。

子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public protected 的非 final 方法。

重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。

构造方法不能被重写。

如果不能继承一个方法,则不能重写这个方法。

 

Super 关键字的使用

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

 1 class Animal{
 2 
 3    public void move(){
 4 
 5       System.out.println("动物可以移动");
 6 
 7    }
 8 
 9 }
10 
11 class Dog extends Animal{
12 
13    public void move(){
14 
15       super.move(); // 应用super类的方法
16 
17       System.out.println("狗可以跑和走");
18 
19    }
20 
21 }
22 
23 public class TestDog{
24 
25    public static void main(String args[]){
26 
27       Animal b = new Dog(); // Dog 对象
28 
29       b.move(); //执行 Dog类的方法
30 
31    }
32 
33 }

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

动物可以移动

狗可以跑和走

 

重载(Overload)

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

每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。

最常用的地方就是构造器的重载。

重载规则:

被重载的方法必须改变参数列表(参数个数或类型不一样)

被重载的方法可以改变返回类型;

被重载的方法可以改变访问修饰符;

被重载的方法可以声明新的或更广的检查异常;

方法能够在同一个类中或者在一个子类中被重载。

无法以返回值类型作为重载函数的区分标准。

 

实例

 1 public class Overloading {
 2 
 3     public int test(){
 4 
 5         System.out.println("test1");
 6 
 7         return 1;
 8 
 9     }
10 
11     public void test(int a){
12 
13         System.out.println("test2");
14 
15     }   
16 
17     //以下两个参数类型顺序不同
18 
19     public String test(int a,String s){
20 
21         System.out.println("test3");
22 
23         return "returntest3";
24 
25     }   
26 
27     public String test(String s,int a){
28 
29         System.out.println("test4");
30 
31         return "returntest4";
32 
33     }   
34 
35     public static void main(String[] args){
36 
37         Overloading o = new Overloading();
38 
39         System.out.println(o.test());
40 
41         o.test(1);
42 
43         System.out.println(o.test(1,"test3"));
44 
45         System.out.println(o.test("test4",1));
46 
47     }
48 
49 }

 

重写与重载之间的区别

区别点

重载方法

重写方法

参数列表

必须修改

一定不能修改

返回类型

可以修改

一定不能修改

异常

可以修改

可以减少或删除,一定不能抛出新的或者更广的异常

访问

可以修改

一定不能做更严格的限制(可以降低限制)

 

 

总结:

  方法的重写(Overriding)和重载(Overloading)java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

1.方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)

2.方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)

3.方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

 

5Java 接口

Java 接口

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

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

除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

接口与类相似点:

一个接口可以有多个方法。

接口文件保存在 .java 结尾的文件中,文件名使用接口名。

接口的字节码文件保存在 .class 结尾的文件中。

接口相应的字节码文件必须在与包名称相匹配的目录结构中。

接口与类的区别:

接口不能用于实例化对象。

接口没有构造方法。

接口中所有的方法必须是抽象方法。

接口不能包含成员变量,除了 static final 变量。

接口不是被类继承了,而是要被类实现。

接口支持多继承。

 

接口特性

接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。

接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。

接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。

抽象类和接口的区别

1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。

2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。

3. 接口中不能含有静态代码块以及静态方法(static 修饰的方法),而抽象类是可以有静态代码块和静态方法。

4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

注:JDK 1.8 以后,接口里可以有静态方法和方法体了。

 

接口的声明

接口的声明语法格式如下:

[可见度] interface 接口名称 [extends 其他的接口名] {

        // 声明变量

        // 抽象方法

}

 

接口有以下特性:

接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。

接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。

接口中的方法都是公有的。

 

接口的实现

当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。

类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。

 

重写接口中声明的方法时,需要注意以下规则:

类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。

类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。

如果实现接口的类是抽象类,那么就没必要实现该接口的方法。

在实现接口的时候,也要注意一些规则:

一个类可以同时实现多个接口。

一个类只能继承一个类,但是能实现多个接口。

一个接口能继承另一个接口,这和类之间的继承比较相似。

接口的继承

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

 

下面的Sports接口被HockeyFootball接口继承:

 

 1 public interface Sports
 2 
 3 {
 4 
 5    public void setHomeTeam(String name);
 6 
 7    public void setVisitingTeam(String name);
 8 
 9 }
10 
11  
12 
13 
14 public interface Football extends Sports
15 
16 {
17 
18    public void homeTeamScored(int points);
19 
20    public void visitingTeamScored(int points);
21 
22    public void endOfQuarter(int quarter);
23 
24 }
25 
26  
27 
28 
29 public interface Hockey extends Sports
30 
31 {
32 
33    public void homeGoalScored();
34 
35    public void visitingGoalScored();
36 
37    public void endOfPeriod(int period);
38 
39    public void overtimePeriod(int ot);
40 
41 }

Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类需要实现六个方法。

相似的,实现Football接口的类需要实现五个方法,其中两个来自于Sports接口。

 

接口的多继承

Java中,类的多继承是不合法,但接口允许多继承。

在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:

public interface Hockey extends Sports, Event

以上的程序片段是合法定义的子接口,与类不同的是,接口允许多继承,而 SportsEvent 可能定义或是继承相同的方法

 

 

标记接口

最常用的继承接口是没有包含任何方法的接口。

标记接口是没有任何方法和属性的接口.它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。

标记接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权。

例如:java.awt.event 包中的 MouseListener 接口继承的 java.util.EventListener 接口定义如下:

1 package java.util;
2 
3 public interface EventListener{
4 }

 

没有任何方法的接口被称为标记接口。标记接口主要用于以下两种目的:

建立一个公共的父接口:

正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。

向一个类添加数据类型:

这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。

内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/sudoit/p/12017135.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!