在 Java 中经过 extends 关键字能够申明一个类是从另一个类继承而来的,通常形式以下:html
class 父类 { } class 子类 extends 父类 { }
接下来咱们经过实例来讲明这个需求。java
开发动物类,其中动物分别为企鹅以及老鼠,要求以下:ide
public class Animal { private String name; private int id; public Animal(String myName, int myid) { name = myName; id = myid; } public void eat(){ System.out.println(name+"正在吃"); } public void sleep(){ System.out.println(name+"正在睡"); } public void introduction() { System.out.println("你们好!我是" + id + "号" + name + "."); } }
这个Animal类就能够做为一个父类,而后企鹅类和老鼠类继承这个类以后,就具备父类当中的属性和方法,子类就不会存在重复的代码,维护性也提升,代码也更加简洁,提升代码的复用性(复用性主要是能够屡次使用,不用再屡次写一样的代码) 继承以后的代码:函数
企鹅类: public class Penguin extends Animal { public Penguin(String myName, int myid) { super(myName, myid); } } 老鼠类: public class Mouse extends Animal { public Mouse(String myName, int myid) { super(myName, myid); } }
须要注意的是 Java 不支持多继承,但支持多重继承。ui
子类拥有父类非 private 的属性、方法。this
子类能够拥有本身的属性和方法,即子类能够对父类进行扩展。spa
子类能够用本身的方式实现父类的方法。设计
Java 的继承是单继承,可是能够多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 A 类继承 B 类,B 类继承 C 类,因此按照关系就是 C 类是 B 类的父类,B 类是 A 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。htm
提升了类之间的耦合性(继承的缺点,耦合度高就会形成代码之间的联系越紧密,代码独立性越差)。对象
继承可使用 extends 和 implements 这两个关键字来实现继承,并且全部的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,因此不须要 import)祖先类。
在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,因此 extends 只能继承一个类。
使用 implements 关键字能够变相的使java具备多继承的特性,使用范围为类继承接口的状况,能够同时继承多个接口(接口跟接口之间采用逗号分隔)。
public interface A { public void eat(); public void sleep(); } public interface B { public void show(); } public class C implements A,B { }
super关键字:咱们能够经过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字:指向本身的引用。
class Animal { void eat() { System.out.println("animal : eat"); } } class Dog extends Animal { void eat() { System.out.println("dog : eat"); } void eatTest() { this.eat(); // this 调用本身的方法 super.eat(); // super 调用父类方法 } } public class Test { public static void main(String[] args) { Animal a = new Animal(); a.eat(); Dog d = new Dog(); d.eatTest(); } } 结果: animal : eat dog : eat animal : eat
final 关键字声明类能够把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写:
声明类:final class 类名 {//类体}
声明方法:修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}
注:实例变量也能够被定义为 final,被定义为 final 的变量不能被修改。被声明为 final 类的方法自动地声明为 final,可是实例变量并非 final
子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。若是父类的构造器带有参数,则必须在子类的构造器中显式地经过 super 关键字调用父类的构造器并配以适当的参数列表。
若是父类构造器没有参数,则在子类的构造器中不须要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
class SuperClass { private int n; SuperClass(){ System.out.println("SuperClass()"); } SuperClass(int n) { System.out.println("SuperClass(int n)"); this.n = n; } } // SubClass 类继承 class SubClass extends SuperClass{ private int n; SubClass(){ // 自动调用父类的无参数构造器 System.out.println("SubClass"); } public SubClass(int n){ super(300); // 调用父类中带有参数的构造器 System.out.println("SubClass(int n):"+n); this.n = n; } } // SubClas2 类继承 class SubClass2 extends SuperClass{ private int n; SubClass2(){ super(300); // 调用父类中带有参数的构造器 System.out.println("SubClass2"); } public SubClass2(int n){ // 自动调用父类的无参数构造器 System.out.println("SubClass2(int n):"+n); this.n = n; } } public class TestSuperSub{ public static void main (String args[]){ System.out.println("------SubClass 类继承------"); SubClass sc1 = new SubClass(); SubClass sc2 = new SubClass(100); System.out.println("------SubClass2 类继承------"); SubClass2 sc3 = new SubClass2(); SubClass2 sc4 = new SubClass2(200); } } 结果: ------SubClass 类继承------ SuperClass() SubClass SuperClass(int n) SubClass(int n):100 ------SubClass2 类继承------ SuperClass(int n) SubClass2 SuperClass() SubClass2(int n):200
重写是子类对父类的容许访问的方法的实现过程进行从新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类能够根据须要,定义特定于本身的行为。 也就是说子类可以根据须要实现父类的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,可是在重写这个方法的时候不能抛出 Exception 异常,由于 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
在面向对象原则里,重写意味着能够重写任何现有方法。实例以下:
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方法,然而运行时,运行的是特定对象的方法。
思考如下例子:
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(); } }
以上实例编译运行结果以下:
TestDog.java:30: cannot find symbol symbol : method bark() location: class Animal b.bark(); ^
该程序将抛出一个编译错误,由于b的引用类型Animal没有bark方法。
当须要在子类中调用父类的被重写方法时,要使用super关键字。
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类的方法 } } 结果: 动物能够移动 狗能够跑和走
重载(overloading) 是在一个类里面,方法名字相同,而参数不一样。返回类型能够相同也能够不一样。
每一个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最经常使用的地方就是构造器的重载。
重载规则:
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)); } }
方法的重写(Overriding)和重载(Overloading)是java多态性的不一样表现,重写是父类与子类之间多态性的一种表现,重载能够理解成多态的具体表现形式。
多态是同一个行为具备多个不一样表现形式或形态的能力。
多态就是同一个接口,使用不一样的实例而执行不一样操做,如图所示:
当使用多态方式调用方法时,首先检查父类中是否有该方法,若是没有,则编译错误;若是有,再去调用子类的同名方法。
多态的好处:可使程序有良好的扩展,并能够对全部类的对象进行通用处理。
如下是一个多态实例的演示,详细说明请看注释:
public class Test { public static void main(String[] args) { show(new Cat()); // 以 Cat 对象调用 show 方法 show(new Dog()); // 以 Dog 对象调用 show 方法 Animal a = new Cat(); // 向上转型 a.eat(); // 调用的是 Cat 的 eat Cat c = (Cat)a; // 向下转型 c.work(); // 调用的是 Cat 的 work } public static void show(Animal a) { a.eat(); // 类型判断 if (a instanceof Cat) { // 猫作的事情 Cat c = (Cat)a; c.work(); } else if (a instanceof Dog) { // 狗作的事情 Dog c = (Dog)a; c.work(); } } } abstract class Animal { abstract void eat(); } class Cat extends Animal { public void eat() { System.out.println("吃鱼"); } public void work() { System.out.println("抓老鼠"); } } class Dog extends Animal { public void eat() { System.out.println("吃骨头"); } public void work() { System.out.println("看家"); } }
结果:
吃鱼 抓老鼠 吃骨头 看家 吃鱼 抓老鼠
虚函数的存在是为了多态。
Java 中其实没有虚函数的概念,它的普通函数就至关于 C++ 的虚函数,动态绑定是Java的默认行为。若是 Java 中不但愿某个函数具备虚函数特性,能够加上 final 关键字变成非虚函数。
咱们将介绍在 Java 中,当设计类时,被重写的方法的行为怎样影响多态性。
咱们已经讨论了方法的重写,也就是子类可以重写父类的方法。
当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。
要想调用父类中被重写的方法,则必须使用关键字 super。
Employee.java 文件代码: /* 文件名 : Employee.java */ public class Employee { private String name; private String address; private int number; public Employee(String name, String address, int number) { System.out.println("Employee 构造函数"); this.name = name; this.address = address; this.number = number; } public void mailCheck() { System.out.println("邮寄支票给: " + 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; } }
Salary.java 文件代码: /* 文件名 : Salary.java */ public class Salary extends Employee { private double salary; // 整年工资 public Salary(String name, String address, int number, double salary) { super(name, address, number); setSalary(salary); } public void mailCheck() { System.out.println("Salary 类的 mailCheck 方法 "); System.out.println("邮寄支票给:" + getName() + " ,工资为:" + salary); } public double getSalary() { return salary; } public void setSalary(double newSalary) { if(newSalary >= 0.0) { salary = newSalary; } } public double computePay() { System.out.println("计算工资,付给:" + getName()); return salary/52; } }
VirtualDemo.java 文件代码: /* 文件名 : VirtualDemo.java */ 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来定义抽象类。以下实例:
/* 文件名 : 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 类没有什么不一样,尽管该类是抽象类,可是它仍然有 3 个成员变量,7 个成员方法和 1 个构造方法。 如今若是你尝试以下的例子:
AbstractDemo.java 文件代码: /* 文件名 : AbstractDemo.java */ public class AbstractDemo { public static void main(String [] args) { /* 如下是不容许的,会引起错误 */ Employee e = new Employee("George W.", "Houston, TX", 43); System.out.println("\n Call mailCheck using Employee reference--"); e.mailCheck(); } } 当你尝试编译AbstractDemo类时,会产生以下错误: Employee.java:46: Employee is abstract; cannot be instantiated Employee e = new Employee("George W.", "Houston, TX", 43); ^ 1 error
抽象类继承:
Salary.java 文件代码: /* 文件名 : Salary.java */ public class Salary extends Employee { private double salary; //Annual salary public Salary(String name, String address, int number, double salary) { super(name, address, number); setSalary(salary); } public void mailCheck() { System.out.println("Within mailCheck of Salary class "); System.out.println("Mailing check to " + getName() + " with salary " + salary); } public double getSalary() { return salary; } public void setSalary(double newSalary) { if(newSalary >= 0.0) { salary = newSalary; } } public double computePay() { System.out.println("Computing salary pay for " + getName()); return salary/52; } } 尽管咱们不能实例化一个 Employee 类的对象,可是若是咱们实例化一个 Salary 类对象,该对象将从 Employee 类继承 7 个成员方法,且经过该方法能够设置或获取三个成员变量。 AbstractDemo.java 文件代码: /* 文件名 : AbstractDemo.java */ public class AbstractDemo { public static void main(String [] args) { Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00); Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00); System.out.println("Call mailCheck using Salary reference --"); s.mailCheck(); System.out.println("\n Call mailCheck using Employee reference--"); e.mailCheck(); } } 以上程序编译运行结果以下: 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 关键字一样能够用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。
抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。
public abstract class Employee { private String name; private String address; private int number; public abstract double computePay(); //其他代码 }
声明抽象方法会形成如下两个结果:
继承抽象方法的子类必须重写该方法。不然,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,不然,从最初的父类到最终的子类都不能用来实例化对象。
若是Salary类继承了Employee类,那么它必须实现computePay()方法:
Salary.java 文件代码: /* 文件名 : 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; } //其他代码 }
1. 抽象类不能被实例化(初学者很容易犯的错),若是被实例化,就会报错,编译没法经过。只有抽象类的非抽象子类能够建立对象。
2. 抽象类中不必定包含抽象方法,可是有抽象方法的类一定是抽象类。
3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。