01-面向对象(Static关键字)
我们发现一个问题,这两个对象中有相同的属性值,每个对象的内容都是一样的,这样会比较耗费内存空间,我们能不能把相同的部分取出来放在一个地方,谁要用就拿过去用,没有必要每个对象都存一份。
这个时候就需要给这个变量前面加上static。
静态:static。
用法:是一个修饰符,用于修饰成员(成员变量,成员函数)。
当成员被静态修饰后,就多了一个调用方式,除了可以被对象调用外,还可以直接被类名调用。调用方式为:类名.静态成员。
被静态修饰后,country就不再在堆内存当中了。
静态修饰的变量是被对象所共享的。
类名调用好方便!那干脆把所有属性都静态好啦,都可以类名调用!比如姓名什么的。
啊?姓名?emmm...好像有点问题诶。
国籍大家都是一样的,都是中国人,但是姓名却是各自不同的。
我们要明白,事物当中哪些属性是共享的,哪些是特有的。特有的一定要跟着对象来存储。
static特点:
1,随着类的加载而加载。
也就是说,静态会随着类的消失而消失。说明它的生命周期最长。
因此,也不建议把所有有的没的的变量都定义成静态,这样即使对象没了,这些变量还在内存中,是对内存的一种消耗。所以定义静态之前一定要想好,这个东西是不是需要被所有对象所共享?是,定义静态。不是,就别定义静态啦!
2,优先于对象存在。
明确一点:静态是先存在,对象是后存在。
即使没有对象存在,这个静态成员也已经存在了。(没有对象存在的时候,也无法通过对象来调用这个成员变量,就只能通过类来调用,所以就有第四条所说的,可以直接被类名调用)
3,被所有对象所共享。
4,可以直接被类名调用。
实例变量和类变量的区别:
1,存放位置。
类变量随着类的加载而存在于方法区中。
实例变量随着对象的建立而存在于堆内存中。
2,生命周期。
类变量声明周期最长,随着类的消失而消失。
实例变量生命周期随着对象的消失而消失。
静态使用注意事项:
1,静态方法只能访问静态成员。成员包括方法和变量。(如果还没有建立对象,这个时候就没办法访问非静态成员啦,因为它们还不存在呢)
非静态方法既可以访问静态也可以访问非静态。
2,静态方法中不可以定义this,super关键字。
因为静态优先于对象存在。所以静态方法中不可以出现this。
3,主函数是静态的。
静态有利有弊:
利:对对象的共享数据进行单独空间的存储,节省空间。没有必要每一个对象中都存储一份。
可以直接被类名调用。
弊:生命周期过长。
访问出现局限性。(静态虽好,只能访问静态)
介绍一个新的内存空间,方法区:
静态变量既不在堆中也不在栈中,而是在方法区中。方法区中同时也存放着该类的各种方法~(细心的我发现了之前堆内存中都木有画方法,嘻嘻)
02-面向对象(main函数)
为什么主函数是静态的呢?
我们这节来讲一讲。
public static void main(String[] args)
主函数:是一个特殊的函数。作为程序的入口,可以被jvm调用。
主函数的定义:
public:代表着该函数访问权限是最大的。
static:代表主函数随着类的加载就已经存在了。
void:主函数没有具体的返回值。
main:不是关键字,但是是一个特殊的单词,可以被jvm识别。
(String[] args):函数的参数,参数类型是一个数组,该数组中的元素是字符串。也就是说,是存储字符串类型的数组。
主函数是固定格式的:jvm识别。
args的原型是arguments,刚开始是这样写的,因为比较长,所以后来简写成args。
那么,虚拟机到底传入了什么呢?
因此,jvm在调用主函数时,传入的是new String[0];
能不能打印角标为0的元素呢?
越界啦!
刚刚那个0的意思是长度是0,是有0个元素,而不是里面存的东西是0。而0角标代表的是第一个元素。
javac启动了编译器,java启动了底层虚拟机。要使用哪个类,就将这个类的类名作为参数传入虚拟机。传进去的同时,它会调用这个类的main方法,我们试着在后面再加一些主函数的参数:emmm...haha hehe heihei。
虚拟机做了一件事,自动的将类名后面所跟的数据,存入了数组当中。
不信看一看~
不信再看一看~
再玩点更有意思的:
这两个类是独立的,虚拟机可以执行类1主函数,也可以执行类2主函数。我们用虚拟机执行其中一个类的主函数,这个的主函数也可以调用另一个类的主函数。
03-面向对象(静态什么时候使用)
什么时候使用静态?
要从两方面下手:
因为静态修饰的内容有成员变量和函数。
什么时候定义静态变量(类变量)呢?
当对象中出现共享数据时,该数据被静态所修饰。(比如姓名就不是共享数据,他们只是共享属性,都有这个属性,但每个人的姓名值是不一样的。)
对象中的特有数据要定义成非静态存在于堆内存中。
什么时候定义静态函数呢?
当功能内部没有访问到非静态数据(对象的特有数据),那么该功能可以定义成静态的。
比如:
show方法改成静态后直接类名调用就OK啦:
04-面向对象(静态的应用-工具类)
这个例子中,将获取最大值的功能封装在一个函数中,提高了代码的复用性。
但是,不只这个类会用到获取最大值的功能,其他类也会用到。我们就可以把这个功能抽取出来,封装在一个类中,这个类想什么时候用就什么时候用。
静态的应用
每一个应用程序中都有共性的功能,可以将这些功能进行抽取,独立封装,以便复用。
第一次编译后,当前目录中会产生一个ArrayTool.java文件,第二次就不用再编译工具类啦。
如果把这个.java文件删掉,编译就会出错。
下一个问题:我们有必要建立这个对象吗?
不用仔细想,就知道,这个对象,并没有用到ArrayTool类中的特有属性和特有功能。
总结一下:
虽然可以通过建立ArrayTool的对象使用这些工具方法,对数组进行操作。
但发现了问题:
1,对象是用于封装数据的,可是ArrayTool对象并未封装特有数据。
2,操作数组的每一个方法都没有用到ArrayTool对象中的特有数据。
这时就考虑,让程序更严谨,是不需要对象的。
可以将ArrayTool中的方法都定义成static的。直接通过类名调用即可。
直接这样调用就OK啦:
将方法都静态后,可以方便于使用,但是该类还是可以被其他程序建立对象的。
为了更为严谨,强制让该类不能建立对象。
可以通过将构造函数私有化来完成。
到这里,这个工具类的功能基本完善啦。
还有一个细节上的小问题:交换的这个功能是工具类中其他方法需要调用到的,并不对外提供使用,也将它私有化~
总之,能隐藏起来的就隐藏起来,不需要暴露别人不需要知道的东西。
05-面向对象(帮助文档的制作javadoc)
接下来,将ArrayTool.class文件发给其他人,其他人只要将该文件设置到classpath路径下,就可以使用该工具类。
但是,很遗憾,该类中到底定义了多少个方法,使用者并不清楚。因为该类中并没有说明书。
工具类做好了,使用者不会使用怎么办呢?这个时候就需要制作说明书。
开始制作程序的说明书。java的说明书通过文档注释来完成。
现在文档注释已经写完了,我们需要用另外的工具对文档注释进行提取和输出。
这个工具叫javadoc。
如果想把一个类生成帮助文档,这个类必须是公共类,要加上public修饰符。
这样就成功啦:
生成了好多文件,我们看哪一个呢?一个一个去看吗?哈哈哈。当然是看这个啦:
打开后是酱紫:
注意私有的方法是不会出现在这个文档中的哦。只有两种权限的方法才会被提取到这个文档中,一个叫public,一个叫protect。
点击方法进去是这样的,注意不会显示具体的实现细节哦,直接拿去用就可以了,使用者不用了解它怎么实现的。
注意这里面试没有构造函数的哦,因为构造函数是私有的:
我们修改一下,不私有但是也不加公有,发现说明文档里依然不会显示。
加上public修饰符:
有啦:
一个类中默认会有一个空参数的构造函数。
这个默认的构造函数的权限和所属类一致。
如果类被public修饰,那么默认的构造函数也带public修饰符。
如果类没有被public修饰,那么默认的构造函数,也没有public修饰。
默认构造函数的权限是随着类的变化而变化的。
当然,不要以为如下的构造函数就是默认构造函数:
这是一个空参数构造函数,而不是默认构造函数,这样写叫自定义构造函数。不写的话,构造函数就是默认的。默认是看不见的:
刚刚做的那个文档,叫程序的说明文档或者帮助文档,也叫做API文档。
API是什么呢?
API的全称是:Appliacation Program Interface(应用程序接口)。
Java为了让用户方便使用,也做了一个帮助文档。
所不同的是,我们刚刚做的只有一个类,而这个帮助文档有好多类。而且这些类还被分别存储到不同的包中。
比如说java.lang这个包:
比如Array这个类中的方法:
有了帮助文档真方便!
我们写好了帮助文档,需要把类(.class文件)和帮助文档一并发过去,让用户使用~
06-面向对象(静态代码块)
接下来说一个小知识点,静态代码块。
静态代码块格式:
static
{
静态代码块中的执行语句。
}
比如:
可是它也没有名字,没人调用怎么执行呢?
注意,静态代码块的特点:
随着类的加载而执行,只执行一次,并优先于主函数。
用于给类进行初始化。
当然这个相对少用一点,一般都是给对象初始化比较多。
这样一段程序:
它的执行结果是怎样呢?
我们来分析一下:
因此运行结果的打印顺序为:
b c a over
是不是类必须要有对象才能加载静态代码块呢?
不是的。
定义一个静态方法show(),采用类名调用,想要调用这个方法,就必须加载StaticCode类,因此静态代码块就会被加载。
但这种情况下静态代码块不会被加载,因为类没有存在的意义:
加上以下任意一句,静态代码块就被加载了,因为用到了类中的函数。
可能会有疑惑,第一句用到了类中的什么呢?用到了类里面的默认构造函数呀。
增加一丢丢难度,StaticCode类中这样写:
StaticDemo类中这样写:
运行结果会怎样呢?
运行结果是:a c d
分析一下:
再一个问题:
报错啦。
因为静态代码块,只能访问静态变量。
但在构造代码块中就阔以:
07-面向对象(对象的初始化过程)
Person p=new Person("zhangsan",20);
这句话都做了什么事情?
1,因为new用到了Person.class,所以会先找到Person.class文件并加载到内存中。
2,执行该类中的static代码块,如果有的话,给Person.class类进行初始化。
3,在堆内存中开辟空间,分配内存地址。
4,在对内存中建立对象的特有属性,并进行默认初始化。
5,对属性进行显式初始化。
6,对对象进行构造代码块初始化。
7,对对象进行对应的构造函数初始化。
8,将内存地址赋给栈内存中的p变量。
08-面向对象(对象调用成员过程)
图有点小乱,栈内存中大概就是如下的过程:
这样才对哦:
09-面向对象(单例设计模式)
设计模式:解决某一类问题最行之有效的方法。
java中23中涉及模式:
单例设计模式:解决一个类在内存只存在一个对象。
想要保证对象唯一的原因:
1,为了避免其他程序过多建立该类对象。先禁止其他程序建立该类对象。
2,还为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象。
3,为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式。
这三步具体怎么用代码体现呢?
1,将构造函数私有化。
2,在类中创建一个本类对象。
3,提供一个方法可以获取到该对象。
整体是这样的:
但是有个问题,没有对象就没有办法调用这个方法,所以需要将这个方法用static修饰。而静态方法只能访问静态成员,因此s也需要被static修饰,同时s只是在类内部被调用,所以将它私有化。
内存中是这样的:
不管new多少个对象,都指向同一个对象,内存中只存在一个,保证了对象的唯一性。
运行下面这段程序:
运行结果:
将Single(){}私有化:
运行结果:
对于事物该怎么描述,还怎么描述。
当需要将该事物的对象保证在内存中唯一时,就将以上的三步加上即可。
10-面向对象(单例设计模式方式二)
两种初始化方式:
饿汉式:
先初始化对象。
Single类一进内存,就已经创建好了对象。
懒汉式:
对象是方法被调用时,才初始化。也叫做对象的延时加载。
Single类进内存,对象还没有存在,只有调用了getInstance方法时,才建立对象。
其实先创建后创建也都差不多,区别不大。懒汉式一般实际开发中不常用,什么时候用呢?面试的时候用,哈哈。注意开发一般用饿汉式,因为它安全,而且简单。
而懒汉式存在一个问题,如果一个人调用这个方法,没有问题。如果多个人同时调用这个方法,就会发生问题。
A、B先后进来,A new了一个对象,return了,B也跟着new了一个对象,这个时候对象就不唯一了。
也有解决方法,加一个锁,这样A进去之后,其他就进不去了。
但是用了这个锁,运行效率就降低了,每次要用这个功能,都得先判断一下里面有人没。
有没有既不低效又有用的方法呢?
我们可以用双重判断的方法来完成,最终解决方案:
它相对不低效的原因是减少了用锁的次数,刚开始第一个第二个会判断时候可以开锁,后面的在第一个if(s==null)这里就被拦住了,直接return s。
这个方法虽然不低效了,但是代码量大了。
所以还是饿汉式方法更好用一些。而且饿汉式不会出问题的,因为它里面就一句话,return s。
记住原则:定义单例,建议用饿汉式。
2018-12-3
之前结尾还有一丢丢没有写详细,今天整理完day07的笔记后又将这边结尾处重新补充完整了,这下没遗憾了,嘻嘻。
补这个后记凑一下字数,本篇也5000+啦。