6.面向对象-2
大约 12 分钟学习笔记Java基础
一、包
包的本质就是不同的文件夹
提示
- 区分相同名字的类
- 控制访问范围
package com.hspedu;
说明:
- package: 关键字,表示打包
- com.hspedu : 表示表名
注
命名规则:
- 只能包含字母、数字、下划线、小圆点
- 不能以数字开头
- 不能是关键字和保留字
- 一般是小写字母+小圆点
- com.公司名.项目名.业务模块名
- 如:com.sina.crm.user
常用的包:
- java.lang
- 基本包,默认引入,不需要再次引入
- java.util
- 系统提供的工具包,工具类,如:Scanner
- java.net
- 网络包,网络开发
- java.awt
- 做 java 的界面开发,GUI
import java.util.Scanner; // 引入 Scanner 类
import java.util.* // 引入 util 下所有的类
注意
注意事项:
- package 的作用是声明当前类所在的包,需要写在类的最上边,一个类中最多只能写一个 package
- import 指令放在 package 的下面,在类定义的前面,可以有多句且没有顺序要求
二、访问修饰符
提示
访问修饰符:控制方法和属性的访问权限
修饰符 | 访问级别 | 本类 | 同包 | 子类 | 不同包 |
---|---|---|---|---|---|
public | 公开的 | √ | √ | √ | √ |
protected | 受保护的 | √ | √ | √ | × |
没有修饰符 | 默认 | √ | √ | × | × |
private | 私有的 | √ | × | × | × |
注意
注意事项:
- 修饰符可以用来修饰类中的属性、方法以及类;
- 只有默认和 public 才能修饰类
- 成员方法的访问规则和属性完全一样
三、封装
封装: 把抽象的数据(属性)和对数据的操作(方法)封装在一起,程序的其他部分只能通过被授权的操作(方法)才能对数据操作
封装方法:
- 将属性私有化 (private)
- 提供 公共的 set 方法,用于对属性进行判断并赋值
public void setxxx(参数){数据验证; 属性 = 参数}
- 提供 公共的 get 方法,用于获取属性值
public 数据类型 getXxxx(){return xxx};
提示
快速设置 get 及 set 方法的快捷键: control + inter(⌃ + 回车键)
// 员工信息
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 来声明继承父类,获取父类的属性和方法;、 父类 也叫 基类 子类 也叫 派生类
class 子类 extends 父类{}
提示
注意事项:
- 子类继承了父类的所有属性和方法,但是私有属性不能在子类直接访问,要通过公共方法去访问
- 子类必须调用父类的构造器,完成父类的初始化
- 当创建子类对象时,不管使用子类的那个构造器,默认情况下都会调用父类的无参构造器
- 如果父类没有无参构造器,则必须在子类的构造器中用 super 指定使用父类的那个构造器完成对父类的初始化工作,否则编译不通过
- super() 和 this() 都只能在构造器中使用,并且只能放在第一行,所以两个方法不能在同一个构造器中
- 父类构造器的调用不限于直接父类,可以一直往上追溯到 object 类
提示
super 关键字:
- super 代表父类的引用,用于访问父类的属性、方法、构造器
- 访问属性: super.属性名
- 访问方法: super.方法名(参数列表)
- 访问构造器: super(参数列表) 1. 只能放在构造器的第一句,同一构造器只能出现一句
子类默认调用父类的 无参构造器
// 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)");
}
}
// 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)");
}
}
// Test.java
public class Test {
public static void main(String[] args) {
Son son = new Son("老王");
}
}
// 父类无参构造器:Father()
// 子类有参构造器:Son(String name)
子类 通过 super 指定 调用父类的构造器
// Father.java --- 父类没有无参构造器
public class Father {
public int age;
public Father(int age) {
this.age = age;
System.out.println("父类有参构造器:Father(int age)");
// 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)");
}
}
// Test.java
public class Test {
public static void main(String[] args) {
Son son = new Son("老王");
}
}
// 父类有参构造器:Father(int age)
// 子类有参构造器:Son(String name)
super 和 this 的比较
区别点 | this | super |
---|---|---|
访问属性 | 如果本类没有此属性,则从父类中继续查找 | 从父类开始查找属性 |
调用方法 | 如果本类没有此方法,则从父类继续查找 | 从父类开始查找方法 |
调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
特殊 | 表示当前对象 | 子类中访问父类对象 |
注意
super : 从他的父类开始,可以调用它所有父类的方法和属性 this : 从他自己开始, 可以调用包括他所有父类的方法和属性,根据就近原则,相同的属性或方法只能调用离他最近的
方法重写
提示
- 子类方法的 参数、方法名 要和父类的方法一致
- 子类方法的返回值类型 和父类方法返回值类型一样,或者父类返回类型的子类
- 父类:public object getInfo()
- 子类:public String getInfo()
- 子类方法不能缩小父类方法的访问权限
- 父类: public void sayOk()
- 子类: void sayOk()
名称 | 发生范围 | 方法名 | 形参列表 | 返回类型 | 修饰符 |
---|---|---|---|---|---|
重载 | 本类 | 必须一样 | 类型、个数或顺序至少有一个不同 | 无要求 | 无要求 |
重写 | 父子类 | 必须一样 | 相同 | 子类和父类的返回类型一致或使其子类 | 子类方法不能缩小父类方法的访问方位 |
// 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);
}
}
// 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);
}
}
// 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 子类类型()
- 特点: 编译类型看左边, 运行类型看右边
- 多态向下转型
- 语法: 子类类型 引用名 = (子类类型)父类类型;
- 只能强转父类的引用,不能强转父类的对象
- 要求父类的引用必须指向当前目标类型的对象
- 当讲下转型后,可以对用子类类型的所有成员
// 向上转型和向下转型
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 类型的子类型
// 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{}
动态绑定机制
提示
- 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
- 当调用对象属性时,没有动态绑定机制,那是声明使用哪里
// 动态绑定机制
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;
}
}
多态数组
// 父类 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);
}
}
// 子类 - 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());
}
}
// 子类 - 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);
}
// 主程序
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:只能判断引用类型(默认判断地址是否相等,子类中会对其重写,用于判断内容是否相等)
// == 和 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
}
}
// 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;
}
}
// 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 对象数组,并根据年龄排序
// 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 + '\'' +
'}';
}
}
// 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]);
}
}
}