需要学习哪些语法?

  1. static静态关键字:之前定义的成员变量:name,age属于每个对象的。如何表示共享的信息?如在线人数等
  2. 继承:有些类只需要一个对象就可以了,如任务管理器对象,如何实现一个类只能对外产生一个对象?
  3. 多态:系统中很多实体类的属性和行为存在重复代码,如何把这些类信息进行优化,降低代码冗余,提升代码复用呢?

static静态关键字

修饰成员变量的用法

  • static是什么
    • static是静态的意思,可以用来修饰成员变量、成员方法。
    • static修饰成员变量之后称为静态成员变量(类变量),修饰方法之后称为静态方法(类方法)
    • static修饰后的成员变量,可以被类的所有对象共享(访问、修改)
1
2
3
4
public class User {
static String name; // 静态成员变量
int age; // 实例成员变量
}

修饰成员变量的内存原理

2022-116 (1)

  • static修饰的成员变量是什么? 有什么特点?

    1. 静态成员变量(有static修饰,属于类、加载一次,内存中只有一份),访问格式:
      1
      2
      类名.静态成员变量(推荐)
      对象.静态成员变量(不推荐)
    2. 实例成员变量(无static修饰,属于对象),访问格式:
      1
      对象.实例成员变量
  • 两种成员变量各自在什么情况下定义?

    1. 静态成员变量:表示在线人数等需要被类的所有对象共享的信息时。
    2. 实例成员变量:属于每个对象,且每个对象的该信息不同时(如:name,age,money…)

修饰成员变量的应用:在线人数统计

略。

修饰成员方法的用法

  • 之前我们定义的方法有的有static修饰,有的是没有的,有什么不同?

    1
    2
    3
    public void run(){
    System.out.println(name + "正在好好学习,天天向上~~");
    }
    1
    2
    3
    public static int getMax(int a , int b){
    return a > b ? a : b;
    }
  • 成员方法的分类:

    • 静态成员方法(有static修饰,归属于类),建议用类名访问,也可以用对象访问。
    • 实例成员方法(无static修饰,归属于对象),只能用对象触发访问。
  • 使用场景

    • 表示对象自己的行为的,且方法中需要访问实例成员的,则该方法必须申明成实例方法。
    • 如果该方法是以执行一个共用功能为目的,则可以申明成静态方法。
  • 成员方法的分类和访问分别是什么样的?

    • 静态成员方法(有static修饰,属于类和对象共享)访问格式:

      类名.静态成员方法。
      对象.静态成员方法。(不推荐)

    • 实例成员方法(无static修饰,属于对象)的访问格式:

      对象.实例成员方法。

修饰成员方法的内存原理

2022-116 (2)

修饰成员方法的应用:工具类

略(见下文)。

练习:定义员工类的实例

  • 需求:请完成一个标准实体类的设计,并提供如下要求实现。
    1. 某公司的员工信息系统中,需要定义一个公司的员工类Employee,包含如下信息(name, age , 所在部门名称dept ) , 定义一个静态的成员变量company记录公司的名称。
    2. 需要在Employee类中定义一个方法showInfos(),用于输出当前员工对象的信息。如name, age ,dept 以及公司名称company的信息。
    3. 需要在Employee类中定义定义一个通用的静态方法compareByAge,用于传输两个员工对象的年龄进入,并返回比较较大的年龄,例如:2个人中的最大年龄是:45岁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package study;

/**
* 员工类 Employee
*/
public class Employee {
private String name; //姓名
private int age; //年龄
private String deptName; //部门名称
public static String companyName; //公司名称

//输出当前员工对象的信息
public void showInfos() {
System.out.println("姓名:" + name + "," + "年龄:" + age + "," + "部门名称:" + deptName + "," + "公司名称:" + companyName);
}

//传输两个员工对象的年龄进入,并返回比较较大的年龄
public static int compareByAge(int a, int b) {
return a > b ? a : b;
}

public Employee() {
}

public Employee(String name, int age, String deptName) {
this.name = name;
this.age = age;
this.deptName = deptName;
}

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 getDeptName() {
return deptName;
}

public void setDeptName(String deptName) {
this.deptName = deptName;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package study;

/**
* 员工测试类 EmployeeTest
*/
public class EmployeeTest {
public static void main(String[] args) {
Employee employee1 = new Employee("周星驰", 30, "表演部");
Employee.companyName = "星辉传媒"; // 类名修改,静态成员变量
System.out.println(Employee.companyName);
employee1.showInfos(); // 类名调用,静态成员变量

Employee employee2 = new Employee("吴孟达",45,"表演部");
Employee.companyName = "佳丽传媒";
System.out.println(Employee.companyName);
employee2.showInfos();

// 类名调用,成员方法
int maxAge = Employee.compareByAge(employee1.getAge(), employee2.getAge());
System.out.println("最大年龄是:"+maxAge);
}
}

星辉传媒
姓名:周星驰,年龄:30,部门名称:表演部,公司名称:星辉传媒
佳丽传媒
姓名:吴孟达,年龄:45,部门名称:表演部,公司名称:佳丽传媒
最大年龄是:45

注意事项

static访问注意事项:

  1. 静态方法只能访问静态的成员,不可以直接访问实例成员。
  2. 实例方法可以访问静态的成员,也可以访问实例成员。
  3. 静态方法中是不可以出现this关键字的。

static应用知识:工具类

工具类的好处、要求

  • 工具类是什么?
    类中都是一些静态方法,每个方法都是以完成一个共用的功能为目的,这个类用来给系统开发人员共同使用的。

  • 案例导学:
    在企业的管理系统中,通常需要在一个系统的很多业务处使用验证码进行防刷新等安全控制。

  • 问题:
    同一个功能多处开发,会出现代码重复度过高。

  • 使用工具类的好处
    一是调用方便,二是提高了代码复用(一次编写,处处可用)

  • 为什么工具类中的方法不用实例方法做?
    实例方法需要创建对象调用,此时用对象只是为了调用方法,这样只会浪费内存。

  • 工具类是什么,有什么好处?

    1. 内部都是一些静态方法,每个方法完成一个功能
    2. 一次编写,处处可用,提高代码的重用性。
  • 工具类定义时有什么要求?
    有些工具类只有静态方法 ,不需要创建实例对象,建议将工具类的构造器私有化处理,避免实例化。

练习:定义数组工具类

  • 需求:在实际开发中,经常会遇到一些数组使用的工具类。请按照如下要求编写一个数组的工具类:ArraysUtils
    1. 我们知道数组对象直接输出的时候是输出对象的地址的,而项目中很多地方都需要返回数组的内容,请在ArraysUtils中提供一个工具类方法toString,用于返回整数数组的内容,返回的字符串格式如:[10, 20, 50, 34, 100](只考虑整数数组,且只考虑一维数组
    2. 经常需要统计平均值,平均值为去掉最低分和最高分后的分值,请提供这样一个工具方法getAerage,用于返回平均分。(只考虑浮点型数组,且只考虑一维数组
    3. 定义一个测试类TestDemo,调用该工具类的工具方法,并返回结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package study;

/**
* 数组的工具类
*/
public class ArraysUtils {
// 此工具类只有静态方法,不需要创建实例对象,建议将工具类的构造器私有化处理,避免实例化
private ArraysUtils() {
}

//静态方法
// 返回整数数组的内容,返回的字符串格式如:[10, 20, 50, 34, 100]
public static String toString(int[] arr) {
String arrToStr = "[";
for (int i = 0; i < arr.length; i++) {
if (i == arr.length - 1) {
arrToStr += arr[i] + "]";
} else {
arrToStr += arr[i] + ", ";
}
}
return arrToStr;
}

// 统计平均值,去掉最低分和最高分
public static double getAerage(double[] arr) {
if(arr == null) {
System.out.println("数组为空");
return -1;
}
if (arr.length < 3){
System.out.println("数组长度过短");
return -1;
}

double sum = 0, max = arr[0], min = arr[0];
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
if (max < arr[i]) {
max = arr[i];
}
if (min > arr[i]) {
min = arr[i];
}
}
return (sum - max - min) / (arr.length - 2);
}
}
1
2
3
4
5
6
7
8
9
10
public class ArraysUtilsTest {
public static void main(String[] args) {
int[] arr1 = {1, 3, 5, 7, 9, 11};
double[] arr2 = {1.56, 2.67, 2.7, 6.99, 3.58, 1.07, 3.6};

//使用 “类名.方法名” 直接调用工具类的方法
System.out.println(ArraysUtils.toString(arr1));
System.out.println(ArraysUtils.getAerage(arr2));
}
}

static应用知识:代码块

代码块的分类、作用

  • 代码块概述
    • 代码块是类的5大成分之一(成员变量、构造器,方法,代码块,内部类),定义在类中方法外。
    • 在Java类下,使用 { } 括起来的代码被称为代码块 。

代码块分为:

  • 静态代码块:

    1. 格式:static{}
    2. 特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次
    3. 使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。
  • 构造代码块(了解,见的少):

    1. 格式:{}
    2. 特点:每次创建对象,调用构造器执行时,都会执行该代码块中的代码,并且在构造器执行前执行
    3. 使用场景:初始化实例资源。
  • 静态代码块的作用是什么?
    如果要在启动系统时对静态资源进行初始化,则建议使用静态代码块完成数据的初始化操作。

静态代码块的应用案例:斗地主

  • 需求:在启动游戏房间的时候,应该提前准备好54张牌,后续才可以直接使用这些牌数据。

  • 分析:

    1. 该房间只需要一副牌。
    2. 定义一个静态的ArrayList集合存储54张牌对象,静态的集合只会加载一份
    3. 在启动游戏房间前,应该将54张牌初始化好
    4. 当系统启动的同时需要准备好54张牌数据,此时可以用静态代码块完成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package demo3;

/**
* 扑克牌 类
*/
public class Card {

private String number;

private String color;

private int index;

public Card() {
}

public Card(String number, String color, int index) {
this.number = number;
this.color = color;
this.index = index;
}

public String getNumber() {
return number;
}

public void setNumber(String number) {
this.number = number;
}

public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;
}

public int getIndex() {
return index;
}

public void setIndex(int index) {
this.index = index;
}

@Override
public String toString() {
return "Card{" +
"number='" + number + '\'' +
", color='" + color + '\'' +
", index=" + index +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package demo3;

import java.util.ArrayList;
import java.util.Collections;

/**
* 模拟初始化牌操作
* 点数:"3","4","5","6","7","8","9","10","J","Q","K","A","2"
* 花色:"♠","♥","♣","♦"
* 准备一个容器,存储54张牌对象,这个容器建议使用静态的集合。静态的集合只加载一次。
*/
public class CardTest {
public static ArrayList<Card> cards = new ArrayList<>();

//静态代码块 随着类的加载而加载
static {
String[] nums = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
String[] colors = {"♠", "♥", "♣", "♦"};

int index = 0;
for (String num : nums) {
//"3"
for (String color : colors) {
//"♠3", "♥3", "♣3", "♦3"
Card card = new Card(num, color, index++);
cards.add(card);
}
}
cards.add(new Card("14", "🃏", index++));
cards.add(new Card("15", "🃏", index++));

System.out.println("新牌是:" + cards);

Collections.shuffle(cards); //随机排列指定的列表
System.out.println("洗牌后:" + cards);
}

public static void main(String[] args) {
//给三个人发牌
ArrayList<Card> p1 = new ArrayList<>();
ArrayList<Card> p2 = new ArrayList<>();
ArrayList<Card> p3 = new ArrayList<>();

//留3个底牌
for (int i = 0; i < cards.size() - 3; i++) {
if (i % 3 == 0) {
p1.add(cards.get(i));
}
if (i % 3 == 1) {
p2.add(cards.get(i));
}
if (i % 3 == 2) {
p3.add(cards.get(i));
}
}

//底牌
ArrayList<Card> lastCards = new ArrayList<>();
lastCards.add(cards.get(cards.size() - 3));
lastCards.add(cards.get(cards.size() - 2));
lastCards.add(cards.get(cards.size() - 1));

System.out.println("p1的牌:" + p1);
System.out.println("p2的牌:" + p2);
System.out.println("p3的牌:" + p3);
System.out.println("底牌:" + lastCards);

}
}

static应用知识:单例

略。


面向对象三大特征之二:继承

继承的概述、好处

  • 什么是继承?

    • Java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起父子关系。
    • public class Student extends People {}
    • Student称为子类(派生类),People称为父类(基类或超类)
    • 作用:当子类继承父类后,就可以直接使用父类公共的属性和方法了
  • 使用继承的好处
    可以提高代码的复用率,减小代码冗余,增强类的扩展性。

  • 什么是继承? 继承的好处是啥?

    • 继承就是java允许我们用extends关键字,让一个类和另一个类建立起一种父子关系。
    • 提高代码复用性,减少代码冗余,增强类的功能扩展性。
  • 继承的格式:子类 extends 父类

  • 继承后子类的特点?

    • 子类继承父类,子类可以得到父类的属性和行为,子类可以使用。
    • Java中子类更强大

设计规范、内存运行原理

  • 继承设计规范:
    子类们相同特征(共性属性,共性方法)放在父类中定义,子类独有的的属性和行为应该定义在子类自己里面。

  • 为什么?
    如果子类的独有属性、行为定义在父类中,会导致其它子类也会得到这些属性和行为,这不符合面向对象逻辑。

案例:继承的设计规范

  • 需求:
    在教学资源管理系统中,存在学生、老师角色会进入系统。

  • 分析:

    • 学生信息和行为(名称年龄,所在班级,查看课表,填写听课反馈)
    • 老师信息和行为(名称年龄,部门名称,查看课表,发布问题)
    • 定义角色类作为父类包含属性(名称,年龄),行为(查看课表)
    • 定义子类:学生类包含属性(所在班级),行为(填写听课反馈)
    • 定义子类:老师类包含属性(部门名称),行为(发布问题)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package demo4;

public class People {
private String name;
private int age;

public void viewTimetable() {
System.out.println(name + "正在查看课表");
}

//无参构造器
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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package demo4;

public class Student extends People {
private String className; // 班级

public void write() {
System.out.println(getName() + "正在填写听课反馈");
}

// 无参构造器
public Student() {
}

public String getClassName() {
return className;
}

public void setClassName(String className) {
this.className = className;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package demo4;

public class Teacher extends People {
private String deptName; //部门名称

public void releaseQuestions() {
System.out.println(getClass() + "发布了一个课堂作业");
}

// 无参构造器
public Teacher() {
}

public String getDeptName() {
return deptName;
}

public void setDeptName(String deptName) {
this.deptName = deptName;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package demo4;

/**
* 继承
*/
public class Test {
public static void main(String[] args) {
Student student = new Student();

student.setName("李狗蛋");
student.setAge(23);
student.setClassName("计算机1班");

System.out.println(student.getName());
System.out.println(student.getAge());
System.out.println(student.getClassName());

//子类特有的
student.write();
//父类继承过来的
student.viewTimetable();
}
}

内存运行原理

2022-116 (3)

继承的特点

  • 继承的特点
    1. 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器。
    2. Java是单继承模式:一个类只能继承一个直接父类。
    3. Java不支持多继承、但是支持多层继承
    4. Java中所有的类都是Object类的子类。Java中所有类,要么直接继承了Object , 要么默认继承了Object , 要么间接继承了Object, Object是祖宗类。

继承后:成员变量、成员方法的访问特点

  • 在子类方法中访问成员(成员变量、成员方法)满足:就近原则

    • 先子类局部范围找
    • 然后子类成员范围找
    • 然后父类成员范围找,如果父类范围还没有找到则报错。
  • 如果子父类中,出现了重名的成员,会优先使用子类的,此时如果一定要在子类中使用父类的怎么办?
    可以通过super关键字,指定访问父类的成员。格式:super.父类成员变量/父类成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package demo5;

/**
* 继承后:成员变量、成员方法的访问特点
*/
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
z.ziShow();
}
}

//父类
class Fu {
String name = "父类成员变量";
}

//子类
class Zi extends Fu {
String name = "子类成员变量";

//子类成员方法
public void ziShow() {
String name = "子类成员方法局部变量";

System.out.println(name); //子类成员方法局部变量(就近原则)
System.out.println(this.name); //子类成员变量(this表示当前对象)
System.out.println(super.name); //父类成员变量(super关键字指定访问父类成员)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package demo5;

/**
* 继承后:成员变量、成员方法的访问特点
*/
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
z.ziShow();
}
}

//父类
class Fu {
String name = "父类成员变量";
String hobby = "钓鱼";
}

//子类
class Zi extends Fu {
String name = "子类成员变量";
String hobby = "吃鸡";

//子类成员方法
public void ziShow() {
//打印子类成员变量
System.out.println(this.name); //子类成员变量(this表示当前对象)
System.out.println(name); //子类成员方法局部变量(就近原则)
System.out.println(this.hobby); //打印吃鸡
System.out.println(hobby); //打印吃鸡

//打印父类成员变量
System.out.println(super.name); //父类成员变量(super关键字指定访问父类成员)
System.out.println(super.hobby); //打印钓鱼
}
}

继承后:方法重写

联系一下之前学的“方法重载”,那个指的是在同一个类中方法名一样但参数不一样。
但“方法重写”的名称和形参列表应该与被重写方法一致。

  • 什么是方法重写?
    在继承体系中,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法。

  • 方法重写的应用场景
    当子类需要父类的功能,但父类的该功能不完全满足自己的需求时。子类可以重写父类中的方法。

  • 案例演示:

    1. 旧手机的功能只能是基本的打电话,发信息
    2. 新手机的功能需要能够:基本的打电话下支持视频通话。基本的发信息下支持发送语音和图片。
  • @Override重写注解

    • @Override是放在重写后的方法上,作为重写是否正确的校验注解。
    • 加上该注解后如果重写错误,编译阶段会出现错误提示。
    • 建议重写方法都加@Override注解,代码安全,优雅!
  • 方法重写注意事项和要求

    • 重写方法的名称、形参列表必须与被重写方法的名称和参数列表一致。
    • 私有方法不能被重写。
    • 子类重写父类方法时,访问权限必须大于或者等于父类 (暂时了解 :private < 缺省 < protected < public
    • 子类不能重写父类的静态方法,如果重写会报错的。
  • 方法重写是什么样的?

    • 子类写一个与父类申明一样的方法覆盖父类的方法。
  • 方法重写建议加上哪个注解,有什么好处?
    @Override注解可以校验重写是否正确,同时可读性好。

  • 重写方法有哪些基本要求?

    • 重写方法的名称和形参列表应该与被重写方法一致。
    • 私有方法不能被重写。
    • 子类重写父类方法时,访问权限必须大于或者等于父类被重写的方法的权限。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package demo5;

/**
* 方法重写
*/
public class Test02 {
public static void main(String[] args) {
Student s = new Student();
s.lunch();
}
}

class Person {
public void eat() {
System.out.println("吃大米饭");
}

public void drink() {
System.out.println("喝开水");
}
}

class Student extends Person {
@Override
public void eat() {
System.out.println("吃黄焖鸡米饭");
}

@Override
public void drink() {
System.out.println("喝奶茶");
}

public void lunch() {
eat(); //吃黄焖鸡米饭
drink(); //喝奶茶

this.eat(); //吃黄焖鸡米饭
this.drink(); //喝奶茶

super.eat(); //吃大米饭
super.drink(); //喝开水
}
}

继承后:子类构造器的特点

  • 子类继承父类后构造器的特点:
    子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己。

  • 为什么?

    • 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。
    • 子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化。
  • 怎么调用父类构造器的?
    子类构造器的第一行语句默认都是:super(),不写也存在。

继承后:子类构造器访问父类有参构造器

  • super调用父类有参数构造器的作用:
    通过调用父类有参数构造器来初始化继承自父类的数据

  • 如果父类中没有无参数构造器,只有有参构造器,会出现什么现象呢?
    会报错。因为子类默认是调用父类无参构造器的。

  • 如何解决?
    子类构造器中可以通过书写 super(…),手动调用父类的有参数构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package demo6;

public class Person {
private String name;
private int age;

// 父类的无参构造器
public Person() {
}

// 父类的有参构造器
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package demo6;

public class Student extends Person {
String className;

// 子类的无参构造器
public Student() {
super();
}

// 子类的有参构造器(只有自己参数的)
public Student(String className) {
this.className = className;
}

// 子类的有参构造器(包括父类全部参数的)
public Student(String name, int age, String className) {
super(name, age);
this.className = className;
}
}

this、super使用总结

  • this:代表本类对象的引用;
  • super:代表父类存储空间的标识。
关键字 访问成员变量 访问成员方法 访问构造方法
this this.成员变量
访问本类成员变量
this.成员方法
访问本类成员方法
this(…)
访问本类构造器
super super.成员变量
访问父类成员变量
super.成员方法
访问父类成员方法
super(…)
访问父类构造器

案例练习:

上方表格中,只有this.(...)访问本地构造器我们还没学,用一个例子练一下

  • 学员信息登记系统中,后台创建对象封装数据的时候如果用户没有输入学校,则默认使用“河南师范大学”。
  • 如果用户输入了学校则使用用户输入的学校信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package demo7;

/**
* 使用 this(…) 调用本类的其他构造器
*/
public class Student {
private String schoolName;
private String name;

// 无参构造
public Student() {
}

// 有参构造
public Student(String name, String schoolName) {
this.name = name;
this.schoolName = schoolName;
}

// 使用 this(…) 调用本类的其他构造器
// 也就是说允许只传一个参数,第二个参数你不传的话就给你写死成固定值
public Student(String name) {
// this.name = name;
this(name, "河南师范大学"); //调用上面的有参构造
}
}
  • this(…)和super(…)使用注意点:
    1. 子类通过 this(…) 去调用本类的其他构造器,本类其他构造器会通过 super 去手动调用父类的构造器,最终还是会调用父类构造器的。
    2. 注意:this(…) super(…) 都只能放在构造器的第一行,所以二者不能共存在同一个构造器中。

面向对象三大特征之三:多态

面向对象三大特征有
封装:把一些静态属性动态方法封装起来成为一个对象
继承:解决了封装重复的问题,减小了代码的冗余
多态

多态的概述

  • 什么是多态?
    指对象可以有多种形态。

  • 多态的常见形式
    父类类型 对象名称 = new 子类构造器;

  • 多态中成员访问特点

    1. 方法调用:编译看左边,运行看右边。
    2. 变量调用:编译看左边,运行也看左边。(注意)
  • 多态的前提
    有继承/实现关系;有父类引用指向子类对象;有方法重写(多态侧重行为多态)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package demo8;

/**
* 多态的使用
*/
public class Person {
private String name;
private int age;

public void show() {
System.out.println(name + ", " + age);
}

//无参构造
public Person() {
}
//有参构造
public Person(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;
}
}
1
2
3
4
5
6
7
8
9
10
package demo8;

public class Student extends Person{
//继承父类,重写学生show方法
@Override
public void show() {
super.show();
System.out.println("学生的信息为:" + getName() + "," + getAge());
}
}
1
2
3
4
5
6
7
8
9
10
package demo8;

public class Teacher extends Person{
//继承父类,重写老师show方法
@Override
public void show() {
super.show();
System.out.println("老师的信息为:" + getName() + "," + getAge());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package demo8;

public class Test {
public static void main(String[] args) {
Student student = new Student();
student.setName("张三");
student.setAge(19);

Teacher teacher = new Teacher();
teacher.setName("王老师");
teacher.setAge(43);

register(student); //student调用方法
register(teacher); //teacher调用方法
}

//register方法,参数是Person类
public static void register(Person p) {
p.show();
}
}

张三, 19
学生的信息为:张三,19
王老师, 43
老师的信息为:王老师,43

多态的优势

  • 在多态形式下,右边对象可以实现解耦合,便于扩展和维护。

    1
    2
    Peoson p = new Student();
    p.show(); // 后续业务行为随对象而变,后续代码无需修改
  • 定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。

  • 多态下会产生的一个问题: 多态下不能使用子类的独有功能,只能使用子类中的重写方法

多态下: 类型转换问题

自动类型转换(从子到父)向上转型:

1
2
Peoson p = new Student(); //向上转型
p.eat(); //弊端是无法调用子类独有方法

强制类型转换(从父到子)向下转型:

  • 从父到子(必须进行强制类型转换,否则报错): 子类 对象变量 = (子类)父类类型的变量
  • 作用:可以解决多态下的劣势,可以实现调用子类独有的功能。
  • 注意:有继承/实现关系的类就可以在编译阶段进行强制类型转换。
    1
    2
    Peoson p = new Student(); //无法调用Student中的独有方法,解决方法如下
    Student s = (Student)p; //向下转型,把Peoson类对象重新转换为原来的Student类对象
  • 如果转型后的类型和对象真实的类型不是同一种类型,那么在运行代码时,就会出现ClassCastException(类强制转换异常)
    1
    2
    3
    4
    5
    Peoson p = new Student();
    Teacher p = (Teacher)p;
    //对象的真实类型是Student
    //你强转成Teacher类型是不可以的
    //因为类型不一样,会出现异常 ClassCastException(类强制转换异常)
  • Java建议强制转换前使用instanceof判断当前对象的真实类型,再进行强制转换。判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是其子类类型,是则返回true,反之返回false。
    1
    变量名 instanceof 真实类型

用一个案例理解一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package demo9;

/**
* 多态的弊端,与强制类型转换
*/
public class Test {
public static void main(String[] args) {
//自动类型转换(向上转型)
Animal a = new Dog();

a.eat();
//eat方法在父类Animal中存在,子类Dog中也进行了方法重写
//所以可以调用,执行的语句是子类中的重写方法

// a.lookHome();
// lookHome是子类Dog中的独有方法,在父类Animal中找不到
// 所以无法使用

//解决方案:强制转换回子类类型
//注意不能乱转换,原来是Dog,也要强转成Dog,不能转成Cat,否则会类型转换异常
Dog d = (Dog) a;

d.lookHome(); // 成功调用Dog中独有的lookHome方法

}
}

//父类
class Animal {
public void eat() {
System.out.println("动物吃东西");
}
}
//子类
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼"); //重写父类方法
}

public void catchMouse() {
System.out.println("猫的工作是抓老鼠"); //猫的独有方法
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头"); //重写父类方法
}

public void lookHome(){
System.out.println("狗看家"); //狗的独有方法
}
}

建议强制转换前使用instanceof判断类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package demo9;

/**
* 多态的弊端,与强制类型转换
*/
public class Test {
public static void main(String[] args) {
Cat a = new Cat();
show(a); //猫吃鱼, 猫的工作是抓老鼠

Dog d = new Dog();
show(d); //狗吃骨头, 狗看家
}

//多态:同一个show方法,可以根据传入的子类不同,执行不同的逻辑
public static void show(Animal a) {
//1. 父类以及各个子类都有的方法,可以直接调用
a.eat();

//2. 子类中独有的方法,不可以直接调用,需要先强制转换为原来的子类
//判断一下原来的子类是啥类型,就转回啥类型
if(a instanceof Dog){
Dog d = (Dog) a; //如果原来是狗,需要转换成狗,才能调用狗的方法
d.lookHome();
}else if(a instanceof Cat){
Cat c = (Cat) a; //如果原来是猫,需要转换成猫,才能调用猫的方法
c.catchMouse();
}else {
System.out.println("没有这个类型,无法转换");
}
}
}

//父类
class Animal {
public void eat() {
System.out.println("动物吃东西");
}
}
//子类
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼"); //重写父类方法
}

public void catchMouse() {
System.out.println("猫的工作是抓老鼠"); //猫的独有方法
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头"); //重写父类方法
}

public void lookHome(){
System.out.println("狗看家"); //狗的独有方法
}
}
  • 引用数据类型的类型转换,有几种方式?

    • 自动类型转换、强制类型转换。
  • 强制类型转换能解决什么问题?

    • 可以转换成真正的子类类型,从而调用子类独有功能。
  • 强制类型转换需要注意什么?

    • 有继承关系/实现的2个类型就可以进行强制转换,编译无问题。
    • 运行时,如果发现强制转换后的类型不是对象真实类型则报错(ClassCastException)
  • 强制类型转换前最好做什么事情,如何进行?

    • 使用instanceof判断当前对象的真实类型,再进行强制转换
    • 对象变量名 instanceof 真实类型

多态的综合案例

  • 需求:模拟一台电脑,实现使用USB鼠标、USB键盘

  • 分析

    1. 定义一个USB的接口(申明USB设备的规范必须是:可以接入和拔出)。
    2. 提供2个USB实现类代表鼠标和键盘,让其实现USB接口,并分别定义独有功能。
    3. 创建电脑对象,创建2个USB实现类对象,分别安装到电脑中并触发功能的执行。
1
先暂时不做,该案例好像以及超出多态的范围了,涉及接下来要讲的接口