一、IDE(集成开发环境)
1.1 IDEA 简介
- IDEA 全称 IntelliJ IDEA
- 在业界被公认为最好的Java开发工具
- IDEA 是 JetBrains 公司的产品,总部位于捷克的首都布拉格
- 除了支持Java开发,还支持HTML、CSS、PHP、MySQL、Python等
下载地址
1.2 Eclipse 简介
- Eclipse 是一个开放源代码的、基于Java的可拓展开发平台
- 最初是由IBM公司耗资3000万美金开发的下一代IDE开发环境
- 2001年11月贡献给开源社区
- Eclipse是目前最优秀的Java开发IDE之一
1.3 IDEA 基本介绍和使用
使用 IEDA 创建Java项目(Project),看看 IDEA 是如何使用的,IDEA 是以项目的概念,来管理我们的Java源码的。
使用入门
- 安装IDEA
- 创建一个项目
- 选择SDK
- 键入项目名
- 选择项目存放位置
- 源码存放在 src 目录下
- 新建 Java Class
在IDEA中,当我们 Run 一个文件时,会先编译成 .class -> 再运行
class文件会根据我们写的类生成,写了多少个类就会对应生成多少个文件
文件保存在项目文件夹 -> out -> production 同 Java Class 名文件夹内IDEA相关设置
设置字体
菜单栏 File -> Settings -> Editor -> Font- 更换外观主题
菜单栏 File -> Settings -> Appearance & Behavior -> Appearance - 字体加粗
菜单栏 File -> Settings -> Editor -> Color Scheme -> General -> Text -> Default text -> Bold - 字符编码设置
菜单栏 File -> Settings -> Editor -> File Encodings - 配置 auto import
菜单栏 File -> Settings -> Editor -> General -> Auto Import - 常用快捷键
删除当前行 Ctrl + Y
复制当前行 Ctrl + D
补全代码 alt + /
添加和取消注释 ctrl + /
导入该行需要的类 先配置 auto import,然后使用 alt + enter 即可
快速格式化代码 ctrl + alt + L
快速运行程序 ctrl + shift + F10
生成构造器方法等 alt + insert
查看一个类的层级关系 ctrl + H
将光标放在一个方法上,输入 ctrl + B,可以选择定位到哪个类的方法
自动的分配变量名,通过 在后面 .var
…… - 自定义快捷键/热键
菜单栏 File -> Settings -> Keymap - 模板/自定义模板
菜单栏 File -> Settings -> Editor -> Live Templates
可以查看有哪些模板快捷键/可以自己增加模板
模板可以高效的完成开发,提高编码速度
二、包
2.1 基本介绍
包的三大作用
- 区分相同名字的类
- 当类很多时,可以很好的管理
控制访问范围
包基本语法
package com.hspedu;
说明:package 关键字,表示打包
- com.hspedu
- 包名
2.2 包的本质分析(原理)
包的本质实际上就是创建不同的文件夹/目录来保存类文件
2.3 案例演示
使用打包技术实现不同包下同名Dog类
- 新建包
- 输入包名 com.xxx
- 引用包中的类 ```java package com.use;
import com.xiaoming.Dog; // 引入com.xiaoming 包的类 Dog // 同类名只能引入一个,再引入 com.xiaoqiang.Dog 会报错
public class Test { public static void main(String[] args) { Dog dog = new Dog(); // 引入后可以直接使用,不用再写包名加以区分 System.out.println(dog);
// 解读
// com.xiaoqiang.Dog 表示从com.xiaoqiang包引用的Dog类,用以区分
com.xiaoqiang.Dog dog1 = new com.xiaoqiang.Dog();
System.out.println(dog1);
}
}
<a name="udg2k"></a>
#### 2.4 包的命名
命名规则 <br />只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字
命名规范 <br />一般是小写字母 + 小圆点<br />com.公司名.项目名.业务模块名<br /> 比如:com.sina.crm.user // 用户模块<br /> com.sina.crm.order // 订单模块<br /> com.sina.crm.utils // 工具类
<a name="fJX7N"></a>
#### 2.5 常用的包
一个包下,包含很多的类,Java中常用的包有<br />java.lang.* // lang 包是基本包,默认引入,不需要再引入<br />java.util.* // util 包,系统提供的工具包,工具类,比如使用Scanner<br />java.net.* // 网络包,网络开发<br />java.awt.* // 是做Java的界面开发,GUI
<a name="x1U02"></a>
#### 2.6 引入包
基本语法 <br />**import **包;
我们引入包的主要目的是要使用该包下的类<br />比如 import java.util.Scanner; 就只是引入一个类 Scanner<br />import java.util.*; 表示将 java.util 包所有都引入<br />注意:一般需要哪个类就导入哪个类即可,不建议使用 * 的方式导入
案例演示
```java
package com.hspedu.pkg;
import java.util.Arrays;
//import java.util.Scanner; 就只是引入一个类 Scanner
//import java.util.*; 表示将 java.util 包所有都引入
public class Import01 {
public static void main(String[] args) {
// 使用系统提供 Arrays 完成 数组排序
int[] arr = {-1, 20, 2, 13, 3};
// 比如对其进行排序
// 传统方法是,自己编写排序(冒泡)
// 系统提供了相关的类,可以方便完成 Arrays
Arrays.sort(arr);
// 输出排序结果
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
}
2.7 包的注意事项和细节
- package 的作用是声明当前类所在的包,需要放在 class 的最上面,一个类中最多只有一句 package
- import 指令 位置放在 package 的下面,在类定义的前面,可以有多句且没有顺序要求
三、访问修饰符
3.1 基本介绍
Java提供四种访问修饰符号,用于控制方法和属性( 成员变量 )的访问权限 ( 范围 ):
- 公开级别:用 public 修饰,对外公开
- 受保护级别:用 protected 修饰,对子类和同一个包中的类公开
- 默认级别:没有修饰符,向同一个包的类公开
- 私有级别:用 private 修饰,只有类本身可以访问,不对外公开
4种访问修饰符的访问范围
访问级别 | 访问修饰符 | 同类 | 同包 | 子类 | 不同包 |
---|---|---|---|---|---|
公开 | public | √ | √ | √ | √ |
受保护 | protected | √ | √ | √ | × |
默认 | (none) | √ | √ | × | × |
私有 | private | √ | × | × | × |
3.2 访问修饰符使用的注意事项
- 修饰符可以用来修饰类中的属性,成员方法以及类
- 只有默认的和 public 才能修饰类! 并且遵循上述访问权限的特点
- 因为还没学习继承,因此关于在子类中的访问权限,在讲子类的时候会再讲解
- 成员方法的访问规则和属性完全一样
四、封装
4.1 基本介绍
封装(encapsulation)就是把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(方法),才能对数据进行操作。
封装的理解和好处
- 隐藏实现细节
- 可以对数据进行验证,保证安全合理
4.2 封装的实现步骤(三步)
- 将属性进行私有化 private 【不能直接修改属性】
- 提供一个公共的(public) set 方法,用于对属性判断并赋值
public void setXxx( 类型 参数名 ) { // Xxx表示某个属性
// 加入数据验证的业务逻辑
属性 = 参数名;
} - 提供一个公共的(public) get 方法,用于获取属性的值
public XX getXxx() { // 权限判断 Xxx表示某个属性
return xx;
}
4.3 案例演示
com.hspedu.encap:Encapsulation01.java
不能随便查看人的年龄、工资等隐私,并对设置的年龄进行合理的验证。年龄合理就设置,否则给默认
年龄必须在 1- 120,年龄、工资不能直接查看, name 长度要在 2 - 6 个字符之间
package com.hspedu.encap;
public class Encapsulation01 {
public static void main(String[] args) {
Person person = new Person();
person.setName("Jack");
person.setAge(30);
person.setSalary(30000);
System.out.println(person.info());
}
}
class Person {
public String name; // name 公开
private int age; // age 私有化
private double salary;
// 自己写 setXxx 和 getXxx 太慢,我们可以使用快捷键
// Alt + insert -> Getter and Setter
// 然后根据要求来完善我们的代码
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=" + salary;
}
}
说明:
如果使用了构造器,可以与 setXxx 方法结合,保证封装的正常使用
public Person(String name, int age, double salary) {
// 我们可以将set方法写在构造器中,这样仍然可以验证
setName(name);
setAge(age);
setSalary(salary);
}
⏱ 小练习
创建程序,在其中定义两个类:Account 和 AccountTest 类 体会Java的封装性
- Account 类要求具有属性:姓名(长度为2位3位或4位)、余额(必须 > 20)、密码(必须是6位)、如果不满足,则给出提示信息,并给默认值
- 通过 setXxx 的方法给 Account 的属性赋值
- 在 AccountTest 中测试
com.hspedu.encap -> Account.java
package com.hspedu.encap;
public class Account {
private String name;
private double balance;
private String password;
// 提供两个构造器
public Account() {
}
public Account(String name, double balance, String password) {
this.setName(name);
this.setBalance(balance);
this.setPassword(password);
}
public String getName() {
return name;
}
public void setName(String name) {
if (name.length() >= 2 && name.length() <=4) {
this.name = name;
} else {
System.out.println("姓名(长度为2位3位或4位),默认 佚名");
this.name = "佚名";
}
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
if (balance > 20) {
this.balance = balance;
} else {
System.out.println("余额(必须 > 20),默认 -1");
this.balance = -1;
}
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
if (password.length() == 6) {
this.password = password;
} else {
System.out.println("密码(必须是6位),默认000000");
this.password = "000000";
}
}
public void showAccount() {
// 可以增加权限校验
System.out.println("name=" + name + "\tbalance=" + balance + "\tpassword="
+ password);
}
}
com.hspedu.encap -> AccountTest.java
package com.hspedu.encap;
public class AccoutTest {
public static void main(String[] args) {
// 创建Account
Account account = new Account();
account.setName("JackSmith");
account.setBalance(30);
account.setPassword("123456789");
account.showAccount();
Account account1 = new Account("Tom", 10, "qweasd");
account1.showAccount();
}
}
五、继承
5.1 基本介绍
继承可以解决代码复用,让我们的编程思维更加靠近人类思维。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。
基本语法
class 子类 extends 父类 {
}
带来的便利
- 代码的复用性提高了
- 代码的扩展性和维护性提高了
5.2 继承示意图
- 子类会自动拥有父类定义的方法和属性
- 父类又叫超类、基类
- 子类又叫派生类
5.3 案例演示
父类:com.hspedu.extend_ : Students.java
package com.hspedu.extend_;
// 父类,是Pupil 和 Graduate类的父类
public class Students {
// 共有属性
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);
}
}
子类:com.hspedu.extend_ : Pupil.java
package com.hspedu.extend_;
// Students 的子类 Pupil
public class Pupil extends Students{
//特有的方法
public void testing() {
System.out.println("小学生" + name + "正在考数学.....");
}
}
子类:com.hspedu.extend_ : Graduate.java
package com.hspedu.extend_;
// Students 的子类 Graduate
public class Graduate extends Students{
public void testing() {
System.out.println("大学生" + name + "正在考高数...");
}
}
调用演示:com.hspedu.extend_ : Extends01.java
package com.hspedu.extend_;
public class Extends01 {
public static void main(String[] args) {
Pupil pupil = new Pupil();
pupil.name = "银角大王";
pupil.age = 10;
pupil.testing();
pupil.setScore(60);
pupil.showInfo();
System.out.println("============");
Graduate graduate = new Graduate();
graduate.name = "金角大王";
graduate.age = 23;
graduate.testing();
graduate.setScore(100);
graduate.showInfo();
}
}
5.4 继承的使用细节
- 子类继承了所有的属性和方法,非私有的属性和方法可以在子类中直接访问,但是私有属性和方法不能在子类中直接访问,要通过公共(public)的方法去访问。
- 子类必须调用父类的构造器,完成父类的初始化。(系统会默认使用 super(); 完成对父类无参构造器的引用)
当创建子类时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的每个构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。
// 父类
public class Sub {
int age;
String name;
public Sub(int age, String name) {
System.out.println("父类的有参构造器");
}
}
// 子类
public class Sup extends Sub {
public Sup() {
super(18,"Tom");
System.out.println("子类的构造器");
}
}
如果希望指定去调用父类的某个构造器,则显示的调用一下。
- super 在使用时,需要放在构造器的第一行。( super() 只能在构造器中使用 )
- super() 和 this() 都只能放在第一行,因此这两个方法不能共存在一个构造器。
- Java 所有类都是 Object 类的子类,Object 类是所有类的基类。
- 父类构造器的调用不限于直接父类!将一直向上追溯到 Object 级( 顶级父类 )。
- 子类最多只能继承一个父类(指直接继承),即Java中是单继承机制。
思考:如何让 A类继承 B类和 C类 [ 让 B类做中转 A -> B -> C ] - 不能滥用继承,子类和父类之间必须满足 is - a 的逻辑关系
Person is a Music ?
Person Music
Music extedns Person // 不合理
Animal
Cat extends Animal // 合理
5.5 继承的本质分析
package com.hspedu.extend_;
// 讲解继承的本质
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son(); //内存中的变化
}
}
class Granpa { //父类
String name = "大头爷爷";
String hobby = "旅游";
}
class Father extends Granpa { //直接父类
String name = "小头爸爸";
int age = 39;
}
class Son extends Father { //子类
String name = "大头儿子";
}
⏱ 小练习
案例1
class A {
A() { System.out.println(“a”); }
A(String name) { System.out.println(“a name”); }
}
class B extends A {
B() { this(“abc”); System.out.println(“b”); }
B(String name) { System.out.println(“b name”); }
}
main 中:B b = new B(); 问:会输出什么?案例2,问输出什么?
public class ExtendsExercise02 {
public static void main(String[] args) {
C c = new C();
}
}
class A {
public A() {
System.out.println(“我是A类”);
}
}
class B extends A {
public B() {
System.out.println(“我是B的无参构造”);
}
public B(String name) {
System.out.println(name + “我是B类的有参构造”);
}
}
class C extends B {
public C() {
this(“hello”);
System.out.println(“我是C类的无参构造”);
}
public C(String name) {
super(“hahah”);
System.out.println(“我是C类的有参构造”);
}
}编写 Computer 类,包含 CPU、内存、硬盘等属性,getDetails 方法用于返回 Computer 的详细信息
编写 PC 子类,继承 Computer 类,添加特有属性【品牌 brand】
编写 NotePad 子类,继承 Computer 类,添加特有属性【颜色 color】
编写 Test 类,在 main 方法中创建 PC 和 NotePad 对象,分别给对象中特有的属性赋值,以及从 Computer 类继承的属性赋值,并使用方法并打印输出信息
六、super 关键字
6.1 基本介绍
super 代表父类的引用,用于访问父类的属性、方法、构造器
基本语法
- 访问父类的属性,但不能访问父类的 private 属性 【 super.属性名 】
- 访问父类的方法,不能访问父类的 private 方法 【 super.方法名(参数列表) 】
- 访问父类的构造器【 super(参数列表) 】 ( 只能放在构造器的第一句,只能出现一句!)
6.2 super 的使用细节
- 调用父类构造器的好处(分工明确,父类属性由父类初始化,子类属性由子类初始化)
- 当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过 super 。如果没有重名,使用 super、this、直接访问,效果是一样的
- super 的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;如果多个基类中都有同名的成员,使用 super 访问遵循就近原则和访问权限规则。A -> B -> C
6.3 super 和 this 的比较
No. | 区别点 | this | super |
---|---|---|---|
1 | 访问属性 | 访问本类中的属性,如果本类中没有此属性则从父类中继续查找 | 跳过本类访问父类中的属性,如果父类中没有则再往父类追溯 |
2 | 调用方法 | 访问本类中的方法,如果本类没有此方法则从父类继续查找 | 跳过本类直接访问父类中的方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
4 | 特殊 | 表示当前对象 | 子类中访问父类 对象 |
七、方法重写/覆盖(override)
7.1 基本介绍
方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法。
7.2 方法重写的注意事项和使用细节
- 子类的方法的形参列表,方法名称,要和父类方法的形参列表,方法名称完全一样。
- 子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类
比如:父类 返回类型是 Object,子类方法返回类型是 String,也构成方法重写
如果不一样且不是子类,就会报错 - 子类方法不能缩小父类方法的访问权限
7.3 方法重载 与 方法重写 的比较
名称 | 发生范围 | 方法名 | 参数列表 | 返回类型 | 修饰符 |
---|---|---|---|---|---|
重载(overload) | 本类 | 相同 | 类型,个数,顺序至少有一个不同 | 无要求 | 无要求 |
重写(override) | 子类和父类之间 | 相同 | 相同 | 与父类相同或是父类返回类型的子类 | 与父类相同或比父类修饰范围大 |
⏱ 小练习
- 编写一个 Person 类,包括属性/private ( name 、age ),构造器,方法 say( 返回自我介绍name,age的字符串 )。
- 编写一个 Student 类,继承 Person 类,增加 id、score 属性/private,以及构造器,定义 say 方法( 返回自我介绍name,age,id,score的信息 )
- 在 main 中,分别创建 Person 和 Student 对象,调用 say 方法输出自我介绍
com.hspedu.override_ : Person.java
package com.hspedu.override_;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
setName(name);
setAge(age);
}
public String say() {
return "姓名:" + getName() + "\t年龄" + getAge();
}
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;
}
}
com.hspedu.override_ : Student.java
package com.hspedu.override_;
public class Student extends Person {
private int id;
private double score;
public Student(String name, int age, int id, double score) {
super(name, age);
setId(id);
setScore(score);
}
public String say() {
return super.say() + "\tID:" + getId() + "\t成绩:" + getScore();
}
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;
}
}
com.hspedu.override_ : OverrideExercise.java
package com.hspedu.override_;
public class OverrideExercise {
public static void main(String[] args) {
Person person = new Person("Tom",18);
System.out.println(person.say());
Student student = new Student("Jack",20,369856,95.5);
System.out.println(student.say());
}
}
八、多态
8.1 基本介绍
传统解决方案:
com.hspedu.poly_ :
Poly01.java
Master.java
Animal.java
Food.java
Dog.java
Cat.java
Bone.java
Fish.java
Pig.java
Rice.java
在 Master.java 中:
package com.hspedu.poly_;
public class Master {
private String name;
public Master(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(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());
}
//如果动物很多,食物也很多
//===> feed 方法就要写很多,不利于管理和维护
}
传统解决方案的问题:
代码复用性不高,而且不利于代码维护。
多态基本介绍
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
8.2 多态的具体体现
- 方法的多态:重写和重载就体现了多态 ```java package com.hspedu.poly_;
public class PolyMethod { public static void main(String[] args) { B b = new B(); //这里我们传入不同的参数,就会调用不同的sum方法,就体现了多态 System.out.println(b.sum(10, 20)); System.out.println(b.sum(10, 20, 30)); //方法重写体现多态 A a = new A(); b.say(); a.say(); } }
class A { public void say() { System.out.println(“A 的 say()方法被调用…”); } }
class B extends A { public int sum(int a, int b) { return a + b; }
public int sum(int a, int b, int c) {
return a + b + c;
}
public void say() {
System.out.println("B 的 say()方法被调用...");
}
}
2. **对象的多态**(核心,重点)
1. 一个对象的编译类型和运行类型可以不一致
1. 编译类型在定义对象时,就确定了,不能改变
1. 运行类型是可以变化的
1. 编译类型看定义时 = 号 的左边,运行类型看 = 号 的右边
案例:<br /> Animal animal = new Dog(); 【 animal 编译类型是 Animal,运行类型是 Dog 】<br />animal = new Cat(); 【 animal 的运行类型变成了 Cat,编译类型仍然是 Animal 】
<a name="fIcsv"></a>
#### 8.3 案例演示
改进后的 Master.java 中:
```java
package com.hspedu.poly_;
public class Master {
private String name;
public Master(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//使用多态机制,可以统一的管理主人喂食的问题
//animal 的编译类型是Animal,可以指向(接收)Animal 的子类对象
//food 的编译类型是Food,可以指向(接收)Food 的子类对象
public void feed(Animal animal,Food food) {
System.out.println("主人" + name + " 给" + animal.getName() + " 喂" + food.getName());
}
}
8.4 多态的注意事项和细节讨论
- 多态的前提是:两个对象(类)存在继承关系
- 多态的向上转型
- 本质:父类的引用指向了子类的对象
- 语法:父类类型 引用名 = new 子类类型();
- 特点:编译类型看左边,运行类型看右边。
可以调用父类中的所有成员(需遵守访问权限),
不能调用子类中的特有成员,
最终运行效果看子类(运行类型)的具体实现!即调用方法时,按照从子类(运行类型)开始查找方法
- 多态的向下转型
- 语法:子类类型 引用名 = ( 子类类型 ) 父类引用;
- 只能强转父类的引用,不能强转父类的对象
- 要求父类的引用必须指向的是当前目标类型的对象
- 当向下转型后,就可以调用子类类型中的所有成员
- 属性没有重写之说!属性的值看编译类型
- instanceOf 比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的子类型
⏱ 小练习
- 请说出下面的每条语言,哪些是正确的,哪些是错误的,为什么?
public class PolyExercise01{
public static void main(String[] args) {
double d = 13.4;
long I = (long)d;
System.out.println();
int in = 5;
boolean b = (boolean)in; // 错误
Object obj = “Hello”; // 可以,向上转型
String objStr = (String)obj; // 可以,向下转型
System.out.printIn(objStr);
Object objPri = new Integer(5); // 可以,向上转型
String str = (String)objPri; // 错误,指向Integer的父类引用,转成String
Integer str1 = (Integer)objPri; //可以
}
}
- 请分析下面代码的运行结果:
class Base{
int count = 10;
public void display() {
System.out.println(this.count);
}
}
class Sub extends Base {
int count = 20;
public void display() {
System.out.println(this.count);
}
}
public class PolyExercise02 {
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.count);
s.display();
Base b = s;
System.out.println(b.count);
b.display();
}
}
8.5 Java的动态绑定机制(非常非常重要)
- 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定。
- 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。
案例演示
package com.hspedu.poly_.dynamic_;
public class DynamicBinding {
public static void main(String[] args) {
//a 编译类型 A, 运行类型 B
A a = new B();
System.out.println(a.sum()); //输出30
System.out.println(a.sum1()); //输出20
}
}
class A { //父类
public int i = 10;
public int sum() { //子类没有,到父类中找
return getI() + 10; //调用 getI() 会根据运行类型寻找调用,因此调用子类中的
}
public int sum1() { //子类没有,到父类中找
return i + 10; //属性没有动态绑定机制,哪里声明,哪里使用
}
public int getI() {
return i;
}
}
class B extends A {
public int i = 20;
public int getI() {
return i;
}
}
8.6 多态的应用
- 多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
应用实例:现有一个继承结构如下:要求创建1个Person、2个Student对象和2个Teacher对象,统一放在数组中,并调用每个对象say方法。
应用实例升级:如何调用子类特有的方法,比如Teacher有一个tech,Student有一个study,怎么调用?
package com.hspedu.poly_.polyarray_;
public class PolyArray {
public static void main(String[] args) {
Person[] person = new Person[5];
person[0] = new Person("Tom",15);
person[1] = new Student("小明",19,88.6); //向上转型
person[2] = new Student("小强",20,85.5); //向上转型
person[3] = new Teacher("张飞",38,20000); //向上转型
person[4] = new Teacher("关羽",47,25000); //向上转型
for (int i = 0; i < person.length; i++) {
//person[i] 编译类型是Person,运行类型根据实际情况由JVM来判断
System.out.println(person[i].say()); //动态绑定机制
if (person[i] instanceof Student) { //类型判断
System.out.println(((Student) person[i]).study()); //向下转型
} else if (person[i] instanceof Teacher) {
System.out.println(((Teacher) person[i]).teach()); //向下转型
}
}
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
setName(name);
setAge(age);
}
public String say() {
return "name:" + getName() + "\tage:" + getAge();
}
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;
}
}
class Student extends Person {
private double score;
public Student(String name, int age, double score) {
super(name, age);
setScore(score);
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public String say() { //重写父类 say() 方法
return "学生 | " + super.say() + "\tscore:" + getScore();
}
//特有方法
public String study() {
return getName() + "在学习...";
}
}
class Teacher extends Person {
private double salary;
public Teacher(String name, int age, double salary) {
super(name, age);
setSalary(salary);
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String say() { //重写父类 say() 方法
return "老师 | " + super.say() + "\tsalary:" + getSalary();
}
//特有方法
public String teach() {
return getName() + "在教书...";
}
}
- 多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型。
应用实例1:前面的主人喂动物
应用实例2:
定义员工类 Employee,包含姓名和月工资[private],以及计算年工资getAnnual 的方法。普通员工和经理继承了员工,经理类多了奖金 bonus 属性和管理 manage 方法,普通员工多了work 方法,普通员工和经理类要求分别重写getAnnual 方法
测试类中添加一个方法 showEmpAnnal(Employee e),实现获取任何员工对象的年工资,并在 main 方法中调用该方法【e.getAnnual()】
测试类添加一个方法,testWork,如果是普通员工,则调用work 方法,如果是经理,则调用 manage 方法
com.hspedu.poly.emply
Test.java
NormalEmployee.java
Manager.java
Employee.java
九、Object 类详解
9.1 equals 方法
== 与 equals 的对比
==:是一个 比较运算符,既可以判断基本类型,又可以判断引用类型;如果判断基本类型,判断的是值是否相等;如果判断引用类型,判断的是地址是否相等,即判断是不是同一个对象。
equals:是 Object 类中的方法,只能判断引用类型,默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如Integer,String 【查看 String 和 Integer 的 equals 源代码】
Tips:如何查看 Jdk 源代码?
IDEA 中,默认会配置好源代码关联,只需在用 equals方法时,在equals 上按 Ctrl + B 即可跳转到关于 equals 的源代码的位置。
如果没有默认关联,可以通过以下方式建立关联:
- 菜单栏 File -> Project Structure… -> SDKs -> Sourcepath
- 点击右侧加号“+”,找到本地jdk安装位置,将 javafx-src.zip 和 src.zip 两个文件添加进来
- 点击 Apply 以应用关联设置,点击 OK 保存。
9.2 重写 equals 方法
应用实例:判断两个Person对象的内容是否相等,如果两个 Person 对象的各个属性值都一样,则返回 true,反之 false。
package com.hspedu.poly_.object_;
public class EqualsExercise {
public static void main(String[] args) {
Person person = new Person("jack", 10, '男');
Person person1 = new Person("jack", 10, '男');
Person person2 = new Person("Alice", 8, '女');
System.out.println(person.equals(person1));
System.out.println(person.equals(person2));
}
}
class Person {
private String name;
private int age;
private char gender;
public Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public boolean equals(Object obj) {
//如果比较的两个对象是同一个对象,直接返回 true
if (this == obj) {
return true;
}
//类型判断
if (obj instanceof Person) { //是 Person 才比较
//进行向下转型,因为要得到 obj 的各个属性
Person p = (Person) obj;
return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
}
return false;
}
}
这里可能会绕晕,要搞清楚 equals 运行本质:
- AAA.equals(BBB) :AAA 调用了 equals 对象,传入的参数(实参)是 BBB
- 到了 public boolean equals(Object obj) 方法中:this 指代的是 AAA,obj 指代的是 BBB
⏱ 小练习
问下面代码输出结果是什么?
public class EqualsExercise02 {
public static void main(String[] args) {
Persons p1 = new Persons();
p1.name = "hspedu";
Persons p2 = new Persons();
p2.name = "hspedu";
System.out.println(p1 == p2);
System.out.println(p1.name.equals(p2.name));
System.out.println(p1.equals(p2));
String s1 = new String("asdf");
String s2 = new String("asdf");
System.out.println(s1.equals(s2));
System.out.println(s1 == s2);
}
}
class Persons {
public String name;
}
问下面代码输出什么? ```java package com.hspedu.poly.object;
public class EqualsExercise03 {
public static void main(String[] args) {
int it = 65;
float fl = 65.0f;
System.out.println(“65和65.0f是否相等?” + (it == fl));
char ch1 = ‘A’;
char ch2 = 12;
System.out.println(“65和‘A’是否相等?” + (it == ch1));
System.out.println(“12和ch2是否相等?” + (12 == ch2));
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1和str2是否相等?" + (str1 == str2));
System.out.println("str1和str2是否equals?" + (str1.equals(str2)));
System.out.println("hello" == new java.sql.Date());
}
}
<a name="Vozcm"></a>
#### 9.3 hashCode 方法
hashCode 用于返回该对象哈希码值。
使用细节:(重要)
1. 提高具有哈希结构的容器效率!
1. 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
1. 两个引用,如果指向的是不同对象,则哈希值是不一样的
1. 哈希值主要根据地址号生成!不能完全将哈希值等价于地址
1. 案例演示:HashCode_.java:obj.hashCode()
1. 后面在集合中 hashCode 如果需要的话,也会重写
<a name="fyWk3"></a>
#### 9.4 toString 方法
1. 基本介绍<br />默认返回:全类名+@+哈希值的十六进制,【查看 Object 的 toString 方法】<br />子类往往重写 toString 方法,用于返回对象的属性信息
1. 重写 toString 方法,打印对象或拼接对象时,都会自动调用该对戏对象的 toString 形式
1. 当直接输出一个对象时,toString 方法会被默认的调用<br />比如:System._out_.println(monster);
<a name="FgGrk"></a>
#### 9.5 finalize 方法
1. 当对象被回收时,系统自动调用该对象的 finalize方法。子类可以重写该方法,做一些释放资源的操作
1. 什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁对象前,会先调用 finalize 方法
1. 垃圾回收机制的调用,是由系统来决定(GC算法),也可以通过 System.gc() 主动触发垃圾回收机制
<a name="w8Jzv"></a>
### 十、断点调试(debug)
<a name="LzdDo"></a>
#### 10.1 基本介绍
一个实际需求
1. 在开发中,新手程序员在查找错误时,这时老程序员就会温馨提示,可以用断点调试,一步一步的看源码执行的过程,从而发现错误所在。
1. 重要提示:在断点调试过程中,是运行状态,是以对象的**运行类型**来执行的。
断点调试介绍
1. 断点调试是指在程序的某一行设置一个断点,调试时,程序运行到这一行就会停住,然后你可以一步一步往下调试,调试过程中可以看到各个变量当前的值,出错的话,调试到出错的代码行即显示错误,停下。进行分析从而找到这个Bug
1. 断点调试是程序员必须掌握的技能
1. 断点调试也能帮助我们查看Java底层源代码的执行过程,提高程序员的Java水平
<a name="mM1RI"></a>
#### 10.2 断点调试的快捷键
1. F7:跳入方法内
1. F8:跳过,逐行执行代码时使用
1. Shift + F8:跳出方法
1. F9:resume,执行到下一个断点
![image.png](https://cdn.nlark.com/yuque/0/2021/png/12677686/1630986326279-f13913f8-83db-4096-abba-ac110095c9ae.png#clientId=uad773c06-d2ca-4&from=paste&height=477&id=u0a3c1cb7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=477&originWidth=1001&originalType=binary&ratio=1&size=244880&status=done&style=none&taskId=u9e3654cc-ba14-4041-b8a8-c5bd870f290&width=1001)
<a name="lvfNm"></a>
#### 10.3 debug 应用
1. 逐行解析运行过程中变量的变化情况<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/12677686/1630996057915-7bc55d7c-8c2f-420f-9b38-1f1dcba2b209.png#clientId=u29cf051a-288b-4&from=paste&height=1117&id=uf34b9e51&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1117&originWidth=1364&originalType=binary&ratio=1&size=155908&status=done&style=shadow&taskId=u5a4c736f-baba-4f65-b726-28dda9abc2d&width=1364)
1. 查看数组越界异常<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/12677686/1630996457161-ff47393b-f957-42b9-b417-ab15addfc88b.png#clientId=u29cf051a-288b-4&from=paste&height=1074&id=u36df74c3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1074&originWidth=1479&originalType=binary&ratio=1&size=184709&status=done&style=shadow&taskId=u0538ea38-ad10-45fe-bdf0-204cdb5a739&width=1479)
1. 追源代码,看看Java的底层实现<br />小技巧:将光标放在某个变量上,可以看到最新的数据。
![](https://cdn.nlark.com/yuque/0/2021/jpeg/12677686/1630998074164-bd0eb0ac-055b-4bbb-b04d-0fd7a77e82b3.jpeg)<br />[IDEA Debug 如何进入 JDK源码](https://www.yuque.com/harborgao/java/hv1cya?view=doc_embed)
4. 执行到下一个断点 F9 resume<br />提示:可以在 Debug 过程中,动态的下断点
5. 使用断点调试的方法,追踪下一个对象创建的过程。
5. 使用断点调试,查看动态绑定机制如何工作
<a name="v0ogN"></a>
### 十一、本章作业
1. 定义一个Person类{name, age, job},初始化Person对象数组,有3个person对象,并按照age从大到小进行排序,提示,使用冒泡排序 Homework01.java
1. 写出四种访问修饰符和各自的访问权限 Homework02.java
3. 编写老师类 Homework03.java
1. 要求有属性”姓名name","年龄age" ,“职称post” ,“基本工资salary”
1. 编写业务方法,introduce (),实现输出一个教师的信息。
1. 编写教师类的三个子类:教授类、副教授类、讲师类。工资级别分别为:教授为1.3、副教授为1.2、讲师类1.1。在三个子类里面都重写父类的introduce ()方法。
1. 定义并初始化一个老师对象,调用业务方法,实现对象基本信息的后台打印。
4. 通过继承实现员工工资核算打印功能
父类:员工类<br />子类:部门经理类、普通员工类<br />(1) 部门经理工资 = 1000+单日工资*天数*等级(1.2)<br />(2) 普通员工工资 = 单日工资*天数*等级(1.0) ;<br />(3) 员工属性:姓名,单日工资,工作天数<br />(4) 员工方法(打印工资)<br />(5) 普遍员工及部门经理都是员工子类,需要重写打印工资方法。<br />(6) 定义并初始化普通员工对象,调用打印工资方法输入工资,定义并初始化部门经理对象,调用打印工资方法输入工资
5. 设计父类一员工类。 子类:工人类,农民类,教师类,科学家类,服务生类。
(1) 其中工人,农民,服务生只有基本工资<br />(2) 教师除基本工资外,还有课酬(元/天)<br />(3) 科学家除基本工资外,还有年终奖<br />(4) 编写一个测试类,将各种类型的员工的全年工资打印出来
6. 在父类和子类中通过this和super都可以调用哪些属性和方法,假定 Grand、Father 和 Son 在同一个包
```java
class Grand {
String name = "AA";
private int age = 100;
public void g1(){}
}
class Father extends Grand {
String id="001";
private double score;
public void f1(){}
//super可以访问哪些成员(属性和方法) ?
//this可以访问哪些成员?
}
class Son extends Father {
String name="BB";
public void g1(){}
private void show() {}
//super可以访问哪些成员(属性和方法)?
//this可以访问哪些成员?
}
写出程序结果
class Test{
String name = "Rose";
Test(){
System.out.println("Test");
}
Test(String name){
this.name = name;
}
}
class Demo extends Test {
String name= "Jack";
Demo() {
super();
System.out.println("Demo");
}
Demo(String s){
super(s);
}
public void test(){
System.out.println(super.name);
System.out.println(this.name);
}
public static void main(String[] args) {
new Demo().test();
new Demo("john").test();
}
}
扩展如下的BankAccount类
class BankAccount {
private double balance ;
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
public void deposit(double amount) {
balance += amount;
}
public withdraw(double amount) {
balance -= amount;
}
}
要求:
(1) 在上面类的基础上扩展新类CheckingAccount 对每次存款和取款都收取1美元的手续费
(2) 扩展前一个练习的BankAccount类,新类SavingsAccount每个月都有利息产生(earnMonthlyInterest方法被调用),并且有每月三次免手续费的存款或取款。在earnMonthlyInterest方法中重置交易计数设计一个Point类,其x和y坐标可以通过构造器提供。提供一个子类 LabeledPoint,其构造器接受一个标签值和x,y坐标,比如:new LabeledPoint( “BlackThursday” ,1929,230.07),写出对应的构造器即可
编写Doctor类{name, age, job, gender, sal}相应的getter(和setter()方法,5个参数的构造器,重写父类的equals()方法: public boolean equals(Object obj),并判断测试类中创建的两个对象是否相等。相等就是判断属性是否相同
现有Person类,里面有方法run、eat, Student类继承了Person类, 并重写了run方法,自定义了study方法,试写出对象向上转型和向下转型的代码,并写出各自都可以调用哪些方法,并写出方法输出什么?
class Person {
public void run() {System.out.println("person run"); }
public void eat() {System.out.println(" person eat" ); }
}
class Student extends Person {
public void run() {System.out.println("student run"); }
public void study) {System.out.println(' 'student study.' ); }
}
说出==和equals的区别
打印效果如下
老师的信息:
姓名:王飞
年龄: 30
性别:男
工龄: 5
我承诺,我会认真教课。
王飞爱玩象棋
———————————————-
学生的信息:
姓名:小明
年龄: 15
性别:男
学号: 00023102
我承诺,我会好好学习。
小明爱玩足球。
案例题目描述:
(1) 做一个Student类,Student类有名称 (namne) ,性别(sex),年龄(age),学号(stu id),做合理封装,通过构造器在创建对象时将4个属性赋值。
(2) 写一个Teacher类,Teacher类有名称(name) ,性别(sex) ,年龄(age) ,工龄(work age),做合理封装,通过构造器在创建对象时将4个属性赋值。
(3) 抽取一个父类Person类, 将共同属性和方法放到Person类
(4) 学生需要有学习的方法(study) ,在方法里写生“我承诺,我会好好学习。
(5) 教师需要有教学的方法(teach) ,在方法里写.上“我承诺,我会认真教学。
(6) 学生和教师都有玩的方法(play) ,学会玩的是足球,老师玩的是象棋,此方法是返回字符串的,分别返回”xx爱玩足球”和”xx爱玩象棋”(其中xx分别代表学生和老师的姓名) 。因为玩的方法名称都一样,所以要求此方法定义在父类中,子类实现重写。
(7) 定义多态数组,里面保存2个学生和2个教师,要求按年龄从高到低排序,
(8) 定义方法,形参为Person类型,功能:调用学生的study或教师的teach方法
程序阅读题在mian方法中执行: C c =new C();输出什么内容?
class A{
public A(){
System.out.println("我是A类");
}
}
class B extends A{
public B(){
System.out.println("我是B类的无参构造");
}
public B(String name){
System.out.println(name+ "我是B类的有参构造");
}
}
class C extends B{
public C(){
this( "hello");
System.out.println("我是c类的无参构造");
}
public C(String name){
super("hahah");
System.out.println("我是c类的有参参构造");
}
}
什么是多态,多态具体体现有哪些? (可举例说明)
java的动态绑定机制是什么?
学习参考(致谢):
- B站 @程序员鱼皮 Java学习一条龙
- B站 @韩顺平 零基础30天学会Java