面向对象——继承
1、 继承
1.1 继承概念的引入
继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
代码实现:
三个类 都有重复的代码,可以把这部分内容
抽出去,抽出去放到另外一个类里面;下面的3个类和上面的类需要发生一点关系(继承),上面的类
我叫做 父类(超类,基类,根类),子类(派生类,拓展类);
好处 : 提高了代码的复用性
上面的示例 使用代码来替换一下:
1.2 Java中类继承的基本语法
① Java类中的继承的语法格式:
> class A{}
> class B extends A{}
> A 就是B的父类; B是A的子类
②
验证:子类中是否可以继承到父类中的东西(通过创建子类对象来操作从父类继承的东西)
测试类:
1.3 子类可以从父类继承哪些成员?(仅仅是从访问权限的角度比较狭隘的考虑)
① 思考 : 一个父类中可能有哪些? 字段 方法 构造方法
② 思考 : 可能还和访问权限有关 private public
③ 验证 :
非私有的字段 和方法
可以被继承到(暂时这样记忆-主要是从是否可以访问的层面考虑)
构造方法不能够被继承
非私有的字段 和方法 可以被继承到
1.4 Java中类的继承特点
① Java中类的继承只能够是单继承(一个类只能够有一个直接父类)
一个人只能够有一个亲爹
(一个双胞胎 据说有两个爹)② 可以支持多重继承(多层级的继承), 一个类可以有子类,子类还可以子类…
子子孙孙无穷尽也…
> 示例:
> class A{}
> class B extends A{}
> class C extends B{}
> class D extends C{}
> C 是D的直接父类;
> A B 都是D的间接父类
③ 每一个类都有一个直接父类,如果没有看到显示的继承代码,那么就隐式继承就Object
class A extends Object{
}
1.5 类的加载顺序(创建对象时的加载顺序)
一个类中
1、静态的 (成员变量和代码块,谁在上面,谁先被执行)
2、初始化代码块
3、构造方法
父类和子类中
1、父类的静态代码块
2、子类的静态代码块
3、父类的初始化代码块
4、父类的构造方法
5、子类的初始化代码块
6、子类的构造方法
演示
父类文件
package com.fs.pojo;
public class Father {
private String name;
// 1.加载父类上面的静态变量
private static int age = 25;
// 2.加载父类的静态代码块
static{
System.out.println("Father的静态代码块被执行了,他有:" + age + "岁");
}
// 5.父类的初始化代码块
{
System.out.println("Father的初始化代码块被调用了");
name = "马云";
}
// 6.父类的构造方法
public Father() {
System.out.println("Father的构造方法被调用了");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void setAge(int age) {
Father.age = age;
}
public static int getAge() {
return age;
}
@Override
public String toString() {
return "Father [name=" + name + "]";
}
}
子类文件
package com.fs.pojo;
public class Child extends Father {
private String name;
// 3.子类的上面的静态变量
private static int age = 5;
// 4.子类的静态代码块
static{
System.out.println("Child的静态代码块被执行了,他有:" + age + "岁");
}
// 7.子类的初始化代码块
{
System.out.println("Child的初始化代码块被调用了");
name = "小马云";
}
// 8.子类的构造方法
public Child() {
System.out.println("Child的构造方法被调用了");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void setAge(int age) {
Child.age = age;
}
public static int getAge() {
return age;
}
@Override
public String toString() {
return "Child [name=" + name + "]";
}
}
测试类
package com.fs.test;
import com.fs.pojo.Child;
import com.fs.pojo.Father;
public class LoadTest {
public static void main(String[] args) {
Father child = new Child();
System.out.println("************************");
Child child2 = new Child();
// 想一下代码执行顺序
//结果:
/*
Father的静态代码块被执行了,他有:25岁
Child的静态代码块被执行了,他有:5岁
Father的初始化代码块被调用了
Father的构造方法被调用了
Child的初始化代码块被调用了
Child的构造方法被调用了
************************
Father的初始化代码块被调用了
Father的构造方法被调用了
Child的初始化代码块被调用了
Child的构造方法被调用了
*/
}
}
1.6 继承总结
1 继承有什么作用? 可以重复利用代码,可以把公共的代码 抽出到父类里面
2 必须会写出多个类的继承关系
3 能够验证子类继承到父类的东西–》字段,方法,但是构造方法不能继承
4 记住Java中类的继承特点–》单继承(类和类之间)
5 类与类之间是继承关系 并且是单继承
6 类的加载顺序
2、 方法覆写(覆盖,重写,Override)
2.1 方法覆写的引入(为什么需要覆写方法)
我们又回到动物
2.2 方法覆写的基本语法
思考 :子类覆写了父类的方法,子类中还有(还可以调用)父类中的方法吗?
为什么需要覆写方法?
当父类中的一个行为不满足子类需要的时候,在子类中覆写父类中的方法
2.3 方法覆写的细节
方法的覆写有要求 4点:
① 保证子类方法和父类方法的方法签名(方法名+参数列表)一致;
两个方法签名一致的,形参名称 随便取;
② 访问权限 :
子类方法的访问权限(修饰符)不能够比父类方法的访问权限更低;(private < 默认不写 < public)
在编译阶段验证是否覆写: 在子类方法上面加 @Override
,让编译器来检查,如果是正确的覆写,编译通过,否则编译报错
私有的方法不能够被覆写!
③ static 方法不能够被覆写 , 思考: 为什么呢? 语法上Java规定!
计算机原理上与内存有关
④ 返回值类型相同:
子类方法的返回值类型可以是父类方法的返回值类型的子类或者相等问题代码: int long根本不是类,int也不是long的子类
下面正确的写法:
方法覆写,子类方法和父类方法主体是否可以一样, 可以! 但是没有什么用!
2.4 方法覆写总结
现在怎么做(一般的做法):
掌握内容:
①明确应该清楚为什么需要覆写(当父类方法不能满足子类的需求的时候)
②知道怎样去验证方法覆写:推荐把@Override加上;
③把父类的方法拷贝到子类,然后把方法体改了;(目前这样做,以后使用eclipse就自动生成呢)
以后会学习很多Java中写好的类(学些什么呢?)
一个类 :
字段 : 私有的内部结果,一般不用管; 全局常量,直接访问使用就可以了
,Math.PI Integer.SIZE;–》int类包装类 static SIZE方法 : 重点,一定会使用,需要记忆常用的方法;
构造方法 : 需要调用实例方法就得先调用构造方法来创建对象;
3、 Object类(及方法覆写的应用)
Object类引入
什么是object类?
我们说每一个类都有一个父类,只要没有自定义父类,它默认父类就是Object类,那我们需要来了解一下这个Object类。
3.1 认识Object类
类 Object 是类层次结构的根类,每个类都使用 Object 作为超类。(和接口没有关系)
所有对象(包括数组)都实现这个类的方法
: 所有的对象都可以调用到Object中的方法;
**比如:①Student s = new Student();**
**s.hashCode(); 可以的**
**②String s2 = “等等”;**
**s2.hashCode();可以的**
**③ int i = 5;**
**i.hashCode();//不可以的**
**④int[] arr = new int[6];**
**arr.hashCode();可以的**
3.2 介绍Object类里面的方法
(1) String toString()
返回调用此方法的当前对象的字符串表示形式(把当前对象转成字符串)
原理、getClass().getName() + ‘@’ + Integer.toHexString(hashCode())
- boolean equals(Object obj)
比较两个对象是否相等(比较调用此方法的对象是否和传入的obj”相等”)
对于引用型变量表示的是两个变量在栈中存储的地址是否相同,即堆中的内容是否相同。
(3)Int hashCode()方法,主要是用来给程序员覆写的
下面给出几个常用的哈希码的算法。
1、Object类的hashCode.返回对象的
[1]
内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。
2、String类的hashCode.根据String类包含的字符串的内容,根据一种特殊算法返回哈希码,只要字符串所在的堆空间相同,返回的哈希码也相同。
3、Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值,例如Integer
i1=new Integer(100),i1.hashCode的值就是100
。由此可见,2个一样大小的Integer对象,返回的哈希码也一样。
目的,把一个对象转换成int类型 ,用于表达
(4)Class<?> getClass() 返回此 Object
的运行时类(获得一个对象的真正的类型),可以打印试试
3.3 覆写Object中的方法-toString
① 打印对象
定义一个对象,然后打印
下面通过追踪源码(了解)
② 分析打印对象的结果为什么是 XXX@1232e3 这种格式,
看一下源码–>
①看一下Student的代码 有没有toString方法;没有toString();
②看父类Object的toString方法;
上面地址 拿到有什么作用呢?我希望 打印什么效果?
小王 17 这样的效果出来 怎么办?
在子类Student中覆写父类的toString方法,返回 name+age
③ 自己覆写toString方法
3.4 覆写Object中的方法-equals(equals 与 == 的区别)
示例代码如下: 已知有如下的学生类
①问题1 : 下面代码的比较结果是?
结果: false
原因: 凡是new出来的对象都会在堆中开辟新的空间,所有s1 和 s2
存储了不同的对象的地址值;②问题2: 使用Object的equals进行比较结果是?
结果: false
原因:
此处的结果应该由equals方法里面的规定决定,所以决定看Student类中的源码(然而并没有),继续向上找到父类Object中看源码,如下:
原来Object中定义的equals方法的比较规则就是==,如果我们想要写自己的规则(应该去比较值,才有意义),则需要在我们自己的类中覆写Object的此方法,在方法里面写我们自己的规则!
覆写toString方法:
编译器看到的obj的类型是 Object ,Object中是没有name和age的,编译报错
但是,我们知道实际调用的时候obj变量中装的是一个学生对象,
应该明确的告诉编译器这个obj是学生对象—》 obj强制转换成 Student
编译器认为 :obj中没有name,
编译器很傻,看到obj是Object类型,所有Object中是没有name的
但是我们知道obj变量中装的Student对象 ----》 把obj 转成Student
(还原它的真实类型)
版本一完善 : 传入的对象中的属性值应该使用get方法来获得
版本二完善 :
this.name 其实类型是String 是引用类型(非常特殊的引用类型) ;
使用== 比较有风险(可能比较的是地址),应该比较字符串的字面值
String类在设计的时候就于已经覆写了Object中的equals方法,比较规则就是使用的字面值进行比较
所以 上面this.name == s.getName() 应该调用String类中的equals方法
版本三完善 :
上面的代码 if中的条件
本身就是一个逻辑运算,逻辑运算表达式的结果值就是一个boolean
所以没有必要写if else
3.5 == 和 equals区别
== 和 equals 都是比较是否相等,请问它们到底有什么区别呢?
相等 : a 传统的理解一般都是数字值是否相等;
b 在程序中任何东西都是数据,都会比较是否相等
> 1、 ==
> 基本数据类型: 比较的就是值是否相等;
引用数据类型: 比较的是对象的地址是否一样;(排除特殊 String,引用类型最好不要用
==)
> 2、 equals (最初定义在根类Object中的)
> 基本数据类型 : 不能够使用! 基本数据类型不是对象,不能够调用Object中的方法
> 引用数据类型 : 在Object的源码中定义的就是==进行比较比较
> 如果我们没有去覆写过equals方法而是直接调用到了Object中的此方法,那么结果和 ==
> 比较应用数据类型一样的;
> 在实际开发中,我们一般比较对象都是通过对象的属性值进行比较(一般比较对象的地址没有多大用处),所以我们会经常覆写Object中的此方法,把自己的规则写在方法里面;
3.6 重载 和 重写区别
* 方法重载:
* 1.同一个类中
* 2.方法名相同。参数列表不同(参数顺序、个数、类型)
* 3.方法返回值、访问修饰符任意
* 4.与方法的参数名无关
*
* 方法重写:
* 1.有继承关系的子类中
* 2.方法名相同,参数列表相同(参数顺序、个数、类型),方法返回值只能与父类兼容
* 3.访问修饰符,访问范围需要大于等于父类的访问范围
* 4.与方法的参数名无关
3.7 super关键字和this关键字区别
子类构造默认调用的是父类无参构造方法!!!
可以通过super()调用父类允许被访问对的其他构造方法!!!
this()/super()必须放在子类构造方法有效代码的第一行!!!
this()和super()不能并存,因为他们都要抢占第一行!!!
4、课程总结
继承
复写(Override)
5、练习
鸟(Bird)和狼(Wolf)都是动物(Animal),动物都有心跳(beat()),会呼吸(beat()),但是鸟会fly(fly()),狼会奔跑(run()),用java程序实现以上描述(使用继承来实现);
public class Animal {
public void beat() {
System.out.println("动物有心跳");
}
public void breach() {
System.out.println("动物会呼吸");
}
}
class Bird extends Animal{
@Override
public void beat() {
super.beat();
}
@Override
public void breach() {
super.breach();
}
public void fly() {
System.out.println("鸟会飞");
}
}
class Wolf extends Animal{
@Override
public void beat() {
super.beat();
}
@Override
public void breach() {
super.breach();
}
public void run() {
System.out.println("狼会跑");
}
}
定义一个Student类,里面有name和age字段,提供对应get/set方法
对name和age进行赋值;
public class Student{
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}