类和对象

一 面向对象概述
 1.1 对象
 现实世界中,随处可见的一种事物就是对象,对象是事物存在的实体,如人类、书桌、计算机、高楼大厦等。人类解决问题的方式总是将复杂的事物简单化,于是就会思考这些对象都是由哪些部分组成的。通常都会将对象划分为两个部分,即静态部分与动态部分。静态部分,顾名思义,就是不能动的部分,这个部分被称为“属性”,任何对象都会具备其自身属性,如一个人,其属性包括高矮、胖瘦、性别、年龄等。然而具有这些属性的人会执行哪些动作也是一个值得探讨的部分,这个人可以哭泣、微笑、说话、行走,这些是这个人具备的行为(动态部分),人类通过探讨对象的属性和观察对象的行为了解对象。
 类实质上就是封装对象属性和行为的载体,而对象则是类抽象出来的一个实例。
 1.2 类
 类就是同一类事物的统称,如果将现实世界中的一个事物抽象成对象,类就是这类对象的统称。
 类与对象之间的关系,对象就是符合某个类的定义所产生出来的实例。更为恰当的描述是,类是世间事物的抽象称呼,而对象则是这个事物相对应的实体。类是封装对象的属性和行为的载体,反过来说具有相同属性和行为的一类实体被称为类。
 可以根据这个类抽象出一个实体对象,最后通过实体对象来解决相关的实际问题。
 1.3 封装
 面向对象程序设计具有以下特点:封装性。继承性。多态性。
 封装是面向对象编程的核心思想。将对象的属性和行为封装起来,其载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。
 采用封装的思想保证了类内部数据结构的完整性,应用该类的用户不能轻易地直接操作此数据结构,只能执行类允许公开的数据。这样就避免了外部操作对内部数据的影响,提高了程序的可维护性。
 1.4 继承
 类与类之间同样具有关系,如一个百货公司类与销售员类相联系,类之间的这种关系被称为关联。关联主要描述两个类之间的一般二元关系。
 继承性主要利用特定对象之间的共有属性。例如,平行四边形是四边形,正方形、矩形也都是四边形,平行四边形与四边形具有共同特性,就是拥有4个边,可以将平行四边形类看作四边形的延伸,平行四边形复用了四边形的属性和行为,同时添加了平行四边形独有的属性和行为,如平行四边形的对边平行且相等。这里可以将平行四边形类看作是从四边形类中继承的。在Java语言中将类似于平行四边形的类称为子类,将类似于四边形的类称为父类或超类。值得注意的是,可以说平行四边形是特殊的四边形,但不能说四边形是平行四边形,也就是说子类的实例都是父类的实例,但不能说父类的实例是子类的实例。
 继承关系可以使用树形关系来表示,父类与子类存在一种层次关系。一个类处于继承体系中,它既可以是其他类的父类,为其他类提供属性和行为,也可以是其他类的子类,继承父类的属性和方法,如三角形既是图形类的子类也是等边三角形的父类。
 1.5 多态
 多态性允许以统一的风格编写程序,以处理种类繁多的已存在的类及相关类。该统一风格可以由父类来实现,根据父类统一风格的处理,可以实例化子类的对象。由于整个事件的处理都只依赖于父类的方法,所以日后只要维护和调整父类的方法即可,这样就降低降低了维护的难度,节省了时间。
 提到多态,就不得不提抽象类和接口,因为多态的实现并不依赖具体类,而是依赖于抽象类和接口。
 在多态的机制中,父类通常会被定义为抽象类,在抽象类中给出一个方法的标准,而不给出实现的具体流程。实质上这个方法也是抽象的,如图形类中的“绘制图形”方法只提供一个可以绘制图形的标准,并没有提供具体绘制图形的流程,因为没有人知道究竟需要绘制什么形状的图形。
 在多态的机制中,比抽象类更方便的方式是将抽象类定义为接口。由抽象方法组成的集合就是接口。

二 类
 类是封装对象的属性和行为的载体,而在Java语言中对象的属性以成员变量的形式存在,对象的方法以成员方法的形式存在。
 2.1 成员变量
 在Java中对象的属性也称为成员变量。为了了解成员变量,首先定义一个图书类,成员变量对应于类对象的属性,在Book类中设置3个成员变量,分别为id、name和category,分别对应于图书编号、图书名称和图书类别3个图书属性。如下例:

public class Book {
	private String name;
	private int id;
	private String category;

	public String getName() {
		int id = 0;
		setName("Java");
		return id + this.name;
	}
	
	private void setName(String name) {
		this.name = name;
	}

	public Book getBook() {
		return this;
	}
}

以上代码中,class关键字来定义类,Book是类的名称。同时在Book类中定义了3个成员变量,成员变量的类型可以设置为Java中合法的数据类型,其实成员变量就是普通的变量,可以为它设置初始值,也可以不设置初始值。如果不设置初始值,则会有默认值。
 2.2 成员方法
 在Java语言中使用成员方法对应于类对象的行为。以上文Book类为例,它包含getName()和setName()两个方法,这两个成员方法分别为获取图书名称和设置图书名称的方法。定义成员方法的语法格式如下:

权限修饰符 返回值类型 方法名(参数类型 参数名){
	...//方法体
	return 返回值;
}

一个成员方法可以有参数,这个参数可以是对象也可以是基本数据类型的变量,同时成员方法有返回值和不返回任何值的选择,如果方法需要返回值可以在方法体中使用return关键字,使用这个关键字后,方法的执行将被终止。Java中的成员方法无返回值可以使用void关键字表示。
 成员方法的返回值可以是计算结果也可以是其他想要的数值和对象,返回值类型要与方法返回的值类型一致。
 在成员方法中可以调用其他成员方法和类成员变量,如上例中的getName()方法中就调用了setName()方法将图书名称赋予一个值。同时在成员方法中可以定义一个变量,这个变量为局部变量。
 如果一个方法中含有与成员变量同名的局部变量,则方法中对这个变量的访问以局部变量进行。
 2.3 权限修饰符
 Java中的权限修饰符主要包括private、public和protected,这些修饰符控制着对类和类的成员变量以及成员方法的访问。
 如果一个类的成员变量或成员方法被修饰为private,则该成员变量只能在本类中被使用,在子类中是不可见的,并且对其他包的类也是不可见的。如果将类的成员变量和成员方法的访问权限设置为public,那么除了可以在本类使用这些数据之外,还可以在子类和其他包的类中使用。
 如果一个类的访问权限被设置为private,这个类将隐藏其内的所有数据,以免用户直接访问它。如果需要使类中的数据被子类或其他包中的类使用,可以将这个类设置为public访问权限。如果一个类使用protected修饰符,那么只有本包内的该类的子类或其他类可以访问此类中的成员变量和成员方法。 这么看来,public和protected修饰的类可以由子类访问,如果子类和父类不在同一包中,那么只有修饰符为public的类可以被子类进行访问。如果父类不允许通过继承产生的子类访问它的成员变量,那么必须使用private声明父类的这个成员变量。下表描述了private、protected和public修饰符的修饰权限。
在这里插入图片描述
 当声明类时不使用public、protected和private修饰符设置类的权限,则这个类预设为包存取范围,即只有一个包中的类可以调用这个类的成员变量或成员方法。即使这个类中的成员方法或成员变量被设置为public权限,访问权限一人与类的访问权限相同。因为Java语言规定,类的权限设定会约束类成员的权限设定。
 2.4 局部变量
 如果在成员方法内定义一个变量,那么这个变量被称为局部变量。方法中的形参也可作为一个局部变量。
 局部变量是在方法被执行时创建,在方法执行结束时被销毁。局部变量在使用时必须进行赋值操作或被初始化,否则会出现编译错误。
 2.5 局部变量的有效范围
 可以将局部变量的有效范围称为变量的作用域,局部变量的有效范围从该变量的声明开始到该变量的结束为止。如下图所示局部变量的作用范围。
在这里插入图片描述
 在相互不嵌套的作用域中可以同时声明两个名称和类型相同的局部变量,如下图所示。 在这里插入图片描述
 但是在相互嵌套的区域中不可以这样声明,如果将局部变量id在方法体的for循环中再次定义,编译器将会报错,如下图所示。 在这里插入图片描述
 2.6 this关键字
 如下例:在项目中创建一个类文件,该类中定义了setName(),并将方法的参数值赋予类中的成员变量。

private void setName(String name){
	this.name = name;
}

在上述代码中可以看到,成员变量与setName()方法中的形式参数的名称相同,都为name,那么该如何在类中区分使用的是哪一个变量呢?在Java语言中规定使用this关键字来代表本类对象的引用,this关键字被隐式地用于引用对象的成员变量和方法。
 如在上述代码中,this.name指的就是Book类中的name成员变量,而this.name=name语句中的第二个name则指的是形参name。实质上setName()方法实现的功能就是将形参name的值赋予成员变量name。
 由此可知,this可以调用成员变量和成员方法,但Java语言中最常规的调用方式是使用“对象.成员变量”或“对象.成员方法”进行调用。

既然this关键字和对象都可以调用成员变量和成员方法,那么this关键字与对象之间具有怎样的关系呢?
 事实上,this引用的就是本类的一个对象。在局部变量或方法参数覆盖了成员变量时,如上面代码的情况,就要添加this关键字明确引用的是类成员还是局部变量或方法参数。如果省略this关键字直接写成name=name,那只是把参数name赋值给参数变量本身而已,成员变量name的值没有改变,因为参数name在方法的作用域中覆盖了成员变量name。
 其实,this除了可以调用成员变量或成员方法之外,还可以作为方法的返回值。如下例:

public Book getBook(){
	return this;
}

在getBook()方法中,方法的返回值为Book类,所以方法体中使用returnthis这种形式将Book类的对象进行返回。

三 类的构造方法
 在类中除了成员方法之外,还存在一种特殊类型的方法,那就是构造方法。构造方法是一个与类同名的方法,对象的创建就是通过构造方法完成的。每当类实例化一个对象时,类都会自动调用构造方法。
 构造方法的特点如下: 
  构造方法没有返回值。 
  构造方法的名称要与本类的名称相同。
 在定义构造方法时,构造方法没有返回值,但这与普通没有返回值的方法不同,普通没有返回值的方法使用 public void methodEx() 这种形式进行定义,但构造方法并不需要使用void关键字进行修饰。
 构造方法的定义语法格式如下:

public book(){
	//...构造方法体
}

public:构造方法修饰符。book:构造方法的名称。
 在构造方法中可以为成员变量赋值,这样当实例化一个本类的对象时,相应的成员变量也将被初始化。如果类中没有明确定义构造方法,编译器会自动创建一个不带参数的默认构造方法。
 如果在类中定义的构造方法都不是无参的构造方法,那么编译器也不会为类设置一个默认的无参构造方法,当试图调用无参构造方法实例化一个对象时,编译器会报错。所以只有在类中没有定义任何构造方法时,编译器才会在该类中自动创建一个不带参数的构造方法。
 
 注意,this关键字除了可以调用类的成员变量和成员方法外,还可以调用类中的构造方法。看下面的实例,在项目中创建AnyThting类,在该类中使用this调用构造方法:

public class AnyThing {
	public AnyThing() {
		this("this 调用有参构造方法");
		System.out.println("无参构造方法");
	}

	public AnyThing(String name) {
		System.out.println("有参构造方法");
	}
}

上例中定义了两个构造方法,在无参构造方法中可以使用this关键字调用有参的构造方法。但使用这种方式需要注意的是只可以在无参构造方法中的第一句使用this调用有参构造方法。

四 静态变量、常量和方法
 由static关键字修饰的变量、常量和方法被称做静态变量、常量和方法。
 有时,在处理问题时会需要两个类在同一个内存区域共享一个数据。例如,在球类中使用PI这个常量,可能除了本类需要这个常量之外,在另外一个圆类中也需要使用这个常量。这时没有必要在两个类中同时创建PI常量,因为这样系统会将这两个不在同一个类中定义的常量分配到不同的内存空间中。为了解决这个问题,可以将这个常量设置为静态的。PI常量在内存中被共享的布局如图所示:
在这里插入图片描述
 
 被声明为static的变量、常量和方法被称为静态成员。静态成员属于类所有,区别于个别对象,可以在本类或其他类使用类名和“.”运算符调用静态成员。语法如下:

类名.静态类成员

在项目中创建StaticTest类,该类中的主方法调用静态成员并在控制台中输出。代码如下:

public class StaticTest() {
	static double PI = 3.14159; 		//在类中定义静态常量
	static int id;									//在类中定义静态变量

	public static void method1() {  	//在类中定义静态方法
		//doSomething
	}

	public void method2() {
		System.out.println(StaticTest.PI);		//调用静态常量
		System.out.println(StaticTest.id);		//调用静态变量
		StaticTest.method1();							//调用静态方法
	}
}

设置了3个静态成员,分别为常量、变量和方法,然后在method2()方法中分别调用这3个静态成员,直接使用“类名.静态成员”形式进行调用即可。注意:虽然静态成员也可以使用“对象.静态成员”的形式进行调用,但通常不建议用这样的形式,因为这样容易混淆静态成员和非静态成员。
 静态数据与静态方法的作用通常是为了提供共享数据或方法,如数学计算公式等,以static声明并实现,这样当需要使用时,直接使用类名调用这些静态成员即可。尽管使用这种方式调用静态成员比较方便,但静态成员同样遵循着public、private和protected修饰符的约束。
 在Java语言中对静态方法有两点规定: 
  在静态方法中不可以使用this关键字。 
  在静态方法中不可以直接调用非静态方法。
 如下例所示

public class StaticTest() {
	static double PI = 3.14159; 		//在类中定义静态常量
	static int id;									//在类中定义静态变量

	public static void method1() {  	//在类中定义静态方法
		//doSomething
	}

	public void method2() {							//在类中定义一个非静态方法
		System.out.println(StaticTest.PI);		//调用静态常量
		System.out.println(StaticTest.id);		//调用静态变量
		StaticTest.method1();							//调用静态方法
	}
	public static StaticTest method3(){			//在类中定义一个静态方法
		method2();											//调用非静态方法
		return this;											//在return语句中使用this关键字
	}
}

在Eclipse中输入上述代码后,编译器会发生错误,这是因为method3()方法为一个静态方法,而在其方法体中调用了非静态方法和this关键字。
 除此以外,在Java中规定不能将方法体内的局部变量声明为static的。
 如果在执行类时,希望子安执行类的初始化动作,可以使用static定义一个静态区域。例如:

public class example {
	static {
		// some
	}
}

当这段代码被执行时,首先执行static块中的程序,并且只会执行一次。

五 类的主方法
 主方法是类的入口点,它定义了程序从何处开始;主方法提供对程序流向的控制,Java编译器通过主方法来执行程序。主方法的语法如下:

public static void main(String[] args) {
	//方法体
}

在主方法的定义中可以看到主方法具有以下特性: 
 主方法是静态的,所以如要直接在主方法中调用其他方法,则该方法必须也是静态的。 
 主方法没有返回值。 
 主方法的形参为数组。其中args[0]~args[n]分别代表程序的第一个参数到第n个参数,可以使用args.length获取参数的个数。
 【例子】在项目中创建TestMain类,在主方法中编写以下代码,并在eclipse中设置程序参数。

public class TestMain {
	public static void main(String[] args) {
		System.out.println(args[i]);
	}
}

在Eclipse中设置程序参数的步骤如下:
 (1)在Eclipse中,在包资源管理器的项目名称节点上单击鼠标右键,在弹出的快捷菜单中选择“运行”/“运行配置”命令,弹出“运行配置”对话框。
 (2)在“运行配置”对话框中选择“自变量”选项卡,在“程序自变量”文本框中输入相应的参数,每个参数间按Enter键隔开。具体设置如图所示。
在这里插入图片描述
 在Eclipse中运行本例,结果如下图所示:
在这里插入图片描述

六 对象
 6.1 对象的创建
 对象可以认为是在一类事物中抽象出某一个特例,可以通过这个特例来处理这类事物出现的问题。在Java语言中通过new操作符来创建对象。每实例化一个对象就会自动调用一次构造方法,实质上这个过程就是创建对象的过程。准确地说,可以在Java语言中使用new操作符调用构造方法创建对象。语法如下:

Test test = new Test();
Test test = new Test("a");

test对象被创建出来时,就是一个对象的引用,这个引用在内存中为对象分配了存储空间。当创建对象时,自动调用构造方法,同时构筑方法可以初始化成员变量。也就是说初始化与创建是被捆绑在一起的。
 每个对象都是相互独立的,在内存中占据独立的内存地址,并且每个对象都具有自己的生命周期,当一个对象的生命周期结束时,对象就变成垃圾,由Java虚拟机自带的垃圾回收机制处理,不能再被使用。
 6.2 访问对象的属性和行为
 使用new操作符创建一个对象后,可以使用“对象.类成员”来获取对象的属性和行为。对象的属性和行为在类中是通过类成员变量和成员方法的形式来表示的,所以当对象获取类成员时,也相应地获取了对象的属性和行为。

public class TransferProperty{
	int i = 47;																						//定义成员变量
	public void call(){																			//定义成员方法
		System.out.println("调用call()方法");										
		for(i = 0; i < 3; i++){
			System.out.print(i + “ ”);
			if(i == 2){
				System.out.println("\n");
			}
		}
	}
	public TransferProperty() {															//定义构筑方法
	}
	public static void main(String[] args){											//定义主方法
		TransferProperty test1 = new TransferProperty();					//创建一个对象
		TransferProperty test2 = new TransferProperty();					//创建另一个对象
		test2.i = 60;																				//将成员变量赋值为60
		System.out.println("第一个实例对象调用变量i的结果: " + test1.i++);
		test1.call();
		System.out.println("第二个实例对象调用变量i的结果: " + test2.i);
		test2.call();
	}
}

运行结果如下:
在这里插入图片描述
 在上述代码的主方法中首先实例化一个对象,然后使用“.”操作符调用类的成员变量和成员方法。但是在运行结果中可以看到,虽然使用两个对象调用同一个成员变量,结果却不相同,因为在打印这个成员变量的值之前将该值重新赋值为60,但在赋值时使用的是第二个对象t2调用成员变量,所以在第一个对象t1调用成员变量打印该值时仍然是成员变量的初始值。由此可见,两个对象的产生是相互独立的,改变了t2的i值,不会影响到t1的i值。
 如果希望成员变量不被其中任何一个对象改变,可以使用static关键字。将上例改成如下情况:

public class TransferProperty{
	static int i = 47;																			//定义静态成员变量
	public void call(){																			//定义成员方法
		System.out.println("调用call()方法");										
		for(i = 0; i < 3; i++){
			System.out.print(i + “ ”);
			if(i == 2){
				System.out.println("\n");
			}
		}
	}
	public TransferProperty() {															//定义构筑方法
	}
	public static void main(String[] args){											//定义主方法
		TransferProperty test1 = new TransferProperty();					//创建一个对象
		TransferProperty test2 = new TransferProperty();					//创建另一个对象
		test2.i = 60;																				//将成员变量赋值为60
		System.out.println("第一个实例对象调用变量i的结果: " + test1.i++);
		test1.call();
		System.out.println("第二个实例对象调用变量i的结果: " + test2.i);
		test2.call();
	}
}

运行结果如下:
在这里插入图片描述
 从上述运行结果中可以看到,由于使用t2.i=60语句改变了静态成员变量的值,使用对象t1调用成员变量的值也为60,这正是i值被定义为静态成员变量的效果,即使使用两个对象对同一个静态成员变量进行操作,依然可以改变静态成员变量的值,因为因为在内存中两个对象同时指向同一块内存区域。t1.i++语句执行完毕后,i值变为3。当再次调用call()方法时又被重新赋值为0,做循环打印操作。
 6.3 对象的引用
 在Java语言中尽管一切都可以看作对象,但真正的操作标识符实质上是一个引用,那么引用在Java中是如何体现的呢?语法如下:

类名 对象引用名称

如一个Book类的引用可以使用以下代码:

Book book;

通常一个引用不一定需要有一个对象相关联。引用与对象相关联的语法如下:

Book book = new Book();

引用只是存放一个对象的内存地址,并非存放一个对象,严格地说引用和对象是不同的,但是可以将这种区别忽略,如可以简单地说book是Book类的一个对象,而事实上应该是book包含Book对象的一个引用。
 6.4 对象的比较
 在Java语言中有两种对象的比较方式,分别为“==”运算符与equals()方法。实质上这两种方式有着本质区别,下面举例说明。

public class Compare{
	public static void main(String[] args){
		String c1 = new String("abc");
		String c2 = new String("abc");
		String c3 = c1;
		System.out.println("c2==c3 的运算结果为: " + (c2 == c3));
		System.out.println("c2.equals(c3) 的运算结果为: " + (c2.equals(c3)));
	}
}

运算结果如下图:
在这里插入图片描述
 从上述运行结果中可以看出,“==”运算符和equals()方法比较的内容是不相同的,equals()方法是String类中的方法,它用于比较两个对象引用所指的内容是否相等;而“==”运算符比较的是两个对象引用的地址是否相等。由于c1与c2是两个不同的对象引用,两者在内存中的位置不同,“String c3 = c1;”语句将c1的引用赋给c3,所以c1与c3这两个对象引用是相等的,也就是打印c1 == c3这样的语句将返回true值。
 6.5 对象的销毁
 每个对象都有生命周期,当对象的生命周期结束时,分配给该对象的内存地址将会被回收。Java拥有一套完整的垃圾回收机制,用户不必担心废弃的对象占用内存,垃圾回收器将回收无用的但占用内存的资源。
 在谈到垃圾回收机制之前,首先需要了解何种对象会被Java虚拟机视为垃圾。主要包括以下两种情况: 
 对象引用超过其作用范围,这个对象将被视为垃圾,如图所示。
在这里插入图片描述
 将对象赋值为null,如图所示。
在这里插入图片描述
 垃圾回收器只能回收那些由new操作符创建的对象,如果某些对象不是通过new操作符在内存中获取一块内存区域,这种对象可能不能被垃圾回收机制所识别,所以在Java中提供了一个finalize()方法。这个方法是Object类的方法,它被声明为protected,用户可以在自己的类中定义这个方法。如果用户在类中定义了finalize()方法,在垃圾回收时会首先调用该方法,在下一次垃圾回收动作发生时,才能真正回收被对象占用的内存。
 垃圾回收或finalize()方法不保证一定会发生,如Java虚拟机内存损耗待尽时,它是不会执行垃圾回收的。由于垃圾回收不受人为控制,具体执行时间也不确定,所以finalize()方法也就无法执行,为此,Java提供了System.gc()方法强制启动垃圾回收器,告知垃圾回收器进行清理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值