java零基础Ⅰ-- 面向对象编程(中级部分)
Intellij IDEA
IDE (集成开发环境)- IDEA
IDEA介绍
- IDEA 全称 Intellij IDEA
- 在业界被公认最好的Java开发工具
- IDEA是JetBrains公司的产品,总部位于捷克的首都布拉格
- 除了支持Java开发,好支持HTML、CSS、PHP、MySQL、Python等
IDE (集成开发环境)- Eclipse
Eclipse 介绍
- Eclipse 是一个开放源代码的,基于Java的可扩展开发平台
- 最初是由IBM 公司耗资3000万美金开发的下一代IDE开发环境
- 2001年11月贡献给开源社区
- Eclipse是目前最优秀的Java开发IDE之一
IDE (集成开发环境)- IDEA的使用
IDEA的安装
- IDEA 下载后,就可以开始安装
IDEA的基本介绍和使用
使用IDEA创建Java项目(project),看看IDEA是如何使用的,IDEA 是以项目的概念,来管理我们的java源码的
创建一个java项目 - hello
创建Hello类,点击src目录右键,
设置字体大小:
File -> Settings -> Editor -> Font
public class Hello {
public static void main(String[] args) {
System.out.printf("hello,idea");
}
}
点击运行:
IDEA使用技巧和经验
设置字体
菜单 File -> sttings
外观字体大小设置
内容字体大小设置
字体粗体设置
设置颜色主题
字符编码设置
练习
使用IDEA 开发一个java项目 testpro01,创建一个类MyTools,编译一个方法,可以完成对int数组冒泡排序的功能
public class ArrayTest {
public static void main(String[] args) {
MyTools myTools = new MyTools();
int[] arr = {2,12,-10,22};
myTools.bubble(arr);
//输出排序后的arr,引用传递
System.out.printf("====排序后的arr====\n");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
//在idea 当我们 run一个文件时,会先编译成 .class -> 在运行
}
}
//创建一个类MyTools,编译一个方法,可以完成对int数组冒泡排序的功能
class MyTools {
public void bubble(int arr[]){
int tmp = 0;
//冒泡排序 从小到大
for (int i = 0; i < arr.length -1; i++) {//外层循环次数 arr.length-1
for (int j = 0; j < arr.length - 1 - i; j++) {//
if(arr[j] > arr[j + 1]){// 交换
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
}
====排序后的arr====
-10 2 12 22
IDEA常用的快捷键
1、删除当前行,默认是 ctrl + Y 自己配置 ctrl + D 【搜索 Delete Line】
2、复制当前行,自己配置 ctrl + alt + 向下光标 【搜索 Duplicate Line or Selection】
3、补全代码 alt + /
4、添加注释和取消注释 ctrl + / 【第一次是添加注释,第二次是取消注释】
演示
已经存在了,但是可以直接使用ok
5、导入该行需要的类 先配置 auto import,然后使用 alt + enter
6、快速格式化代码 ctrl + alt + L 【搜索 Reformat Code】
7、快速运行程序 自定义 alt + R【搜索 Run】
8、生成构造器等 alt + insert 【提供开发效率】
9、查看一个类的层级关系 ctrl + H
生成继承关系图
10、将光标放在一个方法上,输入 ctrl + B,可以选择定位到哪个类的方法
11、自动的分配变量名,通过 在后面加 .var
模板/自定义模板
file -> settings -> editor -> Live templates ->
查看有哪些模板快捷键/可以自己增加模板
自己添加一个模板
包
看一个应用场景
现在有两个程序员共同开发一个java项目,程序员xiaoming希望定义一个类取名 Dog,程序员xiaoqiang也想定义一个类也叫 Dog。两个程序员为此还吵了起来,怎么办? ==》 包
包的三大作用
1、区分相同名字的类
2、当类很多时,可以很好的管理类[看Java Api 文档]
3、控制访问范围
包基本语法
package com.zzpedu;
说明:
1、package 关键字,表示打包
2、com.zzpedu; 表示包名
包的本质分析(原理)
包的本质就是 实际上就是创建不同文件夹/目录来保存类文件
示意图:
快速入门
使用打包技术来解决上面的问题,不同包下Dog类
package com.use;
import com.xiaoqiang.Dog;
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog);//com.xiaoqiang.Dog@1b6d3586
com.xiaoming.Dog dog1 = new com.xiaoming.Dog();
System.out.println(dog1);//com.xiaoming.Dog@4554617c
}
}
包的命名
命名规则:
只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字
demo.class.exec1 //错误 class 是关键字
demo.12a //错误 12a 不能用数字开头
demo.abc12.oa //正确
命令规范:
一般是小写字母 + 小圆点
一般是:com.公司名.项目名.业务模块名
比如:com.zzpedu.oa.model; com.zzpedu.oa.controller;
举例:
com.sina.crm.use //用户模块
com.sina.crm.order //订单模块
com.sina.crm.utils //工具类
常用包
一个包下,包含很多的类,java中常用的包有
java.lang.* //lang包是基本包,默认引入,不需要再引入
java.util.* //util 包,系统提供的工具包,工具类,使用 Scanner
java.net.* //网络包,网络开发
java.awt.* //是做java的界面开发,GUI
如何引入包
com.zzpedu.pkg : //创建包
语法:
import 包;
我们引入一个包的主要目的是要使用该包下的类
比如:import java.util.Scanner; 就只是引入一个类Scanner
import java.util.*; //表示将 java.util 包所有都引入
案例:使用系统提供 Arrays 完成 数组排序
package com.zzpedu.pkg;
//注意:
//我们需要哪个类,就导入哪个类即可,不建议使用 *导入
//import java.util.Scanner;//表示只会引入java.util 包下的 Scanner
//import java.util.*; //表示将 java.util 包下所有类都引入(导入)
import java.util.Arrays;
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] + " ");//-1 2 3 13 20
}
}
}
注意事项和使用细节
1、package 的作用是声明当前类所在的包,需要放在类(或者文件)的最上面,一个类中只有一句package
2、import 指令 位置放在package的下面,在类定义前面,可以有多句且没有顺序要求。
//package 的作用是声明当前类所在的包,需要放在类(或者文件)的最上面,
// 一个类中只有一句package
package com.zzpedu.pkg;
//import 指令 位置放在package的下面,在类定义前面,可以有多句且没有顺序要求。
import java.util.Arrays;
import java.util.Scanner;
//..
//类定义
public class PakDetail {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int[] arr = {-1,0};
Arrays.sort(arr);
}
}
访问修饰符
基本介绍
java提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
1、公开级别:用 public 修饰,对外公开
2、受保护级别:用 protected 修饰,对子类和同一个包中的类公开
3、默认级别:没有修饰符号,向同一个包的类公开
4、私有级别:用 private 修饰,只有类本身可以访问,不对外公开。
4种访问修饰符的访问范围
访问级别 | 访问控制修饰符 | 同类/本类 | 同包 | 子类 | 不同包 |
---|---|---|---|---|---|
公开 | public | √ | √ | √ | √ |
受保护 | protected | √ | √ | √ | × |
默认 | 没有修饰符 | √ | √ | × | × |
私有 | private | √ | × | × | × |
使用的注意事项
1)修饰符可以用来修饰符类中的属性,成员方法以及类
2)只有默认的和public 才能修饰类!,并且遵循上述访问权限的特点
3)因为还没有学习继承,因此关于子类中的访问权限,以后再讲
4)成员方法的访问规则和属性完全一样
//com.zzpedu.modifier: 需要很多文件来说明(A类,B类,Test类)
package com.zzpedu.modifier;
public class A {
//四个属性
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public void m1(){
//在同一个类中,可以访问public,protected,默认,private 修饰的属性或方法
System.out.println("n1=" + n1 +" n2=" + n2 + " n3=" + n3 + " n4=" + n4);
}
protected void m2(){ }
void m3(){ }
private void m4(){ }
public void hi(){
//在同一个类中,可以访问public,protected,默认,private 修饰的属性或方法
m1();
m2();
m3();
m4();
}
}
package com.zzpedu.modifier;
public class B {
public void say(){
A a = new A();
//在同一个包 可以访问 public ,protected 和 默认修饰属性或方法, 不能访问 private 属性或方法
System.out.println("n1=" + a.n1 +" n2=" + a.n2 + " n3=" + a.n3);
a.m1();
a.m2();
a.m3();
//a.m4(); //错误的 private 私有的
}
}
package com.zzpedu.modifier;
public class Test {
public static void main(String[] args) {
A a = new A();
a.m1();//n1=100 n2=200 n3=300 n4=400
B b = new B();
b.say();//n1=100 n2=200 n3=300
a.m2();
a.m3();
//a.m4(); //错误的 private 私有的
}
}
//只有 默认和public 可以修饰类
class Tiger { }
package com.zzpedu.pkg;
import com.zzpedu.modifier.A;
public class Test {
public static void main(String[] args) {
A a = new A();
//在不同包下, 可以访问public 修改的属性或方法
// 但是不能访问 protected,默认,private修饰的属性或方法
System.out.println(a.n1);//n1=100
a.m1();
//不能访问 a.m2() a.m3() a.m4()
}
}
面向对象编程三大特征
基本介绍:
面向对象编程有三大特征:封装、继承、多态
1. 封装
封装介绍
封装(encapsulation) 就是把抽象的数据 [属性] 和对数据的操作 [方法] 封装在一起,数控被保护在内部,程序的其它部分只有通过被授权的操作 [方法],才能对数据进行操作。
封装的理解和好处
1)隐藏实现细节: 方法(连接数据库) <-- 调用(传入参数…)
2)可以对数据进行验证,保证安全合理
Person{name, age}
Person p = new Person();
p.name = "jack";
p.age = 1220;
封装的实现步骤(三步)
1)将属性进行私有化 【private 修饰】 【不能直接修改属性】
2)通过一个公共的(public)set方法,用于对属性判断并赋值
public void setXxx(类型 参数名){//Xxx 表示某个属性
//加入数据验证的业务逻辑
属性 = 参数名;
}
3)提供一个公共的(public)get方法,用于获取属性的值
public 数据类型 getXxx(){//权限判断 Xxx 表示某个属性
return xx;
}
快速入门案例
看一个案例
那么在java中如何实现这种类似的控制呢?
请大家看一个小程序,不能随便查人的年龄,工资等隐私,并对设置的年龄进行合理的验证。年龄合理就设置,否则给默认年龄,必须在1-120,年龄,工资不能直接查看, name的长度在 2 - 6 字符 之间
public class Person {
public String name;
private int age;
private double salary;
private String job;
}
package com.zzpedu.encap;
public class Encapsulation01 {
public static void main(String[] args) {
//如果要使用快捷键 ctrl + r,需要先配置主类
//第一次,我们使用鼠标点击形式运行程序,后面就可以使用
Person person = new Person();
person.setName("jack");
person.setAge(30);
person.setSalary(30000);
System.out.println(person.info());
System.out.println(person.getSalary());
}
}
/*
那么在java中如何实现这种类似的控制呢?
请大家看一个小程序,不能随便查人的年龄,工资等隐私,
并对设置的年龄进行合理的验证。年龄合理就设置,否则给默认年龄,
必须在1-120,年龄,工资不能直接查看, name的长度在 2-6 字符 之间
*/
class Person {
public String name; //名字公开
private int age; //age 私有化
private double salary; //salary 私有化
//自己写setXxx 和 getXxx 太慢,我们使用快捷键 alt + insert
//然后根据要求来完善我们的代码
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;
}
}
========控制台输出=========
信息为 name=jack age=30 薪水=30000.0
30000.0
将构造器和setXxx结合
看一个案例
class Person {
public String name; //名字公开
private int age; //age 私有化
private double salary; //salary 私有化
//构造器 快捷键 alt + insert
public Person() {
}
//还有一种开发方式,就是将构造方法和serXxx结合
//有三个属性的构造器
public Person(String name, int age, double salary) {
// this.name = name;
// this.age = age;
// this.salary = salary;
//我们可以将set方法写在构造器中,这样仍然可以验证
setName(name);//等价于 this.setName(name)
setAge(age);
setSalary(salary);
}
....
}
练习
创建程序,在其中定义两个类:Account和AccountTest类体会Java的封装性。
1、Account类要求具有属性:姓名(长度为2位3位或者4位)、余额(必须>20)、密码(必须是六位),如果不满足,则给出提示信息,并给默认值(自定义)
2、通过setXxx的方法给Account 的属性赋值
3、在AccountTest中测试
提示知识点:
String name = “”;
int len = name.length(); //字符串的长度
package com.zzpedu.encap;
/**
* 创建程序,在其中定义两个类:Account和AccountTest类体会Java的封装性。
* 1、Account类要求具有属性:姓名(长度为2位3位或者4位)、余额(必须>20)、密码(必须是六位),
* 如果不满足,则给出提示信息,并给默认值(自定义)
* 2、通过setXxx的方法给Account 的属性赋值
* 3、在AccountTest中测试
*/
public class Account {
//为了封装,将3个属性设置为private
private String name;
private double balance;//余额
private String pwd;
//通过两个构造器
public Account() {
}
public Account(String name, double balance, String pwd) {
this.setBalance(balance);
this.setPwd(pwd);
this.setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
//姓名(长度为2位3位或者4位)
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) {
//余额(必须>20)
if (balance > 20) {
this.balance = balance;
} else {
System.out.println("余额(必须>20),默认为0");
}
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
//密码(必须是六位)
if (pwd.length() == 6) {
this.pwd = pwd;
} else {
System.out.println("密码(必须是六位),默认密码为 000000");
this.pwd = "000000";
}
}
//显示账号信息
public String showInfo() {
//可以增加一个权限的效验 对密码
return "账号信息 name=" + name + " 余额=" + balance + " 密码=" + pwd;
// if(){
// return "账号信息 name=" + name + " 余额=" + balance + " 密码=";
// }else {
// return "你无权限查看....";
// }
}
}
package com.zzpedu.encap;
public class AccountTest {
public static void main(String[] args) {
//创建Account
Account account = new Account();
account.setName("jack111");
account.setBalance(10);
account.setPwd("12346");
System.out.println(account.showInfo());
}
}
==========控制台输出===========
姓名要求(长度为2位3位或者4位),默认值 无名
余额(必须>20),默认为0
密码(必须是六位),默认密码为 000000
账号信息 name=无名 余额=0.0 密码=000000
2. 继承
为什么需要继承
一个小问题,还是看个程序,提出代码复用
我们编写两个类,一个是Pupil类(小学生),一个是Graduate(大学毕业生)。
问题:两个类的属性和方法很多是相同的,怎么把?
package com.zzpedu.extend_;
//小学生 -> 模拟小学生考试的情况
public class Pupil {
public String name;
public int age;
private double score;//成绩
public void setScore(double score) {
this.score = score;
}
public void testing(){
System.out.println("小学生 " + name + " 正在考小学数学...");
}
public void show(){
System.out.println("学生名=" + name + " 年龄=" + age + " 成绩=" + score);
}
}
package com.zzpedu.extend_;
//大学生类 -> 模拟大学生考试的简单情况
public class Graduate {
public String name;
public int age;
private double score;//成绩
public void setScore(double score) {
this.score = score;
}
public void testing(){//和Pupil不一样
System.out.println("大学生 " + name + " 正在考大学数学...");
}
public void show(){
System.out.println("学生名=" + name + " 年龄=" + age + " 成绩=" + score);
}
}
package com.zzpedu.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.show();
System.out.println("================");
Graduate graduate = new Graduate();
graduate.name = "金角大王";
graduate.age = 22;
graduate.testing();
graduate.setScore(100);
graduate.show();
}
}
========控制台输出==============
小学生 银角大王 正在考小学数学...
学生名=银角大王 年龄=10 成绩=60.0
================
大学生 金角大王 正在考大学数学...
学生名=金角大王 年龄=22 成绩=100.0
==> 继承(代码复用性)
继承基本介绍和示意图
继承可以解决代码复用,让我们的编程更加靠近人类的思想,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽取出父类,在父类中定义这些相同的属性和方法,所有子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。
示意图
继承的基本语法
class 子类 extends 父类{
}
1)子类就会自动拥有父类的属性和方法
2)父类又叫 超类,基类
3)子类有叫派生类
快速入门案例
我们对Extends01.java 改进,使用继承的方法,请大家注意体会使用继承的好处
package com.zzpedu.extend_.improve_;
//父类 是Pupil 和 Graduate的父类
public class Student {
//共用数学
public String name;
public int age;
private double score;//成绩
//共用方法
public void setScore(double score) {
this.score = score;
}
public void show(){
System.out.println("学生名=" + name + " 年龄=" + age + " 成绩=" + score);
}
}
package com.zzpedu.extend_.improve_;
//小学生 -> 模拟小学生考试的情况
//让 Pupil 继承 Student类
public class Pupil extends Student{
public void testing(){
System.out.println("小学生 " + name + " 正在考小学数学...");
}
}
package com.zzpedu.extend_.improve_;
//大学生类 -> 模拟大学生考试的简单情况
//让 Graduate 继承 Student类
public class Graduate extends Student{
public void testing(){//和Pupil不一样
System.out.println("大学生 " + name + " 正在考大学数学...");
}
}
package com.zzpedu.extend_.improve_;
public class Extends01 {
public static void main(String[] args) {
Pupil pupil = new Pupil();
pupil.name = "银角大王~";
pupil.age = 11;
pupil.testing();
pupil.setScore(50);
pupil.show();
System.out.println("================");
Graduate graduate = new Graduate();
graduate.name = "金角大王~";
graduate.age = 23;
graduate.testing();
graduate.setScore(80);
graduate.show();
}
}
============控制台输出=========
小学生 银角大王~ 正在考小学数学...
学生名=银角大王~ 年龄=11 成绩=50.0
================
大学生 金角大王~ 正在考大学数学...
学生名=金角大王~ 年龄=23 成绩=80.0
继承给编程带来的便利
1)代码的复用性提高了
2)代码的扩展性和维护性提高了
继承的深入讨论/细节问题
1、子类继承了所有的属性和方法,非私有属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
package com.zzpedu.extend_;
public class Base {//父类
//4个属性
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public Base(){//无参构造器
System.out.println("父类Base()构造器被调用...");
}
//父类提供一个public的访问private属性,返回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");
}
//提供public方法调用test400
public void callTest400(){
test400();
}
}
package com.zzpedu.extend_;
public class Sub extends Base{//子类
public Sub(){
super();//默认调用父类的无参构造器 可写可不写 默认的
System.out.println("子类Sub()构造器被调用...");
}
public void sayOk(){//子类方法
//非私有属性和方法可以在子类直接访问
//但是私有属性和方法不能在子类直接访问
System.out.println(n1 + " " + n2 + " " + n3 );
test100();
test200();
test300();
//test400();//不能直接访问private属性和方法
//要通过父类提供公共的方法去访问
System.out.println("n4=" + getN4());
callTest400();
}
}
package com.zzpedu.extend_;
public class ExtendsDetail {
public static void main(String[] args) {
Sub sub = new Sub();
sub.sayOk();
}
}
=======控制台输出======
父类Base()构造器被调用...
子类Sub()构造器被调用...
100 200 300
test100
test200
test300
n4=400
test400
子类继承了所有的属性和方法 debug
2、子类必须调用父类的构造器,完成父类的初始化
package com.zzpedu.extend_;
public class ExtendsDetail {
public static void main(String[] args) {
Sub sub = new Sub();//创建了子类对象 sub
}
}
=======控制台输出======
父类Base()构造器被调用...
子类Sub()构造器被调用...
3、当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不通过
public class Base {//父类
public Base(){//无参构造器
System.out.println("父类Base()构造器被调用...");
}
}
public class Sub extends Base{//子类
public Sub(){
//默认调用父类的无参构造器 可写可不写 默认的
super();
System.out.println("子类Sub()构造器被调用...");
}
//当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器
public Sub(String name){
//do nothing...
System.out.println("子类Sub(String name)构造器被调用...");
}
}
package com.zzpedu.extend_;
public class ExtendsDetail {
public static void main(String[] args) {
System.out.println("====第一个对象==");
Sub sub = new Sub();//创建了子类对象 sub
System.out.println("====第二个对象==");
Sub sub2 = new Sub("jack");
}
}
=======控制台输出======
====第一个对象==
父类Base()构造器被调用...
子类Sub()构造器被调用...
====第二个对象==
父类Base()构造器被调用...
子类Sub(String name)构造器被调用...
public class Base {//父类
// public Base(){//无参构造器
// System.out.println("父类Base()构造器被调用...");
// }
public Base(String name,int age){//有参构造器
System.out.println("父类Base(String name,int age)构造器被调用...");
}
}
public class Sub extends Base{//子类
public Sub(){
//默认调用父类的无参构造器 可写可不写 默认的
//super();
//如果父类没有提供无参构造器,则必须在子类的构造器中用 super
// 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不通过
super("smith",20);
System.out.println("子类Sub()构造器被调用...");
}
//当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器
public Sub(String name){
//如果父类没有提供无参构造器,则必须在子类的构造器中用 super
// 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不通过
super("tom",30);
//do nothing...
System.out.println("子类Sub(String name)构造器被调用...");
}
}
package com.zzpedu.extend_;
public class ExtendsDetail {
public static void main(String[] args) {
System.out.println("====第一个对象==");
Sub sub = new Sub();//创建了子类对象 sub
System.out.println("====第二个对象==");
Sub sub2 = new Sub("jack");
}
}
=======控制台输出======
====第一个对象==
父类Base(String name,int age)构造器被调用...
子类Sub()构造器被调用...
====第二个对象==
父类Base(String name,int age)构造器被调用...
子类Sub(String name)构造器被调用...
4、如果希望指定去调用父类的某个构造器,则显示的调用一下:super(参数列表)
public class Base {//父类
public Base(){//无参构造器
System.out.println("父类Base()构造器被调用...");
}
public Base(String name,int age){//有参构造器
System.out.println("父类Base(String name,int age)构造器被调用...");
}
public Base(String name){//有参构造器
System.out.println("父类Base(String name)构造器被调用...");
}
}
public class Sub extends Base{//子类
public Sub(String name,int age){
//1. 调用父类的无参构造器,如下 或者 什么都不写 默认调用父类的无参构造器
//super();//父类的无参构造器,显示调用
//2. 调用父类的 Base(String name) 构造器
//super("zzp");
//3. 调用父类的 Base(String name,int age) 构造器
super("king",20);
System.out.println("子类Sub(String name,int age)构造器被调用...");
}
}
public class ExtendsDetail {
public static void main(String[] args) {
Sub sub = new Sub("king",50);
}
}
=======控制台输出======
父类Base(String name,int age)构造器被调用...
子类Sub(String name,int age)构造器被调用...
5、super 在使用时,必须在构造器第一行(super只能在构造器中使用)
public class Sub extends Base{//子类
public Sub(String name,int age){
//super 在使用时,必须在构造器第一行(super只能在构造器中使用)
super("king",20);
System.out.println("子类Sub(String name,int age)构造器被调用...");
}
}
6、super() 和 this() 都只能放在构造器第一个行,因此这两个方法不能共存在一个构造器
public class Sub extends Base{//子类
public Sub(String name,int age){
//super() 和 this() 都只能放在构造器第一个行,因此这两个方法不能共存在一个构造器
super("king",20);
//this("zzp");//不允许 super() 和 this() 只能存在一个
System.out.println("子类Sub(String name,int age)构造器被调用...");
}
}
7、java所有类都是Object类的子类,Object 是所有类的基类(超类)
8、父类构造器的调用不限于直接父类!将一直往上追溯直到Object类(顶级父类)
public class ToBase {//父类是Object
public ToBase() {
//super(); Object的无参构造器
System.out.println("构造器ToBase() 被调用...");
}
}
public class Base extends ToBase{//父类}..
public class Sub extends Base{//子类}...
public class ExtendsDetail {
public static void main(String[] args) {
Sub sub = new Sub("king",50);
}
}
=======控制台输出======
构造器ToBase() 被调用...
父类Base(String name,int age)构造器被调用...
子类Sub(String name,int age)构造器被调用...
9、子类最大继承一个父类(指直接继承类),即java中是单继承机制
思路:如何让A类继承B类和C类
A类继承B类 --> B类继承C类
10、不能滥用继承,子类和父类直接必须满足 is-a 的逻辑关系
Person is a Music?
Person Music
Music extends Person//不合理
Animal
Cat extends Animal//合理
// An highlighted block
var foo = 'bar';
继承的本质分析(重要)
案例
我们看一个案例来分析继承父类,创建子类对象时,内存中到底发送了什么?【当子类对象创建好后,建立查找关系】
class GrandPa{
String name = "大头爷爷";
String hobby = "旅游";
}
class Father extends GrandPa{
String name = "大头爸爸";
int age= 39;
}
class Son extends Father {
String name = "大头儿子";
}
Son son = new Son();
son.name = ?;
son.age = ?;
son.hobby = ?;
package com.zzpedu.extend_;
/**
* 继承的本质
*/
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();//内存布局
//要按照查找关系来返回信息
//(1) 首先看子类是否有该属性
//(2) 如果子类有这个属性,并且可以访问,则返回信息
//(3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就放回信息..),
//(4) 如果父类没有就按照(3)的规则,继续找上级父类,直到Object类
System.out.println(son.name);//大头儿子
// private修饰的不能直接访问 报错 注: GrandPa.age不会访问 程序在Father.age 跳出
//System.out.println(son.age);//39
System.out.println(son.getAge());//39
System.out.println(son.hobby);//旅游
}
}
class GrandPa{//爷爷类
String name = "大头爷爷";
String hobby = "旅游";
public int age = 100;
}
class Father extends GrandPa{//父类
String name = "大头爸爸";
private int age= 39;
public int getAge() {
return age;
}
}
class Son extends Father {//子类
String name = "大头儿子";
}
==========控制台输出==========
大头儿子
39
旅游
子类创建的内存布局
练习
1、案例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");}//分析有默认的super()
}
main函数中:B b = new B(); 会输出什么?
package com.zzpedu.extend_.exercise;
public class ExtendsExercise01 {
public static void main(String[] args) {
B b = new B();// a, b name, b
}
}
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){
//默认有 super();
System.out.println("b name");
}
}
==========控制台输出==========
a
b name
b
2、案例2
class A {
public A(){
System.out.println("我是A类");
}
}
class B extends A {//B类 继承A类
public B(){
System.out.println("我是B类的无参构造器");
}
public B(String name){
System.out.println(name + "我是B类的有参构造器");
}
}
class C extends B {//C类 继承B类
public C(){
System.out.println("我是C类的无参构造器");
}
public C(String name){
super("haha");
System.out.println(name + "我是C类的有参构造器");
}
}
//main方法中: C c = new C();输出什么内容
package com.zzpedu.extend_.exercise;
public class ExtendsExercise02 {
public static void main(String[] args) {
C c = new C();// 我是A类 haha我是B类的有参构造器 hello我是C类的有参构造器 我是C类的无参构造器
System.out.println("-------------");
C c2 = new C("cc");// 我是A类 haha我是B类的有参构造器 cc我是C类的有参构造器
}
}
class A {//A类
public A(){
System.out.println("我是A类");
}
}
class B extends A {//B类 继承A类
public B(){
System.out.println("我是B类的无参构造器");
}
public B(String name){
//有默认的 super();
System.out.println(name + "我是B类的有参构造器");
}
}
class C extends B {//C类 继承B类
public C(){
this("hello");
System.out.println("我是C类的无参构造器");
}
public C(String name){
super("haha");
System.out.println(name + "我是C类的有参构造器");
}
}
==========控制台输出==========
我是A类
haha我是B类的有参构造器
hello我是C类的有参构造器
我是C类的无参构造器
-------------
我是A类
haha我是B类的有参构造器
cc我是C类的有参构造器
Process finished with exit code 0
3、案例3
编写Computer类,包含CPU、内存、硬盘等属性,getDetails方法用于返回Computer的详细信息
编写PC子类,继承Computer类,添加特有属性【品牌brand】
编写NotePad子类,继承Computer类,添加特有属性【演示color】
编写Test类,在main方法创建PC和NotePad对象,分别给对象中特有的属性赋值,以及从Computer类继承的属性赋值,并使用方法打印输出信息。
package com.zzpedu.extend_.exercise;
//编写Computer类,包含CPU、内存、硬盘等属性,getDetails方法用于返回Computer的详细信息
public class Computer {
private String cpu;
private int memory;
private int disk;
public Computer(String cpu, int memory, int disk) {
this.cpu = cpu;
this.memory = memory;
this.disk = disk;
}
//返回Computer信息
public String getDetails(){
return "cpu=" + cpu + " memory=" + memory + " disk=" +disk;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public int getMemory() {
return memory;
}
public void setMemory(int memory) {
this.memory = memory;
}
public int getDisk() {
return disk;
}
public void setDisk(int disk) {
this.disk = disk;
}
}
package com.zzpedu.extend_.exercise;
//编写PC子类,继承Computer类,添加特有属性【品牌brand】
public class PC extends Computer{
private String brand;
//这里IDEA 根据继承规则,自动把构造器的调用写好
//这里也体现:继承设计基本思想,父类的构造器完成父类属性初始化
// 子类的构造器完成子类属性初始化
public PC(String cpu, int memory, int disk, String brand) {
super(cpu, memory, disk);
this.brand = brand;
}
public void printInfo(){
System.out.println("PC信息如下");
//System.out.println(getCpu() + getMemory() + getDisk());
//调用父类的getDetails()方法,得到相关属性信息
System.out.println(getDetails() + " brand=" + brand);
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
}
package com.zzpedu.extend_.exercise;
//编写NotePad子类,继承Computer类,添加特有属性【演示color】
public class NotePad extends Computer{
private String color;
public NotePad(String cpu, int memory, int disk, String color) {
super(cpu, memory, disk);
this.color = color;
}
public void printInfo(){
System.out.println("NotePad信息如下");
//System.out.println(getCpu() + getMemory() + getDisk());
//调用父类的getDetails()方法,得到相关属性信息
System.out.println(getDetails() + " color=" + color);
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
package com.zzpedu.extend_.exercise;
/*
编写Computer类,包含CPU、内存、硬盘等属性,getDetails方法用于返回Computer的详细信息
编写PC子类,继承Computer类,添加特有属性【品牌brand】
编写NotePad子类,继承Computer类,添加特有属性【演示color】
编写Test类,在main方法创建PC和NotePad对象,分别给对象中特有的属性赋值,
以及从Computer类继承的属性赋值,并使用方法打印输出信息
*/
public class ExtendsExercise03 {
public static void main(String[] args) {
PC pc = new PC("intel",16,500,"IBM");
pc.printInfo();
NotePad notePad = new NotePad("pad",8,128,"白色");
notePad.printInfo();
}
}
=======控制台输出============
PC信息如下
cpu=intel memory=16 disk=500 brand=IBM
NotePad信息如下
cpu=pad memory=8 disk=128 color=白色
super关键字
基本介绍
super 代表父类的引用,用于访问父类的属性、方法、构造器
基本语法
1、访问父类的属性,但是不能访问父类的private属性
案例
super.属性名
2、访问父类的方法,不能访问父类的private方法
案例
super.方法名(参数列表);
3、方法父类的构造器;
super(参数列表); 只能放在构造器的第一句,只能出现一句!
package com.zzpedu.super_;
public class A {
//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 test100(){
}
protected void test200(){
}
void test300(){
}
private void test400(){
}
}
package com.zzpedu.super_;
public class B extends A{
//访问父类的属性,但是不能访问父类的private属性 案例 super.属性名
public void hi(){
System.out.println(super.n1 + " " + super.n2 + " " + super.n3);
}
//访问父类的方法,不能访问父类的private方法 案例 super.方法名(参数列表);
public void ok(){
super.test100();
super.test200();
super.test300();
//super.test400();//不能访问父类private方法
}
//方法父类的构造器; super(参数列表); 只能放在构造器的第一句,只能出现一句!
public B(){
//super();
//super("jack");
super("jack",10);
}
}
super给编程带来的便利/细节
1、调用父类的构造器的好处(分工明确,父类属性由父类初始化,子类的属性由子类初始化)
2、方子类中有父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this、直接访问是一样的效果!
package com.zzpedu.super_;
public class A {
public int n1 = 100;
public void cal(){
System.out.println("A类的cal()方法...");
}
}
package com.zzpedu.super_;
public class B extends A{
public int n1 = 888;
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类
// 提示:如果查找方法过程中,找到了,但不能访问(private), 则报错 cannot access
// 如果查找方法过程中,没有找到,则提示方法不存在
//cal();//第一种
//this.cal();//第二种 ==> 等价于 cal();//第一种
//找cal方法(super.cal()) 的顺序是 直接查找父类,其他的规则一样
super.cal();//第三种
//演示访问属性的规则
//n1 和 this.n1 查找的规则是
//(1)先找本类,如果有,则调用,
//(2)如果没有,则找父类(如果有,并可以调用,则调用)
//(3)如果父类没有,则继续找父类的父类,整个规则,就是一样的,直到 Object类
// 提示:如果查找属性过程中,找到了,但不能访问(private), 则报错 cannot access
// 如果查找属性过程中,没有找到,则提示属性不存在
System.out.println(n1);//888
System.out.println(this.n1);//888
//找n1(super.n1) 的顺序是直接查找父类的n1,其他的规则一样
System.out.println(super.n1);//100
}
}
package com.zzpedu.super_;
public class SuperDetail {
public static void main(String[] args) {
B b = new B();
b.sum();
}
}
=======控制台输出=====
B类的sum()方法...
A类的cal()方法...
888
888
100
3、super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷的成员;如果多个基类(上级类)中都有同名的成员,使用super访问遵循就近原则。 A -> B -> C,当然也要遵守访问权限的相关规则
public class Base {//父类是Object
public int n1 = 999;
public int age = 111;
public void cal(){
System.out.println("Base类的cal()方法...");
}
}
public class A extends Base{
// public int n1 = 100;
// private void cal(){
// System.out.println("A类的cal()方法...");
// }
}
public class B extends A{
public int n1 = 888;
//编写测试方法
public void test(){
//super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷的成员;
// 如果多个基类(上级类)中都有同名的成员,使用super访问遵循就近原则。 A -> B -> C
System.out.println("super.n1=" + super.n1);
super.cal();
}
}
package com.zzpedu.super_;
public class SuperDetail {
public static void main(String[] args) {
B b = new B();//子类对象
b.test();
}
}
=======控制台输出=====
super.n1=999
Base类的cal()方法...
super和this的比较
No. | 区别点 | this | super |
---|---|---|---|
1 | 访问属性 | 访问本类中属性,如果本类 没有此属性则从父类中继承 | 从父类开始查找属性 |
2 | 调用方法 | 访问本类的方法,如果本类 没有此方法则从父类继承查找 | 直接从父类开始查找方法 |
3 | 调用构造器 | 调用本类构造器,必须放在 构造器的首行 | 调用父类的构造器,必须放在 子类构造器的首行 |
4 | 特殊 | 表示当前对象 | 子类中访问父类对象 |
方法重写/覆盖(override)
基本介绍
简单来说:方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的那个方法
快速入门
class Animal{
public void cry(){
System.out.println("动物叫唤。");
}
}
class Dog extends Animal{
public void cry(){
System.out.println("小狗汪汪叫...");
}
}
package com.zzpedu.override_;
public class Animal {
public void cry(){
System.out.println("动物叫唤...");
}
}
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();
}
}
=======控制台输出====
小狗汪汪叫...
注意事项和使用细节
方法重写就叫方法覆盖,需要满足下面的条件
1、子类的方法的形参列表、方法名称,要和父类方法的形参列表、方法名称完全一样。
2、子类方法的返回类型和父类方法返回类型一样,或者是父类的类型的子类,比如:返回类型是 Object,子类方法返回类型是String
public Object getInfo(){…} //父类方法
public String getInfo(){…} //子类方法
3、子类方法不能缩小父类的访问权限
public > protected > 默认 > private
void sayOk(){…} //父类方法
public void sayOk(){…} //子类方法
package com.zzpedu.override_;
public class Animal {
public Object m1(){
return null;
}
public String m2(){
return null;
}
public AAA m3(){
return null;
}
protected void eat(){
}
}
public class Dog extends Animal{
//细节:子类方法的返回类型和父类方法返回类型一样,
// 或者是父类的类型的子类,比如:返回类型是 Object,
// 子类方法返回类型是String
public String m1(){
return null;
}
//这里Object 不是 Animal.m2()方法返回值String的子类,因此编译错误
// public Object m2(){
// return null;
// }
public BBB m3(){
return null;
}
//细节:子类方法不能缩小父类的访问权限
// public --> protected --> 默认 --> private
public void eat(){
}
}
class AAA { }
class BBB extends AAA{ }
练习
1、请求对方法的重写和重载做一个比较
名称 | 范围 | 方法名 | 形参列表 | 返回类型 | 修饰符 |
---|---|---|---|---|---|
重载(overload) | 本类 | 必须一样 | 类型,个数或者顺序 至少有一个不同 | 无要求 | 无要求 |
重写(override) | 父子类 | 必须一样 | 相同 | 子类重写的方法, 返回的类型和父类返回类型一致, 或者是其子类 | 子类方法不能缩小父类方法的访问范围 |
2、
1)编写一个Person类,包括属性/private(name,age),构造器,方法say(返回自我介绍的字符串)。
2)编写一个Student类,继承Person类,增加id,score属性/private,以及构造器,定义say方法(返回自我介绍的信息)
3)在main中,分别创建Person和Student对象,调用say方法输出自我介绍
package com.zzpedu.override_;
//编写一个Person类,包括属性/private(name,age),构造器,方法say(返回自我介绍的字符串)
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String say(){
return "name=" + name + " age=" + age;
}
}
package com.zzpedu.override_;
//编写一个Student类,继承Person类,增加id,score属性/private,
// 以及构造器,定义say方法(返回自我介绍的信息)
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;
}
//say
public String say(){//这里体现super的一个好处,代码复用。
return super.say() + " id=" + id + " score=" + score;
}
}
package com.zzpedu.override_;
public class OverrideExercise {
public static void main(String[] args) {
//在main中,分别创建Person和Student对象,调用say方法输出自我介绍
Person person = new Person("jack",10);
System.out.println(person.say());
Student student = new Student("smith",20,123,100);
System.out.println(student.say());
}
}
========控制台输出======
name=jack age=10
name=smith age=20 id=123 score=100.0
3. 多态
问题描述:
请编写一个程序,Master类 中有一个 feed(食物)方法,可以完成 主人给动物喂食物的信息
使用传统的方法来解决(private属性)
package com.zzpedu.poly_;
public class Food {
private String name;
public Food(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Fish extends Food{
public Fish(String name) {
super(name);
}
}
public class Bone extends Food{
public Bone(String name) {
super(name);
}
}
package com.zzpedu.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;
}
}
public class Cat extends Animal{
public Cat(String name) {
super(name);
}
}
public class Dog extends Animal{
public Dog(String name) {
super(name);
}
}
package com.zzpedu.poly_;
public class Master {
private String 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());
}
//如果动物越多,食物很多
//===> feed 方法很多。不利于管理和维护
//Pig -> Rice
//Tiger -> meat ...
}
package com.zzpedu.poly_;
public class Poly01 {
public static void main(String[] args) {
Master tom = new Master("汤姆");
Dog dog = new Dog("大黄");
Bone bone = new Bone("大棒骨");
tom.feed(dog,bone);
Cat cat = new Cat("小花猫");
Fish fish = new Fish("黄花鱼");
tom.feed(cat,fish);
}
}
=====控制台输出=====
主人:汤姆 给 大黄 吃 大棒骨
主人:汤姆 给 小花猫 吃 黄花鱼
传统的方法带来的问题是什么?如何解决?
问题是:代码的复用性不高,而且不利于代码维护
解决方案:引出我们讲解的 多态
多[多种]态[状态]基本介绍
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
多态的具体体现
1、方法的多态
重写和重载就体现多态
package com.zzpedu.poly_;
public class PolyMethod {
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() 方法被调用...");
}
}
=====控制台输出=====
30
60
A say() 方法被调用...
B say() 方法被调用...
2、对象的多态(重点,核心)
重要的几句话:
(1)一个对象的编译类型和运行可以不一致
(2)编译类型在定义对象时,就确定了,不能改变
(3)运行类型是可以变化的
(4)编译类型看定义时 = 号 的左边,运行类型看 = 号的 右边
案例:
Animal animal = new Dog(); 【animal 编译类型Animal,运行类型是Dog】
animal = new Cat();【animal 的运行类型变成 Cat,编译类型仍然是 Animal】
package com.zzpedu.poly_.objectpoly_;
public class Animal {
public void cry(){
System.out.println("Animal cry() 动物在叫...");
}
}
public class Cat extends Animal{
public void cry() {
System.out.println("Cat cry() 小猫在叫...");
}
}
public class Dog extends Animal{
public void cry() {
System.out.println("Dog cry() 小狗在叫...");
}
}
package com.zzpedu.poly_.objectpoly_;
public class PolyObject {
public static void main(String[] args) {
//体验对象多态特点
//animal 编译类型就是 Animal,运行类型 是就 Dog
Animal animal = new Dog();
//因为运行时,就执行到该行时,animal运行类型是Dog,所以cry就是Dog的cry
animal.cry();//Dog cry() 小狗在叫...
//animal 编译类型就是 Animal,运行类型 是就 Cat
animal = new Cat();
animal.cry();//Cat cry() 小猫在叫...
}
}
=====控制台输出=====
Dog cry() 小狗在叫...
Cat cry() 小猫在叫...
多态快速入门
使用多态即机制来解决主人喂食物的问题
public class Master {
private String name;
public Master(String name) {
this.name = name;
}
//使用多态机制,可以统一的管理主人喂食物的问题
//animal 编译类型是Animal,可以指向(接收)Animal子类的对象
//food by类型是Food,可以指向(接收)Food子类的对象
public void feed(Animal animal,Food food){
System.out.println("主人:" + name + " 给 " + animal.getName() + " 吃 " + food.getName());
}
}
多态注意事项和细节讨论
多态的提前是:两个对象(类)存在继承关系
多态的向上转型
1)本质:父类的引用指向了子类的对象
2)语法:父类类型 引用 = new 子类类型();
3)特点:编译类型看左边,运行类型看右边。
可以调用父类中的所有成员(需遵守访问权限)
不能调用子类中特有成员;
最终运行效果看子类(运行类型)的具体实现!
package com.zzpedu.poly_.detail_;
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{
public void eat() {//方法重写
System.out.println("猫吃鱼");
}
public void catchMouse(){//Cat特有的方法
System.out.println("猫捉老鼠");
}
}
package com.zzpedu.poly_.detail_;
public class PolyDetail {
public static void main(String[] args) {
//向上转型:父类的引用指向了子类的对象
//语法:父类类型 引用 = new 子类类型();
Animal animal = new Cat();
Object obj = new Cat();//可以吗? 可以 Object 也是 Car的父类
//向上转型调用方法的规则如下:
//(1)可以调用父类中的所有成员(需遵守访问权限)
//(2)但是不能调用子类中特有成员;
//(#)因为在编译阶段,能调用哪些成员,是由编译类型来决定的
//animal.catchMouse();//错误
//($)最终运行效果看子类(运行类型)的具体实现!
// 即调用方法时,按照从子类(运行类型)开始查找方法,然后调用,
animal.eat();//猫吃鱼
animal.run();//跑
animal.show();//hello,你好!
animal.sleep();//睡觉
System.out.println("Ok~");
}
}
========控制台输出=========
猫吃鱼
跑
hello,你好!
睡觉
Ok~
多态的向下转型
1)语法:子类类型 引用名 = (子类类型)父类引用;
2)只能强转父类的引用,不能强转父类的对象
3)要求父类的引用必须指向的是当前目标类型的对象
4)当向下转型后,可以调用子类类型中所以成员
package com.zzpedu.poly_.detail_;
public class Dog extends Animal{//Dog是Animal的子类
}
package com.zzpedu.poly_.detail_;
public class PolyDetail {
public static void main(String[] args) {
//希望调用 Cat的catchMouse()方法
Animal animal = new Cat();
//多态的向下转型
//(1)语法:子类类型 引用名 = (子类类型)父类引用
//Cat 编译类型是Cat,运行类型是Cat
Cat cat = (Cat) animal;
cat.catchMouse();//猫捉老鼠
//(2)要求父类的引用必须指向的是当前目标类型的对象
//Dog dog = (Dog) animal;//编译不报错 运行报错 ClassCastException
}
}
========控制台输出=========
猫捉老鼠
属性没有重写之说!属性的值看编译类型
package com.zzpedu.poly_.detail_;
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;//属性
}
==========控制台输出================
10
20
instanceOf
比较操作符,用于判断对象的运行类型是否为XX类型或者XX类型的子类
package com.zzpedu.poly_.detail_;
public class PolyDetail03 {
public static void main(String[] args) {
BB bb = new BB();
//instanceOf 比较操作符,用于判断对象的运行类型是否为XX类型或者XX类型的子类
System.out.println(bb instanceof BB);//true
System.out.println(bb instanceof AA);//true
//aa 编译类型 AA,运行类型 BB
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{}//子类
==========控制台输出================
true
true
true
true
false
true
练习
1、请说出下面每条语言,哪些是正确的,哪里是错误的,为什么?
public class PolyExercise01{
public static void main(String[] args){
double d = 13.4;//ok
long l = (long)d;//ok 强转
System.out.println(l);//13
int in = 5;//ok
boolean b = (boolean)in;//错误 boolean -> int
Object obj = "Hello";//ok,向上转型
String objStr = (String)obj;//ok,向下转型
System.out.println(objStr);// Hello
Object objPri = new Integer(5);//ok,向上转型
//错误ClassCastExcetpion,指向Integer的父类引用,转成String
String str = (String)objPri;
Integer str1 = (Integer)objPri;//ok,向下转型
}
}
2、
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);//20 看编译类型Sub
s.display();//20 看运行类型 Sub
Base b = s;//向上转型
System.out.println(b == s);//true b = s
System.out.println(b.count);//10 看编译类型 Base
b.display();//20 看运行类型 Sub
}
}
java的动态绑定机制( 非常重要)
java重要特性:动态绑定机制
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 sum(){
return i + 20;
}
public int getI(){
return i;
}
public int sum1(){
return i + 10;
}
}
//main方法中
A a = new B();//向上转型
System.out.println(a.sum());//40 看运行类型 B
System.out.println(a.sum1());//30 看运行类型
java的动态绑定机制
1、当调用对象方法的时候,该方法会和对象的内存地址/运行类型绑定
2、当调用对象属性时,没有动态绑定机制,哪里声明,那里使用
package com.zzpedu.poly_.dynamc_;
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;
//动态绑定机制:
//(1)当调用对象方法的时候,该方法会和对象的内存地址/运行类型绑定
public int sum(){//父类sum()
return getI() + 10;//20 + 10 --> getI() 先找子类的getI()
}
//(2)当调用对象属性时,没有动态绑定机制,哪里声明,那里使用
public int sum1(){//父类sum1()
return i + 10;//10 + 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;
// }
}
==========控制台输出================
30
20
多态的应用
1)多态数组
数组的定义类型为父类类型,里面保存了实际元素类型为子类类型
应用实例:现有一个继承结构如下:要求创建1个Person对象、2个Student对象和2个Teacher对象,统一放在数组中,并调用每个对象的say方法
package com.zzpedu.poly_.polyarr_;
public class Person {//父类
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String say(){//返回名字和年龄
return name + "\t" + age;
}
//setXX.getXX...
}
public class Student extends Person{//子类
private double score;
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
//重写父类say方法
public String say() {
return "学生 " + super.say() + " score=" + score;
}
//setXX.getXX...
}
public class Teacher extends Person{//子类
private double salary;
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
//重写父类say方法
public String say() {
return "老师 " + super.say() + " salary=" + salary;
}
//setXX.getXX...
}
package com.zzpedu.poly_.polyarr_;
public class PolyArray {
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,100);
persons[2] = new Student("smith",19,80.8);
persons[3] = new Teacher("scott",30,20000);
persons[4] = new Teacher("king",50,50000);
//循环遍历多态数组,调用say方法
for (int i = 0; i < persons.length; i++) {
//perons[i] 编译类型是 Person ,运行类型是 根据时间情况由JVM来判断
System.out.println(persons[i].say());//动态绑定机制
}
}
}
===========控制台输出======
jack 20
学生 tom 18 score=100.0
学生 smith 19 score=80.8
老师 scott 30 salary=20000.0
老师 king 50 salary=50000.0
应用升级:如何调用子类特有的方法,比如 Teacher 有一个 teach,Student 有一个 study 怎么调用?
public class Student extends Person{//子类
//特有方法
public void study(){
System.out.println("学生 " + getName() + " 正在学习....");
}
}
public class Teacher extends Person{//子类
//特有方法
public void teach(){
System.out.println("老师 " + getName() + " 正在讲课...");
}
}
package com.zzpedu.poly_.polyarr_;
public class PolyArray {
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,100);
persons[2] = new Student("smith",19,80.8);
persons[3] = new Teacher("scott",30,20000);
persons[4] = new Teacher("king",50,50000);
//循环遍历多态数组,调用say方法
for (int i = 0; i < persons.length; i++) {
//perons[i] 编译类型是 Person ,运行类型是 根据时间情况由JVM来判断
System.out.println(persons[i].say());//动态绑定机制
//这里使用 instanceof + 向下转型
if(persons[i] instanceof Student){//判断persons[i] 的运行类型是否是 Student
Student student = (Student) persons[i];//向下转型
student.study();
//或者使用一条语句: ((Student) persons[i]).study();
}else if(persons[i] instanceof Teacher){
Teacher teacher = (Teacher) persons[i];//向下转型
teacher.teach();
}else if (persons[i] instanceof Person){
//不做处理
}else {
System.out.println("你的类型有误,请检查...");
}
}
}
}
=============控制台输出======================
jack 20
学生 tom 18 score=100.0
学生 tom 正在学习....
学生 smith 19 score=80.8
学生 smith 正在学习....
老师 scott 30 salary=20000.0
老师 scott 正在讲课...
老师 king 50 salary=50000.0
老师 king 正在讲课...
2)多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型
应用实例1:前面的主人喂动物
应用实例2:
定义员工类Employee,包含姓名和月工资[private],以及计算年工资getAnnual的方法,普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法
测试类中添加一个方法showEmpAnnual(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法[e.getAnnual()]
测试类中添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法
package com.zzpedu.poly_.polyparameter_;
public class Employee {//父类
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
//得到年工资的方法
public double getAnnual(){
return 12 * salary;
}
//setXX,getXX...
}
public class Manager extends Employee{//子类
private double bonus;
public Manager(String name, double salary, double bonus) {
super(name, salary);
this.bonus = bonus;
}
public void manage(){
System.out.println("经理 " + getName() + " is managing");
}
//重写获取年薪的方法
public double getAnnual() {
return super.getAnnual() + bonus;
}
//setXX,getXX...
}
public class Worker extends Employee{//子类
public Worker(String name, double salary) {
super(name, salary);
}
public void work(){
System.out.println("普通员工 " + getName() + " is working");
}
//重写父类的getAnnual方法
public double getAnnual() {//因为普通员工没有其他收入,则直接调用父类方法
return super.getAnnual();
}
}
public class PolyParameter {
public static void main(String[] args) {
Worker tom = new Worker("tom", 2500);
Manager milan = new Manager("milan",5000,200000);
PolyParameter polyParameter = new PolyParameter();
polyParameter.showEmpAnnual(tom);
polyParameter.showEmpAnnual(milan);
polyParameter.testWork(tom);
polyParameter.testWork(milan);
}
//showEmpAnnual(Employee e)
//实现获取任何员工对象的年工资,并在main方法中调用该方法[e.getAnnual()]
public void showEmpAnnual(Employee e){
System.out.println(e.getAnnual());//动态绑定机制
}
//添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法
public void testWork(Employee e){
if(e instanceof Worker){
((Worker) e).work();//向下转型操作
}else if(e instanceof Manager){
((Manager) e).manage();//向下转型操作
}else {
System.out.println("不做处理...");
}
}
}
========控制台输出========
30000.0
260000.0
普通员工 tom is working
经理 milan is managing
Object类详解
equals方法
== 和 equals 的对比
==
是一个比较运算符
1、==
:即可以判断基本类型,有可以判断引用类型
2、==
:如果判断基本类型,判断的是值是否相等。
实例:int i =10; double d = 10.0;
3、==
:如果判断引用类型,判断的是地址是否相等,即判断是不同一个对象
package com.zzpedu.object_;
public class Equals01 {
public static void main(String[] args) {
A a = new A();
A b = a;
A c = b;
System.out.println(a == c);//true
System.out.println(b == c);//true
B bObj = a;//向上转型
System.out.println(bObj == c);//true
int num1 = 10;
double num2 = 10.0;
System.out.println(num1 == num2);//true
}
}
class A extends B{}
class B{}
IDEA 如何查看 JDK 源码:
1、一般来说 IDEA 配置好 JDK 以后,jdk 的源码也就自动配置好了
2、如果没有的话,点击菜单 File -> Project Structure -> SDKs -> Sourcepath 然后点击右侧绿色的加号
3、在需要查看某个方法源码时,将光标放在该方法,输入 ctrl + b 即可
或者在该方法上,点击右键 -> go to -> Declaration or …
4、equals
:是Object类中的方法,只能判断引用类型,
5、默认判断是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如:Integer,String
源码:
package java.lang;
public class Object {
//即Object 的equals 方法默认就是比较对象的地址是否相等
// 也就是判断两个对象是不是同一个对象。
public boolean equals(Object obj) {
return (this == obj);
}
}
package java.lang;
public final class Integer extends Number implements Comparable<Integer> {
// 从源码可以看到 Integer 也重写了Object的equals方法,
//变成了判断两个值是否相等
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
}
package java.lang;
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
//String类的 equals方法
//把Object的equals方法重写了,变成了比较两个字符串值是否相同
public boolean equals(Object anObject) {
if (this == anObject) {//如果是同一个对象
return true;//返回true
}
if (anObject instanceof String) {//判断类型
String anotherString = (String)anObject;//向下转型
int n = value.length;
if (n == anotherString.value.length) {//如果长度相同
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {//如果一个一个的比较字符
if (v1[i] != v2[i])
return false;
i++;
}
return true;//如果两个字符串的所有字符都相等,则返回true
}
}
return false;//如果比较的不是字符串,则直接返回false
}
}
Integer n1 = new Integer(1000);
Integer n2 = new Integer(1000);
System.out.println(n1 == n2);//false
System.out.println(n1.equals(n2));//true
String str1 = new String("zzpedu");
String str2 = new String("zzp");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2));//true
如何重写equals方法
应用实例:判断两个Person类对象的内容是否相等,如果两个Person对象的各个属性值都一样,则返回true,反之false
package com.zzpedu.object_;
public class EqualsExercise01 {
public static void main(String[] args) {
Person person1 = new Person("jack",20,'男');
Person person2 = new Person("jack",20,'男');
System.out.println(person1.equals(person2));//true
}
}
//应用实例:判断两个Person类对象的内容是否相等,
// 如果两个Person对象的各个属性值都一样,则返回true,反之false
class Person {//extends Object
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;
}
//重写Object 的 equals方法
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;
}
//如果不是Person,则直接返回false
return false;
}
//setXx getXx..
}
练习
1、
class Person{
public String name;
}
Person p1 = new Person();
p1.name = "zzpedu";
Person p2 = new Person();
p2 = "zzpedu";
System.out.println(p1 == p2);//false
System.out.println(p1.name.equals(p2.name));//true
System.out.println(p1.equals(p2));//false
String s1 = new String("asdf");
String s2 = new String("asdf");
System.out.println(s1.equals(s2));//true
System.out.println(s1 == s2);//false
2、
int it = 65;
float fl = 65.0f;
System.out.println("65和65.0f是否相等?" + (it == fl));//true
char ch1 = 'A'; char ch2 = 12;
System.out.println("65和 'A' 是否相等?" + (it == ch1));//true
System.out.println("12和 ch2 是否相等?" + (12 == ch2));//true
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1 和 str2是否相等?" + (str1 == str2));//false
System.out.println("str1 是否equals str2?" + (str1.equals(str2)));//true
System.out.println("hello" == new java.sql.Date());//编译报错
hashCode方法
1)提高具有哈希结构的容器的效率!
2)两个引用,如果指向的是同一个对象,则哈希值肯定一样的!
3)两个引用,如果指向的是不同的对象,则哈希值是不一样的
4)哈希值主要根据地址号来的!不能完全将哈希值等价于地址。
package com.zzpedu.object_;
public class HashCode_ {
public static void main(String[] args) {
AA aa = new AA();
AA aa2 = new AA();
//两个引用,如果指向的是不同的对象,则哈希值是不一样的
System.out.println("aa.hashCode()=" + aa.hashCode());
System.out.println("aa2.hashCode()=" + aa2.hashCode());
//两个引用,如果指向的是同一个对象,则哈希值肯定一样的!
AA aa3 = aa;
System.out.println("aa3.hashCode()=" + aa3.hashCode());
}
}
class AA{}
=============控制台输出=============
aa.hashCode()=460141958
aa2.hashCode()=1163157884
aa3.hashCode()=460141958
toString方法
基本介绍
默认返回:全类名 + @ + 哈希值的十六进制(hashCode),【查看Object 的 toString方法】
子类往往重写toString方法,用于返回对象的属性信息
源码:
package java.lang;
public class Object {
//Object 的toString方法 源码
//(1)getClass().getName() 类的全类名(包名 + 类名)
//(2)Integer.toHexString(hashCode()) 将该对象的hashCode值转换成十六进制字符串
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
}
package com.zzpedu.object_;
public class ToString {
public static void main(String[] args) {
Monster monster = new Monster("小妖怪", "巡山的", 1000);
System.out.println(monster.toString() + " hashcode=" + monster.hashCode());
}
}
class Monster{
private String name;
private String job;
private double sal;
public Monster(String name, String job, double sal) {
this.name = name;
this.job = job;
this.sal = sal;
}
}
=============控制台输出=============
com.zzpedu.object_.Monster@1b6d3586 hashcode=460141958
重写toString方法,打印对象或拼接对象时,都会自动调用该对象的toString形式。案例:Monster[name, job, sal]
package com.zzpedu.object_;
public class ToString {
public static void main(String[] args) {
Monster monster = new Monster("小妖怪", "巡山的", 1000);
System.out.println(monster.toString() + " hashcode=" + monster.hashCode());
}
}
class Monster{
private String name;
private String job;
private double sal;
public Monster(String name, String job, double sal) {
this.name = name;
this.job = job;
this.sal = sal;
}
//重写toString方法,输出对象的属性
//使用快捷键 alt+insert -> toString
@Override
public String toString() {//重写后,一般是把对象的属性值输出,当然程序员也可以自己定制
return "Monster{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
", sal=" + sal +
'}';
}
}
=============控制台输出=============
Monster{name='小妖怪', job='巡山的', sal=1000.0} hashcode=460141958
当直接输出一个对象时,toString 方法会被默认的调用
System.out.println(monster); 就会默认调用 monster.toString()
finalize方法
1、当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作
2、什么时候被回收:当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用finalize方法。
3、垃圾回收机制的调用,是由系统来决定(即有自己的GC算法),也可以通过System.gc() 主动触发垃圾回收机制。
package com.zzpedu.object_;
//演示 Finalize的用法
public class Finalize_ {
public static void main(String[] args) {
Cat bmw = new Cat("宝马");
//这时 car对象就是一个垃圾(没有任何引用),垃圾回收器就会回收(销毁)对象
//在销毁对象前,会调用该对象的Finalize方法
//程序员就可以在 finalize中,写自己的业务逻辑代码(比如释放资源:数据库连接,或者打开文件..)
//如果程序员不重写 finalize方法,那么就会调用 Object类的 finalize,即默认处理
//如果程序员重写了 finalize方法,就可以实现自己的逻辑
bmw = null;
System.gc();//主动调用垃圾回收器
System.out.println("程序退出了..");
}
}
class Cat{
private String name;
//属性,资源...
public Cat(String name) {
this.name = name;
}
//重写finalize方法
@Override
protected void finalize() throws Throwable {
System.out.println("我们销毁了汽车 " + name);
System.out.println("释放了某些资源...");
}
}
==================
我们销毁了汽车 宝马
释放了某些资源...
程序退出了..
我们在实际开发中,几乎不会运用 finalize,所以更多就是为了应付面试
断点调试(debug)
一个实际需求
1、在开发中,新手程序员在查找错误时,这时老程序员就会温馨提示,可以断点调试,一步一步的看源码执行过程,从而发现错误所在。
2、重要提示:在断点调试 过程中,是运行状态,是以对象的 运行类型来执行的。
A extends B; B b = new A(); b.xx();
断点调试介绍
1、断点调试是指在程序的某一行设置一个断点,调试时,程序运行到这一行就会停住,然后你可以一步一步往下调试,调试过程中可以看各个变量当前的值,出错的话,调试到出错的代码即显示错误,停下。进行分析从而找出这个Bug
2、断点调试是程序员必须掌握的技能。
3、断点调试也能帮助我们查看java底层源代码的执行过程,提高程序员的Java水平。
断点调试的快捷键:
F7(跳入) F8(跳过)shift + F8 (跳出) F9(resume,执行到下一个断点)
F7:跳入方法内
F8:逐行执行代码
shift + F8:跳出方法
断点调试应用案例
案例1
看一下变量的变化情况等
package com.zzpedu.debug_;
public class Debug01 {
public static void main(String[] args){
//演示逐行执行 快捷键 F8
int sum = 0;
for(int i = 0; i < 5; i++){
sum += i;
System.out.println("i =" + i);
System.out.println("sum =" + sum);
}
System.out.println("退出for...");
}
}
截图 + 说明 F8 逐行执行代码…
下断点
debug运行:
控制台输出:
案例2
package com.zzpedu.debug_;
public class Debug02 {
public static void main(String[] args){
//debug 数组
int arr[] = {1,10,-1};
for(int i = 0; i <= arr.length; i++){
System.out.println(arr[i]);
}
System.out.println("退出for...");
}
}
截图 + 说明
再按 F8 退出 控制台输出错误信息
案例3
演示如何追踪源码,看看java设计者是怎么实现的。
小技巧:将光标放在某个变量上,可以看到最新的数据
package com.zzpedu.debug_;
import java.util.Arrays;
public class Debug03 {
public static void main(String[] args){
//debug 源码
int arr[] = {1, -1, 10, -20, 100};
//排序 追人sort源码 F7(跳入)
Arrays.sort(arr);
for(int i = 0; i < arr.length; i++){
System.out.print(arr[i] + "\t");
}
}
}
截图 + 说明 F7(跳入)
如果进入不了
1.使用强制进入 (force step into) --> alt + shift + F7
2、配置一下
Setting --> Build,Execution,Deployment --> Debugger --> Stepping
把 Do bot step into the classes中的 java.*, javax.* 取消勾选,其他随意
案例4
演示如何直接执行到下一个断点 F9 resume
小技巧:断点可以在debug过程中,动态的下断点
package com.zzpedu.debug_;
import java.util.Arrays;
//演示执行到下一个断点,同时支持动态的下断点
public class Debug04 {
public static void main(String[] args){
//debug 源码
int arr[] = {1, -1, 10, -20, 100};
//排序 追人sort源码 F7(跳入)
Arrays.sort(arr);
for(int i = 0; i < arr.length; i++){
System.out.print(arr[i] + "\t");
}
System.out.println("hello100");
System.out.println("hello200");
System.out.println("hello300");
System.out.println("hello400");
System.out.println("hello500");
System.out.println("hello600");
System.out.println("hello700");
}
}
截图 + 说明
断点调试练习
1、使用断点调试的方法,追踪下一个对象创建的过程。
Person[name, age, 构造器]
package com.zzpedu.debug_;
//debug对象创建的过程,加深对调试的理解
public class DebugExercise {
public static void main(String[] args) {
//创建对象的流程
//(1)加载 Person对象
//(2)初始化
// 2.1 默认初始化
// 2.2 显示初始化
// 2.3 构造器初始化
//(3)返回对象的地址
Person jack = new Person("jack", 20);
System.out.println(jack);
}
}
class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
加载类
构造器初始化
进入toString方法
2、我们使用断点调试,查看动态绑定机制的如何工作
作业
1、定义一个Person类 {name,age,job},初始化Person 对象数组,有3个person对象,并按照 age 从 大到小 进排序,使用冒泡排序
package com.zzpedu.homework;
public class Homework01 {
public static void main(String[] args) {
//初始化Person 对象数组 有3个person对象
Person[] persons = new Person[3];
persons[0] = new Person("jack",10,"JavaEE工程师");
persons[1] = new Person("tom",50,"大数据工程师");
persons[2] = new Person("mary",35,"PHP工程师");
//输出当前对象数组
for (int i = 0; i < persons.length; i++) {
System.out.println(persons[i]);//默认调用对象的 toString方法
}
//使用冒泡排序
Person person = null;//临时变量,用于交换
for (int i = 0; i < persons.length - 1; i++) {//外出循环
for (int j = 0; j < persons.length -1 -i; j++) {//内层循环
//并按照 age 从 大到小 进排序,如果前面的人的年龄小age < 后面人的年龄,即交换
if(persons[j].getAge() < persons[j+1].getAge()){
person = persons[j+1];
persons[j + 1] = persons[j];
persons[j] = person;
}
}
}
System.out.println("=====排序后的对象数组====");
for (int i = 0; i < persons.length; i++) {
System.out.println(persons[i]);//默认调用对象的 toString方法
}
}
}
/*
定义一个Person类 {name,age,job},初始化Person 对象数组,
有3个person对象,并按照 age 从 大到小 进排序,使用冒泡排序
*/
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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", job='" + job + '\'' +
'}';
}
public int getAge() {
return age;
}
}
===============控制台输出===============
Person{name='jack', age=10, job='JavaEE工程师'}
Person{name='tom', age=50, job='大数据工程师'}
Person{name='mary', age=35, job='PHP工程师'}
=====排序后的对象数组====
Person{name='tom', age=50, job='大数据工程师'}
Person{name='mary', age=35, job='PHP工程师'}
Person{name='jack', age=10, job='JavaEE工程师'}
2、写出四种访问修饰符和各自访问权限
访问级别 | 访问控制修饰符 | 同类/本类 | 同包 | 子类 | 不同包 |
---|---|---|---|---|---|
公开 | public | √ | √ | √ | √ |
受保护 | protected | √ | √ | √ | × |
默认 | 没有修饰符 | √ | √ | × | × |
私有 | private | √ | × | × | × |
3、编写老师类
(1)要求有属性 “姓名name”,“年龄age”,“职称post”,“基本工资salary”
(2)编写业务方法,introduce(),实现输出一个教师的信息。
(3)编写教师类的三个子类:教授类(Professor),副教授类,讲师类。工资级别分别是:教授为1.3、副教授为1.2、讲师类1.1。在三个子类里面都重写父类的introduce()方法
(4)定义并初始化一个老师对象,调用业务方法,实现对象基本信息的后台打印
package com.zzpedu.homework;
/*
* (1)要求有属性 “姓名name”,“年龄age”,“职称post”,“基本工资salary”
* (2)编写业务方法,introduce(),实现输出一个教师的信息。
*/
public class Teacher {
private String name;
private int age;
private String post;
private double salary;
//这里我们在增加一个工资级别
private double grade;
public Teacher(String name, int age, String post, double salary, double grade) {
this.name = name;
this.age = age;
this.post = post;
this.salary = salary;
this.grade = grade;
}
public void introduce(){
System.out.println("name: " + name + " age: " + age + " post: " + post
+ " salary: " + salary + " grade: " + grade);
}
}
//子类
public class Professor extends Teacher {
public Professor(String name, int age, String post, double salary, double grade) {
super(name, age, post, salary, grade);
}
@Override
public void introduce() {
System.out.println(" 这是教授的信息 ");
super.introduce();
}
}
public class Homework03 {
public static void main(String[] args) {
Professor professor = new Professor("贾宝玉",10,"高级职称",30000,1.3);
professor.introduce();
}
}
=======控制台输出=======
这是教授的信息
name: 贾宝玉 age: 10 post: 高级职称 salary: 30000.0 grade: 1.3
4、通过继承实现员工工资打印功能
父类:员工类(Employee)
子类:部门经理类(Manager)、普通员工类(Worker)
(1) 部门经理工资=1000 + 当日工资*天数*等级 (1.2) =》奖金 + 基本工资
(2) 普通员工工资= 当日工资*天数*等级 (1.0) =》 基本工资
(3) 员工属性:姓名、单日工资,工作天数
(4) 员工方法(打印工资)
(5) 普通员工及部门经理都是员工子类,需要重写打印工资方法
(6) 定义并初始化普通员工对象,调用打印工资方法输出工资,定义并初始化部门经理对象,调用打印工资方法输出工资
package com.zzpedu.homework;
public class Employee {
//属性
//员工属性:姓名、单日工资,工作天数
private String name;
private double daySal;
private int workDays;
//分析出还有一个属性 等级
private double grade;
//方法[构造器 getter setter]
public Employee(String name, double daySal, int workDays, double grade) {
this.name = name;
this.daySal = daySal;
this.workDays = workDays;
this.grade = grade;
}
// 打印工资方法
//方法名 void printSal()
public void printSal(){
System.out.println(name + " 工资=" + (daySal * workDays * grade));
}
public String getName() {
return name;
}
public double getDaySal() {
return daySal;
}
public int getWorkDays() {
return workDays;
}
public double getGrade() {
return grade;
}
}
public class Manager extends Employee{
//特有属性 奖金
private double bonus;
//创建Manager对象时,奖金是多少并不是确定的,因此在构造器中,不给bonus
//,可以通过setBonus
public Manager(String name, double daySal, int workDays, double grade) {
super(name, daySal, workDays, grade);
}
//方法:重写父类的 printSal
@Override
public void printSal() {
//因为经理的工资计算方式和Employee不一样,所以我们重写
System.out.println("经理 " + getName() + " 工资=" +
(bonus + getDaySal() * getWorkDays() * getGrade()));
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
}
public class Worker extends Employee{
//分析普通员工没有特有的属性
public Worker(String name, double daySal, int workDays, double grade) {
super(name, daySal, workDays, grade);
}
//方法:重写父类的 printSal
@Override
public void printSal() {//因为普通员工和Employee输出工资情况一样,所以直接调用父类的printSal()
System.out.print("普通员工 ");//自己的输出信息
super.printSal();//调用父类的方法,复用代码
}
}
public class Homework04 {
public static void main(String[] args) {
Manager manager = new Manager("刘备",100,20,1.2);
//设置奖金
manager.setBonus(3000);
//打印经理的工资情况
manager.printSal();
Worker worker = new Worker("关羽",80,10,1.0);
worker.printSal();
}
}
=======控制台输出=======
经理 刘备 工资=5400.0
普通员工 关羽 工资=800.0
5、设计父类 - 员工类。子类:工人类(Worker),农民类(Peasant),教师类(Teacher),科学类(Scientist),服务生(Waiter)类等。
(1) 其中工人,农民,服务生只有基本工资 sal
(2) 教师除基本工资外,还有课酬(元/天) classDay,classSal
(3) 科学家除基本工资外,还有年终奖 bonus
(4) 编写一个测试类,将各种类型的员工的全年工资打印出来
package com.zzpedu.homework.homework5;
public class Employee {//父类
//属性
private String name;
private double sal;
//分析有一个带薪的月份 13薪,15薪,12薪
private int salMonth = 12;//默认12个月
//方法
public Employee(String name, double sal) {
this.name = name;
this.sal = sal;
}
//打印全年工资
public void printSal(){
System.out.println(name + " 年工资=" + (sal * salMonth));
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public int getSalMonth() {
return salMonth;
}
public void setSalMonth(int salMonth) {
this.salMonth = salMonth;
}
}
public class Worker extends Employee{//子类
// 其中工人,农民,服务生只有基本工资 sal
public Worker(String name, double sal) {
super(name, sal);
}
@Override
public void printSal() {
System.out.print("工人 ");
super.printSal();//使用父类的printSal()方法
}
}
public class Peasant extends Employee{//子类
// 其中工人,农民,服务生只有基本工资 sal
public Peasant(String name, double sal) {
super(name, sal);
}
@Override
public void printSal() {
System.out.print("农民 ");
super.printSal();//使用父类的printSal()方法
}
}
public class Waiter extends Employee{//子类
public Waiter(String name, double sal) {
super(name, sal);
}
@Override
public void printSal() {
System.out.print("服务生 ");
super.printSal();//使用父类的printSal()方法
}
}
public class Teacher extends Employee{//子类
//特有属性
private int classDay;//一年上课次数
private double classSal;//课时费
public Teacher(String name, double sal) {
super(name, sal);
}
//方法-重写printSal
@Override
public void printSal() {//老师不能复用super.printSal()
System.out.print("老师 ");
System.out.println(getName() + " 年工资=" +
(getSal() * getSalMonth() + classSal * classDay));
}
public int getClassDay() {
return classDay;
}
public void setClassDay(int classDay) {
this.classDay = classDay;
}
public double getClassSal() {
return classSal;
}
public void setClassSal(double classSal) {
this.classSal = classSal;
}
}
public class Scientist extends Employee{//子类
//特有属性
private double bonus;//年终奖
public Scientist(String name, double sal) {
super(name, sal);
}
//重写年工资打印
@Override
public void printSal() {
System.out.print("科学家 ");
System.out.println(getName() + " 年工资=" + (getSal() * getSalMonth() + bonus));
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
}
public class Homework05 {
public static void main(String[] args) {
Worker jack = new Worker("jack",1000);
jack.setSalMonth(15);//灵活的修改带薪月份
jack.printSal();
Peasant smith = new Peasant("smith",2000);
smith.printSal();
//老师测试
Teacher zzp = new Teacher("zzp", 2000);
zzp.setClassDay(360);
zzp.setClassSal(500);
zzp.printSal();
//科学家
Scientist scientist = new Scientist("重男", 2000);
scientist.setSalMonth(15);
scientist.setBonus(100000);
scientist.printSal();
Waiter waiter = new Waiter("tom",5000);
waiter.setSalMonth(13);
waiter.printSal();
}
}
=======控制台输出=======
工人 jack 年工资=15000.0
农民 smith 年工资=24000.0
老师 zzp 年工资=204000.0
科学家 重男 年工资=130000.0
服务生 tom 年工资=65000.0
6、 在父类和子类中通过this和super都可以调用哪些属性和方法,假定 Grand、Father 和 Son 在同一个包
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可以访问哪些成员(属性和方法)?指Grand类并且不是private修饰的
super.name;
super.g1();
//this可以访问哪些成员?先本类后父类
this.id;
this.score;
this.f1();
this.name;
this.g1();
}
}
class Son extends Father{//子类
String name = "BB";
public void g1(){}
private void show(){
//super可以访问哪些成员(属性和方法)?
super.id;
super.f1();
super.name;
super.g1();
//this可以访问哪些成员?
this.name;
this.g1();
this.show();
this.id;
this.f1();
}
}
7、写出程序结果
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();//匿名对象
}
}
结果:
Test
Demo
Rose
Jack
-------
john
Jack
package com.zzpedu.homework;
public class Homework07 {
public static void main(String[] args){
new Demo().test();//匿名对象
new Demo("john").test();//匿名对象
}
}
class Test{//父类
String name = "Rose";
Test(){
System.out.println("Test");//1 Test
}
Test(String name){//name = john
this.name = name;//这里把父类的 name 修改成 john
}
}
class Demo extends Test{//子类
String name = "Jack";
Demo(){
super();
System.out.println("Demo");//2 Demo
}
Demo(String s){
super(s);
}
public void test(){
System.out.println(super.name);//3 Rose 5 john
System.out.println(this.name);//4 Jack 6 Jack
}
}
====控制台输出=========
Test
Demo
Rose
Jack
john
Jack
8、扩展如下的BankAccount类
class BankAccount{
private double balance;//余额
public BankAccount(double initialBalance){
this.balance = initialBalance;
}
//存款
public void deposit(double amount){
balance += amount;
}
//取款
public void withdraw(double amount){
balance -= amount;
}
//setter gettter..方法
}
要求:
(1) 在上面类的基础上扩展 新类 CheckingAccount 对每次存款和取款都收取1美元的手续费
(2) 扩展前一个练习的BankAccount类,新类SavingsAccount每个月都有利息产生(earnMonthlyInterest 方法被调用),并且有每月三此免手续费的存款或取款。在earnMonthlyInterest方法中重复设置交易次数
package com.zzpedu.homework;
/*
扩展前一个练习的BankAccount类,新类SavingsAccount每个月都有利息产生(earnMonthlyInterest 方法被调用),
并且有每月三此免手续费的存款或取款。在earnMonthlyInterest方法中重复设置交易次数
*/
public class SavingsAccount extends BankAccount{
//分析
//新增加属性
private int count = 3;
private double rate = 0.01;//利率
public SavingsAccount(double initialBalance) {
super(initialBalance);
}
public void earnMonthlyInterest(){//每个月初,我们统计上个月的利息,同时将count=3
count = 3;
super.deposit(getBalance() * rate);
}
@Override
public void deposit(double amount) {//存款
//判断是否还可以免手续费
if(count > 0){
super.deposit(amount);
}else {
super.deposit(amount - 1 );//手续费 1块钱转入银行
}
count--;//减去一次
}
@Override
public void withdraw(double amount) {//取款
//判断是否还可以免手续费
if(count > 0){
super.withdraw(amount);
}else {
super.withdraw(amount + 1 );//手续费 1块钱转入银行
}
count--;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public double getRate() {
return rate;
}
public void setRate(double rate) {
this.rate = rate;
}
}
/*
*在上面类的基础上扩展 新类 CheckingAccount 对每次存款和取款都收取1美元的手续费
*/
public class CheckingAccount extends BankAccount{//新的账号
//没有新的属性
public CheckingAccount(double initialBalance) {
super(initialBalance);
}
@Override
public void deposit(double amount) {//存款
super.deposit(amount - 1);//巧妙的使用父类的deposit
//1 块钱转入银行的账号
}
@Override
public void withdraw(double amount) {//取款
super.withdraw(amount + 1);
//1 块钱转入银行的账号
}
}
public class Homework08 {
public static void main(String[] args) {
CheckingAccount checkingAccount = new CheckingAccount(1000);
checkingAccount.deposit(10);//1010 -1 = 1009
checkingAccount.withdraw(9);//1009 - 9 - 1 = 999
System.out.println(checkingAccount.getBalance());
System.out.println("-------------------------");
//测试SavingsAccount
SavingsAccount savingsAccount = new SavingsAccount(1000);
savingsAccount.deposit(100);
savingsAccount.deposit(100);
savingsAccount.deposit(100);
System.out.println(savingsAccount.getBalance());//1300.0
savingsAccount.deposit(100);
System.out.println(savingsAccount.getBalance());//1399.0
//月初,定时器自动调用一下 earnMonthlyInterest
savingsAccount.earnMonthlyInterest();
System.out.println(savingsAccount.getBalance());//1399.0 + 1399.0* 0.01 = 1399.0 + 13.99 = 1412.99
savingsAccount.withdraw(100);//免手续费
System.out.println(savingsAccount.getBalance());//1312.99 免手续费
savingsAccount.withdraw(100);//免手续费
savingsAccount.withdraw(100);//免手续费
System.out.println(savingsAccount.getBalance());//1112.99
savingsAccount.deposit(100);//扣手续费
System.out.println(savingsAccount.getBalance());//1211.99
}
}
========控制台输出========
999.0
-------------------------
1300.0
1399.0
1412.99
1312.99
1112.99
1211.99
9、设计一个Point类,其x和y坐标可以通过构造器提供。提供一个子类LabeledPoint,其构造器接受一个标签值和x,y坐标,比如:new LabeledPoint(“Black”, 1929, 230.07),写出对应的构造器即可
package com.zzpedu.homework;
public class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
}
public class LabeledPoint extends Point{
//特有属性
private String label;
public LabeledPoint(String label, double x, double y) {
super(x, y);
this.label = label;
}
}
public class Homework09 {
public static void main(String[] args) {
new LabeledPoint("Black", 1929, 230.07);
}
}
10、编写Doctor类 {name, age, job, gender, sal} 相应的getter()和setter()方法,5个参数的构造器,重写父类(Object)的equals()方法:
public boolean equals(Object obj)
并判断测试类中创建两个对象是否相等。相等就是判断属性是否相同
package com.zzpedu.homework;
public class Doctor {
//属性 {name, age, job, gender, sal}
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;
}
//重写父类(Object)的equals()方法:
public boolean equals(Object obj){
if(this == obj){//判断两个比较对象是否相同
return true;
}
//判断obj是否是 Doctor类型或者其子类
if(! (obj instanceof Doctor)){//不是的话
return false;
}
//向下转型,因为obj的运行类型是Doctor或者是其子类型
Doctor doctor = (Doctor) obj;
return this.name.equals(doctor.name) && this.age==doctor.age &&
this.gender==doctor.gender && this.job.equals(doctor.getJob()) &&
this.sal==doctor.sal;
}
//相应的getter()和setter()方法
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; }
}
public class Homework10 {
public static void main(String[] args) {
Doctor doctor1 = new Doctor("jack",20,"牙科医生",'男',20000);
Doctor doctor2 = new Doctor("jack",20,"牙科医生",'男',20000);
System.out.println(doctor1.equals(doctor2));//true
}
}
11、现有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...");
}
}
//向上转型 父类的引用指向子类对象
Person p = new Student();
p.run();//先找Student的run student run
p.eat();//子类没有 person eat
//向下转型:把指向子类对象的父类引用,转成指向子类对象的子类引用
Student stu = (Student) p;
stu.run();//student run
stu.study();//student study...
stu.eat();//person eat
12、说出 == 和 equals 的区别
名称 | 概念 | 用于基本类型 | 用于引用类型 |
---|---|---|---|
== | 比较运算符 | 可以,判断值是否相等 | 可以,判断两个对象是否相等 |
equals | Object类的方法, Java类都可以使用equals | 不可以 | 可以,默认是判断两个对象是否相等, 但是子类往往重写该方法,比较对象的属性是否相等,比如(String,Integer) |
13、打印效果如下:
老师的信息:
姓名:张飞
年龄:30
性别:男
工龄:5
我承诺,我会认真教课。
王飞爱玩象棋
----------------------------------
学生的信息:
姓名:小明
年龄:15
性别:男
学号:00023102
我承诺,我会好好学习
小明爱玩足球
题目描述:
(1)做一个Student类,Student类有名称(name),性别(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分别代表学生和老师的姓名)。因为玩的方法名称都是一样的,所以要求此方法定义在父类中,子类实现重写
应当分析出,我们需要打印信息的方法,printInfo()
package com.zzpedu.homework.homework13;
/*
抽取一个父类Person类,将共同属性和方法放到Person类
*/
public class Person {//父类
//属性
private String name;
private char sex;
private int age;
//构造器
public Person(String name, char sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
//编写一个 play方法,把共有的输出内容写到父类
public String play(){
return name + "爱玩";
}
//返回一个基本信息
/*
姓名:张飞
年龄:30
性别:男
*/
public String basicInfo(){
return "姓名: " + name + "\n年龄: " + age + "\n性别: " + sex;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public char getSex() { return sex; }
public void setSex(char sex) { this.sex = sex; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
/*
Teacher类有名称(name),性别(sex),年龄(age),工龄(work_age),
做合理封装,通过构造器在创建对象时将4个属性赋值
*/
public class Teacher extends Person{//子类
//属性
private int work_age;
public Teacher(String name, char sex, int age, int work_age) {
super(name,sex,age);
this.work_age = work_age;
}
@Override
public String play() {
return super.play() + "象棋";
}
//教师需要有教学的方法(teach),在方法里写上 “我承诺,我会认真教学。
public void teach(){
System.out.println("我承诺,我会认真教学。");
}
//输出信息的方法
public void printInfo(){
System.out.println("老师的信息:");
System.out.println(super.basicInfo());
System.out.println("工龄: " + work_age);
teach();//组合
System.out.println(play());
}
public int getWork_age() { return work_age; }
public void setWork_age(int work_age) { this.work_age = work_age; }
}
/*
Student类有名称(name),性别(sex),年龄(age),学号(stu_id),
做合理封装,通过构造器在创建对象时将4个属性赋值
*/
public class Student extends Person {//子类
//属性
private String stu_id;
//构造器
public Student(String name, char sex, int age, String stu_id) {
super(name,sex,age);
this.stu_id = stu_id;
}
@Override
public String play() {
return super.play() + "足球";
}
//学生需要有学习的方法(study),在方法里写 “我承诺,我会好好学习。
public void study(){
System.out.println("我承诺,我会好好学习。");
}
//编写一个输出信息的方法,这样体现封装
public void printInfo(){
System.out.println("学生的信息:");
System.out.println(super.basicInfo());
System.out.println("学号: " + stu_id);
study();//组合
System.out.println(play());
}
public String getStu_id() { return stu_id; }
public void setStu_id(String stu_id) { this.stu_id = stu_id; }
}
package com.zzpedu.homework.homework13;
public class Homework13 {
public static void main(String[] args) {
//测试老师
Teacher teacher = new Teacher("张飞",'男',30,5);
teacher.printInfo();
System.out.println("--------------------");
//测试
Student student = new Student("小明",'男',15,"00023102");
student.printInfo();//封装
}
}
===========控制台输出=============
老师的信息:
姓名: 张飞
年龄: 30
性别: 男
工龄: 5
我承诺,我会认真教学。
张飞爱玩象棋
----------------
学生的信息:
姓名: 小明
年龄: 15
性别: 男
学号: 00023102
我承诺,我会好好学习。
小明爱玩足球
(7)定义多态数组,里面保存2个学生和2个教师,要求按年龄从高到底排序,
(8)定义方法,形参为Person类型,功能:调用学生的study或教师的teach方法
public class Person {//父类
//...
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", sex=" + sex +
", age=" + age +
'}';
}
}
public class Teacher extends Person{//子类
//...
@Override
public String toString() {
return "Teacher{" +
"work_age=" + work_age +
'}' + super.toString();
}
}
public class Student extends Person {//子类
//...
@Override
public String toString() {
return "Student{" +
"stu_id='" + stu_id + '\'' +
'}' + super.toString();
}
}
public class Homework13 {
public static void main(String[] args) {
//定义多态数组,里面保存2个学生和2个教师,要求按年龄从高到底排序
Person[] persons = new Person[4];
persons[0] = new Student("jack",'男',10,"0001");
persons[1] = new Student("mary",'女',20,"0002");
persons[2] = new Teacher("smith",'男',36,5);
persons[3] = new Teacher("scott",'男',26,1);
Homework13 homework13 = new Homework13();
homework13.bubbleSort(persons);
System.out.println("======排序后的对象数组======");
for (int i = 0; i < persons.length; i++) {
System.out.println(persons[i]);
}
//遍历数组,调用test方法
System.out.println("======------======");
for (int i = 0; i < persons.length; i++) {
homework13.test(persons[i]);
}
}
//定义方法,形参为Person类型,功能:调用学生的study或教师的teach方法
//分析这里会使用到向下转型和类型判断
public void test(Person p){
if(p instanceof Student){//p 的运行类型如果是 Student
((Student) p).study();
}else if(p instanceof Teacher){
((Teacher) p).teach();
}else {
System.out.println("do nothing...");
}
}
//方法,完成年龄从高到底排序
public void bubbleSort(Person[] persons){
Person tmp = null;
for (int i = 0; i < persons.length - 1; i++) {
for (int j = 0; j < persons.length - 1 - i; j++) {
if(persons[j].getAge() < persons[j + 1].getAge()){
tmp = persons[j + 1];
persons[j + 1] = persons[j];
persons[j] = tmp;
}
}
}
}
}
===========控制台输出=============
======排序后的对象数组======
Teacher{work_age=5}Person{name='smith', sex=男, age=36}
Teacher{work_age=1}Person{name='scott', sex=男, age=26}
Student{stu_id='0002'}Person{name='mary', sex=女, age=20}
Student{stu_id='0001'}Person{name='jack', sex=男, age=10}
======------======
我承诺,我会认真教学。
我承诺,我会认真教学。
我承诺,我会好好学习。
我承诺,我会好好学习。
14、程序阅读, 在main 方法中,执行: 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("haha");
System.out.println(name + "我是C类的有参构造");
}
}
C c = new C(); 输出如下
我是A类
haha我是B类的有参构造
hello我是C类的有参构造
我是C类的无参构造
15、什么是多态,多态具体体现有哪些?(可举例说明)
多态:方法或者对象具有多种形态,是OOP的第三大特征,
是建立在封装和继承基础之上的
多态具体体现
1.方法多态
(1)重载体现多态 (2)重写体现多态
2.对象多态
(1)对象的编译类型和运行类型可以不一致,编译类型在定义时,就确定,不能变化
(2)对象的运行类型是可以变化的,可以通过getClass() 来查看运行类型
(3)编译类型看定义时 = 号的左边,运行类型看 = 号 右边
3.举例说明
public class Homework15 {
public static void main(String[] args) {
AAA obj = new BBB();//向上转型
AAA b1 = obj;
System.out.println("obj的运行类型:" + obj.getClass());//BBB
obj = new CCC();//向上转型
System.out.println("obj的运行类型:" + obj.getClass());//CCC
obj = b1;
System.out.println("obj的运行类型:" + obj.getClass());//BBB
}
}
class AAA{ //超类
}
class BBB extends AAA{//父类
}
class CCC extends BBB{//子类
}
==========控制台输出=============
obj的运行类型:class com.zzpedu.homework.BBB
obj的运行类型:class com.zzpedu.homework.CCC
obj的运行类型:class com.zzpedu.homework.BBB
16、java的动态绑定机制是什么?
1.当调用对象的方法时,该方法会和对象的内存地址/运行类型绑定
2.当调用对象的属性时,没有动态绑定机制,哪里声明,哪里使用