本章内容
static关键字
继承super
关键字权限
修饰符方法
重写
多态
final关键字
引用类型转换
1 static
1.1 问题引入
某市有三所中学:第一中学、秀峰中学、娄江中学,分别有3000人、2500
人、1800人,它们都没有图书馆,也无力独自建造一所图书馆。市领导了解这
一情况后,经研究决定,由市委市政府出资建造一所图书馆,命名为"栋梁图
书馆",供该市所有中学师生公共使用。
根据前期学习的内容,用面向对象的编程思想编码描述上述案例,代码具体
如下:
//学 校 类
class School {
p r i v a t e S t r i n g n a m e ; //名 称
p r i v a t e i n t n u m ; //师 生 数 量
private String library;
//定 义 构 造 器
public School() {}
//图 书 馆
public School(String name, int num, String library)
{ this.name = name;
this.num = num;
this.library = library;
}
public void show() {
S y s t e m .o u t .p r i n t l n (" 学 校 名 称 : " + t h i s . n a m e );
S y s t e m .o u t .p ri n t l n (" 师 生 数 量 : " + n u m );
S y s t e m . o u t . p r i n t l n ( " 图 书 馆 : " + l i b r a r y ) ;
}
}
public class Test0101_School {
public static void main(String[] args) {
S c h oo l s 1 = n e w S c ho o l ("第 一 中 学 ",3 0 0 0 ,"栋 梁 图 书 馆 ");
S c h oo l s 2 = n e w S c ho o l ("秀 峰 中 学 ",2 5 0 0 ,"栋 梁 图 书 馆 ");
S c h o o l s 3 = n e w S c h o o l ("娄 江 中 学 ",1 8 0 0 ,"栋 梁 图 书 馆 ");
s1.show();
System.out.println("--------------");
s2.show();
System.out.println("--------------");
s3.show();
}
}
思 考 : 上 述 案 例 中 , 学 校 类 中 添 加 图 书 馆 属 性 private String library; 是
否合理?
结论是不合理。
因为图书馆不为某一个学校所特有,而是为市里面所有中学共同所有,目前
是3所中学共享,将来如果市里建造第四、第五所高中,新高中的师生也是可
以使用该图书馆的。
而 private String library; 这 样 定 义 属 性 , 则 表 示 每 个 学 校 对 象 , 都 有自己独立的图书馆,与实际业务不符。具体可结合下面对象内存图理解:实际我们想要的效果为图书馆有且只有一个,只占用一块内存区域,具体如下图:
static 修饰符能够帮我们实现这样的效果,该关键字的具体细节后续专门学
习。
static是一个修饰符,表示静态的意思,可以修饰属性、方法、代码块
1.2 静态成员
static修饰类中的数据成员,该成员就成了静态数据成员,也称为类成员;
类成员,是属于类的,为这个类所有对象共享,只占用一块内存空间。
生活中案例:
普通成员变量
就像教室中,同学们的水杯,每个同学都有一个自己的水杯,和其他人相
互不影响
静态数据成员
就像教室中,角落里的饮水机,它是这个教室中所有同学共享的,张三接
一杯水,李四就会看到饮水机中的水少了一些,同样的李四接一杯水,张
三也会看到饮水机中的水少了一些
static成员特点:
被类的所有对象共享
随着类的加载而加载,先于对象存在
对象需要类被加载后,才能被创建出来
可以通过类名调用,也可以通过对象名调用,推荐使用类名调用
格 式 : 类 名 .静 态 数 据 成 员 ;
方法区中有一块专门的区域:静态区,专门用来存储类的static成员
静态成员访问方式,案例描述:
class Demo {
public static int num; }
public static void main(String[] args) {
//可以使用类名来访问,推荐用法
Demo.num = 10;
Demo demo = new Demo();
//也可以对象来访问,但不推荐
demo.num = 20; }
静态数据成员是属于类的,并且为这个类所有对象共享,案例描述:
class Demo {
public static int num; }
public static void main(String[] args) {
//1.静态数据成员会随着类的加载而加载(开辟内存并进行默认 初始化)
//先 于 对 象 存 在
Demo.num = 10;
//2.实例化两个对象,然后通过对象访问静态成员,输出其值
Demo demo1 = new Demo();
Demo demo2 = new Demo();
S y s t e m .ou t.println (d e m o 1 .n u m );//输 出 结 果 为 1 0
S y s t e m .ou t .println (d e m o 2 .n u m );//输 出 结 果 为 1 0
//3.通 过 类 名 修 改 静 态 成 员 值 , 再 借 助 对 象 .static成 员 , 输 出 其 值
Demo.num = 20;
S y s t e m .ou t.println (d e m o 1 .n u m );//输 出 结 果 为 2 0
S y s t e m .ou t .println (d e m o 2 .n u m );//输 出 结 果 为 2 0
//4.通 过 对 象 名 修 改 静 态 成 员 值 , 再 借 助 类 名 .static成 员 , 输 出 其 值
demo1.num = 30;
S y s t e m .ou t .println (D e m o .n u m );//输 出 结 果 为 3 0
S y s t e m .out .println (d e m o 2 .n u m );//输 出 结 果 为 3 0
}
可以看出,无论是使用类访问静态属性,还是使用这个类的某个对象访问
静态属性,效果是一样的,这个属性对这个类的所有对象都是可见的、共
享的。
静态属性存储位置:
在方法区中有一块静态区,专门用来存储各个类静态数据成员。当操作静态数
据成员时,不论是通过类名,还是通过对象名操作,都是操作静态区中对应的
内存区域。
静态数据成员初始化:
静态数据成员随着类加载而加载(开辟相应内存),并会进行默认的初始
化
其 默 认 初 始 化 值 为 null(引 用 类 型 )、 0(整 形 )、 0.0(浮 点 型 )、 false(布
尔 类 型 ) 。
案例描述:
class Demo {
public static int num;
public static String str;
public static double dNum;
public static boolean flag;
}
public class Test0102_StaticValue {
public static void main(String[] args)
{ System.out.println(Demo.num); / / 0
System.out.println(Demo.str); //null
System.out.println(Demo.dNum); //0.0
System.out.println(Demo.flag); //false
}
}
对静态数据成员的初始化,一般采用两种方式
显式初始化、静态代码块初始化(后面章节介绍)
显 式 初 始 化 格 式 : [修 饰 符 ] static 数 据 类 型 静 态 成 员 名 = 初 始 值 ;
例 : p u b l i c s t a t i c S t r i n g l i b r a r y = " 栋 梁 图 书 馆 " ;
注意:尽量不要在构造方法中对static成员进行初始化,因为只要创建对象,
构造方法就会自动被执行,导致static成员值不经意间被修改!
案例描述:
请根据讲解内容,修改上一章节中School类代码,并完成测试。
//学 校 类
class School {
p r i v a t e S t r i n g n a m e ; //名 称
p r i v a t e i n t n u m ; //师 生 数 量
//该类所有对象共享的图书馆,显示初始化
p u b l i c s t a t i c S t r i n g l i b r a r y = " 栋 梁 图 书 馆 " ;
//定 义 构 造 器
public School() {}
public School(String name, int num)
{ this.name = name;
this.num = num; }
//该 构 造器 不 合适 ,尽 量 不要 在构 造 方法 中 对 static成员 进 行初 始化
// public School(String name, int num, String library) {
/ / this.name = name;
/ / this.num = num;
/ / this.library = library;
// }
public void show() {
S y s t e m . o u t .p r i n t l n ( " 学 校 名 称 : " + t h i s .n a m e );
S y s t e m .o u t .p ri n t l n (" 师 生 数 量 : " + n u m );
S y s t e m . o u t .p r i n t l n ( " 图 书 馆 : " + S c h o o l . l i b r a r y );
}
}
public class Test0102_School {
public static void main(String[] args) {
//1.在 创 建 School对 象 前 , 输 出 static成 员 值
//此 时 程 序 正 常 输 出 , 说 明 static成 员 的 创 建 与 初 始 化 先 于 对 象
//结 论 : static成 员 依 赖 类 , 而 与 该 类 对 象 无 关
System.out.println(School.library);
//2.创 建 类 对 象
S c h o o l s 1 = n e w S c h o o l ("第 一 中 学 ",3 0 0 0 );
System.out.println(s1.library);
s1.show();
System.out.println("-------------");
S c h o o l s 2 = n e w S c h o o l ("秀 峰 中 学 ",2 5 0 0 );
s2.show();
System.out.println("-------------");
S c h o o l s 3 = n e w S c h o o l ("娄 江 中 学 ",1 8 0 0 );
s3.show();
}
}
运行效果:
1.3 静态方法
在类中,使用static修饰的方法,就是静态方法;
static方法的作用,主要是用来操作static成员;
static方法可以使用类名来调用,也可以使用对象来调用,但推荐使用类名。
案例描述:
public class Demo{
//定 义 静 态 方 法
public static void test() {
S y s t e m . o u t . p r i n t l n ( " 我 是 s t a t i c 静 态 方 法 . . . " );
}
}
//测 试 方 法
public static void main(String[] args){
//借 助 类 名 调 用 static方 法 , 推 荐 方 式
Demo.test();
Demo demo = new Demo();
//借 助 对 象 调 用 static方 法 , 可 以 调 用 , 但 不 推 荐
demo.test(); }
static方法和普通成员方法区别:
静态方法只能访问静态的成员
非静态方法可以访问静态的成员,也可以访问非静态的成员
静态方法中没有this关键字 (本质区别)
案例描述:
//学 校 类
class School {
p r i v a t e S t r i n g n a m e ; //名 称
p r i v a t e i n t n u m ; //师 生 数 量
//用 private 修 饰 static成 员 , 则 该 类 外 无 法 直 接 访 问 该 static成 员
p r i v a t e s t a t i c S t r i n g l i b r a r y = " 栋 梁 图 书 馆 " ;
//定 义 public修 饰 的 static方 法 , 专 门 来 操 作 static成 员
public static String getLibrary() {
//注 意 1 : static方 法 中 没 有 t h is
//return this.library; //error
//注 意 2: static方 法 中 不 可 以 访 问 普 通 数 据 成 员
/ / n a m e = "一 高 ";
return library;
//error
}
}
//测 试 方 法
public static void main(String[] args) {
//1.在 创 建 School对 象 前 , 输 出 static成 员 值
//编 译 报 错 , library为 private , 只 能 S choo l类 内 访 问
//System.out.println(School.library);
//2.借 助 类 名 调 用 static方 法 , 推 荐 用 法
String lib = School.getLibrary();
System.out.println(lib);
//3.借 助 方 法 名 调 用 static方 法 , 不 推 荐
S c h o o l s 1 = n e w S c h o o l ("第 一 中 学 ",3 0 0 0 );
System.out.println(s1.getLibrary()); }
思考:为什么静态方法中不能访问普通数据成员?
静态方法也称为类方法,该类方法的调用不依赖对象,可以直接通过类
名调用。
假设静态成员方法中能够访问普通数据成员,静态方法中有this引用,我
们来看下面场景:我们通过类名调用static方法,此时根本不会传递对象的
地址值给this引用,static方法内部也无法找到普通数据成员对应的内存空
间进行取值,这与我们要实现的功能是矛盾的。
故而,大家记住结论:静态只能访问静态。
1.4 代码块
在Java中,使用 { } 括起来的代码被称为代码块,可以分为三类:
局部代码块
构造代码块
静态代码块
1)局部代码块
位置:类的方法中定义
格 式 : { 语 句 体 ; }
作用:用于限定变量的生命周期,使其尽早释放,从而提高栈空间内存利
用率
重要程度:了解,使用不多
案例展示:
public class Test0104_LocalBlock {
/*
局部代码块
位置:方法中定义
作用:限定变量的生命周期,及早释放,提高内存利用率
*/
public static void main(String[] args) {
//局 部 代 码 块
{
int a = 10;
System.out.println(a);
}
//上面局部代码块中的a是局部变量,遇到 } 其占用的内存空间就销毁了
//故而,下面输出语句编译报错
//System.out.println(a); }
}
2)构造代码块
位置:类中方法外
格 式 : { 语 句 体 ; }
特点:每次构造方法执行前,都会先执行该代码块中代码
作用:如果多个构造方法中出现相同代码,可抽取到该块中,从而提高代
码复用性
重要程度:掌握
案例展示:
//基 础 类
class Module
{ private int
num;
//构 造 |匿 名 代 码 块
{
S y s t e m .o u t .println ( "构 造 代 码 块 : 构 造 方 法 执 行 前 执 行 ..."); }
//无 参 构 造 方 法
public Module()
{ System.out.println("Module() ..."
);
}
//有 参 构 造 方 法
public Module(int num)
{ System.out.println("Module(int) ...");
this.num = num;
}
}
//测 试 类
public class Test0104_ConsBlock {
public static void main(String[] args) {
//实例化2个对象,观察程序输出效果:
//每次构造方法执行前,都会自动执行构造代码块
Module m1 = new Module();
Module m2 = new Module(10); }
}
3)静态代码块
位置:类中方法外
格 式 : s t a t i c { 语 句 体 ; }
特点:需要通过static关键字修饰,随着类的加载而加载,只执行一次
作用:类加载时做一些数据的初始化操作(一般给static成员赋值)
重要程度:掌握
案例展示:
基础类定义:
注意:去com.briup.chap06.bean下创建类School,避免与原来的School冲
突。
package com.briup.chap06.bean;
/*
静 态 代码 块:
位置:类中方法外定义
特点:需要通过static关键字修饰,随着类的加载而加载,并且只执行一次
作用:在类加载的时候做一些数据初始化的操作
*/
//学 校 类
public class School {
p r i v a t e S t r i n g n a m e ; //名 称
p r i v a t e i n t n u m ; //师 生 数 量
//static成 员 第 一 种 初 始 化 方 式 : 显 式 初 始 化
p r i v a t e s t a t i c S t r i n g l i b r a r y = " 栋 梁 图 书 馆 " ;
//静态代码块:随着类加载而加载,只执行一次
//static成 员 第 二 种 初 始 化 方 式
static {
/ / l i b r a r y = " 凌 云 图 书 馆 ";
S y s t e m . o u t . p r i n t l n ( " s t a t i c 代 码 块 执 行 . . . " );
}
//构 造 代 码 块
{
S y s t e m .o u t .p ri n t l n ( " 构 造 代 码 块 执 行 .. ." );
}
public School()
{ System.out.println("School()..."
);
}
public School(String name, int num)
{ System.out.println("School(String,int)...");
this.name = name;
this.num = num; }
public void show() {
S y s t e m . o u t .p r i n t l n ( " 学 校 名 称 : " + t h i s .n a m e );
S y s t e m .o u t .p ri n t l n (" 师 生 数 量 : " + n u m );
S y s t e m . o u t .p r i n t l n ( " 图 书 馆 : " + S c h o o l . l i b r a r y );
}
}
测试类定义
import com.briup.chap06.bean.School;
public class Test0104_StaticBlock {
public static void main(String[] args)
{ School s1 = new School();
S c h o o l s 2 = n e w S c h o o l ("黄 埔 中 学 ", 2 0 0 0 );
}
}
分析运行效果可知:static代码块随着类加载会执行,且只会执行一次。
补充内容:static成员显式初始化和静态代码块,执行顺序问题
结论:显式初始化和静态代码块,谁在上面,谁先执行!
1.5 类加载时机
加载类的含义:JVM虚拟机将该类的class文件加载到内存的方法区中。
当Java代码中用到某个类的时候,就会加载该类。
加载情况列举:
程序启动,找到main方法运行,此时用到main方法所属的类,应该加载该
类
//运 行 程 序 , 先 加 载 Test.class
public class Test {
public static void main(String[] args) {
//...
}
}
第一次使用类:实例化对象
如 : School s 1 = n e w School();
第一次使用类:通过类名访问其静态数据成员
如 : S c h o o l . l i b r a r y = " 凌 志 楼 " ;
第一次使用类:通过类名调用其静态成员方法
如 : S c h o o l . s e t L i b r a r y ( " 逸 夫 楼 ") ;
其他情况后续补充
1.6 对象创建过程
public static void main(String[] args) {
S c h o o l s = n e w S c h o o l ("一 中 ",3 0 0 0 );
}
请描述 School类对象s 的创建和初始化过程:
对 School 类进行类加载
去方法区中的静态区为 School.library 分配内存空间,并做默认初始化
(值为 null )
对 School.library 进行显式初始化,执行 School 类中的静态代码块
注意:谁在前面则谁先执行
在栈空间(main方法函数帧)开辟一块内存,用 s(引用变量) 标识
堆区中分配对象的内存空间,同时进行默认初始化
对 School 中的属性进行显式初始化
例 如 : public int n u m = 200 0 ;
执行 School 类的构造代码块
执行 School 类的构造方法
把对象堆空间的内存地址赋给变量s(写入s那块内存区域中)
至此:对象成功创建,并初始化成功!
案例展示:
基础类:跟之前基本保持一致,注意下面第5、21行
//学 校 类
public class School
{ private String
name;
//显 式 初 始 化
private int num = 2000;
//静 态 代 码 块
static {
l i b ra ry = "凌 云 图 书 馆 ";
S y s t e m . o u t . p r i n t l n ( " s t a t i c 代 码 块 执 行 . . . " );
}
//显 式 初 始 化
p r i v a t e s t a t i c S t r i n g l i b r a r y = " 栋 梁 图 书 馆 " ;
{
S y s t e m .o u t .p ri n t l n ( " 构 造 代 码 块 执 行 .. ." );
}
public School(String name, int num) {
//注意:this.num输 出的 值 为 1000,说 明 显式初始 化早与 构造 方法执
行
System.out.println("School(String,int)... num: " +
this.num);
this.name = name;
this.num = num;
}
/ / . . . 省 略 }
测试类
package com.briup.chap06.test;
import com.briup.chap06.bean.School;
public class Test0106_ObjCreation {
public static void main(String[] args)
{ S c h o o l s = n e w S c h o o l ("黄 埔 中 学 ",
1 0 0 0 ); s.show();
}
}
运行结果:
s t a t i c 代 码 块 执 行 ...
构 造 代 码 块 执 行 ...
School(String,int)... num: 2000
学校名称: 黄埔中学
师 生 数量 : 1000
图书馆: 栋梁图书馆
1.7 静态导入
JDK1.5中增加了静态导入的功能,具体描述如下:
在自定义类中,要使用另一个类中的静态属性和静态方法,可以使用静态
导入。
导入完成后,可以直接使用这个类中的静态属性和静态方法,而不用在前
面加上类名。
实际开发时,大家能看得懂静态导入相关代码即可,不强制要求使用静态导
入。
静态导入格式:
导 入 静 态 成 员 : i mpo r t static 类 的 全 包 名 .static数 据 成 员 名 ;
导 入 静 态 方 法 : i mpo r t static 类 的 全 包 名 .static成 员 方 法 名 ;
案例展示:
没有使用静态导
public class Test0107_StaticImport {
public static void main(String[] args) {
//访问Math类中的静态属性PI,表示圆周率π
System.out.println(Math.PI);
//访 问 Math类 中 的 静 态 方 法 random(),生 成 随机 数
System.out.println(Math.random());
}
}
使用静态导入的情况
package com.briup.chap06.test;
import static java.lang.Math.PI;
import static java.lang.Math.random;
public class Test0107_StaticImport {
public static void main(String[] args) {
//访问Math类中的静态属性PI,表示圆周率π
System.out.println(PI);
//访 问 Math类 中 的 静 态 方 法 random(),生 成 随 机 数
System.out.println(random()); }
}
观察上述案例可知,使用静态导入可以简化我们的代码。
2 继承
类和类之间的关系有很多种,继承是其中一种,除此之外还有依赖、组
合、聚合等关系
2.1 概念理解
继承是面向对象三大特征之一,可以有效的提高代码的复用性。
生活中继承的案例有很多,例如:
继承关系体现的是一种 "is a" 的关系!
例如,蛇继承了爬行动物,爬行动物继承了动物,我们就可以描述:蛇 is a
爬行动物,爬行动物是动物中的一种。
2.2 继承实现
子类继承父类,使用 extends 关键字实现。
格式:
[p u b l i c ] c l a s s 子 类 名 e x t e n d s 父 类 名
{子类新 增内容;
}
在继承关系中,父类也称基类、超类;子类也称派生类。
案例展示:
//定 义 Person类 , 作 为 父 类
class Person {
private String name;
public void setName(String name)
{ this.name = name;
}
public void sayHello()
{ System.out.println("hello~ I am " +
name);
}
}
//定 义 Student类 , 作 为 子 类 继 承 Person
class Student extends Person {
// 新 增 内 容 可 以 为 空
}
public class Test0202_Basic {
// 测 试 代 码
public static void main(String[] args) {
// 实 例 化 一个 子类 对 象
Student stu = new Student();
// setName方法从父类中继承而来
stu.setName("tom");
// sayHello方 法 从 父 类 中 继 承 而 来
stu.sayHello();
}
}
2.3 继承特点
1)Java只支持单继承,不支持多继承
//错误示例:编译报错,一个类只能有且只有一个父类
p ub l i c c l a s s 博 士 生 e x t e n d s 老 师 ,学 生 {
}
2)Java支持多层继承
子类A 继承 父类B ,父类B 继承 爷爷类C
class Grandpa {
public void farming() {
S y s t e m .o u t .p ri n t l n ("爷 爷 喜 欢 种 地 ");
}
}
class Father extends Grandpa {
// Father类 继 承 父 类 中 farming 方 法
//子 类 新 增 方 法
public void fishing() {
S y s t e m . o u t .p ri n t l n ("爸 爸 爱 钓 鱼 ");
}
}
class Son extends Father {
// S o n 类 继 承 父 类 中 farming 方 法 和 fishing方 法
//子 类 新 增 方 法
public void skidding() {
S y s t e m . o u t .p ri n t l n ("孙 子 爱 轮 滑 ");
}
}
//多 层 继 承 测 试
public class Test0203_MulExtends {
public static void main(String[] args) {
Son s = new Son();
s.farming();
s.fishing();
s.skidding();
}
}
2.4 继承细节
子类只能继承父类所有非私有的成员(含成员方法和成员变量)
Java官方文档描述:从继承的概念来说,private修饰的成员不能被继承。
官网描述:
https://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html
案例展示:
父类Animal:
//提 供 父 类
public class Animal {
//成员一个 私有,一个 公有
private String color;
public int age;
//提 供 g e t | s e t 方 法
public String getColor()
{ return color;
}
public void setColor(String color)
{ this.color = color;
}
public int getAge() {
System.out.println("in Animal getAge() ...");
return age;
}
public void setAge(int age)
{ this.age = age;
}
}
子类Dog:
package com.briup.chap06.bean;
//定 义 Dog子 类 继 承 Animal
public class Dog extends Animal {
// 父 类 中的 private成员 ,子 类 中不 可以 直 接操作 ( 无直 接 访问 权限 )
// Animal: private String color;
// 父 类 中 的 非 private成 员 , 子 类 中 可 以 直 接 操 作
// Animal: public int age;
// 子 类 新 增 属 性
private String name;
// 子 类 新 增 get|set方 法
public void setName(String name)
{ this.name = name;
}
public String getName()
{ return name;
}
// 子类新增方法:输出子类对象所有属性(含新增属性、从父类继承)
public void disp() {
// 子 类 方 法 中 不 能 直 接 操 作 父 类 private成 员
/ / S y s t e m . o u t . p r i n t l n ( " 继 承 c o l o r : " + c o l o r ) ; e r r o r
// 子 类 方 法 中 可 以 间 接 操 作 (借 助 public的 get|set) 父 类 private 成
员
S y s t e m . o u t . p r i n t l n ( " 继 承 c o l o r : " + g e t C o l o r ( ) ) ;
// 子 类 方 法 中 可 以 直 接 操 作 父 类 非 private成 员
S y s t e m . o u t .p r i n t l n ( " 继 承 a g e : " + a g e );
S y s t e m .o u t .p r i n t ( "新 增 : " + n a m e );
}
}
测试案例:
package com.briup.chap06.test;
import com.briup.chap06.bean.Animal;
import com.briup.chap06.bean.Dog;
public class Test0204_AccessMember {
public static void main(String[] args)
{ Dog d = new Dog();
//子 类 对 象 调 用 从 父 类 继 承 的 public方 法
d.setColor("yellow");
d.setAge(2);
//子类对象 调用新增的方法
d .s e t N a m e ( "虎 子 ");
d.disp();
}
}
子类对象内存构成:
注意事项:
从内存角度来分析,父类中的private成员,子类实际上也完全继承了下来
(非专业描述)
非专业描述:完全继承、有限访问
新皇登基,全部继承(含private后宫),但不能直接访问,间接方式操作
(武才人)
父类private成员为父类私有,在父类成员方法中可以直接访问,在其他类
中(含子类),不能直接操作
子类中不能直接操作父类private成员,但是可以通过间接方式(比如借助
父类public的get|set方法)操作
额外补充:
Java类,如果定义时没有指定父类,那么这个类会默认继承Object类!
//默 认 继 承 了 Object, 编 译 后 会 自 动 生 成 extends Object 的 代 码 语 句
public class Student {
}
Object类描述如下:
上图注意事项:
Object类中没有属性
绿色圆点表示public修饰方法
黄色菱形表示protected修饰方法
红色方块表示private修饰方法
Java中的每个类都直接或间接继承Object类,Object类是Java继承体系中的最
顶层父类。
2.5 应用场景
使用继承,需要考虑类与类之间是否存在 is a 的关系,不能盲目使用继
承!
从语法上讲,一个类可以继承任何的另一个类,只要满足语法要求即可。但实
际开发中,只有具有 is a 的关系两个类,才会考虑是否要使用继承!
多个类中存在相同属性和行为,且类和类之间具有 is a 的关系,就可以将公
共的内容抽取到单独一个类中(父类),那么这些类(子类)就不需要再定义
这些属性和行为,只要继承同一个类(父类),它们就可以直接访问父类中的
非私有的属性和方法。
2.6 优缺点
优点:
提高代码的复用性
提高了代码的维护性(父类修改后,子类全部生效)
让类与类之间产生了 is a 的关系,是多态的前提
弊端:
继承是侵入性的
继承让类与类之间产生了关系,类的耦合性增强了
代码与代码之间存在关联都可以称之为" 耦合 "
降低了代码的灵活性
当父类发生变化时子类会跟着变化,削弱了子类的独立性
3 super
问题引入:
从父类继承的非private成员,和子类新增的成员重名,如何在子类成员方法
中区分两个成员?
答:
借助super关键字可以实现区分!
具体用法:
super.数据成员 ,表示从父类继承的数据成员
super.成 员 方 法 (实 参 列 表 ) , 表示 调用 从 父类 继 承的 成 员方 法
super表示子类对象中从父类继承的那部分(可看成一个父类对象)引用!
super内存结构图如下:
案例展示:
修改父类代码:
//提 供 父 类
public class Animal {
//对age进行显示初始化,其他代码不动
public int age = 10;
/ / . . . 省 略 }
修改子类代码:
package com.briup.chap06.bean;
//定 义 Dog子 类 继 承 Animal
public class Dog extends Animal {
//子类新增数据成员并做显式初始化,成员名和从父类继承的一样
private int age = 20;
//新增方法,其形参名(局部变量)和数据成员名一样
public void memberAccess(int age) {
System.out.println("age: " + age);
System.out.println("this.age: " + this.age);
System.out.println("super.age: " + super.age);
}
//新增重载 方法,其形参为空
public void memberAccess() {
System.out.println("age: " + age);
System.out.println("this.age: " + this.age);
System.out.println("super.age: " + super.age);
}
//其 他 代 码 不 变 , 省 略 ...
}
测试类:
package com.briup.chap06.test;
public class Test0203_AccessMember {
//修 改 main方 法 如 下
public static void main(String[] args)
{ Dog d = new Dog();
d.memberAccess(30);
System.out.println("------------");
d.memberAccess(); }
}
运行效果:
在子类方法中访问变量,会遵循就近原则,具体如下:
1. 先在子类局部范围中查找局部变量
2. 再在子类中查找新增的成员变量
3. 最后从父类继承的成员变量中查找
如果一定要使用从父类继承的成员,可以通过super关键字,进行区分。
super 总结:
super 关键字的用法和 this 关键字的用法相似
this:代表本类对象的引用
super:代表父类存储空间的标识(可以理解为父类对象引用)
4 构造方法
思考:如何给子类对象进行初始化?
子类对象的数据成员包含两部分:继承部分,新增部分
对新增部分数据成员初始化,子类构造方法中直接 this.新增数据成员 =
值 ; 即 可
对继承部分数据成员初始化
子 类 构 造 方 法 中 直 接 this.继 承 数 据 成 员 = 值 ;
Bug:父类中数据成员如果private修饰,则子类中没有权限直接访问
子类构造方法中采用 this.setXxx(值); 对继承部分数据成员进行初
始化
Bug:过于繁琐,如果从父类继承的成员很多,要写很多set方法
子类对象的初始化(构造方法中),要借助父类的构造方法,对父类部分成员
进行初始化
子类构造方法通过super关键字调用父类构造方法
格 式 : super(实 际 参 数 列 表 );
子类构造方法前,会优先找到父类构造方法调用,对父类继承部分成员进
行初始化
父类部分初始化完成后,再执行子类构造方法代码
如果子类构造方法中没有显式通过 super 调用父类构造方法,则系统会默
认 调 用 super()
案例展示:
//定 义 父 类
class Fu {
//1个 私 有 成 员
private int f;
//2个 构 造 方 法
public Fu() {
System.out.println("in Fu() ..."); }
public Fu(int f)
{ System.out.println("in
Fu(int) ...");
this.f = f; }
//对 应 的 ge t| set 方 法
public int getF()
{ return f;
}
public void setF(int f)
{ this.f = f;
}
}
//定 义 子 类
class Zi extends Fu {
//新 增 1个 数 据 成 员
private int z;
//子 类 构 造 方 法
public Zi() {
//如 果 不显 式 调用 super,则 默 认调 用父 类 无参构 造 器
//super();
System.out.println("in Zi() ...");
}
public Zi(int z)
{ super(10);
System.out.println("in Zi(int) ...");
this.z = z;
}
public Zi(int f, int z) {
//下面这行注释的代码,放开则 编译报错
//System.out.println("in Zi(int,int) ...");
//super调用,必须为子类构造方法的第一行有效代码
super(f);
System.out.println("in Zi(int,int) ...");
this.z = z; }
// 新 增 方 法
public void disp() {
//借助super可以直接访 问父 类继承部分 的成员
System.out.println("super.f: " + super.getF());
//借 助 this, 会 先 去 找 子 类 新 增 getF(),如 果 找 不 到 , 再 去 父 类 继 承 部 分
查找
System.out.println("this.f: " + this.getF());
System.out.println("Zi.z: " + z);
}
}
public class Test04_Init {
public static void main(String[] args)
{ Zi z1 = new Zi();
z1.disp();
System.out.println("---------------");
Zi z2 = new Zi(20);
z2.disp();
System.out.println("---------------");
Zi z3 = new Zi(100,200);
z3.disp();
}
}
运行效果:
注意1:子类构造方法中如果显式调用super(实参列表),则该代码必须为第一
行有效代码!
注意2:子类构造方法中显式调用的super(实参列表),父类中必须提供,否则
编译报错!
我猜你看到这里就困了,所以下章继续昂!