本章内容:
1.继承的概念与实现
2.super关键词
3.多态
4.Java对象类型转换
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; }
}
class Student extends Person { private String category;
public Student(String name, int age, String category) { super(name, age); this.category = category; } }
|
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) { 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; } }
class Student extends Person{ protected int grand;
public Student(String name, int age, int grand) { super(name, age); this.grand = grand; } @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(); } }
class Person { public void run() { System.out.println("Person.run"); } }
class Student extends Person { @Override 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() { return super.hello() + "!"; } }
|
小结:
区分子类方法中变量名的三种访问方式:
- 局部变量:直接写成员变量名
- 本类的成员变量:this.成员变量名
- 父类的成员变量:super.成员变量名
4. Java对象类型转换
将一个类型强制转换成另一个类型的过程被称为类型转换。本节所说的对象类型转换,是指存在继承关系的对象,不是任意类型的对象。当对不存在继承关系的对象进行强制类型转换时,会抛出Java强制类型转换(java.lang.ClassCastException)异常
Java语言允许某个类型的引用变量引用子类的实例,而且可以对这个引用变量进行类型转换。Java中引用类型之间的类型转换(前提是两个类是父子关系)主要有两种,分别是向上转型(upcasting)和向下转型(downcasting)

- 向上转型
父类引用指向子类对象为向上转型,语法格式如下:
fatherClass obj = new sonClass();
向上转型就是把子类对象直接赋给父类引用,不用强制转换。使用向上转型可以调用父类类型中的所有成员,不能调用子类类型中特有成员,最终运行效果看子类的具体实现。
1 2 3 4
| Animal an1=new Cat(); //将Cat 对象当作Animal 类型来使用 Animal an2=new Dog(); //将Dog 对象当作Animal 类型来使用
将子类对象当作父类使用时不需要任何显式地声明,需要注意的是,此时不能通过父类变量去调用子类中的某些方法
|
- 向下转型
与向上转型相反,子类对象指向父类引用为向下转型,语法格式如下:
sonClass obj = (sonClass) fatherClass;
注意:不能直接将父类的对象强制转换为子类类型,只能将向上转型后的子类对象再次转换为子类类型。也就是说,子类对象必须向上转型后,才能再向下转型
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Demo { public static void main(String args[]) { SuperClass superObj = new SuperClass(); SonClass sonObj = new SonClass();
superObj = sonObj; SonClass sonObj1 = (SonClass)superObj; } } class SuperClass{ } class SonClass extends SuperClass{ }
|
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 static int c = 12; final static int d; static { d = 32; } FinalDemo() { b = 3; } }
|
拓展: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 { 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 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();
System.out.println(zi.num); System.out.println("======"); zi.methodZi(); zi.methodFu(); } }
class Fu{ public int num = 10; public int numFu = 100; public void methodFu(){ System.out.println("这是父类方法"); System.out.println(num); } }
class Zi extends Fu{ public int num = 20; public int numFu = 100; public void methodZi(){ System.out.println("这是子类方法"); System.out.println(num); } }
|
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) { 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;
public Student1() { this.id = ++idCounter; }
public Student1(String name, int age) { this.name = name; this.age = age; this.id = ++idCounter; } }
|
静态变量
静态变量: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(); s2.show(); s3.show(); } }
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; 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;
obj.method();
obj.methodStatic();
MyClass.methodStatic();
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);
}}
|
总结:
一旦使用static修饰成员方法,那么这就成为了静态方法。静态方法不属于对象,而是属于类的。
如果没有static关键字,那么必须首先创建对象,然后通过对象才能使用它
如果有了static关键字,那么不需要创建对象,直接就能通过类名称来使用它
无论是成员变量,还是成员方法。如果有了static,都推荐使用类名称进行调用。
静态变量\:类名称.静态变量
静态方法\:类名称.静态方法()
静态常量赋初始值的方法:类名称.静态变量名 = 初始值
注意事项:
静态方法只能访问静态成员,实例方法可以访问静态和实例成员。反之,成员方法可以直接访问类变量或静态方法
原因:因为在内存当中是【先】有的静态内容,【后】有的非静态内容。
“先人不知道后人,但是后人知道先人。”
静态方法当中不能用this。
原因:this代表当前对象,通过谁调用的方法,谁就是当前对象。
对于本类当中的静态方法,调用时可以省略类名称
关于静态变量和静态方法的总结:
- 一个类的静态方法只能访问静态变量
- 一个类的静态方法不能够直接调用非静态方法
- 如访问控制权限允许,静态变量和静态方法也可以通过对象来访问,但是不被推荐
- 静态方法中不存在当前对象,因而不能使用this,当然也不能使用super
- 静态方法不能被非静态方法覆盖
- 构造方法不允许声明为static的
- 局部变量不能使用static修饰
参考文献