一、面向对象高级
此笔记参考黑马教程,仅学习使用,如有侵权,联系必删
文章目录
- 一、面向对象高级
-
- 1. static(静态)
- 2. 面向对象三大特征之二:继承
- 3. 面向对象的三大特征之三:多态
1. static(静态)
1.1 static 修饰成员变量
1.1.1 static
- 叫静态,可以修饰成员变量、成员方法
成员变量按照有无 static 修饰,分为两种:
- 类变量:有 static 修饰,属于类,在计算机里只有一份,会被类的全部对象共享
- 实例变量(对象的变量):无 static 修饰,属于每个对象的
public class Student {
// 类变量
static String name;
// 实例变量(对象的变量)
int age;
}

- 把学生类看成一张学生表,可以创建出学生对象 s1 和 s2,age 没有 static 修饰,所以是对象的变量,也就是每个学生对象中都有一个自己的 age。
- 而成员变量 name 有 static 修饰,就是类变量,就是说它只属于当前这个类自己持有,不属于对象,也就是说无论我们用这个类创建出了多少个学生对象,这些学生对象中都不会持有这个类变量 name
类变量访问方法
类名.类变量(推荐)
对象.类变量(不推荐)
实例变量访问方法
对象.实例变量
1.1.2 成员变量的执行原理
总结
- static 是什么?
- 叫静态,可以修饰成员变量、成员方法
- static 修饰的成员变量叫什么?怎么使用?有啥特点?
- 类变量(静态成员变量)
类名.类变量(推荐)
对象.类变量(不推荐)
- 属于类,与类一起加载一次,在内存中只有一份,会被类的所有对象共享
- 无 static 修饰的成员变量叫什么?怎么使用?有啥特点?
- 实例变量(对象变量)
对象.实例变量
- 属于对象,每个对象中都有一份
1.2 static 修饰成员变量的应用场景
类变量的应用场景
- 在开发中,如果某个数据只需要一份,且希望能够被共享(访问、修改),则该数据可以定义成类变量来记住
案例:
系统启动后,要求用户类可以记住自己创建了多少个用户对象了
User.java
package Advanced.a_static.d1_staticdemo;
public class User {
// 类变量
public static int number; // public是让它对外完全公开和暴露的
public User() {
// User.number++;
// 注意:在同一个类中,访问自己类的类变量,才可以省略类名不写
number++;
}
}
- Test2.java
package Advanced.a_static.d1_staticdemo;
public class Test2 {
public static void main(String[] args) {
// 目标:通过案例理解类变量的应用场景
User u1 = new User();
User u2 = new User();
User u3 = new User();
User u4 = new User();
System.out.println(User.number); // 4
}
}
总结
- 成员变量有几种?各自在什么情况下定义?
- 类变量:数据只需要一份,且需要被共享时(访问,修改)
- 实例变量:每个对象都要有一份,数据各不同(如:name、score、age)
- 访问自己类中的类变量,是否可以省略类名不写?
- 可以的
- 注意:在某个类中访问其他类里的类变量,必须带类名访问
1.3 static 修饰成员方法
1.3.1 成员方法的分类
- 类方法:有 static 修饰的成员方法,属于类
public static void printHelloWorld() {
System.out.println("Hello World!");
System.out.println("Hello World!");
}
类名.类方法(推荐)
对象名.类方法(不推荐)
- 实例方法:无 static 修饰的成员方法,属于对象
public void printPass() {
...
}
对象.实例方法
1.3.2 成员方法的执行原理

代码演示
- Student.java
package Advanced.a_static.d2_static_method;
public class Student {
double score;
// 类方法
public static void printHelloWorld() {
System.out.println("Hello World!");
System.out.println("Hello World!");
}
// 实例方法(对象的方法)
public void printPass() {
System.out.println("成绩:" + (score >= 60 ? "及格" : "不及格"));
}
}
- Test.java
package Advanced.a_static.d2_static_method;
public class Test {
public static void main(String[] args) {
// 目标:掌握有无static修饰方法的用法
// 1. 类方法的用法
// 类名.类方法(推荐)
Student.printHelloWorld();
// 对象.类方法(不推荐)
Student s = new Student();
s.printHelloWorld();
// 2. 实例方法的用法
// 对象.实例方法
s.printPass();
}
}
总结
- static 修饰的成员方法叫什么?如何使用?
- 类方法(静态方法)
- 属于类,可以直接用类名访问,也可以用对象访问
类名.类方法(推荐)
对象名.类方法(不推荐)
- 无 static 修饰的成员方法叫什么?如何使用?
- 实例方法(对象的方法)
- 属于对象,只能用对象访问
对象.实例方法
补充知识:搞懂 mian 方法
public class Test {
public static void main(String[] args) {
...
}
}
- main 方法是啥方法?
- 类方法
- 因为有 static
- main 方法咋就能直接跑起来?
- 我们用 Java 命令执行这个 Test 程序的时候,虚拟机其实会用这个 Test 类直接去点这个 main 方法来触发这个 main 方法的执行
- 为什么可以用类名直接去点这个 main 方法执行呢?因为 main 方法也是类方法
- 参数
String[] args
是什么?
- 当我们来执行这个 main 方法的时候实际上我们可以送一些数据给这个参数接收的,这个参数它是一个 String 类型的数组,它可以接一些数据,然后让你这个 main 方法里面的程序来使用这些数据
- 怎么在执行 main 方法的时候把数据传给这个参数(String[] args)呢?
- 在调用 java 命令执行时,直接跟在文件名后面
1.4 static 修饰成员方法的应用场景
1.4.1 类方法的常见应用场景
- 类方法最常见的应用场景是做工具类
1.4.2 工具类是什么?
- 工具类中的方法都是一些类方法,每个方法都是用来完成一个功能的,工具类是给开发人员共同使用的
1.4.3 使用类方法来设计工具类有啥好处?
- 提高了代码复用;调用方便,提高了开发效率
public class XxxxUtil {
public static void xxx() {
...
}
public static boolean xxxx(String email) {
...
}
public static String xxxxx(int n) {
...
}
...
}
案例
需求:
- 某系统的登录界面需要开发并展示四位随机的验证码,系统的注册界面需要开发并展示六位的随机验证码
这两套程序的代码几乎一模一样,存在大量重复代码,这时我们就可以使用类方法设计一个工具类让这两个程序来调用,这样就优化好这个结构了
代码实现
- MyUtil.java
package Advanced.a_static.d3_util;
import java.util.Random;
public class MyUtil {
private MyUtil() {
}
public static String createCode(int n) {
// 1. 定义2个变量是,一个是记住最终产生的随机验证码,一个是记住可能用到的全部字符
String code = "";
String data = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
Random r = new Random();
// 2. 开始定义一个循环产生每位随机数字符
for (int i = 0; i < 4; i++) {
// 3. 随机一个字符范围内的索引
int index = r.nextInt(data.length());
// 4. 根据索引去全部字符中提取该字符
code += data.charAt(index); // code = code + 字符
}
return code;
}
}
为什么工具类中的方法要用类方法,而不是实例方法?
- 实例方法需要创建对象来调用,此时对象只是为了调用方法,对象占内存,这样会浪费内存
多学一招
- 工具类没有创建对象的需求,建议将工具类的构造器进行私有
总结
- 类方法有啥应用场景?
- 可以用来设计工具类
- 工具类是什么,有什么好处?
- 工具类中的方法都是类方法,每个类方法都是用来完成一个功能的
- 提高了代码的复用性;调用方便,提高了开发效率
- 为什么工具类要用类方法,而不是实例方法?
- 实例方法需要创建对象来调用,会浪费内存
- 工具类定义时有什么要求?
- 工具类不需要创建对象,建议将工具类的构造器私有化
1.5 static 的注意事项
- 类方法中可以直接访问类的成员,不可以直接访问实例成员
- 实例方法既可以直接访问类成员,也可以直接访问实例成员
- 实例方法中可以出现 this 关键字,类方法中不可以出现 this 关键字的
代码演示
package Advanced.a_static.d4_static_attention;
public class Student {
static String schoolName; // 类变量
double score; // 实例变量
// 1. 类方法中可以直接访问类的成员,不可以直接访问实例成员
public static void printHelloWorld() {
// 注意:同一个类中,访问类成员可以省略类名不写
schoolName = "Feng";
Student.printHelloWorld2();
// System.out.println(score); // 会报错
// printPass(); // 会报错
// System.out.println(this); // 会报错
}
// 类方法
public static void printHelloWorld2() {
}
// 2. 实例方法中既可以直接访问类成员,也可以直接访问实例成员
// 实例方法
// 3. 实例方法中可以出现this关键字,类方法中不可以出现this关键字
public void printPass() {
schoolName = "Feng2";
printHelloWorld2();
System.out.println(score);
printPass2();
System.out.println(this); // 哪个对象调用它的实例方法 this就会指向哪个对象
}
// 实例方法
public void printPass2() {
}
}
1.6 static 的应用知识:代码块
1.6.1 代码块概述
- 代码块是类的的5大成分之一(成员变量、构造器、方法、代码块、内部类)
代码块分为两种:
静态代码块:
- 格式:
static {}
- 特点:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次
- 作用:完成类的初识化,例如:对类变量的初始化赋值
- 格式:
实例代码块:
- 格式:
{}
- 特点:每次创建对象时,执行实例代码块,并在构造器前执行
- 作用:和构造器一样,都是用来完成对象的初始化的,例如:对实例对象变量进行初始化赋值
- 格式:
代码演示
- Student.java
package Advanced.a_static.d5_block;
public class Student {
static int number = 80;
static String schoolName;
// 静态代码块
static {
System.out.println("静态代码块执行了");
schoolName = "Feng";
}
// 实例代码块
{
System.out.println("实例代码块执行了~~~");
System.out.println("有人创建了对象" + this);
}
public Student() {
System.out.println("无参数构造器执行了~~~");
}
public Student(String name) {
System.out.println("有参数构造器执行了~~~");
}
}
- Test.java
package Advanced.a_static.d5_block;
public class Test {
public static void main(String[] args) {
// 目标:认识两种代码块,了解他们的特点和基本作用
System.out.println(Student.number); // 静态代码块执行了 80
System.out.println(Student.number); // 80
System.out.println(Student.number); // 80
System.out.println(Student.schoolName); // Feng
System.out.println("-----------------------");
Student s1 = new Student(); // 实例代码块执行了~~~ 有人创建了对象Advanced.a_static.d5_block.Student@b4c966a 无参数构造器执行了~~~
Student s2 = new Student("张三"); // 实例代码块执行了~~~ 有人创建了对象Advanced.a_static.d5_block.Student@1d81eb93 有参数构造器执行了~~~
}
}
1.7 static 的应用知识:单例设计模块
1.7.1 什么是设计模式(Design pattern)?
- 一个问题通常有 n 种解法,其中肯定有一种解法是最优的,这个最优的解法被人总结出来了,称之为设计模式
- 设计模式有20多种,对应20多种软件开发中会遇到的问题
1.7.1.2 单例设计模式(饿汉式单例)
- 确保一个类只有一个对象
写法
- 把类的构造器私有
- 定义一个类变量记住类的一个对象
- 定义一个类方法,返回对象
代码实现
- A.java
package Advanced.a_static.d6_singleInstance;
public class A {
// 2.定义一个类变量记住类的对象
private static A a = new A();
// 1. 必须私有类的构造器
// 使得外面不能创建对象
private A() {
}
// 3. 定义一个类方法返回类的对象
public static A getObject() {
return a;
}
}
- Test1.java
package Advanced.a_static.d6_singleInstance;
public class Test1 {
public static void main(String[] args) {
// 目标:掌握单例设计模式的写法
A a1 = A.getObject();
A a2 = A.getObject();
System.out.println(a1); // Advanced.a_static.d6_singleInstance.A@b4c966a
System.out.println(a2); // Advanced.a_static.d6_singleInstance.A@b4c966a
}
}
1.7.1.3 单例模式的应用场景和好处
- Runtime 类
- 程序的运行环境,Java 程序执行的时候只有一套运行环境,因此 Runtime 它也只需要一个对象就可以代表你那个唯一的运行环境
- 任务管理器
- 无论启动多少次任务管理器,其窗口对象始终只有一个
1.7.1.4 单例设计模式的实现方式很多
总结
- 什么是设计模式,设计模式主要学什么?单例模式解决了上面问题
- 设计模式就是具体问题的最优解决方案
- 解决了什么问题?怎么写?
- 确保一个类只有一个对象
- 单例怎么写?饿汉式单例的特点是什么?
- 把类的构造器私有;定义一个类变量存储类的一个对象;提供一个类方法返回对象
- 在获取类对象时,对象已经创建好了
- 单例有啥应用场景,有啥好处?
- 任务管理器对象、获取运行时对象
- 在这些业务场景下,使用单例模式,可以避免浪费内存
1.7.2 懒汉式单例设计模式
- 拿对象时,才开始创建对象
写法
- 把类的构造器私有
- 定义一个类变量用于存储对象
- 提供一个类方法,保证返回的是同一个对象
代码实现
- B.java
package Advanced.a_static.d6_singleInstance;
public class B {
// 2. 定义一个类变量,用于存储这个类的一个对象
private static B b;
// 1. 把类的构造器私有
private B() {
}
// 3. 定义一个类方法,这个方法要保证第一次调用时才创建一个对象,后面调用时都会用这同一个对象返回
public static B getInstance() {
if (b == null) {
System.out.println("第一次创建对象~~~");
b = new B();
}
return b;
}
}
- Test2.java
package Advanced.a_static.d6_singleInstance;
/**
* 目标:掌握懒汉式单例的写法
*/
public class Test2 {
public static void main(String[] args) {
B b1 = B.getInstance(); // 第一次拿对象
B b2 = B.getInstance();
System.out.println(b1 == b2); // true
}
}
2. 面向对象三大特征之二:继承
2.1 继承的快速入门
2.1.1 认识继承、特点
2.1.1.1 什么是继承?
- Java 中提供了一个关键字
extends
,用这个关键字,可以让一个类和另一个类建立起父子关系
public class B extends A {
}
- A 类称之为父类(基类或超类)
- B 类称之为子类(派生类)
2.1.1.2 继承的特点
- 子类能继承父类的非私有成员(成员变量、成员方法)
继承后对象的创建
- 子类的对象是由子类、父类共同完成的
2.1.1.3 继承的执行原理
- 子类对象实际上是由子父类这两张设计图共同创建出来的
代码实现
- A.java
package Advanced.b_extends.d7_extends;
// 父类
public class A {
// 公开成员
public int i;
public void print1() {
System.out.println("===print1===");
}
// 私有成员
private int j;
private void print2() {
System.out.println("===print2===");
}
}
- B.java
package Advanced.b_extends.d7_extends;
// 子类
public class B extends A {
// 子类是可以继承父类的非私有成员
public void print3() {
System.out.println(i);
print1();
// System.out.println(j); // 报错
// print2(); // 报错
}
}
- Test.java
package Advanced.b_extends.d7_extends;
public class Test {
public static void main(String[] args) {
// 目标:认识继承、掌握继承的特点
B b = new B();
System.out.println(b.i); // 0
// System.out.println(b.j); // 报错
b.print1();
// b.print2(); // 报错
b.print3();
}
}
总结
- 什么是继承?继承后有啥特点?
- 继承就是用
extends
关键字,让一个类和另一个类建立起一种父子关系 - 子类可以继承父类非私有的成员
- 带继承关系的类,Java 会怎么创建它的对象?对象创建出来后,可以直接访问哪些成员?
- 带继承关系的类,Java 会用类和其父类,这多张设计图来一起创建类的对象
- 对象能直接访问什么成员,是由子父类这多张设计图共同决定的,这多张设计图对外暴露了什么成员,对象就可以访问什么成员
2.1.2 继承的好处、应用场景
2.1.2.1 使用继承有啥好处?
- 减少重复代码的编写
需求:
Feng 的员工管理系统种
需要处理讲师、咨询师的数据
讲师的数据有:姓名、具备的技能
咨询师的数据有:姓名、解答问题的总人数
代码实现
- People.java
package Advanced.b_extends.d8_extends_application;
public class People {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- Teacher.java
package Advanced.b_extends.d8_extends_application;
public class Teacher extends People {
private String skill;
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
public void printInfo() {
System.out.println(getName() + "具备的技能" + skill);
}
}
- Test.java
package Advanced.b_extends.d8_extends_application;
public class Test {
public static void main(String[] args) {
// 目标:搞清楚继承的好处
Teacher t = new Teacher();
t.setName("张三");
t.setSkill("Java、Spring");
System.out.println(t.getName()); // 张三
System.out.println(t.getSkill()); // Java、Spring
t.printInfo(); // 张三具备的技能Java、Spring
}
}
总结
- 使用继承有啥好处?
- 减少了重复代码的编写,提高了代码的复用性
2.2 继承相关的注意事项
2.2.1 权限修饰符
2.2.1.1 什么是权限修饰符?
- 就是用来限制类中的成员(成员变量、成员方法、构造器、代码块…)能够被访问的范围
2.2.1.2 权限修饰符有几种?各自的作用是什么?
修饰符 | 在本类中 | 同一个包下的其他类里 | 任意包下的子类里 | 任意包下的任意类里 |
---|---|---|---|---|
private | √ | |||
缺省 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
private < 缺省 < protected < public
代码演示
- d9_modifer.Fu.java
package Advanced.b_extends.d9_modifer;
public class Fu {
// 1. 私有:只能在本类中访问
private void privateMethod(){
System.out.println("===private===");
}
// 2. 缺省:本类,同一个包下的类
void method(){
System.out.println("===缺省===");
}
// 3. protected:本类,同一个包下的类,任意包下的子类
protected void protectedMethod(){
System.out.println("===protected===");
}
// 4. public:本类,同一个包下的类,任意包下的子类,任意包下的任意类
public void publicMethod(){
System.out.println("===public===");
}
public void test(){
privateMethod();
method();
privateMethod();
publicMethod();
}
}
- d9_modifer.Demo.java
package Advanced.b_extends.d9_modifer;
public class Demo {
public static void main(String[] args) {
// 目标:掌握不同权限修饰符的作用
Fu f = new Fu();
// f.privateMethod(); // 报错
f.method();
f.protectedMethod();
f.publicMethod();
}
}
- d10_modifer.Zi.java
package Advanced.b_extends.d10_modifer;
import Advanced.b_extends.d9_modifer.Fu;
public class Zi extends Fu {
public void test() {
// privateMethod(); // 报错
// method(); // 报错
protectedMethod();
publicMethod();
}
}
- d10_modifer.Demo2.java
package Advanced.b_extends.d10_modifer;
import Advanced.b_extends.d9_modifer.Fu;
public class Demo2 {
public static void main(String[] args) {
Fu f = new Fu();
// f.privateMethod(); // 报错
// f.method(); // 报错
// f.protectedMethod(); // 报错
f.publicMethod();
Zi zi = new Zi();
// zi.protectedMethod(); // 报错
}
}
2.2.2 单继承、Object 类
2.2.2.1 单继承
Java 是单继承的,Java 中的类不支持多继承,但是支持多层继承
单继承:指的是一个类只能继承一个直接父类
2.2.2.2 为何 Java 中的类不支持多继承
反证法:
- 假设 Java 中的类支持多继承,现在有两个类,一个 A 中有一个 method 方法里面打印 “aaa”,一个 B 中有一个 method 方法里面打印 “bbb”
- 再定义一个 C 类同时继承 A 和 B,那么 C 调用 method 方法时就不知道调用谁的方法了
2.2.2.3 Object 类
- Object 类是 Java 所有类的祖宗类。我们写的任意一个类,其实都是 Object 的子类或者子孙类
代码演示
package Advanced.b_extends.d11_extends_feature;
public class Test {
public static void main(String[] args) {
// 目标:掌握继承的两个注意事项
// 1. Java是单继承的:一个类只能继承一个直接父类;Java中的类不支持多继承,但支持多层继承
// 2. Object类是Java中所有类的祖宗
A a = new A();
B b = new B();
}
}
class A {
} // extends Object{}
class B extends A {
}
//class C extends B, A{} // 报错
class D extends B {
}
总结
- 继承相关的两个注意事项?
Java 是单继承的:一个类只能继承一个直接父类;Java 中的类不支持多继承,但是支持多层继承
Object 类是 Java 中所有类的祖宗
2.2.3 方法重写
2.2.3.1 认识方法重写
什么是方法重写?
- 当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写
- 注意:重写后,方法的访问,Java 会遵循就近原则
方法重写的其他注意事项
- 重写小技巧:使用 Override 注解,他可以指定 Java 编译器,检查我们方法重写的格式是否正确,代码可读性也会更好
- 子类重写父类方法时,访问权限必须大于或者等于父类该方法的权限(public > protected > 缺省)
- 重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小
- 私有方法、静态方法不能被重写,如果重写会报错的
代码演示
- A.java
package Advanced.b_extends.d12_extends_override;
public class A {
public void print1() {
System.out.println("111");
}
public void print2(int a, int b) {
System.out.println("111111");
}
}
- B.java
package Advanced.b_extends.d12_extends_override;
public class B extends A {
// 方法重写
@Override // 安全,可读性好
public void print1() {
System.out.println("666");
}
// 方法重写
@Override
public void print2(int a, int b) {
System.out.println("666666");
}
}
- Test.java
package Advanced.b_extends.d12_extends_override;
public class Test {
public static void main(String[] args) {
// 目标:认识方法重写,掌握方法重写的常见应用场景
B b = new B();
b.print1(); // 666
b.print2(2, 3); // 666666
}
}
2.2.3.2 方法重写的应用场景
- 子类重写 Object 类的
toString()
方法,以便返回对象的内容
代码演示
- Student.java
package Advanced.b_extends.d12_extends_override;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = 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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- Test.java
package Advanced.b_extends.d12_extends_override;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
// 目标:认识方法重写,掌握方法重写的常见应用场景
B b = new B();
b.print1(); // 666
b.print2(2, 3); // 666666
System.out.println("----------------------------");
Student s = new Student("张三", 19);
System.out.println(s.toString()); // Advanced.b_extends.d12_extends_override.Student@4e50df2e Student{name=张三,age=19}
System.out.println(s); // Advanced.b_extends.d12_extends_override.Student@4e50df2e Student{name=张三,age=19}
ArrayList list = new ArrayList();
list.add("java");
System.out.println(list); // [java] 说明ArrayList类中把Object类里面的toString方法进行了重写
}
}
总结
- 方法重写是什么?
- 子类写了一个方法名称、形参列表与父类某个方法一样的方法去覆盖父类的该方法
- 重写方法有哪些注意事项?
- 建议加上:
@Override
注解,可以校验重写是否正确,同时可读性好 - 子类重写父类方法时,访问权限必须大于或者等于父类被重写的方法的权限
- 重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小
- 私有方法、静态方法不能被重写
- 方法重写有啥应用场景?
- 当子类觉得父类的方法不好用,或者不满足自己的需求时,就可以用方法重写
2.2.4 子类中访问其他成员的特点
- 在子类方法中访问其他成员(成员变量、成员方法),是依照就近原则的
- 先子类局部范围找
- 然后子类成员范围找
- 然后父类成员范围找,如果父类范围还没有找到则报错
- 如果父类中,出现了重名的成员,会优先使用子类的,如果此时一定要在子类中使用父类的怎么办?
- 可以通过
super
关键字,指定访问父类的成员:super.父类成员变量/父类成员方法
代码演示
- F.java
package Advanced.b_extends.d13_extends_visit;
public class F {
String name = "父类名字";
public void print1() {
System.out.println("===父类的print1方法执行===");
}
}
- Z.java
package Advanced.b_extends.d13_extends_visit;
public class Z extends F {
String name = "子类名称";
public void showName() {
String name = "局部名称";
System.out.println(name);
System.out.println(this.name);
System.out.println(super.name);
}
@Override
public void print1() {
System.out.println("===子类的print1方法执行了===");
}
public void showMethod() {
print1(); // 子类的
super.print1(); // 父类的
}
}
- Test.java
package Advanced.b_extends.d13_extends_visit;
public class Test {
public static void main(String[] args) {
// 目标:掌握子类中访问其他成员的特点:就近原则
Z z = new Z();
z.showName(); // 局部名称 子类名称 父类名字
z.showMethod(); // ===子类的print1方法执行了=== ===父类的print1方法执行===
}
}
2.2.5 子类构造器的特点
2.2.5.1 认识子类构造器的特点
子类构造器的特点:
- 子类的全部构造器,都会先调用父类的构造器,再执行自己
子类构造器是如何实现调用父类构造器的:
- 默认情况下,子类全部构造器的第一行代码都是
super();
(写不写都有),它会调用父类的无参数构造器 - 如果父类没有无参数构造器,则我们必须在子类构造器的第一行手写
super(...);
,指定去调用父类的有参数构造器
代码演示
package Advanced.b_extends.d14_extends_constructor;
class F {
// public F() {
// System.out.println("===父类F的无参数构造器执行了===");
// }
public F(String name, int age) {
}
}
class Z extends F {
public Z() {
// super(); // 默认存在的
super("张三",17);
System.out.println("===子类Z的无参数构造器执行了===");
}
public Z(String name) {
// super(); // 默认存在的
super("张三",17);
System.out.println("===子类Z的有参数构造器执行了===");
}
}
public class Test {
public static void main(String[] args) {
// 目标:先认识子类构造器的特点,再掌握这个特点的常见应用场景
Z z = new Z(); // ===父类F的无参数构造器执行了=== ===子类Z的无参数构造器执行了===
Z z2 = new Z("张三"); // ===父类F的无参数构造器执行了=== ===子类Z的有参数构造器执行了===
}
}
2.2.5.1 常见的应用场景
搞清楚子类构造器为什么要调用父类的构造器,有啥应用场景?
- 在继承情况下,由于处理对象数据的构造器拆到了多个类里面去了,所以对象要通过调用这多个构造器才能把对象的数据处理完整
运行过程
- 子类构造器可以通过父类构造器,把对象中包含父类这部分的数据先初始化赋值,再回来把对象里包含子类这部分的数据也进行初始化赋值
代码演示
package Advanced.b_extends.d14_extends_constructor;
public class Test2 {
public static void main(String[] args) {
// 目标:搞清楚子类构造器为什么要调用父类的构造器,有啥应用场景
Teacher t = new Teacher("李四", 36, "Java");
System.out.println(t.getName()); // 李四
System.out.println(t.getAge()); // 36
System.out.println(t.getSkill()); // Java
}
}
class Teacher extends People {
private String skill;
public Teacher(String name, int age, String skill) {
super(name, age);
this.skill = skill;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
}
class People {
private String name;
private int age;
public People() {
}
public People(String name, int age) {
this.name = name;
this.age = 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;
}
}
补充知识:this(…) 调用兄弟构造器
- 任意类的构造器中,是可以通过
this(...)
去调用该类的其他构造器的
this(…) 和 super(…) 使用时的注意事项:
- this(…)、super(…) 都只能放在构造器的第一行,因此,有了 this(…) 就不能写 super(…) 了,反之亦然
代码演示
package Advanced.b_extends.d14_extends_constructor;
public class Test3 {
public static void main(String[] args) {
// 目标:掌握在类的构造器中,通过this(...)调用兄弟构造器的作用
Student s1 = new Student("李四", 26, "家里蹲大学");
// 需求:如果学生没有填写学校,那么学校默认就是Feng
Student s2 = new Student("张三", 28);
System.out.println(s2.getName()); // 张三
System.out.println(s2.getAge()); // 28
System.out.println(s2.getSchoolName()); // Feng
}
}
class Student {
private String name;
private int age;
private String schoolName;
public Student() {
}
public Student(String name, int age) {
this(name, age, "Feng");
}
public Student(String name, int age, String schoolName) {
this.name = name;
this.age = age;
this.schoolName = schoolName;
}
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 String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
}
2.2.6 注意事项小结
- 子类构造器有啥特点?
- 子类中的全部构造器,都必须先调用父类的构造器,再执行自己
- super(…) 调用父类有参数构造器的常见场景是什么?
- 为对象中包含父类这部分的成员变量进行赋值
- this(…) 的作用是什么?
- 在构造器中调用本类的其他构造器
- this(…) 和 super(…) 的使用需要注意什么?
- 都必须放在构造器的第一行
3. 面向对象的三大特征之三:多态
3.1 认识多态
3.1.1 什么是多态?
- 多态是在继承/实现情况下的一种现象,表现为:对象多态、行为多态
3.1.2 多态的具体代码实现
People p1 = new Student();
p1.run();
People p2 = new Teacher();
p2.run();
3.1.3 多态的前提
- 有继承/实现关系;存在父类引用子类对象;存在方法重写
3.1.4 多态的一个注意事项
- 多态是对象、行为的多态,Java 中的属性(成员变量)不谈多态
代码演示
- People.java
package Advanced.c_polymorphism.d1_polymorphism;
// 父类
public class People {
public String name = "父类People的名称";
public void run() {
System.out.println("人可以跑~~~");
}
}
- Student.java
package Advanced.c_polymorphism.d1_polymorphism;
public class Student extends People {
public String name = "子类Student的名称";
@Override
public void run() {
System.out.println("学生跑得贼快~~~");
}
}
- Teacher.java
package Advanced.c_polymorphism.d1_polymorphism;
public class Teacher extends People {
public String name = "子类Teacher的名称";
@Override
public void run() {
System.out.println("老师跑的气喘吁吁~~~");
}
}
- Test.java
package Advanced.c_polymorphism.d1_polymorphism;
public class Test {
public static void main(String[] args) {
// 目标:认识多态:对象多态、行为多态
// 1. 对象多态
People p1 = new Teacher();
// 行为多态:识别技巧:编译看左边,运行看右边
p1.run(); // 老师跑的气喘吁吁~~~
// 注意:对于变量:编译看左边,运行看左边
System.out.println(p1.name); // 父类People的名称
People p2 = new Student();
// 行为多态:识别技巧:编译看左边,运行看右边
p2.run(); // 学生跑得贼快~~~
System.out.println(p2.name); // 父类People的名称
}
}
3.2 使用多态的好处
3.2.1 使用多态的好处
在多态形势下,右边对象是解耦合的,更便于扩展和维护
- 解耦合:比如说我们在开发系统时,希望把这个系统中的每个模块拆分成一个一个的服务,可以随时的对接,这样更容易扩展和维护,这就是解耦合
定义方法时,使用父类类型的形参,可以接收一切子类对象,扩展性更强、更便利
3.2.2 多态下会产生的一个问题,怎么解决?
- 多态下不能使用子类的独有功能
代码演示
- People.java
package Advanced.c_polymorphism.d2_polymorphism;
// 父类
public class People {
public String name = "父类People的名称";
public void run() {
System.out.println("人可以跑~~~");
}
}
- Student.java
package Advanced.c_polymorphism.d2_polymorphism;
public class Student extends People {
public String name = "子类Student的名称";
@Override
public void run() {
System.out.println("学生跑得贼快~~~");
}
public void test() {
System.out.println("学生需要考试~~~");
}
}
- Teacher.java
package Advanced.c_polymorphism.d2_polymorphism;
public class Teacher extends People {
public String name = "子类Teacher的名称";
@Override
public void run() {
System.out.println("老师跑的气喘吁吁~~~");
}
public void test() {
System.out.println("老师需要教知识~~~");
}
}
- Test.java
package Advanced.c_polymorphism.d2_polymorphism;
public class Test {
public static void main(String[] args) {
// 目标:理解多态的好处
// 好处1:可以实现解耦合,右边对象可以随时切换,后续业务随之改变
People p1 = new Student();
p1.run();
// p1.test(); // 多态下存在的问题,无法直接调用子类的独有功能
Student s = new Student();
go(s);
Teacher t = new Teacher();
go(s);
}
// 好处2:可以使用父类类型的变量作为形参,可以接收一切子类对象
public static void go(People p) {
}
}
总结
- 使用多态有什么好处?存在什么问题?
- 可以解耦合,扩展性更强;使用父类类型的变量作为方法的形参时,可以接收一切子类对象
- 多态下不能直接调用子类的独有方法
3.3 多态下的类型转换问题
3.3.1 类型转换
自动类型转换:
父类 变量名 = new 子类();
- eg:
People p = new Teacher();
- eg:
强制类型转换:
子类 变量名 = (子类) 父类变量
- eg:
Teacher t = (Teacher) p;
- eg:
3.3.2 强制类型转换的一个注意事项
- 存在继承/实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错
- 运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常(ClassCastException)的错误出来
People p = new Teahcer();
Student s = (Student) p; // java.lang.ClassCastException
3.3.3 强转前,Java 建议:
- 使用
instanceof
关键字,判断当前对象的真实类型,再进行强转
p instanceof Student
代码演示
- People.java
package Advanced.c_polymorphism.d2_polymorphism;
// 父类
public class People {
public String name = "父类People的名称";
public void run() {
System.out.println("人可以跑~~~");
}
}
- Student.java
package Advanced.c_polymorphism.d2_polymorphism;
public class Student extends People {
public String name = "子类Student的名称";
@Override
public void run() {
System.out.println("学生跑得贼快~~~");
}
public void test() {
System.out.println("学生需要考试~~~");
}
}
- Teacher.java
package Advanced.c_polymorphism.d2_polymorphism;
public class Teacher extends People {
public String name = "子类Teacher的名称";
@Override
public void run() {
System.out.println("老师跑的气喘吁吁~~~");
}
public void teach() {
System.out.println("老师需要教知识~~~");
}
}
- Test.java
package Advanced.c_polymorphism.d2_polymorphism;
public class Test {
public static void main(String[] args) {
// 目标:理解多态的好处
// 好处1:可以实现解耦合,右边对象可以随时切换,后续业务随之改变
People p1 = new Student();
p1.run();
// p1.test(); // 多态下存在的问题,无法直接调用子类的独有功能
// 强制类型转换
Student s1 = (Student) p1;
s1.test(); // 学生需要考试~~~
// 强制类型转换可能存在的问题,编译阶段有继续或者实现关系就可以强制转换,但是运行时可能出现类型转换异常
// Teacher t1 = (Teacher) p1; // 运行时出现了:ClassCastException
if (p1 instanceof Student) {
Student s2 = (Student) p1;
s2.test(); // 学生需要考试~~~
} else {
Teacher t2 = (Teacher) p1;
t2.teach();
}
System.out.println("------------------------------");
// 好处2:可以使用父类类型的变量作为形参,可以接收一切子类对象
Student s = new Student();
go(s); // 学生跑得贼快~~~ 学生需要考试~~~
Teacher t = new Teacher();
go(t); // 老师跑的气喘吁吁~~~ 老师需要教知识~~~
}
// 好处2:可以使用父类类型的变量作为形参,可以接收一切子类对象
public static void go(People p) {
p.run();
if (p instanceof Student) {
Student s2 = (Student) p;
s2.test();
} else if (p instanceof Teacher) {
Teacher t2 = (Teacher) p;
t2.teach();
}
}
}
总结
- 类型转换有几种形式?能解决什么问题?
- 自动类型转换、强制类型转换
- 可以把对象转换成其真正的类型,从而解决了多态下不能调用子类独有方法的问题
- 强制类型转换需要注意什么?
- 存在继承/实现时,就可以进行强制类型转换,编译阶段不会报错
- 但是,运行时,如果发现对象的真是类型与强转后的类型不同会报错(ClassCastException)
- 强制类型转换前,Java 建议我们做什么事情?
- 使用
instanceof
判断当前对象的真是类型:对象 instanceof 类型