Java基础知识(四) -- 面向对象(中)
1.包
1.3.1 包的作用
- (1)可以避免类重名:有了包之后,类的全名称就变为:包.类名【便于使用】
- (2)分类组织管理众多的类【便于管理类】
- (3)可以控制某些类型或成员的可见范围【控制访问范围】
Java中常见的包:
包名 | 作用 |
---|---|
java.lang | Java语言的核心类,如String、Math、Integer、 System和Thread等,提供常用功能 |
java.net | 执行与网络相关的操作的类和接口 |
java.io | 提供多种输入/输出功能的类 |
java.util | 实用工具类,如集合框架类、日期时间、数组工具类Arrays,文本扫描仪Scanner,随机值产生工具Random |
java.text | Java格式化相关的类 |
包的本质就是创建不同的文件夹/目录来保存类文件
1.3.2 如何声明包?
package 包名;
注意:
(1)必须在源文件的代码首行
(2)一个源文件只能有一个声明包的语句
包名命名规范与习惯:
-
- 所有单词都小写,每一个单词之间使用.分割
-
- 习惯用公司的域名倒置
1.3.3 如何跨包使用类?
前提:被使用的类或成员的权限修饰符是>缺省的,即可见的。
-
- 使用类型的全名称
java.util.Scanner input = new java.util.Scanner(System.in);
-
- 使用import 语句之后,代码中使用简名称
import 包.类名; import 包.*; import static 包.类名.静态成员;
2.访问修饰符
2.1 概述
Java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
-
公开级别:用 public 修饰,对外公开
-
受保护级别:用 protected 修饰,对子类和同一个包中的类公开
-
默认级别:没有修饰符号,向同一个包的类公开.
-
私有级别:用 private 修饰,只有类本身可以访问,不对外公开.
2.2 访问修饰符的访问范围
访问级别 | 访问修饰控制符 | 同类 | 同包 | 子类 | 不同包 |
---|---|---|---|---|---|
公开 | public | √ | √ | √ | √ |
受保护 | protected | √ | √ | √ | × |
默认 | 没有修饰符 | √ | √ | × | × |
私有 | private | √ | × | × | × |
## 3.面向对象编程-封装
3.1 概述
面向对象编程语言是对客观世界的模拟,客观世界里每一个事物的内部信息都是隐藏在对象内部的,外界无法直接操作和修改,只能通过指定的方式进行访问和修改。封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。正如现实生活中,每一个个体与个体之间是有边界的,每一个团体与团体之间是有边界的,而同一个个体、团体内部的信息是互通的,只是对外有所隐瞒。
随着业务系统越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要遵循“高内聚、低耦合”,而“高内聚,低耦合”的体现之一:
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合:仅对外暴露少量的方法用于使用
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
Q:封装就是把该隐藏的隐藏起来,该暴露的暴露出来。那么暴露的程度如何控制呢?
A: 依赖访问控制修饰符,也称为权限修饰符来控制。访问控制修饰符来控制相应的可见边界,边界有如下:类、包、子类、模块(Java9之后引入)。权限修饰符:public,protected,缺省,private。
3.2 封装的实现
-
- 将属性私有化private
-
- 提供一个公共的(public)set方法, 用于对属性判断并赋值
public void setXxx(类型 参数名){ //加入业务逻辑判断 属性 = 参数名; }
-
- 提供一个公共的(public)get方法, 用于获取属性值
public void getXxx(){ return xx; }
3.3 成员变量/属性私有化问题
成员变量(field)私有化之后,提供标准的get/set方法,我们把这种成员变量也称为属性(property)。 或者可以说只要能通过get/set操作的就是事物的属性,哪怕它没有对应的成员变量。
3.3.1 成员变量封装目的
- 隐藏类的实现细节
- 保证对象信息的完整性,提高代码的安全性。让使用者只能通过事先预定的方法来访问数据,从而可在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。
- 便于修改,提高代码的可维护性。主要体现在隐藏的部分,在内部修改了,如果其对外可以的访问方式不变,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实现,而对外的方法不变,使用者根本感觉不到它内部的修改。
3.3.2 成员变量封装实现
-
使用
private
修饰成员变量public class Chinese{ private static String Country; private String name; private int age; private boolean marry; }
-
提供
getXxx
方法 /setXxx
方法,可以访问成员变量package JavaBase; public class Chinese { private static String country; private String name; private int age; private boolean marry; public static void setCountry(String c) { country = c; } public static String getCountry() { return country; } public void setName(String n) { name = n; } public String getName() { return name; } public void setAge(int a) { age = a; } public int getAge() { return age; } public void setMarry(boolean m) { marry = m; } public boolean isMarry(){ return marry; } }
3.3.3 如何解决局部变量与成员变量同名问题
当局部变量与类变量(静态成员变量)同名时,在类变量前面加“类名.";
当局部变量与实例变量(非静态成员变量)同名时,在实例变量前面加“this.”。
public class Chinese {
private static String country;
private String name;
private int age;
public static void setCountry(String country) {
Chinese.country = country;
}
public static String getCountry() {
return country;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
4.面向对象编程-继承
4.1 概述
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。如图所示:
其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类、超类(superclass)或者基类。继承描述的是事物之间的所属关系,这种关系是:is-a
的关系。例如,图中猫属于动物,狗也属于动物。可见,父类更通用,子类更具体。通过继承,可以使多种事物之间形成一种关系体系。
继承可以解决代码复用,让编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。
4.2 继承的好处
- 提高代码的复用性。
- 提高代码的扩展性。
- 类与类之间产生了关系,是学习多态的前提。
4.3 继承的格式
【修饰符】 class 父类 {
...
}
【修饰符】 class 子类 extends 父类 {
...
}
示例1:
父类:
package JavaBase.extend.extend01;
// 父类:Pupil 和 Graduate 的父类
public class Student {
// 公共属性
public String name;
public int age;
private double score;
// 公共方法
public void setScore(double score) {
this.score = score;
}
public void showInfo() {
System.out.println("学生名 " + name + " 年龄 " + age + " 成绩 " + score);
}
}
子类:
package JavaBase.extend.extend01;
public class Pupil extends Student {
public void testing() {
System.out.println("小学生" + name + "正在考小学数学..");
}
}
package JavaBase.extend.extend01;
public class Graduate extends Student {
public void testing() {
System.out.println("大学生" + name + "正在考大学数学..");
}
}
测试类:
package JavaBase.extend.extend02;
import JavaBase.extend.extend01.Graduate;
import JavaBase.extend.extend01.Pupil;
public class Extends01 {
public static void main(String[] args) {
Pupil pupil = new Pupil();
pupil.name = "银角大王~";
pupil.age = 11;
pupil.testing();
pupil.setScore(50);
pupil.showInfo();
System.out.println("===========================");
Graduate graduate = new Graduate();
graduate.name="金角大王~";
graduate.age = 23;
graduate.testing();
graduate.setScore(80);
graduate.showInfo();
}
}
/*
小学生银角大王~正在考小学数学..
学生名 银角大王~ 年龄 11 成绩 50.0
===========================
大学生金角大王~正在考大学数学..
学生名 金角大王~ 年龄 23 成绩 80.0
*/
4.4 理解继承
-
- 子类继承父类所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问。
-
- 子类必须调用父类的构造器, 完成父类的初始化。
-
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不通过。
-
- 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)。
-
- super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)。
-
- super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器。
-
- Java 所有类都是 Object 类的子类, Object 是所有类的基类。
-
- 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)。
-
- 子类最多只能继承一个父类(指直接继承),即 Java 中是单继承机制。
-
- 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系。
4.5 继承的本质分析
示例:
public class GrandPa {
String name = "大头爷爷";
String hobby = "旅游";
}
public class Father extends GrandPa {
String name = "大头爸爸";
private int age = 39;
public int getAge(){
return age;
}
}
public class Son extends Father{
String name = "大头儿子";
}
public class ExtendTheory {
public static void main(String[] args) {
Son son = new Son();
System.out.println(son.name);// 大头儿子
System.out.println(son.getAge()); //39
System.out.println(son.hobby);//旅游
}
}
从以上的案例可以看出来:
- (1) 首先看子类是否有该属性
- (2) 如果子类有这个属性,并且可以访问,则返回信息
- (3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息…)
- (4) 如果父类没有就按照(3)的规则,继续找上级父类,直到 Object…
5.super关键字
5.1 概述
super 代表父类的引用,用于访问父类的属性、方法、构造器。
5.2 基本语法
-
1.访问父类的属性,但不能访问父类的private属性
super.属性名;
-
2.访问父类的方法,不能访问父类的private方法
super.方法名(参数列表);
-
3.访问父类的构造器
super(参数列表);//只能放在构造器的第一句,只能出现一句!
5.3 super的好处
- 1.调用父类的构造器的好处(分工明确,父类属性由父类初始化,子类的属性由子类初始化)
- 2.当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this、直接访问是一样的效果!
- 3.super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,使用super访问遵循就近原则。
5.4 super 与 this 比较
编号 | 区别点 | this | super |
---|---|---|---|
1 | 访问属性 | 访问本类中的属性,如果本类中没有此属性则从父类中继续查找 | 从父类开始查找属性 |
2 | 调用方法 | 访问本类中的方法,如果本类中没有此方法则从父类中继续查找 | 从父类开始查找方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类的构造器,必须放在子类构造器的首行 |
4 | 特殊 | 表示当前对象 | 子类中访问父类对象 |
6.方法重写/覆盖(override)
6.1 概述
简单的说:方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法。
示例
public class Animal {
public void cry() {
System.out.println("动物叫唤..");
}
public Object m1() {
return null;
}
public String m2() {
return null;
}
protected void eat() {
}
}
public class Dog extends Animal {
//1. 因为 Dog 是 Animal 子类
//2. Dog 的 cry 方法和 Animal 的 cry 定义形式一样(名称、返回类型、参数)
//3. 这时我们就说 Dog 的 cry 方法,重写了 Animal 的 cry 方法
public void cry() {
System.out.println("小狗汪汪叫..");
}
}
public class Override01 {
public static void main(String[] args) {
//演示方法重写的情况
Dog dog = new Dog();
dog.cry();// 小狗汪汪叫..
}
}
6.2 方法重写的注意事项
方法重写也叫方法覆盖,需要满足下面的条件:
- 1.子类的方法的形参列表,方法名称,要和父类方法的形参列表、方法名称完全一样。
- 2.子类方法的返回值类型和父类方法的返回值类型完全一样,或者是父类返回值类型的子类。
- 3.子类方法不能缩小父类方法的访问权限(public>protected>默认>private)。
6.3 方法重写与重载的区别
名称 | 发生范围 | 方法名 | 形参列表 | 返回值类型 | 修饰符 |
---|---|---|---|---|---|
重载(overload) | 本类 | 必须一样 | 类型、个数或顺序至少有一个不同 | 无要求 | 无要求 |
重写(overwride) | 父子类 | 必须一样 | 相同 | 子类重写的方法,返回值类型和父类返回值类型一致,或者其子类 | 子类不能缩小父类的方法的访问范围 |
7.面向对象编程-多态
7.1 概述
在Java中,多态是面向对象编程中的一个重要概念,它允许不同类型的对象对同一方法进行不同的实现。具体来说,多态性指的是通过父类的引用变量来引用子类的对象,从而实现对不同对象的统一操作。方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
例如:狗和猫都是动物,动物共同的行为都有吃这个动作,而狗可以表现为啃骨头,猫则可以表现为吃老鼠。这就是多态的表现,即同一件事情,发生在不同对象的身上,就会产生不同的结果。
public class Animal {
public void cry(){
System.out.println("Animal Cry() 动物在叫...");
}
}
public class Dog extends Animal{
public void cry(){
System.out.println("Dog Cry() 小狗汪汪叫...");
}
}
public class Cat extends Animal{
public void cry(){
System.out.println("Cat cry() 小猫喵喵叫...");
}
}
public class PolyObject {
public static void main(String[] args) {
//animal1 编译类型就是 Animal , 运行类型 Dog
Animal animal1 = new Dog();
animal1.cry(); // Dog Cry() 小狗汪汪叫...
//animal2 编译类型就是 Animal , 运行类型 Cat
Animal animal2 = new Cat();
animal2.cry(); // Cat cry() 小猫喵喵叫...
}
}
注:
-
- 一个对象的变编译类型和运行类型可以不一致。
-
- 编译类型在定义对象时, 就确定了,不能改变。
-
- 运行类型是可以变化的。
-
- 编译类型看定义时 = 的左边, 运行类型看 = 号的右边。
7.2 多态实现的条件
在Java中,要实现多态性,就必须满足以下条件:
-
- 继承关系
- 存在继承关系的类之间才能够使用多态性。多态性通常通过一个父类用变量引用子类对象来实现。
-
- 方法重写
- 子类必须重写(Override)父类的方法。通过在子类中重新定义和实现父类的方法,可以根据子类的特点行为改变这个方法的行为,如猫和狗吃东西的独特行为。
-
- 父类引用指向子类对象
- 使用父类的引用变量来引用子类对象。这样可以实现对不同类型的对象的统一操作,而具体调用哪个子类的方法会在运行时多态决定
多态的向上转型
本质:父类引用指向了子类的对象
语法:父类类型 引用名 = new 子类类型();
特点:① 编译类型看左边,运行类型看右边
② 可以调用父类中所有的成员(遵守访问权限)
③ 不能调用子类中特有成员
public class Animal {
String name = "动物";
int age = 10;
public void sleep() {
System.out.println("睡");
}
public void run() {
System.out.println("跑");
}
public void eat() {
System.out.println("吃");
}
public void show() {
System.out.println("hello,你好");
}
}
public class Cat extends Animal {
@Override
public void eat() {//方法重写
System.out.println("猫吃鱼");
}
public void catchMouse() {//Cat 特有方法
System.out.println("猫抓老鼠");
}
}
public class PolyObject {
public static void main(String[] args) {
//向上转型: 父类的引用指向了子类的对象
//语法:父类类型引用名 = new 子类类型();
Animal animal = new Cat();
animal.eat();
animal.sleep();
animal.show();
animal.run();
}
}
多态的向下转型
语法:子类类型 引用名 = (子类类型) 父类引用
特点:① 只能强转父类引用,但是不能父类对象
② 要求父类的引用必须指向的是当前目标类型的对象
③ 当向下转型后,可以调用子类类型中所有的成员
// 现在想调用Cat类中的catchMouse 方法--> 向下转型
public class PolyObject {
public static void main(String[] args) {
//向上转型: 父类的引用指向了子类的对象
//语法:父类类型引用名 = new 子类类型();
Animal animal = new Cat();
animal.eat();
animal.sleep();
animal.show();
animal.run();
Cat cat = (Cat) animal;
cat.catchMouse();
}
}
8.Java的动态绑定机制
-
- 当调用对象方法时, 该方法会和该对象的内存地址/运行类型绑定。
-
- 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。
示例如下:
public class A {
public int i = 10;
public int sum() {
return getI() + 10;
}
public int sum1() {
return i + 10;
}
public int getI() {
return i;
}
}
public class B extends A {
public int i = 20;
public int sum() {
return getI() + 20;//20+20
}
public int sum1() {
return i + 10;// 20+10
}
public int getI() {
return i;
}
}
public class DynamicBinding {
public static void main(String[] args) {
A a = new B();
System.out.println(a.sum());// 40
System.out.println(a.sum1());// 30
}
}
public class A {
public int i = 10;
public int sum() {
return getI() + 10;//20+10
}
public int sum1() {
return i + 10;//10+10
}
public int getI() {
return i;
}
}
public class B extends A {
public int i = 20;
public int getI() {
return i;//20
}
}
public class DynamicBinding {
public static void main(String[] args) {
A a = new B();
System.out.println(a.sum());// 30
System.out.println(a.sum1());// 20
}
}
9.Object类详解
9.1 equals方法
==和 equals 的对比
- == 是一个比较运算符, 既可以判断基本类型, 又可以判断引用类型。如果判断基本类型,判断的是值是否相等。如果判断引用类型,判断的是地址是否相等,判定是不是同一个对象。
- equals是Object类中的方法,只能判断引用数据类型。默认是判断地址是否相等,子类往往重写该方法,用于判断内容是否相等。
示例:
/***
*
* 判断两个 Person 对象的内容是否相等,
* 如果两个 Person 对象的各个属性值都一样,则返回 true,反之 false
*/
public class Person {
private String name;
private int age;
private String gender;
@Override
public boolean equals(Object obj) {
//判断如果比较的两个对象是同一个对象,则直接返回 t
if (this == obj) {
System.out.println("同一个对象");
return true;
}
//类型判断
if (obj instanceof Person) {
//进行 向下转型, 因为需要得到obj的各个属性
Person person = (Person) obj;
System.out.println("不是同一个对象");
return this.name.equals(person.name) && this.age == person.age && this.gender.equals(person.gender);
}
//如果不是 Person ,则直接返回 false
return false;
}
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class EqualObj {
public static void main(String[] args) {
Person person1 = new Person("jack", 10, "男");
Person person2 = new Person("jack", 20, "男");
Person person3 = new Person("jack", 10, "男");
System.out.println(person1.equals(person1));
System.out.println("=================================");
System.out.println(person1.equals(person2));
System.out.println("=================================");
System.out.println(person1.equals(person3));
}
}
9.2 hashCode方法
hashCode方法返回该对象的哈希码值。实际上, 由Object类定义的hashCode方法会针对不同的对象返回不同的整数[一般通过将该对象的内部地址转换成一个整数来实现]。该方法是提高具有哈希结构的容器效率。两个引用, 如果指向的是同一个对象, 则哈希值肯定是相同的, 反之哈希值是不同的。
示例:
public class HashCode {
public static void main(String[] args) {
Person person1 = new Person();
Person person2 = new Person();
Person person3 = person1;
System.out.println("person1的哈希值: " + person1.hashCode());
System.out.println("person2的哈希值: " + person2.hashCode());
System.out.println("person3的哈希值: " + person3.hashCode());
}
}
/*
person1的哈希值: 1163157884
person2的哈希值: 1956725890
person3的哈希值: 1163157884
*/
9.3 toString方法
该方法返回对象的字符串表示形式。Object类的toString方法返回一个包含该类的对象是一个实例的名称字符串的符号` @ ',和符号进制表示的对象的哈希码[全类名+@+哈希值的十六进制]。换句话说,此方法返回一个等于值的字符串:getClass().getName() + ‘@’ + Integer.toHexString(hashCode())。子类往往重写toString方法,用于返回对象的属性信息, 重写toString方法,打印对象或拼接对象时,都会自动调用该对象的toString形式。
public class Monster {
private String name;
private String job;
private double salary;
//重写toString方法,输出对象的属性
@Override
public String toString() {
return "Monster {name=" + name + ", job=" + job + ", salary=" + salary + "}";
}
@Override
protected void finalize() throws Throwable {
System.out.println("fin....");
}
public Monster() {
}
public Monster(String name, String job, double salary) {
this.name = name;
this.job = job;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
public class ToString_ {
public static void main(String[] args) {
Monster monster = new Monster("小妖怪", "巡山", 1000);
System.out.println(monster.toString() + "\nhashcode=" + monster.hashCode());
System.out.println("==当直接输出一个对象时,toString方法会被默认的调用==");
System.out.println(monster);//等价monster.toString()
}
}