JAVA笔记
一、Java关键字及标识符
- 组成关键字的字母全部小写。
- 标识符:由字母、_、$开头,包含数字(区分大小写)。
- 包:就是文件夹,用于把相同的类名进行区分(全部小写),多个单词时,用.隔开。
- 类或接口名:
一个单词:单词首字母必须大写。
多个单词:每个单词首字母必须大写。
- 方法或者变量名:
一个单词:单词的字母小写。
多个单词:从第二个单词开始,每个单词首字母大写(驼峰命名法)。
- 常量名---全部大写
一个单词:大写 举例:PI
多个单词:大写,并用_隔开 举例:STUDENT_MAX_AGE
7.Java规定,如果类前面用public来修饰,那么文件名必须和类名相同。
8.java没有无符号型整数。
- java数据类型
1.二进制:以0b开头。
八进制:以0(零)开头。
十六进制:以0x或0X开头。
2.长整型后缀用L或l标记。long i=1000000000L
单精度浮点数用F或f标记。float j=1.23456F
浮点常量默认为双精度数。
3.一个局部变量在使用前必须要初始化,未初始化的值的变量不能直接使用。
4.char占用2个字节数。Java字符型存放的不是ASCII码,是Unicode码。(Unicode码与ASCII码是兼容的,所有ASCII码字符在高字节位置添上0都会变成Unicode码。例:字母a的ASCII码是0x61,在Unicode中,编码是0x0061。)
5.数据类型转换
(1)boolean类型不参与转换
(2)默认转换(系统自动进行)
A:从小到大
B:byte,short,char --> int --> long --> float --> double
C:byte,short,char之间不相互转换,直接转成int类型参与运算。
(3)强制转换
A:从大到小
B:可能会有精度的损失,一般不建议这样使用。
C:格式:目标数据类型 变量名 = (目标数据类型) (被转换的数据);
- 字符串数据和其他数据做+,结果是字符串类型。(这里的+不是加法运算,而是字符串连接符。)
如:System.out.println("hello"+'a'+1); //helloa1
System.out.println('a'+1+"hello"); //98hello
- 负数对正数取模结果为负数。-5%2=-1
正数对负数取模结果为正数。 5%-2=1
三、运算符与表达式
1.运算符的优先级(从高到低)
优先级 | 描述 | 运算符 |
1 | 括号 | ()、[] |
2 | 正负号 | +、- |
3 | 自增自减,非 | ++、--、! |
4 | 乘除,取余 | *、/、% |
5 | 加减 | +、- |
6 | 移位运算 | <<、>>、>>> |
7 | 大小关系 | >、>=、<、<= |
8 | 相等关系 | ==、!= |
9 | 按位与 | & |
10 | 按位异或 | ^ |
11 | 按位或 | | |
12 | 逻辑与 | && |
13 | 逻辑或 | || |
14 | 条件运算 | ?: |
15 | 赋值运算 | =、+=、-=、*=、/=、%= |
16 | 位赋值运算 | &=、|=、<<=、>>=、>>>= |
2.关系运算符:比较运算符“==”的结果都是boolean型,也就是要么是true,要么是false。
3.“&”和“&&”的区别:(“|”和“||”的区别同理)
单&时,左边无论真假,右边都进行运算;
双&时,如果左边为真,右边参与运算,如果左边为假,那么右边不参与运算。
&&优先级比||高,两者具有左结合性。
!比所有双目运算符优先级都高,具有右结合性。
- 异或( ^ )与或( | )的不同之处是:当左右都为true时,结果为false。
- 位运算:正数右移,高位用0补,负数右移,高位用1补,当负数使用无符号右移时,用0进行补位(自然而然的,就由负数变成了正数了)
注意:笔者在这里说的是右移,高位补位的情况。正数或者负数左移,低位都是用0补。
左移几位其实就是该数据乘以2的几次方。
右移几位其实就是该数据除以2的几次幂。
四、流程控制语句
1.switch语句
格式:
switch(表达式) {
case 值1:
语句体1;
break;
case 值2:
语句体2;
break;
…
default:
语句体n+1;
break;
}
·表达式的取值类型:byte,short,int,char四种。
(JDK5以后可以是枚举,JDK7以后可以是String)
·case后面只能是常量,不能是变量。
·执行顺序是先执行case,最后执行default。
2.while(条件表达式)
{
执行语句
}
3.do{
执行语句
}
While(条件表达式);
4.while和for可以互换,区别在于for为了循环而定义的变量在for循环结束就在内存中释放,而while循环使用的变量在循环结束后还可以继续使用。
五.数组
- 数组:是一种引用类型。
是一种简单的数据结构,线性结构。
是一种容器,用来存储数据。
可以存储任意数据类型的元素,但存储的元素类型相同。
数组一旦创建,长度不可改变。
每个元素在内存中所占用的空间大小是相同的。
每个元素的地址不同,数组的地址和首元素的地址相同。
数组查找元素的效率非常高。
- 数组的初始化
·静态初始化
程序员手动输入给定值,JVM给定数组长度。
知道数组中存储什么元素时使用。
·动态初始化
程序员手动输入给定值,JVM给定数组初始值。
不知道数组中存储什么元素时使用。
六.类和对象
- 类的声明:
【类修饰符】class 类名 【extends父类名】 【implements接口名列表】
一个类的初始化过程:
a.成员变量进行初始化
b.默认初始化
c.显示初始化
d.构造方法初始化
子父类初始化(分层初始化):先进行父类初始化,再进行子类初始化。
2. 在面向对象的编程中,通常把用类创建对象的过程称为实例化。实例化的过程就是为对象分配内存空间的过程,只有用类创建了对象之后,才会真正占用内存空间。
对象的创建:类名 对象名=new 构造方法名([参数列表]);
3.java中的每个类都有父类,如果不含父类名,则默认其父类为object类。Object
是java中唯一没有父类的类。
4.final类----最终类,不能拥有子类。
abstract----抽象类,类中的某些方法没有实现,必须由其子类实现。如果没有此修饰符,则类中间的所有方法必须都实现。
public----公共类,本类可以被所属包以外的类访问。
(final和abstract是互斥的,其他情况下可以组合使用)
5.方法: 成员变量(属性) 类的
成员方法(行为) 成员
构造方法
一个类中的方法分为三类:
全局方法:用类直接访问
成员方法:必须用类的实例化对象进行访问
构造方法:实例化对象时进行初始化的
构造方法:是一种特殊的的方法,与类同名且没有返回值类型的方法。对象的创建就是通过构造方法来完成的,其功能主要是完成对象的初始化。当类实例化一个对象时会自动调用构造方法。构造方法和其他方法一样也可以重载。
·注意:
·每个对象在生成时都必须执行构造方法,而且只能执行一次。
·如果构造方法调用失败,那么对象也无法创建。
6.成员变量名字可以和类中某个方法的名字相同。如果成员变量没有赋初值,系统自动为其赋初值。具体规定:所有简单变量除boolean类型外均赋初值为0,boolean类型赋初值为false,其他类型的对象均赋初值为null。
7.成员变量修饰符:
·成员变量的访问权限(4种)
·成员变量是否为静态(默认实例成员,在外部需要通过对象才能操作。如果用static修饰,就成了静态成员,无需通过对象就可以操作)
·是否为常量(默认是变量,若前面加上final就是常量)
8.静态成员变量:
- 它被类的所有对象共享,又称为类变量。
- 不属于某个具体对象,也不是保存在某个对象的内存区域中,而是保存在类的公共存储单元。因此,可以在类的对象被创建之前就能使用。
- 既可以通过“对象名.变量名”的方式访问,也可以通过“类名.变量名”的方式访问,是等价的。
9.成员变量的访问权限
·公共变量:被public修饰的,可以被任何类所访问。
·私有变量:被private修饰的,只允许在本类内部访问。(可以修饰成员,无法被子类继承,当子类必须继承成员变量时,需要使用其他的访问类型)
·保护变量:被protected修饰的,允许在本类的内部访问之外,还允许它的子类以及同一个包中的其他类访问。
·默认访问变量friendly:变量前不加任何访问修饰符。限定了访问权限只能在包中,只允许在同一个包中的其他类访问,即便是子类,如果父类不在同一包中,也不能继承默认变量(这是与保护变量唯一区别)
10.成员变量和局部变量的区别:
(1)在类中的位置不同
成员变量 类中方法外
局部变量 方法内或者方法声明上
(2)在内存中的位置不同
成员变量 堆内存
局部变量 栈内存
(3)生命周期不同
成员变量 随着对象的存在而存在,随着对象的消失而消失
局部变量 随着方法的调用而存在,随着方法的调用完毕而消失
(4)初始化值不同
成员变量 有默认的初始化值
局部变量 没有默认的初始化值,必须先定义,赋值,才能使用。
11.成员变量和静态变量的区别:
(1)所属不同
静态变量属于类,所以也称为为类变量
成员变量属于对象,所以也称为实例变量(对象变量)
(2)内存中位置不同
静态变量存储于方法区的静态区
成员变量存储于堆内存
(3)内存出现时间不同
静态变量随着类的加载而加载,随着类的消失而消失
成员变量随着对象的创建而存在,随着对象的消失而消失
(4)调用不同
静态变量可以通过类名调用,也可以通过对象调用
成员变量只能通过对象名调用
·注意:局部变量名称可以和成员变量名称一样,在方法中使用的时候,采用的是就近原则。
12.匿名对象
匿名对象:就是没有名字的对象。(是对象的一种简化表示形式)
匿名对象的两种使用情况:
(1)对象调用方法仅仅一次的时候
(2)作为实际参数传递
13.抽象类
定义:在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类。
抽象方法是一种特殊的方法,它只有声明,而没有具体的实现。必须为public或protected。
(1)抽象类特点:
·抽象类和抽象方法必须用abstract关键字修饰
格式:
abstract class 类名 {}
public abstract void eat();
·抽象类不一定有抽象方法,有抽象方法的类必须定义为抽象类。
抽象方法不能有主体。public abstract void eat(){}//错误
public abstract void eat();//正确
·抽象类不能创建对象,不能实例化(因为抽象类中含有无具体实现的方法)
那么,抽象类如何实例化呢?
通过多态的方式,由具体的子类实例化。这也是多态的一种,抽象类多态。
·抽象类的子类:
如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类定义为abstract类。
(2)抽象类的成员特点:
·成员变量
可以是变量,也可以是常量
·构造方法
有构造方法,但是不能实例化
那么,构造方法的作用是什么呢?
用于子类访问父类数据的初始化
·成员方法
可以是抽象方法 限定子类必须完成某些事情。
也可以是非抽象方法 子类继承的事情,提高代码复用性。
(3)abstract不能和哪些关键字共存
private 冲突
final 冲突
static 无意义
一个类如果没有抽象方法,可不可以定义为抽象类?如果可以,有什么意义?
A:可以。
B:不让创建对象。
14.内部类概述:
把类定义在其他类的内部,这个类就被称为内部类。
举例:在类A中定义了一个类B,类B就是内部类。
(1)内部类的访问特点:
内部类可以直接访问外部类的成员,包括私有。
外部类要访问内部类的成员,必须创建对象。
内部类和外部类没有继承关系。
(2)内部类位置
按照内部类在类中定义的位置不同,可以分为如下两种格式:
成员位置(成员内部类)
局部位置(局部内部类)
·成员内部类
·可以等同看作是成员变量
·可以用访问控制权限修饰符
·不能有静态声明
·外界如何创建对象?
格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
(一般内部类就是不让外界直接访问的。)
·成员内部的常见修饰符:
private 为了保证数据的安全性
static 为了让数据访问更方便
·被静态修饰的成员内部类只能访问外部类的静态成员。
·内部类被静态修饰后的访问方式:
格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
·局部内部类
·可以等同看作是局部变量
·可以直接访问外部类的成员。
·不可以使用访问控制权限修饰符
·在局部位置可以创建内部类对象,通过对象调用内部类方法,来使用局部内部类功能。
·局部内部类访问局部变量的注意事项:
局部内部类访问局部变量时,局部变量必须被final修饰(因为局部变量会随着方法的调用完毕而消失,这个时候,局部对象并没有立马从堆内存中消失,还要使用那个变量。为了让数据还能继续被使用,就用final修饰,这样,在堆内存里面存储的其实是一个常量值。)
·静态内部类
·可以等同看作是静态变量
·可以用访问控制权限修饰符
·可以直接访问外部类静态数据,但不能直接访问外部类成员
·匿名内部类:就是内部类的简化写法,指的是这个内部类没有名字。
前提:存在一个类或者接口。(这里的类可以是具体类也可以是抽象类。)
格式:new 类名或者接口名() {
重写方法;
}
本质:是一个继承了该类或者实现了该接口的子类匿名对象。
好处:少写一个类
缺点:不能重复利用
七.继承
格式:class 子类名 extends 父类名{}
1.优点:提高代码复用性和维护性,程序结构清晰。
缺点:增强了类的耦合性,打破了封装性。
2.Java只支持单继承,不支持多继承,但支持多层继承。
一个类只能有一个父类,不可以有多个父类。
class SubDemo extends Demo{} //正确
class SubDemo extends Demo1,Demo2...//错误
Java支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}
3.子类只能继承父类所有非私有的成员(成员方法和成员变量)。
4.子类不能继承父类的构造方法,但是可以通过super关键字去访问父类构造方法。
5.继承中成员变量的关系:
在子类方法中访问一个变量时(前提:子类中成员变量和父类中的成员变量名称一样),符合就近原则:
首先在子类方法的局部范围找,
然后在子类成员范围找,
最后在父类成员范围找(不能访问到父类局部范围),
如果还是没有就报错。
6.继承中构造方法的关系:
(1)子类中所有的构造方法(无参或带参)默认都会访问父类中空参构造方法(因为子类初始化之前,一定要完成父类的初始化)。
(2)子类的每一个构造方法的第一条语句默认都是:super();
子类通过super去显示调用父类其他的带参的构造方法
(3)如果父类中没有无参构造方法,子类构造方法会报错。
解决办法:
·在父类中加一个无参构造方法。
·子类通过super去显示调用父类其他的带参的构造方法。
·子类通过this去调用本类的其他构造方法。
注意:
子类中一定要有一个去访问父类的构造方法,否则父类数据就没有初始化。
本类其他构造也必须首先访问父类构造。
super(…)或者this(….)必须出现在第一条语句。(否则,就会有父类数据的多次初始化。)
7.继承中成员方法的关系:
(1)通过子类对象去访问一个方法:
首先在子类中找,然后在父类中找,如果还是没有就报错。
(2)方法重写(override)概述:
应用:当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的功能。
子类中出现了和父类中一模一样(方法名、参数列表、返回值类型)的方法声明,也被称为方法覆盖,方法复写。
·使用特点:
如果方法名不同,就调用对应的方法。
如果方法名相同,最终使用的是子类自己的。
·方法重写的注意事项:
父类中私有方法不能被重写。
子类重写父类方法时,访问权限不能更低。(要大于等于,最好一致)
父类静态方法,子类也必须通过静态方法进行重写。
·判断子类方法是否重写:
在此方法代码的上一行写@Override,若不报错则方法重写。
- 方法重载:
让类以统一的方式处理不同数据类型的一种手段。构造方法的重载就是在类中可以创建多个构造方法,但具有不同的参数列表。调用时通过传递参数的不同来决定具体使用哪个构造方法。
·方法名相同
·参数列表不同
·返回值不同,参数列表相同不构成重载
八.多态
1.前提和条件:
(1)要有继承条件。
(2)要有方法重写。
(3)要有父类引用指向子类对象。
2.成员访问特点:
Fu f=new Zi();
(1)成员变量:编译看左边,运行看左边
(2)成员方法:编译看左边,运行看右边 (由于存在方法重写,所以运行看右边)
(3)静态方法:编译看左边,运行看左边(静态和类相关,算不上重写,所以访问还是左边)
(4)构造方法:创建子类对象时,访问父类构造方法,对父类数据进行初始化。
3.多态的好处:
提高了程序的维护性(由继承保证)。
提高了程序的扩展性(由多态保证)。
多态的弊端:
不能访问子类特有功能。
- 多态中的转型:
向上转型:Fu f=new Zi();
从子到父
父类引用指向子类对象
向下转型(把父类引用强制转换为子类引用):
Zi z=(Zi)f;(要求该f必须能够转换为Zi的)
从父到子
父类引用转为子类对象
九.接口
1.接口特点:
(1)接口用关键字interface表示,也是一种引用类型。
格式:interface 接口名 {}
(2)类实现接口用implements表示
格式:class 类名 implements 接口名 {}
“接口名+Impl”这种格式是接口的实现类格式
比如:class InterImpl implements Inter{}
(3)接口不能实例化
那么,接口如何实例化呢?
按照多态的方式,由具体的子类实例化。其实这也是多态的一种,接口多态。
(4)接口的子类
要么是抽象类(意义不大)
要么是具体类,要重写接口中的所有抽象方法(推荐方案)
- 接口成员特点:
(1)成员变量:
只能是常量,并且是静态的。
默认修饰符 public static final
(2)构造方法:
没有,因为接口主要是扩展功能的,而没有具体存在
(3)成员方法:
只能是抽象方法
默认修饰符 public abstract
- 类与类,类与接口以及接口与接口的关系
(1)类与类:
继承关系,只能单继承,但是可以多层继承
(2)类与接口:
实现关系:可以单实现,也可以多实现。还可以在继承一个类的同时实现多个接口
(3)接口与接口:
继承关系,可以单继承,也可以多继承
- 抽象类和接口的区别:
(1)成员区别:
抽象类:
成员变量:可以变量,也可以常量
构造方法:有
成员方法:可以抽象,也可以非抽象
接口:
成员变量:只可以常量
成员方法:只可以抽象
(2)关系区别:
类与类:继承,单继承
类与接口:实现,单实现,多实现
接口与接口:继承,单继承,多继承
(3)设计理念区别:
抽象类: 被继承体现的是:“is a”的关系。 共性功能
接口: 被实现体现的是:“like a”的关系。扩展功能
十.包
包:就是文件夹,是对类进行分类管理。
基本的划分:按照模块和功能分。
- 定义包的格式:package 包名;
多级包用.分开即可
注意事项:
·package语句必须是程序的第一条可执行的代码
·package语句在一个java文件中只能有一个
·如果没有package,默认表示无包名
2.带包的类的编译和运行:
(1)手动式:
a:javac编译当前类文件。
b:手动建立包对应的文件夹。
c:把a步骤的class文件放到b步骤的最终文件夹下。
d:通过java命令执行。注意了:需要带包名称的执行
(例:java cn.itcast.HelloWorld)
(2)自动式:
a:javac编译的时候带上-d即可(例:javac -d . HelloWorld.java)
b:通过java命令执行和手动式一样
- 不同包下类之间的访问
4.导包
概述:不同包下的类之间的访问,我们发现,每次使用不同包下的类的时候,都需要加包的全路径。比较麻烦。这个时候,java就提供了导包的功能。
导包格式:import 包名;
注意:
·这种方式导入是到类的名称。
·虽然可以最后写*(把此包的所有类都导了过来),但是不建议。
·package,import,class的顺序关系:package>import>class
·package:只能有一个
·import:可以有多个
·class:可以有多个,建议一个
5.控制访问权限修饰符
| public | protected | 默认(缺省) | private |
同一类中 | √ | √ | √ | √ |
同一包下(子类、其他类) | √ | √ | √ |
|
不同包下(子类) | √ | √ |
|
|
不同包下(其他类) | √ |
|
|
|
总结: | 所有类都能使用 | 能在本包和子类中使用 | 只能在本包下使用 | 只能在本类中使用 |
- 类及其组成可以用的修饰符
修饰符:
权限修饰符:private,protected,public,默认
状态修饰符:static,final
抽象修饰符:abstract
(1)类:
public(居多),默认,final,abstract
(2)成员变量:
private(居多),protected,public,默认,final,static
- 构造方法:
private,protected,public(居多),默认
(4)成员方法:
Private,protected,public(居多),默认,final,static, abstract
除此之外的组合规则:
成员变量:public static final
成员方法:public static
public abstract
public final
十一.异常
1.异常:运行时出现的不正常情况,在java中用类的形式对不正常情况进行了描述和封装对象。描述不正常的情况的类,就是异常类。异常可以创建对象,“类”表示一类异常,“对象”表示其中一个具体的异常。
2.异常处理机制:作用是为了在程序发生异常时,输出详细的信息。提高程序的健壮性。
3.可抛性:通过两个关键字来实现throw、throws,凡是可以被这两个关键字所操作的类和对象都具备可抛性。
4.不正常情况分为两大类:
·一般不可处理:Error
特点:由jvm抛出的严重性问题。一般不针对性处理,直接修改程序。
·可以处理的:Exception
5.Throwable:无论是error,还是异常、问题,问题发生时就应该可以抛出,让调用者知道并处理。该体系特点在于Throwable及其所有的子类都具有可抛性。
Throwable中的方法:
·getMessage():获得异常信息,返回字符串。
·toString():获得异常类名和异常信息,返回字符串。
·printStackTrace():获得异常类名和异常信息,以及异常出现在程序中的位置,返回值void。
6.如果一个类称为异常类,必须要继承异常体系,因为只有成为异常体系的子类才有资格具备可抛性,才可以被两个关键字所操作:throw、throws。
7.throw、throws区别:
·throws用于标识函数暴露出的异常类,可以抛出多个,用“,”分隔。Throw用于抛出异常对象。
·throws用在函数上,后面跟异常类名。throw用在函数内,后面跟异常对象。
·定义功能方法时,需要把出现的问题暴露出来让调用者去处理,那么就通过throws在函数上标识。
·在功能方法内部出现某种情况,程序不能继续运行,需要进行跳转时,就用throw吧异常对象抛出。
8.自定义异常:可以自定义出的问题称为自定义异常。自定义类继承Exception或者其他子类,通过构造函数定义异常信息。
编译时异常:直接继承Exception
运行时异常:直接继承RuntimeException
9.异常的分类:
编译时异常:只要是Exception和其他子类都是,除了特殊子类RuntimeException体系。
运行时异常:就是Exception中的RuntimeException和其子类。
10.finally语句块:
可以直接和try语句联用。try...finally...(try...catch...finally...也可以)
finally语句块中的代码一定会执行。
11.final、finally、finalize区别:
final:修饰变量、方法、类
finally:属于try,它里面的代码必须被执行(只有一种情况不会被执行,就是在之前执行了System.exit(0))
finalize:方法属于object类,使用finalize可以在垃圾回收机制中将对象(不用了的)清除出去。
- 异常处理的捕捉形式:
try{
//需要被检测异常代码
}
catch(异常类 变量)//该变量用于接收发生的异常对象
{
//处理异常的代码
}
finally{
//一定会执行的代码
}
此代码块的特点:
try catch(catch可以有多个):当没有资源需要释放时,可以不用定义finally。
Try finally:异常无法直接catch处理,但资源必须关闭。
处理过程:
try中检测到异常会将异常对象传递给catch,catch捕捉到异常进行处理。
finally里通常用来关闭资源。如:数据库资源、IO资源等。
需要注意:try是一个独立的代码块,在其中定义的变量只在该变量块中有效。
如果在try以外继续使用,需要在try外建立引用,在try中对其进行初始化。
- 异常处理原则:
·函数内容如果抛出需要检测的异常,那么函数上必须要声明。否则,必须在函数内用try/catch,否则编译失败。
·如果调用到了声明异常的函数,要么try/catch,要么throws,否则编译失败。
·什么时候catch,什么时候throws?
功能内容可以解决,用catch。
解决不了,用throws告诉调用者,由调用者解决。
· 一个功能如果抛出多个异常,那么调用时,必须有对应多个catch进行针对性处理。内部有几个需要检测的异常,就抛几个异常,抛出几个,就catch几个。
- 异常注意事项:
·RuntimeException以及其子类如果在函数中被throw抛出,可以不用在函数上声明。
·子类在覆盖父类方法时,父类的方法如果抛出异常,那么子类的方法只能抛出父类的异常或该异常的子类。
·如果父类抛出多个异常,子类只能抛出父类异常的子集。
·如果父类的方法没有抛出异常,那么子类覆盖时绝不能抛,就只能try。
十二.应用程序编程接口:API(Application Programming Interface) ---集合
1.Object类概述及其构造方法:
Object类概述:
类层次结构的根类。
所有类都直接或者间接的继承自该类。
构造方法:
public Object()
子类的构造方法默认访问的是父类的无参构造方法。
- Object类的成员方法:
public int hashCode() 返回该对象的哈希码值(不是实际地址值)
public final Class getClass() 返回此object运行时的类
public String toString() 返回该对象的字符串类型
public boolean equals(Object obj)
protected void finalize()
protected Object clone()
3.Scanner类概述及其构造方法:
Scanner类概述:
JDK5以后用于获取用户的键盘输入
构造方法:
public Scanner(InputStream source)
4.Scanner类的成员方法:
基本格式:
hasNextXxx() :判断是否还有下一个输入项,其中Xxx可以是Int,Double等。如果需要判断是否包含下一个字符串,则可以省略Xxx。
nextXxx() :获取下一个输入项。Xxx的含义和上个方法中的Xxx相同。
默认情况下,Scanner使用空格,回车等作为分隔符。
常用方法:
public int nextInt()
public String nextLine()
先获取一个数值,再获取一个字符串,会出现问题。
原因:换行符号的问题。
解决:
A:先获取一个数值之后,再创建一个新的键盘录入对象获取字符串。
B:把所有的数据都先按照字符串获取,然后要什么,你就对应的转换成什么。
字符串:由多个字符组成的一串数据,也可以看成是一个字符数组。
通过查看API,可以知道
A:字符串字面值“abc”也可以看成是一个字符串对象。
B:字符串是常量,一旦赋值,就不能改变。
字符串直接赋值的方式是先到字符串常量池里去找,如果有就直接返回,没有就创建并返回。
5.String类概述及其构造方法:
String类概述:
字符串是由多个字符组成的一串数据(字符序列)
字符串可以看成是字符数组
字符串是常量,它的值在创建之后不能更改
构造方法:
public String() //空构造
public String(byte[] bytes) //把字节数组转换成字符串
public String(byte[] bytes,int offset,int length)//把字节数组的一部分转成字符串
public String(char[] value) //把字符数组转换成字符串
public String(char[] value,int offset,int count) //把字符数组的一部分转成字符串
public String(String original) //把字符串常量值转换成字符串
String s=new String(“hello”)和String s=”hello”的区别?
答:有区别,前者会创建两个对象,后者创建一个对象。
==:比较引用类型比较的是地址值是否相同
equals:比较引用类型默认也是比较地址值是否相同,而String类重写了equals()方法,比较的是内容是否相同。
6.String类的判断功能:
boolean equals(Object obj) //比较字符串的内容是否相同,区分大小写
boolean equalsIgnoreCase(String str)//比较字符串的内容是否相同,不区分大小写
boolean contains(String str) // 判断大字符串中是否包含小字符串
boolean startsWith(String str) //判断字符串是否以某个指定的字符串开头
boolean endsWith(String str) //判断字符串是否以某个指定的字符串结尾
boolean isEmpty() //判断字符串是否为空
注:字符串内容为空和字符串对象为空。
String s=””; //内容为空
String s=null; //对象为空
7.String类的获取功能:
int length() //获取字符串长度
char charAt(int index) //获取指定索引位置的字符
int indexOf(int ch) //返回指定字符在此字符串中第一次出现处的索引
为什么这里是int类型,而不是char类型?
因为’a’和97其实都可以代表’a’。
int indexOf(String str) //返回指定字符串在此字符串中第一次出现处的索引
int indexOf(int ch,int fromIndex)//返回指定字符在此字符串中从指定位置后第一次 出现处的索引
int indexOf(String str,int fromIndex)//返回指定字符串在此字符串中从指定位置后第 一次出现处的索引
String substring(int start) //从指定位置开始截取字符串,默认到末尾,包
含start这个索引。
String substring(int start,int end) //从指定位置开始到指定位置结束截取字符串,包
含start索引不包含end 索引。
8.String类的转换功能:
byte[] getBytes() //把字符串转换为字节数组
char[] toCharArray() //把字符串转换为字符数组
static String valueOf(char[] chs)//把字符数组转换为字符串
static String valueOf(int i) //把int类型的数据转成字符串
注:String类的valuesOf方法可以把任意类型的数据转换为字符串
String toLowerCase() //把字符串转成小写
String toUpperCase() //把字符串转成大写
String concat(String str) //把字符串拼接
String类的其他功能:
替换功能:
String replace(char old,char new)
String replace(String old,String new)
去除字符串两空格:
String trim()
按字典顺序比较两个字符串:
int compareTo(String str)
int compareToIgnoreCase(String str)//不区分大小写
9.StringBuffer类概述及其构造方法:
StringBuffer类概述:我们如果对字符串进行拼接操作,每次拼接,都会构建一个新的String对象,既耗时,又浪费空间。而StringBuffer就可以解决这个问题。
线程安全的可变字符序列。
String、StringBuffer、StringBuilder的区别?
答:String是内容不可变的,而StringBuffer、StringBuilder都是内容可变的。
StringBuffer是同步的,数据安全,效率低。
StringBuilder是不同步的,数据不安全,效率高。
StringBuffer和数组的区别:
二者都可容易看作是一个容器,装其他的数据。
StringBuffer的数据最终是一个字符串数据。
而数组可以放置多种数据,但必须是同一种数据类型。
构造方法:
public StringBuffer() //无参构造
public StringBuffer(int capacity) //指定容量的字符串缓冲区对象(容量:可用于最新插入的字符的存储量,超过这一容量就需要再次进行分配)
public StringBuffer(String str) //指定字符串内容的字符串缓冲区对象
- StringBuffer类的成员方法:
Public int capacity():返回当前容量,理论值
Public int length():返回长度(字符数),实际值
添加功能:
public StringBuffer append(String str) //可以把任意类型数据添加到字符串缓冲区
面,并返回字符串缓冲区本身 。
public StringBuffer insert(int offset,String str)//在指定位置把任意类型数据插入到字符串缓冲区里面,并返回字符串缓冲区本身。
删除功能:
public StringBuffer deleteCharAt(int index)//删除指定位置的字符,并返回本身。
public StringBuffer delete(int start,int end)//删除从指定位置开始到指定位置结束的内容,并返回本身。
替换功能:
public StringBuffer replace(int start,int end,String str)//从start到end用str替换。
反转功能:
public StringBuffer reverse()
截取功能:
public String substring(int start)
public String substring(int start,int end)
截取功能和前面几个功能的不同:
返回值类型是String类型,本身没有发生改变。
- toString:返回该对象的内存地址
- equals:默认比较的是对象的引用是否指向同一个内存地址,如果需要判断自定义类型对象内容是否相等,则需要重写equals方法。(String类已经重写equals方法)
- ==:比较的是值是否相同,如果相同返回true,否则返回false。如果是引用数据类型则变量比较的是地址值(地址值相等返回true,否则返回false)
- 数组和集合类同是容器,有何不同?
数组虽然也可以存储对象,但长度是固定的。数组中可以存储基本数据类型。
集合长度是可变的。集合只能存储对象。
- 集合类的特点
集合只用于存储对象,集合长度是可变的,集合可以存储不同类型的对象
16.List的子类特点:
ArrayList:
底层数据结构是数组,查询快,增删慢
线程不安全,效率高
Vector:
底层数据结构是数组,查询快,增删慢
线程安全,效率低
LinkedList:
底层数据结构是链表,查询慢,增删快
线程不安全,效率高
- 应用程序编程接口:API(Application Programming Interface) ---IO
1.异常:Java程序在运行过程中出现的错误。
(1)异常处理:
A:JVM的默认处理
把异常的名称,原因,位置等信息输出在控制台,但是呢程序不能继续执行了。
B:自己处理
a:try{
}catch{
}finally{
} 自己编写处理代码,后面的程序可以继续执行
b:throws
把自己处理不了的,在方法上声明,告诉调用者,这里有问题
(2)面试题:
A:编译期异常和运行期异常的区别?
Java中的异常被分为两大类:编译时异常和运行时异常。所有的RuntimeException类及其子类的实例被称为运行时异常,其他的异常就是编译时异常。
编译期异常 必须要处理的,否则编译不通过
运行期异常 可以不处理,也可以处理
B:throw和throws的区别:
throw:
·在功能方法内部出现某种情况,程序不能继续运行,需要进行跳转时,就用throw 把异常对象抛出。
·用在方法体内,跟的是异常对象名
·只能抛出一个异常对象名
·表示抛出异常,由方法体内的语句处理
·throw则是抛出了异常,执行throw则一定抛出了某种异常
throws:
·定义功能方法时,需要把出现的问题暴露出来让调用者去处理。那么就通过throws在方法上标识。
·用在方法声明后面,跟的是异常类名
·可以跟多个异常类名,用逗号隔开
·表示抛出异常,由该方法的调用者来处理
·throws表示出现异常的一种可能性,并不一定会发生这些异常
C:如何处理异常
原则:如果该功能内部可以将问题处理,用try,如果处理不了,交由调用者处理,这是用throws。
区别:
后续程序需要继续运行就try
后续程序不需要继续运行就throws
举例:
感冒了就自己吃点药就好了,try
吃了好几天药都没好结果得了H7N9,那就的得throws到医院
如果医院没有特效药就变成Error了
(3)finally关键字及其面试题
A:finally用于释放资源,它的代码永远会执行。(特殊情况:在执行到finally之前jvm退出了)
B:面试题
a:final,finally,finalize的区别?
b:如果在catch里面有return,请问finally还执行吗?如果执行,在return前还是后
会,前。
C:异常处理的变形
try...catch...finally
try...catch...
try...catch...catch...
try...catch...catch...fianlly
try...finally
(4)自定义异常
继承自Exception或者RuntimeException,只需要提供无参构造和一个带参构造即可
(5)异常的注意事项
A:父的方法有异常抛出,子的重写方法在抛出异常的时候必须要小于等于父的异常
B:父的方法没有异常抛出,子的重写方法不能有异常抛出
C:父的方法抛出多个异常,子的重写方法必须比父少或者小
2:File(掌握)
(1)IO流操作中大部分都是对文件的操作,所以Java就提供了File类供我们来操作文件
(2)构造方法
A:File file = new File("e:\\demo\\a.txt");
B:File file = new File("e:\\demo","a.txt");
C:File file = new File("e:\\demo");
File file2 = new File(file,"a.txt");
(3)File类的功能
A:创建功能public boolean createNewFile()
public boolean mkdir()
public boolean mkdirs()
B:删除功能public boolean delete() //JAVA的删除不走回收站
//要想删除文件夹,此文件夹里不能有文件或文件夹
C:重命名功能public boolean renameTo(File dest)//路径相同就改名,不同就改名并剪切
D:判断功能public boolean isDirectory()
public boolean isFile()
public boolean exists()
public boolean canRead()
public boolean canWrite()
public boolean isHidden()
E:获取功能public String getAbsolutePath()
public String getPath()
public String getName()
public long length()
public long lastModified()
F:高级获取功能public String[] list() //返回某个目录下的所有文件和目录的文件名,返 回的是String数组
public File[] listFiles() //返回某个目录下所有文件和目录的绝对路 径,返回的是File数组
G:过滤器功能public String[] list(FilenameFilter filter)
public File[] listFiles(FilenameFilter filter)
//通过实现接口里面的 boolean accept(File dir, String name) 或者boolean accept(File pathname)方法来过滤出满足条件的文件
- IO流概述:
IO流用来处理设备之间的数据传输(上传文件和下载文件)
Java对数据的操作是通过流的方式
Java用于操作流的对象都在IO包中
(1)分类:
A:流向
输入流 读取数据
输出流 写出数据
B:数据类型
字节流
字节输入流
字节输出流
字符流
字符输入流
字符输出流
注意:
a:如果我们没有明确说明按照什么分,默认按照数据类型分。
b:如果数据所在的文件通过windows自带的记事本打开并能读懂里面的内容, 就用字符流。其他用字节流。
(2)FileOutputStream写出数据
A:操作步骤
a:创建字节输出流对象
b:调用write()方法
c:释放资源
B:代码体现:
FileOutputStream fos = new FileOutputStream("fos.txt");
fos.write("hello".getBytes());
fos.close();
C:要注意的问题?
a:创建字节输出流对象做了几件事情?
b:为什么要close()?
c:如何实现数据的换行?
d:如何实现数据的追加写入?
(3)FileInputStream读取数据
A:操作步骤
a:创建字节输入流对象
b:调用read()方法
c:释放资源
B:代码体现:
FileInputStream fis = new FileInputStream("fos.txt");
//方式1
int by = 0;
while((by=fis.read())!=-1) {
System.out.print((char)by);
}
//方式2
byte[] bys = new byte[1024];
int len = 0;
while((len=fis.read(bys))!=-1) {
System.out.print(new String(bys,0,len));
}
fis.close();
(5)案例:2种实现
A:复制文本文件
B:复制图片
C:复制视频
(6)字节缓冲区流
A:BufferedOutputStream
B:BufferedInputStream
(7)案例:4种实现
A:复制文本文件
B:复制图片
C:复制视频
3:自学字符流
IO流分类
字节流:(字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,java本身在设计的时候,也考虑到了这样的设计思想(装饰设计模式后面讲解),所以提供了字节缓冲区流。BufferedInputStream、BufferedOutputStream比FileInputStream、FileOutputStream效率高)
InputStream
FileInputStream构造方法:FileInputStream(File file)
FileInputStream(String name)
写数据的方式:public int read()
public int read(byte[] b)
BufferedInputStream字节缓冲输入流
OutputStream
FileOutputStream构造方法:FileOutputStream(File file)
FileOutputStream(String name)
写数据的方式:public void write(int b)
public void write(byte[] b)
public void write(byte[] b,int off,int len)
BufferedOutputStream字节缓冲输出流
字符流:
Reader
FileReader
BufferedReader
Writer
FileWriter
BufferedWriter
- 应用程序编程接口:API(Application Programming Interface) ---多线程
补充:
●Eclipse
- 组成之视窗与视图
ackageExplorer 显示项目结构,包,类,及资源
Outline 显示类的结构,方便查找,识别,修改
Console 程序运行的结果在该窗口显示
Problems 显示所有语法及错误所在的位置
Hierarchy 显示Java继承层次结构,选中类后F4
- 快捷键:
内容辅助键:alt+/ 起提示作用
main+alt+/,sout+alt+/,给出其他提示
常用快捷键:
格式化 ctrl+shift+f
导入包 ctrl+shift+o
注释 ctrl+/,ctrl+shift+/,ctrl+shift+\
代码上下移动 选中代码alt+上/下箭头
查看源码 选中类名(F3或者Ctrl+鼠标点击)
- 如何制作帮助文档
编写源程序(设计接口,抽象类,具体类案例)
针对源程序添加文档注释
选中项目--右键--Export--Java--Javadoc—Finish
- Jar
jar是什么?
jar是多个class文件的压缩包。
jar有什么用?
用别人写好的东西
打jar包:
选中项目--右键--Export--Java--Jar--自己指定一个路径和一个名称--Finish
使用jar包:
复制到项目路径下并添加至构建路径。
(5)删除项目和导入项目
删除项目:
选中项目 – 右键 – 删除
从项目区域中删除
从硬盘上删除
导入项目:
在项目区域右键找到import
找到General,展开,并找到(Existing Projects into Workspace)
点击next,然后选择你要导入的项目(注意:这里选择的是项目名称)
(6)常见小问题
如何查看项目所在路径:
选中 -- 右键 -- Properties -- Resource -- Location
导入项目要注意的问题:
项目区域中不可能出现同名的项目(新建或者导入)
自己随意建立的文件夹是不能作为项目导入的
修改项目问题:
不要随意修改项目名称
如果真要修改,不要忘记了配置文件.project中的<name>把这里改为你改后的名称</name>
●final关键字:(最终的意思,可以修饰类,成员变量,成员方法)
特点:修饰类:修饰后为最终类,不能被继承。
修饰变量:该变量不能被赋值,变量就是常量,只能被赋值一次。
修饰方法:方法不能被重写。
修饰局部变量: 在方法内部:该变量不可以被改变。
在方法声明上: 基本类型:值不能被改变。
引用类型:地址值不能被改变,但该对象的堆内存的值可以改变。
修饰变量的初始化时机:被final修饰的变量只能赋值一次。
在构造方法完毕之前。(非静态的常量)
final关键字的优点:
(1)final关键字提高了性能。JVM和Java应用都会缓存final变量。
(2)final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
(3)使用final关键字,JVM会对方法、变量及类进行优化。
final的重要知识点:
(1)final关键字可以用于成员变量、本地变量、方法以及类。
(2)final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
(3)你不能够对final变量再次赋值。
(4)本地变量必须在声明时赋值。
(5)在匿名类中所有变量都必须是final变量。
(6)final方法不能被重写。
(7)final类不能被继承。
(8)final关键字不同于finally关键字,后者用于异常处理。
(9)final关键字容易与finalize()方法搞混,后者是在Object类中定义的方法,是在垃圾回收之前被JVM调用的方法。
(10)接口中声明的所有变量本身是final的。
(11)final和abstract这两个关键字是反相关的,final类就不可能是abstract的。
(12)final方法在编译阶段绑定,称为静态绑定(static binding)。
(13)没有在声明时初始化final变量的称为空白final变量(blank final variable),它们必须在构造器中初始化,或者调用this()初始化。不这么做的话,编译器会报错“final变量(变量名)需要进行初始化”。
(14)将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
(15)按照Java代码惯例,final变量就是常量,而且通常常量名要大写:
●new关键字:一看到关键字new ,就表示分配一个空间。调用相应类中的构造方法(包括祖先类的构造方法),来完成内存分配以及变量的初始化工作,然后将分配的内存地址返回给所定义的变量。
●math.random()是令系统随机选取大于等于 0.0 且小于 1.0 的伪随机 double 值,是Java语言常用代码。例如:vara:Number=Math.random()*2+1,设置一个随机1到3的变量。
●static关键字:可以修饰成员变量和成员方法
特点:随着类的加载而加载,优先于对象存在,被类的所有对象共享(这也是我们判断是否使用静态关键字的条件),可以通过类名.操作符直接调用。
注意事项:
在静态方法或语句块中不能使用this、super关键字的。
静态方法只能访问静态成员(属性和方法),不能使用非静态成员。
非静态方法可以使用静态成员(属性和方法),也可以使用非静态成员。
●super关键字
super的用法和this很像
this代表本类对应的引用。
super代表父类存储空间的标识(可以理解为父类引用,可以操作父类的成员)
用法:(this和super均可如下使用)
访问成员变量:
this.成员变量 (调用本类的成员变量)
super.成员变量 (调用父类的成员变量)
访问构造方法:
this(…) (调用本类的构造方法)
super(…) (调用父类的构造方法)
访问成员方法:
this.成员方法() (调用本类的成员方法)
super.成员方法() (调用父类的成员方法)
子类构造方法必须调用父类的构造方法
·如果未显示,则默认调用父类无参构造
·如果显示,则super必须位于第一行
·如果未显示且父类没有无参构造则程序报错
●this:代表所在类的对象的引用,在局部变量隐藏成员变量时使用。(哪个对象调用了this所属的方法,this就代表哪个对象)
通过this显示的去使用当前对象的成员,如果需要在一个构造方法中调用另一个构造方法来初始化信息可以使用this。
用在成员方法中:访问当前对象的属性,调用当前对象的方法
用在构造方法中:语法:this(实参);//目的是代码重用
●代码块:在Java中,使用{}括起来的代码被称为代码块,根据其位置和声明的不同,可以分为局部代码块,构造代码块,静态代码块,同步代码块。
(1)局部代码块 :
在方法中出现;限定变量生命周期,及早释放,提高内存利用率。
(2)构造代码块 :
在类中方法外出现;多个构造方法中相同的代码存放到一起,在创建对象时被调用,每次创建对象都会被调用。是对所有对象进行统一初始化,构造代码块中定义的是不同对象共性的初始化内容。而构造函数是给对应的对象初始化,构造代码块优先于构造函数,每次调用构造方法执行前,都会先执行构造代码块。
(3)静态代码块 :
在类中方法外出现,并加上static修饰;用于给类进行初始化,随着类的加载而执行,并且只执行一次。(即静态代码块的内容会优先执行)。
- 静态代码块、构造代码块、构造方法的执行顺序:
静态代码块>静态属性>静态方法>普通属性>普通代码块>构造方法>普通方法
●形式参数与返回值的问题
形式参数:
基本类型:(简单)
引用类型:
类名:需要的是该类的对象(匿名对象讲过)
抽象类:需要的是该抽象的类子类对象
接口:需要的是该接口的实现类对象
返回值类型:
基本类型:(简单)
引用类型:
类:返回的是该类的对象
抽象类:返回的是该抽象类的子类对象
接口:返回的是该接口的实现类的对象
●内存的划分:
java程序在运行时,需要在内存分配空间。为提高运算效率又对空间进行了不同区域的划分。
- 寄存器
- 本地方法区
- 方法区
- 堆内存:数组、对象、方法属性,通过new建立的实例都存放在堆内存中。
- 栈内存:存放对象的引用,用于存储局部变量、基本数据类型,当变量所属的作用域一旦结束,所占的空间自动释放。
堆和栈区别:
栈内存:栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。
堆内存:存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java有垃圾回收机制不定时的收取。
1.栈内存存储的是局部变量而堆内存存储的是实体;
2.栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
3.栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。
●选择排序
- 首先拿数组第一个元素依次与除自身外的其他每个元素顺序比较,如果第一个元素大于剩下的某个元素,就互换内容。
- 此时第一个元素就是最小的元素。然后再拿第二个元素与除第一个元素和其自身的元素进行比较,如果第二个元素大于剩下的某个元素,就互换内容。此时,第二个元素就是数组中倒数第二小的元素。
- 以此类推,直到最后一个元素。
●封装:
表现:方法就是一个最基本的封装体,类也是一个封装体。
特点:隐藏属性,提供对外访问的方法
好处:
- 提高了代码安全性
- 提高代码复用性
- 隐藏实现细节
●单例模式: Java中单例(Singleton)模式是一种广泛使用的设计模式。单例模式的主要作用是保证在Java程序中,某个类只有一个实例存在。一些管理器和控制器常被设计成单例模式。它能够避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间;能够避免由于操作多个实例导致的逻辑错误。如果一个对象有可能贯穿整个应用程序,而且起到了全局统一管理控制的作用,那么单例模式也许是一个值得考虑的选择。
- 饿汉模式
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton newInstance(){
return instance;
}
}
从代码中可以看到,类的构造函数定义为private的,保证其他类不能实例化此类,然后提供了一个静态实例并返回给调用者。饿汉模式是最简单的一种实现方式,饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。它的好处是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。
它的缺点也很明显,即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。
这种实现方式适合单例占用内存比较小,在初始化时就会被用到的情况。但是,如果单例占用的内存比较大,或单例只是在某个特定场景下才会用到,使用饿汉模式就不合适了,这时候就需要用到懒汉模式进行延迟加载。
- 懒汉模式
public class Singleton{
private static Singleton instance = null;
private Singleton(){
}
public static Singleton newInstance(){
if(null == instance){
instance = new Singleton();
}
return instance;
}
}
懒汉模式中单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。如果某个单例使用的次数少,并且创建单例消耗的资源较多,那么就需要实现单例的按需创建,这个时候使用懒汉模式就是一个不错的选择。但是这里的懒汉模式并没有考虑线程安全问题,在多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,因此需要加锁解决线程同步问题。
●