一、继承
1.继承概述
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
通过extends关键字可以实现类与类的继承
class 子类名 extends 父类名 {}
单独的这个类称为父类,基类或者超类;这多个类可以称为子类或者派生类。
2.继承的优点与缺点
①提高了代码的复用性
多个类相同的成员可以放到同一个类中
②提高了代码的维护性
如果功能的代码需要修改,修改一处即可
③让类与类之间产生了关系,是多态的前提
其实这也是继承的一个弊端:类的耦合性很强
开发原则:低耦合,高内聚
耦合:类与类的关系
内聚:独立完成某件事情的能力
3.Java中继承的特点
①Java只支持单继承,不支持多继承。
//一个类只能有一个父类,不可以有多个父类。
class SubDemo extends Demo{} //ok
class SubDemo extends Demo1,Demo2...//error
②Java支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}
4.Java中继承的注意事项
①子类可以继承父类的所有属性和方法,包括私有属性和方法,但是子类是不可以直接的访问父类的private属性和方法。
无论父类中的成员变量是私有的、共有的、还是其它类型的,子类都会拥有父类中的这些成员变量。但是父类中的私有成员变量,无法在子类中直接访问,必须通过从父类中继承得到的protected、public方法(如getter、setter方法)来访问。
②子类不能继承父类的构造方法,但是可以通过super关键字去访问父类构造方法。
不要为了部分功能而去继承。
class A
{
public void show1(){}
public void show2(){}
}
class B
{
public void show2(){}
public void show3(){}
}
//我们发现B类中出现了和A类一样的show2()方法,所以,我们就用继承来体现
class B extends A
{
public void show3(){}
}
//这样其实不好,因为这样你不但有了show2(),还多了show1()。
我们到底在什么时候使用继承呢?
继承中类之间体现的是:”is a”的关系。
5.继承中成员变量
①子类中的成员变量和父类中的成员变量名称不一样
②子类中的成员变量和父类中的成员变量名称一样
在子类方法中访问一个变量的查找顺序:
a:在子类方法的局部范围找,有就使用
b:在子类的成员范围找,有就使用
c:在父类的成员范围找,有就使用
d:如果还找不到,就报错。
6.this和super区别和应用
区别
-this代表本类对应的引用。
-super代表父类存储空间的标识(可以理解为父类引用,可以操作父类的成员)。
应用
①调用成员变量
-this.成员变量 调用本类的成员变量。
-super.成员变量 调用父类的成员变量。
②调用构造方法
-this(…) 调用本类的构造方法。
-super(…) 调用父类的构造方法。
③调用成员方法
-this.成员方法 调用本类的成员方法。
-super.成员方法 调用父类的成员方法。
7.继承中构造方法的关系
①子类中所有的构造方法默认都会访问父类中空参数的构造方法。
原因:
因为子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化。
每一个构造方法的第一条语句默认都是:
super()[表示调用父类的无参构造]
②如果父类没有无参构造方法,那么子类的构造方法会出现报错现象。
解决方法:
A:在父类中加一个无参构造方法
B:通过使用super关键字去显示的调用父类的带参构造方法
C:子类通过this去调用本类的其他构造方法。
-子类中一定要有一个去访问了父类的构造方法,否则父类数据就没有初始化。
super(…)或者this(….)必须出现在第一条语句上,否则,就会有父类数据的多次初始化
class Father
{
/*
public Father()
{
System.out.println("Father的无参构造方法");
}
*/
public Father(String name)
{
System.out.println("Father的带参构造方法");
}
}
class Son extends Father
{
public Son()
{
super("随便给");
System.out.println("Son的无参构造方法");
//super("随便给");
}
public Son(String name)
{
//super("随便给");
this();
System.out.println("Son的带参构造方法");
}
}
思考题:
思考1:
看程序写结果
看程序写结果:
class Fu {
static {
System.out.println("静态代码块Fu");
}
{
System.out.println("构造代码块Fu");
}
public Fu() {
System.out.println("构造方法Fu");
}
}
class Zi extends Fu {
static {
System.out.println("静态代码块Zi");
}
{
System.out.println("构造代码块Zi");
}
public Zi() {
System.out.println("构造方法Zi");
}
}
class ExtendsTest2 {
public static void main(String[] args) {
Zi z = new Zi();
}
}
答:
A:一个类的静态代码块,构造代码块,构造方法的执行流程
静态代码块 > 构造代码块 > 构造方法
B:静态的内容是随着类的加载而加载
静态代码块的内容会优先执行
C:子类初始化之前先会进行父类的初始化
结果是:
静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi
—————————————————————————
思考二
看程序写结果:
class X
{
Y b = new Y();
X() {
System.out.print("X");
}
}
class Y
{
Y() {
System.out.print("Y");
}
}
public class Z extends X
{
Y y = new Y();
Z() {
//super
System.out.print("Z");
}
public static void main(String[] args)
{
new Z();
}
}
答:
A:成员变量的问题
int x = 10; //成员变量是基本类型
Student s = new Student(); //成员变量是引用类型
B:一个类的初始化过程
成员变量的初始化
默认初始化
显示初始化
构造方法初始化
C:子父类的初始化(分层初始化)
先进行父类初始化,然后进行子类初始化。
结果:
YXYZ
问题:
虽然子类中构造方法默认有一个super()
初始化的时候,不是按照那个顺序进行的。
而是按照分层初始化进行的。
它仅仅表示要先初始化父类数据,再初始化子类数据。
8.继承中成员方法的关系
①子类中的方法和父类中的方法声明不一样。
②子类中的方法和父类中的方法声明一样。
通过子类对象调用方法:
a:先找子类中,看有没有这个方法,有就使用
b:再看父类中,有没有这个方法,有就使用
c:如果没有就报错。
二、方法重写
1.方法重写概述
子类中出现了和父类中方法声明一模一样的方法。
-如果方法名不同,就调用对应的方法。
-如果方法名相同,最终使用的是子类自己的。
2.方法重写应用
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法。这样,即沿袭了父类的功能,又定义了子类特有的内容。
案例:
A:定义一个手机类。
B:通过研究,我发明了一个新手机,这个手机的作用是在打完电话后,可以听天气预报。
按照基本的设计把代码给写出。
但是,又发现新手机应该是手机,所以,它应该继承自手机。
其实这个时候的设计,并不是最好的。
因为手机打电话功能,是手机本身就具备的最基本的功能。
所以,我的新手机是不用在提供这个功能的。
但是,这个时候,打电话功能就没有了。这个不好。
最终,还是加上这个功能。由于它继承了手机类,所以,我们就直接使用父类的功能即可。
那么,通过super关键字调用使用父类的功能
class Phone
{
public void call(String name) {
System.out.println("给"+name+"打电话");
}
}
class NewPhone extends Phone
{
public void call(String name) {
//System.out.println("给"+name+"打电话");
super.call(name);
System.out.println("可以听天气预报了");
}
}
class ExtendsDemo
{
public static void main(String[] args) {
NewPhone np = new NewPhone();
np.call("石原里美");
}
}
3.方法重写注意事项
①父类中私有方法不能被重写。
-因为父类私有方法子类根本就无法继承。
②子类重写父类方法时,访问权限不能更低。
-最好就一致。
③父类静态方法,子类也必须通过静态方法进行重写。[多态]
思考题:
思考1
Override和Overload的区别?方法重载能改变返回值类型吗?
答:
方法重写:在子类中,出现和父类中一模一样的方法声明的现象。
方法重载:同一个类中,出现的方法名相同,参数列表不同的现象。
方法重载能改变返回值类型,因为它和返回值类型无关。
—————————————————————————
思考2
this关键字和super关键字分别代表什么?以及他们各自的使用场景和作用。
答:
this:代表当前类的对象引用
super:代表父类存储空间的标识。(可以理解为父类的引用,通过这个东西可以访问父类的成员)
场景:
——成员变量:
-this.成员变量
-super.成员变量
——构造方法:
-this(…)
-super(…)
——成员方法:
-this.成员方法
-super.成员方法
4.final关键字
由于继承中方法有一个现象:方法重写。所以,父类的功能,就会被子类给覆盖调。
而有些时候,我们不想让子类去覆盖掉父类的功能,只能让他使用。这个时候,针对这种情况,Java就提供了一个关键字:final
final概述
final关键字是最终的意思,可以修饰类,成员变量,成员方法。
-修饰类,类不能被继承。
-修饰变量,变量就变成了常量,只能被赋值一次。
-修饰方法,方法不能被重写。
思考题
思考1:
final修饰局部变量的问题:
class Student
{
int age = 10;
}
class FinalTest
{
public static void main(String[] args)
{
//局部变量是基本数据类型
int x = 10;
x = 100;
System.out.println(x);
final int y = 10;
//无法为最终变量y分配值
//y = 100;
System.out.println(y);
System.out.println("--------------");
//局部变量是引用数据类型
Student s = new Student();
System.out.println(s.age);
s.age = 100;
System.out.println(s.age);
System.out.println("--------------");
final Student ss = new Student();
System.out.println(ss.age);
ss.age = 100;
System.out.println(ss.age);
//重新分配内存空间
//无法为最终变量ss分配值
//ss = new Student();
}
}
答:
final修饰局部变量为基本类型时,基本类型的值不能发生改变。
final修饰局部变量为引用类型时,引用类型的地址值不能发生改变,但是,该对象的堆内存的值是可以改变的。
—————————————————————————
思考2:
final修饰变量的初始化时机
class Demo
{
//int num = 10;
//final int num2 = 20;
int num;
final int num2;
{
//num2 = 10;
}
public Demo() {
num = 100;
//无法为最终变量num2分配值
num2 = 200;
}
}
class FinalTest2
{
public static void main(String[] args)
{
Demo d = new Demo();
System.out.println(d.num);
System.out.println(d.num2);
}
}
答:
①被final修饰的变量只能赋值一次。
②在构造方法完毕前。(仅针对非静态的常量)