6.面向对象(中级)

一、包

包的本质就是不同的文件夹

  • 区分相同名字的类
  • 控制访问范围
1
package com.hspedu;

说明:

  • package: 关键字,表示打包
  • com.hspedu : 表示表名

命名规则:

  • 只能包含字母、数字、下划线、小圆点
    • 不能以数字开头
    • 不能是关键字和保留字
  • 一般是小写字母+小圆点
    • com.公司名.项目名.业务模块名
    • 如:com.sina.crm.user

常用的包:

  • java.lang
    • 基本包,默认引入,不需要再次引入
  • java.util
    • 系统提供的工具包,工具类,如:Scanner
  • java.net
    • 网络包,网络开发
  • java.awt
    • 做 java 的界面开发,GUI
1
2
3
import java.util.Scanner;	// 引入 Scanner 类

import java.util.* // 引入 util 下所有的类

注意事项:

  1. package 的作用是声明当前类所在的包,需要写在类的最上边,一个类中最多只能写一个 package
  2. import 指令放在 package 的下面,在类定义的前面,可以有多句且没有顺序要求

二、访问修饰符

访问修饰符:控制方法和属性的访问权限

修饰符访问级别本类同包子类不同包
public公开的
protected受保护的×
没有修饰符默认××
private私有的×××

注意事项:

  1. 修饰符可以用来修饰类中的属性、方法以及类;
  2. 只有默认和 public 才能修饰类
  3. 成员方法的访问规则和属性完全一样

三、封装

封装: 把抽象的数据(属性)和对数据的操作(方法)封装在一起,程序的其他部分只能通过被授权的操作(方法)才能对数据操作

封装方法:

  1. 将属性私有化 (private)
  2. 提供 公共的 set 方法,用于对属性进行判断并赋值
    1. public void setxxx(参数){数据验证; 属性 = 参数}
  3. 提供 公共的 get 方法,用于获取属性值
    1. public 数据类型 getXxxx(){return xxx};

快速设置 get 及 set 方法的快捷键: control + inter(⌃ + 回车键)

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
// 员工信息
public class Encap01 {
public static void main(String[] args) {
Person person = new Person();
person.setName("老王");
person.setAge(10);
System.out.println(person.info());
}
}

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

public void setAge(int age) {
if (age >=1 && age <= 120){
this.age = age;
}else{
System.out.println("年龄需要在 1 ~ 120 之间");
this.age = 18;
}
}

public int getAge() {
return age;
}

public void setName(String name) {
if (name.length() >= 2 && name.length() <= 6){
this.name = name;
}else {
System.out.println("名字程度需要在 2~6 之间");
}
}

public String getName() {
return name;
}

public String info(){
return "姓名:" + name + "\t年龄:" + age + "\t薪资:" + money;
}
}

四、继承

继承: 可以解决代码复用,通过 extends 来声明继承父类,获取父类的属性和方法;、
父类 也叫 基类
子类 也叫 派生类

1
class 子类 extends 父类{}

注意事项:

  • 子类继承了父类的所有属性和方法,但是私有属性不能在子类直接访问,要通过公共方法去访问
  • 子类必须调用父类的构造器,完成父类的初始化
  • 当创建子类对象时,不管使用子类的那个构造器,默认情况下都会调用父类的无参构造器
    • 如果父类没有无参构造器,则必须在子类的构造器中用 super 指定使用父类的那个构造器完成对父类的初始化工作,否则编译不通过
  • super() 和 this() 都只能在构造器中使用,并且只能放在第一行,所以两个方法不能在同一个构造器中
  • 父类构造器的调用不限于直接父类,可以一直往上追溯到 object 类

super 关键字:

  1. super 代表父类的引用,用于访问父类的属性、方法、构造器
    1. 访问属性: super.属性名
    2. 访问方法: super.方法名(参数列表)
    3. 访问构造器: super(参数列表) 1. 只能放在构造器的第一句,同一构造器只能出现一句

子类默认调用父类的 无参构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
// Father.java --- 父类有参构造器
public class Father {
public int age;

public Father() {
System.out.println("父类无参构造器:Father()");
}

public Father(int age) {
this.age = age;
System.out.println("父类有参构造器:Father(int age)");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Son.java
package com.study;

class Son extends Father {
public String name;

public Son(){
System.out.println("子类无参构造器:Son()");
}

public Son(String name){
this.name = name;
System.out.println("子类有参构造器:Son(String name)");
}
}
1
2
3
4
5
6
7
8
9
// Test.java
public class Test {
public static void main(String[] args) {
Son son = new Son("老王");
}
}

// 父类无参构造器:Father()
// 子类有参构造器:Son(String name)

子类 通过 super 指定 调用父类的构造器

1
2
3
4
5
6
7
// Father.java --- 父类没有无参构造器
public class Father {
public int age;

public Father(int age) {
this.age = age;
System.out.println("父类有参构造器:Father(int age)");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Son.java --- 子类通过 super 指定调用父类的构造器
class Son extends Father {
public String name;

public Son(){
super(10);
System.out.println("子类无参构造器:Son()");
}

public Son(String name){
super(10);
this.name = name;
System.out.println("子类有参构造器:Son(String name)");
}
}
1
2
3
4
5
6
7
8
9
// Test.java
public class Test {
public static void main(String[] args) {
Son son = new Son("老王");
}
}

// 父类有参构造器:Father(int age)
// 子类有参构造器:Son(String name)

super 和 this 的比较

区别点thissuper
访问属性如果本类没有此属性,则从父类中继续查找从父类开始查找属性
调用方法如果本类没有此方法,则从父类继续查找从父类开始查找方法
调用构造器调用本类构造器,必须放在构造器的首行调用父类构造器,必须放在子类构造器的首行
特殊表示当前对象子类中访问父类对象

super : 从他的父类开始,可以调用它所有父类的方法和属性
this : 从他自己开始, 可以调用包括他所有父类的方法和属性,根据就近原则,相同的属性或方法只能调用离他最近的

方法重写

  1. 子类方法的 参数、方法名 要和父类的方法一致
  2. 子类方法的返回值类型 和父类方法返回值类型一样,或者父类返回类型的子类
    1. 父类:public object getInfo()
    2. 子类:public String getInfo()
  3. 子类方法不能缩小父类方法的访问权限
    1. 父类: public void sayOk()
    2. 子类: void sayOk()
名称发生范围方法名形参列表返回类型修饰符
重载本类必须一样类型、个数或顺序至少有一个不同无要求无要求
重写父子类必须一样相同子类和父类的返回类型一致或使其子类子类方法不能缩小父类方法的访问方位
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
// Person.java
public class Person {
private String name;
private int age;

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;
}

public String say(){
return String.format("我叫 %s,今年 %d 岁",name,age);
}
}
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
// student.java
public class Student extends Person{
private int id;
private double score;

public Student(String name, int age, int id, double score) {
super(name, age);
this.id = id;
this.score = score;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public double getScore() {
return score;
}

public void setScore(double score) {
this.score = score;
}

public String say(){
return super.say() + String.format("我的学号是 %d,我考了 %f",id, score);
}
}
1
2
3
4
5
6
7
8
9
10
11
// test.java
public class Test {
public static void main(String[] args) {
Person person = new Person("老王", 20);
String a = person.say();
System.out.println(a);
Student student = new Student("张三",21,22,12);
String b = student.say();
System.out.println(b);
}
}

五、多态

多态: 方法或对象具有多种形态,是建立在封装和继承基础之上的

重点:

  • 一个对象的编译类型和运行类型可以不一致
  • 编译类型在定义对象时,就确定了,不能改变
  • 运行类型是可以改变的
  • 编译类型看定义时 = 号 的左边,运行类型看 = 号的 右边

注意事项:

  • 多态的前提: 两个对象存在继承关系
  • 多态向上转型
    • 本质: 服务类的引用指向了子类的对象
    • 语法: 父类类型 引用名 = new 子类类型()
    • 特点: 编译类型看左边, 运行类型看右边
  • 多态向下转型
    • 语法: 子类类型 引用名 = (子类类型)父类类型;
    • 只能强转父类的引用,不能强转父类的对象
    • 要求父类的引用必须指向当前目标类型的对象
    • 当讲下转型后,可以对用子类类型的所有成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 向上转型和向下转型
public class PolyDetail01 {
public static void main(String[] args) {
// 向上转型
Base base = new Sub();
System.out.println(base.count);

// 向下转型
Sub sub = (Sub)base;
System.out.println(sub.count);
}
}

class Base{
int count = 10;
}

class Sub extends Base{
int count = 20;
}

instanceOf : 比较符,用于判断对象的运行类型是否为 xx 类型或者 xx 类型的子类型

1
2
3
4
5
6
7
8
9
10
11
// instanceOf : 比较符
public class PolyDetail02 {
public static void main(String[] args) {
BB bb = new BB();
System.out.println(bb instanceof BB); // true
}
}

class AA{}

class BB extends AA{}

动态绑定机制

  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
// 动态绑定机制
public class DynamicBinding {
public static void main(String[] args) {
A a = new B();
// 方法有动态绑定机制,所以先查看他的运行类型,也就是子类中的方法
System.out.println(a.sum()); // 20 + 10
// 属性没有动态绑定机制,所以他会在当前类查找
System.out.println(a.sum1()); // 10 + 10
}
}

class A{
public int i=10;
public int sum(){
return getI() + 10;
}
public int sum1(){
return i+10;
}

public int getI() {
return i;
}
}

class B extends A{
public int i=20;

public int getI() {
return i;
}
}

多态数组

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
// 父类 Person
public class Person {
public String name;
public int age;

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;
}

public String say() {
return String.format("姓名= %s,年龄=%d", name, age);
}
}
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
// 子类 - Student
public class Student extends Person {
private double score;

public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}

public double getScore() {
return score;
}

public void setScore(double score) {
this.score = score;
}

@Override
public String say() {
return super.say() + String.format("分数=%f", score);
}

public String study() {
return String.format("学生:%s 正在学习", getName());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 子类 - Teacher
public class Teacher extends Person {
private double salary;

public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}

public double getSalary() {
return salary;
}

public void setSalary(double salary) {
this.salary = salary;
}

@Override // 方法重写
public String say() {
return super.say() + String.format("薪水= %f", salary);
}
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
// 主程序
public class Test {
public static void main(String[] args) {
Person[] person = new Person[5];
// 动态数组
person[0] = new Person("jack", 20);
person[1] = new Student("Tom", 20, 99);
person[2] = new Student("smith", 18, 60);
person[3] = new Teacher("scott", 50, 2000);
person[4] = new Teacher("king", 60, 10000);

// 遍历动态数组
for (int i = 0; i < person.length; i++) {
System.out.println(person[i].say());
// 判断 是否为 Student 类型,是就调用 study方法
if (person[i] instanceof Student) {
// 向下转型
System.out.println(((Student) person[i]).study());

} else if (person[i] instanceof Teacher) {
Teacher teacher = (Teacher) person[i];
System.out.println(teacher.teach());
}
}
}
}

六、object 类

1.equals

== 和 equals 的区别:

  • ==:
    • 既可以判断两个引用类型是否相等(地址是否相等),又可以判断两个值是否相等
  • equals:
    • 只能判断引用类型(默认判断地址是否相等,子类中会对其重写,用于判断内容是否相等
1
2
3
4
5
6
7
8
9
10
11
12
// == 和 equals
public class Equals {
public static void main(String[] args) {
String str1 = new String("你好");
String str2 = new String("你好");

// 判断两个引用类型的 地址 是否相等
System.out.println(str1 == str2); // false
// 判断两个引用类型的 值 是否相等
System.out.println(str1.equals(str2)); // true
}
}
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
// equals 方法重写
class Person{
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}
// equals 方法重写
public boolean equals(Object obj){
// 判断如果比较的两个对象是同一个对象,则直接返回true
if (this == obj){
return true;
}
// 类型判断
if (obj instanceof Person){
// 向下转型
Person person = (Person)obj;
return this.name.equals(person.name) && this.age == person.age;
}
return false;
}

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
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
70
71
72
73
74
75
76
77
78
// equals 重写案例
public class Doctor {
private String name;
private int age;
private String job;
private char gender;
private double sal;

public Doctor(String name, int age, String job, char gender, double sal) {
this.name = name;
this.age = age;
this.job = job;
this.gender = gender;
this.sal = sal;
}

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 getJob() {
return job;
}

public void setJob(String job) {
this.job = job;
}

public char getGender() {
return gender;
}

public void setGender(char gender) {
this.gender = gender;
}

public double getSal() {
return sal;
}

public void setSal(double sal) {
this.sal = sal;
}

@Override
public boolean equals(Object obj) {
// 判断 是否为同一地址
if (this == obj){
return true;
}
// 判断 obj 的运行类型 是否为 Doctor 的子类
if (obj instanceof Doctor){
// 向下转型
Doctor doctor = (Doctor) obj;
// 基本数据类型 使用 == ,String 使用 equals
return this.name.equals(doctor.name) && this.age == doctor.age && this.job.equals(doctor.job) && this.gender == doctor.gender && this.sal == doctor.sal;
}
return false;
}

public static void main(String[] args) {
Doctor doctor1 = new Doctor("老王", 50, "程序员", '男', 50000);
Doctor doctor2 = new Doctor("老王", 50, "程序员", '男', 50000);
System.out.println(doctor1.equals(doctor2));
}
}

2.hashCode

  • 提高具有哈希结构的容器的效率
  • 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的
  • 两个引用, 如果指向的是不同对象,则哈希值是不一样的
  • 哈希值主要是根据地址来的, 但是哈希值不等价于地址.

3.toString

  • 默认返回 : 全类名 + @ + 哈希值的十六进制
    • 子类往往会重写 toString 方法, 用于返回对象的属性信息
  • 重写 toString 方法, 打印对象或拼接对象时,都会自动调用该对象的 toString 形式
  • 当直接输出一个对象时, toString 方法会被默认调用

4.finalize

  • 当对象被回收时,系统自动调用该对象的 finalize 方法
    • 子类可以重写该方法, 做一些释放资源的操作
  • 当某个对象没有任何引用时, 就是使用回收机制来销毁对象, 在销毁前会先调用 finalize 方法
  • 垃圾回收机制是由系统来决定的, 也可以通过 System.gc() 主动触发垃圾回收机制

七.案例

1.创建 3 个 person 对象数组,并根据年龄排序

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
// person.java
public class Person {
private String name;
private int age;
private String job;

public Person(String name, int age, String job) {
this.name = name;
this.age = age;
this.job = job;
}

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 getJob() {
return job;
}

public void setJob(String job) {
this.job = job;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", job='" + job + '\'' +
'}';
}
}
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
// Test.java
public class Test {
public static void main(String[] args) {
Person[] person = new Person[3];
person[0] = new Person("张三", 30, "盗墓");
person[1] = new Person("李四", 28, "摸金校尉");
person[2] = new Person("王五", 50, "古董贩子");

Person temp = null;

for (int i = 0; i < person.length-1; i++) {
for (int j = 0; j < person.length-i-1; j++) {
if (person[j].getAge() < person[j+1].getAge()){
temp = person[j];
person[j] = person[j+1];
person[j+1] = temp;
}
}
}

for (int i = 0; i < person.length; i++) {
System.out.println(person[i]);
}
}
}