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(){
//super("Hello.Grandparent.");①
System.out.println("Parent Created");
//super("Hello.Grandparent.");②
  }
}
class Child extends Parent {
public Child(){
System.out.println("Child Created");
  }
}
public class TestInherits {
public static void main(String args[]){
Child c = new Child();
}
}

① 结果:

② 结果:

结论:通过super调用基类构造方法,必须是子类构造方法中的第一句。

2.为什么子类构造方法运行之前,必须调用父类的构造方法?

构造方法的主要作用是初始化,如果子类先运行,没有初始化,会出错。

3.使用javap –c命令反编译。

public class ExplorationJDKSource {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(new A());
}
}
class A{}

结果:

反编译:

分析:main方法实际调用了public void println(Object x),这一方法内部调用了String类的valueOf()方法。

valueOf方法又调用了Object.toString方法:

public String toString(){

  return getClass().getName()+”@”+ Integer.toHexString(hashCode());

}

结论:为明确继承的类,都继承类Object。

4.子类,父类的覆盖关系

public class Fruit{
public String toString(){
return "Fruit toString.";
}
public static void main(String args[]){
Fruit f=new Fruit();
System.out.println("f="+f);①
// System.out.println("f="+f.toString());②
}
}

  

① 结果:

② 结果:

分析:

Fruit类覆盖类Object类的toString方法。

“+”运算任何一个对象与一个String对象连接时会隐式调用toString()方法。

默认情况下方法返回“类名@+方法hashCode”。为了返回有意义的信息,子类可以重写toString()方法.所以两次结果为:f = Fruit toString。

5.调用父类被覆盖的方法

class Hello{
public void printTest(){
System.out.println("Hello!");
}
}
class Hi extends Hello{
public void printTest(){
System.out.println("Hi!");
}
public void test(){
super.printTest();//使用super调用父类方法
printTest();
}
}
public class Test{
public static void main(String[] args){
Hi h = new Hi();
h.test();
}
} 

结果:

6.类型转换,运行代码

class Mammal{}
class Dog extends Mammal {}
class Cat extends Mammal{}
public class TestCast{
public static void main(String args[]){
Mammal m;
Dog d=new Dog();
Cat c=new Cat();
m=d;
d=m;//会报错
d=(Dog)m;
d=c;//会报错
c=(Cat)m;
}
}

结果:

7.运行代码思考问题

class Parent{
public int myValue=100;
public void printValue() {
System.out.println("Parent.printValue(),myValue="+myValue);
}
}
class Child extends Parent{
public int myValue=200;
public void printValue() {
System.out.println("Child.printValue(),myValue="+myValue);
}
}
猜想以下代码运行结果:
public class ParentChildTest {
public static void main(String[] args) {
Parent parent=new Parent();
parent.printValue();
Child child=new Child();
child.printValue();
parent=child;
parent.printValue();
parent.myValue++;
parent.printValue();
((Child)parent).myValue++;
parent.printValue();
}
}

猜想:100 200 200 201 202

结果:

分析:

Parent parent=new Parent();parent.printValue(); 调用了父类方法输出为100

Child child=new Child();child.printValue();调用子类方法输出为200

parent=child;子类赋给父类parent.printValue();输出200

parent.myValue++;父类的myValue+1 为101

parent.printValue();这里输出的是子类赋给父类的方法输出200

((Child)parent).myValue++;父类强转为子类+1 为201

parent.printValue();输出201

总结:

①  当子类与父类拥有一样的方法,并且让一个父类变量引用一个子类对象时,到底调用哪个方法,由对象自己的“真实”类型所决定,这就是说:对象是子类型的,它就调用子类型的方法,是父类型的,它就调用父类型的方法。

②  如果子类与父类有相同的字段,则子类中的字段会代替或隐藏父类的字段,子类方法中访问的是子类中的字段(而不是父类中的字段)。如果子类方法确实想访问父类中被隐藏的同名字段,可以用super关键字来访问它。

③  如果子类被当作父类使用,则通过子类访问的字段是父类的。

8.多态代码生成字节指令

class Parent {
  public int value=100;
  public void Introduce(){
  System.out.println("I'm father");
}
}
class Son extends Parent{
  public int value=101;
public void Introduce(){
  System.out.println("I'm son");
  }
}
class Daughter extends Parent{
  public int value=102;
public void Introduce(){
  System.out.println("I'm daughter");
  }
}
public class TestPolymorphism{
public static void main(String args[]){
Parent p=new Parent();
p.Introduce();
System.out.println(p.value);
p=new Son();
p.Introduce();
System.out.println(p.value);
p=new Daughter();
p.Introduce();
System.out.println(p.value);
}
}

多态实现:

JAVA使用了后期绑定的概念。当向对象发送消息时,在编译阶段,编译器只保证被调用方法的存在,并对调用参数和返回类型进行检查,但是并不知道将被执行的确切代码,被调用的代码直到运行时才能确定。

将一个方法调用同一个方法主体关联起来被称作绑定,JAVA中分为前期绑定和后期绑定(动态绑定或运行时绑定),在程序执行之前进行绑定(由编译器和连接程序实现)叫做前期绑定,因为在编译阶段被调用方法的直接地址就已经存储在方法所属类的常量池中了,程序执行时直接调用,具体解释请看最后参考资料地址。后期绑定含义就是在程序运行时根据对象的类型进行绑定,想实现后期绑定,就必须具有某种机制,以便在运行时能判断对象的类型,从而找到对应的方法,简言之就是必须在对象中安置某种“类型信”,JAVA中除了static方法、final方法(private方法属于)之外,其他的方法都是后期绑定。后期绑定会涉及到JVM管理下的一个重要的数据结构——方法表,方法表以数组的形式记录当前类及其所有父类的可见方法字节码在内存中的直接地址。

  动态绑定具体的调用过程为:

    1.首先会找到被调用方法所属类的全限定名

    2.在此类的方法表中寻找被调用方法,如果找到,会将方法表中此方法的索引项记录到常量池中(这个过程叫常量池解析),如果没有,编译失败。

    3.根据具体实例化的对象找到方法区中此对象的方法表,再找到方法表中的被调用方法,最后通过直接地址找到字节码所在的内存空间。

9.多态含义和用途

让我们看一个开发场景:

某动物园有一饲养员小李,每天需要给他所负责饲养的狮子、猴子和鸽子喂食。

请用一个程序来模拟他喂食的过程。

①三种动物对应三个类,每个类定义一个eat()方法,表示吃饲养员给它们的食物。

再设计一个Feeder类代表饲养员,其name字段保存饲养员名字,三个方法分别代表喂养三种不同的动物,其参数分别引用三种动物对象。

public class Zoo
{
public static void main(String args[]){
Feeder f = new Feeder("小李");
// 饲养员小李喂养一只狮子
f.feedLion(new Lion());
// 饲养员小李喂养十只猴子
for (int i = 0; i < 10; i++){
f.feedMonkey(new Monkey());
}
// 饲养员小李喂养5只鸽子
for (int i = 0; i < 5; i++){
f.feedPigeon(new Pigeon());
}
}
}
class Feeder {
public String name;
public Feeder(String name){
this.name = name;
}
public void feedLion(Lion l){
l.eat();
}
public void feedPigeon(Pigeon p){
p.eat();
}
public void feedMonkey(Monkey m){
m.eat();
}
}
class Lion
{
public void eat() {
System.out.println("我不吃肉谁敢吃肉!");
}
}
class Monkey
{
public void eat() {
System.out.println("我什么都吃,尤其喜欢香蕉。");
}
}
class Pigeon
{
public void eat() {
System.out.println("我要减肥,所以每天只吃一点大米。");
}
}

这种编程方式有什么不合理的地方?

每次喂食都要创建一次类。重复步骤多。

①引入继承

定义一个抽象基类Animal,其中定义一个抽象方法eat(),三个子类实现这个抽象方法。

Feeder类的三个喂养方法现在可以合并为一个feedAnimal()方法,注意它接收一个类型为Animal参数,而不是三个具体的动物类型。

依据多态特性,此方法将可以接收任何一个派生自Animal类的子类对象

public class Zoo {
public static void main(String args[]){
Feeder f = new Feeder("小李");
//饲养员小李喂养一只狮子
f.feedAnimal(new Lion());
//饲养员小李喂养十只猴子
for (int i = 0; i < 10; i++) {
f.feedAnimal(new Monkey());
}
//饲养员小李喂养5只鸽子
for (int i = 0; i < 5; i++) {
f.feedAnimal(new Pigeon());
}
}
}
class Feeder {
public String name;
Feeder(String name) {
this.name = name;
}
public void feedAnimal(Animal an) {
an.eat();
}
}
abstract class Animal {
public abstract void eat();
}
class Lion extends Animal {
public void eat() {
System.out.println("我不吃肉谁敢吃肉!");
}
}
class Monkey extends Animal {
public void eat() {
System.out.println("我什么都吃,尤其喜欢香蕉。");
}
}
class Pigeon extends Animal {
public void eat() {
System.out.println("我要减肥,所以每天只吃一点大米。");
}
}

①进一步优化喂养一群动物

package zoo3;
public class Zoo
public static void main(String args[]) {
Feeder f = new Feeder("小李");
Animal[] ans = new Animal[16];
//饲养员小李喂养一只狮子
ans[0] = new Lion();
//饲养员小李喂养十只猴子
for (int i = 0; i < 10; i++) {
ans[1 + i] = new Monkey();
}
//饲养员小李喂养5只鸽子
for (int i = 0; i < 5; i++) {
ans[11 + i] = new Pigeon();
}
f.feedAnimals(ans);
}
}
class Feeder {
public String name;
Feeder(String name) {
this.name = name;
}
public void feedAnimals(Animal[] ans) {
for (Animal an : ans) {
an.eat();
}
}
}
abstract class Animal {
public abstract void eat();
}
class Lion extends Animal {
public void eat() {
System.out.println("我不吃肉谁敢吃肉!");
}
}
class Monkey extends Animal {
public void eat() {
System.out.println("我什么都吃,尤其喜欢香蕉。");
}
}
class Pigeon extends Animal {
public void eat() {
System.out.println("我要减肥,所以每天只吃一点大米。");
}
}

④第二次重构之后,Feeder类的feedAnimals()方法接收的是一个Animal数组,这有一个限制,就是只能创建固定个数的数组,无法动态地增减动物个数。

想想以下场景:

(1)动物园新进了一些动物

(2)某动物生病不幸死亡

(3)……

我们的代码能否应付以上的场景?

import java.util.Vector;
public class Zoo {
public static void main(String args[]) {
Feeder f = new Feeder("小李");
Vector<Animal> ans = new Vector<Animal>();
//饲养员小李喂养一只狮子
ans.add(new Lion());
//饲养员小李喂养十只猴子
for (int i = 0; i < 10; i++) {
ans.add(new Monkey());
}
//饲养员小李喂养5只鸽子
for (int i = 0; i < 5; i++) {
ans.add(new Pigeon());
}
f.feedAnimals(ans);
}
}
class Feeder {
public String name;
Feeder(String name) {
this.name = name;
}
//Vector<T>是JDK中提供的一个对象集合,可以随时向其中加入或移除对象
public void feedAnimals(Vector<Animal> ans) {
for (Animal an : ans) {
an.eat();
}
}
}
abstract class Animal {
public abstract void eat();
}
class Lion extends Animal {
public void eat() {
System.out.println("我不吃肉谁敢吃肉!");
}
}
class Monkey extends Animal {
public void eat() {
System.out.println("我什么都吃,尤其喜欢香蕉。");
}
}
class Pigeon extends Animal {
public void eat() {
System.out.println("我要减肥,所以每天只吃一点大米。");
}
}

总结:

多态编程有两种主要形式:

(1)继承多态:示例程序使用的方法

(2)接口多态:使用接口代替抽象基类。

使用多态最大的好处是:

当你要修改程序并扩充系统时,你需要修改的地方较少,对其它部分代码的影响较小!千万不要小看这两个“较”字!程序规模越大,其优势就越突出。

java课后实验性问题6的更多相关文章

  1. java课后实验性问题5

    课后作业一:字符串加密 程序设计思想: 从键盘获取字符串,将字符串转为字符数组,将每个元素加事前协定的“key”,再转为字符串输出. 程序流程图: 源代码: import java.util.Scan ...

  2. java课后实验性问题4

    课后作业一: 使用类的静态字段和构造函数,我们可以跟踪某个类所创建对象的个数.请写一个类,在任何时候都可以向它查询“你已经创建了多少个对象? 设计思路:定义类的构造函数时使静态变量i进行i++,即每构 ...

  3. java课后实验性问题2

    课后作业一:计算组合数 程序设计思想: 从键盘获取组合数,判断是否构成组合数.分别用三种方法计算组合数输出. 程序流程图: import java.util.Scanner; public class ...

  4. java课后实验性问题7

    1.异常处理 import javax.swing.*; class AboutException { public static void main(String[] a) { int i = 1, ...

  5. java课后实验性问题3

    一 .生成随机数 import java.util.*; public class Test1 { public static void main(String[] args) { //建立一个生产随 ...

  6. java课后实验性问题1

    一.一个java类文件中只能有一个公有类吗? 测试代码 public class Test{ public static void main(String[] args){ } public clas ...

  7. JAVA 数组作业——动手动脑以及课后实验性问题

    JAVA课后作业——动手动脑 一:阅读并运行示例PassArray.java,观察并分析程序输出的结果,小结,然后与下页幻灯片所讲的内容进行对照. 1.源代码 // PassArray.java // ...

  8. JAVA语法基础作业——动手动脑以及课后实验性问题 (八)

    一.动手动脑 运行AboutException.java示例,了解Java中实现异常处理的基础知识. 1)源代码 import javax.swing.*; class AboutException ...

  9. JAVA 多态和异常处理作业——动手动脑以及课后实验性问题

    1.  阅读以下代码(CatchWho.java),写出程序运行结果: 1)  源代码 public class CatchWho { public static void main(String[] ...

随机推荐

  1. 编译 SharpNav 遇到的问题和解决过程

    https://github.com/Robmaister/SharpNav 是github上基于recastnavtigation的一个C#项目. github上并没有详细的编译过程. 首先把项目c ...

  2. document对象详解

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD ...

  3. 一个SAP开发人员的养蚕流水帐

    Jerry打算以此文来给汪子熙全家进行了一个多月的养蚕经历画上一个圆满的句号. 南方长大的80后,对蚕应该不会太陌生.大家还记得你们小时候学过的课文<蚕姑娘>么?课文开头是这样的: 春天天 ...

  4. MySQL操作规范总结

    来源:静以致远√团团 用户权限管理创建用户命令:CREATE USER 'username'@'host' IDENTIFIED BY 'password';说明:Username所创建的用户名hos ...

  5. 【2018-01-26】SqlServer 检查死锁和阻塞

    利用sys.sysprocesses SQL进程检查是否出现死锁和阻塞 Sys.SysProcesses 系统表是一个很重要的系统视图,主要用来定位与解决Sql Server的阻塞和死锁 select ...

  6. SQL性能优化思路

    1. 尽可能把数据的存储和计算放入Memory而不是Disk,且减少IO操作,比如运用Redis等缓存技术 2. 对数据表进行精心设计,特别是大数据表,对常用数据字段进行适当的冗余,尽可能避免分表导致 ...

  7. python3 虚拟环境

    一.python中的虚拟环境 1.虚拟环境:局部的,独立的python环境,完全模拟系统全局python环境的使用 二.安装 http://virtualenv.pypa.io/en/latest/u ...

  8. Oracle12c-ADG搭建

    实验环境: 角色 IP hostname CDB name db_unique_name pdb name 版本 主 192.168.0.115 Node11 cdb1 cdb_p pdb1 12.2 ...

  9. js中当call或者apply传入的第一个参数是null/undefined时,js函数内执行的上下文环境是什么?

    在js中我们都知道call/apply,还有比较少用的bind;传入的第一个参数都是改变函数当前上下文对象; call/apply区别在于传的参数不同,一个是已逗号分隔字符串,一个以数组形式.而bin ...

  10. 51nod 1594 Gcd and Phi 反演

    OTZ 又被吊打了...我当初学的都去哪了??? 思路:反演套路? 提交:\(1\)次 题解: 求\(\sum_{i=1}^{n}\sum_{j=1}^{n}\varphi(gcd(\varphi(i ...