一,继承
动手实验_1:
题目:运行 TestInherits.java 示例,观察输出,注意总结父类与子类之间构造方法的调用关系修改Parent构造方法的代码,显式调用GrandParent的另一个构造函数,注意这句调用代码是否是第一句,影响重大!
TestInherits.java:
class Grandparent {
public Grandparent(){
System.out.println("GrandParent Created.");
}
public Grandparent(String string) {
System.out.println("GrandParent Created.String:" + string);
}
}
class Parent2 extends Grandparent{
public Parent2(){
super("Hello.Grandparent.");
System.out.println("Parent Created");
// super("Hello.Grandparent.");
}
}
class Child2 extends Parent2 {
public Child2() {
System.out.println("Child Created");
}
}
public class TestInherits {
public static void main(String args[]) {
Child2 c = new Child2();
}
}
结论:
1,子类的构造方法在运行之前,必须调用父类的构造方法。
2,通过 super 调用基类构造方法,必须是子类构造方法中的第一个语句。
(注:super的用法:(1)调用父类的构造方法(2)操作被隐藏的成员变量和被覆盖的成员方法)
思索:为什么子类的构造方法在运行之前,必须调用父类的构造方法?能不能反过来?为什么不能反过来?
答:
构造函数(constructor)是一种特殊的方法 。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中 。构造一个对象,先调用其构造方法,来初始化其成员函数和成员变量。子类拥有父的成员变量和成员方法,如果不调用,则从父类继承而来的成员变量和成员方法得不到正确的初始化。不能反过来调用也是这个原因,因为父类根本不知道子类有神魔变量而且这样一来子类也得不到初始化的父类变量,导致程序运行出错!
2,探索技术的奥秘:参看ExplorationJDKSource.java示例并运行得到了一个奇特的运行结果: A@1c5f743版)为什么?
曲折的探索之路:
(1)使用javap –c命令反汇编ExplorationJDKSource.class;
(2)阅读字节码指令,弄明白println()那条语句到底调用了什么?
前面示例中,main方法实际上调用的是: public void println(Object x),这一方法内部调用了String类的valueOf方法。
valueOf方法内部又调用Object.toString方法:
public String toString(){
return getClass().getName() +"@" + Integer.toHexString(hashCode());
}
hashCode方法是本地方法,由JVM设计者实现: public native int hashCode();
3,神奇的“+”号
Fruit.java
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());
}
}
结论:
1,首先,Fruit类覆盖了Object类的toString方法。
2,在“+”运算中,当任何一个对象与一个String对象,连接时,会隐式地调用其toString()方法,默认情况下,此方法返回“类名 @ + hashCode”。为了返回有意义的信息,子类可以重写toString()方法。
4,请自行编写代码测试以下特性(动手动脑):
在子类中,若要调用父类中被覆盖的方法,可以使用super关键字。
Test.java
class fu{
void fun() {
System.out.println("父类——fun()");
}
}
class zi extends fu{
void fun() {
super.fun();
}
}
public class Test1 {
public static void main(String[] args) {
zi t1=new zi();
t1.fun();
}
}
二,多态
1,在实践中理解把握复杂的知识-1
ParentChildTest.java
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();
}
}
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);
}
}
问题:
1. 上边的程序运行结果是什么?
2. 你如何解释会得到这样的输出?
前两行正常,
第三行,当子类与父类拥有一样的方法,并且让一个父类变量引用一个子类对象时,到底调用哪个方法,由对象自己的“真实”类型所决定,
第四行,parent=child;仅仅是将parent中有的值用child的值代替,所以parent.myValue++;parent只有一个自己的myValue值,而输出的是child的myValue。
第五行,强制类型转换,++作用在child的myValue,输出的也是child的myValue
3. 计算机是不会出错的,之所以得 到这样的运行结果也是有原因的, 那么从这些运行结果中,你能总 结出Java的哪些语法特性?
总结:
1,当子类与父类拥有一样的方法,并且让一个父类变量引用一个子类对象时,到底调用哪个方法,由对象自己的“真实”类型所决定,这就是说:对象是子类型的,它就调用子类型的方法,是父类型的,它就调用父类型的方法。
2,这个特性实际上就是面向对象“多态”特性的具体表现。
3,如果子类与父类有相同的字段,则子类中的字段会代替或隐藏父类的字段,子类方法中访问的是子类中的字段(而不是父类中的字段)。如果子类方法确实想访问父类中被隐藏的同名字段,可以用super关键字来访问它。
4,如果子类被当作父类使用,则通过子类访问的字段是父类的!
多态小结:
1,)从饲养员喂食的示例中可以看到,通过在编程中应用多态,可以使我们的代码具有更强的适用性。当需求变化时,多态特性可以帮助我们将需要改动的地方减少到最低限度。 多态编程有两种主要形式:
(1)继承多态:饲养员喂食的示例程序使用的方法
(2)接口多态:使用接口代替抽象基类,这个任务留为作业。
zoo5.zoo.java
package zoo5;
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;
}
public void feedAnimals(Vector<Animal> ans) {
for (Animal an : ans) {
an.eat();
}
}
}
interface Animal {//动物基类
public abstract void eat();
}
class Lion implements Animal {//狮子类通过继承接口的方式,从而来继承接口的抽象方法。
public void eat() {
System.out.println("我不吃肉谁敢吃肉!");
}
}
class Monkey implements Animal {//猴子类通过继承接口的方式,从而来继承接口的抽象方法。
public void eat() {
System.out.println("我什么都吃,尤其喜欢香蕉。");
}
}
class Pigeon implements Animal {//鸽子类通过继承接口的方式,从而来继承接口的抽象方法。
public void eat() {
System.out.println("我要减肥,所以每天只吃一点大米。");
}
}
(注:在此链接上学的接口相关知识:https://blog.csdn.net/margin_0px/article/details/82979376)
2,)为什么要用多态?它有什么好处?
使用多态最大的好处是: 当你要修改程序并扩充系统时,你需要修改的地方较少,对其它部分代码的影响较小!千万不要小看这两个“较”字!程序规模越大,其优势就越突出。
三,抽象
1,先来看一个现实生活中的实例: 鸭子是一种鸟,会游泳, 同时又是一种食物。 如何使用面向对象的思想,为上述场景建立一个 系统模型(建模)? 什么叫“建立一个系统模型”? 简单地说,就是”你打算设计哪些类来“仿真” 鸭子这一现实事物?
1,)分析:因为“鸭子是一种(IS_A)鸟,又是一种(IS_A)食物”,所以我们可以创建三个类,Bird代表鸟,Food代表食物,Duck代表鸭子,让Duck派生自Bird和Food。
这个方案存在的问题: (1)Java不支持多继承 (2)“会游泳”这个方法放在哪个类中?放到Bird中似乎并不合适,因为只有部分鸟类会游泳。放到Duck中也不好,因为会游泳的水鸟不止鸭子一种
2.)
能否把“会游泳”、“能被吃”这种特性独立出来作为一种“可选项”,可以被“附加”到具体对象上? 这样一来,水鸟可以拥有“会游泳”这个特性,其它种类的鸟就不具备这个特性,但它可能有其他的特性。 在面向对象世界中,可以使用“接口(interface)”来抽象对象的行为特性。
设置两个接口:zuoyie.Dock.java:
package zuoyie;
interface Bird {
public abstract void swing();
}
interface IFood {
public abstract void Cook();
}
public class Dock implements Bird, IFood {
public void swing() {
System.out.println("我是鸭子我会游泳!");
}
public void Cook() {
System.out.println("我是鸭子我是一种食物!");
}
public static void main(String[] args) {
Dock dock = new Dock();
dock.swing();
dock.Cook();
}
}
2,Java中“接口”的语法特性:
1)定义一个接口,采用关键字interface,实现一个接口,采用关键字implements
2)接口的成员函数自动成为public的,数据成员自动成为 static和final的。
3)如果接口不声明为public的,则自动变为package。
4)一个类可以同时实现多个接口。
3,接口的使用:IFood f = new Duck(); 接口类型 接口类型的变量=new 实现了接口的具体类型();,
4,接口的扩充
1)可以通过继承接口来扩充已有接口,并形成一个新的接口。
interface OneInterface
{ void f1(); }
interface TwoInterface extends OneInterface { void f2(); }
2)实现子接口的类,必须实现“父”“子”接口所定义的所有方法,才能被实例化(即new出一个对象)。
5,
利用接口定义常量
public interface ArrayBound{
public static final int LOWBOUND=1;
public static final int UPBOUND=100; }
1)只要一个类声明实现了这个接口,就可以直接使用这些常量名
2)在实际开发中,这种编程方式非常常见。
3)注意:定义在接口中的常量必须被初始化。
6)接口与抽象类的区别
1)抽象类是一个不完全的类,而接口只是表明类应该具有哪些“外部”特征,不涉及任何实现细节。
2)接口基本上不具备继承的任何具体特点,它仅仅承诺了外界能够调用的方法。
3)一个类一次可以实现若干个接口,但一个类只能继承一个父类。