本篇笔记主要参考阿里巴巴集团技术团队于2022年整理而成的黄山版《Java开发手册》,并结合本人浅薄的工作经验和学习的积累整理而成。
《Java 开发手册》是阿里巴巴集团技术团队的集体智慧结晶和经验总结,经历了多次大规模一线实战的检验及不断完善,公开到业界后,众多社区开发者踊跃参与,共同打磨完善,系统化地整理成册,当前最新的版本是黄山版。
阿里巴巴集团技术团队整理的原版《Java开发手册》下载链接在文档最下方。
现代软件行业的高速发展对开发者的综合素质要求越来越高,因为不仅是编程知识点,其它维度的知识点也会影响到软件的最终交付质量。
对软件来说,适当的规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,提升协作效率,降低沟通成本。代码的字里行间流淌的是软件系统的血液,代码质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升系统稳定性,码出质量。
作用:告诉程序员,数据在程序中的书写格式。
Java中的字面类型有六种,分别是:整数,小数,字符,字符串,布尔值和空值。
Char字符形只能存放一个字符,放在单引号里面。char ch = ‘A’ 和char ch =65是一样的。字符串可以存放多个字符,放在双引号里面,String str =“ABC”
变量是用来记录程序中数据的,其本质上是内存中的一块区域,你可以把这块区域理解成一个小盒子。如下图,当执行int age = 18; 这句代码时,JVM会在栈内存中申请一块区域,在这个区域中存储了一个整数18,给这个区域取的名字叫age;相当于在盒子中存了一个数据18,这个盒子的名字是age,当我们需要用到age时,就从盒子中把盒子中的数据取出来。
1.变量定义在哪个{}范围就只在哪个大括号内有效。变量有效范围称为变量的作用域。定义long类型的变量时,需要在整数的后面加L(大小写均可,建议大写)。因为整数默认是int类型,整数太大可能超出int范围。定义float类型的变量时,需要在小数的后面加F(大小写均可,建议大写)。因为浮点数的默认类型是double, double的取值范围是大于float的,类型不兼容。
1.标志符就是由一些字符、符号组合起来的名称,用于给类,方法,变量等起名的规矩。这些规矩你必须遵守,不遵守就会报错。
2.标志符可以由字母、数字、下划线_、美元符($)组成,但不能包含%,空格等其他特殊字符,不能以数字开头,区分大小写,不能是java关键字。
3.补充:阿里开发手册中的约定:所有编程相关的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
4.所有的名字要见名知义,便于自己和别人阅读。按照这样的方式取名字会显得更加专业。
5.方法名、参数名、成员变量、局部变量:满足标志符规则,建议全英文、有意义、首字母小写,满足“小驼峰模式”,例如:int studyNumber = 59。
6.类名,接口名,枚举名::满足标识符规则,建议全英文、有意义、首字母大写,满足“大驼峰模式”,例如:HelloWorld.java。
7.常量命名应该全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
8.抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾,测试类命名以它要测试的类的名称开始,以Test 结尾。
9.POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。
10.杜绝完全不规范的英文缩写,避免望文不知义。
11.接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的 Javadoc 注释。尽量不要在接口里定义常量,如果一定要定义,最好确定该常量与接口的方法相关,并且是整个应用的基础常量。
12.枚举类名带上Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。说明:枚举其实就是特殊的常量类,且构造方法被默认强制是私有。
14.Service / Dao层方法命名规约:
1.获取单个对象的方法用 get 做前缀。
2.获取多个对象的方法用 list 做前缀,复数结尾,如:listObjects
3.获取统计值的方法用 count 做前缀。
4.插入的方法用 save / insert 做前缀。
5.删除的方法用 remove / delete 做前缀。
6.修改的方法用 update 做前缀。
15.领域(一个特定的业务领域,如订单领域)模型命名规约:
1.数据对象:xxxDO,xxx 即为数据表名。
2.数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
3.展示对象:xxxVO,xxx 一般为网页名称。
4.POJO 是 DO / DTO / BO / VO 的统称,禁止命名成 xxxPOJO。
任何数据在计算机中都是以二进制表示的。二进制就是一种数据的表示形式,它的特点是逢2进1。二进制中只有0和1两个数,用二进制表示10进制的1就是1,表示2就是10,表示3就是11。
数据的表示形式除了二进制(逢2进1),八进制(逢8进1)、还有十进制(逢10进1)、十六进制(逢16进1)等。计算机中最小的存储单位是字节(Byte),一个字节占8位(bit),也就是说即使这个数据不足8位也需要用8位来存储。比如存储一个整数6,对应的二进制是110,在计算机中存储是00000110。
字符不是直接存储的,而是把每一个字符编为一个整数,存储的是字符对应整数的二进制形式。
1.字符0对应48,后面的1,2,3,4...9 对应的十进制整数依次往后顺延。2.字符a对应97,后面的b,c,d,e...z 对应的十进制整数依次往后顺延。3.字符A对应65,后面的B,C,D...Z 对应的十进制整数依次往后顺延。
在定义变量时我们首先要声明数据类型的,这里的数据类型是用来规定变量存储什么类型的数据。比如int a = 10; 这里的int就是限制变量只能存储整数。Java的数据类型整体上来说分为两大类:基本数据类型、引用数据类型。
class创建的对象或者数组都是引用数据类型。例如String,HashMap等对象都行引用数据类型。这些对象在创建的时候,首先堆内存开辟存储空间,然后在栈内存中存储对象在堆内存的空间地址。
自动类型转换指的是,数据范围小的变量可以直接赋值给数据范围大的变量,转换顺序:
byte,short,char 三种类型数据在和其他类型数据运算时,都会转换为int类型再运算。
自动类型转换就是数据类型小的数据可以直接赋值给数据范围大的变量。将数据范围大的数据直接赋值给数据范围小的变量需要进行强制类型转换,但答案可能会报错。因为数据范围大的数据,赋值给数据范围小的变量,它有可能装不下;就像把一个大桶的水倒入一个小桶中,有溢出的风险。格式:目标数据类型 变量名 = (目标数据类型)(被转换的数据);
强制类型转换的原理,其实就是强行把前面几个字节砍掉,这就有可能导致数据丢失的风险。
`+` 表示加法:能用于数值计算,也可以用于字符串拼接。
[1] 运算符在左边的话就会先进行自增或者自减后,然后将运算后的值赋给左边的变量。[2] 运算符在右边的话就会先把右边的变量值赋给左边的变量,然后右边的变量再进行自增或者自减。[3] 一个等式有多个相同的自增自减的变量,程序是从左往右执行的,左边执行完成后就赋值给右边的变量。
基本运算符:其实就是“=”号,意思就是把右边的数据赋值给左边的变量。扩展运算符:+=;-=;*=;/=;%=(自动转换为int类型进行计算)。
关系运算符(也叫比较运算符),只能用于数值的比较,不能是字符串等其他非数值。
关系运算符在程序中常用于条件判断,根据条件判断的结果是true还是false,来决定后续该执行哪些操作。
有短路作用(如果左边为假,右边的表达式就不回被执行)。转操作数,如果条件为true,则逻辑非运算符将得到false。
if判断,一般不if(res==flase) 这样写,而是应该if(!res) 这样写。
成立就返回第1个条件的值,不成立返回第2个条件的值。如果返回字符串就要String进行接收,如果返回整数就用int去接收。
* / % + - >,>=, <, <=,==,!=,&&,||,?,=
一行代码有多个运算符,则按照下面的运算符从上到下,从左到右进行运算。和数学运算一样,如果不确定哪个先算,可以加括号控制优先级。
if分支一般的先把不合法的都排除掉,剩下的就是合法的。
switch 分支的作用,是通过比较值来决定执行哪条分支代码。
如果条件判断是某个区间的值,只能通过if来做,如果是等值判断,超过三个条件就应该用switch来做。
非必要,尽量不要在循环体里面创建对象,而是应该在循环体外面创建,因为每一次创建一个对象都会堆内存开辟很多的空间,占用很多内存。
世界最高山峰珠穆朗玛峰高度是:8848.86米=8848860毫米,假如我有一张足够大的它的厚度是0.1毫米。该纸张折叠多少次,可以折成珠穆朗玛峰的高度?
do-while是先执行循环体的语句再进行循环判断,就是循环至少执行一次。
for循环 和 while循环(先判断后执行)。
do...while (先执行后判断)。
for循环和while循环的执行流程是一模一样的:从功能上来说:功能上无区别,for能做的while也能做,反之亦然。从规范上来说:如果已知循环次数建议使用for循环,如果不清楚要循环多少次建议使用while循环。
for循环中控制循环的变量只在循环中使用。while循环中,控制循环的变量在循环后还可以继续使用。
所谓循环嵌套,就是一个循环中又包含另一个循环,两个循环的变量要不一样。循环嵌套执行流程:外部循环每循环一次,内部循环会全部执行完一轮。
break作用:跳出并结束当前所在循环的执行。只能用于结束所在循环,或者结束switch分支的执行,switch的case如果没加break就会造成穿透现象。
continue作用:结束本次循环,进入下一次循环。只能在循环中使用。
导包: import java.util.Random;
创建随机数生成器对象: Random rand = new Random();
生成一个0-99之间的随机整数: int 随机整数 = rand.nextInt(100);
生成随机长整数: long 随机长整数 = rand.nextLong();
返回一个0.0-1.0之间的随机双精度浮点数rand.nextDouble();
返回一个随机的布尔值: boolean nextBoolean()
- 导包: import java.util.Scanner;
- 创建扫描器对象: Scanner sc = new Scanner(System.in);
- 扫描数据_整数: int 变量名 = sc.nextInt();
- 扫描数据_字符串: String 变量名 = sc.next();
- 扫描数据_小数: double 变量名 = sc.nextDouble();
Arrays.sort(数组名),默认升序排序
排序方式2:让实体类实现Comparable接口,同时重写compareTo方法。Arrays的sort方法底层会根据compareTo方法的返回值是正数、负数、还是0来确定谁大、谁小、谁相等。
排序方式3:在调用Arrays.sort(数组,Comparator比较器);时,除了传递数组之外,传递一个Comparator比较器对象。Arrays的sort方法底层会根据Comparator比较器对象的compare方法方法的返回值是正数、负数、还是0来确定谁大、谁小、还是相等。
因为数组的长度是不可变,程序扩展升级会带来索引越界等问题,所以在开发中我们一般都是使用集合来存储数据的。
做相同事情的代码,就可以用方法进行封装。需要用到这段代码功能时,调用方法就行。方法的作用:用来存储一段功能代码,以便重复调用,提高代码的复用性,使程序逻辑更加清晰。
如果方法需要接收数据,就明确形式参数的类型,多个参数用逗号隔开,变量参数不能给初始化值。声明好形参列表的数据类型后,调用的时候一定要给相同数据类型的参数才能调用到方法的功能。如果方法不需要返回方法执行结果给调用者,用void定义就可以了。如果方法执行后的数据需要返回值给调用者,需要根据方法执行后生成的数据类型声明返回值的数据类型,需要返回字符串,在定义方法的时候就要用String类型进行定义方法,返回整数就用int进行定义,方法需要返回集合对象,返回类型就定义为响应的集合类型。
在一个类中,出现多个相同的方法名,但是他们的形参列表不同,这就是方法重载,与返回值和修饰符无关。
在方法中单独使用return语句,可以用来提前结束方法的执行。方法里面有switch,应该用return代替break。
需求:求101到200之间的素数,素数只能被1和本身整除的数是素数,比如: 3是素数,21不是素数(因为9可以被3整除,21可以被3和7整除)。
[1] 面向对象就是将世间万物抽象成一个类,然后形成相应的对象。[2] 面向过程,就是编写一个的方法,有数据要进行处理就交给方法来处理。面向对象只是关注有没有这个功能,不关注这个功能是怎么实现的。例如我想做键盘录入,就要想到谁可以实现键盘录入这个功能,如果没有现有的对象能实现就要自己去设计对象。总的说来是面向对象就是开发一个一个的对象来处理数据,把数据交给对象,再调用对象的方法来完成数据的处理。
[3] 所以面向对象编程的好处,用一句话总结就是:面向对象的开发更符合人类的思维习惯,让编程变得更加简单、更加直观。
面向对象的三大特性:封装,继承,多态。
面向对象的两个重点:
[2] 学习如何自己设计对象,random,scanner功能是有限的,自己的业务没有找到相应对象功能就要自己设计对象。
在实际开发中,我们通常会将类作为一种数据类型使用,把应用程序中一些有相同性质的属性的行为封装到相应的类中。在实际开发中,会实体类封装数据,使用业务类封装对数据处理的处理方法,以此来实现数据和数据业务处理相分离的目标。
一般实体类只封装该事物必然局部的属性和行为,例如学生这个类,学生比如得吃饭,必然得睡觉,这才是必然要具备的功能。算平均成绩和总成绩的方法,这不是他必然具备的,因此可以将这些方法封装到service层的业务类中。
类就是一个模板,是对一群具有相同特征或者行为的事物的一个统称,是抽象的模板,不能直接使用。类就是一个模板,他有方法和属性,方法就是他能干什么事情。属性就是什么是属于他的独有的。比如猫这个类有年龄,姓名,主人等属性,有学习、跑步、吃饭、睡觉等方法。
对象就是类的实例化,比如猫这个类可以实例化,具体是什么猫。
有static修饰的变量叫静态变量,这个变量在计算机里只有一份,会被类的全部对象共享。
static修饰的方法或者变量,优先于对象执行,可以用它来完成类中的一些前置工作。static修饰变量就是类变量,修饰方法就是静态方法。可以直接通过类名.静态变量名和类名.静态方法名进行调用。
代码块根据有无static修饰分为两种:静态代码块、实例代码块。
关于静态代码块重点注意:静态代码块,随着类的加载而执行,而且只执行一次。
2、示例代码块
实例代码块的作用和构造器的作用是一样的,用来给对象初始化值;而且每次创建对象之前都会先执行实例代码块。对于实例代码块重点注意:实例代码块每次创建对象之前都会执行一次。
如果一个类中的方法全都是静态的,那么这个类中的方法就全都可以被类名直接调用,由于调用起来非常方便,就像一个工具一下,所以把这样的类就叫做工具类。
权限修饰符是用来限制类的成员(成员变量、成员方法、构造器...)能够被访问的范围。一个是public(公有的)、一个是private(私有的)、protected(受保护的)、一个是缺省的(不写任何修饰符)
成员变量是指在类中声明的变量,用于存储对象的属性或状态。方法中的变量叫做局部变量。
成员方法指的是定义在类中的方法,这些方法可以在类的对象上调用,用于执行特定的功能或操作。成员方法也被称为实例方法或对象方法。
构造器的名称只能有一个public修饰,而且构造方法名要与类名相同。构造方法只能给成员方法赋值一次,成员方法可以重复给成员变量赋值。
构造器的作用,其实构造器就是用来创建对象的。可以在创建对象时给对象的成员变量做一些初始化操作。
例如上面定义了满参构造器,在创建对象时就可以直接给对象的成员变量赋值。[1] 在设计一个类时,如果不写构造器,Java会自动生成一个无参数构造器。[2] 一但定义了有参数构造器,Java就不再提供空参数构造器,此时建议自己加一个无参数构造器(不然创建对象,没有写上实参,就会报错)。封装的设计规范用8个字总结,就是:合理隐藏、合理暴露。比如,设计一辆汽车时,汽车的发动机、变速箱等一些零件并不需要让每一个开车的知道,所以就把它们隐藏到了汽车的内部。把发动机、变速箱等这些零件隐藏起来,这样做其实更加安全,因为并不是所有人都很懂发动机、变速箱,如果暴露在外面很可能会被不懂的人弄坏。
在设计汽车时,除了隐藏部分零件,但是还是得合理的暴露一些东西出来,让司机能够操纵汽车,让汽车跑起来。比如:点火按钮啊、方向盘啊、刹车啊、油门啊、档把啊... 这些就是故意暴露出来让司机操纵汽车的。
继承是面向对象三大特性之一,Java中提供了一个关键字extends,用这个关键字可以让一个类和另一个类建立起父子关系,被继承的类称为父类,继承的类称为子类,子类继承父类后就可以拥有父类非私有的成员变量和成员方法,他的好处是可以减少重复代码的编写。Java语言只支持单继承,不支持多继承,但是可以多层继承。就像家族里儿子、爸爸和爷爷的关系一样:一个儿子只能有一个爸爸,不能有多个爸爸,但是爸爸也是有爸爸的。
this就是一个变量,用在方法中,可以拿到当前类的对象。我们看下图所示代码,通过代码来体会这句话到底是什么意思。哪一个对象调用方法方法中的this就是哪一个对象。
分析上面的代码s2.setScore(432),调用方法printPass方法时,方法中的this.score也是432;而方法中的参数score接收的是425。执行结果是:
关于this,重点记住这句话:哪一个对象调用方法方法中的this就是哪一个对象。
super是一个特殊的关键字,它用于引用当前对象的直接父类(超类)的成员变量或者成员方法。当子类觉得父类方法不好用,或者无法满足父类需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。
1.写一个A类作为父类,定义两个方法print1和print2。2.再写一个B类作为A类的子类,重写print1和print2方法。
4.执行代码,我们发现真正执行的是B类中的print1和print2方法。
1.重写的方法上面,可以加一个注解@Override,用于标注这个方法是复写的父类方法2.子类复写父类方法时,访问权限必须大于或者等于父类方法的权限3.重写的方法返回值类型,必须与被重写的方法返回值类型一样,或者范围更小4.私有方法、静态方法不能被重写,如果重写会报错。继承至少涉及到两个类,而每一个类中都可能有各自的成员(成员变量、成员方法),就有可能出现子类和父类有相同成员的情况,那么在子类中访问其他成员有什么特点呢?原则:在子类中访问其他成员(成员变量、成员方法),是依据就近原则的。
2.再定义一个子类,代码如下。有一个同名的name成员变量,有一个同名的print1成员方法。3.接下来写一个测试类,观察运行结果,我们发现都是调用的子类变量、子类方法。
4.如果子类和父类出现同名变量或者方法,优先使用子类的;此时如果一定要在子类中使用父类的成员,可以加this或者super进行区分。
多态一般是在继承、实现情况下的一种现象,具体表现为:对象多态和行为多态。总的说来多态就是同一个行为具有多个不同的表现形式。他的好处是解耦,更方便扩展和维护。对象多态是:父类接收子类的对象,p1和p2都是父类变量,但指向子类的对象不一样。行为多态是:p1和p2都可以调用run方法,但是两个run方法表现的行为不一样。
final关键字是最终的意思,可以修饰类、修饰方法、修饰变量。
1.当final关键字修饰一个类时,表示该类不能被继承。这常常用于工具类,因为其功能是固定的,不需要进行扩展或修改。
2.当final关键字修饰一个方法时,表示该方法不能被重写(覆盖)。这常用于父类中的一些核心方法,子类不应该修改这些方法的行为。
3.当final关键字修饰一个属性时,表示该属性是一个常量,其值在初始化后不能被修改。需要注意的是,对于引用类型的属性,其地址值不能改变,但是引用的对象的内容是可以改变的。
常量介绍:被 static final 修饰的成员变量,称之为常量。通常用于记录系统的配置信息。常量命名规范:建议都采用大写字母命名,多个单词之间使用 “_” 隔开。Java中的抽象是一种非常重要的编程概念,它可以帮助我们设计更加清晰和灵活的程序结构。在Java中,抽象可以分为两个方面:抽象类和抽象方法。2.被abstract修饰的方法,就是抽象方法(不允许有方法体)。抽象类也是可以有成员(成员变量、成员方法、构造器)的。
抽象类是一个只能被继承不能被实例化的类,因此抽象类是不能创建对象的,如果抽象类的对象就会报错。
抽象类虽然不能创建对象,但是它可以作为父类让子类继承。而且子类继承父类必须重写父类的所有抽象方法。
子类继承父类如果不复写父类的抽象方法,要想不出错,这个子类也必须是抽象类。
设计模式介绍:设计模式是解决某一类问题的最优方案。抽象的好处就是用在模板方法上面:模板方法模式主要解决方法中存在重复代码的问题。比如A类和B类都有sing()方法,sing()方法的开头和结尾都是一样的,只是中间一段内容不一样。此时A类和B类的sing()方法中就存在一些相同的代码。
我们可以先写一个抽象类C类,在C类中写一个doSing()的抽象方法。再写一个sing()方法,代码如下:
然后写一个A类继承C类,复写doSing()方法,代码如下:接着,再写一个B类继承C类,也复写doSing()方法,代码如下:综上所述:模板方法模式解决了多个子类中有相同代码的问题。具体实现步骤如下第1步:定义一个抽象类,把子类中相同的代码写成一个模板方法。第2步:把模板方法中不能确定的代码写成抽象方法,并在模板方法中调用。第3步:子类继承抽象类,只需要父类抽象方法就可以了。Java提供了一个关键字interface,用这个关键字来定义接口这种特殊结构。格式如下:接口不能创建对象,是用来被类实现(implements)的,我们称之为实现类。一个类是可以实现多个接口的(接口可以理解成干爹),类实现接口必须重写所有接口的全部抽象方法,否则这个类也必须是抽象类。
在JDK8中,接口有了一些新的特性,这些特性有助于更好地设计和使用接口。以下是一些主要的JDK8接口新特性:默认方法和静态方法:JDK8允许在接口中定义默认方法和静态方法。默认方法使用“default”关键字进行修饰,可以在接口中包含具体的实现体。这使得接口可以更加灵活地扩展,而不会影响到实现该接口的类的代码。静态方法使用“static”关键字进行修饰,可以在接口中定义静态方法,实现一些与接口相关的不依赖于对象的状态的操作。实现类只需要实现接口的抽象方法就可以了,默认方法不需要子类重写。
在jdk8中,接口除了添加了默认方法和静态方法,还新引入了Lambda表达式、函数式接口和Stream API。
函数式接口是只有一个抽象方法的接口。在JDK8中,可以使用@FunctionalInterface注解来标记一个接口为函数式接口。这有助于在使用Lambda表达式时更加清晰地表达意图,并且使得代码更加易于理解和维护。
成员内部类就是类中的一个普通成员,类似于成员变量、成员方法。
代码应用:
对于内部类中的静态常量,我们仍然可以通过 ”外部类.内部类.常量名“ 来访问它。
静态内部类,其实就是在成员内部类的前面加了一个static关键字。
局部内部类是定义在方法中的类,和局部变量一样,只能在方法中有效。所以局部内部类的局限性很强,一般在开发中是不会使用的。
匿名内部类是一种特殊的局部内部类;所谓匿名,指的是程序员不需要为这个类声明名字。
匿名内部类本质上是一个没有名字的子类对象、或者接口的实现类对象。
比如,先定义一个Animal抽象类,里面定义一个cry()方法,表示所有的动物有叫的行为,但是因为动物还不具体,cry()这个行为并不能具体化,所以写成抽象方法。
匿名内部类的作用:简化了创建子类对象、实现类对象的书写格式。只有在调用方法时,当方法的形参是一个接口或者抽象类,为了简化代码书写,而直接传递匿名内部类对象给方法,这样就可以少写一个类。在使用Comparator比较器进行排序时,可以使用匿名内部类简化书写。
Java枚举(enum)是Java中一种特殊的类类型,它用于定义一组固定的、预定义的常量。枚举在Java中通过关键字 enum 来声明,并且每个枚举类型都具有自己的一组命名的实例。这些实例都是单例,不可变的,并且在编译时就已经确定。
在这个例子中,Color 是一个枚举类型,它有三个可能的值:RED,GREEN 和 BLUE。想要获取枚举类中的枚举项,只需要用类名调用就可以了除了简单的常量定义,枚举类型还可以包含方法、变量和构造函数:
2、与枚举状态码相应的状态描述进行比较
单例设计模式是一种常用的软件设计模式,它通过将类中所有的构造方法都私有化了,在类中通过静态方法返回唯一的对象,以此来确保一个类只有一个实例,并提供一个全局访问点来获取该实例。这种模式在需要频繁创建和销毁实例的场景中特别有用,因为它可以避免不必要的开销,提高性能。在Java中,可以通过多种方式实现单例模式,包括懒汉式(使用时才加载)、饿汉式(加载字节码的时候就创建对象)、双重检查锁定和枚举。使用枚举实现单例模式是最简单和最安全的方法之一。因为Java枚举类型在初始化时只会被加载一次,所以这确保了单例的唯一性。此外,枚举类型是线程安全的,因为它们的构造函数是同步的,即使多个线程同时尝试创建枚举实例,也只有一个线程能够成功。其他线程将会等待,直到第一个线程完成构造。这确保了在多线程环境中单例的线程安全性。
然后就可以通过Student student=枚举名.唯一的枚举项创建对象。
1.线程安全:枚举类型的实例化是由JVM在加载枚举类时进行的,这个过程由JVM保证线程安全。2.防止反射攻击:尝试通过反射调用newInstance()方法创建新的枚举实例会抛出InstantiationException异常,因为枚举类不能被实例化多次。3.防止序列化破坏单例:即使将枚举实例序列化和反序列化,得到的仍然是同一个实例,因此不会破坏单例模式。
所谓泛型指的是,在定义类、接口、方法时,同时声明了一个或者多个类型变量(如:<E>),称为泛型类、泛型接口、泛型方法、它们统称为泛型。泛型的好处:在编译阶段可以避免出现一些非法的数据。注意事项:泛型只能是引用数据类型,不能是基本数据类型,基本数据类型要转为包装数据类型。
只有写工具包的时候才会使用到泛型,以及写框架的时候才会定义泛型。
1. <?>无边界的通配符。
2. <? extends E> 固定上边界的通配符。
3. <? super E> 固定下边界的通配符。
使用IDEA写代码时不记得某个类的API如何使用,可以泛型的提示进行传参。
Java虚拟机(JVM,Java Virtual Machine)是Java平台的核心组成部分,它是一个抽象化的计算机系统,为Java程序提供运行环境。JVM的设计目的是实现一次编写、到处运行(Write Once, Run Anywhere,简称WORA),使得使用Java编写的程序能够在任何安装了Java Runtime Environment (JRE)的设备上运行。
在Java虚拟机(JVM)中,内存被划分为不同的区域来存储不同类型的数据和执行程序时所需的资源。
堆内存和栈内存是JVM两个非常关键的部分,堆内存负责存储引用类型的对象数据,栈内存负责存储基本类型的数据,以及引用类型对象在堆内存的地址。
1.栈内存是一种线程私有的内存区域,每个线程都有自己的栈空间。2.主要用于存储方法调用过程中的局部变量、方法参数以及返回地址等信息。3.在方法调用时,Java虚拟机会为该方法创建一个栈帧(Stack Frame),其中包含了局部变量表、操作数栈和其他辅助信息。4.当方法执行完毕,相应的栈帧会被弹出(Pop),释放其占用的栈空间,这部分内存管理由编译器自动完成,速度极快且无需垃圾回收机制参与。5.栈内存由于其后进先出(LIFO)的特性,可以高效地实现方法的调用和返回。
1.堆内存是所有线程共享的一块内存区域,在JVM启动时创建。2.主要用于存储Java应用程序运行过程中创建的所有类的实例对象(Object)和数组(Array)。3.对象的生命周期由JVM的垃圾回收机制(Garbage Collection)进行管理,包括分配内存、使用内存和回收不再使用的内存。4.创建对象时,首先会在堆内存中分配一块连续的空间,并通过引用指向这个对象。5.堆内存相比于栈内存没有大小限制,但分配和回收内存的过程相对复杂,需要考虑内存碎片、可达性分析等因素。
如果某个对象没有一个变量引用它,则该对象无法被操作了,该对象会成为垃圾对象,但也不会立即消失,而是等待垃圾回收器进行回收,清理垃圾对象由两种情况:
[1] 定时清理
[2] 堆内存的内容满了,就清理(不清理就会导致程序宕机)
堆内存是非常占内存的,能不new创建对象就尽量不要new创建对象,能节约就尽量节约。
总结来说,栈内存主要处理函数调用时的局部变量,生命周期随函数调用结束而结束;而堆内存则存储全局或长期存在的对象实例,其生命周期不受函数调用的影响,直到没有任何引用指向它时才可能被垃圾回收器清理。
吴灿锦,泰伯一百零一世孙,明朝开国名将安陆侯吴复的后代,毕业于吉林财经大学。曾作为《以“计”之长——大学生计算机专业人才“培训+就业”一体化先行者》项目主持人,带领鹰迅公司创业团队参加第九届中国国际“互联网+”创新创业大赛,荣获吉林省赛区金奖。曾作为《鹰迅公司——“互联网+办公技能培训”一体化服务平台》项目主持人,带领鹰迅公司创业团队参加第十三届“挑战杯”中国大学生创业计划大赛,在吉林省赛区中荣获特等奖,在全国总决赛中荣获铜奖。曾作为《鹰迅公司——“互联网+办公技能培训”一体化服务平台》项目主持人,带领鹰迅公司创业团队参加大学生创业实践,在2022年荣获吉林财经大学创业实践国家级立项第一名(已结项)。
2022年阿里黄山版《Java开发手册》下载链接:
链接:https://pan.baidu.com/s/1ocXJYRUfCDrzlfW52DjDnA
如果链接失效了,可以关注我的公众号发送:“Java开发手册” 进行获取。往期精彩