第三章 –– 继承的概念
一丶继承的概念
(1) 多个类中存在相同属性和行为时, 将这些内容抽取到单独一个类中, 那么多个类无需再定义这些属性和行为, 只要继承那个类即可
(2) 和构造代码块区分, 构造代码块针对的是对象的相同属性进行初始化, 继承这是针对多个类相同的行为和属性提取到同一个类中的行为
二丶继承的好处和坏处
1.好处
(1) 提高了代码的复用性(最大的好处)
(2) 提高了代码的维护性
(3) 让类与类产生了关系, 是多态的前提(也是它的弊端)
类的耦合性增强了,
开发原则: 低耦合, 高内聚
耦合: 类与类之间的关系
内聚: 就是自己完成某件事情的能力
2.坏处
(1) 破坏了封装性
(2) 类与类之间产生了关系, 高耦合了
总结: 不要乱用继承
三丶Java只支持类的单继承, 但支持多继承接口类
四丶开发中什么使用继承???
采用假设法
如果两个类, A,B 只有他们符合 A是B 的一种, 或者 B 是 A 的一种我们就可以考虑使用继承
五丶继承中成员变量的关系
1. 子类中的成员变量和父类中的成员变量名称不同, 这个太简单了
2. 子类中的成员变量和父类中的成员变量的名字一样, 这个时候使用就近原则
这个时候就推出了 super 和 this
六丶super 和 this
1. 区别
(1). this 代表本类对象的引用
(2). super 代表着父类的存储空间, 可以理解为父类的引用(可以操作父类的成员和方法)
(1). this 代表本类对象的引用
(2). super 代表着父类的存储空间, 可以理解为父类的引用(可以操作父类的成员和方法)
2. 怎么用?
(1) 调用成员变量
this,成员变量
super.成员变量
(2) 调用构造方法
this(...) 调用本类的构造方法
super(...) 调用父类的构造方法
但是他们不能同时显示调用, 会出错的, 这两个方式调用都必须要在第一行中才能够使 用导致了冲突
(3) 调用成员方法
this.成员方法
super.成员方法
七丶继承中构造方法的关系
1. 子类中所有的构造方法默认都会访问父类中空参数的构造方法
2. 为什么???
因为子类会继承父类中的数据, 可能还会使用父类的数据,所以子类初始化之前, 一定要完成为父类数据的初始化, 注意: 子类每一个构造方法的第一条默认都调用了 super()
注意: super必须放在子类构造函数的第一行, 如果不是的话, 子类也会在第一行调用super然后再是我们写的super, 所以这里的话, 调用了两次 super
拓展:
在c++中, 子类构造函数执行时会调用默认的无参构造函数, 但是如果使用了构造函数的初始化成员列表的话, 这样子的话就会调用我们初始化成员列表上面的构造函数, 而不再默认调用父类的无参构造函数
面试要点1:
首先要注意的点:
1. 成员变量 ---------- 就近原则
2. this 和 super 的问题
this 本类对象
super 父类对象
3. 子类构造方法执行前默认先执行父类的无参构造函数方法
4. 一个类的初始化过程
1) 成员变量初始化
默认初始化
显示初始化
构造方法初始化
面试要点2:
1. 一个类的静态代码块, 构造代码块, 构造方法的执行流程
静态代码块 > 构造代码块 > 构造方法
2. 静态的内容是随着类的加载而加载
静态代码块的内容会优先执行
3. 子类初始化之前先会优先执行父类的初始化
--------------------------------------------------------------------------------
package com.bagiao.Test;
import org.junit.Test;
// JVM 的加载器在加载执行 Zi z = new Zi(); 的 Zi z 这句话的时候
// 就会让加载器去加载 Zi 但是 Zi 是 Fu 的之类, 加载器便会先加载父类 Fu
// 的类, 这个时候加载时, 发现Fu 存在 static 代码块, 就先执行 static块中
// 的代码, 然后Fu 类加载完毕后, 回到子类让加载器去加载子类Zi 的类
// 由于 static 随着类的存在而存在所以先执行 static 代码块加载完毕后回到
// Zi z = new Zi(); 发现存在 new Zi 这句话, 便会去调用子类的构造方法
// 然后通过子类的构造方法函数入口点进入父类的构造方法这时JVM会先去
// 执行构造代码块中的代码, 然后在执行父类的构造函数, 之后回到子类的构造函数入口点
// 这时会先去加载 构造代码块中的代码, 之后执行子类的构造方法, 最后放回一个对象
// 由 z 指向(引用) 这个对象
class Fu
{
static
{
System.out.println("父类的静态代码块"); // (1)
}
{
System.out.println("父类的构造代码块"); // (3)
}
public Fu() // (4)
{
System.out.println("父类的构造方法");
}
}
class Zi extends Fu
{
static
{
System.out.println("子类的静态代码块"); // (2)
}
{
System.out.println("子类的构造代码块"); // (5)
}
public Zi() // (6)
{ // 这里隐藏着一个 super() 初始化地方, { 和 super() 一样, super 并不是一个语句, 而是一个标记, 标记的作用优先执行父类
System.out.println("子类的构造方法");
}
}
public class _Main
{
@Test
public void test1()
{
Zi z = new Zi();
}
}
package com.bagiao.Test;
import org.junit.Test;
// JVM 的加载器在加载执行 Zi z = new Zi(); 的 Zi z 这句话的时候
// 就会让加载器去加载 Zi 但是 Zi 是 Fu 的之类, 加载器便会先加载父类 Fu
// 的类, 这个时候加载时, 发现Fu 存在 static 代码块, 就先执行 static块中
// 的代码, 然后Fu 类加载完毕后, 回到子类让加载器去加载子类Zi 的类
// 由于 static 随着类的存在而存在所以先执行 static 代码块加载完毕后回到
// Zi z = new Zi(); 发现存在 new Zi 这句话, 便会去调用子类的构造方法
// 然后通过子类的构造方法函数入口点进入父类的构造方法这时JVM会先去
// 执行构造代码块中的代码, 然后在执行父类的构造函数, 之后回到子类的构造函数入口点
// 这时会先去加载 构造代码块中的代码, 之后执行子类的构造方法, 最后放回一个对象
// 由 z 指向(引用) 这个对象
class Fu
{
static
{
System.out.println("父类的静态代码块"); // (1)
}
{
System.out.println("父类的构造代码块"); // (3)
}
public Fu() // (4)
{
System.out.println("父类的构造方法");
}
}
class Zi extends Fu
{
static
{
System.out.println("子类的静态代码块"); // (2)
}
{
System.out.println("子类的构造代码块"); // (5)
}
public Zi() // (6)
{ // 这里隐藏着一个 super() 初始化地方, { 和 super() 一样, super 并不是一个语句, 而是一个标记, 标记的作用优先执行父类
System.out.println("子类的构造方法");
}
}
public class _Main
{
public void test1()
{
Zi z = new Zi();
}
}
输出结果:
父类的静态代码块
子类的静态代码块
父类的构造代码块
父类的构造方法
子类的构造代码块
子类的构造方法
父类的静态代码块
子类的静态代码块
父类的构造代码块
父类的构造方法
子类的构造代码块
子类的构造方法
--------------------------------------------------------------------------------
3丶分层初始化
面试要点3:
--------------------------------------------------------------------------------
class X
{
// 这个相当于初始化成员列表, 或者是构造代码块, 比构造方法要早的初始化
Y b = new Y();
X()
{
System.out.println("X");
}
}
class Y
{
Y()
{
System.out.println("Y");
}
}
public class Z extends X
{
Y y = new Y(); // 构造代码块
Z()
{
// 这个 super 根本就不是一句语句, 而是一个标记, 这个标记在构造函数中
// 的第一行, 表示在调用构造函数之前必须先自行super 标记的地方
// this () 也一样
super();
System.out.println(y);
System.out.println("Z");
}
public static void main(String[] args)
{
new Z();
}
}
class X
{
// 这个相当于初始化成员列表, 或者是构造代码块, 比构造方法要早的初始化
Y b = new Y();
X()
{
System.out.println("X");
}
}
class Y
{
Y()
{
System.out.println("Y");
}
}
public class Z extends X
{
Y y = new Y(); // 构造代码块
Z()
{
// 这个 super 根本就不是一句语句, 而是一个标记, 这个标记在构造函数中
// 的第一行, 表示在调用构造函数之前必须先自行super 标记的地方
// this () 也一样
super();
System.out.println(y);
System.out.println("Z");
}
public static void main(String[] args)
{
new Z();
}
}
输出结果
Y
X
Y
Z
--------------------------------------------------------------------------------
总结下来便是:
注意:
1. 在执行构造函数时, 会先去执行第一行的 super 之后再初始化这个类的成员变量, 也就是先初始化父类的成员变量之后再初始化子类的成员
Super的注意点:
虽然子类中构造方法默认有一个super()
初始化的时候, 不是按照那个顺序进行的
而是按照分层初始化进行的
它仅仅表示先初始化父类数据, 在初始化子类数据
注意: 方法重写和方法重载的方法
首先方法的重写便是
函数名相同, 函数参数相同
其次方法的重载是
函数名字相同, 函数的参数不同, (但是函数的返回值不相同)
当相同的函数签名存在但函数的返回值不同时, 这个时候便会报错
首先方法的重写便是
函数名相同, 函数参数相同
其次方法的重载是
函数名字相同, 函数的参数不同, (但是函数的返回值不相同)
当相同的函数签名存在但函数的返回值不同时, 这个时候便会报错
八丶方法重写的注意事项
1. 父类私有化方法不能被重写
因为父类私有化方法子类根本就无法继承
2. 子类重写父类方法时, 访问权限不能更低, 最好一致
3. 父类静态方法, 子类也必须通过静态方法进行重写
其实这个算不上方法重写, 但是现象确实如此, 至于为什么算不上方法重写, 看多态
九丶面试题
1. 方法重写成立的条件
(1) 发生在父类与子类之间(作用域)
(2) 子类存在一个函数的参数丶函数名和参数和父类相同
2. 方法重载成立的条件
(1) 发生在同一个作用域之间
(2) 方法的名字相同但是它的参数列表不同(函数签名的问题)
(3) 方法重载和返回值没多大关系, 函数返回值不同可以同时存在,c++也可以存在
3. this和super 的区别
(1) this 表示该对象的引用
(2) super 表示标识父类对象的存储空间
(1) this 表示该对象的引用
(2) super 表示标识父类对象的存储空间
其中注意下:
super 这个是关键字, 它不是一个函数, 是一个标记, 放在子类的第一行的话表示的是告诉JVM去调用父类的构造函数或者方法属性, 说白了你可以把super和隐藏的super当成是构造方法的 { 这个括号
this() 必须放在构造函数的第一行和显示调用 super 一样, 所以 super 和 this 不能同时调用用
Day01总结:
--------------------------------------------------------------------------------
1:如何制作帮助文档(了解)
(1)写一个类
(2)加入文档注释
(3)通过javadoc工具生成即可
javadoc -d 目录 -author -version ArrayTool.java
2:通过JDK提供的API学习了Math类(掌握)
(1)API(Application Programming Interface)
应用程序编程接口(帮助文档)
(2)如何使用呢?
请参照
day08\code\02_如何使用JDK提供的帮助文档\如何使用帮助文档.txt
(3)Math类
A:是针对数学进行操作的类
B:没有构造方法,因为它的成员都是静态的
C:产生随机数
public static double random(): [0.0,1.0)
public static double random(): [0.0,1.0)
D:如何产生一个1-100之间的随机数
int number = (int)(Math.random()*100)+1;
int number = (int)(Math.random()*100)+1;
E:猜数字小游戏
3:代码块(理解)
(1)用{}括起来的代码。
(2)分类:
A:局部代码块
用于限定变量的生命周期,及早释放,提高内存利用率。
B:构造代码块
把多个构造方法中相同的代码可以放到这里,每个构造方法执行前,首先执行构造代码块。
C:静态代码块
对类的数据进行初始化,仅仅只执行一次。
(3)静态代码块,构造代码块,构造方法的顺序问题?
静态代码块 > 构造代码块 > 构造方法
4:继承(掌握)
(1)把多个类中相同的成员给提取出来定义到一个独立的类中。然后让这多个类和该独立的类产生一个关系,
这多个类就具备了这些内容。这个关系叫继承。
(2)Java中如何表示继承呢?格式是什么呢?
A:用关键字extends表示
B:格式:
class 子类名 extends 父类名 {}
(3)继承的好处:
A:提高了代码的复用性
B:提高了代码的维护性
C:让类与类产生了一个关系,是多态的前提
(4)继承的弊端:
A:让类的耦合性增强。这样某个类的改变,就会影响其他和该类相关的类。
原则:低耦合,高内聚。
耦合:类与类的关系
内聚:自己完成某件事情的能力
B:打破了封装性
(5)Java中继承的特点
A:Java中类只支持单继承
B:Java中可以多层(重)继承(继承体系)
(6)继承的注意事项:
A:子类不能继承父类的私有成员
B:子类不能继承父类的构造方法,但是可以通过super去访问
C:不要为了部分功能而去继承
(7)什么时候使用继承呢?
A:继承体现的是:is a的关系。
B:采用假设法
(8)Java继承中的成员关系
A:成员变量
a:子类的成员变量名称和父类中的成员变量名称不一样,这个太简单
b:子类的成员变量名称和父类中的成员变量名称一样,这个怎么访问呢?
子类的方法访问变量的查找顺序:
在子类方法的局部范围找,有就使用。
在子类的成员范围找,有就使用。
在父类的成员范围找,有就使用。
找不到,就报错。
B:构造方法
a:子类的构造方法默认会去访问父类的无参构造方法
是为了子类访问父类数据的初始化
b:父类中如果没有无参构造方法,怎么办?
子类通过super去明确调用带参构造
子类通过this调用本身的其他构造,但是一定会有一个去访问了父类的构造
让父类提供无参构造
C:成员方法
a:子类的成员方法和父类中的成员方法名称不一样,这个太简单
b:子类的成员方法和父类中的成员方法名称一样,这个怎么访问呢?
(9)两个面试题:
A:Override和Overload的区别?Overload是否可以改变返回值类型?
B:this和super的区别和各自的作用?
(10)数据初始化的面试题
A:一个类的初始化过程
B:子父类的构造执行过程
C:分层初始化
(11)案例:
A:学生和老师案例
继承前
继承后
B:猫狗案例的分析和实现
--------------------------------------------------------------------------------
九丶final 修饰符的使用
// (1) 首先 final 可以修饰类, 方法, 变量
// (2) 被修饰的东西, 被标记为最终形态
// 1) 修饰类的话, 该类 不能被继承
// 2) final 修饰方法, 该方法不能被重写(覆盖, 复写)
// 3) final 可以修饰变量, 被修饰的变量将成为常量
// 常量分两类
// (1) 字面量常量
// "hello" 10 true
// (2) 自定义常量
// final int x = 10;
// 修饰变量, 只能被赋值一次
// 修饰方法, 不能被重写
// (1) 首先 final 可以修饰类, 方法, 变量
// (2) 被修饰的东西, 被标记为最终形态
// 1) 修饰类的话, 该类 不能被继承
// 2) final 修饰方法, 该方法不能被重写(覆盖, 复写)
// 3) final 可以修饰变量, 被修饰的变量将成为常量
// 常量分两类
// (1) 字面量常量
// "hello" 10 true
// (2) 自定义常量
// final int x = 10;
// 修饰变量, 只能被赋值一次
// 修饰方法, 不能被重写
例子 final 的面试题
package com.bangiao.Final04;
import org.junit.Test;
// 总结下:
/*
* 这个 final final修饰谁, 谁就是最终变量
* 这里的 final Student ss = new Student(); ss 这个变量, 而不是它的值
* 所以 ss = new Student(); 这个的话是错误的, 这个和 c++ 中的
* const int x = 10; 修饰的是 x
* const Student s = Student();
* 这里修饰的是 s
* const int * p = null;
* 这里修饰的 是 int * 这个值 这个是底层const
* int * const p = null;
* 这个修饰的是 p 这个变量 这个是一个顶层 const
* */
class Student
{
int age = 10;
}
public class TestFinal面试题
{
@Test
public void test1()
{
int x = 10;
x = 100;
final int y = 100;
// y = 19;
Student s = new Student();
System.out.println(s.age);
s.age = 100;
System.out.println(s.age);
final Student ss = new Student();
System.out.println(ss.age);
ss.age = 100;
System.out.println(ss.age);
// ss = new Student();
}
}
package com.bangiao.Final04;
import org.junit.Test;
// 总结下:
/*
* 这个 final final修饰谁, 谁就是最终变量
* 这里的 final Student ss = new Student(); ss 这个变量, 而不是它的值
* 所以 ss = new Student(); 这个的话是错误的, 这个和 c++ 中的
* const int x = 10; 修饰的是 x
* const Student s = Student();
* 这里修饰的是 s
* const int * p = null;
* 这里修饰的 是 int * 这个值 这个是底层const
* int * const p = null;
* 这个修饰的是 p 这个变量 这个是一个顶层 const
* */
class Student
{
int age = 10;
}
public class TestFinal面试题
{
public void test1()
{
int x = 10;
x = 100;
final int y = 100;
// y = 19;
Student s = new Student();
System.out.println(s.age);
s.age = 100;
System.out.println(s.age);
final Student ss = new Student();
System.out.println(ss.age);
ss.age = 100;
System.out.println(ss.age);
// ss = new Student();
}
}
final 修饰基本数据类型的值不能发生改变, 但是final修饰的变量之只能被初始化一次
引用类型的地址不能发生改变, 但是堆的值是可以被改变的
--------------------------------------------------------------------------------
十丶final修饰变量的初始化时机
(1)被final 修饰的变量只能赋值初始化一次
(2) 在构造方法完毕前(非静态的常量)