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. ConfigParser读取配置文件时报错:ConfigParser.MissingSectionHeaderError

    使用ConfigParser来读取配置文件,经常会发现经过记事本.notepad++修改后的配置文件读取时出现下面的问题: ConfigParser.MissingSectionHeaderError ...

  2. Shell学习笔记:awk实现group by分组统计功能

    日常部分数据以 txt 的文件格式提供,为避免入库之后再进行统计的麻烦,故学习 shell 进行处理,减少工作量. 1.样例数据 # test.txt YD5Gxxx|6618151|68254490 ...

  3. 如何用HAProxy+Nginx实现负载均衡

    一.什么是HAProxy HAProxy提供高可用性.负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费.快速并且可靠的一种解决方案.HAProxy特别适用于那些负载特大的web站点, ...

  4. Java基础加强-泛型

    /*泛型*/ (泛型是给编译器看的) 泛型是提供给 /*javac编译器使用的*/,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型带类型说明的集合时,会去掉 "类 ...

  5. xshell生成公钥和私钥

    一.打开你的xshell工具,工具栏有一个工具选项,点开选择新建用户密钥生成向导(如下图所示) 二. 点开之后就会如上图所示一样,点击选择下一步,出现如下,再点击下一步 点击完下一步会出现如下图所示 ...

  6. LVM——header

  7. mysql 创建用户并授权数据库

    create user test identified by ‘password’:password  你要创建的用户对应的密码 grant all on database.*  to  test;  ...

  8. java之rpc/orm

    Netty线程模型 其中ChannelPiepline的设计模型采用的是Handler组成的责任链模型 blocking I/O 阻塞nonblocking I/O 非阻塞I/O multiplexi ...

  9. JS遍历表格获取每行数据及每个单元格数据

    /** * 遍历表格获取每行数据及每个单元格数据 * @param tableID 表格ID */ function GetTable(tableID) { var milasUrl = {};//新 ...

  10. P1772 [ZJOI2006]物流运输 最短路+DP

    思路:最短路+DP 提交:1次 题解: $f[i]$表示到第$i$天的最小代价,我们可以预先处理出$i,j$两天之间(包括$i,j$)都可通行的最短路的代价记做$s[i][j]$,然后有$f[i]=m ...