1.继承
1.1 为什么需要继承
在使用类的时候,是将生活中的实物,抽象到代码中进行表示,在生活中,很多实物都是存在关联的,例如 哈士奇、中华田园犬、萨摩耶 都是狗,他们有共性信息,也有属于自己的特点
共同点:有一个头,四条腿,一个尾巴等等
不同点:哈士奇就是活泼好动,拆家小能手。中华田园犬性格温顺,独立能力强。萨摩耶“微笑天使”
继承 就可以将这些共同点提取出来作为父类,而不同点就可以基于 “父类” 派生出 “子类”,将不同的特点放到子类中。这样,就可以实现 “代码复用”
1.2 继承的概念
通过 “父类” 产生 “子类” 的过程,就可以称为 “派生”。
父类 派生出 子类
子类 继承自 父类
父类(parent class)又称 基类(base class)、超类(super class)
分别对应
子(孩子的子)类(child class)又称 派生类(derived class)子(子集的子)类(sub class)
1.3 继承的语法
首先要创造一个父类,与普通的类一样,在创建子类时借助 extends 关键字
语法格式:
修饰符 class 子类 extends 父类 {
//......
}
代码示例
//创建一个父类,用来表示动物
public class Animal {
private String name;//名字
private int age;//年龄
public void eat(String food){
//吃东西的方法
System.out.println(name + "正在吃" + food);
}
public void sleep(){
//睡觉的方法
System.out.println(name + "正在睡觉");
}
}}
//再定义一个类表示狗,继承自动物类
//这个时候 dog 就有了 animal 所有的属性和方法
//只需要再写出 dog 独有的属性和方法
public class Dog extends Animal {
public void bark(){
System.out.println("汪汪汪");
}
}
public class Test {
//通过 Test 类去测试前面所写的类
public static void main(String[] args) {
Dog dog = new Dog();//实例化dog
dog.eat("骨头");
dog.sleep();
dog.bark();
}
}
这样就实现了通过子类调用父类的属性和方法,接下来我们给动物的名称补充一下
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
dog.setName("大黄");
如果在叫声前加上 name ,就会发现编译错误
报错信息:“name” 在 “object2.Animal” 中拥有私有访问权限
这是 private 限制了该属性不能在类的外部被访问,即使是子类也不行,解决方案依旧是使用 getter 方法
1.4 父类成员方法
1.4.1 子类中访问父类的成员变量
子类和父类不存在同名成员变量
//父类
public class Animal {
public String name = "父类中的name";//名字
public void eat(String food){
System.out.println(name + "正在吃" + food);
}
}
//子类
public class Dog extends Animal {
public String name = "子类中的name"
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();//实例化dog
dog.eat("骨头");
System.out.println(dog.name);
}
}
//运行结果:
//父类中的name正在吃骨头
//子类中的name
这段代码中可以得到两个结论:
- 来自父类的方法调用的就是父类中的属性;
- 当子类和父类中有重名的属性,会优先调用子类的属性。
访问方法与属性类似,先看参数有无相同,依旧是子类先,父类后,都没有则报错
语法上使用了就近原则,在实际代码中不建议这样写,影响了代码的可读性
1.4.2 super 关键字
如果迫不得已出现了这种重名的情况,又想要直接访问父类,就可以通过 super 关键字指定要访问的父类的属性
this 指向当前对象的引用
super 指向当前对象内部的父类对象的引用
没有出现重名:
this 访问的属性就是父类,super 访问的属性还是父类
这里两者是一致的
出现重名的情况:
this 访问的就是子类的,super 访问的就是父类的
这里两者就不一致了
代码示例:
//父类
public class Animal {
public String name = "父类中的name";//名字
}
//子类
public class Dog extends Animal {
public String name = "子类中的name"
public void bark(){
//访问父类的属性
System.out.println(super.name + "汪汪汪");
//访问子类的属性
System.out.println(this.name + "汪汪汪");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();//实例化dog
dog.bark();
}
}
//运行结果:
父类中的name汪汪汪
子类中的name汪汪汪
注意事项:super 和 this 一样,不能在静态方法中使用
super 也可以调用方法,方法类似只放代码不作介绍
//父类
public class Animal {
public void bark(){
System.out.println("父类");
}
}
//子类
public class Dog extends Animal {
public void bark(){
super.bark();
System.out.println("子类");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();//实例化dog
dog.bark();
}
}
//运行结果:
父类
子类
super 还可以用来调用构造方法
在创建完父类和子类继承后,写上构造方法,这是会发现
编译报错:“object2.Parent” 中没有可用的默认构造函数
这是因为构造子类时需要先构造父类(父类中的属性被子类继承)
如果父类只有一个无参数的默认构造方法,这里是不会报错的(编译器会在子类自动添加super)
public class Parent {
public Parent(){
System.out.println("父类构造方法");
}
}
public class Child extends Parent{
public Child(){
System.out.println("子类的构造方法");
}
}
当父类构造方法带有参数时,就需要手动调用父类的构造方法并且传参
public class Parent {
public Parent(int x){
System.out.println("父类构造方法");
}
}
public class Child extends Parent{
public Child(){
super(10);
System.out.println("子类的构造方法");
}
}
}
注意事项:
- 这里的 super 的顺序必须在第一个,先构造了父类才可以再构造子类
- super 只能在子类构造方法中出现一次,且不能和 this 同时出现
1.5 访问修饰限定符-protected 关键字
protected 关键字,和 pubilc、private 是并列关系
在Java中访问控制权限的关键字有三个,权限等级分为四种
还有一种情况是什么都不写,用 default 来表示,不需要真的去写
代码示例:
因为涉及到不同包之间的操作,所以需要提前创好两个不同的包
接下来通过代码来测试限定符的权限
1.5.1 default
default 在当前包 类内部的使用(可以访问):
package object3.pakeage1;
public class Test {
//不使用任何修饰符来限制权限
//此时权限就是 default 级别
int x = 10;
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.x);
}
}
default 在同一包 中其他类使用(可以访问):
package object3.pakeage1;
public class Test2 {
public static void main(String[] args) {
Test t = new Test();
// default 这样的属性,是可以被同一个包中的其他类使用的
System.out.println(t.x);
}
}
default 在不同包中的使用(编译错误):
//跨包进行访问
//如果访问的类就在当前包中,就不需要 import
//如果是在其他包中就需要 import
import object3.pakeage1.Test;
public class Test3 {
public static void main(String[] args) {
Test t = new Test();
//已经超出 default 权限
System.out.println(t,x);
}
}
错误信息:
1.5.2 protected
protected 在当前包 类内部的使用(可以访问)
public class Test {
//使用 protected 来修饰
protected int x = 10;
public static void main(String[] args) {
Test t = new Test();
// default 级别的属性可以在类内部使用
System.out.println(t.x);
}
}
protected 在同一包 中其他类使用(可以访问):
public class Test2 {
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.x);
}
}
protected 在不同包的不相关类中使用(编译错误):
package object3.pakeage2;
import object3.pakeage1.Test;
public class Test3 {
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.x);
}
}
protected 在不同包的子类中使用:
package object3.pakeage2;
import object3.pakeage1.Test;
public class Test4 extends Test {
public static void main(String[] args) {
Test4 t4 = new Test4();
System.out.println(t4.x);
}
}
1.6 继承的方式
Java是一个单继承体系,只能有一个父类
单继承
public class A{
.....
}
public class B extends A{
.....
}
多层继承
public class A{
.....
}
public class B extends A{
.....
}
public class C extends B{
.....
}
不同类继承同一个类
public class A{
.....
}
public class B extends A{
.....
}
public class C extends A{
.....
}
多继承(Java不支持)
public class A{
.....
}
public class B{
.....
}
public class C extends A,B{
.....
}
1.7 final 关键字
主要作用:
1、使用 final 修饰变量后,变量不再可以被修改
2、使用 final 修饰类,该类就不可被继承
3、使用 final 修饰方法,该方法不能被子类重写
1.8 继承和组合
从设计的角度来看待 类 之间的关系就有如下两种
is - a 的关系 继承
动物 -> 狗 -> 哈士奇
见到一个哈士奇,就可以称它是 “一只狗” ,也可以是 “一只动物”
这里的核心就是 “是”
has - a 的关系 组合
猫 有 一个头,一个尾巴,有四条腿
这些肢体组成了这只猫
这里的核心就是 “有”
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合