继承
Java 继承
继承可以被定义为一个类获取另一个类的属性(方法和字段)的过程。 通过使用继承,信息可以按照分层次序进行管理。
继承其他属性的类称为子类(派生类,子类),其属性被继承的类称为超类(基类,父类)。
extends 关键词
extends是用于继承类的属性的关键字。 以下是extends关键字的语法。
语法
class Super {
.....
.....
}
class Sub extends Super {
.....
.....
}
例子代码
以下是演示Java继承的示例。 在这个例子中,你可以观察两个类,即Calculation和My_Calculation。
使用extends关键字,My_Calculation会继承Calculation类的addition()和Subtraction()方法。
将以下程序复制并粘贴到名为My_Calculation.java的文件中
例子
class Calculation {
int z;
public void addition(int x, int y) {
z = x + y;
System.out.println("The sum of the given numbers:"+z);
}
public void Subtraction(int x, int y) {
z = x - y;
System.out.println("The difference between the given numbers:"+z);
}
}
public class My_Calculation extends Calculation {
public void multiplication(int x, int y) {
z = x * y;
System.out.println("The product of the given numbers:"+z);
}
public static void main(String args[]) {
int a = 20, b = 10;
My_Calculation demo = new My_Calculation();
demo.addition(a, b);
demo.Subtraction(a, b);
demo.multiplication(a, b);
}
}
编译并执行上面的代码,如下所示。
javac My_Calculation.java java My_Calculation
执行程序后,会产生以下结果 −
The sum of the given numbers:30 The difference between the given numbers:10 The product of the given numbers:200
在给定的程序中,当创建My_Calculation类的对象时,将在其中创建超类内容的副本。 这就是为什么,使用子类的对象可以访问超类的成员。
超类引用变量可以保存子类对象,但使用该变量只能访问超类的成员,因此为了访问这两个类的成员,建议始终为该子类创建引用变量。
如果你考虑上面的程序,你可以实例化下面给出的类。 但是使用超类引用变量(本例中为cal),您不能调用属于子类My_Calculation的方法multiplication()。
Calculation cal = new My_Calculation(); demo.addition(a, b); demo.Subtraction(a, b);
注 - 子类从其超类继承所有成员(字段,方法和嵌套类)。 构造函数不是成员,因此它们不会被子类继承,但可以从子类调用超类的构造函数。
super 关键词
super关键字与this关键字相似。 以下是使用super关键字的场景。
- 它用于区分超类的成员和子类的成员(如果它们具有相同的名称)。
- 它用于从子类调用超类构造函数。
区分会员
如果一个类正在继承另一个类的属性。 如果超类的成员具有与子类相同的名称,为了区分这些变量,我们使用super关键字,如下所示。
super.variable super.method();
例子代码
本节为您提供一个演示super关键字用法的程序。
在给定的程序中,你有两个类,即Sub_class和Super_class,两者都有一个名为display()的方法,具有不同的实现,以及一个名为num的变量具有不同的值。 我们调用这两个类的display()方法并打印这两个类的变量num的值。 在这里你可以观察到我们使用了super关键字来区分超类的成员和子类。
将程序复制并粘贴到名为Sub_class.java的文件中。
例子
class Super_class {
int num = 20;
// display method of superclass
public void display() {
System.out.println("This is the display method of superclass");
}
}
public class Sub_class extends Super_class {
int num = 10;
// display method of sub class
public void display() {
System.out.println("This is the display method of subclass");
}
public void my_method() {
// Instantiating subclass
Sub_class sub = new Sub_class();
// Invoking the display() method of sub class
sub.display();
// Invoking the display() method of superclass
super.display();
// printing the value of variable num of subclass
System.out.println("value of the variable named num in sub class:"+ sub.num);
// printing the value of variable num of superclass
System.out.println("value of the variable named num in super class:"+ super.num);
}
public static void main(String args[]) {
Sub_class obj = new Sub_class();
obj.my_method();
}
}
使用以下语法编译并执行上述代码。
javac Super_Demo java Super
在执行程序时,您会得到以下结果 -
This is the display method of subclass This is the display method of superclass value of the variable named num in sub class:10 value of the variable named num in super class:20
调用Superclass构造函数
如果一个类正在继承另一个类的属性,那么子类会自动获取超类的默认构造函数。 但是如果你想调用超类的参数化构造函数,你需要使用super关键字,如下所示。
super(values);
例子代码
本节中给出的程序演示了如何使用super关键字来调用超类的参数化构造函数。 这个程序包含一个超类和一个子类,其中超类包含一个接受整数值的参数化构造函数,我们使用super关键字来调用超类的参数化构造函数。
将以下程序复制并粘贴到名为Subclass.java的文件中
例子
class Superclass {
int age;
Superclass(int age) {
this.age = age;
}
public void getAge() {
System.out.println("The value of the variable named age in super class is: " +age);
}
}
public class Subclass extends Superclass {
Subclass(int age) {
super(age);
}
public static void main(String argd[]) {
Subclass s = new Subclass(24);
s.getAge();
}
}
使用以下语法编译并执行上述代码。
javac Subclass java Subclass
在执行程序时,您会得到以下结果 -
The value of the variable named age in super class is: 24
IS-A关系
IS-A指的是类别的父子继承关系,例如类别D是另一个类别B的子类别(类别B是类别D的父类别)。或是d被包含在乙内)指的是,概念体d物是概念体乙物的特殊化,而概念体乙物是概念体d物的一般化。 让我们看看如何使用extends关键字来实现继承。
public class Animal {
}
public class Mammal extends Animal {
}
public class Reptile extends Animal {
}
public class Dog extends Mammal {
}
现在,基于上面的例子,在面向对象的术语中,下面是真实的 -
- 动物是哺乳类的超类
- 动物是爬虫类的超类
- 哺乳动物和爬行动物是Animal类的子类
- 狗是哺乳动物和动物类的子类
现在,如果我们考虑IS-A关系,我们可以说 -
- 哺乳动物IS-A动物
- 爬行动物IS-A动物
- 狗是一只哺乳动物
- 因此:狗也是一种动物
通过使用extends关键字,子类将能够继承超类的所有属性,但超类的私有属性除外。
我们可以通过使用实例操作符来确保哺乳动物实际上是一种动物。
class Animal {
}
class Mammal extends Animal {
}
class Reptile extends Animal {
}
public class Dog extends Mammal {
public static void main(String args[]) {
Animal a = new Animal();
Mammal m = new Mammal();
Dog d = new Dog();
System.out.println(m instanceof Animal);
System.out.println(d instanceof Mammal);
System.out.println(d instanceof Animal);
}
}
这将产生以下结果 −
true true true
由于我们对extends关键字有很好的理解,让我们来看看如何使用implements关键字来获取IS-A关系。
通常,implements关键字与类一起使用以继承接口的属性。 一个类永远不能扩展接口。
例子
public interface Animal {
}
public class Mammal implements Animal {
}
public class Dog extends Mammal {
}
instanceof 关键词
让我们使用instanceof操作符来检查确定哺乳动物(Mammal)是否实际上是动物(Animal),而狗实际上是动物(Dog)。
例子
interface Animal{}
class Mammal implements Animal{}
public class Dog extends Mammal {
public static void main(String args[]) {
Mammal m = new Mammal();
Dog d = new Dog();
System.out.println(m instanceof Animal);
System.out.println(d instanceof Mammal);
System.out.println(d instanceof Animal);
}
}
这将产生以下结果 −
true true true
HAS-A 关系
这些关系主要基于使用情况。 这决定了某个类是否具有某种特定的功能。 这种关系有助于减少代码的重复以及错误。
让我们看一个例子 -
public class Vehicle{}
public class Speed{}
public class Van extends Vehicle {
private Speed sp;
}
Van HAS-A Speed类。 通过为Speed单独设置一个类,我们不需要在Van类中放入Speed的整个代码,这使得可以在多个应用程序中重用Speed类。
在面向对象的功能中,用户不需要担心哪个对象正在进行真正的工作。 为了达到这个目的,Van类隐藏了Van类用户的实现细节。 所以,基本上会发生什么是用户会要求Van类做一些行动,Van类将自己不做这项工作而要求另一个类来执行这个行动。
继承的类型
如下所示,有各种类型的继承。
需要记住一个非常重要的事实是Java不支持多继承。 这意味着一个班级不能超过一个班级。 因此,以下是非法的 -
例子
public class extends Animal, Mammal{}
然而,一个类可以实现一个或多个接口,这有助于Java消除多重继承的不可能性。
重载
Java 重载
在前一章中,我们讨论了超类和子类。 如果一个类从它的父类继承了一个方法,那么有机会覆盖该方法,只要它没有标记为final。
重写的好处是:能够定义特定于子类类型的行为,这意味着子类可以根据其要求实现父类方法。
在面向对象的术语中,覆盖意味着覆盖现有方法的功能。
让我们看一个例子。
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 TestDog {
public static void main(String args[]) {
Animal a = new Animal(); // Animal reference and object
Animal b = new Dog(); // Animal reference but Dog object
a.move(); // runs the method in Animal class
b.move(); // runs the method in Dog class
}
}
这将产生以下结果 −
Animals can move Dogs can walk and run
在上面的例子中可以看到,尽管b属于Animal类型,但是它运行的是Dog类的move方法。
这是由于在编译阶段,只是检查参数的引用类型。
然而在运行时,Java虚拟机(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 TestDog {
public static void main(String args[]) {
Animal a = new Animal(); // Animal reference and object
Animal b = new Dog(); // Animal reference but Dog object
a.move(); // runs the method in Animal class
b.move(); // runs the method in Dog class
b.bark();
}
}
这将产生以下结果 −
TestDog.java:26: error: cannot find symbol b.bark(); ^ symbol: method bark() location: variable b of type Animal 1 error
重载方法的规则
- 参数列表应该与重写的方法完全相同。
- 返回类型应该与超类中原始重写方法中声明的返回类型相同或是其子类型。
- 访问级别不能比重写的方法的访问级别更具限制性。例如:如果超类方法被声明为public,那么子类中的重写方法不能是私有的或保护的。
- 实例方法只有在被子类继承时才能被覆盖。
- 声明为final的方法不能被覆盖。
- 声明为静态的方法不能被覆盖,但可以重新声明。
- 如果一个方法不能被继承,那么它不能被覆盖。
- 与实例的超类相同的包中的子类可以覆盖任何未声明为private或final的超类方法。
- 不同包中的子类只能覆盖public或protected声明的非final方法。
- 无论重写的方法是否抛出异常,覆盖方法都可以抛出任何不检查异常。但是,重写方法不应该抛出比重写方法声明的更新或更宽的检查异常。重写方法可以抛出比重写方法更少或更少的异常。
- 构造函数不能被覆盖。
super 关键字的使用
当需要在子类中调用父类的被重写方法时,要使用super关键字。
例子
class Animal {
public void move() {
System.out.println("Animals can move");
}
}
class Dog extends Animal {
public void move() {
super.move(); // invokes the super class method
System.out.println("Dogs can walk and run");
}
}
public class TestDog {
public static void main(String args[]) {
Animal b = new Dog(); // Animal reference but Dog object
b.move(); // runs the method in Dog class
}
}
这将产生以下结果 −
Animals can move Dogs can walk and run
多态
Java 多态
多态性是一个对象采取多种形式的能力。 当使用父类引用来引用子类对象时,OOP中最常见的使用多态性。
任何可以通过多个IS-A测试的Java对象都被认为是多态的。 在Java中,所有Java对象都是多态的,因为任何对象都将通过针对它们自己的类型和类Object的IS-A测试。
知道访问对象的唯一可能方式是通过引用变量,这一点很重要。 参考变量只能是一种类型。 一旦声明,参考变量的类型不能改变。
只要它没有被声明为final,引用变量就可以被重新分配给其他对象。 引用变量的类型将决定它可以在对象上调用的方法。
引用变量可以引用其声明类型的任何对象或其声明类型的任何子类型。 引用变量可以声明为类或接口类型。
让我们看一个例子。
public interface Vegetarian{}
public class Animal{}
public class Deer extends Animal implements Vegetarian{}
现在,Deer类被认为是多态的,因为它具有多重继承。 对于上述例子,以下情况属实 -
- A Deer IS-A Animal
- A Deer IS-A Vegetarian
- A Deer IS-A Deer
- A Deer IS-A Object
当我们将引用变量事实应用于Deer对象引用时,以下声明是合法的 -
Deer d = new Deer(); Animal a = d; Vegetarian v = d; Object o = d;
所有引用变量d,a,v,o都指向堆中相同的Deer对象。
Virtual 方法
在本节中,我将向您展示Java中重载方法的行为如何让您在设计类时利用多态性。
我们已经讨论了方法重载,其中子类可以在其父类中重载一个方法。 重载的方法基本上隐藏在父类中,除非子类在重载方法中使用super关键字,否则不会被调用。
例子
/* File name : 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("Constructing an Employee");
this.name = name;
this.address = address;
this.number = number;
}
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类如下 -
/* File name : 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;
}
}
现在,您仔细研究以下程序并尝试确定其输出 −
/* File name : VirtualDemo.java */
public class VirtualDemo {
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 ailing check to Mohd Mohtashim with salary 3600.0 Call mailCheck using Employee reference-- Within mailCheck of Salary class ailing check to John Adams with salary 2400.0
在这里,我们实例化两个Salary对象。 一个使用Salary引用s,另一个使用Employee引用e。
在调用s.mailCheck()时,编译器会在编译时看到Salary类中的mailCheck(),并且JVM在运行时调用Salary类中的mailCheck()。
e上的mailCheck()与e很不相同,因为e是一个Employee引用。 当编译器看到e.mailCheck()时,编译器会在Employee类中看到mailCheck()方法。
在编译时,编译器使用Employee中的mailCheck()来验证此语句。 然而,在运行时,JVM调用Salary类中的mailCheck()。
这种行为被称为虚拟方法调用,这些方法被称为虚拟方法。 在运行时调用重写的方法,无论编译时在源代码中使用的引用是什么数据类型。
抽象类
Java 抽象类
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
抽象类
在其声明中包含abstract关键字的类称为抽象类。
- 抽象类可能包含也可能不包含抽象方法。
- 但是,如果一个类至少有一个抽象方法,那么该类必须声明为抽象的。
- 如果一个类被声明为抽象的,它就不能被实例化。
- 要使用抽象类,必须从另一个类继承它,为其中的抽象方法提供实现。
- 如果您继承了抽象类,则必须为其中的所有抽象方法提供实现。
本节为您提供了一个抽象类的例子。 要创建抽象类,只需在类关键字之前的类关键字中使用abstract关键字即可。
/* 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类与Java中的普通类相同。 该类现在是抽象的,但它仍然有三个字段,七个方法和一个构造函数。
现在您可以尝试按以下方式实例化Employee类 −
/* File name : AbstractDemo.java */
public class AbstractDemo {
public static void main(String [] args) {
/* Following is not allowed and would raise error */
Employee e = new Employee("George W.", "Houston, TX", 43);
System.out.println("\n Call mailCheck using Employee reference--");
e.mailCheck();
}
}
当你编译上面的类时,它会给你下面的错误 −
Employee.java:46: Employee is abstract; cannot be instantiated
Employee e = new Employee("George W.", "Houston, TX", 43);
^
1 error
继承抽象类
我们可以通过以下方式继承Employee类的属性,就像具体类一样 -
例子
/* File name : 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类的所有三个字段和七个方法,如下所示。
/* File name : 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.0
Abstract 方法
如果你想要一个类包含一个特定的方法,但你希望该方法的实际实现由子类来确定,那么你可以在父类中声明该方法作为一个抽象。
- Abstract关键字用于将方法声明为抽象。
- 您必须将方法名称前面的abstract关键字放在方法声明中。
- 抽象方法包含方法签名,但不包含方法体。
- 抽象方法不是大括号,而是末尾会有一个semoi冒号(;)。
以下是absract方法的一个例子。
public abstract class Employee {
private String name;
private String address;
private int number;
public abstract double computePay();
// Remainder of class definition
}
将方法声明为抽象有两个结果 -
- 包含它的类必须声明为抽象。
- 任何继承当前类的类都必须重写抽象方法或将其声明为抽象。
注 - 最后,后代类必须实现抽象方法; 否则,您将拥有无法实例化的抽象类的层次结构。
假设Salary类继承Employee类,那么它应该实现computePay()方法,如下所示 -
/* 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
}
封装
Java 封装
封装是四种基本OOP概念之一。 另外三个是继承,多态和抽象。
Java中的封装是一种将数据(变量)和代码作用于数据(方法)的代码作为一个整体进行封装的机制。 在封装中,类的变量对其他类是隐藏的,只能通过当前类的方法访问。 因此,它也被称为数据隐藏。
在Java中实现封装 -
- 将类的变量声明为私有的。
- 提供公共setter和getter方法来修改和查看变量值。
以下是一个演示如何在Java中实现封装的示例 -
/* 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;
}
}
公共setXXX()和getXXX()方法是EncapTest类的实例变量的接入点。 通常,这些方法被称为获取者和设置者。 因此,任何想访问这些变量的类都应该通过这些getter和setter来访问它们。
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.print("Name : " + encap.getName() + " Age : " + encap.getAge());
}
}
这将产生以下结果 −
Name : James Age : 20
封装的好处
- 类的字段可以是只读的或只写的。
- 一个类可以完全控制在其字段中存储的内容。
接口
Java 接口
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
接口与类相似点 -
- 一个接口可以有多个方法。
- 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
- 接口的字节码文件保存在 .class 结尾的文件中。
- 接口相应的字节码文件必须在与包名称相匹配的目录结构中。
接口与类的区别 -
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法。
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
声明接口
interface关键字用于声明一个接口。 这是一个简单的例子来声明一个接口 -
以下是一个interface的例子 -
/* File name : NameOfInterface.java */
import java.lang.*;
// Any number of import statements
public interface NameOfInterface {
// Any number of final, static fields
// Any number of abstract method declarations\
}
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
例子
/* File name : Animal.java */
interface Animal {
public void eat();
public void travel();
}
实现接口
当一个类实现一个接口时,您可以将该类视为签署合同,同意执行接口的特定行为。 如果一个类没有执行接口的所有行为,则该类必须声明自己为抽象。
一个类使用implements关键字来实现一个接口。 implements关键字出现在声明的extends部分之后的类声明中。
例子
/* File name : MammalInt.java */
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();
}
}
这将产生以下结果 −
Mammal eats Mammal travels
重写接口中声明的方法时,需要注意以下规则 -
- 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
- 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
- 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。
在实现接口的时候,也要注意一些规则 -
- 一个类可以同时实现多个接口。
- 一个类只能继承一个类,但是能实现多个接口。
- 一个接口能继承另一个接口,这和类之间的继承比较相似。
接口继承
一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。
下面的Sports接口被Hockey和Football接口继承 -
// Filename: Sports.java
public interface Sports {
public void setHomeTeam(String name);
public void setVisitingTeam(String name);
}
// Filename: Football.java
public interface Football extends Sports {
public void homeTeamScored(int points);
public void visitingTeamScored(int points);
public void endOfQuarter(int quarter);
}
// Filename: Hockey.java
public interface Hockey extends Sports {
public void homeGoalScored();
public void visitingGoalScored();
public void endOfPeriod(int period);
public void overtimePeriod(int ot);
}
Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类需要实现六个方法。
相似的,实现Football接口的类需要实现五个方法,其中两个来自于Sports接口。
接口的多继承
一个Java类只能扩展一个父类。 多重继承是不允许的。 但是,接口不是类,并且接口可以扩展多个父接口。
extends关键字仅使用一次,并且父接口在逗号分隔列表中声明。
例如,如果Hockey接口同时继承Sports和Event接口,它将被宣布为 -
public interface Hockey extends Sports, Event
标记接口
当父接口不包含任何方法时,会出现扩展接口的最常见用法。 例如,java.awt.event包中的MouseListener接口扩展了java.util.EventListener,该接口被定义为 -
package java.util;
public interface EventListener
{}
其中没有方法的接口被称为标记接口。 标记界面有两个基本的设计目的 -
- 建立一个公共的父接口
正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。 - 向一个类添加数据类型
这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。
包
Java 包
在Java中使用包是为了防止命名冲突,控制访问,简化类,接口,枚举和注释的搜索/定位和使用等。
包可以被定义为提供访问保护和名称空间管理的相关类型(类,接口,枚举和注释)的分组。
Java中的一些现有软件包是 -
- java.lang-打包基础的类
- java.io-包含输入输出功能的函数
程序员可以定义他们自己的软件包来捆绑一些类/接口等。将由您实现的相关类进行分组是一种很好的做法,以便程序员可以轻松地确定类,接口,枚举和注释是相关的。
由于该包创建了一个新的名称空间,因此不会有与其他包中的名称发生任何名称冲突。 使用包,提供访问控制更容易,而且找到相关类也更容易。
创建包
创建程序包时,应为程序包选择一个名称,并在包含要包含在程序包中的类,接口,枚举和注释类型的每个源文件的顶部包含一个包语句以及该名称。
包语句应该是源文件中的第一行。 每个源文件中只能有一个包语句,并且它适用于文件中的所有类型。
如果没有使用包语句,那么类,接口,枚举和注释类型将被放置在当前的默认包中。
要使用包语句编译Java程序,您必须使用-d选项,如下所示。
javac -d Destination_folder file_name.java
然后在指定的目标中创建一个具有给定包名的文件夹,编译后的类文件将被放置在该文件夹中。
例子
让我们看一个创建一个名为animals的包的例子。 使用小写字母的包名称是避免与类和接口名称冲突的良好习惯。
以下包示例包含名为animals的接口 -
/* File name : Animal.java */
package animals;
interface Animal {
public void eat();
public void travel();
}
现在,让我们在相同的包animals中实现上述接口 -
package animals;
/* File name : MammalInt.java */
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();
}
}
现在编译java文件,如下所示 −
$ javac -d . Animal.java $ javac -d . MammalInt.java
现在,将在当前目录中创建一个名为animals的包/文件夹,并将这些类文件放置在其中,如下所示。
您可以执行包中的类文件并获得如下所示的结果。
Mammal eats Mammal travels
import 关键词
如果一个类想要在同一个包中使用另一个类,则不需要使用包名。 同一个软件包中的类无需任何特殊的语法即可找到彼此。
这里,名为Boss的类被添加到已经包含Employee的工资包中。 然后,Boss可以引用Employee类而不使用工资单前缀,如以下Boss类所示。
package payroll;
public class Boss {
public void payEmployee(Employee e) {
e.mailCheck();
}
}
如果Employee类不在payroll包中会发生什么? Boss类必须使用以下其中一种技术来引用不同包中的类。
- 可以使用该类的全限定名称。 例如 −
payroll.Employee
- 软件包可以使用import关键字和通配符(*)导入。 例如 −
import payroll.*;
- 该类本身可以使用import关键字导入。 例如 −
import payroll.Employee;
注一个类文件可以包含任意数量的import语句。 import语句必须出现在包声明之后和类声明之前。 −
包的目录结构
类放在包中会有两种主要的结果 -
- 包名成为类名的一部分,正如我们前面讨论的一样。
- 包名必须与相应的字节码所在的目录结构相吻合。
下面是管理你自己 java 中文件的一种简单方式 -
将类、接口等类型的源码放在一个文本中,这个文件的名字就是这个类型的名字,并以.java作为扩展名。例如 -
// File Name : Car.java
package vehicle;
public class Car {
// Class implementation.
}
接下来,把源文件放在一个目录中,这个目录要对应类所在包的名字。
....\vehicle\Car.java
现在,正确的类名和路径将会是如下样子 -
- 类名 -> vehicle.Car
- 路径名 -> vehicle\Car.java (在 windows 系统中)
一般来说,公司使用其反向因特网域名作为其软件包名称。
示例 - 公司的Internet域名为apple.com,则其所有软件包名称均以com.apple开头。 包名称的每个组件都对应一个子目录。
示例 - 该公司有一个包含Dell.java源文件的com.apple.computers包,它将包含在一系列子目录中 -
....\com\apple\computers\Dell.java
在编译时,编译器会为每个类定义不同的输出文件,在其中定义接口和枚举。 输出文件的基本名称是该类型的名称,其扩展名是.class。
例子
// File Name: Dell.java
package com.apple.computers;
public class Dell {
}
class Ups {
}
现在,按如下所示使用-d选项编译此文件 -
$javac -d . Dell.java
这些文件将被编译如下 -
.\com\apple\computers\Dell.class .\com\apple\computers\Ups.class
您可以按如下方式导入\com\apple\computers\中定义的所有类或接口 -
import com.apple.computers.*;
与.java源文件一样,编译后的.class文件应位于一系列反映包名称的目录中。 但是,.class文件的路径不必与.java源文件的路径相同。 您可以单独安排源和目录目录,
<path-one>\sources\com\apple\computers\Dell.java <path-two>\classes\com\apple\computers\Dell.class
这样,你可以将你的类目录分享给其他的编程人员,而不用透露自己的源码。用这种方法管理源码和类文件可以让编译器和java 虚拟机(JVM)可以找到你程序中使用的所有类型。
类目录的绝对路径叫做 class path。设置在系统变量 CLASSPATH 中。编译器和 java 虚拟机通过将 package 名字加到 class path 后来构造 .class 文件的路径。
<path- two>\classes 是 class path,package 名字是 com.runoob.test,而编译器和 JVM 会在 <path-two>\classes\com\runoob\test 中找 .class 文件。
一个 class path 可能会包含好几个路径,多路径应该用分隔符分开。默认情况下,编译器和 JVM 查找当前目录。JAR 文件按包含 Java 平台相关的类,所以他们的目录默认放在了 class path 中。
设置 CLASSPATH 系统变量
用下面的命令显示当前的CLASSPATH变量 -
- In Windows → C:\> set CLASSPATH
- In UNIX → % echo $CLASSPATH
删除当前CLASSPATH变量内容:
- In Windows → C:\> set CLASSPATH =
- In UNIX → % unset CLASSPATH; export CLASSPATH
设置CLASSPATH变量:
- In Windows → set CLASSPATH = C:\users\jack\java\classes
- In UNIX → % CLASSPATH = /home/jack/java/classes; export CLASSPATH