1 面向对象的三大特征
- 封装
- 继承
- 多态
2 封装
- 封装(Encapsulation)的定义
在面向对象程序设计方法中,封装是指一种将抽象性函数接口的实现细节包装、隐藏起来的方法。
封装可以被认为是一个保护屏障,防止类的代码和数据被外部代码随机访问。
要访问类的代码和数据,必须通过严格的接口控制。
- 封装的优点
- 良好的封装能够减少耦合
- 类内部的结构可以自由修改,而不用修改那些调用类的代码
- 更改器方法可以完成错误检查。
如setSalary()
方法可以检查工资是否小于0。
实现Java封装的步骤
- 修改实例字段的可见性为
private
- 为
private
字段提供对外的公共方法,即根据需要提供访问器方法(getter)和更改器方法(setter)
- 修改实例字段的可见性为
不要编写返回可变对象引用的访问器方法,否则会破坏封装性,如下实例: ```java public class MyClass { public static void main(String[] args) {
User user = new User();
user.printPasswd(); //out: [1, 2, 3, 4]
int[] p = user.getPasswd();
p[0] = 999;
user.printPasswd(); //out: [999, 2, 3, 4]
} }
class User { private int[] passwd;
User() {
passwd = new int[]{1, 2, 3, 4};
}
public int[] getPasswd() {
return passwd;
}
public void printPasswd() {
System.out.println(Arrays.toString(passwd));
}
}
- 数组是可变对象,返回数组破坏了封装性。
上例中,在外部改变p就可以改变user对象的私有状态,因为`getPasswd()`方法的返回值和user的私有成员passwd引用的是同一个数组对象。
- **如果需要返回一个可变对象的引用,首先应该对它进行克隆(clone)。**
上述代码中的`return`语句应该改为`return (int[])passwd.clone();`
- `clone()`方法返回Object类型对象,需要进行强制类型转换。
- 一个方法可以访问**所属类的所有对象的私有数据**
```java
class Employee {
private String name;
...
public boolean equals(Employee other) {
return name.equals(other.name);
}
}
- 如在外部调用
if(employee.equals(boss)) ...
equals
方法不但访问了employee的私有字段,还访问了boss的私有字段。
3 继承
- 生活中的继承
- 兔子和羊属于食草动物类,狮子和豹属于食肉动物类。
- 食草动物和食肉动物又是属于动物类。
- 虽然食草动物和食肉动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特性。
继承的定义
- 从已有的类派生出新的类,称为继承。
- 继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
- 父类更通用,子类更具体。
- 子类不是父类的子集,子类一般比父类包含更多的数据域和方法。
- 继承需要符合的关系是:
**is-a**
,否则无法使用继承,如狮子is-a食肉动物类, - 父类也称为超类、基类;子类也称为次类、派生类、扩展类
继承实现的具体功能
- 子类可以访问父类非
**private**
的属性、方法。
- 子类可以访问父类非
子类还可以定义自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以重新实现父类的方法,称为方法的重写。
- 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
- 使用继承的意义
- 在不同的类中也可能会有共同的特征和动作,可以把这些共同的特征和动作放在一个类中,让其它类共享。因此可以定义一个通用类,然后将其扩展为其它多个特定类,这些特定类继承通用类中的特征和动作。
- 使用继承可以使得代码维护性提高、更加简洁、复用性提高
- 类的继承格式——extends关键字
在Java中通过extends
关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:
class father {
}
class son extends father {
}
- Java的继承类型
- Java的继承是单继承,不支持多继承,但是支持多重继承
单继承就是一个子类只能继承一个父类,多重继承就是,例如B类继承A类,C类继承B类,所以按照关系就是B类是C类的父类,A类是B类的父类,这是Java继承区别于C++继承的一个特性。
1 与继承相关的关键字
1 extends
子类使用该关键字实现单继承父类
- 由于所有的类都是继承于
java.lang.Object
,当一个类没有关键字extends
和implements
,则默认继承Object祖先类。
2 super
**super**
用于指代父类的对象实例本身,我们可以通过super
关键字来实现对父类成员的访问super
和this
一样,只能在非静态方法中使用- super可用于
- 调用父类的构造器
- 调用父类的方法
- 访问父类可以被访问的数据域(
**super**
无法访问**private**
字段)
- 实际使用中
**super**
主要用于调用父类的构造器;- 从父类继承的方法没必要使用
**super**
调用,使用**this**
也可以调用
如果子类重写了父类的方法,此时只有使用**super**
才能在子类中调用父类被重写的方法
- 一般不访问父类的数据域 ```java public class MyClass{ public static void main(String[] args) { Son son = new Son(); son.fun(); //out } }
class father { private String a;
public void setA(String a){
this.a = a;
}
public String getA(){
return a;
}
}
class Son extends father{ public void fun(){ super.setA(“父类的实例字段值”); System.out.println(super.getA()); } }
<a name="QE7pL"></a>
#### 3 final
`final`除了可以声明变量,令变量不能被修改,还能声明方法和类,对继承进行限制
- **修饰类:被**`**final**`**修饰的类不能被继承,即最终类;**
- **修饰方法:被**`**final**`**修饰的方法不能被子类重写:**
<br />
<a name="l0plV"></a>
#### 4 this
父类中的this实际上指代的是子类对象
```java
class Father{
void printClass(){
System.out.println(this.getClass());
}
}
class Son extends Father{ }
public class Main {
public static void main(String[] args) {
new Son().printClass(); // out: class Son
}
}
2 继承中的构造器
由于子类是不继承父类的构造器的,所以它需要调用父类构造器(隐式或显式)。
子类调用父类构造器的方式分为隐式和显式
- 显式
如果父类的构造器带有参数,则必须在子类的构造器中显式地通过**super**
关键字调用父类的构造器并配以适当的参数列表。
- `**super**`**语句必须是子类构造方法的第一条语句。**
- 隐式
如果父类构造器没有参数,则在子类的构造器中不需要使用super
关键字调用父类构造器,系统会自动调用父类的无参构造器。
- 在执行子类的构造函数前,总是会先执行父类中的构造函数,无论是显式还是隐式调用父类构造器
实例 ```java class SuperClass { private int n;
SuperClass() {
System.out.println("SuperClass()");
}
SuperClass(int n) {
System.out.println("SuperClass(int n):" + n);
this.n = n;
} }
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;
}
}
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 MyClass { 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); } }
- 上述代码的输出为
```bash
------SubClass------
SuperClass()
SubClass()
SuperClass(int n):300
SubClass(int n):100
------SubClass2------
SuperClass(int n):300
SubClass2()
SuperClass()
SubClass2(int n):200
3 继承中的访问控制
子类继承了父类的所有的东西(包括
**private**
成员),除了构造方法- 但是得到不等于可以随便使用:每个父类成员有不同的访问属性,子类通过继承得到了父类所有的成员,但是不同的访问属性使得子类在使用这些成员时有所不同
下表列出了不同访问属性的父类成员在子类中的访问属性,可以说控制由上到下越来越严格: | 父类成员访问属性 | 在父类中的含义 | 在子类中的含义 | | —- | —- | —- | | public | 对所有人开放 | 对所有人开放 | | protected | 只有包内其它类、自己和子类可以访问 | 只有包内其它类、自己和子类可以访问 | | 缺省 | 只有包内其它类可以访问 | 如果子类与父类在同一个包内:只有包内其它类可以访问,否则:相当于private,不能访问 | | private | 只有自己可以访问 | 不能访问 |
- 父类的
**public**
的成员直接成为子类的public
成员
- 父类的
父类的**protected**
的成员也直接成为子类的protected
成员
- 父类的缺省的未定义访问属性的成员是在父类所在的包内可见
如果子类不属于父类的包,那么在子类里面这些缺省属性的成员和private
的成员一样不可见。
- 父类的
**private**
的成员在子类里仍然是存在的,只是子类中不能直接访问,但是可以通过父类的方法访问 ```java public class MyClass { public static void main(String[] args) { SubClass subClass = new SubClass(); System.out.println(subClass.getN()); //out: 100 } }
class SuperClass { private int n = 100;
public int getN(){
return n;
}
} class SubClass extends SuperClass {
}
<a name="r4yPX"></a>
### 4 转型问题
- **Java中,父子对象之间的类型可以互相转换。**
父子类型转换分为了**向上转型**和**向下转型**,它们区别如下:
- **向上转型(常用)**
通过子类对象(具体)实例化父类引用(通用),这种属于**自动转换**
- **向下转型(使用较少)**
将指向子类对象的父类引用(通用)强制转换为子类引用(具体),这种属于**强制转换**
- 两种转换方法的核心是:**父类引用可以指向子类对象,但子类引用不可以指向父类对象。**如下转型实例:
```java
class Father {
}
class Son extends Father {
}
public class MyClass {
public static void main(String[] args) {
Father father = new Son(); //向上转型,father引用指向Son对象
Son son = (Son)father; //向下转型,son引用指向Son对象
Father father2 = new Father();
Son son2 =(Son)father2; //出错,son2引用不能指向Father对象
}
}
- 向上转型
向上转型就是将子类对象转为父类对象。此处父类对象可以是接口。
- 指向子类对象的父类引用会遗失子类特有的方法,仅保留子类与父类共有的方法。
- 如果子类重写了父类的方法,那么父类引用将调用子类的方法 ```java class Animal { public void eat(){ System.out.println(“animal eatting…”); } } class Bird extends Animal{ public void eat(){ System.out.println(“bird eatting…”); } public void fly(){ System.out.println(“bird flying…”); } }
public class MyClass { public static void main(String[] args) { Animal bird=new Bird(); //向上转型 bird.eat(); //out: bird eatting… not: animal eatting… bird.fly(); //报错,bird虽指向子类对象,但此时丢失子类独有的fly()方法 } }
- **向上转型的作用**
向上转型体现了Java的抽象编程思想
- 调用某个方法时,**当我们需要多个同父的对象作为参数时,通过向上转换,可以使参数统一**,否则有多少个子类就需要写多少个参数。
```java
class Human {
public void sleep() {
System.out.println("Human sleep..");
}
}
class Male extends Human {
@Override
public void sleep() {
System.out.println("Male sleep..");
}
}
class Female extends Human {
@Override
public void sleep() {
System.out.println("Female sleep..");
}
}
public class MyClass {
public static void main(String[] args) {
//传参时向上转型
doSleep(new Male()); //out: Male sleep..
doSleep(new Female()); //out: Female sleep..
}
//只需要写一个方法,参数声明为父类
public static void doSleep(Human h) {
h.sleep();
}
}
- 如果某个类实现了多个接口,可以利用这个类来实例化接口,这样使得接口引用只可以使用类的某个功能领域的方法。如
Queue
接口://Queue接口可以被实现了该接口的LinkedList类实例化
//queue只能使用由LinkedList实现的有关队列的方法
Queue<String> queue = new LinkedList<>();
- 向下转型与向下转型的作用
向下转型是将指向子类对象的父类引用强制转换为子类引用
- 向下转型是为了调用子类独有的方法
- 为了保证向下转型的顺利完成,需要通过使用Java提供的关键字
**instanceof**
来判断某对象引用指向的是否是某类的实例,如果是则返回true
,否则为false
```java class Animal { public void eat() { System.out.println(“animal eatting…”); } } class Bird extends Animal { public void eat() { System.out.println(“bird eatting…”); } public void fly() { System.out.println(“bird flying…”); } }
public class MyClass { public static void main(String[] args) { Animal animal = new Bird(); //向上转型 animal.eat(); //out: bird eatting…
//此时调用bird.fly()会报错
if (animal instanceof Bird) { //向下转型前,先使用instanceof判断
Bird bird = (Bird) animal; //向下转型
bird.fly(); //out: bird flying...
}
}
}
<a name="kjmgK"></a>
## 4 重写(Override)与重载(Overload)
- 方法重载是一个类中定义了多个**方法名相同**,而他们的参数列表不同,则称为方法的重载(Overloading)。
- 方法重写是在子类存在方法与父类的**方法名相同**,而且参数列表相同,返回值类型也相同的方法,就称为重写(Overriding)。
![](https://cdn.nlark.com/yuque/0/2021/png/1169704/1616227328190-5b2c77f6-afb7-49ac-a74b-2f12a67015c5.png#crop=0&crop=0&crop=1&crop=1&height=305&id=XCs5E&originHeight=305&originWidth=730&originalType=binary&ratio=1&rotation=0&showTitle=false&size=0&status=done&style=none&title=&width=730)
- **何时使用重载与重写**
- 需要同样的一个方法能够根据输入数据的不同,做出不同的处理,就要重载
- 当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,就要重写父类方法
<a name="fFtnC"></a>
### 1 重写
- **重写的定义**
重写(Override)是子类对**继承自父类的方法**的实现过程进行重新编写<br />**只改变方法的实现,不改变方法的外观称为重写**(除了static方法)
- **重写的好处在于**子类能够根据需要实现父类的方法。
- **重写实例**
```java
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
//重写父类Animal的方法,外观(即参数和返回值)必须与父类方法相一致
public void move(){
System.out.println("狗可以跑和走");
}
}
public class MyClass {
public static void main(String[] args) {
Animal a = new Animal(); //Animal对象
Animal b = new Dog(); //Dog对象(向上转型)
a.move();//执行Animal类的move()方法
b.move();//执行Dog类的move()方法
}
}
上述代码输出
动物可以移动
狗可以跑和走
在上面的例子中可以看到,尽管b属于Animal类型,但是它运行的是Dog类的
move()
方法。
这是由于在编译阶段,只是检查参数的引用类型。然而在运行时,Java虚拟机(JVM)指定变量引用的对象的类型并且运行该对象的方法。
因此在上面的例子中,之所以能编译成功,是因为Animal类中存在**move()**
方法,然而运行时,运行的是特定对象的方法。
- 重写的规则
- 重写方法的参数列表与被重写方法的参数列表必须完全相同(参数列表如果不同就是重载了)。
重写方法的返回类型与被重写方法的返回值类型可以不相同,但是如果不同,重写方法的返回值类型必须是父类返回值类型的子类。
构造方法不能被重写。
重写可以改变访问修饰符
访问权限不能比父类中被重写的方法的访问权限更严格。
例如:如果父类的一个方法被声明为public
,那么在子类中重写该方法就不能声明为protected
重写不能改变非访问修饰符
由于父类的
private
方法不能被子类继承,因此子类定义与父类**private**
方法重名的方法不是重写,而是定义了一个新的方法,不需要遵循重写的规则。class Animal{
private void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
//声明的新方法,外观随便定义,与父类的private方法无关
public int move(int cc){
System.out.println("狗可以跑和走");
return cc;
}
}
父类中声明为
**final**
的方法不能被重写,也不能重新声明。
父类中声明为**static**
的方法也不能被重写,但是可以重新声明一个**static**
方法
class Animal {
public static void move() {
System.out.println("father");
}
public final void kick() {}
}
class Dog extends Animal {
//不是重写,而是声明了一个与父类无关的方法
public static void move() {}
//报错
public final void kick() {}
}
public class MyClass {
public static void main(String[] args) {
Animal a = new Dog(); //Dog对象
a.move(); //out: father,印证了子类的move()不是重写
}
}
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为
**private**
、**final**
和**static**
的方法。
子类和父类不在同一个包中,除了上述不能重写的方法外,也不能重写缺省访问属性的方法,因为这些方法此时相当于**private**
方法
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常,也可以不抛出异常,即使被重写的方法抛出了异常
但是,重写的方法不能抛出新的强制性异常,只能抛出相同的强制性异常或强制性异常的子类异常
例如: 父类的一个方法申明了一个检查异常IOException
,但是在重写这个方法的时候不能抛出 Exception
异常,因为Exception
是IOException
的父类,只能抛出IOException
的子类异常。
- 当需要在子类中调用父类的被重写方法时,需要使用
super
关键字。
2 重载
- 重载的定义
重载(Overload) 是在同一个类里面或者在子类中,定义名字相同,而参数列表不同的方法。
- 重载最常用的地方就是构造器的重载
- 如果父类的方法被子类继承,那么也是可以被重载的
- 重载规则
- 重载的方法参数列表必须不同(参数个数或类型或顺序不同);
- 重载的方法返回类型可以不同。
- 重载的方法访问修饰符和非访问修饰符都可以不同;
- 重载的方法可以声明新的或更广的强制性异常;
3 重写与重载的区别
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 可以修改,但仅限于返回值类型的子类 |
异常 | 可以修改 | - 可以减少或删除异常 - 可以抛出新的非强制性异常 - 不能抛出新的或者更广的强制性异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
5 抽象类和抽象方法
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能被实例化为对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
- 由于抽象类不能被实例化为对象,所以抽象类必须被继承后才能使用。
也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
- Java使用
**abstract**
关键字来声明抽象类和抽象方法
1 抽象类
定义抽象类
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 int getNumber() {
return number;
}
}
- Employee类与普通的类相比没有什么不同,它有3个成员变量,7个成员方法和1个构造方法,同时,并不包含抽象方法。
但是Employee类无法被实例化为对象:
public class MyClass {
public static void main(String[] args) {
//下方代码报错
Employee e = new Employee("George W.", "Houston, TX", 43);
}
}
继承抽象类
继承自Employee类的Salary类
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 double computePay() {
System.out.println("Computing salary pay for " + getName());
return salary / 52;
}
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;
}
}
}
- 我们不能实例化一个Employee类的对象,但是我们可以实例化一个继承自Employee类的Salary类对象,该对象将从 Employee类继承7个成员方法,重写了其中2个,然后又新定义了2个成员方法。
执行下述代码
public class MyClass {
public static void main(String[] args) {
Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00);
System.out.println("\nCall mailCheck using Employee reference--");
e.mailCheck();
}
}
输出结果为
Constructing an Employee
Call mailCheck using Employee reference--
Within mailCheck of Salary class
Mailing check to John Adams with salary 2400.0
2 抽象方法
如果抽象类内的某个方法只能由其子类实现,那么需要在抽象类中声明该方法为抽象方法。
- 对于一个父类,如果它的某个方法在父类中实现出来没有任何意义,必须根据子类的实际需求来进行不同的实现,那么就可以将这个方法声明为抽象方法,此时这个类也就成为抽象类了。
抽象方法必须使用
**abstract**
关键字声明
抽象方法没有定义实现,所以方法名小括号后面直接跟一个分号,而不是花括号。
abstract class Employee
{
private String name;
private String address;
private int number;
public abstract double computePay(); //抽象方法
...
}
抽象方法使用规则
任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
抽象方法必须为
**public**
或者**protected**
,建议显式声明为public
抽象方法必须用
**abstract**
显式声明子类重写父类的抽象方法时,访问控制不能更严格
- 构造方法,类方法(用
**static**
修饰的方法)不能声明为抽象方法。
- 继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。
最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象(因为都是抽象类了)。
例如:Salary类继承了上面的Employee类,那么它必须实现
computePay()
方法:class Salary extends Employee
{
private double salary;
public double computePay(){
System.out.println("Computing salary pay for " + getName());
return salary/52;
}
...
}
6 接口
- 接口(interface)定义
- 接口是一个抽象类型,是抽象方法的集合,接口以
interface
关键字来声明。
- 接口是一个抽象类型,是抽象方法的集合,接口以
一个类通过实现(implements
)接口来实现接口的抽象方法。
- 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。
类描述对象的属性和方法;接口则包含类要实现的方法。
- 接口内可以包含变量和方法
- 接口无法被实例化,但是可以被类实现。
- 接口变量
接口类型可用来声明一个变量,变量将成为一个空指针,或是引用实现此接口的对象。
public class MyClass {
public static void main(String[] args) {
Animal dog = new Dog(); //接口类型变量
}
}
interface Animal {
void eat();
}
class Dog implements Animal {
public void eat() {}
}
1 接口和接口内方法的声明
- 接口的声明
接口是隐式抽象的,当声明一个接口的时候,不必使用abstract
关键字。
接口的声明语法格式如下:
[访问控制] interface 接口名称 [extends 其他的接口名] {
// 声明变量
// 声明抽象方法
}
**interface**
关键字用来声明一个接口。(相对应的,class
关键字用来声明类)interface Interface{} //声明一个接口
class Class{} //声明一个类
- 接口内方法的声明
接口内的方法和抽象方法一样,没有定义实现,方法名后直接是冒号
interface Animal {
void eat();
void travel();
}
- 接口中的方法默认是
**public abstract**
,不需要再额外修饰
在接口中,也可以使用**default**
和**static**
修饰方法(这也就意味着接口中可以不止有抽象方法)
- static
在Java11前,通常将接口需要用到的静态方法放在伴随类中,
因此在标准库中有很多成对出现的接口和伴随工具类,如Collection
/Collections
、Path
/Paths
等
在Java11中,Path接口提供了与伴随类等价的静态方法,这样Paths类就不再是必须的了
因此在实现自己的接口时,也没必要再为工具方法提供另外一个伴随类。
- default
- 可以在接口中声明一个
default
修饰的方法,这样做的一个重要的作用就是接口演化。 - 比如在Java8中为Collection接口增加了一个
stream()
方法,如果stream()
不是默认方法,那么很多实现了该接口的类将不能通过编译,因为没有实现新增的方法。
- 可以在接口中声明一个
- 接口内的变量
接口内可以声明变量,默认是**public static final**
,也只能是**public static final**
2 接口的实现
- 类实现接口
类使用implements
关键字实现接口,位置与继承的关键字extends
相同
- 实例 ```java interface Animal { void eat(); void travel(); }
class MammalInt implements Animal { public void eat() { System.out.println(“Mammal eats”); } public void travel() { System.out.println(“Mammal travels”); } }
public class MyClass { public static void main(String[] args) { MammalInt m = new MammalInt(); m.eat(); //out: Mammal eats m.travel(); //out: Mammal travels } }
- **类实现接口的规则**
- **一个实现接口的类,必须实现接口内所描述的所有方法**,否则就必须声明为抽象类。
- **一个类可以实现多个接口**
- **实现接口的类不能继承接口的静态方法,但是继承抽象类的类可以继承抽象类的静态方法**
Java支持实现多个接口,所以如果实现接口的类可以继承接口的静态方法,那么类所实现的多个接口出现同名静态方法时,编译器就不知道要调用哪个方法了
- **重写接口中声明的方法时,需要注意以下规则:**
- 类在实现接口的方法时,**不能抛出强制性异常**,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
- 类在重写方法时**必须要保持一致的方法名、一致的参数列表、相兼容的返回值类型**。
<a name="2D16W"></a>
### 3 接口的继承
- **接口的继承**
- **一个接口能继承另一个接口**,和类之间的继承方式比较相似,同样使用`extends`关键字
- **实现一个接口时,同样需要实现其父接口的方法**
- 下面的`Sports`接口被`Hockey`和`Football`接口继承:
```java
interface Sports {
void setHomeTeam(String name);
void setVisitingTeam(String name);
}
interface Football extends Sports {
void homeTeamScored(int points);
void visitingTeamScored(int points);
void endOfQuarter(int quarter);
}
interface Hockey extends Sports {
void homeGoalScored();
void visitingGoalScored();
void endOfPeriod(int period);
void overtimePeriod(int ot);
}
Hockey
接口自己声明了四个方法,从Sports
接口继承了两个方法,这样,实现**Hockey**
接口的类实际上需要实现六个方法。
相似的,实现Football
接口的类需要实现五个方法,其中两个来自于Sports
接口。
- 接口的多继承
类的多继承是不合法,但接口允许多继承。
在接口的多继承中**extends**
关键字只需要使用一次,在其后跟着继承接口。
public interface Hockey extends Sports, Event
4 标记接口
- 没有任何方法的接口被称为标记接口。
例如:java.awt.event
包中的MouseListener
接口继承的java.util.EventListener
接口定义如下
public interface EventListener{}
- 实现标记接口的目的
- 给实现标记接口的类一个标签,表明类属于一个特定的类型
常使用**instanceof**
进行类型查询,进而判断类是否实现了某些接口
interface Animal { }
class Dog implements Animal{ }
public class MyClass {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog instanceof Animal); //out: true
}
}
- 建立一个公共的父接口
正如EventListener
接口,这是被几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener
接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。
7 抽象类和接口的区别
应用场景的不同
- 抽象类是对一类事物的抽象,即对类抽象,而接口是对行为的抽象。
抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
实例
飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane
,将鸟设计为一个类Bird
,但是不能将飞行这个特性也设计为类,因为它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将飞行设计为一个接口Fly
,包含方法fly( )
,然后Airplane
和Bird
分别根据自己的需要实现Fly
这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane
即可,对于鸟也是类似的,不同种类的鸟直接继承Bird
类即可。
从这里可以看出,类继承是一个 “是不是”的关系,而接口实现则是 “有没有”的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
设计层面不同
抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。
模板式设计
什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和 ppt C公共的部分就是模板 A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。
- 辐射式设计
辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。
- 更新
- 对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;
- 对于接口,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
8 多态
对于接口List<>来说,其方法get()在ArrayList和LinkedList的实现下,内部机制完全不同,这就是多态的具体展现
- 多态的定义
多态是同一个行为具有多个不同表现形式或形态的能力。
例如:
现实中,比如我们按下 F1 键这个动作:
如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
如果当前在 Word 下弹出的就是 Word 帮助;
在 Windows 下弹出的就是 Windows 帮助和支持。
多态一般分为重写式多态和重载式多态。在面向对象编程中,主要讲的是重写式多态。
有的人认为重载不算作多态,而且重载不是面向对象编程特有的。
- 重写式多态
重写式多态是通过动态绑定技术来实现,是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
也就是说,只有程序运行起来,你才知道调用的是哪个子类的方法。
多态的优点
- 消除类型之间的耦合关系
- 可替换性、可扩充性
- 接口性、灵活性、简化性
多态的必要条件
- 继承类/抽象类 或 实现接口
- 重写
- 向上转型(父类引用指向子类对象
**Parent p = new Child()**
)
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。