上一篇文章已经初步认识了类和对象,今天我们来在此基础上进一步地学习类和对象
在实际开发过程中,我们要写很多的类,但是在前面我们已经说了一个.java文件最好只写一个类,那有很多类地时候我们怎么解决,这个时候就有了包这个概念
一. 包
1.作用
包是Java中组织类的一种方式,用来防止类名冲突和提高代码的可读性。我们可以把包理解成一个文件夹,这个文件夹既可以存放别的包,也可以存放.java文件(类)。
对着这张截屏再来解释下上面包的作用,java-SE-basic是项目名称,src是源代码根目录,里面的day01,day02是包名,最下面day05包里面存放的是.java文件(类)
2.自定义包
一个项目的所有包都在src里面,在idea中右击src,选择新建,选择软件包,输入包名回车即可
按照上面的操作,新建Test1包和Test2包
这个时候在Test1或者Teste2里面新建的.Java文件(类)最上面会有一行package语句,提示当前类在那个包中,以Test2里面的animal类为例
3.如何使用非当前.java文件中的类
基于上面创建的Test1包和Test2包来讲这个问题
<1>.要使用的类不和当前文件位于两个包中(不同包)
如上图 ,我们想在example这个Java文件里面使用Test2包里面的animal类创建dog对象,那就需要做一件事情——导包,语句是import 包名.类名
下面看代码示例
可以看到在example类里面我们成功用animal类创建了dog这个对象,并对成员变量赋值,调用成员方法,这里我们先不管成员方法变量前面的public,这个是访问权限修饰符,下面会讲到
<2>.父子包
wenju包是Test1包的子包,example类在父包Test1下,WenJu类在wenju包里面,这个时候我们如果想在example类里面用WenJu类创建pencil对象,还是做导包这件事情,语句是import 父包名.子包名.类名
运行结果符合我们的预期,使用import语句导包还有一种比较简单的写法,
import 包名.*,这表示把包里面的所有类都导过来,这个时候我们突然想到一个问题,如果有两个·不同的包,但是它们里面有名字相同的类,这个时候如果我们把这个两个包里面名字相同的类都导过来,会出现什么情况?怎么解决?
4.同时导入不同包中的同名类
java中已经提供了许多类供我们使用,例如之前输入使用的Scanner类,我们以Java提供的Date类为例,java.util包和java.sql里面都有Date类,我们新建一个java文件,在里面同时导入这两个包的Date类并创建对象
程序出现警告,原因就是虽然不同的包里面可以有同名的类,但是我们不能用import同时导入两个同命类,解决方法是一个类用import导入,另一个写完整包的路径(全限定名),这算是另一种导包的方法,下面看代码示例
我们看到这下就没有报错了,问题解决。写完整包的路径(全限定名)的格式就是把原来写在import后面的语句直接当做类名,拿来创建对象
二.封装
1.概念
面向对象语言的三大特点:封装,继承,多态。类和对象就是用来封装的。封装的概念:将数据和操作数据的方法有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对向进行交互。简单的理解就是,我们每个人都有家,这个家隐藏了我们在家里的行为(睡觉,吃饭等)和属性(财产等),我们通过家的大门,门铃,猫眼,(接口)来和别人进行交流(对象交互)
下面看代码示例
在这里插入代码片publicclasshome{publicStringname;privatedoublevalue;//value只能在home类里面使用publicintpeopleNum;//peopleNum的可以在类外使用//先不管这里的public和private,下面会讲}publicclassHome{publicstaticvoidmain(String[]args){home home1=newhome();home1.name="HHH";home1.peopleNum=10;System.out.println(home1.name+"家有"+home1.peopleNum+"个人");}}最终会打印HHH家有10个人
由于value被private修饰,只能在Home类里面使用,那如果我们要设置在Test类里面设置或者使用value该怎么做?这个时候我们就要手动写接口了(setvalue,getvalue),看示例
在外部类Test里面我们无法通过类名.value来访问value,只能通过setValue和getValue这两个接口来访问value,至于setValue和getValue是怎么操作value的我们在Test里面并不知道,这就体现了封装的特性, 套壳子,遮细节
2.访问限定符
封装的好处之一就是提高代码的安全性,说到安全就会有有权限这个说法,有些时候一些成员变量和成员方法我们需要它们只能在特定的地方使用,比如上面的的value只能在它所在的Home类里面使用,Java提供了四种限定符来实现这种功能。看下图
protected会在继承中讲到,default表示什么修饰符都不加。
三.static关键字
static关键字可以修饰成员变量和成员方法,
1.static修饰成员变量
定义一个People类,,两个人的家是同一个,我们怎么定义家这个属性?
在这里插入代码片classPeople{publicStringname;publicStringhomename;}publicclassTest{publicstaticvoidmain(String[]args){Peoplep1=newPeople();Peoplep2=newPeople();}}这样定义,这两个人都有家这个属性,但是家是它们两个人共有的,并不单独属于谁,因此这样定义是有点瑕疵的,而static就可以解决这个问题
在这里插入代码片classPeople{publicStringname;publicstaticStringhomename="1-101";}publicclassTest{publicstaticvoidmain(String[]args){Peoplep1=newPeople();Peoplep2=newPeople();}}这个时候homename这个变量就不再属于某个具体的对象了,而是属于People这个类,被这个类所有的对象共享
static修饰成员变量的特性总结:
- 属于类,不属于对象:static修饰的成员变量(也叫类变量/静态变量)在内存中只有一份,被该类的所有对象共享,任何一个对象修改了它,其他对象看到的值都会改变。
- 随类加载而创建:静态变量在类加载时就被初始化,不依赖任何对象的创建,即使没有new对象,也可以通过
类名.变量名直接访问。 - 访问方式:既可以通过
对象名.变量名访问,也可以通过类名.变量名访问,推荐使用后者,更能体现它属于类的特性。 - 生命周期:静态变量的生命周期与类相同,从类加载开始,到程序结束(类卸载)才销毁,比普通成员变量(实例变量)存活时间更长。
- 存储位置:静态变量存放在方法区(JDK8及以后是堆中的元空间),而普通成员变量存放在堆中每个对象自己的空间里。
下面通过代码来验证这些特性:
classPeople{publicStringname;publicstaticStringhomename="1-101";}publicclassTest{publicstaticvoidmain(String[]args){Peoplep1=newPeople();Peoplep2=newPeople();// 1. 通过对象访问System.out.println(p1.homename);// 1-101System.out.println(p2.homename);// 1-101// 2. 通过类名直接访问(推荐)System.out.println(People.homename);// 1-101// 3. 验证共享性:p1修改,p2和类名访问都跟着变p1.homename="2-202";System.out.println(p2.homename);// 2-202System.out.println(People.homename);// 2-202// 4. 不创建对象也能访问静态变量People.homename="3-303";System.out.println(People.homename);// 3-303}}运行结果:
1-101 1-101 1-101 2-202 2-202 3-303可以看到,无论通过哪个对象修改homename,所有对象以及类名访问到的值都保持一致,这正是静态变量「共享」特性的体现。
2.static修饰成员方法
static不仅可以修饰成员变量,还可以修饰成员方法,被static修饰的方法叫做静态方法(类方法),它和静态变量一样,属于类而不属于某个具体的对象。
static修饰成员方法的特性总结:
- 属于类,不属于对象:静态方法属于类本身,不依赖任何对象,即使没有创建对象,也可以通过
类名.方法名()直接调用。 - 访问方式:既可以通过
对象名.方法名()调用,也可以通过类名.方法名()调用,推荐使用后者,更能体现它属于类的特性。 - 静态方法中不能直接访问非静态成员:静态方法在类加载时就存在,而此时对象可能还没有创建,所以静态方法中不能直接访问实例变量和实例方法,只能访问静态成员。
- 静态方法中不能使用this和super:this和super都代表某个具体的对象,而静态方法不依赖对象,所以无法使用。
- 非静态方法可以访问静态成员:实例方法中既可以访问实例成员,也可以访问静态成员,因为对象创建时类一定已经加载了。
- 静态方法只能访问静态变量:静态方法中只能直接访问静态成员(静态变量和静态方法),不能直接访问实例变量和实例方法,因为静态方法不依赖对象而存在,而实例成员必须依赖对象才能访问。下面通过代码来验证这些特性:
classPeople{publicStringname;// 实例变量publicstaticStringhomename="1-101";// 静态变量// 静态方法publicstaticvoidshowHome(){// 静态方法中可以直接访问静态变量System.out.println("家在"+homename);// 下面这行会报错:静态方法中不能直接访问非静态成员// System.out.println(name);}// 非静态方法publicvoidshowName(){// 非静态方法中既可以访问实例变量,也可以访问静态变量System.out.println("我叫"+name+",家在"+homename);}// 静态方法中调用静态方法publicstaticvoidshowAll(){showHome();// 静态方法可以直接调用另一个静态方法// showName(); // 这行会报错:静态方法中不能直接调用非静态方法}}publicclassTest{publicstaticvoidmain(String[]args){// 1. 不创建对象,直接通过类名调用静态方法(推荐)People.showHome();// 家在1-101// 2. 通过对象调用静态方法(不推荐,但可以)Peoplep1=newPeople();p1.showHome();// 家在1-101// 3. 非静态方法必须通过对象调用p1.name="张三";p1.showName();// 我叫张三,家在1-101// 4. 静态方法中调用静态方法People.showAll();// 家在1-101}}运行结果:
家在1-101 家在1-101 我叫张三,家在1-101 家在1-101可以看到,静态方法只能直接访问静态成员(静态变量和静态方法),不能直接访问实例成员。如果静态方法中想访问实例成员,必须先创建对象,再通过对象来访问,这一点在写代码时要特别注意。
运行结果:
家在1-101 家在1-101 我叫张三,家在1-101可以看到,静态方法可以直接通过类名调用,不需要创建对象;而普通方法必须通过对象来调用。同时,静态方法中只能访问静态成员,这一点在写代码时要特别注意。