欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java语言程序设计—面向对象程序设计

程序员文章站 2022-06-27 16:12:22
本文主要由个人用于在Java语言程序设计,尤其是面向对象程序设计方面的学习理解,方便回顾总结。采用书籍为 Y.Daniel Liang 所著的《Java语言程序设计-基础篇》第八版。文章目录一、对象和类二、字符串和文本I/O三、关于对象的思考四、继承和多态1. 父类和子类五、异常处理六、抽象类和接口七、二进制I/O一、对象和类二、字符串和文本I/O三、关于对象的思考四、继承和多态我们知道,面向对象程序设计的三个特点是封装、继承和多态,在这里我们首先来学习继承。什么是继承?继承就是从已有...

本文主要由个人用于在Java语言程序设计,尤其是面向对象程序设计方面的学习理解,方便回顾总结。
采用书籍为 Y.Daniel Liang 所著的《Java语言程序设计-基础篇》第八版。
工程量较大,正在慢慢挖坑填坑


一、对象和类

从本节开始,我们开始接触与学习面向对象程序设计。在此之前,我们所学习过的如C语言属于的是面向过程语言,那么面向对象和面向过程之间的区别是什么?面向对象中的这个对象又是什么?让我们带着这样的问题开始进入学习。

1.1 定义对象的类

面向对象程序设计(OOP)就是使用对象进行程序设计,而我们首先要明白的就是对象是什么。
对象(object)代表现实世界中可以明确标识的一个实体,例如:一个学生、一张桌子、一个圆、一个按钮甚至一笔贷款都可以看作是一个对象。每个对象都有自己独特的标识、状态和行为。

一个对象的属性(也可以称为特征或者状态)是指那些具有它们当前值的数据域。例如:圆对象具有一个数据域radius,它是标识圆的属性。一个矩形对象具有数据域width和height,它们都是标识矩形的属性。

一个对象的行为(也可以称为动作)是由方法定义的。调用对象的一个方法就是要求对象完成一个动作。例如:可以为圆对象定义一个名为getArea()的方法。圆对象可以调用getArea()返回圆的面积。

但在我们的现实生活中会遇到许多相同类型的对象,就像不同半径的圆虽然不相同,但是它们都属于圆形这一个类型。为了将具有相同类型的对象进行归纳,我们可以使用一个通用类来定义同一类型的对象。

Java语言程序设计—面向对象程序设计

类是一个模板、蓝本或者说是合约,用来定义对象的数据域是什么以及方法是做什么。上图显示了名为Circle的类以及它的三个对象。
一个对象是类的一个实例,即将原本模板中抽象的概念变成了现实存在的东西,类和对象之间的关系类似于苹果派配方和苹果派之间的关系。
我们可以从一个类中创建多个实例,创建实例的过程称为实例化。实际上,实例与对象在术语上经常是可以互换的。
我们通过代码来进一步学习。

class Circle{
	double radius = 1.0;	//	数据域
	//	创建一个默认的圆对象
	Circle(){	//	无参构造方法
	}
	//	创建一个半径为newRadius的圆对象
	Circle(double newRadius){	//	含参构造方法
		radius = newRadius;
	}
	//	返回圆的面积
	double gerArea(){	//	方法
		return radius * radius * Math.PI;
	}
}

Java类使用变量定义数据域,使用方法定义动作。除此之外,类还提供了一种称为构造方法的特殊类型方法,调用它可以创建一个新对象。
构造方法本身是可以完成任何动作的,但是设计构造方法的初衷还是为了完成初始化动作,例如:初始化对象的数据域。在上述代码中,无参构造方法创建了一个radius默认为1.0的圆,而含参构造方法创建了一个半径为newRadius的圆。
在后面我们会再对构造函数进行介绍讲解。

Circle类由于没有main方法,实际上是不能运行的,它只是对圆对象的定义。当然也有包含main方法的类,为了方便我们将包含main方法的类称为主类。

1.1.2 举例:定义类和创建对象

为了加深对类和对象的理解,我们通过举例和代码进一步学习。下面的代码定义了一个Circle类并使用该类创建对象。程序构造了三个圆对象,其半径分别为1.0、25和125,然后显示这三个圆的半径和面积。将第二个对象的半径改为100,然后显示它的新半径和面积。

public class TestCircle {
	//	main方法
	public static void main(String[] args) {
		//	创建一个半径为1.0的圆对象
		Circle circle1 = new Circle();
		System.out.println("The area of the circle of radius "
				+ circle1.radius + " is " + circle1.getArea() );
		//	创建一个半径为25的圆对象
		Circle circle2 = new Circle(25);
		System.out.println("The area of the circle of radius "
				+ circle2.radius + " is " + circle2.getArea() );
		//	创建一个半径为125的圆对象
		Circle circle3 = new Circle(125);
		System.out.println("The area of the circle of radius "
				+ circle3.radius + " is " + circle3.getArea() );
		//	修改第二个圆对象的半径为100
		circle2.radius = 100;
		System.out.println("The area of the circle of radius "
				+ circle2.radius + " is " + circle2.getArea() );
	}
}

class Circle {
	double radius;
	//	创建一个半径为1.0的圆
	Circle(){
		radius = 1.0;
	}
	//	创建一个半径为newRadius的圆
	Circle(double newRadius){
		radius = newRadius;
	}
	//	返回当前圆对象的面积
	double getArea() {
		return radius * radius * Math.PI;
	}
}

程序包括两个类。其中第一个类TestCircle是主类,它的唯一目的就是测试第二个类Circle。使用这样的类的程序通常称为是该类的客户。运行这个程序时,Java运行系统会调用这个主类的main方法并输入如下结果。

Java语言程序设计—面向对象程序设计

我们可以把两个类放在同一个文件中,但是文件中只能有一个类是公共的。此外,公共类必须与文件同名。因此,文件名应该是TestCircle.java,因为TestCircle是公共的。

上面的代码增加了许多注释,实际上是简单易读的,在这我们再对其进行解读。
主类包含main方法,该方法创建三个对象。和创建数组一样,使用new操作符从构造方法创建一个对象。new Circle()创建一个半径为1.0的对象,new Circle(25)创建一个半径为25的对象,而new Circle(125)创建一个半径为125的对象。这三个对象分别通过circle1、circle2和circle3来引用,它们有不同的数据,但是有相同的方法。因此,可以使用getArea()方法计算它们各自的面积。
这三个对象是相互独立的,可以通过circle1.radius、circle2.radius、circle3.radius来通过对象引用访问数据域,我们也是用这样的办法更改circle2的半径的。同时对象可以分别使用circle1.getArea()、circle2.getArea()、circle3.getArea()来通过对象引用调用它的方法。

通过这个例子展示了类和对象的概貌。你可能已经有很对关于构造方法、对象、引用变量、访问数据域以及调用对象方法方面的问题,我们随后会详细讨论这些话题

1.2 使用构造方法构造对象

构造方法是一种特殊的方法,它们具有以下三个特殊性:
1):构造方法必须具备和类相同的名字。
2):构造方法没有返回类型,甚至连void也没有。
3):构造方法是在创建一个对象使用new操作符时调用的。构造方法的作用是初始化对象。

构造方法具有和定义它的类完全相同的名字。和所有其他方法一样,构造方法也可以重载(也就是说可以有多个同名的构造方法,但它们要有不同的签名),这样更易于用不同的初始数据值来构造对象。

一个常见的错误就是将关键字void放在构造方法的前面。例如:

class Circle{
	public void Circle(){
		...
	}
}

在这种情况下,Circle()是一个方法,而不是构造方法。
构造方法是用来构造对象的。为了能够从一个类构造对象,我们使用new操作符调用这个类的构造方法,如下所示:

new ClassName(arguments);	//	new 类名(参数);

例如: new Circle()使用Circle类中定义的第一个构造方法创建一个Circle对象。new Circle(25)调用Circle类中定义的第二个构造方法创建一个Circle对象。【代码见1.1.2】

通常,一个类会提供一个没有参数的构造方法(例如:Circle())。这样的构造方法称为无参构造方法。
一个类可以不定义构造方法。在这种情况下,类中隐含定义一个方法体为空的无参构造方法。这个构造方法称为默认构造方法,当且仅当没有明确定义任何构造方法时才会自动提供它。

1.3 通过引用变量访问对象

我们在创建新的对象的时候需要在内存中分配空间给它,而被新建的对象可以用引用变量来访问。

1.3.1 引用变量和引用类型

对象是通过对象引用变量来访问的,该变量包含对对象的引用,使用如下语言格式声明这样的变量:

ClassName objectRefVar;	//	类名 对象引用变量;

一个类基本上等同于一个程序员定义的类型,换言之,一个类就是一种引用类型,这意味着任何类型为类的变量都可以引用该类的一个实例。下面的语句声明myCircle的类型是Circle类型:

Circle myCircle;

变量myCircle能够引用一个Circle对象。下面的语句创建一个对象,并且将它的引用赋给变量myCircle:

myCircle = new Circle();

我们可以将上面两条语句进行整合,就可以获得写一条包括声明对象引用变量、创建对象以及将对象的引用赋值给这个变量的语句,其中变量myCircle中放的是对Circle对象的一个引用。

Circle myCircle = new Circle();
//	ClassName objectRefVar = new ClassName();
//	类名	对象引用变量   = new	类名();

注1: 从表面上看,对象引用变量中似乎存放了一个对象,但事实上,它只是包括了对该对象的引用,即对象引用变量存放的是这个对象的地址。

注2:大多数时候,我们创建一个对象,然后会把它赋值给一个变量。接下来就可以使用这个变量来引用对象。但有的时候,一个对象被创建之后并不需要被引用,在这种情况下,就可以创建一个对象,而不将它明确赋值给一个变量,如下所示:

new Circle();
//	或者
System.out.println("Area is" + new Circle(5).getArea() );

1.3.2 访问对象的数据和方法

在创建了一个对象之后,它的数据和方法可以使用圆点运算符(.)来访问和调用,该运算符也称为对象成员访问运算符。
我们通过举例进行说明。

class Circle{
	double radius = 1.0;
	//	在这里我们省略构造函方法 使用默认构造方法
	public double getArea(){
		return radius * radius * Math.PI;
	}
}
public class TestCircle(){
	public static void main(String[] args){
		Circle myCircle = new Circle();
		System.out.println("myCircle radius is " + myCircle.radius);
		System.out.println("myCircle Area is " + myCircle.getArea() );
	}
}

在上面的例子中,myCircle().radius引用myCircle的半径,而myCircle.getArea()调用myCircle的getArea方法。方法作对象上的操作被调用。
其中,数据域radius称为实例变量,因为它依赖于某个具体的实例。基于同样的原因,getArea方法称为实例方法,因为只能在具体的实例上调用它。调用对象上的实例方法的过程称为调用对象。

1.3.3 引用数据域和null值

数据域也可能是引用型的。例如:下面的Student类包含一个String类型的name数据域,String是一个预定义的Java类。

class Student{
	String name;	//	name 有默认值 null
	int age;	//	age 有默认值 0
	boolean isScienceMajor;	//	isScienceMajor 有默认值 false
	char gender;	//	gender 有默认值 '\u0000'
}

如果一个引用类型的数据域没有引用任何对象,那么这个数据域就有一个特殊的Java值null。null同true和false一样都是一个直接量。true和false是boolean类型的直接量,而null是引用类型直接量。

引用类型数据域的默认值是null,数值类型数据域的默认值是0,boolean类型数据域的默认值是false,而char类型数据域的默认值是’\u0000’。

class Test{
	public static void main(String[] args){
		Student student = new Student();
		System.out.println("name? " +  student.name );
		System.out.println("age? " + student.age );
		System.out.println("isScienceMajor? " + student.isScienceMajor );
		System.out.println("gender? " + student.gender );
	}
}

上述代码给出了Student对象中数据域name、age、isScienceMajor和gender的默认值,其中因为’\u0000’表示空格所以“不可见”。

Java语言程序设计—面向对象程序设计

但是,Java并没有给方法中的局部变量赋默认值,在下面代码中的局部变量x和y都没有被初始化,所以它会出现编译错误:

class Test{
	public static void main(String[] args){
		int x;
		String y;
		System.out.println("x is " + x );
		System.out.println("y is " + y );
	}
}

Java语言程序设计—面向对象程序设计

注: NullPointException是一种常见的运行错误,当调用值为null的引用变量上的方法时会发生此类异常。在通过引用变量调用一个方法之前,确保先将对象引用赋值给这个变量。

1.3.4 基本类型变量和引用类型变量的区别

每个变量都代表一个储存值的内存位置,在声明一个变量时,就是在告诉编译器这个变量可以存放什么类型的值。
对于基本类型变量而言,其对应内存所存储的值是基本类型值;而对于引用类型变量而言,对应内存所存储的值是一个引用,也就是对象的存储地址。

Java语言程序设计—面向对象程序设计

如图所示,其中int型变量i的值就是int值1,而Circle对象c的值存储的是一个引用,它指明这个Circle对象的内容存储在内存中的什么位置。

将一个变量赋值给另一个变量时,另一个变量就被赋了同样的值。
对于基本类型变量而言,就是将一个变量的实际值赋给另一个变量。对于引用变量而言,就是将一个变量的引用赋给另一个变量。我们通过一组图片来加深理解。

Java语言程序设计—面向对象程序设计

我们可以通过画图简单的理解:假设 i 的地址p为100,其中存储的值为1;而 j 的地址p为200,其中存储的值为2。那么在进行 i=j 的赋值语句后,改变了i中存储的实际值。基本类型的赋值就是将被赋值的那一块地址中所存储的数据直接改变,也就是改变其实际值。

Java语言程序设计—面向对象程序设计

对于引用变量的赋值,我们通过画图能理解的更加透彻:假设原本引用变量c1的地址p为300,其中存储了第一个Circle类的对象circle1的地址100;而引用变量c2的地址p为400,其中存储了第二给Circle类的对象circle2的地址200。这个时候我们就可以理解为引用变量c1指向了对象cricle1,而c2就自然是指向circle2的。然后我们进行 c1=c2的赋值操作,此时,c1中存储的实际值100(circle1的地址)就被更改为200(circle2的地址)。

注1: 这样的赋值操作唯一的影响是原本指向circle1的引用c1指向了circle2。此时我们如果通过引用变量c1输出radius值显然输出值为2,但这并不意味着原本c1指向的circle1的属性radius值被改变为2。事实上,circle1的radius值仍然是1,只不过没有引用变量指向circle1;而调用c1.radius输出的radius实际上是c1现在指向的circle2的radius。

注2:执行完赋值语句 c1=c2 后,c1指向c2所指向的同一个对象,而c1以前引用的对象就不再有用,因此,它就成了垃圾。而Java运行系统会检测垃圾并自动回收它所占的空间,这个过程称为垃圾回收。
也就是说,如果进行赋值c1=c2后,没有引用变量指向原本的实例circle1,那么circle1就不再存在,除非再次被创建。

1.4 使用Java库中的类

本节主要给出Java类库中一些类的例子。

1.4.1 Date类

Java在java.util.Date类中提供了与系统无关的对日期和时间的封装,如图:

Java语言程序设计—面向对象程序设计

我们可以使用Date类中的无参构造方法为当前的日期和时间创建一个实例,它的getTime()方法返回自从GMT时间1970年1月1日算起至今逝去的时间,它的toString()方法返回日期和时间的字符串。例如下面的代码:

public class Timeis {

	public static void main(String[] args) {
		java.util.Date date = new java.util.Date();
		System.out.println("The elapsed time sice Jan 1, 1970 is " + 
				date.getTime() + "milliseconds" );
		System.out.println(date.toString());
	}
}

显示输出为:

Java语言程序设计—面向对象程序设计

Date类还有另外一个构造方法:Date(long elapseTime),可以用它创建一个Date对象。该对象有一个从GMT时间1970年1月1日起至今逝去的以毫秒为单位的给定时间。

1.4.2 Random类

可以使用Math.random()获取一个0.0到1.0(不包括1.0)之间的随机double值。另一种产生随机数的方法是使用java.util.Random类,它可以产生一个int、long、double、float和boolean型值。

Java语言程序设计—面向对象程序设计

创建一个Random对象时,必须指定一个种子或者使用默认的种子。无参构造方法使用当前已经逝去的时间作为种子,创建一个Random对象。如果这两个Random对象有相同的种子,那它们将产生相同的数列。
下面的代码都用相同的种子3来产生两个Random对象。

import java.util.Random;

public class getRandom {

	public static void main(String[] args) {
		Random random1 = new Random(3);
		System.out.print("From random1: ");
		for(int i=0; i<10; i++)
			System.out.print(random1.nextInt(1000) + " ");
		System.out.println(" ");
		Random random2 = new Random(3);
		System.out.print("From random2: ");
		for(int i=0; i<10; i++)
			System.out.print(random2.nextInt(1000) + " ");
	}
}

这些代码产生相同的int类型的随机数列:

Java语言程序设计—面向对象程序设计

注: “种子”是什么?
实际上我们通过计算机创造的随机数方法都是伪随机的,上面所述的种子就是人为设定的数作为初始条件,然后通过一系列算法不停迭代以产生随机数。

1.4.3 显示GUI组件

图形用户界面(GUI)组件是讲授OOP(面向对象程序设计)的很好的例子。我们在此只介绍一些简单的GUI的例子。对GUI程序的完整学习我会在以后开博客进行学习记录。

开发程序创建图形用户界面时,将会用到像JFrame、JButton、JRadioButtom、JcomboBox和JList这样的Java类来创建框架、单选按钮、组合框、列标等。
我们首先使用JFrame类来创建两个窗口进行举例说明:

import javax.swing.JFrame;

public class TestFrame {

	public static void main(String[] args) {
		JFrame frame1 = new JFrame();
		frame1.setTitle("Window 1");	//	设置窗口的标题
		frame1.setSize(200,150);	//	设置窗口的宽度和高度
		frame1.setLocation(200,200);	//	设置窗口的位置(实际上是指明窗口左上角的位置)
		frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);	//	设置在关闭窗体的时候终止程序
		frame1.setVisible(true);	//	设置窗体可见
		
		JFrame frame2 = new JFrame();
		frame2.setTitle("Window 1");
		frame2.setSize(200,150);
		frame2.setLocation(410,100);
		frame2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame2.setVisible(true);
	}

}

运行程序后的结果如图:

Java语言程序设计—面向对象程序设计

这个程序创建了两个JFrame类的对象,其中JFrame类的方法我在代码中都已经给出了注释,在此就不再次阐述了。
我们还可以给窗口添加像按钮、标签、文本域、复选框和组合框这样的组件。组件是使用类来定义的。
我们再给出一个创建图形用户界面的例子:

import javax.swing.*;	//	导入swing包内所有的类和接口

public class GUIComponents {
	public static void main(String[] args) {
		//	创建一个带有文本"OK"的按钮
		JButton jbtOK = new JButton("OK");
		//	创建一个带有文本"Cancel"的按钮
		JButton jbtCancel = new JButton("Cancel");
		//	创建一个带有文本"Enter your name:"的标签
		JLabel jlblName = new JLabel("Enter your name: ");
		//	创建一个带有文本"Type Name Here"的可输入文本框
		JTextField jtfName = new JTextField("Type Name Here");
		//	创建一个带有文本"Bold"的复选框(用于检测确认后勾选)
		JCheckBox jchkBold = new JCheckBox("Bold");
		//	创建一个带有文本"Italic"的复选框
		JCheckBox jchkItalic = new JCheckBox("Italic");
		//	创建一个带有文本"Red"的单选按钮
		JRadioButton jrbRed = new JRadioButton("Red");
		//	创建一个带有文本"Yellow"的单选按钮
		JRadioButton jrbYellow = new JRadioButton("Yellow");
		//	创建一个下拉列表,其中有"Freshman"、"Sophomore"、"Junior"和"Senior"
		JComboBox jcboColor = new JComboBox(new String[] {"Freshman",
				"Sophomore" , "Junior" , "Senior"});
		
		
		JPanel panel = new JPanel();	//	创建一个面板组件实例来添加上述组件
		panel.add(jbtOK);	//	在面板panel中添加 jbtOK 按钮
		panel.add(jbtCancel);	//	在面板panel中添加 jbtCancel 按钮
		panel.add(jlblName);	//	在面板panel中添加 JlblName 标签
		panel.add(jtfName);	//	在面板panel中添加 jtfName 文本框
		panel.add(jchkBold);	//	在面板panel中添加 jchBold 复选框
		panel.add(jchkItalic);	//	在面板panel中添加 jchkItalic 复选框
		panel.add(jrbRed);	//	在面板panel中添加 jrbRed 单选按钮
		panel.add(jrbYellow);	//	在面板panel中添加 jrbYellow 单选按钮
		panel.add(jcboColor);	//	在面板panel中添加 jcboColor 下拉列表
		
		JFrame frame = new JFrame();	//	创建一个框架实例
		frame.add(panel);	//	把panel面板加入到frame框架实例中
		frame.setTitle("Show GUI Components");
		frame.setSize(450,200);
		frame.setLocation(200,100);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}

}

这个程序使用类JButton、JLabel、JTextField、JCheckBox、JRadioButton和JComboBox来创建GUI对象。然后,它使用JPanel类创建一个面板对象,并将按钮、标签、文本域、复选框、单选框以及组合框添加到这个面板中。接着,程序创建一个框架,将刚才的面板添加到这个框架中,最后显示这个框架。
显示结果如下图:

Java语言程序设计—面向对象程序设计

二、字符串和文本I/O

三、关于对象的思考

四、继承和多态

我们知道,面向对象程序设计的三个特点是封装、继承和多态,在这里我们先来学习继承。
什么是继承?继承就是从已有的类A中派生出新的类B,类B除了有自己特有的属性之外,还包含了类A中的所有属性。
通过继承,我们可以避免设计类时造成冗余,并且可以使得程序更易于理解和维护。

4.1 父类和子类

在Java的术语中,如果类C1拓展自另一个类C2,那么就将C1称为次类,将C2称为超类。超类也称为父类或基类,次类又称为子类、拓展类或派生类。

我们通过举例来进一步理解父类和子类。
假设我们在平面设计建模时需要创建像圆、矩形这样的几何对象,这些几何对象有一些共同的属性和行为 —— 它们可以是用某种颜色画出来的,并且可以选择是否填充。 那么此时,我们可以设计一个通用类 GeometricObject 并用它来建模所有的几何对象,这个类包含了属性color和 illed,以及适用于这些属性的get和 set 方法。但是这些几何对象除了这些共同的属性之外,还有各自的形状、大小。因此我们需要通过拓展GeometricObject类来定义Circle类或者Rectangle类或者其它几何图形类。此时,我们称 GeometricObject 类为 Circle 的父类,Circle类为GeometricObject类的子类。

Java语言程序设计—面向对象程序设计

子类继承了它父类的属性的同时,也从它的父类中继承了可访问的数据域和方法,同时还可以添加新数据域和新方法。
如图11-1所示,Circle类继承了GeometricObject类所有可以访问的数据域和方法。除此之外,它还有一个新的数据域radius,以及与radius相关的get和set方法。它还包括getArea()、getPerimeter()等方法以返回圆的面积、周长。
基本的概念我们已经了解了,然后我们通过代码来进一步学习继承,以下是GeometricObject类以及Circle类的代码。

//GeometricObject.java
public class GeometricObject{
	private String color = "white";
	private boolean filled;
	private Java.util.Date dateCreated;
	
	//创建一个默认的几何对象
	public GeometicObject(){	
		dateCreated = new java.util.Date();
	}
	//	使用指定的颜色和填充值构造一个几何对象
	public GeometricObject(String color,boolean filled){
		dateCreated = new java.util.Date();
		this.color = color;
		this.filled = filled;
	}
	//	返回该几何图形的颜色
	public String getColor(){
		return color;
	}
	//	设置一个新的颜色
	public void setColor(String color){
		this.color = color;
	}
	//	返回该几何图形的填充值
	public boolean isFilled(){
		return filled;
	}
	//	更改填充值
	public void setFilled(boolean filled){
		this.filled = filled;
	}
	//	获得这个几何图形
	public java.util.Date getDateCreated(){
		return dateCreated;
	}
	//	对toString重写 返回该几何图形的字符串表示形式
	public String toString(){
		return "created on" + dateCreated + "\ncolor" + color+
			" and filled" + filled;
	}
}
//	Circle.java
public class Circle extends GeometricObject{
	private double radius;
	//	创建一个默认的圆(这个默认包括Circle类的属性以及GeometricObject类的属性)
	public Circle(){
	}
	//	更改圆的半径
	public Circle4(double radius){
		this.radius = radius;
	}
	//	更改圆的半径、颜色以及填充值
	public Circle(double radius, String color, boolean filled){
		this.radius = radius;
		setColor(color);
		setFilled(filled);
	}
	//	返回圆的半径
	public double getRadius(){
		return radius;
	}
	//	更改圆的半径
	public void setRadius(double radius){
		this.radius = radius;
	}
	//	返回圆的面积
	public double getArea(){
		return radius * radius * Math.PI;
	}
	//	返回圆的直径
	public double getDiameter(){
		return 2 * radius
	}
	//	返回圆的周长
	public double Perimeter(){
	return 2 * radius * Math.PI;
	}
	//	输出圆的信息
	public void printCircle(){
		System.out.println("The circle is created " + gerDateCreated() + 
			" and the radius is " + radius );
	}
}

Circle类代码的第一行关键字extends告诉编译器,Circle类拓展GeometricObject类,这样,它就继承了getColor、setColor、isFilled、setFilled和toString方法。
重载的构造方法Circle(double radius, String color,boolean filled)是通过调用getColor和setFilled方法设置color和filled属性来执行的。这两个公共方法是在父类GeometricObject中定义的,在Circle中继承。因此,可以在子类中使用它们。
可以尝试在构造方法中使用数据域color和filled,如下所示:

public Circle(double radius, String color, boolean filled){
	this.radius = radius;
	this.color = color;	//	Illegal(非法的)
	this.filled = filled;	//	Illegal
}

这是错误的,因为GeometricObject类中的私有数据域color和filled是不能被除了GeometricObject类本身之外的其他任何类访问的。而唯一读取和改变color与filled的方法就是通过它们的get和set方法。

下面是在考虑继承时很值得注意的几点:

(1):和传统的理解相反,子类并不是父类的一个子集。实际上,一个子类通常比它的父类包含更多的信息和方法。
(2):父类中的私有数据域在该类之外是不可访问的,因此不能在子类中直接使用它们。
(3):不是所有的“是关系”都该用继承来建模。例如:一个正方形是一个矩形,但不应该定义一个Square类来拓展Rectangle类,因为没有任何东西能从矩形拓展到正方形。如果要用类B去拓展类A,那么A应该比B包含更多的信息。
(4):继承是用来为“是关系”建模的,不应该盲目地拓展一个类。即使Person类和Tree类可以共享高度和重量这样的通用特性,但从Person类拓展出Tree类是毫无意义的。一个父类和它的子类之间必须存在是关系。
(5):在Java中不允许多重继承,一个Java类只可能直接继承自一个父类,这种限制被称为单一继承。如果使用extends关键字来定义一个子类,它只允许有一个父类。

五、异常处理

在程序的运行过程中,如果环境检测出一个不可能执行的操作,就会出现运行时错误(Runtime Error),而异常就是一个表示阻止程序正常进行的错误或情况。如果异常没有被处理,程序就会非正常终止,而如何处理这个异常,以使程序可以继续运行或者平稳终止就是我们要学习的。

5.1 异常处理概述

为了演示异常处理,我们从一个读取两个整数并显示它们的商的例子开始。

import java.util.Scanner;

public class Quotient {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		
		System.out.print("请输入两个整数:");
		int number1 = input.nextInt();
		int number2 = input.nextInt();
		
		System.out.println(number1 + " 除以 " + number2 + " 的商 " + 
				(number1/number2) );
	}

}

对于这个程序我们输入两个整数5和2时得到了正确的商等于2。

Java语言程序设计—面向对象程序设计

但是当第二个数字输入的是0,那就会产生一个运行时错误,因为不能用0除一个整数。

Java语言程序设计—面向对象程序设计

解决这个错误的一个简单方法就是添加一个if语句来测试第二个数字。

import java.util.Scanner;

public class Quotient {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		
		System.out.print("请输入两个整数:");
		int number1 = input.nextInt();
		int number2 = input.nextInt();
		
		if(number2 != 0)
			System.out.println(number1 + " 除以 " + number2 + " 的商 " + 
					(number1/number2) );
		else
			System.out.println("除数不能为0");
	}

}

Java语言程序设计—面向对象程序设计

在某种层面上,这样的处理方法也可以称为对异常的处理,因为它确实将运行时错误处理掉了。但这样的方法只有异常处理的思想,为了演示异常处理的概念,包括如何创建、抛出、捕获以及处理异常,我们再对代码进行修改。

import java.util.Scanner;

public class Quotient {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		
		System.out.print("Enter two integers:");
		int number1 = input.nextInt();
		int number2 = input.nextInt();
		
		try {
			if(number2 == 0)	//	在number2=0时抛出一个ArithmeticException类的变量
				throw new ArithmeticException("Divisor cannot be zero");
			System.out.println(number1 + " /" + number2 + " is " + 
					(number1/number2) );
		}
		catch(ArithmeticException ex) {	//	当number2=0时抛出的异常被捕获
			System.out.println("Exception: an integer " + 
					"cannot be divided by zero ");
		}
		
		System.out.println("Execution continues ...");
	}
}

Java语言程序设计—面向对象程序设计

这个程序除了在正确输入时能正确地输出结果外,在遇到错误的输入时也有相应的处理方法。

Java语言程序设计—面向对象程序设计

而在程序中出现了一些我们之前没有见过的东西。这个程序包含了一个try块和一个catch块,其中try块包含的是在正常情况下执行的代码,catch块包含的是在number2为0时执行的代码。
当number2不等于0,即正确输入时,程序正确执行并只执行try内的代码。但当number2等于0时,程序会通过执行下面的语句来抛出一个异常:

throw new ArithmeticException("Divisor cannot be zero");

在这种情况 new ArithmeticException(“Divisor cannot be zero”)下抛出的值,可以称为一个异常(Exception)。
异常其实是一个从异常类创建的对象,此时的异常类是java.lang.ArithmeticException。

上面的代码中throw语句的执行称为抛出一个异常。“抛出异常”就是将异常从一个地方传递到另一个地方。当异常被抛出时,正常的执行流程就被中断了,而异常被传递到的地方就是catch块。
throw语句类似于方法的调用,但是不同于调用方法的是它调用的是catch块。从某种意义上讲,catch块就像带参数的方法定义,而这些参数匹配抛出的异常的类型。但是它不像方法,它在执行完catch块之后会执行catch块后的下一条语句而不返回到throw语句。

catch(ArithmeticException ex)

异常在进入到catch块的过程我们称为异常的捕获,而执行catch块中的代码则被称为代码的处理。
catch块的头部标识符ex的作用和方法中的参数类似,所以,这个参数称为catch块的参数。ex之前的类型(如ArithmeticException)指定了catch块可以捕获的异常类型。一旦捕获该异常,就能从catch块体中的参数访问这个抛出的值。

5.2 关于异常处理的更多知识

在上一节中我们给出了异常处理的概况,同时简单地介绍了异常处理所包含的操作。本节会对异常处理进行深入的讨论。
Java的异常处理模型实际上基于三个操作:声明一个异常、抛出一个异常和捕获一个异常。

Java语言程序设计—面向对象程序设计

5.2.1 异常类型

在5.1节中我们已经使用了异常ArithmeticException,事实上,在Java API中有很多预定义的异常类。

Java语言程序设计—面向对象程序设计

通过观察上图我们可以知道Throwable类是所有异常类的根。所有的Java异常类都直接或者间接地继承自Throwable。
这些异常类可以分为三种类型:系统错误、异常和运行时错误。

系统错误(System Error)是由Java虚拟机抛出的,用Error类表示,而Error类描述的是内部系统错误,这样的错误实际上很少发生。如果发生,除了通知用户尽量稳妥地终止程序外几乎什么也不能做。

Java语言程序设计—面向对象程序设计

异常(Exception) 是用Exception类表示的,它描述的是由程序和外部环境所引起的错误,这些错误能被程序捕获和处理。

Java语言程序设计—面向对象程序设计

运行时错误(Runtime Exception)是用RuntimeException类表示的,它描述的是程序设计错误。例如:错误的类型转换、访问一个越界数组或数值错误。运行时异常通常是由Java虚拟机抛出的。

Java语言程序设计—面向对象程序设计

RuntimeException、Error以及它们的子类都称为免检异常。所有其他异常都被称为必检异常,意思是指编译器会强制程序员检查并处理它们。
大多数情况下,免检异常都会反映出程序设计上不可恢复的逻辑错误。例如:如果通过一个引用变量访问一个对象之前并未将一个对象赋值给它,就会抛出NullPointerException异常;这都是程序中必须纠正的逻辑错误。
免检异常可能在程序的任何一个地方出现,为了避免过多的使用try-catch块,Java语言不允许编写代码捕获或声明免检异常。

5.2.2 声明异常

在Java中,当前执行的语句必属于某个方法,它可能属于main方法,也可能属于其它的方法。对于一个方法,如果它有可能会抛出异常,就必须对可能抛出的异常进行声明,这称为声明异常。
因为任何代码都可能发生系统错误和运行时错误,因此,Java不要求在方法中显式声明Error和RuntimeException(免检异常)。但是,方法要抛出的异常都必须在方法头显示声明,这样方法的调用者会被告知有异常。

public void myMethod() throws IOException

为了在方法中声明一个异常,就要在方法头中使用关键字throws。关键字throws表明myMethod()方法可能会抛出异常IOException。
如果方法可能会抛出多个异常,就可以在关键字throws后添加一个用逗号分隔的异常列表。

public void myMethod()
	throws Exception1,Exception2,...,ExceptionN

注1:如果方法没有在父类中声明异常,那么就不能在子类对其进行覆盖来声明异常。
注2:重写方法需要抛出与原方法所抛出异常类型一致异常或不抛出异常。
注3:子类抛出的异常类型不能比父类抛出的异常类型更宽泛 。

5.2.3 抛出异常

在检测一个错误的程序后创建一个正确异常类型的实例并抛出它,这就称为抛出一个异常。

IllegalArgumentException ex = new IllegalArgumentException("Wrong Argumnet");
throw ex;

上面的代码就是一个抛出异常的例子:假如程序发现传递给方法的参数与方法的合约不符(例如,方法中的参数必须是非负的,但是传入的是一个负参数),这个程序就可以创建IllegalArgumentException的一个实例并抛出它。

注1:IllegalArgumentException是Java API中的一个异常类。通常,Java API中的每个异常类至少有两个构造方法:一个无参构造方法和一个可带描述这个异常的String参数的构造方法。该参数称为异常信息,它可以用getMessage()获取。
注2:声明异常的关键字为throws,抛出异常的关键字是throw

5.2.4 捕获异常

现在知道了如何声明一个异常以及如何抛出一个异常。当抛出一个异常时,可以在try-catch块中捕获和处理它。

try{
	statements;	//	Statements that may throw exceptions
}
catch(Exception exVar1){
	handler for exception1;
}
catch(Exception exVar2){
	handler for exception2;
}
...	//	 这显然是不规范的 在此仅用于表示省略
catch(ExceptionN exVar3){
	handler for exceptionN;
}

接下来我们来讨论以下catch是如何捕获异常的:
如果在执行try块的过程中没有出现异常,则跳过catch子句。
如果try块中的某条语句抛出异常,就会跳过try块中剩余的语句,然后开始查找处理这个异常代码的过程。处理这个异常的代码称为异常处理器。可以从当前的方法开始,沿着方法调用链,按照异常的反向传播方向找到这个处理器。
处理器会从第一个到最后一个逐个检查catch块,判断在catch块中的异常类实例是否是该异常对象的类型。如果是,就将该异常对象赋值给所声明的变量,然后执行catch块中的代码。
如果没有发现异常处理器,Java就会退出这个方法,把异常传递给调用这个方法的方法,继续同样的过程来查找处理器。如果在调用的方法链找不到处理器,程序就会终止并在控制台打印报错。
寻找处理器的过程称为捕获一个异常。

Java语言程序设计—面向对象程序设计

之间的讲解可能过于抽象难懂,我们用上面的图片再来阐述一遍。
假设main方法调用method1,method1调用method2,method2调用method3,method3抛出一个异常,考虑下面的情形:

1):如果异常类型是Exception3,它就会被method2中处理异常ex3的catch块捕获。跳过statement5,然后执行statement6。

2):如果异常类型是Exception2,则退出method2,控制被返回给method1,而这个异常就会被method1中处理异常ex2的catch块捕获。跳过statement3,然后执行statement4。

3):如果异常类型是Exception1,则退出method1,控制被返回给main方法,而这个异常就会被main方法中处理异常的catch块捕获。跳过statement1,然后执行statement2。

4):如果异常类型没有在method2、method1和main方法中被捕获,程序就会终止,不执行statement1和statement2。

注1: 从一个通用父类可以派生出各种异常类。如果一个catch块可以捕获一个父类的异常对象,它就能捕获那个父类的所有子类的异常对象。

Java语言程序设计—面向对象程序设计
Java语言程序设计—面向对象程序设计

注2: 在catch块中异常被指定的顺序是非常重要的。如果父类的catch块出现在子类的catch块之前,就会导致编译错误。
【这是因为在try中出现异常的时候抛出的一个异常类或者其子类的变量,然后依次到catch的括号中匹配。在上图a)中无论抛出的变量是什么类型,都肯定会匹配到第一个catch块,这使得第二个catch块没有意义,因此会导致编译错误。】

Java语言程序设计—面向对象程序设计

注3: Java强迫程序员处理必检异常。如果一个方法声明了一个必检异常,就必须在try-catch块中调用它,或者在调用方法中声明要抛出异常。
如果假定方法p1调用方法p2,而p2可能会抛出一个必检异常(例如,IOException),就必须如图a或图b来编写代码。

六、抽象类和接口

七、二进制I/O

本文地址:https://blog.csdn.net/zstu19_Nightmare/article/details/109863236