Java 继承
Java 继承
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
1 实现继承的格式
- 继承通过extends实现
- 格式:
class 子类 extends 父类 { }
2 继承的特性
- 子类拥有父类非 private 的属性、方法。
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
- Java 的继承是单继承,但是可以多重继承。
- 单继承就是一个子类只能继承一个父类
- 多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
- 错误范例:
class A extends B, C { }
- 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
package com.fcarey.extend;
public class Father {
public void sayHello() {
System.out.println("Father, says: Hello World");
}
}
package com.fcarey.extend;
public class Son extends Father{
public void hey(){
System.out.println("Son says: hey world");
}
}
package com.fcarey.extend;
public class Demo {
public static void main(String[] args) {
Father f = new Father();
Son s = new Son();
f.sayHello();
s.sayHello();
s.hey();
}
}
3 继承关键字
3.1 super 与 this 关键字
super 关键字:我们可以通过 super 关键字来实现对父类成员的访问,用来引用当前对象的父类。
this 关键字:指向自己的引用,引用当前对象,即它所在的方法或构造函数所属的对象实例。
this(…)
与super(…)
必须放在构造方法的第一行有效语句,并且二者不能共存
package com.fcarey.extend;
public class Father {
public void sayHello() {
System.out.println("Father, says: Hello World");
}
public void hey(){
System.out.println("Father, hey son, your turn.");
}
}
package com.fcarey.extend;
public class Son extends Father{
public void hey(){
System.out.println("Son says: hey world");
}
public void hey2(){
super.hey();
this.hey();
}
}
package com.fcarey.extend;
public class Demo {
public static void main(String[] args) {
Son s = new Son();
s.hey2();
}
}
3.2 final 关键字
-
final 可以用来修饰变量(包括类属性、对象属性、局部变量和形参)、方法(包括类方法和对象方法)和类。表明该变量是一个常量,不能再次赋值
- 若变量是基本类型,则不能改变的是值
- 若变量是引用类型,则不能改变的是地址值,但地址里面的内容是可以改变的
-
使用 final 关键字声明类,就是把类定义定义为最终类,不能被继承,或者用于修饰方法,
-
该方法不能被子类重写(不能有子类,但是可以有父类)
-
声明类:
final class 类名 {//类体}
-
声明方法:
修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}
注: final 定义的类,其中的属性、方法不是 final 的。
package com.fcarey.MyFinal;
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
package com.fcarey.MyFinal;
public class Demo {
public static void main(String[] args) {
final Student s = new Student("s1", 23);
s = new Student("s1", 24); // 错误
s.setAge(24); // 正确
System.out.println(s.getName());
System.out.println(s.getAge());
}
}
4 继承中构造方法的访问特点
-
如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
-
子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化,原因在于,每一个子类构造方法的第一条语句默认都是:super()
-
问题:如果父类中没有无参构造方法,只有带参构造方法,该怎么办呢?
1. 如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。 2. 子类通过this去调用本类的其他构造方法
5 Java 重写(Override)
重写(Override)是指子类定义了一个与其父类中具有相同名称、参数列表和返回类型的方法,并且子类方法的实现覆盖了父类方法的实现。 即外壳不变,核心重写!
- 重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。这样,在使用子类对象调用该方法时,将执行子类中的方法而不是父类中的方法。
- 重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,抛出 IOException 异常或者 IOException 的子类异常。
- verride注解:用来检测当前的方法,是否是重写的方法,起到【校验】的作用
5.1 方法的重写规则
- 参数列表与被重写方法的参数列表必须完全相同。
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类。
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
- 父类的成员方法只能被它的子类重写。
- 声明为
final
的方法不能被重写。 - 声明为
static
的方法不能被重写,但是能够被再次声明。 - 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为
private
和final
的方法。 - 子类和父类不在同一个包中,那么子类只能够重写父类的声明为
public
和protected
的非final
方法。 - 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个类,则不能重写该类的方法。
修饰符 | 同一个类中 | 同一个包中子类无关类 | 不同包的子类 | 不同包的无关类 |
---|---|---|---|---|
private | √ | |||
默认 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
6 抽象类
-
当我们在做子类共性功能抽取时,有些方法在父类中并没有具体的体现,这个时候就需要抽象类了
-
在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类!
//抽象类的定义 public abstract class 类名 {} //抽象方法的定义 public abstract void eat();
-
声明抽象方法会造成以下两个结果:
- 如果一个类包含抽象方法,那么该类必须是抽象类。
- 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
- 继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。
6.1 抽象类总结规定
- 抽象类不能被实例化,如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
- 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
- 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
- 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
package com.fcarey.MyAbstract;
public abstract class Animal {
public void drink(){
System.out.println("Drinking");
}
public void eat(){
System.out.println("Eating");
}
abstract public void sleep();
}
package com.fcarey.MyAbstract;
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("cat eat");
}
public void sleep() {
System.out.println("cat sleep");
}
}
package com.fcarey.MyAbstract;
public class Dog extends Animal{
@Override
public void eat(){
System.out.println("dog, eat");
}
public void sleep(){
System.out.println("dog sleep");
}
}
package com.fcarey.MyAbstract;
public class Demo {
public static void main(String[] args) {
Dog d = new Dog();
d.eat();
d.sleep();
Cat c = new Cat();
c.eat();
d.sleep();
}
}
6.2 模板设计模式
- 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
- 使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
- 模板设计模式
- 把抽象类整体就可以看做成一个模板,模板中不能决定的东西定义成抽象方法
- 让使用模板的类(继承抽象类的类)去重写抽象方法实现需求
- 模板设计模式的优势
- 模板已经定义了通用结构,使用者只需要关心自己需要实现的功能即可
package com.fcarey;
public abstract class Template {
public final void write(){
System.out.println("啊,五环~");
body();
System.out.println("你比四环多一环");
}
public abstract void body();
}
package com.fcarey;
public class Text extends Template{
@Override
public void body() {
System.out.println("你比六环少一环");
}
}
package com.fcarey;
public class Demo {
public static void main(String[] args) {
Text text = new Text();
text.write();
}
}
7 代码块
在Java中,使用 { } 括起来的代码被称为代码块
7.1 局部代码块
局部代码块和构造代码块的区别:
-
构造代码块是在类中定义的。
-
局部代码块是在方法体中定义的。
-
局部代码块的执行顺序和书写顺序一致。
-
局部代码块
-
位置: 方法中定义
-
作用: 限定变量的生命周期,及早释放,提高内存利用率
-
-
例:
package com.fcarey.codeBlock; public class CodeBlock { { int a = 10; System.out.println("局部代码块" + a); } // System.out.println(a); 出错 }
7.2 构造代码块
-
位置: 类中方法外定义
-
特点: 每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行
-
作用: 将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性
package com.fcarey.codeBlock;
public class CodeBlock {
public static void main(String[] args) {
Student stu1 = new Student();
Student stu2 = new Student(10);
}
}
class Student {
{
System.out.println("好好学习");
}
public Student() {
System.out.println("空参数构造方法");
}
public Student(int a) {
System.out.println("带参数构造方法..........."+a);
}
}
输出:
// 好好学习
// 空参数构造方法
// 好好学习
// 带参数构造方法...........10
7.3 静态代码块
- 位置: 类中方法外定义
- 特点:静态代码块在类被加载的时候就运行了,而且只运行一次,并且优先于各种代码块以及构造函数。如果一个类中有多个静态代码块,会按照书写顺序依次执行。
- 作用: 在类加载的时候做一些数据初始化的操作
package com.fcarey.codeBlock;
public class CodeBlock {
public static void main(String[] args) {
Student stu1 = new Student();
Student stu2 = new Student(10);
}
}
class Student {
static {
System.out.println("好好学习");
}
public Student() {
System.out.println("空参数构造方法");
}
public Student(int a) {
System.out.println("带参数构造方法..........."+a);
}
}
// 好好学习
// 空参数构造方法
// 带参数构造方法...........10
8 开发原则
-
直接修改这种操作方式,不符合我们开发中的一个原则。
-
开闭原则 ( 对扩展开放对修改关闭 ) : 尽量在不更改原有代码的前提下以完成需求