第七章总结

7.1 类的继承

例题7.1:

 
class Paren {        //定义一个父类方法
	public Paren() {
		System.out.println("调用父类构造方法");
	}
}
class Child extends Paren{    //定义一个子类方法继承父类
	public Child() {
		System.out.println("调用子类构造方法");
	}
}
 
public class liti7_1 {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new Child();
	}
 
}


继承在面向对象开发思想中是一个非常重要的概念,它使整个程序架构具有一定的弹性,在程序中复用已经定义完善的类不仅可以减少软件开发周期,还可以提高软件的可维护性和可扩展性。

例题1 创建子类对象,观察构造方法执行顺序

父类Parent和子类Child都各自有一个无参的构造方法,在main()方法中创建子类中创建子类对象时,Java虚拟机会先执行父类的构造方法,然后再执行子类的构造方法。

一个类继承另一个类,用extends关键字,语法如下:

class Child extends Parent { }

java只支持单继承,即一个类只能有一个父类

父类Parent:

public class Parent {
	public Parent (){
 
	System.out.println("调用Parent类构造方法");
}
}

子类Child:

 class Child extends Parent{
	public Child() {
		System.out.println("调用Child类构造方法");
	}
}

第七章总结_第1张图片

 例题2 在电话类基础上衍生出手机类

Telephone电话类作为父类衍生出Mobile手机类,手机类可以直接使用电话类的按键属性和拨打电话行为。

父类Telephone:

public class Telephone {					//电话类
	String button = "button:0~9";			//成员属性,10个按键
	void call() {							//拨打电话
		System.out.println("开始拨打电话");
	}
}

子类Mobile:

 class Mobile extends Telephone {			//手机类继承电话类
	 String screen ="screen:液晶屏";			//成员属性,液晶屏幕
}

创建一个Demo2类:

public class Demo2{
	 public static void main(String[]arge) {
		 Mobile motto = new Mobile();
		 System.out.println(motto.button);		//子类调用父类属性
		 System.out.println(motto.screen);		//子类调用父类没有的属性
		 motto.call();							//子类调用父类方法
	 }
}

第七章总结_第2张图片

 7.2 Object类

学习使用class关键字定义类时 ,就应用了继承原理 ,因为在Java中,所有的类都直接或间接继承了java.lang.Object类。Object类是比较特殊的类,它是所有类的父类,是Java类层中的最高层类 。当创建一个类时 ,总是在继承 ,除非某个类已经指定要从其他类继承 ,否则它就是从java.lang.Object 类继承而来的 ,可见Java中的每个类都源于 java.lang.Object类 ,如 String  、Integer等类都是继承于添加字群 Object类;除此之外自定义的类也都继承于Object类。\n\n 由于所有类都是Object子类,所以在定义类时,省略了extends Object关键字,如下图所示便描述了这一原则:

第七章总结_第3张图片

在Object类中主要包括clone()、finalize()、equals()、toString()等方法,其中常用的两个方法为equals()和toString()方法。由于所有的类都是Object 类的子类,所以任何类都可以重写Object类中的方法。

注意:Object类中的getClass()、notify()、notifyAll()、wait()等方法不能被重写,因为这些方法被定义为final类型。 

7.2.1 getClass()方法

getClass()方法是Object类定义的方法,它会返回对象执行时的Class实例,然后使用此实例调用getName()方法可以取得类的名称。

语法如下:

getClass().getName ();

可以将getClass)方法与toString)方法联合使用。

7.2.2 toString()方法

toString()方法的功能是将一个对象返回为字符串形式,它会返回一个String实例。在实际的应用中通常重写toString()方法,为对象提供一个特定的输出模式。当这个类转换为字符串或与字符串连接时,将自动调用重写的toString()方法。

例题 让学生自我介绍

创建Student类,重写toString()方法,使用该类的对象可以自定义输出自己的姓名和年龄。

代码如下:

public class Student {						
	String name;
	int age;
	
	public Student(String name,int age) {
		this.name =name;
		this.age = age;
		
	}
	public String toString() {
		return "我叫"+name+",今年"+age+"岁。";
	}
	public static void main(String[] args) {
		Student s1 =new Student("张三",16);
		System.out.println(s1);
		Student s2 =new Student("李四",19);
		System.out.println(s2);
		
	}
}


第七章总结_第4张图片

7.2.3 equals方法

equals方法是比较“=”运算符与equals)方法,说明“=”比较的是两个对象的引用是否相等,而equals方法比较的是两个对象的实际内容。带着这样一个理论来看下面的实例。

例题 根据身份证号判断是否为同一人
为People类创建身份证号和姓名两个属性,重写equals方法,仅以身份证号作为区分条件。创建
n个People对象,用equalsO方法和“==”运算符来判断是否存在多个对象代表同一个人。

代码如下:
 

public class People {
	int id;													 //身份证号
	 String name; 											//名字
	 public People(int id, String name) {
	  this.id = id;
	        this.name = name;
	    }
	  public boolean equals(Object obj) {					//重写Object类的equals()方法
	   if(this==obj)										//如果参数与本类是同一个对象
	    return true;
	   if(obj==null)										//如果参数是null
	    return false;
	   if(getClass()!=obj.getClass())						//如果参数与本类类型不同
	    return false;
	   People other = (People)obj;							//将参数强转成本类对象
	   if(id!=other.id)										//如果俩者的身份证号不相等
	    return false;
	   return true;
	  }
	  public String toString() {							//重写Object类toString()方法
	   return name;											//只输出名字
	  }
	  public static void main(String[]args) {
	   People p1 = new People(220,"tom");
	      People p2 = new People(220,"汤姆");
	      People p3 = new People(330,"张三");
	   Object o=new Object();
	   System.out.println(p1+"与"+p2+"是否为同一人?"); 
	         System.out.println("equals()方法结果:"+p1.equals(p2)); 
	         System.out.println("==运算符的结果:"+(p1==p2));
 
	         System.out.println(); 
	         System.out.println(p1+"与"+p3+"是否为同一人?"); 
	         
	         System.out.println(p1.equals(p3));
	         System.out.print(p1+"与"+o+"是否为同一人?");
	         System.out.println(p1.equals(0));
	     }
	  
	 }

第七章总结_第5张图片

从这个结果可以看出,“tom”和“汤姆”虽然名字不同,但是两者的身份证号相同,所以equals)
方法判断出了这两个对象实际上是同一个,而“=”运算符无法做出有效判断。如果两个对象的身份
证号不同,或者两个对象类型都不同,equals()方法就会认为两者不是同一个人。 

7.3 对象类型的转换

7.3.1 向上转型

向上转型可以理解为将子类类型的对象转换为父类类型的对象,即把子类类型的对象直接赋值给父类类型的对象,进而实现按照父类描述子类的效果。

7.3.2 向下转型

向下转型可以被理解为将父类类型的对象转换为子类类型的对象。但是,运用向下转型,如果一个较抽象的类的对象转换为一个较具体的类的对象,这样的转型通常会出现错误。例如,可以说某只鸽子是一只鸟,却不能说某只鸟是一只鸽子。因为鸽子是具体的,鸟是抽象的。一只鸟除了可能是鸽子,还有可能是老鹰、麻雀等。因此,向下转型是不安全的。

例题 谁是鸽子?

编写代码证明“可以说某只鸽子是一只鸟,却不能说某只鸟是一只鸽子”:鸟类是鸽子类的父类,用Bird表示鸟类,用Pigeon表示鸽子类。

 class Bird {}
class Pigeon extends Bird{}
public class Demo4{
	public static void main(String[]arge) {
		Bird bird = new Pigeon();			//某只鸽子是一只鸟	向上转型
		Pigeon pigeon = bird;				//某只鸟是一只鸽子	向下转型
	}
 
}

本例在运行之前,Eclipse会报出如下图所示的编译错误,这是因为父类对象不能直接眼值给子类对象

第七章总结_第6张图片

如果想要告诉编译器“某只鸟就是一只鸽子”,应该如何修正?答案就是强制类型转换。也就是说,要想实现向下转型,需要借助强制类型转换。语法如下:

子类类型子类对象=(子类类型)父类对象; 

因此,要想实现把鸟类对象转换为鸽子类对象(相当于告诉编译器“某只鸟就是一只鹊子”,用要将图中第8行代码修改为:
 

Pigeon pigeon = (Pigeon) bird; //通过强制类型转换,告诉编译器“某只鸟就是一只鸽子”

注意:

(1)两个没有继承关系的对象不可以进行向上转型或者向下转型。
(2)父类对象可以强制转换为子类对象,但有一个前提条件:这个父类对象要引用这个子类对象

7.4 使用instanceof关键字判断对象类型
当在程序中执行向下转型操作时 ,如果父类对象不是子类对象的实例  ,就会发生ClassCastException异常  ,所以在执行向下转型之前需要养成一个良好的习惯,就是判断父类对象是否为子类对象的实例。这个判断通常使用instanceof操作符来完成。可以使用instanceof操作符判断是否一个类实现了某个接口,也可以用它来判断一个实例对象是否属于一个类。

instanceof的语法格式如下:
 

myobject instanceof ExampleClass

myobject: 某类的对象引用

ExampleClass: 某个类

使用 instanceof操作符的表达式返回值为布尔值。如果返回值为true,说明 myobject 对象为ExampleClass的实例对象;如果返回值为false,说明myobject对象不是ExampleClass的实例对象。

注意:

instanceof是Java语言的关键字,在Java语言中的关键字都为小写。

下面来看一个向下转型与instanceof关键字结合的例子。
例题 分析几何图形之间的继承关系
创建Quadrangle四边形类、Square 正方形类和Circular圆类。其中,Square类继承Quadrangle类,在主方法中分别创建这些类的对象,然后使用instanceof关键字判断它们的类型并输出结果。


Quadrangle四边形类代码:

 class Quadrangle {
}

Square 正方形类代码:

class Square extends Quadrangle {
}

Circular圆形类代码:

class Circular {
}

创建一个Demo5类:

public class Demo5 {
	public static void main(String args[]) {
	Quadrangle q = new Quadrangle(); 				//四边形对象
	Square s =new Square(); 						//正方形对象
	System.out.println(q instanceof Square);		//判断四边形是否为正方形的子类
	System.out.println(s instanceof Quadrangle); 	//判断正方形是否为四边形的子类
	System.out.println(q instanceof Circular); 		//判断正方形是否为圆形的子类
}
}

本实例在运行之前,Eclipse就会报出如下图:

因为四边形类与圆形类没有继承关系,因此两者不能使用instanceof关键字进行比较,否则会发生
“不兼容”错误。如果删除或注释掉这行代码,运行结果如下:

 

7.5 方法的重载


构造方法的名称由类名决定,构造方法只有一个名称,但如果希望以不同的方式来实例化对象,就需要使用多个构造方法来完成。由于这些构造方法都需要根据类名进行命名,为了让方法名相同而形参不同的构造方法同时存在,必须用到“方法重载”。虽然方法重载起源于构造方法,但是它也可以应用到其他方法中。方法的重载就是在同一个类中允许同时存在一个以上的同名方法,只要这些方法的参数个数或类型不同即可。

例题 编写不同形式的加法运算方法

在项目中创建OverLoadTest类,在类中编写add()方法的多个重载形式,然后在主方法中分别输出这些方法的返回值。

代码如下:

public class OverLoadTest {
	public static int add (int a, int b) {				//定义一个方法
		return a+b;
	}
	public static double add(double a,double b) {		//与第一个方法名称相同、参数类型不同
		return a+b;
	}
	public static int add(int a) {						//与第一个方法参数个数不同
		return a;
	}
	public static int add (int a,double b) {			//先int参数,后double参数
		return a;										//输出int参数值
	}
	public static int add(double a,int b) {				//先double参数,后int参数
		return b;										//输出int参数
	}
	public static void main(String args[]) {
		System.out.println("调用add(int,int)方法:"+add(1,2));
		System.out.println("调用add(double,double)方法:"+add(2.1,3.3));
		System.out.println("调用add(int)方法:"+add(1));
		System.out.println("调用add(int,double)方法:"+add(5,8.0));
		System.out.println("调用add(double,int)方法:"+add(5.0,8));
		/*
		 * 
		 * 输出结果
		 * 
		 */
	}
}

第七章总结_第7张图片

        在本实例中分别定义了6个方法,在这6个方法中,前两个方法的参数个数不同,所以构成了重载关系;前两个方法与第3个方法比较时,方法的参数类型不同,并且方法的返回值类型也不同,所以这3个方法也构成了重载关系;比较第4、第5两个方法时,发现除了参数出现的顺序不同之外,其他都相同,这样同样可以根据这个区别将两个方法构成重载关系;而最后一个使用不定长参数的方法,实质上与参数数量不同是一个概念,也构成了重载。 

7.6 final 关键字


7.6.1 final 变量

final关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值。通常,由final定义的变量为常量。例如,在类中定义PI值,可以使用如下语句:

final double PI=3.14;

当在程序中使用PI这个常量时,它的值就是3.14,如果在程序中再次对定义为final的常量赋值,编译器将不会接受。

 final关键字定义的变量必须在声明时对其进行赋值操作。final除了可以修饰基本数据类型的常量,还可以修饰对象引用。由于数组也可以被看作一个对象来引用,所以final可以修饰数组。一旦一个对象引用被修饰为final后,它只能恒定指向一个对象,无法将其改变以指向另一个对象。一个既是static又是final的字段只占据一段不能改变的存储空间。

7.6.2 final 方法

定义为fǐnal的方法不能被重写。

将方法定义为fǐnal类型可以防止子类修改该类的定义与实现方式,同时定义final的方法的执行效率要高于非final方法。在修饰权限中曾经提到过private修饰符,如果一个父类的某个方法被设置为private修饰符,子类将无法访问该方法,自然无法覆盖该方法,所以一个定义为private的方法隐式被指定为fǐnal类型,这样无需将一个定义为private的方法再定义为final类型。例如下面的语句:

private final void test() {
    …//省略一些程序代码
} 

例题  使用final关键字为电视机上儿童锁
创建Dad爸爸类,给Dad类定义一个打开电视机的方法,该方法使用final关键字修饰。创建Dad
类的子类Baby类,让Baby类尝试自己重写打开电视的方法。

父类Dad代码:

class Dad {
	public final void turnOnTheTV() {
		System.out.println("爸爸打开了电视");
		
	}
}

子类Baby代码:

class Baby extends Dad{
	 public final void turnOnTheTV() {
		 System.out.println("宝宝也要打开电视");
	 }
}

本实例在运行之前,Eclipse就会报出如下图所示的编译错误。因为打开电视这个方法是由final修饰的,子类无法重写。所以Baby 想要打开电视,就只能找爸爸来打开了。

第七章总结_第8张图片

7.6.3 final 类 

定义为fǐnal的类不能被继承。如果希望一个类不允许任何类继承,并且不允许其他人对这个类进行任何改动,可以将这个类设置为fǐnal形式。fǐnal类的语法如下:

final class类名()

如果将某个类设置为final形式,则类中的所有方法都被隐式地设置为final形式,但是final类中的成员变量可以被定义为final或非final形式。

7.7 多态

利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理

假如现在要编写一个绘制图形的方法 draw(),如果传入正方形对象就绘制正方形,如果传入圆形对象就绘制圆形,这种场景可以使用重载来实现,定义如下:

public void draw(Square s){            //绘制正方形的方法
}
public void draw(Circular c){          //绘制圆形的方法
}

多态意为一个名字可具有多种语义,在程序设计语言中,多态性是指“一种定义,多种实现”,例如,运算符“+”作用于两个整型量时是求和,而作用于两个字符型量时则是将其连接在一起。利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。类的多态性可以从两方面体现:一是方法的重载,二是类的上下转型。

例题 万能的绘图方法

创建Shape图形类,作为Square正方形类和Circular圆形类的父类。创建Demo6类,并在该类中
创建一个绘图用的draw)方法,参数为Shape类型,任何Shape类的子类对象都可以作为方法的参数,并且方法会根据参数的类型绘制相应的图形。

父类代码 图形类:
 

	class Shape {		//图形类
 
}

Demo6类:

class Circular extends Shape {}								//圆形类继承图形类
 
class Square extends Shape {}								//正方形类继承图形类
 
public class Demo6 {
	public static void draw(Shape s) {				//绘制方法
		if(s instanceof Square) {					//如果是正方法
			System.out.println("绘制正方形");
		} else if(s instanceof Circular){			//如果是圆形
			System.out.println("绘制圆形");
		} else {									//如果是其他类型
			System.out.println("绘制父类图形");
		}
	}
	public static void main(String[] args) {
		draw (new Shape());
		draw (new Square());
		draw (new Circular());
	}
}

从本实例的运行结果中可以看出,以不同类对象为参数调用draw()方法,可以处理不同的图形绘制问题。使用多态节省了开发和维护时间,因为程序员无须在所有的子类中定义执行相同功能的方法,避免了大量重复代码的编写。同时,只要实例化一个继承父类的子类对象,即可调用相应的方法,如果需求发生了变更,只需要维护一个draw()方法即可。 

7.8 抽象类与接口

通常可以说四边形具有4条边,或者更具体一点,平行四边形是具有对边平行且相等特性的特殊四边形,等腰三角形是其中两条边相等的三角形,这些描述都是合乎情理的,但对于图形对象却不能使用具体的语言进行描述,它有几条边,究竟是什么图形,没有人能说清楚,这种类在Java中被定义为抽象类。

7.8.1 抽象类


如鸽子类继承鸟类、鸟类继承动物类等。在多态机制中,并不需要将父类初始化对象,需要的只是子类对象,所以在Java语言中设置抽象类不可以实例化对象,因为图形类不能抽象出任何一种具体图形,但它的子类却可以。

Java中定义抽象类时,需要使用abstract关键字,其语法如下:

[权限修饰符] abstract class 类名 {  类体     }

使用abstract关键字定义的类称为抽象类,而使用abstract关键字定义的方法称为抽象方法。

抽象方法的定义语法如下:

[权限修饰符] abstract   返回参数 方法名 (传入参数);

 从上面的语法可以看出,抽象方法是直接以分号结尾的,它没有方法体,抽象方法本身没有任何意义,除非它被重写,而承载这个抽象方法的抽象类必须被继承,实际上,抽象类除了被继承之外没有任何意义。下图中说明了抽象关系:
第七章总结_第9张图片

 从图中可以看出,继承抽象类的所有子类都需要将抽象类中的抽象方法进行覆盖,这样在多态机制中,就可以将父类修改为抽象类,将draw)方法设置为抽象方法,然后每个子类都重写这个方法来处理。

7.8.2 接口

接口是抽象类的延伸,可以将它看作是纯粹的抽象类,接口中的所有方法都没有方法体。可以将draw)方法封装到一个接口中,这样可以让一个类既能继承图形类,又能实现draw)方法接口,这就是接口存在的必要性。在下图中描述了各个子类继承图形类后使用接口的关系。

 

第七章总结_第10张图片

接口使用interface关键字进行定义,其语法如下:

public interface Paintable{
    void draw();            //定义接口方法可省略 public abstract 关键字
}

 * public:接口可以像类一样被权限修饰符修饰,但public关键字仅限用于接口在与其同名的文件中被定义。
* interface:定义接口关键字。
* Paintable:接口名称。
一个类继承一个父类的同时再实现一个接口,可以写成如下形式:

public class Parallelogram extends Quadrangle implements Paintable {

……

}

注意
(1)在接口中,方法必须被定义为public或abstract形式,其他修饰权限不被Java编译器认可。或者说,即使不将该方法声明为public形式,它也是public形式。
(2)在接口中定义的任何字段都自动是static和final的。


例题 将绘图方法设为接口方法
将图形对象的绘图方法剥离出来,作为Paintable可绘制接口中的抽象方法。创建四边形类作为平行四边形类和正方形类的父类,同时让这两个子类实现Paimable接口。创建圆形类实现Paintable接口,但不继承四边形类。

代码:

package 复习;
 
interface Paintable {  //可绘制接口
 public void draw(); //绘制抽象方法
 }
 
class Quadrangle { //四边形类
 public void doAnything() {
 System.out.println("四边形提供的方法");
 }
 }
 //平行四边形类,继承四边形类,并实现了可绘制接口
 
class Parallelogram extends Quadrangle implements Paintable{
public void draw(){//由于该类实现了接口,所以需要覆盖draw()方法
 System.out.println("绘制平行四边形");
 }
}
class Square extends Quadrangle implements Paintable{
 public void draw() {
 System.out.println("绘制正方形");
 }
 }
 
 
 public class Demo7{
 public static void main(String[] args) {
 Square s = new Square();
 s.draw();
 s.doAnything();
 Parallelogram p = new Parallelogram();
 p.draw();
 p.doAnything();
 Circular c=new Circular();
 c.draw();
}
}

第七章总结_第11张图片

从这个结果可以看出,“绘制”这个行为可不是四边形独有的,圆形也能绘制,所以draw方法被独立封装在了可绘制接口中。
正方形类与平行四边形类分别继承了四边形类并实现了可绘制接口,所以正方形类与平行四边形类既可以调用绘制方法,又可以调用四边形提供的doAnything0方法。但是,圆形不属于四边形,且可以绘制,所以最后圆形对象只调用了draw0方法。

 

你可能感兴趣的:(java)