继承
继承的概念
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
为什么要继承
当多种类型具有相同的属性或行为时,比如吃喝拉撒。如果不适用继承,那么每种类型都要的创建这些相同的属性和方法,造成代码冗余。使用继承可以将各个类型相同的属性和方法提取出来组成一个父类,让这些类继承这个父类也就继承了这些属性和方法,避免了代码冗余,也利于后期维护。
继承格式
class 父类 {
}
class 子类 extends 父类 {
}
继承类型
注意:不支持多继承。一个类不能同时继承多个父类;如果继承多个父类,若父类中有相同的方法或者属性名且各个实现的功能不同,在子类调用时该方法或属性时,会出现歧义。
super,this 和 final关键字
super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
在子类的构造器中,会自动隐式的调用父类的无参构造;如果想要调用父类的有参构造器,子类构造器中需要显示的通过super以及参数列表来调用父类的有参构造。
this关键字:指向自己的引用。
final 关键字:声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写;修饰实例变量,即被定义为常量不能不修改。
注意:被声明为 final 类的方法自动地声明为 final,但是实例变量并不是 final。
访问权限
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
-
default : 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
-
private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
-
public : 对所有类可见。使用对象:类、接口、变量、方法
-
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
我们可以通过以下表来说明访问权限:
修饰符 | 当前类 | 同一包内 | 同包内(子孙类) | 不同包内(子孙类) | 其他包 |
---|---|---|---|---|---|
public | Y | Y | Y | Y | Y |
protected | Y | Y | Y | Y/N | N |
default | Y | Y | Y | N | N |
private | Y | N | N | N | N |
其中protected较难理解,需要从以下两个点来分析说明:
-
子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;
-
子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法(注意:子类中,子类自身的实例才能访问,其他子类的实例不能),而不能访问基类实例的protected方法。
实例
package p1;
public class Father1 {
protected void f() {} // 父类Father1中的protected方法
}
package p1;
public class Son1 extends Father1 {}
package p11;
public class Son11 extends Father1{}
package p1;
public class Test1 {
public static void main(String[] args) {
Son1 son1 = new Son1();
son1.f(); // Compile OK ----(1)
son1.clone(); // Compile Error ----(2)
Son11 son = new Son11();
son11.f(); // Compile OK ----(3)
son11.clone(); // Compile Error ----(4)
}
}
对于上面的示例,首先看(1)(3),其中的f()方法从类Father1继承而来,其可见性是包p1及其子类Son1和Son11,而由于调用f()方法的类Test1所在的包也是p1,因此(1)(3)处编译通过。其次看(2)(4),其中的clone()方法的可见性是java.lang包及其所有子类,对于语句"son1.clone();“和"son11.clone();”,二者的clone()在类Son1、Son11中是可见的,但对Test1是不可见的,因此(2)(4)处编译不通过。
判断外包protected方法可见性:
class A{
}
Class B{
public static void main(String[] args) {
A a = new A();
a.method(); //method是受保护的
}
- 对象a的method方法是否是继承来的;
- 如果不是继承,而是自己建的,或者重写过的;则不同于A所在包不可见。
- 如果是继承C类而继承的方法,那么该方法的可见性:与C类同包的,以及C的子类可见。
如何判断method方法所继承的父类:
package p6;
class MyObject6 extends Test6{}
public class Test6 {
public static void main(String[] args) {
MyObject6 obj = new MyObject6();
obj.clone(); // Compile OK -------(1)
}
}
clone()方法时Object类的,Test6 和MyObject6 都是继承与Object的,那么obj.clone() 是继承的Test6的clone()还是Object的clone()呢?
- 如果obj.clone()使用在 class MyObject6 {} 或 class Test6{} 中,那么clone()是从Test6 继承来的;
- 如果obj.clone()使用在其他类中,继承自Object。
更多实例参考:https://www.runoob.com/w3cnote/java-protected-keyword-detailed-explanation.html
继承的特性
-
子类拥有父类非 private 的属性、方法。
-
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
-
子类可以用自己的方式实现父类的方法。
-
Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 A 类继承 B 类,B 类继承 C 类,所以按照关系就是 C 类是 B 类的父类,B 类是 A 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
-
提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
接口
接口(Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
接口与类的区别
接口不能用于实例化对象。
接口没有构造方法。
接口中所有的方法必须是抽象方法。
接口不能包含成员变量,除了 static 和 final 变量。
接口不是被类继承了,而是要被类实现。
接口支持多继承。
接口特性
接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
注:JDK 1.8 以后,接口里可以有静态方法和方法体了。
抽象类和接口的区别
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
重写接口中声明的方法时,需要注意以下规则:
- 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
- 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
- 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。
在实现接口的时候,也要注意一些规则:
- 一个类可以同时实现多个接口。
- 一个类只能继承一个类,但是能实现多个接口。
- 一个接口能继承另一个接口(或多个),这和类之间的继承比较相似。