继承的概念和特点
不同的类中可能会有重复的代码。
我们抽取一个公共的父类:
这样子类继承了父类后,只需要写自己不同的代码即可:
特点:
- 利于代码复用
- 缩短开发周期
继承:
- 一种类与类之间的关系
- 使用已存在的类的定义作为基础建立新类
- 已存在的类:父类(基类)
- 新类:子类(派生类)
- 新类的定义可以增加新的数据或功能,也可以用父类的功能,但不能选择性地基础父类
继承的实现
关键字:extends
语句:子类名称 extends 父类名称
一个子类只能继承一个父类。
子类只能继承父类中非私有的成员。
子类自己的私有成员,它的兄弟类是无法访问的。
如:
Animal类中有private
的name
属性和public
的getName
方法,则当Cat类继承了Animal类,它只能调用getName
,而不能直接访问name
。
用eclipse自带的快捷键继承:在Choose a type
中写上我们想要继承的类即可。
则新建的类的自带的语句:
public class Dog extends Animal {
}
Animal类:
package com.animal;
public class Animal {
private String name;//昵称
private int month;//月份
private String species;//品种
public Animal() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name=name;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month=month;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species=species;
}
public void eat() {
System.out.println(this.getName()+" is eating!");
}
}
Cat类:
package com.animal;
public class Cat extends Animal{
private double weight;//体重
public Cat() {
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public void run() {
System.out.println("品种:"+this.getSpecies()+" "+this.getName()+" is running!");
}
}
Dog类:
package com.animal;
public class Dog extends Animal {
private String sex;//性别
public Dog() {
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public void sleep() {
System.out.println("月份:"+this.getMonth()+" "+this.getName()+" is sleeping");
}
}
测试类:
public static void main(String[] args) {
// TODO Auto-generated method stub
Cat one = new Cat();
one.setName("A");
one.setSpecies("中华田园猫");
one.eat();
one.run();
System.out.println("-----------------------");
Dog two = new Dog();
two.setName("B");
two.setMonth(1);
two.eat();
two.sleep();
}
输出:
A is eating!
品种:中华田园猫 A is running!
-----------------------
B is eating!
月份:1 B is sleeping
方法的重写
关于方法重载:
- 同一个类中
- 方法名相同,参数列表不同(参数顺序,个数,类型)
- 方法返回值,访问修饰符任意
- 与方法参数名无关
关于方法重写:
- 在子类中定义,重写父类的方法
- 方法名,参数列表(参数顺序,个数,类型)要与父类完全一致
- 当父类的方法返回值是void或基本数据类型时,子类的要与父类的完全相同;若是引用类型时,子类的返回值可以是父类或是它的子类。
父类Animal中有:
public void eat() {
System.out.println(this.getName()+" is eating!");
}
在子类Dog中进行方法的重写:
public void eat() {
System.out.println(this.getName()+"自己的eat");
}
在测试类中调用:
Dog two = new Dog();
two.eat();
输出:
B自己的eat
一个关于返回值不同的方法重写
在父类Anmial中有一个void类型的jump方法:
public void jump() {
System.out.println("jump!");
}
在子类Dog中写一个int类型的jump方法:
public int jump() {
System.out.println("Dog Jump!");
return 0;
}
则:报错:这个返回值类型与父类Animal中的jump 不兼容 。
但是,若把父类中的void改成int,Dog类中的jump方法就不会报错了。
那么这是否意味着子类的方法重写的返回值一定要与父类的相同呢?我们这里再做一个测试:
令父类的jump方法返回值为Animal
:
ps:这样写只为了测试代码语法是否合法,这段代码其实本身没有逻辑。
public Animal jump() {
System.out.println("jump!");
return new Animal();
}
子类Dog的jump也改成返回值为Animal
的:
public Animal jump() {
System.out.println("Dog Jump!");
return new Animal();
}
确实没有报错!
我们再把子类Dog的jump改成返回值为Dog
的——也没有报错!
改成Cat
:
都没有报错!
原因:
若父类的方法返回值类型是基本数据类型或void时,则子类的方法返回值要与父类的相同。
若父类的方法返回值类型是引用数据类型,则子类的方法返回值可以是其引用数据类型的父类或子类。
注意:在子类中,可以定义与父类重名的属性。
访问修饰符的分类及作用
- 公有:public
- 私有:private
- 受保护的:protected
- 默认
我们从本类、同包、子类、其他 这四个方面来认识一下上述的访问修饰符。
private
private只允许在本类中进行访问。
如:父类Animal中有private类型的三个属性:
private String name;//昵称
private int month;//月份
private String species;//品种
我们在子类中定义一个Animal类的对象,然后通过对象调用属性——会发现调用不到Animal类的属性。
public
public允许在任意位置进行访问。
protected
- 允许在当前类访问
- 允许在同包子类中访问
- 允许挎包子类中访问
- 允许同包非子类中访问
- 不允许挎包非子类访问
即:
- 允许当前类
- 允许同包
- 允许子类
- 不允许挎包非子类
访问。
——允许在当前类访问:类内属性为protected,且其getter类和setter类没有报错:
——允许在同包子类中访问:在同包的子类Cat中可以调用到Animal的属性name
、month
、species
——允许挎包子类中访问:把Cat移动到另一个包下,也可以访问。
——不允许挎包非子类访问:在另一个包中的test类(非子类)定义Animal对象,发现访问不到protected的属性:
——允许同包非子类中访问:把test挪到同包下,就可以访问。
默认
允许在当前类、同包子类/非子类调用;
不允许挎包子类/非子类调用。
即:
- 允许当前类
- 允许同包
- 不允许挎包
访问。
总结
访问修饰符对方法重写的影响
当子类进行方法重写时,访问修饰符允许改变:子类的重写的方法的访问范围要大于等于父类的访问范围。
举个例子:
父类:public
子类:只能是public
父类:protected
子类:可以protected或public
即,子类的访问权限只能比父类的大(等)。
无端联想:父母希望孩子比自己更好,而不是更差。
继承的初始化顺序
父类Animal:各种访问权限的属性,各种访问权限的静态属性,代码块与构造方法。
public class Animal {
private String name="A";//昵称
protected int month=1;//月份
String species="中华田园猫";//品种
public int temp=15;
private static int st1=22;
public static int st2=23;
//静态代码块
static {
System.out.println("我是父类的静态代码块!");
}
//构造代码块
{
System.out.println("我是父类的构造代码块!");
}
public Animal() {
System.out.println("我是父类的无参构造方法!");
}
}
子类Cat:
public class Cat extends Animal{
private double weight=6;//体重
public static int st3=44;
public int temp=12;
//静态代码块
static {
System.out.println("我是子类的静态代码块!");
}
//构造代码块
{
System.out.println("我是子类的构造代码块!");
}
public Cat() {
System.out.println("我是子类的无参构造方法!");
}
}
测试类语句:
Cat one=new Cat();
System.out.println(one.temp);
输出:
我是父类的静态代码块!
我是子类的静态代码块!
我是父类的构造代码块!
我是父类的无参构造方法!
我是子类的构造代码块!
我是子类的无参构造方法!
12
调试一下,得知执行顺序:
- 加载父类:执行父类的静态赋值语句和静态代码块
- 加载子类:执行子类的静态赋值语句和静态代码块
- ——类在进行加载的时候会优先加载父类的静态信息
- 在new语句会先找父类的构造方法(然后找父类的父类——
object
类),再找子类 - 执行输出temp语句
ps:object是所有类的父类。
执行顺序:子类——父类——父类的父类…
省流:继承后的初始化顺序
- 父类静态成员
- 子类静态成员
- 父类对象构造
- 子类对象构造
对于静态的赋值语句和静态代码块:
访问修饰符不影响成员加载顺序,跟书写位置有关
即:这里是赋值语句先加载,静态代码块后加载。
public static int st2=23;
//静态代码块
static {
System.out.println("我是父类的静态代码块!");
}
而:这里是静态代码块先加载,赋值语句后加载。
//静态代码块
static {
System.out.println("我是父类的静态代码块!");
}
public static int st2=23;
super关键字
super:父类对象的引用。
可以通过super去访问父类可以被子类派生的任意成员。
子类如果想调用父类的构造方法——直接调用会报错:Animal不是Cat类的方法:父类的构造不允许被继承、不允许被重写、但会影响子类的实例化过程
若父类有两个构造方法(带参,无参),子类也有两个构造方法(带参,无参),那么在子类实例化的时候会如何选择呢?
父类:
public Animal() {
System.out.println("父类-无参构造");
}
public Animal(String name,int month) {
System.out.println("父类-双参构造");
}
子类:
public Cat(String name,int month) {
System.out.println("子类-双参构造");
}
public Cat() {
System.out.println("子类-无参构造");
}
测试类语句:Cat one=new Cat("A",2);
输出:
父类-无参构造
子类-双参构造
原因:
在子类构造方法中,没有显式标注的时候,默认调用父类的无参构造方法。
因此,若我们把父类的无参构造方法注释掉,则子类的构造方法会直接报错:Animal的无参构造方法并没有被定义
如何在子类构造方法中调用父类的带参构造?
答案:super
如果想调用双参的父类构造函数,则:super(name,month);
传入参数即可,如:
public Cat(String name,int month) {
super(name,month);
System.out.println("子类-双参构造");
}
测试类:Cat one=new Cat("A",2);
输出:
父类-双参构造
子类-双参构造
若没有传入参数,super()
:
public Cat(String name,int month) {
super();
System.out.println("子类-双参构造");
}
则输出:
父类-无参构造
子类-双参构造
注意:构造方法的调用必须放在构造方法里。
如:若想在其他方法中用super调用构造函数——报错:
总结:
代表父类引用:
- 访问父类成员方法
super.print()
- 访问父类属性
super.name
- 访问父类构造方法
super()
- 子类构造必须调用父类构造方法
- 若子类的构造方法中没有显式标注,则系统默认调用父类无参构造放啊
- 可以通过super()调用父类允许被访问的其他构造方法
- super()必须放在子类构造方法有效代码第一行
super VS this
构造方法调用时,super和this不能同时出现
总结
继承
- 一种类与类之间的关系
- 使用已存在的类的定义作为基础建立新类
- 新类的定义可以增加新的数据或功能,也可以用父类的功能,但不能选择性地继承父类
- 满足 A is a B的关系就可以继承,如:学生是人,老师是人,则学生和老师都可以继承 人这一父类 。
特点
- 利于代码复用
- 缩短开发周期
语法
- 使用
extends
实现封装 - 单一继承,只能有一个父类
- 子类也叫派生类
- 父类也叫基类,超类
如:
class Dog extends Animal{
//子类特有的属性和方法
}
继承后的初始化顺序
- 父类静态成员
- 子类静态成员
- 父类对象成员
- 子类对象成员
通过super关键字访问父类相关成员
- 访问父类成员方法
super.print()
- 访问父类属性
super.name
- 访问父类构造方法
super()
super
- 子类构造必须调用父类构造方法
- 若子类的构造方法中没有显式标注,则系统默认调用父类无参构造放啊
- 可以通过super()调用父类允许被访问的其他构造方法
- super()必须放在子类构造方法有效代码第一行
- 如果子类构造方法中既没有显式标注,而父类有没有无参构造方法,则编译出错
super VS this
- super 父类的引用
- this 当前对象的引用
- 构造方法调用时,super和this不能同时出现
方法重写 VS 方法重载
访问修饰符