八 Java代码块、继承、方法重写和final关键字
一. 主要内容
在这篇博客中,主要介绍了:
1.代码块的相关知识(重要):将在博客中通过代码介绍各个代码块的相关概念,并进行比较。
2.继承(重要):继承是面向对象编程过程中的一个重要方法。在以前的博客中介绍了面向对象编程的三个重要方法,分别是:封装、继承和多态。在这个博客中,将对继承予以介绍。
3.方法重写的相关概念:在学习了继承的相关概念之后,当我们不满足直接继承父类的方法,而想对父类的方法进行改造,加入子类特有内容,可以使用方法重写。同时需注意与方法重载做区分。
4.final关键字:我们已经学过多个java关键字,在这次博客中,对final关键字进行介绍。
二. 代码块
什么是代码块?代码块怎样分类?
答:用程序做例子:
public class Test {
{
////
}
}
这种形式的程序段我们将其称之为代码块,所谓代码块就是用大括号“{}”将多行代码封装在一起,形成一个独立的数据体,用于实现特定的算法。一般来说代码块是不能单独运行的,它必须要有运行主体。
代码块分为四种:
局部代码块,构造代码块,静态代码块,同步代码块,其中同步代码块将在以后进行介绍。
2.1 普通代码块
普通代码块是我们用得最多的也是最普遍的,它就是在方法名后面用{}括起来的代码段。普通代码块是不能够单独存在的,它必须要紧跟在方法名后面。同时也必须要使用方法名调用它。
public class Test {
public void test(){
System.out.println("普通代码块");
}
}
2.2 静态代码块
要用到static关键字,静态代码块就是用static修饰的用“{}”括起来的代码段,它的主要目的就是对静态属性进行初始化。
public class Test {
static{
System.out.println("静态代码块");
}
}
其特点是,在加载的时候就执行,并且只执行一次。
2.3 构造代码块
在类中直接定义没有任何修饰符、前缀、后缀的代码块即为构造代码块。我们明白一个类必须至少有一个构造函数,构造函数在生成对象时被调用。构造代码块和构造函数一样同样是在生成一个对象时被调用,通过下面的程序可以看出构造代码块何时被调用。
public class Test {
/**
* 构造代码
*/
{
System.out.println("执行构造代码块...");
}
/**
* 无参构造函数
*/
public Test(){
System.out.println("执行无参构造函数...");
}
/**
* 有参构造函数
*/
public Test(String id){
System.out.println("执行有参构造函数");
}
}
定义了一个简单的类,该类包含无参构造函数、有参构造函数以及构造代码块,同时在上面也提过代码块是没有独立运行的能力,其必须要有一个可以承载的载体。编译器会将代码块按照他们的顺序(假如有多个代码块)插入到所有的构造函数的最前端,这样就能保证不管调用哪个构造函数都会执行所有的构造代码块。
所以最终运行的结果为:
public static void main(String[] args) {
new Test();
System.out.println("----------------");
new Test("1");
}
------------
Output:
执行构造代码块...
执行无参构造函数...
----------------
执行构造代码块...
执行有参构造函数...
由此可见,当new一个对象的时候,总是先执行构造代码块,再执行构造函数。需要注意的是,构造代码块不是在构造函数之前运行的,而是依托构造函数执行。
正是基于构造代码块的特点,我们可以用它来干一些事情:
1.初始化实例变量
如果一个类中存在若干个构造函数,这些构造函数都需要对实例变量进行初始化,如果我们直接在构造函数中实例化,必定会产生很多重复代码,繁琐和可读性差。这里我们可以充分利用构造代码块来实现。
2.初始化实例环境
一个对象必须在适当的场景下才能存在,如果没有适当的场景,则就需要在创建对象时创建此场景。我们可以利用构造代码块来创建此场景,尤其是该场景的创建过程较为复杂。构造代码会在构造函数之前执行。
三. 继承
什么是继承?为什么要使用继承?
答:继承是面向对象最显著的一个特性。在使用继承之后,可以提高代码的复用性、提高了代码的维护性。对以后多态的产生奠定了基础。
继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
3.1 继承的语法
class 子类 extends 父类 {}
子类被称为派生类,父类被称为超类(Super Class)
3.2 继承的特点
1.java语言中,不支持多继承,但支持多层继承。由以下程序予以说明:
错误写法:(语法不支持)
class A {}
class B {}
class C extends A,B {} // 一个子类继承了两个父类
正确写法:
class A {}
class B extends A {}
class C extends B {}
上面的程序描述了继承的语法和支持多层继承的特点。
2.在一个子类继承的时候,实际上会继承父类之中的所有操作(属性、方法),但是需要注意的是,对于所有的非私有(no private)操作属于显式继承(可以直接利用对象操作),而所有的私有操作属于隐式继承(间接完成)。
class A {
private String msg;
public void setMsg(String msg) {
this.msg = msg;
}
public String getMsg() {
return this.msg;
}
}
class B extends A {
public void print() {
//System.out.println(msg); // 错误: msg定义为private,不可见
}
}
public class TestDemo {
public static void main(String args[]) {
B b = new B();
b.setMsg("张三");
System.out.println(b.getMsg());
}
}
对于A类之中的msg这个私有属性发现无法直接进行访问,但是却发现可以通过setter、getter方法间接的进行操作。
3.在继承关系之中,如果要实例化子类对象,会默认先调用父类构造,为父类之中的属性初始化,之后再调用子类构造,为子类之中的属性初始化,即:默认情况下,子类会找到父类之中的无参构造方法。
class A {
public A() { // 父类无参构造
System.out.println("*************************") ;
}
}
class B extends A {
public B() { // 子类构造
System.out.println("#########################");
}
}
public class TestDemo {
public static void main(String args[]) {
B b = new B() ; // 实例化子类对象
}
}
程序运行之后,得到的结果为:
*************************
#########################
虽然实例化的是子类对象,但是发现它会默认先执行父类构造,调用父类构造的方法体执行,而后再实例化子类对象,调用子类的构造方法。而这个时候,对于子类的构造而言,就相当于隐含了一个super()的形式:
即:
class B extends A {
public B() { // 子类构造
super(); // 调用父类构造
System.out.println("#########################");
}
}
如果默认调用的是无参构造,而如果这个时候父类没有无参构造,则子类必须通过super()调用指定参数的构造方法:
即:
class A {
public A(String msg) { // 父类构造
System.out.println("*************************");
}
}
class B extends A {
public B() { // 子类构造
super("Hello"); // 调用父类构造
System.out.println("#########################");
}
}
public class TestDemo {
public static void main(String args[]) {
B b = new B(); // 实例化子类对象
}
由此可以看出,需要通过super(String …)来调用父类的方法。
3.3 继承中成员变量的关系
1 子类中的成员变量和父类中的成员变量名称不一样
2 子类中的成员变量和父类中的成员变量名称一样
在子类中访问一个变量的查找顺序(“就近原则”)
(1)在子类的方法的局部范围找,有就使用
(2)在子类的成员范围找,有就使用
(3)在父类的成员范围找,有就使用
(4)如果还找不到,就报错
3.4 继承中成员方法的关系
1 当子类的方法名和父类的方法名不一样的时候
2 当子类的方法名和父类的方法名一样的时候
通过子类调用方法:
(1) 先查找子类中有没有该方法,如果有就使用
(2)在看父类中有没有该方法,有就使用
(3)如果没有就报错
4. 方法重写
为什么要使用方法重写?重写的前提是什么?
答:子类当然可以原封不动的继承父类的变量与方法,但是如果我们对于父类的方法功能不满意时,就要使用方法重载。重写的前提是必须要有重载。
总之一句话,方法重写即是子类定义特殊方法的过程。
4.1 方法重写注意事项
1.方法重写时, 方法名与形参列表必须一致。
2.方法重写时,子类的权限修饰符必须要大于或者等于父类的权限修饰符。
3.方法重写时,子类的返回值类型必须要小于或者 等于父类的返回值类型。
4.2 方法重写与方法重载的区别
首先,回顾一下方法重载的概念:
构成方法重载的必要条件是 方法名相同 参数列表不同(参数类型\当有两个不同类型参数时,顺序不同\个数不同)
两者比较:
1.方法的声明:
方法重载:方法名相同,参数列表不同
方法重写:方法名相同,参数列表相同
2.返回值:
方法重载:没有要求
方法重写:返回值必须相同或者子类
3.访问权限:
方法重载:没有要求
方法重写:子类不能比父类更严格
4.范围:
方法重载:同一个类中
方法重写:继承关系中
5. final关键字
final关键字有以下几种主要用途:
1.用来修饰数据,包括成员变量和局部变量,该变量只能被赋值一次且它的值无法被改变。对于成员变量来讲,我们必须在声明时或者构造方法中对它赋值;
2.用来修饰方法参数,表示在变量的生存期中它的值不能被改变;
3.修饰方法,表示该方法无法被重写;
4.修饰类,表示该类无法被继承。