[Java入门] 继承
继承
1. 继承的概念
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
很明显,从概念中可以看出继承最大的功能就是实现代码的复用。
2. 继承的语法
在讲解语法前,我们先来了解extends关键字。
exstends关键字是用以表示类与类之间的继承关系。例如:
class Person {
}
class Student extends Person {
}
从代码中,我们可以看出父类(通常我们称被继承的类为父类或基类)为Peson类,而子类(即派生类)为Student类,还有extends关键字的运用。
接下来,我们来说说继承的基本语法。还是以Person类为例:
class Person {
String name;
String sex;
int age;
public void play() {
System.out.println(name + "在玩");
}
public void talk() {
System.out.println(name + "在说话");
}
}
class Student extends Person {
}
class Teacher extends Person {
}
public class Test {
public static void main(String[] args) {
//首先,先实例化对象
Student student = new Student();
//其次,调用父类里的成员和方法要以“对象名.成员变量/方法”的方式调用
System.out.println(student.name);
System.out.println(student.sex);
System.out.println(student.age);
student.play();
student.talk();
//这里调用的都是父类的属性和方法,是从父类中继承下来的
//子类并没有这些属性和方法
}
}
需要注意的是:
- 子类会将父类的成员变量和成员方法继承下来
- 子类继承父类之后,最好要新添加自己特有的成员,体现出与父类的不同,否则就没有必要继承了
3. 父类成员访问
在Java中子类要访问父类的属性和方法并不只是简单的直接调用,还存在着许多细节需要注意
3.1 子类中访问父类的成员变量
3.1.1 子类与父类中不存在同名的成员变量
class Person {
String name;
String sex;
int age;
public void play() {
System.out.println(name + "在玩");
}
public void talk() {
System.out.println(name + "在说话");
}
}
class Student extends Person {
String id;
public void func() {
name = "lihua";
sex = "male";
age = 15;
id = "12345678";
//在两类成员变量不同名时,子类访问父类成员变量仅需赋值访问
}
//注意:要访问父类的成员变量,需写一个方法,在方法里面调用
}
3.1.2 子类与父类中存在同名的成员变量
如果子类和父类同时存在同名成员变量,子类应该如何访问?
class Person {
String name = "lihua1";
String sex;
int age = 5;
}
class Student extends Person {
String id;
String name = "lihua2";
int age = 15;
}
public class Test {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.name);
System.out.println(student.age);
}
}
输出结果:
lihua2
15
====================
从上述代码可以看出两类有同名成员变量时,子类优先访问自己的成员变量!
若将Student类中的name、age两行屏蔽,那么输出结果即为lihua1 5
如果一定要同时访问子类和父类的同名成员变量,那么就要用到super关键字(后面会讲到)
【总结】
- 子类在访问成员变量时,若该成员变量是存在在子类中的,则优先访问自己的成员变量(子类和父类的成员变量同名也是如此)
- 若不存在在子类中,则访问父类的成员变量,若父类中也不存在,那么将编译报错
- 即子类访问成员变量时,遵循就近原则,若子类中有,则优先访问,否则在父类中访问
3.2 子类中访问父类的成员方法
3.2.1 子类与父类中不存在同名的成员方法
class Person {
String name ;
String sex;
int age;
public void play() {
System.out.println(name + "在玩");
}
public void talk() {
System.out.println(name + "在讲话");
}
}
class Student extends Person {
String id;
public void write() {
System.out.println(name + "在写作业");
}
public void func() {
play();
talk();
write();
//不同名方法的访问规则是直接调用方法
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student();
student.name = "lihua";
student.func();
}
}
===================
输出结果:
lihua在玩
lihua在讲话
lihua在写作业
3.2.2 子类与父类中存在同名的成员方法
访问不同名的成员方法时与访问不同名的成员变量的思想是相同的,而访问两类中同名的成员方法时却与访问同名的成员变量略有不同。
class Person {
String name ;
public void play() {
System.out.println(name + "在打篮球");
}
public void talk() {
System.out.println(name + "在讲话");
}
}
class Student extends Person {
String id;
public void play() {
System.out.println(name + "在踢足球");
}
public void talk(int num) {
System.out.println(name + "和" + num + "个人在讲话");
}
public void func() {
play();//不含形参的同名方法访问规则也遵循就近原则
talk();
talk(2);//前面说的不同就在这里,给方法传参可以本质上区别同名方法
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student();
student.name = "lihua";
student.func();
}
}
========================
输出结果:
lihua在踢足球
lihua在讲话
lihua和2个人在讲话
【说明】
- 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
- 通过子类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法需要传递的参数选择合适的方法访问,如果没有则报错;
4. super关键字
程序员在写代码时,常常会遇到子类和父类成员变量和成员方法同名的情况,而在访问时,通常只能访问到子类的成员,父类的成员变量和方法无法访问,为解决这一情况,Java提供了super关键字,该关键字主要作用:在子类方法中访问父类的成员
class Person {
String name ;
int age;
public void play() {
System.out.println("Person在玩");
}
public void talk() {
System.out.println("Person在说话");
}
}
class Student extends Person {
String name;
int age;
//方法内容与父类不同为了分辨访问顺序
public void play() {
System.out.println("Student在玩");
}
public void talk() {
System.out.println("Student在说话");
}
public void func() {
//子类与父类的成员变量同名,直接给变量赋值,访问的都是子类的变量
name = "lihua";//等同于:this.name = "lihua"
age = 15;//等同于:this.age = 15
//要想访问父类的成员变量,需要借助super关键字
super.name = "james";
super.age = 40;
//同理,访问方法也一样
play();//直接访问,永远都只能访问到子类的play和talk
super.play();
System.out.println("===================");
talk();
super.talk();
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student();
student.func();
}
}
=================
输出结果:
Student在玩
Person在玩
===================
Student在说话
Person在说话
在用super关键字时,须注意的是super只能在非静态方法中使用。
5. 子类构造方法
在子类构造方法中,一般先调用父类的构造方法,接着再执行自己的构造方法
class Person {
String name ;
int age;
public Person() {
System.out.println("Person构造");
}
}
class Student extends Person {
String name;
int age;
public Student() {
//super(); // 这行语句表示的是在子类构造方法中会先调用父类构造
//但为什么注释调了呢? 因为父类构造是无参的,所以就算不写super(),在子类构造方法中也会默认生成
//只有无参的父类构造,在子类中才会默认生成,有参则需要自己写
//super()必须是子类构造方法中的第一条语句,且只出现一次,否则会编译报错
System.out.println("Student构造");
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student();
//student.func();
}
}
========================
输出结果:
Person构造
Student构造
在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整。
【总结】
- 在执行子类构造方法前,会先执行父类构造方法,若父类构造是无参的或是默认构造,子类构造方法中第一行会默认有隐含的super()调用,此时写与不写super()均可成功执行。
- 若父类构造是含参的,那么子类的构造便不再是默认构造,便需要显示定义子类构造方法,并在第一行写下合适的父类构造调用,即super的()内填入与父类一致的参数
- 在子类构造中,super的调用必须是第一条执行语句,且只能在构造方法中出现一次,否则编译报错
6. super和this
super和this都能调用成员变量和其他的成员方法,都是在构造方法中的第一条语句,那两者有什么区别呢?
相同:
- 都是只能在非静态方法中访问非静态成员变量和方法
- 都是构造方法中的第一条语句,且只能出现一次
不同:
- this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
- 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
- 在构造方法中:this(…)用于调用本类的其他构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现
- 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有
class Person {
String name ;
int age;
public Person() {
System.out.println("Person构造");
}
}
class Student extends Person {
String name;
int age;
public Student(int age) {
System.out.println("Student(int age)");
}
public Student() {
this(15);
//super(); // 这行语句表示的是在子类构造方法中会先调用父类构造
//但为什么注释调了呢? 因为父类构造是无参的,所以就算不写super(),在子类构造方法中也会默认生成
//只有无参的父类构造,在子类中才会默认生成,有参则需要自己写
//super()必须是子类构造方法中的第一条语句,且只出现一次,否则会编译报错
//System.out.println("Student构造");
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student();
//student.func();
}
}
=================================
输出结构:
Person构造
Student(int age)
7. 再谈初始化
再谈初始化的目的是补充之前没有讲的构造、实例代码块、静态代码块的执行先后顺序,先来看看在没有继承关系下三者的执行顺序。
class Person {
String name ;
int age;
public Person() {
System.out.println("Person构造");
}
{
System.out.println("实例代码块");
}
//这里故意把静态代码块留在最后写就是为了辨别执行顺序
static{
System.out.println("静态代码块");
}
}
===============================
输入结果:
静态代码块
实例代码块
Person构造
可以看出,在没有继承关系下,三者的执行顺序是:静态代码块->实例代码块->构造方法
那么在继承关系下呢?
class Person {
String name ;
int age;
public Person() {
System.out.println("Person构造");
}
{
System.out.println("父类实例代码块");
}
//这里故意把静态代码块留在最后写就是为了辨别执行顺序
static{
System.out.println("父类静态代码块");
}
}
class Student extends Person {
String name;
int age;
public Student() {
System.out.println("Student构造");
}
{
System.out.println("子类实例代码块");
}
static{
System.out.println("子类静态代码块");
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student();
System.out.println("==============");
Student student1 = new Student();
}
}
======================
输出结果:
父类静态代码块
子类静态代码块
父类实例代码块
Person构造
子类实例代码块
Student构造
==============
父类实例代码块
Person构造
子类实例代码块
Student构造
Process finished with exit code 0
【总结】
- 代码块的执行顺序总是先与构造方法,且静态代码块的执行顺序先于实例代码块
- 在继承关系下,先执行父类的静态代码块,再执行子类的静态代码块,且静态代码块只执行一次,后面再调用构造方法时静态代码块不再执行
- 接着先执行父类的实例代码块、父类构造,然后再执行子类的实例代码块、子类构造
8. protected 关键字
protected关键字同public、private一样是访问限定符,它们的主要限定是:类或者类中成员能否在类外或者其他包中被访问。
那么在继承关系下,父类不同访问权限的成员在子类中又如何访问呢?
public class A {
//设置用不同访问限定符修饰,用来区分访问权限
public int a;
private int b;
protected int c;
int d;
}
//同一包中的不同类
class B extends A {
void func() {
super.a = 10; //public修饰可直接访问
//super.b = 10; //private修饰不可访问,编译报错
super.c = 10; //protected修饰可直接访问
super.d = 10;//默认访问限定符修饰可直接访问
}
}
//不同包中的子类
class C extends A {
void func() {
super.a = 10; //public修饰可直接访问
//super.b = 10; //private修饰不可访问,编译报错
super.c = 10; //protected修饰可直接访问
//super.d = 10;//默认访问限定符修饰不可直接访问,编译报错
}
}
//不同包中的非子类
class D {
public static void main(String[] args) {
A a = new A();
System.out.println(a.a);//public修饰可直接访问
//System.out.println(a.b);//private修饰不可访问,编译报错
//System.out.println(a.c);//protected修饰不可直接访问,编译报错
//System.out.println(a.d);//默认访问限定符修饰不可直接访问,编译报错
}
}
【总结】
- public访问限定符在同一包中同一类、同一包中不同类、不同包中的子类和不同包中的非子类都有权限可直接访问
- private访问限定符只能在同一包中同一类中访问
- protected访问限定符只有在不同包中非子类不能访问,换句话说,被protected修饰的成员在继承关系下中能被子类访问
- default默认访问限定符只能在同一包中同一类和同一包中不同类中访问
什么时候下用哪一种呢?
我们希望类要尽量做到 “封装”, 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.
因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用 public.
另外, 还有一种 简单粗暴 的做法: 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是对访问权限的滥用, 还是更希望同学们能写代码的时候认真思考, 该类提供的字段方法到底给 “谁” 使用(是类内部自己用, 还是类的调用者使用, 还是子类使用)
9. final 关键字
final关键字可以用来修饰变量、成员方法以及类。学过c的话,final关键字功能类似于c的const。
1. 修饰变量或字段,表示常量(即不能修改)
public static void main(String[] args) {
final int a = 10;
//a = 100; //编译报错!
}
2. 修饰类:表示此类不能被继承
final public class Animal {
...
}
public class Bird extends Animal {
...
}
// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继
3. 修饰方法:表示该方法不能被重写(后序在多态中介绍)
本期继承到这就结束啦,希望大家能多多提出问题和建议!!!
2104



被折叠的 条评论
为什么被折叠?



