【JAVA进化论】LV2-2:包的定义、类的基本组成、访问权限修饰符

一、包

包说白了就是一个个的文件夹,在java中,所有的类按照其特点,以包的形式进行做区分,包的目录层级命名规则一般是公司的网址倒过来,例如:

图1

如上图所示,.java文件内就是存放的java代码,例如Cat.java文件里面放的就是上一节里那只猫的Cat类的代码,由于现在Cat类有了目录,所以它就需要在头部加上一句话:

代码块1
1
2
3
4
5
package com.bilibili.pet; //这句话位于代码首部,用于标识当前类的java文件在哪个目录下

public class Cat {
//代码省略..
}

因为Cat有了相对目录,这里就需要加上package来标记自己所在的目录层级,否则报错。

再回到图1中,我们看下分包的目的是什么,从图1可以看到,基础包为com.bilibili(这里提一下,java代码里的包目录引用都是以“.”做分隔的,而不是斜杠),然后基础包下细分了俩包,一个是pet(宠物),一个是human(人类),这俩包下分别有两个java文件,pet下放的是Cat和Dog,而human下放的是Boy和Girl,所以其实包就是用来给类分类的(套娃警告),这里可能会晕,因为之前说过,类是用来描述一类事物的单元,那么既然这样,为啥还需要用目录再分割一层做分类呢?这其实是一个分类大小方向的划分,例如,粗略划分人类包下的类可以包含男生、女生、学生、老师,但是像猫狗明显和人类不是一回事,这时候就需要用一种更高层级的东西来做隔离,包就这样产生了,当然这个是没有绝对标准的,只要你喜欢,可以按照你的方式做分包,比如,你仍然可以认为,pet和human两个包可以合并为biology(生物),因为不管你是人类还是宠物,都是生物,所以对于包的划分,是个“哲学”问题,不过先不用担心,等写的多了,自然就会划分了,这一部分你只需要知道包是个什么东西,以及在存在包目录的情况下类要加package声明头即可。

二、如何使用代码定义一个类?

2.1:类的定义和java文件

上一节已经知道Cat类的代码了,只是没有细说一个类该如何定义,这次我们来讲下类是如何定义的,以及它和java文件是什么关系。

首先,新建一个叫Cat.java的文件,然后在这个文件里写入以下代码:

代码块2
1
2
3
public class Cat {

}

保存,这样一个Cat类就就建好了,诶??就这?

是的,我们来拆解和分析一下这段代码:

图2

public是一种权限修饰符,可以控制被修饰内容的访问权限,在介绍完类的所有概念之前,不会涉及它的概念,这里只需要记住,一个java文件允许定义多个class,但只允许有一个public类,而且这个public的类名字一定要和这个java文件的命名一致,比如例子里的public的Cat类就定义在Cat.java里,我们还可以在一个java文件里加多个class,但是public的只能有一个。

代码块2里只是定义了一个空类,通过上一节内容,我们可以知道一个类所包含的内容大概有两大类,一个是属性,一个是方法(也叫函数,我们后面统称方法),所有的类都符合这个结构,类可以不定义任何属性,也可以不定义任何方法,这个没有硬性要求。

2.2:类的属性和方法

上一节介绍过,这一节细分析下。

通过上一节的Cat我们会发现类的属性其实也是各种变量组成的,这种变量在类定义里叫做类的属性(之前也说过,类本身是一种信息模板,而类产生的对象才是我们需要访问的实际数据),而在类产生的对象里叫做对象的成员变量,成员变量在对象内部是可以在任意地方随意访问的,比如Cat类里所有方法都是可以直接访问到对象内的成员变量的。成员变量的作用域就是整个类对象,如果别的地方需要用到它们,则需要通过其对应的引用变量进行访问。

我们现在再把Cat类改造下:

代码块3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//猫类的定义
public class Cat {

//性别:0雌,1雄
private int gender;
//年龄
private int age;
//毛色:1黑,2白
private int color;
//昵称
private String name;

//构造方法,用于构造一个类对象
public Cat(int gender, int age, int color, String name) {
//通过传入的参数,初始化自己对象内的每一个成员变量
this.gender = gender;
this.age = age;
this.color = color;
this.name = name;
}

public void cry() {
if (this.tooOld()) { //通过本类的私有方法来判断当前这只猫是否年龄太大了
System.out.println("叫不动了...");
} else {
System.out.println("喵喵喵~");
}
}

public void eat() {
System.out.println("要恰饭的嘛");
}

public void emm() {
if (this.tooOld()) {
System.out.println("骚不动了...");
} else {
System.out.println("猪肉卷和千层面不存在二选一,我全都要!");
}
}

public void run() {
if (tooOld()) {
System.out.println("跑不动了...");
} else if (age > 10) {
System.out.println("奔跑速度:10km/h");
} else {
System.out.println("奔跑速度:15km/h");
}
}

//这个方法用来介绍自己
public String intro() {
String gender = this.gender == 0 ? "母" : "公";
String color = this.color == 1 ? "黑色" : "白色";

//将这些属性值拼成一句间接返回出去
String result = "我是一只名叫" + name + "有着" + color + "皮毛的" + age + "岁" + gender + "猫";

return result;
}

//给猫加年龄的方法
public void plusAge(int plusCount) {
this.age += plusCount;
}

//返回猫当前的年龄
public int getCurrentAge() {
return this.age;
}

//private修饰的私有方法,私有方法仅允许本类内访问
private boolean tooOld() {
return this.age > 15; //如果年龄超过15岁,则返回true
}
}

我们改造了之前的旧方法,让猫的行为根据自己的年龄的不同再次发生改变,此外新增了intro方法用来返回出去自己的简介,plusAge用来增加猫的年龄,getCurrentAge用来返回猫当前的年龄,tooOld使用了private修饰,跟public一样属于访问权限,加了这个访问权限意味着在别的地方通过对象无法调用该方法,只能由对象内部触发调用,后面会着重介绍访问权限修饰符。此外,方法又分为两种,一种是有返回值的,比如getCurrentAge,一种是没有返回值的,比如plusAge,它们的区分方式在于访问权限修饰符public后面是void还是其它类型,如果是void则说明此方法没有返回值,否则就是返回值对应的类型(如int、long,返回结果为引用变量的话,则为对应的类,例如String、Cat等)。

现在拿着这个Cat类来做个试验,我们之前有说过,访问对象里面的方法或者属性时,需要通过引用变量来进行访问,引用变量又是通过初始化类对象来的,初始化类对象之前说过,通过new后面加上构造方法来进行构造一个类对象:

代码块4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static void main(String[] args) {
//造出两只猫,现在我们有了两个引用变量,可以利用它们访问对象实体了
Cat cat1 = new Cat(0, 1, 0, "咲川");
Cat cat2 = new Cat(1, 5, 1, "龟田志斌");

String cat1Into = cat1.intro(); //这个方法会返回对应猫咪的简介,这里用一个变量接收
String cat2Into = cat2.intro();
System.out.println(cat1Into); //打印第一只猫的简介
System.out.println(cat2Into); //打印第二只猫的简介

System.out.println("------------------------------------"); //为方便观察,这里用分割线隔开

cat1.emm(); //由于这里cat1对应的猫对象里的age为1,因此这里仍然可以打印迷惑语录
cat2.emm(); //由于这里cat2对应的猫对象里的age为5,因此这里仍然可以打印迷惑语录

System.out.println("------------------------------------");

cat1.plusAge(15); //让川桑老15岁,由于plusAge的逻辑为原age加上传入的年龄增量,所以现在的咲川应为16岁
cat2.plusAge(6); //让龟田老6岁,由于plusAge的逻辑为原age加上传入的年龄增量,现在的龟田志斌应为11岁

//通过getCurrentAge把二位的年龄拿到
int cat1Age = cat1.getCurrentAge();
int cat2Age = cat2.getCurrentAge();

System.out.println("咲川当前年龄为:" + cat1Age + " " + "龟田志斌当前年龄为:" + cat2Age); //这里再次打印出二位当前的年龄值

System.out.println("------------------------------------");

//再触发一下它们的迷惑行为
cat1.emm(); //由于咲川现在为16岁,符合tooOld为true,因此这里打印骚不动了...
cat2.emm(); //由于龟田志斌现在为11岁,符合tooOld为false,因此这里仍然打印迷惑语录
}

上方代码块打印:

代码块5
1
2
3
4
5
6
7
8
9
10
我是一只名叫咲川有着白色皮毛的1岁母猫
我是一只名叫龟田志斌有着黑色皮毛的5岁公猫
------------------------------------
猪肉卷和千层面不存在二选一,我全都要!
猪肉卷和千层面不存在二选一,我全都要!
------------------------------------
咲川当前年龄为:16 龟田志斌当前年龄为:11
------------------------------------
骚不动了...
猪肉卷和千层面不存在二选一,我全都要!

通过这个例子我们会发现:

  1. 类在产生对象以后,可以利用引用变量来操纵它,具体引用变量可以调用对象的方法,甚至可以直接访问它的属性(能不能访问具体看访问权限修饰符,publicprivate等,后面讲)
  2. 类在产生对象后,每个对象都持有自己的属性和方法,互不干扰
  3. 对象内的方法可以访问对象内的任意成员变量以及其他方法(无视访问权限修饰符)
  4. 对象内某些方法可以改变成员变量的值,比如plusAge,它会重新给自己的成员变量赋值,而有些方法则不会,例如intro,它仅仅是拼接自己内部的成员变量

2.3:构造器

构造器是类里的一个特殊的方法,注意,它也是一个方法,构造器特殊的原因在于它有且仅被触发一次,就是在类构造类对象的时候,一般你定义了一个类如果不写构造器,那就会默认一个构造器,例如代码块2里,虽然是个空类,但它有一个隐藏的构造器,即:

代码块6
1
2
3
4
5
6
public class Cat {

public Cat() { //不定义构造器,则默认有这样一个构造器,由于它并没有什么实际意义(因为{}内没有任何实质性的逻辑代码),所以java语法里允许不写

}
}

而在代码块4里的构造器没有省略,因为它是有实际意义的,它的意义在于给成员变量赋值:

代码块7
1
2
3
4
5
6
7
8
//构造方法,用于构造一个类对象
public Cat(int gender, int age, int color, String name) {
//通过传入的参数,初始化自己对象内的每一个成员变量
this.gender = gender;
this.age = age;
this.color = color;
this.name = name;
}

通过上面的代码我们可以发现构造器的特点,名称和类名一致,没有标记返回结果的修饰符(void关键词或其他返回类型),且在构造一个对象时必须要通过调用构造方法完成。

三、访问权限修饰符

3.1:访问权限符介绍

在前面所介绍的类定义、类属性定义、类方法的定义的最前端,都会有类似publicprivate这种修饰符,它们的意义和作用是什么呢?

首先我们来介绍一下它们,它们是用来控制访问权限的修饰符,是编译器做访问语法检查时所做的判断依据,一般放到代码首部,可以用来修饰类、类的属性变量、类的方法,用来约定它们的访问权限。

表1

关于表里父子类相关内容需要了解过继承后再去看

ps:这个访问权限,仅仅是根据类定义的上下文来判定的。

单看表和描述是很难发现它们的区别的,现在我们来举个具体的例子,我们现在再来改造下Cat类(修改了一些内容的访问权限,顺带精简了下代码):

代码块8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Cat {

public int gender; //gender字段改为public访问权限

protected int age; //age字段改为protected访问权限

int color; //color字段改成默认(default关键字可以省略不写)的访问权限

private String name; //name仍然是private访问权限

public Cat(int gender, int age, int color, String name) {
this.gender = gender;
this.age = age;
this.color = color;
this.name = name;
}

public void eat() { //eat方法仍为public访问权限
System.out.println("要恰饭的嘛");
}

protected void emm() { //emm方法改为protected访问权限
if (this.tooOld()) {
System.out.println("骚不动了...");
} else {
System.out.println("猪肉卷和千层面不存在二选一,我全都要!");
}
}

void run() { //run方法改为默认访问权限
if (tooOld()) {
System.out.println("跑不动了...");
} else if (age > 10) {
System.out.println("奔跑速度:10km/h");
} else {
System.out.println("奔跑速度:15km/h");
}
}

private boolean tooOld() { //tooOld方法仍为private访问权限
return this.age > 15; //如果年龄超过15岁,则返回true
}
}

3.2:举例说明:同一个类

ok,现在让我们基于改造好的Cat类,从第1列的内容试起:

图3

我们在写代码的时候也发现了,同一个类内部的所有属性、方法无视其访问权限,直接获取&访问即可,现在再来试试这样:

图4

由上图可以看到,同一个类定义内,如果存在另外一个相同类产生的对象,仍然可以通过其引用变量无视访问权限进行访问,因为它们属于同一个类,分析如下:

首先cry是Cat类的方法,接收的参数是Cat类产生的引用变量,因此在cry内可以无视访问权限,直接通过cat这个变量访问到所有的内容。

3.3:举例说明:同一个包

我们现在再在同一个包创建一个Test类,在其main方法里做实验:

图5

这是符合预期的,Test和Cat不属于同一个类定义,但它们在同一个包内,因此除了private的内容,均可访问。

3.4:举例说明:同包/不同包的父子类

后面讲继承的时候会讲。

3.5:举例说明:不同包的类

3.3相反,我们现在新建一个跟Cat不同包的Test类,然后用其main方法做实验:

图6

这是符合预期的,Test和Cat不属于同一个类定义,且它们不在同一个包内,因此除了public的内容,均不可访问。