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. 补充:Python安装

    需要安装Python2.7.Numpy和Matplotlib.由于Python不支持向下兼容,因此在Python3.×下你一定能正常运行Python2.×的代码.上述模块最简单的安装方法就是用软件包安 ...

  2. 日语单词N3_N4_N5

    单 词 讲 解 あ行单词 ああ:0[副]那样.那种 例句:ああ言うことはしないほうがいい.那样的事情最好不做. 電車の窓からごみを棄てているああ言うことはしないほうがいい. 挨拶(あいさつ):① 寒暄 ...

  3. log:日志处理模块

    为了更好的跟踪程序,我们通常都会使用日志,当然在golang中也提供了相应的模块. 基本使用 可以直接通过log来调用格式化输出的方法. package main import "log&q ...

  4. Auth2.0 例子【转载】

    本文转载自:https://www.cnblogs.com/flashsun/p/7424071.html 1.引言 本篇文章是介绍OAuth2.0中最经典最常用的一种授权模式:授权码模式 非常简单的 ...

  5. ThreadLocal 是什么?(未完成)有哪些使用场景?(未完成)

    ThreadLocal 是什么?(未完成)有哪些使用场景?(未完成)

  6. .gitignore文件不起作用,怎么处理?

    遇到这么个场景,项目之前没有.gitignore文件,新建的.gitignore文件中已经标明忽略的文件目录下的文件,但是git push上去忽略的文件还是在push的目录中.查阅资料了解到 在git ...

  7. MySQL 进阶3 排序查询

    #进阶3 排序查询 格式: select 查询列名 from 表 [where 筛选条件] order by 排序列名 [asc / desc] 排序查询/嵌套排序查询/函数查询/[按别名进行 排序] ...

  8. linux实操_shell运算符

    1."$((运算式))"或"[运算式]" 2.expr m + n 注意:expr运算符要有空格 3.expr m - n 4.expr \*,/,/% 乘,除 ...

  9. Linux下恢复误删除的文件

    原文地址:http://www.libenfu.com/vim-分区下误删的文件,恢复文件全记录-转 当时我的工作目录是/source/needrecovered. $ pwd /source/nee ...

  10. Windows启动报错:无效的分区表 解决方法,哈哈

    Windows系统启动时出现Invalid Partition Table错误 2018-07-27 16:32 今天KB小网管跟大家分享一个在重装Windows系统时会出现的一个问题Invalid ...