面向对象-day02
今日学习内容
- this关键字
- 包
- 构造器和setter方法的选用
- 继承思想
- 方法覆盖
- 抽象方法和抽象类
- Object类的常用方法
今日学习目标
- 掌握使用this解决二义性
- 了解如何选用构造器和setter方法设置对象内容
- 掌握import和package关键字
- 理解继承的作用
- 必须掌握继承的语法
- 了解子类可以继承父类那些成员
- 掌握方法覆盖的判断规则和如何覆盖方法
- 掌握super关键字的含义
- 掌握抽象方法的定义和特点
- 掌握抽象类的定义和特点
- 掌握如何覆盖Object类的toString方法
- 掌握equals方法和==的区别
10.4 this 关键字(一)
10.4.1 this关键字(掌握)
之前说过,变量名称或方法参数名称,要见名知意,下列两个set方法的参数名,就显得太LOW了。
public class Student {
private String name;
private int age;
public void setName(String n) {
name = name;
}
public void setAge(int a) {
age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
不就是设置名字和年龄吗,如果此时把参数名分别改为name和age。
public class Student {
private String name;
private int age;
public void setName(String name) {
name = name;
}
public void setAge(int age) {
age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
此时会发现参数根本就设置不进去,name和age打印出来都是各自的初始值,运行测试类的结果如下:
null,0
先回忆方法的参数属于局部变量这个结论,导致参数设置不进去的原因是:
局部变量和成员变量同名,此时在方法中调用变量时根据就近原则,优先使用局部变量,示意图如下。
可以看出setName方法中两次使用的name,都是直接寻找距离自己最近的形参name,就相当于把参数name的值设置给参数name,根本就没有把参数值设置给成员变量。
提示:
当在一个作用域访问变量时,首先在当前作用域查找该变量, 如果能找到,不继续查找。 如果在当前作用域找不到该变量,尝试去上一层作用域查找,如果找到,停止查找,如果找不到,继续上一层查找,依次类推。这整个过程形成一个查找链,这个称为作用域链。
该问题,更专业的叫法是局部变量和成员变量存在二义性,也就是变量名有歧义。为了解决该问题——有请this关键字。
使用 this.变量名 的语法,此时访问的就是成员变量,this的其他操作,后面再讲。
具体代码如下:
public class Student {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
10.4.2 使用构造器还是setter方法(了解)
构造器和setter方法都可以给对象设置数据:
- 构造器,在创建对象的时候设置初始数据,只能初始化一次。
- setter方法,创建对象后再设置初始数据,可以设置多次。
10.5 包 (会用)
包是为了解决同名类问题。
包可以看成目录,由于包本身即是一种树状结构(目录结构),因此具有天然地项目管理的优势;
包的作用
[1] 防止类命名冲突
[2] 更好的组织类、方便管理维护,以及未来进行项目管理和对项目进行分层架构设计。
10.5.1 package (掌握)
在开发中存在几百个Java文件,为了更好的管理多个Java文件,我们可以使用package(包)来组织管理,类似于磁盘的文件夹一样。
定义语法为
语法格式:
package 包名.子包名.子子包;
如: package test.demo;
注意:
必须把该语句作为Java文件中第一行代码(所有代码之前)
包名使用全小写字母组成,命名符合标识符规则
父包和子包之间使用点符号(.)分割
因为Java的安全机制,自定义包名不允许使用 java 单词作为包名。
包命名原则:一定保持唯一。
规则/规范:
【1】包名由小写字母组成,不能以圆点开头或结尾。.是包的分隔符,类似于目录中的/
【2】包名之前最好加上唯一的前缀,通常使用组织倒置的网络域名,例如:cn.wolfcode
【3】包名后续部分依不同机构内部的规范不同而不同。建议遵循大企业包的标准的命名规范 :
package 企业域名倒写.项目名.模块名.组件名
package 企业域名倒写.模块名.组件名;
package cn.wolfcode.pay.util;
以上命名规范当我们接触到项目时,我们才会有深刻体会,项目时还会被提及,此处暂时了解即可。
10.5.2 import(掌握)
在代码中使用包中的类Student
package cn.wolfcode.demo2;
public class Test01Student {
public static void main(String[] args) {
// Student student = new Student();
cn.wolfcode.demo1.Student student = new cn.wolfcode.demo1.Student();
}
}
当一个类Test01Student需要的类不和Test01Student在同一包下时,就需要导包,目的是告诉jvm去哪里可以找到该类。
导包语法
import 类的全路径;
类的全路径 = 包名+类名。 全路径专门用于便于虚拟机定位和查找一个类。
import com.baidu.*; // 导入 com.baidu 包下的所有类
import com.baidu.Dog; // 导入 com.baidu 包下的 Dog 类
小结:对package定义包和import导入某个类,会用就可以了,无需过于深究。
11、继承思想
需求,使用面向对象的知识定义出老师(Teacher)、学生(Student)、员工(Employee)三个类:
- 老师:拥有名字、年龄、级别三个状态,有授课和休息两个功能
- 学生:拥有名字、年龄、学号三个状态,有学习和休息两个功能
- 员工:拥有名字、年龄、入职时间三个状态,有工作和休息两个功能
代码截图如下:
此时,发现三个类中的存在着大量的共同代码,而我们要考虑的就是如何解决代码重复的问题。
面向对象的继承思想,可以解决多个类存在共同代码的问题。
继承关系设计图:
记住几个概念:
- 被继承的类,称之为父类、基类
- 继承父类的类,称之为子类,拓展类
- 父类:存放多个子类共同的字段和方法
- 子类:存放自己特有的(独有的)字段和方法
11.1.继承语法(重点)
在java程序中,如果一个类需要继承另一个类,此时使用extends关键字。
public class 父类名{
// 存放多个子类共同的字段和方法
}
public class 子类名 extends 父类名{
// 存放自己特有的(独有的)字段和方法
}
注意:Java中类只支持单继承,但是支持多重继承。也就是说一个子类只能有一个直接的父类,父类也可以再有父类。
- 下面是错误的写法! Java中的类只支持单继承。
class SuperClass1 { }
class SuperClass2 { }
// 错误用法
class SubClass extends SuperClass1,SuperClass2{
}
- 下面代码是正确的。一个父类可以有多个子类。
class SuperClass{}
class SubClass1 extends SuperClass{ }
class SubClass2 extends SuperClass{ }
- 下面代码是正确的,支持多重继承。
class SuperSuperClass{}
class SuperClass extends SuperSuperClass{}
class SubClass extends SuperClass
例如:大学生 extends 学生,学生 extends 人类,我们就说,大学生具有人类的特征和行为。
- Object类是Java语言的根类,任何类都是Object的子类,要么是直接子类,要么是间接子类(后讲)
public class Person {
}
等价于
public class Person extends Object{
}
11.1.1. 继承操作(重点)
父类代码:
public class Person {
private String name;
private int age;
public void rest() {
System.out.println("休息");
}
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;
}
}
子类代码:
public class Student extends Person{
private String sn; // 学号
public void study() {
System.out.println("学习");
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
}
测试代码:
public class ExtendsDemo {
public static void main(String[] args) {
// 创建学生对象
Student stu = new Student();
// 设置字段信息
stu.setName("will"); //继承了父类
stu.setAge(17); //继承了父类
stu.setSn("s_123");
// 调用方法
stu.study();
stu.rest(); //继承了父类
}
}
11.1.2. 子类可以继承到父类哪些成员(了解)
子类继承父类之后,可以拥有到父类的某一些成员(字段和方法),根据访问修饰符来判断:
- 如果父类中的成员使用public和protected修饰,子类都能继承.
- 如果父类和子类在同一个包中,使用缺省访问修饰的成员,此时子类可以继承到
- 如果父类中的成员使用private修饰,子类继承不到。private只能在本类中访问
- 父类的构造器,子类也不能继承,因为构造器必须和当前的类名相同
访问修饰符访问权限 ( 了解 )
| 关键字 | 本类 | 同包子类 | 同包其他类 | 不同包子类 | 不同包其他类 |
|---|---|---|---|---|---|
| private | ✔ | ✘ | ✘ | ✘ | ✘ |
| 默认 | ✔ | ✔ | ✔ | ✘ | ✘ |
| protected | ✔ | ✔ | ✔ | ✔ | ✘ |
| public | ✔ | ✔ | ✔ | ✔ | ✔ |
private: 私有的,本类可见。
默认: 同包可见,也经常把它成为包访问权限。
protected: 受保护的。1 同包可见 2 子类可见
public 公共的。任何地方可见
一句话:子类继承父类的非私有成员(字段和方法),但构造器除外。
11.2. 方法覆盖(掌握)
子类继承了父类,可以拥有父类的部分方法和成员变量。可是当父类的某个方法不适合子类本身的特征时,此时怎么办? 比如鸵鸟(Ostrich)是鸟类(Bird)中的一个特殊品种,所以鸵鸟类是鸟类的一个子类,但是鸟类有飞翔的功能,但是对应鸵鸟,飞翔的行为显然不适合于它。
父类:
public class Bird {
public void fly() {
System.out.println("飞呀飞...");
}
}
子类:
public class Ostrich extends Bird{
}
测试类:
public class OverrideDemo {
public static void main(String[] args) {
//创建鸵鸟对象
Ostrich os = new Ostrich();
//调用飞翔功能
os.fly();
}
}
运行结果:
飞呀飞...
上述代码从语法是正确的,但从逻辑上是不合理的,因为鸵鸟不能飞翔,此时怎么办?——方法覆盖操作。
11.2.1. 方法覆盖操作(重点掌握)
当子类存在一个和父类一模一样的方法时,我们就称之为子类覆盖了父类的方法,也称之为重写(override) 。
提示:
当子类继承父类的方法时不能满足自身需要时,子类就可以根据自身需要对父类的同名方法进行重写。
那么我们就可以在子类方法体中,重写编写逻辑代码。
public class Ostrich extends Bird{
public void fly() {
System.out.println("扑扑翅膀,快速奔跑...");
}
}
运行测试代码:
扑扑翅膀,快速奔跑...
重写方法的调用顺序:
通过对象调用方法时,先在子类中查找有没有对应的方法,若存在就执行子类的,若子类不存在就执行父类的,如果父类也没有,报错。
方法覆盖的细节:(理解掌握)
-
实例方法签名必须相同 (方法签名= 方法名 + 方法的参数列表)
-
子类方法的返回值类型是和父类方法的返回类型相同或者是其子类
-
子类方法的访问权限 >= 父类方法访问权限
- 如果父类方法是private,子类方法不能重写。==> 重写建立在继承的基础上,没有继承,就不能重写。
-
子类方法中声明抛出的异常小于或等于父类方法声明抛出异常类型 (后续讲解)
上述的方法覆盖细节真多,记不住,那么记住下面这句话就万事OK了。
精华:直接拷贝父类中方法的定义粘贴到子类中,再重新编写子类方法体,打完收工!
11.2.2. 覆盖中super关键字(掌握)
问题,在子类中的某一个方法中需要去调用父类中被覆盖的方法,此时得使用super关键字。
public class Ostrich extends Bird{
public void fly() {
System.out.println("扑扑翅膀,快速奔跑...");
}
public void say() {
super.fly();//调用父类被覆盖的方法
fly();//调用本类中的方法
}
}
如果调用被覆盖的方法不使用super关键字,此时调用的是本类中的方法。
- super关键字表示父类对象的意思,更多的操作,后面再讲。
- super.fly() 可以翻译成调用父类对象的fly方法。
综合案例:输出老师基本信息
public class Person {
private String name;
private int age;
public Person() { }
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 省略setter and getter
// 输出人类对象的基本信息
public void showInfo(){
System.out.println("name:" + name);
System.out.println("age:" + age);
}
}
--------------------------------------------------------------
public class Teacher extends Person{
private int level;
// 省略 setter and getter
public Teacher(){}
public Teacher(String name,int age,int level){
setName(name);
setAge(age);
this.level = level;
}
@Override
public void showInfo() {
// System.out.println("name:" + getName());
// System.out.println("age:" + getAge());
super.showInfo();
System.out.println("level:" + level);
}
}
------------------------------------------------------------------
public class TestOverride {
public static void main(String[] args) {
Teacher teacher = new Teacher();
teacher.setName("kallen");
teacher.setAge(20);
teacher.setLevel(1);
// 调用子类覆盖的方法
teacher.showInfo();
}
}
11.3. 抽象方法和抽象类(掌握)
需求:求圆(Circle)和矩形(Rectangle)两种图形的面积。
分析:无论是圆形还是矩形,还是其他形状的图形,只要是图形,都有面积,也就说图形都有求面积的功能,那么我们就可以把定义一个图形(Graph)的父类,该类拥有求面积的方法,但是作为图形的概念,而并不是某种具体的图形,那么怎么求面积是不清楚的,姑且先让求面积的getArea方法返回0。
父类代码:
public class Graph {
public double getArea() {
return 0.0;
}
}
子类代码(圆形):
public class Circle extends Graph {
private int r; //半径
public void setR(int r) {
this.r = r;
}
public double getArea() {
return 3.14 * r * r;
}
}
子类代码(矩形):
public class Rectangle extends Graph {
private int width; // 宽度
private int height; // 高度
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public double getArea() {
return width * height;
}
}
测试代码:
public class GraphDemo {
public static void main(String[] args) {
// 圆
Circle c = new Circle();
c.setR(10);
double ret1 = c.getArea();
System.out.println("圆的面积:" + ret1);
// 矩形
Rectangle r = new Rectangle();
r.setWidth(5);
r.setHeight(4);
double ret2 = r.getArea();
System.out.println("矩形的面积:" + ret2);
}
}
运行结果如下:
圆的面积:314.0
矩形的面积:20.0
11.3.1. 引出抽象方法(了解)
问题1:既然不同的图形求面积的算法是不同的,所以必须要求每一个图形子类去覆盖getArea方法,如果没有覆盖,应该以语法报错的形式做提示。
问题2:在Graph类中的getArea方法的方法体没有任何存在意义,因为不同图形求面积算法不一样,子类必须要覆盖getArea方法。
要满足上述对方法的要求,就得使用abstract来修饰方法,被abstract修饰的方法具备两个特征:
- 该方法没有方法体
- 要求子类必须覆盖该方法
这种方法,我们就称之为抽象方法。
11.3.2. 抽象方法和抽象类(重点掌握)
使用abstract修饰的方法,称为抽象方法。
public abstract 返回类型 方法名(参数);
抽象方法的特点:
- 使用abstract修饰,没有方法体,留给子类去覆盖
- 抽象方法必须定义在抽象类或接口中
使用abstract修饰的类,成为抽象类。
public abstract class 类名 {
}
一般的,抽象类以Abstract作为类名前缀,如AbstractGraph,一看就能看出是抽象类。
抽象类的特点:
- 抽象类不能创建对象,调用没有方法体的抽象方法没有任何意义
- 抽象类中可以同时拥有抽象方法和普通方法
- 抽象类要有子类才有意义,子类必须覆盖父类的抽象方法,除非子类也是抽象类。
父类代码:
public abstract class AbstractGraph {
public abstract double getArea(); // 没有方法体
}
子类代码:
public class Circle extends AbstractGraph {
private int r;// 半径
public void setR(int r) {
this.r = r;
}
public double getArea() { //覆盖父类抽象方法
return 3.14 * r * r; //编写方法体
}
}
测试类没有改变。
\