第八章 类和对象

面向对象编程是现代高级语言的标准内容,它不仅仅是一种编程方式的,也是一种编程思想。计算机编程的发展来源于人类对现实世界事物的认识和处理。软件是对现实世界的抽象,现实世界中的事物映射到程序中就是对象,对象是描述现实世界事物的程序单位。

例如,在现实世界中,“人”只是一个抽象概念,而具体的“张三”,“李四”才是一个具体的存在的对象。那么,这个现实世界的“人”在程序世界中,就是可以当作“类”来表示,而“张三”和“李四”在程序的世界中,就可以使用两个不同的“对象”来表示。

当然,类和对象是有关系的,类就好比一个模板,而对象则是根据类(模板)而“实例化”出来的一个确切的存在。“实例化”就是分配内存,让“对象”在内存中真实的存在。在通俗的讲,类就是描述对象的数据类型,对象则是类的实例变量。类是引用数据类型。

事物到对象的抽象包括两个方面:数据抽象和行为抽象。这两种抽象在类中的体现就是数据成员(属性)和方法成员(行为),属性通过数据变量实现,而行为则通过方法来实现的。一般情况下,我们将类中的方法成员对外公开,而数据成员则受到保护。

现实世界中的事物并不是孤立存在的,他们之间存在着错综复杂的关系网络。这也体现在他们在计算机世界的抽象。不同对象之间的关系通常是通过“调用”来实现的,既一个对象调用另一个对象的属性或者行为。

其实,在我们之前的代码中,我们已经使用到类了,还记得我们第一个Java程序嘛,如下:

public class Hello {
	
	public static void main(String[] arg){
		
		
		System.out.println("hello world!");
	}
}

class就是Java的类定义关键字,后面的Hello就是类名,类的名称遵从Java标识符的命名规则。最开始的public是访问控制符,意思是公开的表示其他代码都可以访问使用这个类。类的数据成员和方法成员,被包裹在花括号之内,类似于方法体一样。在类的里面,也就是花括号里面,我们可以定义变量,也就是数据成员,也可以定义方法,也就是行为成员。

在上述代码中,我们定义了一个方法main,该方法也是public公开的,也就是说,其他代码可以访问这个类的main方法。static是静态的,我们后续会详细介绍。void表示该方法没有返回值。main就是方法的名称,请注意,这个main方法是一个特殊的方法,它是我们整个程序的入口方法,因此,它必须这样写,这是规定。如果是我们自己定义的方法,我们就可以按照自己的意愿定义方法名称了。紧接着,就是main方法的形参,一般情况我们不使用它。在main方法体内,我们只有一句代码,就是输出字符串”hello”。由于这个类只是用来演示Java程序的入门演示,因此它并不能说明类的使用。

接下来,我们定义一个新的Person类来说明我们人类的属性和行为。代码如下:

public class Hello {
	
	public static void main(String[] arg){
		
		// 声明并实例化类
		Person p1 = new Person();
		p1.name = "zhangsan";
		p1.say();
		Person p2 = new Person();
		p2.name = "lisi";
		p2.say();
		
	}
}

// 定义类
class Person {
	
	// 定义数据成员(属性)
	public String name;
	public boolean sex;
	public int age;
	
	// 定义方法成员
	public void say(){
		
		System.out.println("I am " + name);
	}
}

请注意,在我们的Hello.java源程序中,我们定义了两个类,一个是Hello,一个是Person。这里需要注意的是,在Java中,我们基本上是一个文件存储一个类的定义,并且文件名称就是类的名称。如果一个文件中存储两个类的时候,文件名称必须和Public修饰的类名保持一致。因此,在上文中,我们新定义的Person类没有使用Public修饰。

在我们新定义的Person类中,首先我们定义了三个数据成员,其实就是三个变量而已,分别代表姓名,性别和年龄,然后我们又定义了一个say方法,用来输出这个人的姓名。类定义完毕之后,我们就可以使用它了。在我们的入口main方法中,我们声明并实例化了该类。在数组章节中,我们就使用过new关键字,它就是实例化的意思,也就是分配内存地址。我们定义了一个Person类型的变量p1,并使用new关键字进行实例化,并将内存地址返回给变量p1。我们之前说过,类是引用数据类型,因此变量p1就是引用数据类型,它的内部就是内存地址,通常情况,我们称p1就是对象。通过对象p1才能访问它的属性和方法。在接下来的代码中,我们给对象p1赋予名称字符串值,然后调用其say方法。

看到这里,大家就明白类的使用了。类就是一个数据类型模板,使用它定义的变量就是该类的一个对象,多个对象(p1和p2)的数据是不一样的,行为是相同的。

还有一个需要注意的是,在上述代码中,虽然是一个Java源程序,但是经过javac编译后,确实两个class文件,一个是Hello.class字节码,另一个是Person.class字节码文件。我们使用java运行的时候,是运行的Hello类(有入口main方法),而不是Person类。

我们之前在结构化程序设计中讲过,方法是功能的基本单位。那么在面向对象的程序设计中,类就是功能的基本单位,类不仅仅包含方法,还包含数据。因此,类就不仅仅代表了单一功能的实现,而是更高一个层面的封装。在之前的程序化设计中,我们关注的是功能,因此需要对方法进行统一的规划。但是现在,我们关注的不仅仅是功能,更多的是跟数据处理相关的业务逻辑,这样就会将一些紧密联系的业务实现统一到一个类中。这样的设计也符合我们日常所说的“高内聚,低耦合”的原则。这个也是我们“封装”思想的体现。我们在任何地方使用该类的时候,就需要声明实例化它,然后调用它的方法即可,当然我们也可以访问它的数据属性,也是可以的。

接下来,我们来讲解一下Java的包,关键字是package,简单理解就是文件夹。我们都知道,window文件夹中不能存储同类型同名称的文件。也就是说,我们不可以在同一个目录下,创建同名的Java源程序文件。在我们日常编程中,文件名以及类名重复是很难避免的,因此,Java就通过package包来解决这个问题。它的表现形式看似是一个文件夹而已,实际也包含了类的加载。

Java的package包解决了类名重复的问题,其实就是类名相同,包名不同而已。当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。在我们使用Java API或者其他第三方的API开发的时候,我们首先做的就是使用import来引入包路径。

我们来看以下Java包的简单使用的代码:

package com.java;

public class Hello {
	
	public static void main(String[] arg){
				
		System.out.println("hello");
	}
}

我们在原来代码的基础上,增加了 package com.java; 代码,它的意思就是说,当前Hello的类文件属于“com.java”这个包下,其实就是com文件夹下的java文件夹下。一般情况下,在创建package时必须是小写,而且是由一个或多个有意义的单词连缀而成。

这个源文件就需要使用如下命令来编译:javac –d . Hello.java

请注意的是,-d后面有一个点,这个点代表当前目录,也就是Hello.java源文件的目录。编译成功后,就会在当前目录下,生成com目录,然后再在其下生成java目录,最后在java目录下生成Hello.classs字节码文件。当然,我们运行代码命令为:java com.java.Hello

也就是说,我们执行Hello类的时候,必须添加它的包前缀,如下所示:

请注意,我们的java源程序文件Hello.java是在当前目录F:\workspace目录下的。我们在源程序中指定 package com.java; 的意思是让我们的字节码文件位于F:\workspace\com\java目录下。因此,当我们执行编译命令之后,就会在F:\workspace\com\java目录下生成Hello.class文件了。也就是说,我们的源程序文件Hello.java并不需要放置到\com\java下,当然在类似于Eclipse的集成开发环境中,源程序文件也是按照包路径进行管理的。其实,应该按照这种方式管理源程序和字节码程序,两者保持一致。当然,为了方便编译,我们就不这样做了

当然,上文代码中,仅仅这样的包使用,并不是我们真正的目的。我们肯定是希望通过包来分层管理我们的所有类文件,我们不可能将所有的类文件全部统一放在同一个目录下。接下来,我们将上文中的Person类放置到包“com.java.test”下。说白了,我们就是想把Person类放置到com/java/test目录下,代码如下:

package com.java.test;

// 定义类
public class Person {
	
	// 定义数据成员(属性)
	public String name;
	public boolean sex;
	public int age;
	
	// 定义方法成员
	public void say(){
		
		System.out.println("I am " + name);
	}
}
package com.java;
import com.java.test.*;

public class Hello {
	
	public static void main(String[] arg){
		
		// 声明并实例化类
		Person p1 = new Person();
		p1.name = "zhangsan";
		p1.say();
	}
}

如上图所示,我们制定了两个源文件,一个是Person类,一个是Hello类,两个源文件在同一目录下(上文提到,我们暂时不按照包路径管理源文件)。接着,我们编译运行他们:

请大家注意,我们在Hello类中是如何使用Person类的,主要使用:import com.java.test.*;的使用,import是Java的关键字,意思是引入这个包(命名空间),这样我们就可以在Hello类中直接使用Person类了。当然,这个import并不是必须的,如果我们不import的话,我们可以按照”com.java.test.Person”的方式使用Person类,显然这样写太繁琐了。

接下来介绍Java的访问权限修饰符。Java中有四种访问权限,分别是public、protected、包访问权限(默认)、private,如果省略了访问修饰符,那默认访问权限为包访问权限。 这四种权限从最大权限到最小权限分别是: public  >  protected  > 包访问权限 >  private

我们可以使用访问权限修饰符来修饰类和它的成员。但是,一般情况下,我们都使用Public来修饰类,否则它无法被import使用,写一个不能被使用的类,没啥意义啊,除非像本章节开始的部分,将Person类和Hello类写到一个文件中,或者Person类文件和Hello类文件在同一包路径下,这样Person类就可以默认不写Public了,而且Hello类中也可以直接使用它,而不需要import引入。默认不写Public的类只能在本包内部访问使用。其实,java的访问权限更多的是适用于类的成员。我们大概说明一下Java的四种访问权限的范围:

private:        只有本类可以访问,其他所有类都无法访问这个成员。

protected:     只有本类和其子类可以访问,其他所有类不可访问这个成员。要理解protected权限,就需要了解继承,关于继承的知识,我们在后续章节中介绍。

包访问权限:当前包的所有类都可以访问这个成员。

public:         任何一个类都可以访问到这个成员。

总起来说,Java的包机制是我们管理类文件的一种组织方式,搭配访问控制符的话,可以让我们的程序更加完全。我们只需要将提供给外界的功能修饰为public即可。

最后我们来说以下类中的数据成员。我们知道,普通变量在定义的时候,可以初始化赋值。那么类在定义的时候,数据初始化如何进行呢?这就涉及到类的一个特殊的方法,构造方法。

这个构造方法特殊的原因在于,它与类名一样,且在new对象的时候自动调用。这样的话,我们就可以在该方法中对类的数据成员进行初始化了。如下所示:

package com.java;

public class Hello {
	
	public static void main(String[] arg){
		
		// 声明并实例化类
		Person p = new Person();
		System.out.println(p.age);
	}
}

// 定义类
class Person {
	
	// 定义数据成员(属性)
	public String name;
	public boolean sex;
	public int age;
	
	// 定义构造方法
	Person(){
		
		age = 10;
		name = "name";
		sex = false;
	}
	
	// 定义方法成员
	public void say(){
		
		System.out.println("I am " + name);
	}
}



在上述代码中,我们定义了这个构造方法,在该方法中,我们将数据成员变量都初始化数值了。我们在随后的输出代码中可以验证,这些成员变量确实被初始化了。我们之前介绍方法的时候,讲过方法重载的用法,该方式也适用于类中的方法成员,也适用于这个特殊的构造方法。接下来,我们使用另一种方式来初始化类成员,代码如下:

	// 定义有参构造方法
	Person(String name, boolean sex, int age){
		
		this.name = name;
		this.sex = sex;
		this.age = age;
	}

在上述代码中,我们分别定义了三个方法参数,分别对应类里面的三个成员变量。然后在该方法里面,使用三个参数变量分别赋值给三个类成员变量。这里需要注意的是,方法的参数变量名和类成员变量名是一样的,这个时候怎么区分呢?我们使用this.变量名来代表类的成员变量,而普通的变量名就代表参数变量,这样就能区分开了。当然,方法的参数变量名是自己定义的,我们可以完全避开类成员变量的名字,而使用其他的名字,这样就不需要使用this来做区分了。这里,老师故意做的原因,就是想讲解一下this这个关键字。在类中使用this代表的就是实例化后的对象,我们通常在类的方法中使用this关键字。如果我们定义Person p1,那么this就代表p1,如果我们定义Person p2,那么this就代表p2。

定义完有参数的构造方法的时候,我们怎么使用它呢?

		// 声明并实例化类
		Person p2 = new Person("zhangsan", true, 30);
		p2.say();

我们之前也提到过,类在实例化的时候会自动调用构造方法。那么,我们在实例化类的时候,传递对应的参数,就会调用对应参数的构造方法啦。就是这么的简单。

接下来,再介绍一个特殊的方法:析构方法,对应的就是finalize()。这个方法会在类对象被销毁的时候自动调用,而且这个方法是没有参数的。一般情况下,我们都会在该方法中去释放类对象持有的一些特殊资源。这个方法其实非常的简单,这里主要想给大家提一下Java的垃圾回收机制。类对象什么时候被销毁呢,这个取决于Java解释器,也就是Java运行时,更具体说就是JVM(Java虚拟机)。我们知道Java的程序是运行在JVM上面的,因此类对象何时被销毁(调用finalize方法),当然是由JVM来决定的。Java垃圾回收机制采用引用计数算法来判断一个对象是否被回收。该算法其实就是通过判断对象的引用数量来决定对象是否可以被回收的。Java的垃圾回收机制可以有效的防止内存泄露、保证内存的有效使用,从而使得Java程序员在编写程序的时候不再需要考虑内存管理问题。

类的对象是引用类型,那么对象在作为方法参数进行传递的时候,也属于引用传递。传递过去的是对象的地址。如果我们使用形参来修改类成员变量的值,那么实参对象的成员变量值也会同步修改,因为形参和实参指向的是同一个内存地址,也就是同一的对象实例。当然,如果我们使用赋值的方式去直接修改对象变量的话,形实参就不是同一个内存地址了。我们在日常编程中,基本不会这样做,大部分就是直接调用类的方法执行特定的功能。

package com.java;

public class Hello {
	
	public static void main(String[] arg){
		
		// 类对象作为方法参数
		Person p = new Person();
		p.name = "zhangsan";
		change(p);
		System.out.println(p.name);
	}
	
	// 类对象作为方法参数
	public static void change(Person p){
		
		p.name = "lisi";
	}
}

// 定义类
class Person {
	
	// 定义数据成员(属性)
	public String name;
	public boolean sex;
	public int age;
	
	// 定义方法成员
	public void say(){
		
		System.out.println("I am " + name);
	}
}

最后,我们来讲解一下static这个修饰符,这个关键字代表静态的,它主要用来修饰变量和方法。那么,对应的变量称之为静态变量,对应的方法称之为静态方法。我们知道类里面的成员(变量和方法)必须实例化对象之后,通过对象才能访问和使用。而static修改过后的变量或方法,可以直接通过类来访问,但是通过对象也能访问。如下所示:

// 定义类
class Person {
	
	// 静态变量:姓
	public static String familyName;
	
	// 静态方法:输出姓
	public static void family(){
		
		System.out.println(familyName);
	}
	
	// 普通变量:名
	public String name;
	
	// 普通方法:输出姓名
	public void show(){
		
		System.out.println(familyName+" "+name);
	}
}

在上述代码示例中,我们在一个类中定义了静态变量和静态方法,普通变量和普通方法。接下来,我们就在main方法中使用他们,代码如下:

		// 直接使用静态成员
		Person.familyName = "zhang";
		Person.family();
		//Person.name = "san";
		//Person.show();

首先,我们直接使用类名去访问静态成员,这是没有问题,可以运行的。但是,如果我们使用类名去访问普通成员的话,这是不符合Java语法的,编译就无法通过,更不用说运行了。

接下来,我们实例化两个变量,来查看静态成员和普通成员的区别:

		// 实例化对象使用
		Person p = new Person();
		p.familyName = "zhang";
		p.name = "san";
		p.family();
		p.show();
		
		// 实例化另一个对象使用
		Person p2 = new Person();
		p.family();

我们发现,我们可以使用实例化后的对象访问普通成员和静态成员,这个是没有问题的。然后,我们实例化另一个对象,直接调用静态方法,结果发现,它输出了”zhang”。我们之前讲过,根据类去实例化不同的对象,他们的数据成员是隔离的,因为处于不同的内存空间中,因此谁也不影响谁。但是,对于静态成员,尤其是静态变量,它属于每一个实例对象。也就是说,任何一个实例化后的对象,都可以访问同一个静态变量。因此,我们说,静态成员不属于对象,它属于类,被所有对象所共享。

另外一个重点内容就是,静态方法里面只能访问静态变量,静态方法也只能调用静态方法,但是静态方法的参数可以是普通的变量。这就是为什么我们在main方法里面调用其他方法的时候,必须将这个方法定义成静态方法的原因啦。当然,普通方法就比较自由了,它既可以访问普通变量和静态变量,也可以访问普通方法和静态方法。

那么,很多学员就可能产生疑问,为什么存在这个东西呢,有这样的东西,它不乱嘛?其实,一般情况下,我们也不会主动跟使用static,但是对于一些特殊的情况,你就会发现,将变量和方法声明为static真的非常的方便,因为它不需要实例化就可以使用。因为,我们在日常开发中,有些功能的实现可以不用建立在对象之上的,可以直接通过类来实现即可。在后续的开发中,我们自然会使用到static,大家自然也就明白了。

本课程涉及的代码可以免费下载:
https://download.csdn.net/download/richieandndsc/85645935

今天的内容就讲的这里,我们来总结一下。今天我们主要讲了Java的类和对象。类就是对现实事物的代码抽象,它包括数据成员和行为成员,其实就是变量和方法。类就是一种引用数据类型,我们可以使用类来实例化一个变量,这个变量就是类的实例对象。我们可以使用这个对象来访问类中变量和方法,不同对象之间的数据是不一样的。接下来就是介绍了Java包的使用,它主要用来组织类文件的层次结构。在包的基础上,我们又介绍了四种访问控制符,类和成员的访问受到访问控制符的限制,它让程序更加完全。最后我们又介绍了类的构造方法,使用它来对类数据成员进行初始化。好的,谢谢大家的收看,欢迎大家在下方留言,我也会及时回复大家的留言的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值