本章内容

1.继承的概念与实现
2.super关键词
3.多态
4.instanceof运算符
5.final关键字
6.static关键字

1.继承的概念与实现

继承是类与类之间的关系,是一个很简单很直观的概念,与现实世界中的继承(例如儿子继承父亲财产)类似。

继承可以理解为一个类从另一个类获取方法和属性的过程。如果类B继承于类A,那么B就拥有A的方法和属性。

继承是面向对象编程中非常强大的一种机制,它首先可以复用代码。当我们让Student从Person继承时,Student就获得了Person的所有功能,我们只需要为Student编写新增的功能。

继承的特点:

  • Java是单继承语言:一个类的直接父类只能有唯一一个
  • Java语言可以多级继承(父类也可以继承父类的父类)且一个父类可以拥有多个子类/兄弟子类
  • 继承使用extend关键字,子类可以覆盖父类的方法
  • 父类中的private成员在子类中是不可见的,因此在子类中不能直接使用它们

继承实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

// 此处省略setter与getter
}

class Student extends Person {
// 不要重复定义name和age字段/方法,只需要定义新增score字段/方法:
private String category;

public Student(String name, int age, String category) {
super(name, age);
this.category = category;
}
// 此处省略setter与getter
}

2.super关键字

super关键字与this类似,this用来表示当前类的实例,super用来表示父类

super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName
实际上,这里使用super.name,或者this.name,或者name,效果都是一样的。编译器会自动定位到父类的name字段

1
2
3
4
5
class Student extends Person {
public String hello() {
return "Hello, " + super.name;
}
}

在继承时,如果父类存在构造函数,在这个时候,就必须使用super,其正确写法应为(牢记):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class OOP {
public static void main(String[] args) {
// 实例s为Person的子类Student类的实例
Student s = new Student("gz",18,10010,100);
System.out.println(s.getAge());
System.out.println(s.grand);
}
}

class Person {
protected String name;
protected int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 此处省略setter与getter
}

class Student extends Person{
protected int grand;

public Student(String name, int age, int grand) {
super(name, age); // 自动调用父类的构造方法Person(name, age)
this.grand = grand; // 这是新构建的字段
}

@Override // Override重写,方法名,参数,返回值类型完全相同
public int getAge(){
return this.grand;
}
}

继承小结:

  • Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类
  • 子类无法访问父类的private字段或者private方法,为了让子类可以访问父类的字段,我们需要把private改为protected
  • 子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的

3.多态

在继承关系中,子类如果与父类的方法名完全相同(方法名相同,参数相同,子类方法的返回值必须小于等于父类的返回值范围(推荐:子类和父类方法的返回值类型相同)),被称为重写(Override,这里要区别于Overload是方法名相同,但各自的参数不同)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run(); // 应该打印Person.run还是Student.run?
}
}

class Person {
// class中的方法不要写static
public void run() {
System.out.println("Person.run");
}
}

// Student类继承Person类
class Student extends Person {
@Override // 这是重写符号,将子类的run方法进行重写,增加新的功能
public void run() {
System.out.println("Student.run");
}
}

在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
protected String name;
public String hello() {
return "Hello, " + name;
}
}

class Student extends Person {
@Override
public String hello() {
// 调用父类的hello()方法:
return super.hello() + "!";
}
}

小结:
区分子类方法中变量名的三种访问方式:

  • 局部变量:直接写成员变量名
  • 本类的成员变量:this.成员变量名
  • 父类的成员变量:super.成员变量名

4. Java对象类型转换

将一个类型强制转换成另一个类型的过程被称为类型转换。本节所说的对象类型转换,是指存在继承关系的对象,不是任意类型的对象。当对不存在继承关系的对象进行强制类型转换时,会抛出Java强制类型转换(java.lang.ClassCastException)异常

Java语言允许某个类型的引用变量引用子类的实例,而且可以对这个引用变量进行类型转换。Java中引用类型之间的类型转换(前提是两个类是父子关系)主要有两种,分别是向上转型(upcasting)和向下转型(downcasting)

  1. 向上转型

父类引用指向子类对象为向上转型,语法格式如下:
fatherClass obj = new sonClass();

向上转型就是把子类对象直接赋给父类引用,不用强制转换。使用向上转型可以调用父类类型中的所有成员,不能调用子类类型中特有成员,最终运行效果看子类的具体实现。

  1. 向下转型

与向上转型相反,子类对象指向父类引用为向下转型,语法格式如下:
sonClass obj = (sonClass) fatherClass;

4.instanceof运算符

多态性带来了一个问题,就是如何判断一个变量所实际引用的对象的类型。Java使用instanceof操作符,用来判断一个变量所引用的对象的实际类型,注意是它引用的对象的类型,不是变量的类型

实例:obj instanceof ClassName ,可以直白理解为 实例obj是ClassName类或是其子类?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public final class Demo{
public static void main(String[] args) {
// 引用 People 类的实例
People obj = new People();
if(obj instanceof Object){ // Person实例的obj是Object类的子类
System.out.println("我是一个对象");
}
if(obj instanceof People){ // obj等于Person类
System.out.println("我是人类");
}
if(obj instanceof Teacher){ // obj是Teacher类的父类,判断语句为错误
System.out.println("我是一名教师");
}
System.out.println("-----------"); // 分界线
// 引用 Teacher 类的实例
obj = new Teacher();
if(obj instanceof Object){
System.out.println("我是一个对象");
}
if(obj instanceof People){
System.out.println("我是人类");
}
if(obj instanceof Teacher){
System.out.println("我是一名教师");
}
if(obj instanceof President){
System.out.println("我是校长");
}
}
}

class People{ }
class Teacher extends People{ }
class President extends Teacher{ }

运行结果:
我是一个对象
我是人类


我是一个对象
我是人类
我是一名教师

可以看出,如果变量引用的是当前类或它的子类的实例,instanceof 返回true,否则返回false

5.final关键字

在 Java 中,声明类、变量和方法时,可使用关键字final来修饰。final所修饰的数据具有“终态”的特征,表示“最终的”意思。具体规定如下:

  • final用在变量的前面表示修饰的变量不可被改变,一旦获得了初始值,该final变量的值就不能被重新赋值,此时该变量可以被称为常量。
  • final用在方法的前面表示方法不可以被重写(子类中如果创建了一个与父类中相同名称、相同返回值类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写,又称为方法覆盖。这里了解即可,教程后面我们会详细讲解)。
  • final用在类的前面表示该类不能有子类,即该类不可以被继承。

final修饰变量

final修饰的变量即成为常量,只能赋值一次,但是final所修饰局部变量和成员变量有所不同:

  • final修饰的局部变量必须使用之前被赋值一次才能使用
  • final修饰的成员变量在声明时没有赋值的叫“空白final变量”。空白final变量必须在构造方法或静态代码块中初始化

注意:final修饰的变量不能被赋值这种说法是错误的,严格的说法是,final修饰的变量不可被改变,一旦获得了初始值,该final变量的值就不能被重新赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class FinalDemo {
void doSomething() {
// 没有在声明的同时赋值
final int e;
// 只能赋值一次
e = 100;
System.out.print(e);
// 声明的同时赋值
final int f = 200;
}
// 实例常量
final int a = 5; // 直接赋值
final int b; // 空白final变量
// 静态常量
final static int c = 12;// 直接赋值
final static int d; // 空白final变量
// 静态代码块
static {
// 初始化静态变量
d = 32;
}
// 构造方法
FinalDemo() {
// 初始化实例变量
b = 3;
// 第二次赋值,会发生编译错误
// b = 4;
}
}

拓展:final修饰基本类型变量和引用类型变量的区别

当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变,比如说数组。

final修饰方法

final修饰的方法不可被重写,如果出于某些原因,不希望子类重写父类的某个方法,则可以使用 final 修饰该方法

1
2
3
4
5
6
7
8
9
public class FinalMethodTest {
public final void test() {
}
}
class Sub extends FinalMethodTest {
// 下面方法定义将出现编译错误,不能重写final方法
public void test() {
}
}

上面程序中父类是FinalMethodTest,该类里定义的test()方法是一个final方法,如果其子类试图重写该方法,将会引发编译错误。

对于一个private方法,因为它仅在当前类中可见,其子类无法访问该方法,所以子类无法重写该方法。如果子类中定义一个与父类private方法有相同方法名、相同形参列表、相同返回值类型的方法,也不是方法重写,只是重新定义了一个新方法。因此,即使使用final修饰一个private访问权限的方法,依然可以在其子类中定义与该方法具有相同方法名、相同形参列表、相同返回值类型的方法。

下面程序示范了如何在子类中“重写”父类的private final方法。

1
2
3
4
5
6
7
8
9
10
public class PrivateFinalMethodTest {
private final void test() {
}
}
class Sub extends PrivateFinalMethodTest {
// 下面的方法定义不会出现问题
public void test() {
// 并不是真正意义上的重写,而是定义一个 相同方法名、相同形参列表、相同返回值类型的方法
}
}

final修饰类

final修饰的类不能被继承。当子类继承父类时,将可以访问到父类内部数据,并可通过重写父类方法来改变父类方法的实现细节,这可能导致一些不安全的因素。为了保证某个类不可被继承,则可以使用final修饰这个类

1
2
3
4
final class SuperClass {
}
class SubClass extends SuperClass { //编译错误
}

2020-3-9 新增:
在父子类的继承关系中,如果成员变量重名,则创建子类访问对象时,访问有两种方法:

  1. 通过子类对象访问成员变量:等号左边是谁,就优先用谁,没有则向上找父类
  2. 通过成员方法访问成员变量:该方法属于谁,就优先用谁,没有向上找父类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.Alibaba;

public class extendsField {
public static void main(String[] args) {
Fu fu = new Fu();
Zi zi = new Zi();// 实例化两个对象
/*
* 当父类和子类都具有num这个成员变量时
* 等号左边是谁,优先用谁
* */
System.out.println(zi.num); // 20
System.out.println("======");
zi.methodZi(); // 20:这个方法是子类的,优先使用子类的
zi.methodFu(); // 10:这个方法实在父类中定义的
}
}

// 创建父类
class Fu{
public int num = 10;
public int numFu = 100;
public void methodFu(){
System.out.println("这是父类方法");
// 优先使用本类中的num
System.out.println(num); // 10
}
}
// 创建子类
class Zi extends Fu{
public int num = 20;
public int numFu = 100;
public void methodZi(){
System.out.println("这是子类方法");
// 优先使用本类中的成员变量
System.out.println(num); // 20
}
}

6.static关键字

关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属于某个对象的。也就是说,既然属于类,就可以不靠创建对象来调用了。

在一个类中,有变量和方法,而变量有成员变量,静态变量;而方法有成员方法和静态方法之分(只要带static关键字就代表静态变量或方法)

如何区分我们什么时候使用static关键字呢?


从上述图中我们可以看出在学生类中,我们new三个学生对象,每一个对象除了姓名,年龄,学号不同之外,每一个学生对象的所在教室完全一样。
对于姓名,年龄,学号来说,每一个对象都有自己独立的数据。但对于所在的教室来说,这应该是多个对象共享同一份数据才对。
一旦使用了static关键字,那么这样的内容不在属于对象自己,而是属于类的,所以凡是本类的对象,都共享同一份

我们来实现这个图片展示的功能(以标准的类的创建方式):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class staticDemo {
public static void main(String[] args) {
// 静态static常量赋初值(类名称.变量名)
Student1.room = "101教室";

Student1 one = new Student1("郭靖", 20);
System.out.println("one的姓名:" + one.getName());
System.out.println("one的年龄:" + one.getAge());
System.out.println("one的教室:" + Student1.room);
System.out.println("============");

Student1 two = new Student1("黄蓉", 18);
System.out.println("two的姓名:" + two.getName());
System.out.println("two的年龄:" + two.getAge());
System.out.println("two的教室:" + Student1.room);

myMethod() // 本类中的静态方法直接调用
}
// 本类中的静态方法,调用时可以省略类名称,直接调用
public static void myMethod() {
System.out.println("自己的方法!");
}
}


class Student1 {

private int id; // 学号
private String name; // 姓名
private int age; // 年龄
static String room; // 所在教室
private static int idCounter = 0; // 学号计数器,每当new了一个新对象的时候,计数器++

public Student1() {
this.id = ++idCounter;
}

public Student1(String name, int age) {
this.name = name;
this.age = age;
this.id = ++idCounter;
}
// 省略此处的setter与getter
}

静态变量

静态变量:static 数据类型 变量名 (static int numberID)

比如说,基础班新班开班,学员报到。现在想为每一位新来报到的同学编学号(sid),从第一名同学开始,sid为 1,以此类推。学号必须是唯一的,连续的,并且与班级的人数相符,这样以便知道,要分配给下一名新同学的学 号是多少。这样我们就需要一个变量,与单独的每一个学生对象无关,而是与整个班级同学数量有关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class staticDemo {
public static void main(String[] args) {
Student1 s1 = new Student1("迪丽热巴",18);
Student1 s2 = new Student1("古力娜扎",20);
Student1 s3 = new Student1("马尔扎哈",33);
s1.show(); // Student : name=迪丽热巴, age=18, 学生编号=1
s2.show(); // Student : name=古力娜扎, age=20, 学生编号=2
s3.show(); // Student : name=马尔扎哈, age=33, 学生编号=3
}
}


class Student1{
// 成员变量
private String name;
private int age;
private int id;
// 静态变量,记录学生数量,分配学号
public static int numberNo = 0;

public Student1(String name,int age){
this.name = name;
this.age = age;
// 表示每创建一个Student1对象,id就会自动加一
this.id = ++ numberNo;
}
// 打印属性值   
public void show() {
System.out.println("Student : name="+this.name+", age="+this.age+", 学生编号="+this.id);
}
}

静态方法

使用格式:修饰符 static 返回值类型 方法名 (参数列表){ 执行语句代码块 }

被static修饰的成员建议通过类名直接访问。虽然也可以通过对象名访问静态成员,原因即多个对象均属于一个类,共享使用同一个静态成员,但是不建议,会出现警告信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.bytedance;

public class staticDemo {
public static void main(String[] args) {
MyClass obj = new MyClass(); // 首先创建对象
obj.num = 18;
MyClass.numStatic = 20;

// 创建对象之后,然后才能使用没有static关键字的内容
obj.method();

// 对于静态方法来说,可以通过对象名进行调用,也可以直接通过类名称来调用。
obj.methodStatic(); // 正确,不推荐,这种写法在编译之后也会被javac翻译成为“类名称.静态方法名”

MyClass.methodStatic(); // 正确,推荐

// 对于本类当中的静态方法,可以省略类名称
myMethod();
staticDemo.myMethod(); // 对于本类中的静态方法,myMethod()与staticDemo.myMethod()完全等效,可以省略类名称
}

public static void myMethod() {
System.out.println("自己的方法!");
}
}


class MyClass {
int num; // 成员变量
static int numStatic; // 静态变量

// 成员方法
public void method() {
System.out.println("这是一个成员方法。");
// 成员方法可以访问成员变量
System.out.println(num);
// 成员方法可以访问静态变量
System.out.println(numStatic);
}

// 静态方法
public static void methodStatic() {
System.out.println("这是一个静态方法。");
// 静态方法可以访问静态变量
System.out.println(numStatic);
// 静态不能直接访问非静态【重点】
// System.out.println(num); // 错误写法!

// 静态方法中不能使用this关键字。
// System.out.println(this); // 错误写法!
}}

总结:
一旦使用static修饰成员方法,那么这就成为了静态方法。静态方法不属于对象,而是属于类的。

如果没有static关键字,那么必须首先创建对象,然后通过对象才能使用它
如果有了static关键字,那么不需要创建对象,直接就能通过类名称来使用它

无论是成员变量,还是成员方法。如果有了static,都推荐使用类名称进行调用
静态变量\:类名称.静态变量
静态方法\:类名称.静态方法()

静态常量赋初始值的方法:类名称.静态变量名 = 初始值

注意事项:

  1. 静态方法只能访问静态成员,实例方法可以访问静态和实例成员。反之,成员方法可以直接访问类变量或静态方法
    原因:因为在内存当中是【先】有的静态内容,【后】有的非静态内容。
    “先人不知道后人,但是后人知道先人。”

  2. 静态方法当中不能用this。
    原因:this代表当前对象,通过谁调用的方法,谁就是当前对象。

  3. 对于本类当中的静态方法,调用时可以省略类名称

关于静态变量和静态方法的总结:

  • 一个类的静态方法只能访问静态变量
  • 一个类的静态方法不能够直接调用非静态方法
  • 如访问控制权限允许,静态变量和静态方法也可以通过对象来访问,但是不被推荐
  • 静态方法中不存在当前对象,因而不能使用this,当然也不能使用super
  • 静态方法不能被非静态方法覆盖
  • 构造方法不允许声明为static的
  • 局部变量不能使用static修饰

参考文献


 评论

联系我 | Contact with me

Copyright © 2019-2020 谁知你知我,我知你知深。此恨经年深,比情度日久

博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议