(一)学习总结
1.阅读下面程序,分析是否能编译通过?如果不能,说明原因。应该如何修改?程序的运行结果是什么?为什么子类的构造方法在运行之前,必须调用父 类的构造方法?能不能反过来?
class Grandparent {
public Grandparent() {
System.out.println("GrandParent Created.");
}
public Grandparent(String string) {
System.out.println("GrandParent Created.String:" + string);
}
}
class Parent extends Grandparent {
public Parent() {
System.out.println("Parent Created");
super("Hello.Grandparent.");
}
}
class Child extends Parent {
public Child() {
System.out.println("Child Created");
}
}
public class Test{
public static void main(String args[]) {
Child c = new Child();
}
}
这个程序不能通过编译。程序中Parent类继承了Grandparent类,父类中已经含有无参构造方法,对父类含参数的构造方法,子类可以通过在定义自己的构造方法中使用super关键字来调用它,但这个调用语句必须是子类构造方法的第一个可执行语句。
修改结果:
class Grandparent {
public Grandparent() {
System.out.println("GrandParent Created.");
}
public Grandparent(String string) {
System.out.println("GrandParent Created.String:" + string);
}
}
class Parent extends Grandparent {
public Parent() {
super("Hello.Grandparent.");
System.out.println("Parent Created");
}
}
class Child extends Parent {
public Child() {
System.out.println("Child Created");
}
}
public class Test{
public static void main(String args[]) {
Child c = new Child();
}
}
运行结果:
GrandParent Created.String:Hello.Grandparent.
Parent Created
Child Created
构造方法的继承原则:
- 子类无条件地继承父类的不含参数的构造方法。
- 若子类没有定义自己的构造方法,它将继承父类无参数的构造方法作为自己的构造方法。
- 若子类定义了自己的构造方法,它先执行继承自父类的无参数构造方法,再执行自己的构造方法。
- 对父类含参数的构造方法,子类可以通过在定义自己的构造方法中使用super关键字来调用它,但这个调用语句必须是子类构造方法的第一个可执行语句。
- 子类构造方法没有显式调用父类构造方法,而父类又没有无参构造方法时,则编译出错。
不能在调用父类的构造方法之前,运行子类的构造方法。
构造方法(constructor)是一种特殊的方法 。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中 。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。构造函数的功能主要用于在类的对象创建时定义初始化的状态。
构造一个对象,先调用其构造方法,来初始化其成员函数和成员变量。
子类拥有父的成员变量和成员方法,如果不调用,则从父类继承而来的成员变量和成员方法得不到正确的初始化。
因为父类不知道子类有什么变量,子类也得不到初始化的父类变量,导致程序运行出错!
2.阅读下面程序,分析程序中存在哪些错误,说明原因,应如何改正?正确程序的运行结果是什么?
class Animal{
void shout(){
System.out.println("动物叫!");
}
}
class Dog extends Animal{
public void shout(){
System.out.println("汪汪......!");
}
public void sleep() {
System.out.println("狗狗睡觉......");
}
}
public class Test{
public static void main(String args[]) {
Animal animal = new Dog();
animal.shout();
animal.sleep();
Dog dog = animal;
dog.sleep();
Animal animal2 = new Animal();
dog = (Dog)animal2;
dog.shout();
}
}
程序中的错误:
1.编译出错
animal.sleep();
Dog dog = animal;
2.编译时没有问题但运行时会出错
Animal animal2 = new Animal();
dog = (Dog)animal2;
dog.shout();
原因:
1.animal是子类Dog的上转型对象,上转型对象不能操作子类新增加的成员变量,不能使用子类新增的方法。父类对象定义为子类对象称为向下转型,对于向下转型,需要强制转型,即必须明确指明要转型的子类类型: 格式:子类名称 子类对象 =(子类)父类实例;
2.animal2是一个新定义的父类对象,即父类引用的对象是父类本身。父类对象不能确定dog是自己的子类。需要使用instanceof关键字避免此错误。
改正:
class Animal{
void shout(){
System.out.println("动物叫!");
}
}
class Dog extends Animal{
public void shout(){
System.out.println("汪汪......!");
}
public void sleep() {
System.out.println("狗狗睡觉......");
}
}
public class Test{
public static void main(String args[]) {
Animal animal = new Dog();
animal.shout();
//animal.sleep();
Dog dog = (Dog) animal;
dog.sleep();
Animal animal2 = new Animal();
if(animal2 instanceof Dog)
{
dog = (Dog)animal2;
dog.shout();
}
}
}
运行结果:
汪汪......!
狗狗睡觉......
3.运行下列程序
class Person {
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
}
public class Test{
public static void main(String args[]){
Person per = new Person("张三",20) ;
System.out.println(per);
System.out.println(per.toString()) ;
}
}
(1)程序的运行结果如下,说明什么问题?
Person@166afb3
Person@166afb3
System.out.println(per);默认调用父类Object 的toString方法。
(2)那么,程序的运行结果到底是什么呢?利用eclipse打开println(per)方法的源码,查看该方法中又调用了哪些方法,能否解释本例的运行结果?
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
valueOf(x) //if the argument is null, then a string equal to "null";
otherwise, the value of obj.toString() is returned.
如果参数为空字符串,则返回空,否则,返回toString()的返回值。
toString()返回一个字符串用于描述当前对象,返回的具体内容:类名@对象的hash码十六进制表示。
(3)在Person类中增加如下方法
public String toString(){
return "姓名:" + this.name + ",年龄:" + this.age ;
}
重新运行程序,程序的执行结果是什么?说明什么问题?
可参考教材P229
执行结果:
姓名:张三,年龄:20
姓名:张三,年龄:20
说明在Person类中完成了对父类Object的toString类的重写。
4.汽车租赁公司,出租汽车种类有客车、货车和皮卡三种,每辆汽车除了具有编号、名称、租金三个基本属性之外,客车有载客量,货车有载货量,皮卡则同时具有载客量和载货量。用面向对象编程思想分析上述问题,将其表示成合适的类、抽象类或接口,说明设计思路。现在要创建一个可租车列表,应当如何创建?
定义一个车辆 接口包含三个属性:编号、名称、租金。对每个属性分别定义get,set方法。
分别定义一个载客量和载货量接口,分别具有载客量和载货量属性并且定义get,set方法。
客车类、货车类、皮卡类同时继承车辆接口,客车类继承载客量接口,货车类继承载货量接口,皮卡类再同是继承载客量和载货量接口。
在测试类中完成可租车列表的创建。分别为客车类、货车类、皮卡类创建对象数组,每个类的对象个数表示可租车数量,每次租一辆车去掉一个对象,每进行一次租车重新显示一次可租车对象,用户每次从可租车对象中选择要租用的车辆。
5.阅读下面程序,分析代码是否能编译通过,如果不能,说明原因,并进行改正。如果能,列出运行结果
interface Animal{
void breathe();
void run();
void eat();
}
class Dog implements Animal{
public void breathe(){
System.out.println("I'm breathing");
}
void eat(){
System.out.println("I'm eating");
}
}
public class Test{
public static void main(String[] args){
Dog dog = new Dog();
dog.breathe();
dog.eat();
}
}
不能通过编译。
- 在类的声明中用implements子句来表示一个类使用某个接口,在类中可以使用接口中定义的常量,而且必须实现接口中定义的所有方法。
- 在类中实现接口所定义的方法时,必须显式地使用public修饰符,否则将被系统警告为缩小了接口中定义的方法的访问控制范围。
改正:
interface Animal{
void breathe();
void run();
void eat();
}
class Dog implements Animal{
public void breathe(){
System.out.println("I'm breathing");
}
public void run()
{
}
public void eat(){
System.out.println("I'm eating");
}
}
public class Test{
public static void main(String[] args){
Dog dog = new Dog();
dog.breathe();
dog.eat();
}
}
运行结果:
I'm breathing
I'm eating
6.super键字和final关键字
在子类重写父类方法后,要访问父类被重写的方法,需要用super关键字来引用当前类的父类。super的用法有两种情况:
(1) 访问的父类中的成员变量和成员方法
super.变量名
super.方法名([参数表])
(2)调用父类的构造方法
super([参数表])
在Java中可以使用final关键字定义类、方法、属性:
- 使用final关键字定义的类不能有子类
- 使用final声明的方法不能被子类所覆盖
- 使用final声明的变量即成为常量,常量必须在声明时给出具体的内容。
在声明一个常量的时候所有单词的字母都必须大写,如果要想声明一个全局常量的话:
public static final声明。
public static final String INFO = "hello" ;
7.多态
- 多态性是指同名的不同方法在程序中共享,即为同一个方法定义几个版本,运行时根据不同情况执行不同的版本。
- 一个名字,多个方法
- 多态性的实现:
覆盖实现多态:(子类重写父类中的方法)
重载实现多态(同一个类中的同名方法)
相同的一条语句在不同的运行环境中可以产生不同的运行结果。
覆盖实现多态:
- 子类方法的返回类型、参数、方法名称要和父类的返回类型、参数、方法名称完全一样,否则编译出错。
- 被覆盖的方法不能为private
- 子类方法不能缩小父类方法的访问权限。
- 覆盖后的方法不能比被覆盖的方法产生更多的例外,即抛出更多的异常。
- 根据调用对象区分调用的方法
重载实现多态:
- 方法名相同
- 方法的参数类型,个数,顺序至少有一项不同
- 根据形参来决定调用的是哪个方法
8.对象转型:
- 上转型对象
向上转型:子类对象 父类对象
对于向上转型,程序会自动完成:
格式:父类名称 父类对象 = 子类实例;
上转型对象具有如下特点: - 上转型对象不能操作子类新增加的成员变量,不能使用子类新增的方法。
- 上转型对象可以操作子类继承或隐藏的成员变量,也可以使用子类继承的或重写的方法。
- 上转型对象调用方法时,就是调用子类继承和重写过的方法。而不会是新增的方法,也不是父类原有的方法。
- 下转型对象
向下转型:父类对象 子类对象
对于向下转型,需要强制转型,即必须明确指明要转型的子类类型:
格式:子类名称 子类对象 =(子类)父类实例;
在向下转型过程中,分为两种情况: - 如果父类引用的对象是指向的子类对象,那么在向下转型的过程中是安全的。也就是编译是不会出错误的。
- 如果父类引用的对象是父类本身,那么在向下转型的过程中是不安全的,编译不会出错,但是运行时会出现java.lang.ClassCastException错误。它可以使用instanceof来避免出错此类错误。
- instanceof关键字
通过instanceof关键字可以判断某一个对象是否是某一个类的实例。
格式:对象 instanceof 类返回boolean类型
public class Test{
public static void main(String[] args) {
Person p = new Student(); //向上转型
Student s=(Student)p; //向下转型
s.eat();
s.study();
Person p1=new Person();
if (p1 instanceof Student)
{
s=(Student)p1; //向下转型
s.eat();
}
}
}
9.接口
接口与类的不同在于:
- (1) 没有变量的声明,但可以定义常量。
- (2) 只有方法的声明,没有方法的实现。
接口的定义:
[public] interface 接口名称 [extends 父接口列表]
{
//抽象方法和全局常量
}
- public指明任意类均可使用这个接口,缺省时,表明只有与接口定义在同一个包中的类才可访问此
- extends是关键字,父接口列表表示一个接口可以有多个父接口,用逗号分开,而类只能有一个父类。子接口继承父接口中所有的常量和方法。
接口中没有自身的构造方法。 - 在接口中定义的常量必须是pubilc static final,这是系统默认的规定,所以常量也可以没有任何修饰符。
- 接口中只有方法声明,而无方法实现,接口中声明的方法必须是public abstract,也是系统默认的规定。
接口定义的完整格式:
interface A{
public static final String AUTHOR = "李兴华" ; // 定义全局常量
public abstract void print() ; // 定义抽象方法
public abstract String getInfo() ; // 定义抽象方法
}
接口定义的简化格式:
interface A{
String AUTHOR = "李兴华" ; // 定义全局常量
void print() ; // 定义抽象方法
String getInfo() ; // 定义抽象方法
}
接口的实现:
格式:
class 类名 implements 接口列表 { 类体 }
(1)在类的声明中用implements子句来表示一个类使用某个接口,在类中可以使用接口中定义的常量,而且必须实现接口中定义的所有方法。
(2)一个类可实现多个接口,在implements子句中用逗号分隔。
注意:
- 在类中实现接口所定义的方法时,方法的声明必须与接口中所定义的完全一致。
- 在类中实现接口所定义的方法时,必须显式地使用public修饰符,否则将被系统警 告为缩小了接口中定义的方法的访问控制范围。
- 抽象类可以不实现接口的抽象方法,而非抽象类必须实现接口中的所有方法。
接口的继承:
格式:
interface 子接口 extends 父接口A,父接口B,…{
}
10.接口回调
把实现某一接口的类创建的对象的引用赋给该接口声明的接口变量中,那么该接口变量就可以调用被类重写的接口方法。实际上,当接口变量调用被类重写的接口方法时,就是通知相应的对象调用这个方法。
分析:
Com com;//声明接口对象
ImpleCom obj= new ImpleCom();//实现接口子类对象
com = obj; //接口回调
interface ShowMessage {
void showMessage(String s);
}
class TV implements ShowMessage {
public void showMessage(String s) {
System.out.println(s);
}
}
class PC implements ShowMessage {
public void showMessage(String s) {
System.out.println(s);
}
}
public class Test{
public static void main(String args[]) {
ShowMessage sm; //声明接口变量
sm=new TV(); //接口变量中存放对象的引用
sm.showMessage("长城牌电视机"); //接口回调。
sm=new PC(); //接口变量中存放对象的引用
sm.showMessage("联想奔月5008PC机"); //接口回调
}
}
接口回调实现多态
interface SpeakHello {
void speakHello();
}
class Chinese implements SpeakHello {
public void speakHello() {
System.out.println("中国人问候语:你好,吃饭了吗? ");
}
}
class English implements SpeakHello {
public void speakHello() {
System.out.println("英国人问候语:你好,天气不错 ");
}
}
class KindHello{
public void lookHello(SpeakHello hello) {//接口类型参数
hello.speakHello(); //接口回调
}
}
public class Test {
public static void main(String args[]) {
KindHello kindHello = new KindHello();
kindHello.lookHello(new Chinese());
kindHello.lookHello(new English());
}
}
11.接口和抽象类的区别
- 语法层面上的区别
1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。 - 设计层面上的区别
1)抽象类是对一种事物的抽象,即对类抽象,包括属性、行为,而接口是对行为的抽象。抽象类和它的子类之间应该是一般和特殊的关系,继承是一个 “是不是(ISA)”的关系,而接口仅仅是它的子类应该实现的一组规则,接口实现则是 “能(CAN-DO)"的关系。
2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规 范,它是一种辐射式设计。
例:门和警报--门都有open( )和close( )两个动作
抽象类:
abstract class Door {
public abstract void open();
public abstract void close();
}
接口
interface Door {
public abstract void open();
public abstract void close();
}
需要门具有报警alarm( )的功能,怎么办?
方案一:在类中或接口中增加alarm()方法,有问题吗?
分析:open()和close()属于门本身固有的行为特性,而alarm()属于扩展的行为。
方案二:将报警设计为一个接口,包含alarm()行为,Door设计为一个抽象类,包含open和close两种行为。设计一个报警门继承Door类和实现Alarm接口。
interface Alarm {
void alarm();
}
abstract class Door {
public abstract void open();
public abstract void close();
}
class AlarmDoor extends Door implements Alarm {
public void open() { //...... }
public void close() { //...... }
public void alarm(){ //...... }
}
(二)实验总结
1.银行新用户现金业务办理
设计思路:
- (1)定义银行类Bank:银行名称bankName(静态变量)、用户名name、密码password、账号余额balance、交易额turnover。
包括如下方法:
静态方法welcome():打印欢迎语
构造方法:实现新用户的开户。包括用户名,密码,交易额。开户时扣除10元开卡费。
存款方法deposit():根据存款额修改账户余额。输出相关信息。
取款方法withdrawal():对用户密码进行验证,密码错误或取款额大于余额,不能办理业务,并提示用户。否则,修改用户余额。
静态方法welcomeNext():输出欢迎下次光临。 - (2)定义用户交易类Trade,模拟新用户到某个银行办理业务的场景。
2.定义员工类,具有姓名、年龄、性别属性,并具有构造方法和显示数据方法。
设计思路:
- 定义管理层类,继承员工类,有自己的属性职务和年薪。
- 定义职员类,继承员工类,并有自己的属性所属部门和月薪。
- 定义一个测试类,进行测试。
3.按照下面要求完成类的设计
设计思路:
- (1)设计一个平面图形抽象类(提供求该类对象周长和面积的方法)和一个立体图形抽象类(提供求该类对象表面积和体积的方法)
- (2)设计球类、圆柱类,圆锥类、矩形类、三角形类、圆类,分别继承平面图形抽象类和立体图形抽象类。
- (3)设计一个测试类,随机产生球,圆柱、圆锥体、矩形、三角形、圆的尺寸,模拟考试,要求用户回答它们的体积和表面积(或者周长和面积),并判断用户的回答是否正确。
4.饲养员小李
设计思路:
- (1)某动物园有一饲养员小李, 每天需要给他所负责饲养的一只狮子、五只猴子和十只鸽子喂食。 请用一个程序来模拟他喂食的过程。
- (2)利用抽象类和对象多态重构(1)中的程序,Animal类采用抽象类, 合并Feeder类中的方法
- (3)第二次重构,修改feedAnimals方法,让它接收一个Animal数组
5.宠物商店
宠物种类有猫、狗,宠物信息包括:编号、种类、品种、单价、数量。
要求实现以下功能:
(1)展示所有宠物
(2)购买宠物
(3)展示购买清单:显示购买的宠物品种,数量,价格合计及购买宠物的总价钱
6.设计一个动物声音“模拟器”,希望模拟器可以模拟许多动物的叫声。
设计思路:
- (1)接口Animal:有2个抽象方法cry()和getAnimaName(),要求实现该接口的各种具体动物类给出自己的叫声和种类名称。
- (2)模拟器类Simulator:有一个playSound(Animal animal)方法。即参数animal可以调用实现Animal接口类重写的cry()方法播放具体动物的声音、调用重写的getAnimalName()方法显示动物种类的名称。
- (3)实现Animal接口的Dog类和Cat类
- (4)测试类:main方法中至少包含如下代码:
Simulator simulator = new Simulator();
simulator.playSound(new Dog());
simulator.playSound(new Cat());
7.模拟物流快递系统
设计思路:
- (1)定义一个抽象类交通工具类Transportation,属性:编号,型号,运输负责人。方法:无参构造和含三个参数的构造方法,get和set方法,抽象方法transport()
- (2)定义一个专用运输车类JDTransportation继承交通工具类,继承父类构造方法,实现transport()方法,输出一个运输信息。
- (3)定义一个接口GPS实现对货物的定位。接口中有方法showCoordinate()跟踪货物的位置信息。
- (4)定义一个类Phone实现GPS接口
- (5)定义一个快递任务类SendTask,具有私有属性:快递单号、货物重量。方法:无参和含参构造方法,get和set方法,
sendBefore()方法输出运输前的检查信息和快递单号。
send(Transportation t,GPS tool)方法输出运货人、车辆编号、型号信息,该方法中调用transport方法输出运输信息,调用showCoordinate()方法输出货物位置坐标。
sendAfter( Transportation t )方法输出货物运输完成后的信息。 - (6)定义一个测试类,创建一个快递任务类对象,一个交通工具类对象,一个GPS工具对象。模拟物流快递过程。输出运输前、运输中和运输后的信息。
程序运行效果参考如下。
订单开始处理,仓库开始验货...
货物重量:13.2kg
订单已发货
快递单号:hx201835 //运输前信息
运货人小张正在驾驶编号为zh1002的长城发送货物!
京东快递运输中......
货物当前坐标:193,485 //运输中信息
货物运输已完成
运货人小张所驾驶编号为zh1002的长城已归还! //运输后信息
8.修改实验三的第四题,使用java.util.Date类表示职工的生日和参加工作时间,并将职工信息按照生日大小排序后输出。(分别用comparable和comparator实现)
设计思路:
将实验三的第四题中的时间类删除,原本的时间类数据生日、工作时间用Date类替换,用comparable和comparator接口Arrays类的sort方法排序后输出。
(三)代码托管(务必链接到你的项目)
- 码云提交历史截图