53
第4第 第第 第第第第第 4.1 第第第第第 4.2 第第第第第 4.3 第第第 4.4 第第 final 第第 4.5 第第第第第第第第 4.6 第第 4.7 第第第 4.8 第第第第第第

第 4 章 继承、多态与接口

  • Upload
    anoki

  • View
    101

  • Download
    0

Embed Size (px)

DESCRIPTION

第 4 章 继承、多态与接口. 4.1 继承的概念 4.2 访问控制符 4.3 多态性 4.4 理解 final 修饰 4.5 抽象类和抽象方法 4.6 接口 4.7 内嵌类 4.8 对象引用转换. 父类 或超类。实际上是所有子类的公共域和公共方法的集合 子类 ,父类的特殊化 , 是对公共域和方法在功能、内涵方面的扩展和延伸 ,祖先类的所有成员均将成为子类拥有的 “ 财富 ” Object 类 是所有类的祖先. 继承的概念. class Student { // 未使用继承 - PowerPoint PPT Presentation

Citation preview

第 4 章 继承、多态与接口

4.1 继承的概念 4.2 访问控制符 4.3 多态性4.4 理解 final修饰 4.5 抽象类和抽象方法 4.6 接口 4.7 内嵌类 4.8 对象引用转换 

继承的概念 • 父类或超类。实际上是所有子类的公共域和公共方法的集合• 子类,父类的特殊化 ,是对公共域和方法在功能、内涵方面的扩展和延伸 ,祖先类的所有成员均将成为子类拥有的“财富” • Object类是所有类的祖先 

Java继承的实现 class Student { //未使用继承   private String address; //籍贯   private String name; //姓名   private int age; //年龄   String no; //学号   public Student(String name1,int age1) {        name=name1;       age=age1;     } //其它… }

Java继承的实现class Student extends Person {    String no; //学号    //其它… }

只有 no 属性是新加入的,其它属性在 Person类中均存在

继承关系中构造方法的作用 ( 1) 当子类未定义构造方法时 ,创建对象时将无条件地调用父类的无参构造方法; ( 2) 对于父类的含参数构造方法 ,子类可以在自己构造方法中使用关键字 super来调用它 , 但 super调用语句必须是子类构造方法中的第一个可执行语句; ( 3) 子类在自己定义构造方法中如果没有用 super明确调用父类的构造方法,则在创建对象时 ,将自动先执行父类的无参构造方法 ,然后再执行自己定义的构造方法。 

以下程序在编译时将出错,为什么?class parent { String my; public parent(String x) { my=x; } } public class subclass extends parent { }

例 4-1 类的继承中构造方法的调用测试  class Person { // Person类 

private String address; //籍贯 private String name; //姓名 private int age; //年龄 public String getName() { //获取人名  return name; } public Person(String name1,String address1,int age1) { name=name1; address=address1; age=age1; } public Person() { name="无名氏 "; }

}  

例 4-1 类的继承中构造方法的调用测试public class Student extends Person { String no; //学号  public Student(String name1,String address1,int age1,String

no1) { super(name1,address1,age1); no=no1; } public static void main(String a[]) { Student x=new Student("张三 ","江西 ",25, "20012541"); System.out.println("name="+x.getName()); System.out.println("no="+x.no); //Student y= new Student(); 不能使用,子类无该构造方法 } }

子类中将隐藏父类的同名变量  class parent {

  int a=3;   int m=2; }public class subclass extends parent {  int a=4;    int b=1;   public static void main(String a[]) {    subclass my=new subclass();    System.out.println("a="+my.a+",b="+my.b+",m="+my.m);    }}

4.2.1 公共访问控制符  public

• 作为类的修饰符,将类声明为公共类 , 表明它可以被所有的其它类所访问和引用 • 作为类的成员的访问修饰符,表明在其他类中可以无限制地访问该成员。 • 要真正做到类成员可以在任何地方访问,在进行类设计时必须同时满足两点:首先类被定义为 public,其次,类的成员被定义

为 public。 

4.2.2 缺省访问控制符• 没有给出访问控制符情形 • 该类只能被同一个包中的类访问和引用 

4.2.3  私有访问控制符  private • 用 private修饰的域或方法只能被该类自身所访问例 4-3 测试对私有成员的访问 class Myclass { private int a; //私有变量  void display() { System.out.println(a); } }   public class test { public static void main(String arg[]) { Myclass my=new Myclass(); my.a=5; my.display(); } }

4.2.4  保护访问控制符  protected

用 protected修饰的成员可以在三种类中所引用 : • 该类本身; • 与它在同一个包中的其它类; • 在其它包中的该类的子类。 

例 4-4 测试包的访问控制的一个简单程序文件 1 : PackageData.java(该文件存放在 sub子目录下)  package sub;

public class PackageData {    protected static int number=1;}

  文件 2 : Mytest.java import sub.*;

public class Mytest {  public static void main( String args[] ) {   System.out.println("result="+PackageData.number);  }

}

各类访问控制符的作用可以归纳为表 3-1

可直接访问 修饰

同一类中 同一包中 不同包的子类中 其他 private Yes      

默认  Yes Yes    

protected Yes Yes Yes  

public Yes Yes Yes Yes

4.3.1 方法的重载 (例)public class A { protected void test(int x,int y) {     System.out.println("test(int,int):"+x+" "+y); } protected void test(int x) {

   System.out.println("test(int):" + x); } protected void test(String str ) {

   System.out.println("test(String):" + str);  }public static void main (String[] args) {

   A a1 = new A();    a1.test("hello");   a1.test(5,4);

} }

4.3.1 方法的重载 • 通过形式参数表的差异来区分 • 方法调用的匹配原则 :

1) 精确匹配2) 自动转换匹配基本类型转换对象引用转换

例 4-5 方法调用的匹配测试 public class A {

  protected int x = 0;   protected void test(int x) { System.out.println("test(int):" + x); }  protected void test(Object obj) {

      System.out.println("test(Object):" + obj );  }  protected void test(String str ) {

      System.out.println("test(String):" + str); } public static void main (String[] args) {    A a1 = new A();    a1.test("hello"); a1.test(5); } }

【思考】

• 如果在以上程序中加上如下方法 ,并将 test(int x)方法注释掉 ,则调用 test(5)如何 ? protected void test(long x) {

        System.out.println("test(long):" + x );    }

• 如果是以下情形 , 调用 test(6.3) ? protected void test(float x) {

        System.out.println("test(float):" + x ); }

4.3.2 方法的覆盖 • 以下类 B定义的方法中,方法覆盖如何?  class B extends A {

protected void test(int x) {    System.out.println("in B.test(int):" + x);  }

protected void test(String x , int y) {    System.out.println("in B.test(String,int):" + x+","+y);  }}

• 【思考】通过子类 B的对象可调用多少 test方法?

关于方法覆盖有以下问题值得注意:•   方法名、参数列表、返回类型完全相同才会产生方法覆盖; 如果返回类型不一样编译将报错。• 方法覆盖不能改变方法的静态与非静态属性。子类中不能将父类非静态方法定义为静态方法,反之也一样。 • 不允许子类中方法的访问修饰符比父类有更多的限制。例如:子类不能将父类的 public方法定义为

protected方法。但可以将父类的 private方法在子类中重新定义为 public方法 .• final方法不能被覆盖。

this ---出现在类的实例方法或构造方法中,用来代表使用该方法的对象 ( 1)把当前对象的引用作为参数传递给另一个方法。              如:  obj.f(this) ( 2)可以调用当前对象的其它方法或访问当前对象的实例变量。             如:  this.g(); ( 3)使用 this可以区分当前作用域中同名的不同变量。 

String x;    public Test(String x , int a) {     this.x=x;     }

( 4)一个构造方法中调用另一个构造方法。  public Test(final int x) { this(x,0); }

(1) 用 Super 访问超类的变量或方法class parent {

   int a=3;   void f()  {   a=a+1;    } }public class subclass extends parent {   int a=6;    void f() {     super.f();       a= a+super.a -3; }

( 2)调用超类的构造方法 public class graduate_student extends Student { Date enterDate; //入校时间  public graduate_student(String name,int age, Date d)    {      super(name,age); enterDate=d;    }

如果有 super(),必须放在构造方法的第一条语句

子类中调用方法的查找过程以及 this 和 super的用法 

【思考】如果 graduate_student 中无 toString() 方法,则 this.toString() 将会出现什么情况?

4.5 final修饰符 • final作为类修饰符 ----最终类 (不能有子类)• 用 final修饰方法  ----不能被子类重新定义 • 用 final定义常量  ----只能赋值一次      注意: 如果将引用类型的变量标记为 final,那么该变量固定指向一个对象,但可以改变对象内的属性值。 

例 4-7 常量赋值测试public final class test { public static int totalNumber=5; public final int id; public int weight; public test(int weight) { id=totalNumber++;

this.weight=weight; } public static void main(String args[]) { final test t=new test(5); t=new test(4); t.weight=t.weight+2; } }

4.6.1 抽象类的定义 abstract class 类名称  { 成员变量;      方法() {……} //一般方法  abstract 方法();   //抽象方法 }   • 在抽象类中可以包含一般方法和抽象方法。• 所有的抽象方法必须存在于抽象类中。 • 抽象类表示的是一个抽象概念,不能被实例化为对象。

4.6.2 抽象类的实现 abstract class Animal { //抽象类  String name; abstract public int getLeg(); //抽象方法 } class Dog extends Animal { int leg=4; public Dog(String n) { name=n; } public int getLeg() { return leg ; } }

4.6.2 抽象类的实现(续 1)class Fish extends Animal { public Fish(String n) { name=n; } public int getLeg() { return 0; } }  

4.6.2 抽象类的实现(续 2)public class test { public static void main(String args[]) { Animal a[]=new Animal[3]; a[0]=new Dog("dog-A "); a[1]=new Fish(“fish-A "); a[2]=new Dog("dog-B "); for (int i=0;i<3;i++) { System.out.println(a[i].name+"has "+

a[i].getLeg() +" legs"); } } }

4.7.1 接口定义 [public] interface 接口名  [extends 父接口名列表  ] { [public] [static] [final] 域类型 域名  = 常量值  ; [public] [abstract] [native] 返回值 方法名 (参数列

表 ) [throw 异常列表 ];}    • 声明接口可给出访问控制符; • 一个接口还可以继承多个父接口,父接口间用逗号分隔。 • 系统默认接口中所有属性的修饰都是  public static final; •   系统默认接口中所有方法的修饰都是  public abstract 。

4.7.1 接口定义举例interface Shape { void draw(); //用于绘制形状  double area(); //用于求面积 }

4.7.2 接口的实现 abstract public class Rectangle implements Shape { private double x,y,w,h; public Rectangle(double x,double y,double

w,double h) { this.x=x; this.y=y; this.w=w; this.h=h; } public double area() { return w*h; }   }

有关接口的实现 ,要注意以下问题•   一个类可以实现多个接口。接口间用逗号分隔; • 如果实现某接口的类不是抽象类 ,则在类的定义部分必须实现指定接口的所有抽象方法; • 一个类在实现某接口的抽象方法时 ,必须使用完全相同的方法头; • 接口的抽象方法的访问限制符默认为  public,在实现时要在方法头中显式地加上 public修饰。 

例 4-9 接口应用举例 interface StartStop { void start (); void stop (); }   class Conference implements StartStop { public void start () { System.out.println ("Start the conference."); } public void stop () { System.out.println ("Stop the conference."); } }

例 4-9 接口应用举例 (续 1)class Car implements StartStop { public void start () { System.out.println ("Insert key in ignition and

turn."); } public void stop () { System.out.println ("Turn key in ignition and

remove."); } }

例 4-9 接口应用举例 (续 2)public class TestInterface { public static void main (String [] args) { StartStop [] ss ={ new Car(), new Conference() }; for (int i = 0; i < ss.length; i++) { ss[i].start (); ss[i].stop (); } } }

二义性问题 interface Frob { float v = 2.0f; } //接口定义 class Parent { int v = 3; } //父类定义 class Test extends Parent implements Frob { public static void main(String[] args) { new Test().printV(); } void printV() { System.out.println((super.v + Frob.v)/2); } }

例 4-10 一个简单例子 public class OuterOne { private int x=3; InnerOne ino = new InnerOne(); public class InnerOne { //内嵌类  private int y=5; public void innerMethod() { System.out.println("y is "+y); } public void innerMethod2() { System.out.println("x2 is "+x); } }

创建内嵌类的对象作为外部类的一个属性成员

例 4-10 一个简单例子(续)public void OuterMethod() { System.out.println("x is "+x); ino.innerMethod(); ino.innerMethod2(); }   public static void main(String arg[]) { OuterOne my=new OuterOne(); my.OuterMethod(); } }

调用内嵌类的方法

有关说明• 内嵌类经过编译后产生的字节码文件名为:

OuterOne$InnerOne.class • 在内嵌类中可以访问外层类的成员 • 内嵌类可以使用访问控制符

public 、 protected 、 private修饰 

在外层类中访问内嵌类的方法 • 方法 1:在外层类的成员定义中创建内嵌类的对象,例如: InnerOne ino=new InnerOne(); 然后,在外层类中通过该成员变量 ino访问内嵌类的方法

• 方法 2:在程序的某个方法体中创建内嵌类的对象,然后通过该对象访问内嵌类的成员,如:  public void accessInner() { Innerone anInner=new Innerone(); anInner.innerMethod(); }

在 main方法中间接创建内嵌类的对象• 在 main方法中,要创建内嵌类的对象必须先创建外层类对象,然后通过外层类对象创建内嵌类对象。例如: public static void main(String arg[]) { OuterOne.InnerOne i = new OuterOne() . new

InnerOne(); i.innerMethod(); }

     在内嵌类中使用 this ---在内嵌类中, this指内嵌类的对象,要访问外层类的当前对象须加上外层类名作前缀 public class A { private int x=3; public class B { //内嵌类  private int x=5;

public void M(int x) { System.out.println("x ="+x);

System.out.println("this.x="+this.x); System.out.println("A.this.x="+A.this.x);

} } //内嵌类结束 

内嵌类对象

外部类的当前对象

例 4-12 静态内嵌类举例 public class Outertwo { private static int x=3; private int y=5;   public static class Innertwo { //静态内嵌类  public static void Method() { System.out.println("x is "+x); } public void Method2() { System.out.println("x is "+x); } } //内嵌类结束    public static void main(String arg[]) { Outertwo.Innertwo.Method(); new Outertwo.Innertwo().Method2(); } }

方法中的内嵌类 (例 4-13 )public class OuterTwo { private int x=3; public void OuterMethod( int m ) { final int n=x+2; class InnerTwo { //方法内的内嵌类  private int y=5; public void innerMethod() { System.out.println("y is "+y); System.out.println("n is "+n); System.out.println("x is "+x); } } //内嵌类结束 

只能访问外部方法中的常量(带 final修饰)

方法中的内嵌类(例 4-13续) InnerTwo in2=new InnerTwo(); in2.innerMethod(); }   public static void main(String arg[]) { OuterTwo my=new OuterTwo(); my.OuterMethod(8); } }

匿名内嵌类interface sample { void testMethod(); } public class AnonymousInner { void OuterMethod() { new sample() { public void testMethod( ) { System.out.println("just test"); } } .testMethod(); //调用内嵌类中定义的方法  }

---字节码文件为 AnonymousInner$1.class。如果有更多的匿名内嵌类将按递增序号命名

由接口派生匿名内嵌类,根据该内嵌类创建对象

 4.9.1 对象引用赋值转换 

Object x=new Apple(); Fruit m=new Orange();Apple x=new Fruit();

 4.9.1 对象引用赋值转换      在赋值时允许的转换归纳如下: • 接口类型可转换为父接口或 Object类; • 类对象可以转换为父类或该类实现了的接口类型; • 数组可以转换为 Object,也可转换为 Cloneable 或

Serializable接口。数组还可转换为一个新数组,但旧数组的元素类型必须能够允许转换为新类型方可。 

4.9.2 对象引用强制转换           将父类引用赋值给子类变量时要进行强制转换,这种强制转换在编译时总是认可的,但运行时的情况取决于对象的值 

Friut x; Apple y; Orange m , n; n=new Orange(); x=n; m=(Orange)x; y=(Apple)x;

作业1) 利用随机函数定义 10 对 (x , y)值,由此创建的

Point类实例存入一个数组中,按与原点( 0 ,0)的距离由小到大的顺序输出所有的点及到原点的距离。

2)编写一个三角形类,能根据 3个实数构造三角形对象,如果三个实数不满足三角形的条件,则自动构造以最小值为边的等边三角形。输入任意三个数,求构造的三角形面积。