Java篇第四回——类与对象

2022/3/22 1:28:16

本文主要是介绍Java篇第四回——类与对象,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Java的三大特点是封装、继承、多态。
Java的三大特点是封装、继承、多态。
Java的三大特点是封装、继承、多态。
这次我们来讲讲封装

一、类和对象从哪来

1、在买房中学习类与对象

幻想这样一幅场景:你去售楼部买房,销售经理会给你拿一份他们的宣传册,上面印着他们的户型,你喜欢这个户型,就表明你喜欢这“类”房子。

对于上面说的这“类”房子,我们就会把它们抽象出来形成Java中类的概念,这也就是开发者提出类的概念的目的——抽象出同类事物的属性和行为进行封装。在此,类的描述就直接体现了Java的封装特性。

1.1 类的UML图

Java的魅力集中在类,制定好类的属性和行为是编程的第一步。属性就是这个类有什么东西,行为就是这个类能干什么或者我们能让它干什么。要理清这两个东西,我们需要掌握一个至关重要的东西——UML图。如果系统地学习过某种数据库,那理解UML图就很简单了。

UML图是一个框架,一个体现我们观察结果的框架,它可以体现出某一类事物的属性与行为,即上面提到的封装。具体来说,UML图分为三层,如图
在这里插入图片描述
名称层表示这个类的名称,变量层和操作层分别体现这个类的属性和行为。现在我们来试着简单写一下关于某一类房子的UML图。

名称层:假设我们要买汤臣一品的房,那就给这个类起名叫TangChen

变量层:我们日常生活中经常听到别人谈到:“谁谁谁买的房在哪哪哪,多少多少平米,花了多少多少万,在几楼……”先根据这几句话来简单观察一下:

  • 首先我们提到了买房的人,那这就是我们的第一个变量owner
  • 然后说到了地理位置,那这就是我们第二个变量location
  • 紧接着我们就会提到面积房价,那么又诞生了两个变量area和price
  • 有时还会提到楼层,就再加一个变量floor

可以和开头的买房联系,在购置房屋时,这些都是我们要谈妥的最基本问题。

操作层:要让房子自己可以有什么行为还是有那么亿点离谱的,在这个问题中,这个类的基本操作就是我们来买房、定位、定面积、定价、定楼层,也就是给这五个变量赋值,那我们就来写五个设定属性值的方法:

void setOwner(String houseOwner){}			//设定房主
void setLocation(String houseLocation){}	//设定房屋地理位置
void setArea(float houseArea){}			//设定房屋面积
void setPrice(int housePrice){}			//设定房屋价格
void setFloor(int houseFloor){}			//设定房屋所在楼层

综上所述,变量共有5个:owner、location、area、price、floor,每个变量相应的也有设置该变量的操作。

接下来要说的就是这五个变量的数据类型问题。在Java中,我们必须指明每一个变量的基本类型,这样JVM才会正确处理。关于数据类型如果有疑问可以简单参考Java篇第二回——基本数据类型与数组。

owner是房主的名字,所以我们把这个变量的类型置为String。
location是房屋的地址,所以我们把这个变量的类型也置为String。
area是房屋面积,所以我们把这个变量的类型置为float。
price是买房要花的钱,所以我们把这个变量的类型置为int。
floor是房子所在的楼层,所以我们把这个变量的类型置为int。
到这里,变量层要做的工作基本就结束了。

画UML图之前先了解一下UML图的内容写法,如下:
在这里插入图片描述

这就好像是UML图的语法一样,我们依此就可以画出TangChen类的UML图了。如下:
在这里插入图片描述

1.2 看着UML图做类定义

到这,我们就开始可以开始代码部分了,关键字class是用来声明类的,类定义中有两个概念叫做类声明和类体,如下图
在这里插入图片描述

再回过头来看我们的案例,这个TangChen类的类定义如下:

class TangChen{
	//变量的声明最好是一行声明一个
	String owner;
	String location;
	float area;
	int price;
	int floor;
	//方法
	void setOwner(String houseOwner){		//设定房主
		owner = houseWoner;
	}			
	
	void setLocation(String houseLocation){		//设定房屋地理位置
		location = houseLocation;
	}		
	
	void setArea(float houseArea){		//设定房屋面积
		area = houseArea;
	}			
	
	void setPrice(int housePrice){		//设定房屋价格
		price=housePrice;
	}	
	
	void setFloor(int houseFloor){		//设定房屋所在楼层
		floor = houseFloor;
	}		
}

也就是这样,一个class关键字把某一类事物的属性和行为抽象了出来形成了一般的概念,也就是这里的“汤臣一品”房子类。

1.3 由房子类到房子对象

那对象又是什么?对象就是这个类里面的一个实例,也就是这个案例中汤臣一品房子中的某一套房子;在数据结构中,Java对象叫做数据元素,而Java的类叫做数据类型;而Java的某一类对象的一些集合在数据结构中叫数据对象,也就是整个类的子集。要把java中的对象和数据结构中的数据对象分清楚。

对象怎么创建呢?Java中提供了new这个运算符,这个运算符的用法如下:

TangChen tc = new TangChen();

这样我们就创建出了一个名叫tc的对象——可以理解为建造出了一套汤臣一品的房子,这个对象拥有TangChen类的所有属性和行为,这几个属性和行为作为实体被保存在了系统堆中,而new运算符就是把这部分堆的地址加以计算,计算出一个十六进制的数作为tc的引用,来表示(指向)这部分堆,这样,tc对象就可以直接通过点运算符“.”直接访问自己的属性、实施拥有的行为了。这个引用保存在系统栈中。
在这里插入图片描述
到这里,对象就可以通过符号“.”来访问自己的变量和行为了。

二、重要知识点

1、 成员变量相关问题

1.1 隐藏成员变量

类里面定义的变量就叫成员变量或域变量。那怎么会隐藏呢?假设我们写这样一段代码:

class Animal{
	int leg;		//定义动物的腿的数量
	int hand; 		//定义动物的手的数量
	void setLeg(int leg){		//设置腿的数量
		leg = leg;
	}
	void setHand(){				//设置手的数量
		int hand;
		hand = hand;			
	}
}

显然代码是有语法错误的,因为我们在设置动物的腿的数量时,方法中使用的参数名称是leg,而这个类本身拥有的变量名也叫leg,所以计算机并不知道我们在给对象的leg变量赋值时,setLeg()方法中的哪一个leg是参数,哪一个又是成员变量;对于设置手的数量时也一样,并不能分清谁才是成员变量,谁是局部变量。也就是在这种时候,即当方法的参数名称和成员变量的名称一样时,成员变量就会被隐藏,这是就需要靠this关键字来解决这个问题。this关键字就代表了当前对象,可以理解为this本身就保存着当前对象的引用,所以正确代码应该是。

class Animal{
	int leg;		//定义动物的腿的数量
	int hand; 		//定义动物的手的数量
	void setLeg(int leg){		//设置腿的数量
		this.leg = leg;
	}
	void setHand(){				//设置手的数量
		int hand;
		this.hand = hand;			
	}
}

这样的操作仍然可以正常为成员变量赋值。

1.2 成员变量的赋值(注意初始值问题)

说到成员变量赋值,我们还有要注意的一个点。成员变量只能直接赋值或者在方法中赋值,类的定义中不能出现赋值语句,比如下面这样是不合法的:

class Animal{
	int leg;
	leg = 2;		//非法,类体中不能出现
}

只能通过下面两种方式进行成员变量的赋值。

class Animal{			//声明的同时直接赋值
	int leg=2;
}

class Car{				//通过方法赋值
	int mirror;
	void Method(){
		mirror = 3;
	}
}

同时要注意,成员变量是有初始值的,而局部变量没有。成员变量初始值如下
在这里插入图片描述

2、 构造方法

什么是构造方法呢?想想之前买房的例子,我们知道了汤臣一品房屋的基本属性,但是我们需要给这些属性赋值,那有没有一种方式能让我们在创建好对象之后它本身就已经有了我们想要的数值了呢?这种方法就叫构造方法,可以理解为它的作用就是对我们的属性值进行初始化任务。构造方法的写法如下:

class A{
	int num;
	A(参数){
		构造方法中的语句;
	}
}

其中,第三行到第五行就是A类的一个构造方法。
我们可以观察到构造方法的两个基本特点:

  • 构造方法没有返回类型
  • 构造方法的名称和类名相同

当我们没有特意写明构造方法时,Java会提供一个默认的构造方法,这个构造方法没有参数,没有语句;也就是说下面两个代码块是等同的。

class A{
	int num;
}
class A{
	int num;
	A(){
	
	}
}

这样的构造方法构造出来的对象是包含着类中声明的属性与行为的,但是通过这个构造方法对属性没有任何的操作。

当我们要写明构造方法时,Java就不会再默认提供空的构造方法。

class A{
	int m;
	A(){

	}
	A(int n){
		m=n;
	}
}

上面这个例子体现出了两种构造方法,一种是不带参数的构造方法,一种是有赋值语句的构造方法。那么,我们就可以不带参数或者带一个int型参数来创建对象。通过int型参数构建出来的对象的属性值num就会有相应于n的初始值。

class A{
	int m;
	A(int n){
		m=n;
	}
}

然而,用上面这个类创建对象时就必须给出一个int型参数,因为这时Java已经不再提供默认的构造方法了,我们只能使用带参数的构造方法。

说了这么多,那构造方法如何使用呢?我们前面说过tc对象的创建是这样的

TangChen tc = new TangChen();

这里其实用到的就是TangChen类不带参数的,即默认提供的构造方法。
而这里的A类如果要使用带参的构造方法,就要这样使用:

A apple = new A(6);

也就是说,在new后面跟着的就是类的构造方法,我们用这样的构造方法来创建对象,这里A类创建出了一个apple对象,而apple对象的属性m值为n。

3、 类变量与类方法

又回到汤臣一品的案例中,我们假设汤臣一品的所有楼层都是一层一户,且面积相等,即所有对象变量(对象变量就是对象)的area属性值都是相等的。那么在计算机中,我们为每一个房屋对象都在内存中开辟一块空间来存储它们的area值就造成了很大的浪费。这种情况下,我们就可以把area属性值设置为类变量,表示这个类共有的变量。声明方法如下:

把原先类中的

float area;

改成

static float area;

即可。这时整个类的定义如下:

class TangChen{
	//变量的声明最好是一行声明一个
	String owner;
	String location;
	static float area;
	int price;
	int floor;
	//方法
	void setOwner(String houseOwner){		//设定房主
		owner = houseWoner;
	}			
	
	void setLocation(String houseLocation){		//设定房屋地理位置
		location = houseLocation;
	}		
	
	void setArea(float houseArea){		//设定房屋面积
		area = houseArea;
	}			
	
	void setPrice(int housePrice){		//设定房屋价格
		price=housePrice;
	}	
	
	void setFloor(int houseFloor){		//设定房屋所在楼层
		floor = houseFloor;
	}		
}

这样创建出来的对象都有一个共同的变量area,从创建第一个对象开始,类变量就开始存在,后面创建的对象就不必再为它们开辟新空间存储area值了。所以,在主程序中,一旦某一个对象改变了area的值,那么其它用同样类创建出来的对象的area值都会随之改变。

注意:当成员变量中有了类变量,成员变量就会被分为两类,一种是没有被static声明的实例变量,另一种就是类变量。

类似地,我们用static声明的方法就叫类方法。类方法的入口是在字节码被载入内存后就分配好的,在没有对象被创建之前就已经存在,所以它的特征是不能操作实例变量,而且在对象被创建出来之前也不能操作类变量,如果想让某一个类多出一个方法,而这个类又不用操作实例变量时,就可以将它定义为类方法。含有类方法的类创建的对象就不用再为这个方法分配多余的内存空间了(对于方法指的是方法入口地址)。

类变量和类方法都可以直接通过类名来访问。

class People{
	static void speak(){
		System.out.println("我是人类");
	}
	public static void main(String[] args){
		People.speak();
	}
}

上面这段程序的运行结果就是输出“我是人类”。其中,
public static void main(String[] args){}
就是主程序,整个Java程序都从在里运行。含有主程序的类叫做主类,即这里的People类就是一个主类。

4、 方法重载Override

我们几乎每天都会用到数学中的加法运算,不难思考到,两个数的加法运算用代码是这样写的:

int add(int a, int b){
	return a+b;
}

而如果我们既需要两个数相加的方法,也需要三个数相加的方法,那又该怎么做呢?这里就用到了方法重载。

方法重载指的是同一个类中两个方法的方法名相同,而参数类型不同或参数个数不同。返回类型可以随意一些。

那对于上面的例子我们就可以同时在类中定义两个方法:

class A{
	int add(int a, int b){
		return a+b;
	}
	int add(int a, int b, int c){
		return a+b+c;
	}
	public static void main(String[] args){
		int a = A.add(2,3);
		int b = A.add(2,3,4);
	}
}

这样,主程序中a的值就是5,b就是9。这就是方法的重载。
同时,我们还要注意,不能写出有歧义的重载方法。比如

class A{
	static int add(int a, int b){
		return a+b;
	}
	static float add(int a, float b){		//注意这里的变化
		return a+b+b;
	}
	public static void main(String[] args){
		int a = A.add(2,3);
		int b = A.add(2,4);
	}
}

这时我们的主程序编译就会有问题,因为当调用类方法add时,如果传入的是两个int型变量,那计算机就不知道该用哪一个add方法了。

5、 包

什么是包?比如我们现在在桌面上创建了一个文件夹up,在里面又创建了一个文件夹middleOne,在middle里面我们才写了我们的java文件,那么,up就是一个包,middle也是一个包,只不过包也可以有包含的关系。如果不写包语句,那就默认源文件所在文件夹(即当前文件夹)是包,只不过是一个无名包。

5.1 有包名的类的编译与解释

在java文件中,我们可以用package指明当前类所在的路径,中间用逗号隔开,比如我们现在写一个最简单的HelloWorld.java。

package up.middleOne;		//这就是包语句。注意,这里要写上分号
class A{
	public static void main(String[] args){
		System.out.println("Hello World");
	}
}

在这里插入图片描述

用命令行编译的话,只要能在当前目录下递归找到这个主类java文件就可以,比如我们现在就在这个文件夹下面编译,获得字节码文件
在这里插入图片描述
但是,重点来了,用解释器运行的时候必须必须必须在所在最大包的上一层目录,即桌面,也就是程序中package声明的路径的保存位置去解释运行,并且要指明类的包名与类名,中间全用"."隔开,否则就会出错。

在这里插入图片描述

在这里插入图片描述

5.2 import语句

提到包,我们还有一个很重要的关键字——import。

现在我们需要回忆一下在配置java环境时我们设置的classpath值,如果配置有问题可以参考Java篇第一回–Java简介与JDK安装
我们当时是这样设置的:

classpath = jdk/jre/lib/rt.jar;.;

这就是我们谈及import时最基础的东西。

比如说,我们有两个类A和B,A类是已经写好的类,B类是我们正在编写的类而且是主类,而我们有在B类中创建一个A类的对象或者使用A类的类方法等的需求,那就有一下三种情况:
(其中圆柱体代表一个包,正方形表示源文件。)

第一种:二类同径
在这里插入图片描述
A和B在同一个包中,那就可以直接写编程语句了,直接创建对象或使用方法而不用添加其它的语句,这种模式也是我们初学Java自己编程时最常见的。classpath的结尾我们有“.;”这表示当前目录下的文件,我们无需手动引入,因为在字节码被调入内存时,当前目录(源程序所在目录)下的字节码文件都会被默认调入内存。

第二种:引入类库
在这里插入图片描述
Java作为一个很成熟的编程语言,有着大量的写好的类供我们使用,而classpath的值中第一个就是rt.jar。我们用的很多类都是从这个包里引用出来的,因此这个包也有一个很形象的名字——类库。类库中存放的都是编译之后的字节码文件(感兴趣的可以去lib文件夹下面找到自己看看),当我们运行程序时,这些字节码会和我们自己编译出来的字节码同时进入内存。

好像我们在写一些简单的代码时没有用过类库的类,其实不是这样的。在Java中,每一个程序都默认导入了java.lang这个包中所有的类,再次拿上面的HelloWorld来举个例子(注意注释)。

import java.lang.*;		//这句可以不用加,java默认已经引入了
class A{
	public static void main(String[] args){
		System.out.println("Hello World");
	}
}

java.lang这个包是java最基本的一个包,其中就包括了System类,所以我们才能进行系统输出“HelloWorld”。此外,还有很多其它的类,后面再慢慢学慢慢了解。

到这里,我们可以发现一个问题,其实Java中的import引的并不是包,而是包中的类,如果我们需要整个包的话,我们可以通过“包名.* ”这样的方式来引入整个包中的所有类,只是这样有时比较浪费内存,因为我们不可能用完某个包下面所有的类。不管怎么说,这样就可以使用某一个外部的类创建对象了。

第三种:二类同大包而不同小包
在这里插入图片描述
这种情况下,我们就需要使用import进行引包操作了,这和python中的import、C语言中的include作用都是一样的,那这里需要注意的问题就是,我们需要找到A和B所共有的那个包的位置(图中的大圆柱体),在这个位置对B类进行解释运行(这里我们的假设是在B中使用A类,反过来也一样)。下面以耿祥义老师的《Java2实用教程》中Example4_18为例进行说明。
在这里插入图片描述
上面的图左右两边都是等价的,只不过表示方法不同而已。这个例子是说,我们希望在Example4_18类的主程序中使用Triangle类,那我们就可以使用import进行引包,按照上面说的我们需要找到两个类共有的那个包位置,也就是进入ch4目录中去运行Example4_18。这个主类的代码如下:

package hello.nihao;					//包名原则:先写本类的包再写引入的包,即先己后人
import sohu.com.Triangle;					//引入sohu.com包中的Triangle类,注意也要加分号
public class Example4_18{
	public static void main(String [] args){
		Triangle tri = new Triangle();			//可以直接使用Triangle类了
		tri.setSides(30,40,50);
		System.out.println(tri.getArea());
	}
}

可以不去关注Triangle的细节,只需注意这里import后面的路径

在import中最后要注意的是:有包名的类永远不能引入无包名的类,其他情况随意

6、 权限

我们经常会写public class,这个public就是一个权限关键字。
表达权限主要有private -> 友好 -> protected -> public,权限等级依次降低。
友好型就是不写权限的类型,比如直接写 int a ,那a就是一个友好型变量。
要记住:

  • private不出类,友好和protected不出包,public随便用
  • private和protected不能修饰类

解释如下:

private型变量和方法只能在本类中直接访问,出了这个类,再用它创建对象,那这个对象就不能直接访问自己这个变量和方法了,但可以通过其它可以直接访问的方法来操作。

友好和protected声明的变量和方法只能在这个包中直接访问,出了包就不行了,但同样可以通过其它可以直接访问的方法来操作。

public随便用,这个字面意思。

三、结语

虽然隔了很久没有水博客,但这次真的有点肝了,去看《人世间》充电了,886~



这篇关于Java篇第四回——类与对象的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程