JAVA学习笔记(四)
继承要解决的问题
什么是继承关系
子类继承了父类的哪些成员
方法覆盖
方法重载(Overload)和方法覆盖(方法重写 Override)的区别
supper关键字
子类初始化过程
super应用场景和各种隐藏现象
Object类
Object类常用的方法
1.继承要解决的问题
-
父类/基类/超类/被拓展类
extends关键字父类(super class)存储的共性:共同的特征(状态/行为) -
子类/拓展类
子类(sub class)存储的特性:自己特有的特性(状态/行为)继承关系解决的是:代码重复的问题
2.什么是继承关系:
基于某个父类对对象的定义加以扩展,而产生新的子类,子类可以继承父类原来的某些定义,也可以增加原来父类没有的定义,或者复写父类中的某些特性。从面向对象的角度来说:继承是一种从一般到特殊的关系,是一种“is a”的关系,即子类是对父类的扩展,是一种特殊的父类。
在Java语言中,存在多个类的时候,我们使用“extends”关键字来表示子类和父类之间的关系。
语法格式:在定义子类的时候来表明自己需要扩展于哪一个父类。
public class 子类类名 extends 父类类名
{
编写自己特有的状态和行为
}
-
在Java中。类和类之间只允许单继承,不允许多继承(C++允许多继承),也就是说一个类A,只能有一个直接父类,不能出现类A同时继承于类B和类C。但是Java中允许多重继承:例如动物有胎生动物和卵生动物之分,胎生动物又分华南虎、东北虎、孟加拉虎等
-
在Java中,除了Object类之外,每一个类都有一个直接父类。Object类是Java语言的根类,它要么就是一个类的直接父类,要么是一个类的间接父类。比如:class Teacher extends People{} 我们就说Teacher的直接父类是People,而People的父类是Object.也就是说:class Person{} 等价于 class Person extends Object{}
-
继承关系的作用
- 解决代码重复的问题
- 真正的作用,表示一个体系(组合关系)
// super class 基类/超类/父类/被拓展类 extends关键字
// 父类存储的共性:共同的特征(状态/行为)
// 子类存储的特性:自己特有的特性(状态/行为)
// 继承关系解决的是:代码重复的问题
class People {
String name; // 姓名
int age; // 年龄
public void sleep() {}
}
// 老师 sub class 子类/拓展类
class Teacher extends People {String level; // 级别}
// 学生
class Schoolmate extends People {String number; // 学号}
// 员工
class Employee extends People {String hireDate; // 入职时间}
// 引出继承关系
public class ExtendDemo {
public static void main(String[] args) {}
}
3.子类继承了父类的哪些成员
先写父类还是先写子类:
一般的,在开发过程中先编写多个自定义类,写完之后,发现多个类之间存在共同的代码,此时可以抽取出一个父类,以后做开发都是基于框架/组件来做的,是在别人的基础之上继续做开发。定义新的类去继承于框架/组件中提供的父类。
子类继承父类之后,可以拥有父类的某一些状态和行为(子类复用了父类的功能或者状态)那么子类到底继承了父类的哪些成员(根据访问修饰符来判断):
- 如果父类中的成员使用public 修饰,子类继承;
- 如果父类中的成员使用protected修饰,子类也继承,即使父类和子类不在一个包中;
- 如果父类和子类中同一个包中,此时子类可以继承父类中缺省修饰符的成员;
- 如果父类中的成员使用private修饰,子类无法继承,因为private只能在本类中使用;
- 父类的构造器子类也不能继承,因为构造器必须和当前的类名相同。
4.方法覆盖
子类拓展了父类,可以获得父类的部分方法和成员变量,当父类的某一个方法不适合于子类本身特征的时候,此时子类需要重新定义父类的方法,并重写方法体
-
方法覆写的原则(一同两小一大):Override
- 一同:方法签名必须相同。(方法签名=方法名+方法参数)
- 两小:
- 1)子类方法的返回值类型是和父类方法的返回值类型相同或者是其子类。子类可以返回一个更加具体的类。例如父类返回值类型是Object,子类的返回值类型可以是Object也可以是String类。
- 2)子类方法声明抛出的异常类型和父类方法声明抛出的异常类型相同或者是其子类。
- ①子类方法中声明抛出的异常小于或等于父类方法声明抛出异常类型;
- ②子类方法可以同时声明抛出多个属于父类方法声明抛出异常类的子类(RuntimeException类型除外)
- 一大:
- 子类方法的访问权限比父类方法的访问权限更大或者相等,如报错:“错误: Student中的sleep()无法覆盖Person中的sleep()正在尝试分配更低的访问权限; 以前为public”。private修饰的方法不能被子类所继承,也就不存在覆盖的概念。
-
判断是否是覆写方法使用@Override标签。
- 若方法是覆写方法,在方法前或上贴上该标签,编译通过,否则便出错。
- 只有方法存在覆盖的概念,字段没有覆盖
-
方法覆盖解决的问题:当父类的某一个行为不符合子类具体的特征的时候,此时子类需要重新定义父类的方法,并重写方法体
class Bird {
public void fly() {
System.out.println("我在飞翔");
}
}
class Penguin extends Bird {
// 重新定义fly 方法的覆盖/复写 override
@Override // 判断当前子类的方法是否覆盖了父类的方法
public void fly() {
System.out.println("我不会飞");
}
}
public class OverrideDemo {
public static void main(String[] args) {
Penguin p = new Penguin();
// 先从子类中寻找fly方法,如果找不到继续从父类中找,Bird中还没有就去Object中找,
// 如果还找不到,编译就报错
p.fly();
}
}
5.方法重载(Overload)和方法覆盖(方法重写 Override)的区别
-
方法的重载(Overload):是在一个类中使用
- 作用:解决了同一个类中,相同功能方法名不同的问题.既然是相同的功能,那么方法的名字就应该相同.
- 规则:两同一不同.同类中,方法名相同,方法参数列表不同,(参数类型,参数个数,参数顺序)
-
方法重写(Override):是在两个类中使用
- 作用:解决子类继承父类之后,可能父类的某个方法不满足子类的具体特征,此时需要重新在子类中定义该方法,并重写方法体.
- 规则:一同两小一大.一同:父类和子类的方法签名是相同的,所以建议直接拷贝父类中方法的定义到子类中,再重写方法体,就OK了.
6.supper关键字
this 表示当前对象
super 表示当前对象的父类
内存分配示意图.png
class Bird {
public void fly() {
System.out.println("我在飞翔");
}
}
class Penguin extends Bird {
@Override
public void fly() {
System.out.println("我不会飞");
}
// 在子类中某一个方法中,去调用父类被覆盖的方法
public void say() {
// 调用Bird类中
System.out.println("我在唱歌");
// 调用当前类的fly方法
this.fly();
System.out.println("+++++");
// 调用父类Bird类中的fly方法
super.fly();
}
}
public class OverrideDemo {
public static void main(String[] args) {
Penguin p = new Penguin();
p.say();
}
}
7.子类初始化过程
在创建子类对象时的执行顺序是:先进入子类构造器,然后在构造器中会先调用父类构造器(创建父类对象),再执行子类构造器代码。默认调用的是父类的无参构造器
class Bird {
Bird(){
System.out.println("父类构造器调用");
}
public void fly() {
System.out.println("我在飞翔");
}
}
class Penguin extends Bird {
Penguin(){
System.out.println("子类构造器");
}
@Override
public void fly() {
System.out.println("我不会飞");
}
// 在子类中某一个方法中,去调用父类被覆盖的方法
public void say() {
// 调用Bird类中
System.out.println("我在唱歌");
// 调用当前类的fly方法
this.fly();
System.out.println("+++++");
// 调用父类Bird类中的fly方法
super.fly();
}
}
public class ExtendsDemo {
public static void main(String[] args) {
Penguin p = new Penguin();
p.say();
}
}
打印结果:
父类构造器调用
子类构造器
我在唱歌
我不会飞
+++++
我在飞翔
- 如果父类不存在可以被子类调用的构造器,则不能存在子类.例如父类所有构造器都是私有的,则无法存在子类. 即使存在子类,也无法创建对象
- 如果父类不存在无参数构造器,则子类必须通过super语句显示调用含有父类参数的构造器.即在子类构造器中第一句话调用super(parameter);
class Bird {
Bird(String s){
System.out.println("父类构造器调用");
}
public void fly() {
System.out.println("我在飞翔");
}
}
class Penguin extends Bird {
// 父类对象没有无参构造器,这时候必须先调用super(paraments),也就是说子类构造器的创建一定要有父类构造器
Penguin(){
super("你好");
System.out.println("子类构造器");
}
@Override
public void fly() {
System.out.println("我不会飞");
}
// 在子类中某一个方法中,去调用父类被覆盖的方法
public void say() {
// 调用Bird类中
System.out.println("我在唱歌");
// 调用当前类的fly方法
this.fly();
System.out.println("+++++");
// 调用父类Bird类中的fly方法
super.fly();
}
}
public class ExtendsDemo {
public static void main(String[] args) {
Penguin p = new Penguin();
p.say();
}
}
// 动物
class Animal {
// 要尽量私有化
private String name;
private int age;
Animal() {
System.out.println("Animal构造器");
}
Animal(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
}
// 鱼
class Fish extends Animal {
private String color; // 颜色
Fish() {
super(); // 对super的调用必须是构造器中的第一个语句
// 其实存在一个隐式的父类无参构造器方法,即使父类中没有无参的构造器,编译也不会报错
// 如果父类没有没有提供无参构造器,在子类中要显示调用父类的有参构造器
// 但是如果父类中是有参数的构造器,在这里调用父类无参构造器编译器就会报错,提示没有找到参数
// 如果父类的无参构造器经过private修饰,在子类中同样是无法调用的
// 如果父类中既有无参构造器也有有参构造器,那么在子类中调用父类构造器就会去找相应的是否有参数的构造器
System.out.println("Fish构造器");
}
// 重载一个带参数的构造器
Fish(String name, int age, String color) {
super(name, age); // 调用父类的构造器这句话必须作为子类构造器的第一句话
this.color = color;
}
public void description() {
// System.out.println(super.getName() + ", " + this.color);
// 这里的super可以省略,因为子类找不到getName()方法会去父类中找
System.out.println(getName() + ", " + this.color);
}
}
public class SubClassDemo {
public static void main(String[] args) {
// 创建鱼对象
Fish fish = new Fish("尼莫", 10, "橘黄色");
// 在创建子类对象时的执行顺序:先进入子类构造器,然后在子类构造器中会
// 先调用父类构造器(创建父类对象),再执行子类构造器代码
fish.description();
}
}
8.super应用场景和各种隐藏现象
-
super关键字的是所有场景:
- 可以使用super解决子类隐藏了父类字段情况,该情况一般不讨论,因为破坏封装.
- 在子类方法中,调用父类被覆盖的方法,此时必须使用super.
- 在子类构造器中,调用父类构造器,此时必须使用super语句;(super(实参))
-
隐藏的意思(注意区分方法的覆盖)Java中不存在字段的覆盖,隐藏出现在子类定义的字段和父类的字段一样
- 满足继承的访问权限下,隐藏父类的静态方法:若子类定义的静态方法的签名与父类中的静态方法签名相同,那么此时就是隐藏父类方法。注意:仅仅是静态方法,子类存在和父类一模一样的静态方法
- 满足继承的访问权限下,隐藏父类的字段:若子类中定义的字段和父类中的字段名相同(不管类型),此时就是隐藏父类字段,并且只能通过super访问被隐藏的字段
- 隐藏本类字段:若同类中某局部变量名和字段名相同,此时就是隐藏本类字段,并且只能通过this访问被隐藏的字段
class SuperClass{
public String name="superclass.name";
public static void method(){}
}
class SubClass{
public int name=18; // 隐藏了父类的name字段
public void doWork() {
boolean name = false; // 隐藏调用本类中的字段
System.out.println(name); // 根据就近原则,打印出来的结果是false
System.out.println(this.name); // 打印出来的结果是18
System.out.println(super.name); // 打印出来的结果是superclass.name
}
public static void method(){} // 隐藏不叫覆盖
}
static不能和this以及super共存.
9.Object类
-
Object类是Java语言的根类,要么是一个类的直接父类,要么就是一个类的间接父类
class ABC{} 等价于 class ABC extends Object{}
-
所有对象(包括数组)都实现这个类的方法。Object本身指对象的意思,发现所有的对象都具有某一些共同的而行为。所以抽象出来一个类:Object,表示对象类,其他都会继承于Object类,也就拥有Object类中的方法。
-
引用数据类型:类/接口/数组。引用类型又称之为对象类,所谓的数组变量名称指的是数组对象
-
对象有一个共同的特点就是存在于堆空间。
10.Object类常用的方法
-
protected void finalize();
当垃圾回收期GC确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。GC在回收某一个对象之前,会先调用该方法,做扫尾操作,该方法我们不要去调用。 -
Class<?> getClass();
返回当前对象的真实类型。 -
int hashCode();
返回该对象的哈希码值,hashCode决定了对象在哈希表中的存储位置,不同对象的hashCode是不一样的。 -
boolean equals(Object obj);
拿当前对象this和参数obj作比较- 在Object类中的equals方法,本身和“==”符号相同,都是比较对象的内存地址。
- 官方建议:每个类都应该覆盖equals方法,不要比较内存地址,而是比较我们关心的数值。例如比较两个学生对象,我们不管是如何new出来的,只要学号相同就应该认为是同一个类
public class Demo {
public static void main(String[] args) {
String str = new String("AA");
String str1 = new String("AA");
String str2 = "AA";
System.out.println(str.equals(str1)); // true
System.out.println(str.equals(str2)); // true
// 两个字符串比较只要内容相同就相等,说明String类中覆盖了equals方法
Object o1 = new Object();
Object o2 = new Object();
System.out.println(o1.equals(o2)); // false
System.out.println(o1 == o2); // false 因为每次new都是一个新的内存空间
}
}
-
String toString();
表示把一个对象转换为字符串。- 打印对象时,其实打印的就是对象的toString方法
- System.out.println(obj对象); 等价于 System.out.println(obj对象.toString());
- 默认情况下打印对象,打印的是对象的十六进制的hashCode值,如
User@74a14482
,但是我们更关心的是对象中存储的数据,因此官方建议我们应该为每一个类覆盖toString()方法,返回我们关心的数据 - System.out.println(obj对象)查看源码可以看到最终还是调用到Object.toString()方法。
Object类中toString的实现:
public String toString() {
return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode());
}
class User {
private int age;
private String name;
User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return this.name + ", " + this.age;
}
}
public class ExtendsDemo {
public static void main(String[] args) {
User u = new User("xiaoming", 16);
System.out.println(u); // com.example.ExtendsDemos.User@74a14482
// 覆盖toString()方法后,这时打印出来的结果是xiaoming, 16
}
}