1、包
1.1、需求引出
1.2、包的三大作用
- 区分相同名字的类
- 当类很多时,可以很好的管理类【看Java API文档】
- 控制访问范围
1.3、基本语法
package com.helloedu;
说明:
1、package: 关键字, 表示打包
2、com.helloedu: 表示包名
1.4、包的本质分析
1.5、包的命名
只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字
demo.class.exec1 // 错误 class是关键字
demo.12a // 错误 12a是数字开头
demo.ab12.oa // 对
命名规范:
一般是小写字母+小圆点
例如:com.xxx.oa.model;
com.xxx.oa.controller;
举例:
com.sina.crm.user; // 用户模块
com.sina.crm.order; // 订单模块
com.sina.crm.utils; // 工具类
1.6、常用的包
一个包下,包含很多的类,java 中常用的包有:
- java.lang.* // lang 包是基本包,默认引入,不需要再引入
- java.util.* // util 包,系统提供的工具包,工具类,使用 Scanner
- java.net.* // 网络包,网络开发
- java.awt.* // 是做 java 的界面开发,GUI
1.7、如何引入包
// 一个类中最多只有一句 package
package com.use_;
// 建议: 我们需要使用到哪个类, 就导入哪个类即可, 不建议使用 * 导入
// import 指令位置放在 package 的下面, 在类定义前面, 可以有多句且没有顺序要求
import java.util.Arrays; // 表示只会引入 java.util 包下的 Arrays
import java.util.*; // 表示将 java.util 包下的所有类都引入(导入)
public class Sort_ {
public static void main(String[] args) {
int[] arrList = {1, -2, 99, -105, 82, 30, -3, 0, 5, 29};
// 系统提供了相关的类, 可以方便完成排序 (Arrays 类)
Arrays.sort(arrList);
for (int i = 0; i < arrList.length; i++) {
System.out.print(arrList[i] + "\t");
}
}
}
1.8、注意事项和使用细节
2、访问修饰符
2.1、定义
java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
- 公开级别:用 public 修饰,对外公开
- 受保护级别:用 protected 修饰,对子类和同一个包中的类公开
- 默认级别:没有修饰符号,向同一个包的类公开
- 私有级别:用 private 修饰,只有类本身可以访问,不对外公开
2.2、4 种访问修饰符的访问范围
2.3、使用的注意事项
- 修饰符可以用来修饰类中的属性,成员方法及类
- 只有默认的和public才能修饰类,并且遵循上述访问权限的特点
- 成员方法的访问规则和属性完全一样
2.4、代码实现
TestClass.java
package access;
import access1.TestClass3;
import access1.TestClass4;
public class TestClass {
public int n1 = 10;
protected int n2 = 20;
int n3 = 30;
private int n4 = 40;
public void m1() {
System.out.println("TestClass中 public m1 方法");
}
protected void m2() {
System.out.println("TestClass中 protected m2 方法");
}
void m3() {
System.out.println("TestClass中 默认 m3 方法");
}
private void m4() {
System.out.println("TestClass中 private m4 方法");
}
public static void main(String[] args) {
// 在同类中测试
// 结论: 所有属性和方法都能正常访问
TestClass testClass = new TestClass();
System.out.println(testClass.n1);
System.out.println(testClass.n2);
System.out.println(testClass.n3);
System.out.println(testClass.n4);
testClass.m1();
testClass.m2();
testClass.m3();
testClass.m4();
System.out.println("=====");
// 在同一个包中, 不同的类中测试 测试 TestClass1 类
// 结论: private 属性和方法不能正常访问
TestClass1 testClass1 = new TestClass1();
System.out.println(testClass1.h1);
System.out.println(testClass1.h2);
System.out.println(testClass1.h3);
// System.out.println(testClass1.h4); // 报错
testClass1.q1();
testClass1.q2();
testClass1.q3();
// testClass1.q4(); // 报错
System.out.println("=====");
// 在同一个包中, 子类继承父类 测试 测试 TestClass2 类
// 结论: private 属性和方法不能正常访问
TestClass2 testClass2 = new TestClass2();
System.out.println(testClass2.h1);
System.out.println(testClass2.h2);
System.out.println(testClass2.h3);
// System.out.println(testClass2.h4); // 报错
testClass2.q1();
testClass2.q2();
testClass2.q3();
// testClass2.q4(); // 报错
System.out.println("=====");
// 在不同的包中, 子类继承父类 测试 测试 TestClass3 类
// 结论: 默认和private 属性和方法不能正常访问
TestClass3 testClass3 = new TestClass3();
System.out.println(testClass3.n1);
System.out.println(testClass3.n2);
// System.out.println(testClass3.n3); // 错误
// System.out.println(testClass3.n4); // 错误
testClass3.m1();
testClass3.m2();
// testClass3.m3(); // 错误
// testClass3.m4(); // 错误
System.out.println("=====");
// 在不同的包中, 测试 TestClass4 类
// 结论: protected、默认private 属性和方法不能正常访问, 只有公开类的属性和方法可以正常访问
TestClass4 testClass4 = new TestClass4();
System.out.println(testClass4.v1);
// System.out.println(testClass4.v2); // 报错
// System.out.println(testClass4.v3); // 报错
// System.out.println(testClass4.v4); // 报错
testClass4.c1();
// testClass4.c2(); // 报错
// testClass4.c3(); // 报错
// testClass4.c4(); // 报错
}
}
TestClass1.java
package access;
// 在同一个包中, 不同的类中测试
public class TestClass1 {
public int h1 = 100;
protected int h2 = 200;
int h3 = 300;
private int h4 = 400;
public void q1() {
System.out.println("TestClass1中 public q1 方法");
}
protected void q2() {
System.out.println("TestClass1中 protected q2 方法");
}
void q3() {
System.out.println("TestClass1中 默认 q3 方法");
}
private void q4() {
System.out.println("TestClass1中 private q4 方法");
}
}
TestClass2.java
package access;
public class TestClass2 extends TestClass1{
}
TestClass3.java
package access1;
import access.TestClass;
public class TestClass3 extends TestClass {
}
TestClass4.java
package access1;
public class TestClass4 {
public int v1 = 1000;
protected int v2 = 2000;
int v3 = 3000;
private int v4 = 4000;
public void c1() {
System.out.println("TestClass4中 public c1 方法");
}
protected void c2() {
System.out.println("TestClass4中 protected c2 方法");
}
void c3() {
System.out.println("TestClass4中 默认 c3 方法");
}
private void c4() {
System.out.println("TestClass4中 private c4 方法");
}
}
3、面向对象编程三大特征
封装、继承和多态
3.1、封装
3.1.1、定义
3.1.2、封装的优势
3.1.3、封装的实现步骤
public class Encapsulation {
public static void main(String[] args) {
Person person = new Person();
person.setName("张三");
person.setAge(150); // 年龄150不在范围内, 用默认值 18
person.setSalary(1000.98);
System.out.println(person.info());
}
}
// 不能随便查看人的年龄, 工资等隐私, 并对设置的年龄进行合理的验证
// 年龄合理就设置, 否则给默认年龄 18, 年龄必须在 1-120, 工资不能直接查看, name 的长度在 2-6 字符之间
class Person {
public String name;
private int age;
private double salary;
public Person() {
}
public Person(String name, int age, double salary) {
// 我们可以将 set 方法写在构造器中, 这样仍然可以验证
setName(name);
setAge(age);
setSalary(salary);
}
//自己写 setXxx 和 getXxx 太慢, 我们使用快捷键
// 然后根据要求来完善我们的代码
// 加入对数据的校验, 相当于增加了业务逻辑
public String getName() {
return name;
}
public void setName(String name) {
if (name.length() >= 2 && name.length() <= 6) {
this.name = name;
} else {
System.out.println("名字的长度不对, 需要(2-6)个字符, 默认名字");
this.name = "无名";
}
}
public int getAge() {
return age;
}
public void setAge(int age) { // 如果是合理范围
if (age >= 1 && age <= 120) {
this.age = age;
} else {
System.out.println("你设置年龄不对, 需要在 (1-120), 给默认年龄 18");
this.age = 18; // 给一个默认年龄
}
}
public double getSalary() {
// 可以这里增加对当前对象的权限判断
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
// 写一个方法, 返回属性信息
public String info() {
return "信息为 name=" + name + " age=" + age + " 薪水=" + salary;
}
}
3.1.4、将构造器和 setXxx 结合
public Person(String name, int age, double salary) {
// 我们可以将 set 方法写在构造器中, 这样仍然可以验证
setName(name);
setAge(age);
setSalary(salary);
}
3.2、继承
3.2.1、定义
继承可以解决代码复用, 让我们的编程更加靠近人类思维, 当多个类存在相同的属性(变量)和方法时, 可以从这些类中抽象出父类, 在父类中定义这些相同的属性和方法, 所有的子类不需要重新定义这些属性和方法, 只需要通过 extends 来 声明继承父类即可
3.2.2、定义继承的基本语法
3.2.3、代码实现
Extends.java
package com.jm;
public class Extends {
public static void main(String[] args) {
Pupil pupil = new Pupil();
pupil.name = "张三";
pupil.age = 18;
pupil.setScore(95);
pupil.testing();
pupil.showInfo();
System.out.println("=====");
Graduate graduate = new Graduate();
graduate.name = "李四";
graduate.age = 20;
graduate.setScore(100);
graduate.testing();
graduate.showInfo();
}
}
// 父类, 是 Pupil 和 Graduate 的父类
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);
}
}
Pupil.java
package com.jm;
public class Pupil extends Student { // 子类Pupil 继承父类Student
public void testing() {
System.out.println("小学生 " + name + " 正在考小学数学..");
}
}
Graduate.java
package com.jm;
public class Graduate extends Student { // 子类Graduate 继承父类Student
public void testing() {
System.out.println("大学生 " + name + " 正在考大学数学..");
}
}
3.2.4、继承给编程带来的便利
- 代码的复用性提高了
- 代码的扩展性和维护性提高了
3.2.5、继承的细节问题
- 子类继承了所有的属性和方法, 非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问, 要通过父类提供公共的方法去访问
- 子类必须调用父类的构造器, 完成父类的初始化
- 当创建子类对象时, 不管使用子类的哪个构造器, 默认情况下总会去调用父类的无参构造器, 如果父类没有提供无参构造器, 则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作, 否则, 编译不会通过
- 如果希望指定去调用父类的某个构造器, 则显式的调用一下:super(参数列表)
- super 在使用时, 必须放在构造器第一行(super 只能在构造器中使用)
- super() 和 this() 都只能放在构造器第一行, 因此这两个方法不能共存在一个构造器
- java 所有类都是 Object 类的子类, Object 是所有类的基类.
- 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
- 子类最多只能继承一个父类(指直接继承), 即 java 中是单继承机制
- 不能滥用继承, 子类和父类之间必须满足 is-a 的逻辑关系
ExtendsDetail.java
package com.jm;
public class ExtendsDetail {
public static void main(String[] args) {
System.out.println("===第 1 个对象====");
Sub sub = new Sub(); // 创建了子类对象 sub
/* 输出分析:
1 构造器 TopBase() 被调用...
2 父类 Base(String name, int age)构造器被调用....
3 子类 Sub()构造器被调用....
*/
System.out.println("===第 2 个对象====");
Sub sub2 = new Sub("jack"); // 创建了子类对象 sub2
/*
1 构造器 TopBase() 被调用...
2 父类 Base(String name, int age)构造器被调用....
3 子类 Sub(String name)构造器被调用....
*/
System.out.println("===第 3 对象====");
Sub sub3 = new Sub("king", 10); // 创建了子类对象 sub2
sub.sayOk();
/*
1 构造器 TopBase() 被调用...
2 父类 Base(String name, int age)构造器被调用....
3 子类 Sub(String name, int age)构造器被调用....
4 100 200 300
5 test100
6 test200
7 test300
8 n4=400
9 test400
*/
}
}
TopBase.java
package com.jm;
public class TopBase {
public TopBase() {
super(); // Object 的无参构造器
System.out.println("构造器 TopBase() 被调用...");
}
}
Base.java
package com.jm;
public class Base extends TopBase { //父类
// 4 个属性
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public Base() {
// 无参构造器
System.out.println("父类 Base()构造器被调用....");
}
public Base(String name, int age) { // 有参构造器
// 默认 super()
System.out.println("父类 Base(String name, int age)构造器被调用....");
}
public Base(String name) { // 有参构造器
System.out.println("父类 Base(String name)构造器被调用....");
}
//父类提供一个 public 的方法, 返回了 n4
public int getN4() {
return n4;
}
public void test100() {
System.out.println("test100");
}
protected void test200() {
System.out.println("test200");
}
void test300() {
System.out.println("test300");
}
private void test400() {
System.out.println("test400");
}
// call
public void callTest400() {
test400();
}
}
Sub.java
package com.jm;
// 输入 ctrl + H 可以看到类的继承关系
public class Sub extends Base { // 子类
public Sub(String name, int age) {
// 1. 调用父类的无参构造器, 如下或者什么都不写, 默认就是调用super()
// super(); // 父类的无参构造器
// 2. 调用父类的 Base(String name) 构造器
// super("tom");
// 3. 调用父类的 Base(String name, int age) 构造器
super("king", 20);
// 细节: super 在使用时, 必须放在构造器第一行
// 细节: super() 和 this() 都只能放在构造器第一行, 因此这两个方法不能共存在一个构造器
// this() 不能再使用了
System.out.println("子类 Sub(String name, int age)构造器被调用....");
}
public Sub() { // 无参构造器
// super(); // 默认调用父类的无参构造器
super("smith", 10);
System.out.println("子类 Sub()构造器被调用....");
}
// 当创建子类对象时, 不管使用子类的哪个构造器, 默认情况下总会去调用父类的无参构造器
public Sub(String name) {
super("tom", 30);
// do nothing...
System.out.println("子类 Sub(String name)构造器被调用....");
}
public void sayOk() {
// 子类方法
// 非私有的属性和方法可以在子类直接访问
// 但是私有属性和方法不能在子类直接访问
System.out.println(n1 + " " + n2 + " " + n3);
test100();
test200();
test300();
// test400(); 错误
// 要通过父类提供公共的方法去访问
System.out.println("n4=" + getN4());
callTest400();
}
}
3.2.6、继承的本质分析
当子类对象创建好后,建立查找的关系
ExtendsTheory.java
package com.jm;
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son(); // 内存的布局
// 要按照查找关系来返回信息
// (1) 首先看子类是否有该属性
// (2) 如果子类有这个属性, 并且可以访问, 则返回信息
// (3) 如果子类没有这个属性, 就看父类有没有这个属性(如果父类有该属性, 并且可以访问, 就返回信息..)
// (4) 如果父类没有就按照(3)的规则, 继续找上级父类, 直到 Object...
System.out.println(son.name); // 返回就是大头儿子
// System.out.println(son.age); // 报错
System.out.println(son.getAge()); // 返回的就是 39
System.out.println(son.hobby); // 返回的就是旅游
}
}
class GrandPa { // 爷类
String name = "大头爷爷";
String hobby = "旅游";
}
class Father extends GrandPa { //父类
String name = "大头爸爸";
private int age = 39;
public int getAge() {
return age;
}
}
class Son extends Father { //子类
String name = "大头儿子";
}
子类创建的内存布局
3.2.7、super 关键字
super 代表父类的引用,用于访问父类的属性、方法、构造器
3.2.7.1、基本语法
Base.java
package _super;
public class Base { // 父类是Object
public int n1 = 999;
public int age = 111;
public int n4 = 20;
public void cal() {
System.out.println("Base 类的 cal() 方法...");
}
public void eat() {
System.out.println("Base 类的 eat().....");
}
}
A.java
package _super;
public class A extends Base {
// 4 个属性
// public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public A() {
}
public A(String name) {
}
public A(String name, int age) {
}
// public void cal() {
// System.out.println("A 类的 cal() 方法...");
// }
public void test100() {
}
protected void test200() {
}
void test300() {
}
private void test400() {
}
}
B.java
package _super;
public class B extends A {
public int n1 = 888;
protected int n10 = 200;
// 编写测试方法
public void test() {
// super 的访问不限于直接父类, 如果爷爷类和本类中有同名的成员, 也可以使用 super 去访问爷爷类的成员;
// 如果多个基类(上级类)中都有同名的成员, 使用 super 访问遵循就近原则 A->B->C
System.out.println("super.n1=" + super.n1);
super.cal();
}
// 访问父类的属性, 但不能访问父类的 private 属性
public void hi() {
System.out.println(super.n1 + " " + super.n2 + " " + super.n3);
}
public void cal() {
System.out.println("B 类的 cal() 方法...");
}
public void sum() {
System.out.println("B 类的 sum()");
// 希望调用父类-A 的 cal 方法
// 这时, 因为子类 B 没有 cal 方法, 因此我可以使用下面三种方式
// 找 cal 方法时(cal() 和 this.cal()),顺序是:
// (1) 先找本类, 如果有, 则调用
// (2) 如果没有, 则找父类(如果有, 并可以调用, 则调用)
// (3)如果父类没有, 则继续找父类的父类, 整个规则, 就是一样的, 直到 Object 类
// 提示: 如果查找方法的过程中, 找到了, 但是不能访问, 则报错, cannot access
// 如果查找方法的过程中, 没有找到, 则提示方法不存在
cal();
this.cal(); // 等价 cal
// 找 cal 方法(super.call()) 的顺序是直接查找父类,其他的规则一样
// super.cal();
// 演示访问属性的规则
// n1 和 this.n1 查找的规则是:
// (1) 先找本类, 如果有, 则调用
// (2) 如果没有, 则找父类(如果有, 并可以调用, 则调用)
// (3) 如果父类没有, 则继续找父类的父类, 整个规则, 就是一样的, 直到 Object 类
// 提示: 如果查找属性的过程中, 找到了, 但是不能访问, 则报错, cannot access
// 如果查找属性的过程中, 没有找到, 则提示属性不存在
System.out.println(n1);
System.out.println(this.n1);
// 找 n1 (super.n1) 的顺序是直接查找父类属性,其他的规则一样
System.out.println(super.n1);
// System.out.println(super.n4); // 父类中n4是private, 爷爷类中是public, 也不会向上继续查找, 直接报错
// System.out.println(super.n10); // n10在本类中, 调用了super 不会从本类查找, 直接报错
}
// 访问父类的方法,不能访问父类的 private 方法 super.方法名(参数列表);
public void ok() {
super.test100();
super.test200();
super.test300();
// super.test400(); // 不能访问父类 private 方法
}
// 访问父类的构造器(这点前面用过): super(参数列表);只能放在构造器的第一句, 只能出现一句!
public B() {
// super();
// super("jack", 10);
super("jack");
}
}
Super01.java
package _super;
public class Super01 {
public static void main(String[] args) {
B b = new B(); // 子类对象
b.sum();
/*
1 B 类的 sum()
2 B 类的 cal() 方法...
3 B 类的 cal() 方法...
4 888
5 888
6 999
*/
b.test();
/*
1 super.n1=999
2 Base 类的 cal() 方法...
*/
}
}
3.2.7.2、super 细节
3.2.7.3、super 和 this 的比较
3.2.8、方法重写/覆盖(override)
Animal.java
package override_;
public class Animal {
public void cry() {
System.out.println("动物叫唤..");
}
public Object m1() {
return null;
}
public String m2() {
return null;
}
public AAA m3() {
return null;
}
protected void eat() {
}
}
Dog.java
package override_;
public class Dog extends Animal {
// 1. 因为 Dog 是 Animal 子类
// 2. Dog 的 cry 方法和 Animal 的 cry 定义形式一样(名称、返回类型、参数)
// 3. 这时我们就说 Dog 的 cry 方法, 重写了 Animal 的 cry 方法
public void cry() {
System.out.println("小狗汪汪叫..");
}
// 子类方法的返回类型和父类方法返回类型一样, 或者是父类返回类型的子类
// 比如 父类 返回类型是 Object, 子类方法返回类型是 String
public String m1() {
return null;
}
// 这里 Object 不是 String 的子类, 因此编译错误
// public Object m2() {
// return null;
// }
public BBB m3() {
return null;
}
// 子类方法不能缩小父类方法的访问权限
// public > protected > 默认 > private
public void eat() {
}
}
class AAA {
}
class BBB extends AAA {
}
Override01.java
package override_;
public class Override01 {
public static void main(String[] args) {
// 方法重写的情况
Dog dog = new Dog();
dog.cry(); // ctrl+b
}
}
3.2.8.1、注意事项和使用细节
方法重写也叫方法覆盖,需要满足下面的条件
3.2.8.2、重写和重载的比较
3.3、多态
3.3.1、传统方法与多态方法
Animal.java
package poly_;
public class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Dog.java
package poly_;
public class Dog extends Animal{
public Dog(String name) {
super(name);
}
}
Cat.java
package poly_;
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
}
Food.java
package poly_;
public class Food {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Food(String name) {
this.name = name;
}
}
Bone.java
package poly_;
public class Bone extends Food{
public Bone(String name) {
super(name);
}
}
Fish.java
package poly_;
public class Fish extends Food {
public Fish(String name) {
super(name);
}
}
Master.java
package poly_;
public class Master {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Master(String name) {
this.name = name;
}
// 传统方法
public void feed(Dog dog, Bone bone) {
System.out.println("主人" + name + " 正在给 " + dog.getName() + " 吃 " + bone.getName());
}
public void feed(Cat cat, Fish fish) {
System.out.println("主人" + name + " 正在给 " + cat.getName() + " 吃 " + fish.getName());
}
// 多态方法
// 使用多态机制, 可以统一的管理主人喂食的问题
// animal 编译类型是 Animal, 可以指向(接收) Animal 子类的对象
// food 编译类型是 Food, 可以指向(接收) Food 子类的对
public void feed(Animal animal, Food food) {
System.out.println("主人" + name + " 正在给 " + animal.getName() + " 吃 " + food.getName());
}
public static void main(String[] args) {
// 传统方法
Master master = new Master("张三");
Dog dog = new Dog("大黄狗");
Bone bone = new Bone("大棒骨");
master.feed(dog, bone);
Cat cat = new Cat("小花猫");
Fish fish = new Fish("黄花鱼");
master.feed(cat, fish);
// 多态方式
Master master1 = new Master("李四");
Dog dog1 = new Dog("二哈");
Bone bone1 = new Bone("小排骨");
master1.feed(dog1, bone1);
Cat cat1 = new Cat("大脸猫");
Fish fish1 = new Fish("金枪鱼");
master1.feed(cat1, fish1);
}
}
3.3.2、定义
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的
3.3.3、多态的具体体现
3.3.3.1、方法的多态
重写和重载就体现多态
package PloyMethod;
public class PloyMethod {
public static void main(String[] args) {
// 方法重载体现多态
A a = new A();
// 这里我们传入不同的参数, 就会调用不同 sum 方法, 就体现多态
System.out.println(a.sum(10, 20));
System.out.println(a.sum(10, 20, 30));
// 方法重写体现多态
B b = new B();
a.say();
b.say();
}
}
class B { // 父类
public void say() {
System.out.println("B say() 方法被调用...");
}
}
class A extends B { // 子类
public int sum(int n1, int n2) { // 和下面 sum 构成重载
return n1 + n2;
}
public int sum(int n1, int n2, int n3) {
return n1 + n2 + n3;
}
public void say() {
System.out.println("A say() 方法被调用...");
}
}
3.3.3.2、对象的多态
- 一个对象的编译类型和运行类型可以不一致
- 编译类型在定义对象时就确定了, 不能改变
- 运行类型是可以变化的
- 编译类型看定义时 = 号的左边, 运行类型看 = 号的右边
Animal animal = new Dog(); 【animal编译类型是Animal, 运行类型是Dog】
animal = new Cat(); 【animal的运行类型变成了Cat, 编译类型仍然是Animal】
Animal.java
package PloyMethod;
public class Animal {
public void cry() {
System.out.println("Animal cry() 动物在叫....");
}
}
Cat.java
package PloyMethod;
public class Cat extends Animal {
public void cry() {
System.out.println("Cat cry() 小猫喵喵叫...");
}
}
Dog.java
package PloyMethod;
public class Dog extends Animal {
public void cry() {
System.out.println("Dog cry() 小狗汪汪叫...");
}
}
PolyObject.java
package PloyMethod;
public class PolyObject {
public static void main(String[] args) {
// 体验对象多态特点
// animal 编译类型就是 Animal, 运行类型 Dog
Animal animal = new Dog();
// 因为运行时, 执行到改行时, animal 运行类型是 Dog, 所以 cry 就是 Dog 的 cry
animal.cry(); // 小狗汪汪叫
// animal 编译类型 Animal, 运行类型就是 Cat
animal = new Cat();
animal.cry(); // 小猫喵喵叫
}
}
3.3.4、多态注意事项和细节讨论
多态的前提是:两个对象(类)存在继承关系
3.3.4.1、多态的向上转型
3.3.4.2、多态的向下转型
3.3.4.3、代码实现
Animal.java
package PolyDetail;
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, 你好");
}
}
Cat.java
package PolyDetail;
public class Cat extends Animal {
public void eat() { // 方法重写
System.out.println("猫吃鱼");
}
public void catchMouse() { // Cat 特有方法
System.out.println("猫抓老鼠");
}
}
PolyDetail.java
package PolyDetail;
public class PolyDetail {
public static void main(String[] args) {
// 向上转型: 父类的引用指向了子类的对象
// 语法: 父类类型引用名 = new 子类类型();
Animal animal = new Cat();
Object object = new Cat(); // 可以正常访问, Object 也是 Cat 的父类
// 向上转型调用方法的规则如下:
// (1) 可以调用父类中的所有成员(需遵守访问权限)
// (2) 但是不能调用子类的特有的成员
// (3) 因为在编译阶段, 能调用哪些成员, 是由编译类型来决定的
// animal.catchMouse(); // 报错
// (4) 最终运行效果看子类(运行类型)的具体实现, 即调用方法时按照从子类(运行类型)开始查找方法, 调用规则与方法调用规则一致
animal.eat(); // 猫吃鱼..
animal.run(); // 跑
animal.show(); // hello, 你好
animal.sleep(); // 睡
// 可以调用 Cat 的 catchMouse 方法
// 多态的向下转型
// (1) 语法: 子类类型 引用名 = (子类类型)父类引用
Cat cat = (Cat) animal; // cat 的编译类型 Cat, 运行类型是 Cat
cat.catchMouse(); // 猫抓老鼠
// (2) 要求父类的引用必须指向的是当前目标类型的对象
// Dog dog = (Dog) animal; // 报错
}
}
属性没有重写之说!属性的值看编译类型
package PolyDetail;
public class PolyDetail02 {
public static void main(String[] args) {
// 属性没有重写之说!属性的值看编译类型
Base base = new Sub(); // 向上转型
System.out.println(base.count); // 看编译类型 10
Sub sub = new Sub();
System.out.println(sub.count); // 20
}
}
class Base { // 父类
int count = 10; // 属性
}
class Sub extends Base { // 子类
int count = 20; // 属性
}
instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型
package PolyDetail;
public class PolyDetail03 {
public static void main(String[] args) {
BB bb = new BB();
System.out.println(bb instanceof BB); // true
System.out.println(bb instanceof AA); // true
// aa 编译类型 AA, 运行类型是 BB
// BB 是 AA 子类
AA aa = new BB();
System.out.println(aa instanceof AA); // true
System.out.println(aa instanceof BB); // true
Object obj = new Object();
System.out.println(obj instanceof AA); // false
String str = "hello";
// System.out.println(str instanceof AA); // 报错
System.out.println(str instanceof Object); // true
}
}
// 父类
class AA {
}
//子类
class BB extends AA {
}
3.3.5、java 的动态绑定机制
package DynamicBinding;
public class DynamicBinding {
public static void main(String[] args) {
// a 的编译类型 A, 运行类型 B
A a = new B(); // 向上转型
System.out.println(a.sum()); // 40
System.out.println(a.sum1()); // 30
}
}
class A { // 父类
public int i = 10;
//动态绑定机制:
public int sum() { // 父类 sum()
return getI() + 10;
}
public int sum1() { // 父类 sum1()
return i + 10;
}
public int getI() { // 父类 getI
return i;
}
}
class B extends A { // 子类
public int i = 20;
public int sum() {
return i + 20;
}
public int getI() { // 子类 getI()
return i;
}
public int sum1() {
return i + 10;
}
}
3.3.6、多态的应用
3.3.6.1、多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
需求
现有一个继承结构如下:要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象say 方法
应用实例升级:如何调用子类特有的方法,Teacher 有一个 teach , Student 有一个 study方法
Person.java
package polyarr_;
public class Person { // 父类
private String name;
private int 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 Person(String name, int age) {
this.name = name;
this.age = age;
}
public String say() { // 返回名字和年龄
return name + "\t" + age;
}
}
Student.java
package polyarr_;
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() + " score=" + score;
}
// 特有的方法
public void study() {
System.out.println("学生 " + getName() + " 正在学 java...");
}
}
Teacher.java
package polyarr_;
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() + " salary=" + salary;
}
public void teach() {
System.out.println("老师 " + getName() + " 正在讲 java 课程...");
}
}
PloyArray.java
package polyarr_;
public class PloyArray {
public static void main(String[] args) {
// 要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中, 并调用每个对象 say 方法
Person[] persons = new Person[5];
persons[0] = new Person("jack", 20);
persons[1] = new Student("tom", 18, 95);
persons[2] = new Student("lily", 21, 78);
persons[3] = new Teacher("smith", 50, 9500.2);
persons[4] = new Teacher("king", 37, 10000.6);
// 循环遍历多态数组, 调用 say
for (int i = 0; i < persons.length; i++) {
// person[i] 编译类型是 Person, 运行类型是是根据实际情况有 JVM 来判断
System.out.println(persons[i].say()); // 动态绑定机制
// 输出各自的方法, 使用 类型判断 + 向下转型
if (persons[i] instanceof Student) {
Student student = (Student) persons[i];
student.study();
} else if (persons[i] instanceof Teacher) {
Teacher teacher = (Teacher) persons[i];
teacher.teach();
} else if (persons[i] instanceof Person) {
System.out.println("你的类型有误, 请自己检查...");
} else {
System.out.println("你的类型有误, 请自己检查...");
}
}
}
}
3.3.6.2、多态参数
方法定义的形参类型为父类类型, 实参类型允许为子类类型
Employee.java
package polyparameter_;
public class Employee {
private String name;
private double salary;
public String getAnnual() {
return name + "的工资是" + salary * 12;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
}
Worker.java
package polyparameter_;
public class Worker extends Employee {
public Worker(String name, double salary) {
super(name, salary);
}
@Override
public String getAnnual() {
return super.getAnnual();
}
public void work() {
System.out.println("员工" + super.getName() + "正在工作...");
}
}
Manager.java
package polyparameter_;
public class Manager extends Employee {
private double bonus;
public Manager(String name, double salary, double bonus) {
super(name, salary);
this.bonus = bonus;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
@Override
public String getAnnual() {
return super.getAnnual() + ", 奖金是" + bonus + ", 合计是" + (super.getSalary() * 12 + bonus);
}
public void manage() {
System.out.println("管理" + super.getName() + "正在管理...");
}
}
TestEmployee.java
package polyparameter_;
public class TestEmployee {
public static void main(String[] args) {
Worker worker = new Worker("张三", 3000);
Manager manager = new Manager("李四", 28000, 1500);
TestEmployee testEmployee = new TestEmployee();
testEmployee.showEmpAnnual(worker);
testEmployee.showEmpAnnual(manager);
}
public void showEmpAnnual(Employee e) {
System.out.println(e.getAnnual());
// 区分普通员工和经理
if (e instanceof Manager) {
Manager manager = (Manager) e;
manager.manage();
System.out.println(manager.getBonus());
} else if (e instanceof Worker) {
Worker worker = (Worker) e;
worker.work();
} else {
System.out.println("未知错误...");
}
}
}