目录
java标识符命名规范
这是好的编程习惯,建议学习
- 包名:多单词组成时所有字母全部小写
例:com.lhy.study - 类名丶接口名:多单词组成时,所有单词的首字母大写
例:XxxYxxZzz - 变量名丶方法名:多单词组成时,第一个单词首字母大写,第二个单词开始每个单词首字母大写
例:xxxYyyZzz - 常量名:所有字母全部大写
例:XXX_YYY_ZZZ
以下几点注意:
- 不可以数字
- 不可以使用关键字和保留字
- 可以包含关键字和保留字(不可以单独定义一个名字为关键字和保留字的变量)
- 标识符不能包含空格。
- 在命名时,要求见名知意
java中的变量:
变量的作用:在内存中存储数据
变量三要素:数据类型 变量名 变量值
变量的声明格式 :数据类型 变量名 变量值
注意点:变量必须声明之后才能使用,变量只能在自己的作用域之内使用。赋值不要超过变量的数据类型的范围。在同一作用域内,不可以声明两个同名的变量。
数据类型:
分为基本类型和引用数据类型
基本数据类型分为:
整数型:byte(1字节)、short(2字节)、int(4字节)、long(8字节)
浮点型:float(4字节)、double(8字节)
字节型:char(1字节)
布尔型:boolean(1字节) 1个字节是8个bit位
说明:1.在整数型中,变量默认的数据类型是int,变量浮点型是double
2.char在变量定义的时候,''里面必须有值,不然报错,可以存储空格和转义字符
3.布尔类型在编译过程中不谈boolean占有几个字节
4.long类型在变量定义的时候,不加l会自动转换成int类型,加了l也要在long类型的数据范围之内,不然会报错
5.float、double的数据不适合在不容许舍入误差的金融计算领域,使用‘BigDecimal’类
基本数据类型之间的自动类型提升:
- 容量小的变量与容量大的变量一起运算的时候,运算结果自动转换成容量大的变量的数据类型
- (注意:此处提到的容量大小不是指数据类型占有的字节大小,而是指的数据范围,例如float和long,float只有四个字节,long有8个字节,但是float比long数据范围大)
- byte、char、short->int->long->float->double(不包含布尔类型)
- 特别说明byte、short、char 三者之间做运算,结果至少在int类型或者在int之上。
基本类型之间的强制类型转换:
- 强制类型转换就是自动类型提升的逆运算,即将容量大的数据类型的变量赋值给容量小的数据类型的变量
- 强制类型的转换也不包含boolean类型。
- 强制类型的转换需要使用强转符
- 强制类型转换可能带来精度损失的问题
例子 short a = short (b+1);b是short类型的
String类型与基本数据类型之间的运算
String在与基本类型之间进行运算,就只是连接运算,运算之后还是String类型
引用数据类型分为:
集合
数组
接口
数组
注解
java运算符
-
算术运算符: + - + - * / % (前)++ (后)++ (前)-- (后)-- +
-
赋值运算符: = += -= *= /= %=
-
比较运算符: == != > < >= <= instanceof
-
逻辑运算符: & && | || ^ !
-
位运算符: << >> >>> & | ^ ~
-
条件运算符: (条件表达式) ? 表达式1 : 表达式2
开发中推荐使用短路符号(效率高,左边为false,直接跳出判断条件)
&与&&异同:
相同点:1.两者运算结果相同 2.当符号左边是true时,二者都会执行右边的运算
不同点:当符号左边是false时,&会执行右边的运算,但是&&不会
|与||异同:
相同点:1.两者运算结果相同 2.当符号左边是false时,二者都会执行右边的运算
不同点:当符号左边是true时,|会执行右边的运算,但是||不会
位运算符:
举例
- << 左移 3<<2 =12 3*2*2=12
- >> 右移 3>>1 =1 3/2=1
- >>> 无符号右移 3>>>1 =1 3/2=1
- & 与运算 用二进制进行与,都为1时结果唯一,否则就为0
- | 或运算 用二进制进行或,一个为1时结果唯一,否则就为0
- ^ 异或运算 二者不同为1,否则为o
- ~ 取反运算 将二进制的原数安按位取反
对左移右移以及无符号右移进行说明:
左移<< :就是该数对应二进制码整体左移,左边超出的部分舍弃,右边补零。举个例子:253的二进制码1111 1101,在经过运算253<<2后得到1111 0100。很简单
右移>> :该数对应的二进制码整体右移,左边的用原有标志位补充,右边超出的部分舍弃。
无符号右移>>> :不管正负标志位为0还是1,将该数的二进制码整体右移,左边部分总是以0填充,右边部分舍弃。
特别说明:位运算符操作数据全部都是整数类型 ,左移一位相当于乘2,右移一位相当于除以2
三元运算符:
(条件表达式)?表达式1:表达式2
条件表达式的结果为boolean:true or false
如果条件表达式的结果为true,执行表达式1,反之为2
- ++(变量):先加1在运算
- (变量)++:先运算再加1
- --(变量):先减1再远算
- (变量)--:先运算在减一
- 连接符+:只用在String和其他数据变量类型之间使用
比较运算符
结果为布尔型boolean
< > >= >= :只能使用在数值类型的数据之间
== 和!=任意类型
从键盘获取不同类型变量
具体步骤:
- 导入 import.util.Scanner包
- Scanner实例化
- 调用Scanner类的相应方法,获取对应类型的变量
面试题
int m = 2;
m = m++;
int n = m++;
System.out.println(m+","+n);
这道题看上去简单,感觉还挺绕的。
第二行 赋值之后m还是2 ,所以第三行此时m还是为2,n也是2,赋值完m变成3
最终答案就是3,2
流程控制
单分支条件判断:if
if(条件表达式){
语句块;
}
执行流程:
-
首先判断条件表达式看其结果是true还是false
-
如果是true就执行语句块
-
如果是false就不执行语句块
双分支条件判断:if...else
格式:
if(条件表达式) {
语句块1;
}else {
语句块2;
}
执行流程:
- 首先判断条件表达式看其结果是true还是false
- 如果是true就执行语句块1
- 如果是false就执行语句块2
多分支条件判断:if...else if...else
格式:
if (条件表达式1) {
语句块1;
} else if (条件表达式2) {
语句块2;
}
...
}else if (条件表达式n) {
语句块n;
} else {
语句块n+1;
}
执行流程:
-
首先判断关系表达式1看其结果是true还是false
-
如果是true就执行语句块1,然后结束当前多分支
-
如果是false就继续判断关系表达式2看其结果是true还是false
-
如果是true就执行语句块2,然后结束当前多分支
-
如果是false就继续判断关系表达式…看其结果是true还是false
说明:条件表达式是boolean类型,条件表达式是false时候就跳出当前结构。
switch-case多分支选择结构
语法格式:
switch(表达式){
case 常量值1:
语句块1;
//break;
case 常量值2:
语句块2;
//break;
// ...
[default:
语句块n+1;
break;
]
}
根据表达
注意:在switch语句中,如果case的后面不写break,将出现穿透现象,也就是一旦匹配成功,不会在判断下一个case的值,直接向后运行,直到遇到break或者整个switch语句结束,switch语句执行终止。
还有一种特殊情况:当你把default方法放到case前边的时候,还是会先执行case语句,等到执行完之后,不管default语句后边有没有break,会直接执行case第一个结果。
while循环
while循环语句基本格式:
①初始化部分
while(②循环条件部分){
③循环体部分;
④迭代部分;
}
注意:
-
while(循环条件)中循环条件必须是boolean类型
-
注意不要忘记声明④迭代部分。否则,循环将不能结束,变成死循环。
-
for循环和while循环可以相互转换
do-while循环
do-while循环语句标准格式:
①初始化部分;
do{
③循环体部分
④迭代部分
}while(②循环条件部分);
循环的区别与选择
从循环次数角度分析
- do...while循环至少执行一次循环体语句
- for和while循环先循环条件语句是否成立,然后决定是否执行循环体,至少执行零次循环体语句
如何选择
- 遍历有明显的循环次数(范围)的需求,选择for循环
- 遍历没有明显的循环次数(范围)的需求,循环while循环
- 如果循环体语句块至少执行一次,可以考虑使用do...while循环
- 本质上:三种循环之间完全可以互相转换,都能实现循环的功能
break
break语句用于终止某个语句块的执行
{ ……
break;
……
}
continue
- continue只能使用在循环结构中
- continue语句用于跳过其所在循环语句块的一次执行,继续下一次循环
- continue语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环
break和continue关键字的异同点:
相同点:关键字后面不能声明执行语句
不同点:
- break结束当前循环,此关键字后边不能声明执行语句,continue结束当次循环,此关键字后边不能声明执行语句。
- 使用范围:break用在switch循环结构中,continue使用在所有循环结构中
数组
数组,多个相同类型的数据按照一定顺序排列的集合,并使用一个名字命名,通过编号的方式对这些数据进行统一管理。
数组里面常见的概念:
- 数组名
- 下标(索引)
- 元素
- 长度
数组的特点:数组是有序排列的
数组属于引用数据类型,数组里面的元素可以是引用数据类型,也可以是基本数据类型。
数组是连续存储空间,链表不连续。(数组适合按照索引查询操作,链表适合增加,修改操作)
数组分类:
- 按照维数:一维二维数组
-
按照类型:基本数据,引用数据
注意:数组一但初始化之后,数组长度就固定就不能更改,不管是静态初始化还是动态初始化。
数组就相当于一个容器,创造出来的时候就规定了容器的容量。
定义数组:
int [] a= new int[]{1,2,3};
调用数组中的元素:(数组的索引是从0开始的)
a[0]=1;
a[1]=2;
a[2]=3;
获取数组长度
System.out.println(a.length);
当数组的类型不同,默认初始化的值也不同:
- 整型时:0
- 浮点型:0.0
- char:0或者'\u0000'
- boolean:false
- 引用数据类型:null
对下列一维数组进行内存解析:
int [] arr= new int []{1,2,3};
String [] arr1 = new String [4];
arr1 [1]="刘德华";
arr1 [2]="张学友";
arr1=new String[3];
左边为栈空间,右边为堆空间
使用引用计数算法,垃圾机制会把没有首地址的数组自动回收
二维数组定义
其实java本没有二维数组,他只是更形象的把它表达出来,本质是一维数组中存放着另一个一维数组的地址值
1.int[][]arr =new int [][]{1,2,3},{4,5,6},{7,8,9};
2.int []arr[]=new int [][]{1,2,3},{4,5,6},{7,8,9};
3.int arr[][]=new int [][]{1,2,3},{4,5,6},{7,8,9};
遍历二维数组
for(int i=0;i<arr.length;i++){
for(int j=0;j<arr.length,j++){
System.out.print(arr[i][j] + "\t");
}
System.out.println();
}
二维数组有两种动态初始化方式(打印元素的区别)
int a[][] = new int [4][];
//此处输出a[4],输出的是null,在第二维度的数组初始化之后,如果没有赋值,
//输出的就是当前数组元素类型的默认初始化值。
int b[][] = new int[3][2];
//此处如果输出a[2],输出的就是地址值,因为此时第二维度的数组已经初始化,
//输出a[2][2],输出的就是当前数组元素类型的默认初始化值。
练习题
答:x为一维数组,y为二维数组
- x[0]为一维数组的第一个值,不能被整个二维数组赋值
- y[0]是一个一维数组,可以赋值
- y[0][0]这是一个int类型的数值,不可以被整个一维数组赋值
- x[0][0]本身就是错误的
- 两个int类型的可以相互赋值
- 一维数组和二维数组之间不能赋值
关于数组的算法
冒泡排序:这是我的个人理解
n个元素就比较n-1次找出最大的
例子 564912
第一轮
5 6 4 9 1 2
5 6 4 9 1 2
5 4 6 9 1 2
5 4 6 9 1 2
5 4 6 1 9 2
5 4 6 1 2 9
第二轮
5 4 6 1 2
4 5 6 1 2
4 5 6 1 2
4 5 1 6 2
4 5 1 2 6
第三轮
4 5 1 2
4 5 1 2
4 1 5 2
4 1 2 5
第四轮
4 1 2 5
1 4 2 5
1 2 4 5 最后的结果就是 1 2 4 5 6 9
代码
public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) { //外循环只需要比较arr.length-1次就可以了
for (int j = 0; j < arr.length - 1 - i; j++) { //-1为了防止索引越界,-i为了提高效率
if(arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j+1] = temp;
}
}
}
}
数组中的常见异常
1.数组角标越界:厕所只有五个坑,你上厕所外面拉就越界了
public void test(){
int arr[]= new int []{1,2,3};
Systrm.out.println(arr[4]);
}
2.空指针异常:
//此时第二维度数组还没有初始化,为空,输出任何一个元素都是空指针异常
public void test(){
int arr[][]= new int [3][];
System.out.println(arr[0][2]);
}
面向对象(上)
面向对象核心概念:
对象:对象是类进行实例化在堆中创建的实例
类:类就是对具有共同特征的事物的抽象描述,类中包含属性方法。
对象的创建与使用
堆(Heap):本质就是存放对象实例以及数组。
栈(Stack):即虚拟机栈,存储局部变量。局部变量表存放编译器可知长度的各种基本类型(int,short,long,char,float,double,boolean,byte)、对象引用(并不是对象本身,而是,而是对象在堆里面的首地址)
方法区(Method Area):加载并存放完整对象结构
类的属性:
1.变量
变量必须先声明赋值后才能使用。
成员变量:声明在类中,方法外
局部变量:声明在方法中,代码块中,构造器中,方法的形参
成员变量跟局部变量区分点:
1.内存中存放位置:成员变量存放在对象中,对象存储在堆空间中 。
局部变量存放在方法对应栈帧中,栈帧存放在栈中。
2.生命周期:成员变量的生命周期跟对象同步。
局部变量随着方法的出栈入栈而创建和消亡。
3.作用域:成员变量的作用域是类内部,在类外部使用需要创建对象后再使用。
局部变量的作用域是方法内部,代码块内部,构造器内部。
4.权限修饰符:成员变量可以使用权限修饰符,局部变量不可以使用权限修饰符。
5.是否有默认值(挺重要):成员变量有默认初始化值。
局部变量没有默认初始化值,必须显示赋值后才能够使用。
6.变量的赋值过程:
非静态属性(实例变量)复制位置
1.默认初始化(有默认初始化的时候,还没有真正赋值)
2.显式初始化
3.构造器中初始化
4.创建对象之后,通过对象.属性或者对象.方法赋值
5.代码块中初始化
执行顺序:1 -> 2/5 ->3->4 (2跟5的执行顺序按照命名位置的先后顺序执行)
2.方法
1.命名格式:权限修饰符 [其他权限修饰符 可以是抽象] 返回值类型 方法名字(形参列表){
方法体
}
2.方法中使用的权限修饰符:public protected 默认 private
3.形参列表,可以有可以没有,
4.return 关键字,再有方法返回值的时候使用,也可以当做结束方法的一种方式。
3.方法重载
方法重载就是在类中有多个重名方法,只是形参列表不一样。方法重载与返回值,权限修饰符无关。
两同一不同(名字相同,同一个类,不同的形参列表)
举例:数组工具类中的二分法查找,放入不同形参类型调用不同的方法。
4.方法的值传递机制
实参:方法调用时实际传递进来的参数
形参的数据类型基本数据类型,实参传递给形参的就是基本数据类型的数据值。
形参的数据类型是引用数据类型,实参传递给形参的就是引用数据类型的地址值。
5.递归
递归就是自己调自己
我们在使用递归时,要向着已知方向去调用,否则方法自己调自己可能会造成栈溢出(StackOverflowError)
6.类的构造器
构造器就是用来实例化类的对象,没有显示定义类的构造器的时候,类本身会默认提供一个空参构造器。|
但是我们显示定义过构造器之后,系统就不会再默认提供构造器。
构造器和构造器之间会构成重载。
7.方法重写
子类继承父类方法,然后不满足自己的需求,对父类方法在子类中进行重写。
方法重写的要求:
①权限修饰符不能小于父类,方法名和父类必须相同,参数列表必须和父类相同,
注意:如果父类方法权限修饰符为私有,子类不能重写父类方法
如果父类中有静态方法,子类在自己类中声明,且各方面都满足重写,但是这不是方法重写
②返回值类型:如果父类是void,子类也必须是void
如果父类是基本数据类型,子类也必须跟父类相同
如果父类是引用数据类型,子类需小于等于父类的数据类型(多态)
③异常类型:子类抛出的异常类型需要小于等于父类异常类型
类的成员:
代码块:
1.作用:对当前类或者属性进行初始化
2.代码块分类
代码块也非为静态代码块、非静态代码块
3.具体使用
静态代码块:
初始化当前类的加载信息
内部编写输出语句,定义变量,运算等
随着类的加载而加载
静态代码块的执行早于非静态代码块
静态代码块之间的执行顺序,先声明先执行
静态代码块内部只能调用静态方法,静态属性
class a{
static{
}
}
非静态代码块:
初始化对象的信息
内部可以编写输出语句、定义变量、运算等
随着对象的创建而执行,每创建一个对象,就执行一次,但是是在对象创建之前就执行
非静态代码块的执行顺序,就是声明的顺序
非静态代码块可以调用非静态方法、非静态属性、静态方法、静态属性
class a{
static{
}
}
面向对象特征:
一丶封装性
1.高内聚,低耦合:
高内聚就是方法内部或者类内部处理细节自己完成,不需要外边操作。
低耦合就是暴露自己的调用方法给外部使用,其它联系很少。
简单粗暴来说:封装性就是把自己想暴露暴露出去,自己想隐藏的隐藏起来。
2.实现数据封装是通过调用四种权限修饰符来调整,外边来调用数据可见性的大小。
3.封装新的体现:
①属性的封装:主要是通过priate私有化类的属性,让外边不能直接操作属性,通过类内部get,set方法来操作数据
②方法封装:私有化类的方法,让外部类不能直接调用方法,但是可以在类内部通过创造其他方法,从而实现间接调用方法的目的,但是这个是类的拥有者才可以去实现的,否则我们还是无法去调用到该方法。
③构造器的封装:构造器的封装体现在单例模式,我们无法直接通过new 类名去实例化该类的对象,也就是不允许在类的外部去创建该类对象。
二、继承性
1.继承性的理解
多个类之间有共同属性和方法,为了避免代码冗余,我们可以把这些属性和方法统一放到一个父类中,让这些具有共同特性的子类去继承此父类,子类中就不用重复去声明这些属性和方法。
2.继承性的优点
①减少代码冗余②利于功能扩展③为多态性的使用提供前提
3.声明格式
class A extends B
4.继承的条件:类跟类之间是is a 的关系
5.在子类继承父类之后,子类拥有父类的所有属性,子类也可以根据自己的业务逻辑去重写父类方法,在权限允许的情况下,我们可以去嗲用父类的方法和属性。
6.所有类的共同父类都是Object类,还有java不支持多继承,每个类只能直接继承一个类,可以间接继承多个类,但是java支持多实现。
三、多态性
1、多态性的理解
类与类之间以继承性为前提,父类变量指向子类引用(或者是子类对象赋值给父类引用),子类对象是多种形态,这就叫多态性。
优点:扩大子类延展性,减少代码冗余。
缺点:不能调用子类特有的方法和属性,只能调用子类重写父类的方法,多态不能使用在属性上。
2.多态性的应用
虚方法调用:
前提:父类有方法a,子类重写了a
理解:编译时以为调用的是父类的方法,运行时调用的是子类重写的方法
简写为:编译看左边,运行看右边
3、多态性使用前提
继承关系、方法重写
4.instanceOf关键字
某些特殊场景需要把父类对象强转成子类类型,在强转之前需要instanceOf判断。
a instance b 判断对象a是否是b类的实例,是就返回true,不是就返回false。
Object
1. Object类的声明
所有类都直接或者间接继承Object类
Object类中没有属性,构造器只有一个,是空参构造器
Object类中的方法只要权限允许,所有的类都可以进行调用
2.常用方法
equals(Object obj)、toString() 、clone()、finalize()
深拷贝、浅拷贝:
深拷贝:对象b把内容赋给对象a,a在修改自己的内容时,不会影响b
浅拷贝:对象b把内容赋值给对象a,同时也把地址赋给a,a或者b在修改内容时会影响对方。
关键字
一丶this关键字
1.在构造器中出现形参和传进来的实参同名问题怎么解决?
这时候就需要用到this关键字来区分这两个变量。
2.this可以调用结构:属性丶方法丶构造器
①this调用属性
public class Person{
private String name;
public 类名(String name){
this.name = name;
}
}
②this调用方法
这里面的this我们都会忽略不写
public void eat(){
this.fly();
}
③this调用构造器
this在一个构造器中调用构造器只能声明在第一行,而且只能调用一次,调用时默认会调用this.空参构造器
public Person{
this();
}
3.this的理解:this就是调用当前对象,在构造器中调用就是当前正在创建的对象
二丶super关键字
- 为什么使用super?
因为子类继承父类之后,需要调用父类中的同名方法同名方法或者同名属性,需要super去调用。 - super
super就是父类的意思 - super调用属性、方法、构造器
- 在调用这些结构之前,父类的结构可见性需要满足(权限允许得情况下),子类才能去调用
- super调用属性、方法
子类在继承父类之后,会获得父类所有的属性和方法,权限允许的情况下,子类可以全部调用。子类调用父类中重名方法和属性,使用super.方法名,super.属性名去调用。 - super调用构造器
在子类的构造器中,首行调用super(参数)来调用父类中构造器
注意:this(参数)和super(参数),不能同时调用 - 子类实例化全过程
class A{
public A(){}
}
class B extends A{
public B(){
super();
}
}
class C extends C{
public C(){
super();
}
}
子类C在实例化时候,会加载父类B构造器,父类B也会直接或者间接调用到其父类A的构造器,直到调用到最终父类Object的构造器,子类A实例化过程结束。正是因为加载过直接父类或者间接父类的属性结构,所以在权限允许的情况下,我们可以去调用父类中的结构。
三、static关键字
1.定义 静态的
2.修饰结构
①变量:修饰的变量成为类变量或者是静态变量,不被修饰的成为非静态变量
静态变量与非静态变量对比:
静态变量在内存中只有一份,被类的多个对象共享。其中一个对象修改静态变量值会影响其他对象,
非静态变量每个对象都有一份,修改变量值也不会影响其他对象
内存分析:静态变量1.6:存放在方法区,1.6之后存放在堆空间的静态域中
非静态变量存放在堆空间中的对象空间中
生命周期:静态变量随着类的加载而加载,随着类的消亡而消亡。类只加载一次,静态变量也只加载一次。
非静态变量随着对象的加载而加载,随着对象的消亡而消亡。每创建一次对象,非静态变量就加载一次。
调用者:静态变量可以被类直接调用,类名.静态变量名
非静态变量只能被对象调用,需要初始化对象之后才能调用
②方法:
静态方法和非静态方法对比:
静态方法也随着类的加载而加载,只加载一次,类名可以直接调用
非静态方法随着对象的创建而加载,每创建一次对象加载一次,对象名调用
非静态方法内可以调用静态方法,非静态方法内不能调用静态方法,因为静态方法在非静态方法没加载的时候就已经加载了,违反逻辑
静态方法内不可以调用this、super关键字,因为this关键字是指当前对象、super关键字是指父类对象,对象没创建也就不能调用该关键字。
3.为什么使用static关键字?
因为在一些开发场景中,需要多个对象共享一个变量,类似我们的国籍都是中国,大家都一起使用。
什么时候把方法设置成静态的?
类似于工具类,我们在一些开发场景就不用特意去创建对象去调用,这样就会很方便。
四丶final关键字
1.final 最终的
2.final 修饰类,方法,变量
3.final修饰类,类就不能被继承
例如:String、StringBuffer、StringBuilder
final修饰方法,方法就不能被重写
例如:Object里面的getClass()方法
final修饰变量,变量就变成常量
例如:Math.PI 这个就是常量
final可以在哪些地方修饰属性?
final可以在显示初始化(变量定义时候)、构造器中、代码块中修饰属性。
final修饰局部变量,一旦局部变量初始化赋值之后,进入方法内不能改变。
注意:final修饰的成员变量没有默认初始化值,必须在构造器结束前给变量赋值。
五、abstract关键字
1.abstract概念:抽象的
2.abstract可以用来修饰类、方法
3.abstract修饰类:
表明此类是修饰类,抽象类不能实例化,虽然不能实例化,但是抽象类中有构造器,这是因为子类可以实例化,会调用到父类构造器。
抽象类中有抽象方法,也有非抽象方法。抽象类中可以没有抽象方法,但是抽象方法一定在抽象类中。
4.abstract修饰方法:
表明此方法是抽象方法,抽象方法没有方法体,具体功能就不能实现。
子类方法继承父类(抽象类)之后,必须重写父类中所有抽象方法,才能实例化,否则该子类仍然还是抽象类。
5.abstract关键字不能修饰的结构:
不能修饰属性,构造器,静态方法,final修饰的类,final修饰的方法(final修饰的结构都不能被子类操作,这跟abstract相矛盾)。
六.interface关键字
1.接口概念理解:接口是一种规范,标准(就是给实现类定义的一种标准)
2.接口内可以声明的结构:
jdk1.8之前:只能声明抽象方法,用public abstract修饰
jdk.1.8:还可以定义静态方法、默认方法
jdk:还可以生命private方法
3.接口内不能声明的结构:
构造器、内部类、代码块
因为接口不能实例化
4.类用implement关键字实现接口
5.实现类实现了接口之后,获取到接口中声明的属性和方法,必须全部实现接口中的抽象方法,才能实例化。否则实现类就是一个抽象类。
类可以多实现接口,弥补不能多继承的局限性。
关于接口和抽象类的一个面试题:
抽象类跟接口的区别:相同点:二者都不能实例化,内部都有抽象方法
不同点:抽象类中有构造器,接口中没有构造器,接口可以实现多实现,抽象类只能单继承。
匿名对象的使用:
只在堆空间内开辟空间,并没有把堆空间内的地址值赋值给栈空间内的某个变量进行存储
不显示实例化一个对象,直接(new 对象名)去调用方法或者直接赋值。
在含有参数的方法中直接新建对象进行使用
匿名对象一般就被使用在被调用在一次的方法中,并且使用完成后马上就会被垃圾回收机制回收
//此处为定义的方法
public void method(Person person){
System.out.println(person.name);
}
//此处为调用此方法,并采用匿名对象的方式
method(new Person);
变量的赋值
变量为基本数据类型时:此时赋值的是变量所保存的数据值
变量为引用数据类型时:此时赋值的是变量所保存的数据的地址值
权限修饰符
修饰符
修饰符 类内部 同一个包 不同包的子类 同一个工程
private 1
default 1 1
protected 1 1 1
public 1 1 1 1
对class类的修饰只能用public和default
不同包的子类(在不同包中,A类继承了B类,可以进行调用)
下面这张图可以更好地理解权限修饰符的范围大小
继承性
子类继承父类之后,可以获取到父类中私有的方法,但是不可以显示调用,这就是封装性的影响。子类继承父类之后,拓展性更强。
java不允许多继承(多个父亲),允许单继承和多层继承。
重写(override)
1.子类继承父类之后,可以对父类中同名同参数的方法,进行覆盖操作。
2.重写之后,创建完子类对象之后,通过子类对象调用父类中同名同参数的方法时,实际执行的是子类重写父类的方法。
3.子类中不能重写父类私有的方法
返回值类型
1.父类方法返回值类型为void,子类也得是void
2.父类方法返回值类型为A类(引用数据类型),子类的返回值类型可以是A类,也可以是A类的子类,例如object的子类---String
3.父类方法返回值类型为基本数据类型,子类必须相同
4.异常必须比父类小或者相等
5.子类和父类中的同名同参数的方法要末都声明为非static的,要末都是static的可以重写,二者必须一致。否则一方声明为static的是不可以进行重写的,单方面的付出得不到回应,也是徒劳的。
重写: 若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法
@Override是伪代码,表示重写(当然不写也可以),不过写上有如下好处:
1、可以当注释用,方便阅读;
2、编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错。例如,你如果没写@Override,而你下面的方法名又写错了,这时你的编译器是可以编译通过的,因为编译器以为这个方法是你的子类中自己增加的方法。
举例:在重写父类的onCreate时,在方法前面加上@Override 系统可以帮你检查方法的正确性。
面试题:区分方法的重载与重写
方法重载:首先来说,二者毫无关系
- 在同一个类中
- 方法名相同,参数列表不同
- 方法返回值 权限修饰符任意
- 与方法的参数名无关
方法重写:
- 具有子父类关系的两个类中
- 方法名相同,参数列表相同,方法返回值相同
- 访问修饰符,访问范围大于等于父类权限
super
super理解为父类的什么什么(构造器,属性,方法)
当子类与父类方法名相同时,也就是子类重写父类的方法时,用super.方法名就是调用了父类的方法。(方法也是一样的)
子类对象实例化的过程
虽然子类实例化时,调用了多个父类对象的构造方法,但是自始至终只创建了一个子类对象。
子类对象在实例化时,会默认先调用父类中的无参构造器,然后在调用子类本身的相应构造方法。
多态性
对象的多态性:父类的引用指向子类的对象。(子类的对象赋给父类的引用)
多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法--虚拟方法调用。
Person person= new Man();
person.(此处只能调用父类中有的方法,但是在执行的时候执行的是Man重写Person里面的方法)
总结:编译在左,执行在右。(多态)
多态使用前提:1.两个类之间需要是子父类关系。
2.子类需重写父类中的方法。
注意:对象的多态性只适用于方法,属性不可以使用,也没有作用。
多态性是运行时行为
动态绑定:编译时候是父类类型,方法的调用是运行时确定的,调用的是子类方法
子类继承了父类方法,在调用方法时,形参是父类对象,可以new一个子类对象进行调用,调用的方法是子类重写的方法,详情可见代码。
jdbc就是多态的体现
比如:连接数据库,mysql就要重写java给的总连接的子类,然后新建父类对象,但是这个实例对象是mysql,然后调用父类的方法,但是执行的时候是去执行mysql里面的方法。
下转型判断方法instanceof方法
会抛出异常:比如两个不相干的,男人女人都是人的子类,二者之间不可以强制类型转换
还有,子类不可以被强制类型转换成父类,只有父类引用指向子类对象,父类对象才可以强制类型转换成子类。
面试题:==和equals的区别
1.==是运算符 ,equals()是方法
2.==在基本类型比较时,比较的是数值,不一定类型要相同(注意:不能比较布尔类型,剩下都可以比较)
3.在引用类型的时候比较的是地址值,看是否指向同一地址,也就是是否为同一对象。
4.Object中equals是比较的是地址值,看是否指向同一对象。
5.像Object的子类String,Date,File,包装类都是重写了Object的equals()方法,比较的不是地址值,而是它里面的值。
toString方法
Object里面的返回的是String型的数据:类名+@+哈希值的16进制形式
String,Date,File等包装类都重写了ToString方法
类型转换(包装类和基本数据类型相互转换)
包装类转换为基本数据类型(装箱)
int num1 = 10;
Integer num2 = new Integer(num1);
包装类转换为基本数据类型(拆箱)
Integer num1 = new Integer(10);
int num2 = num1.intValue();
在JDK5.0之后引入了自动装箱和拆箱
自动装箱(包装类转换为基本数据类型)
int num1 = 20;
Integer num2 = num1;
自动拆箱(包装类转换为基本数据类型)
Integer num1 = new Integer();
int b = num1;
基本数据类型、包装类转换为String类型
调用了String重载的valueOf()方法
//基本数据类型转换为String
float f1 = 12.3f;
String s1 = String.valueOf(f1);
结果:“12.3”
//包装类转换为String
Double d1 = new Double(12.4);
String s1 = String.valueOf(d1);
结果为:“12.4”
String类型转换为基本数据类型、包装类
调用包装类的parseXxx()方法(Xxx为类型)
String s1 = "123";
int num1 = Integer.pareInt(s1);
以上方法转换时可能会报NumberFormatExceptio
面向对象(下)
Static静态变量
static 可以修饰属性、方法、代码块、内部类
先来区分一下成员变量和局部变量
变量分为成员变量和局部变量
在方法体外,类里面声明的变量为成员变量:
- 实例变量:(不用static修饰)
- 类变量(用static修饰)
在方法体内部声明的变量为局部变量:
- 形参(方法,构造器中定义的变量)
- 方法局部变量(在方法内定义)
- 代码块局部变量(在代码块内定义)
成员变量和局部变量值初始化方面的异同:
相同:都有生命周期
不同:局部变量除形参外,需要显示初始化
属性按是否使用static分为 静态属性 和非静态属性
静态属性和非静态属性的异同:
- 在内存中存放位置不同 :static修饰的方法和属性存放在内存中的方法区,而非静态属性存放在内存中的堆区
- 静态属性和静态方法随着类的加载而加载,非静态的要随着实例化对象之后才存在
- 生命周期不同:静态属性和静态方法随着类的销毁而销毁,非静态的要随着对象销毁之后才销毁
- 调用方法不同:静态属性和静态方法可以直接用:类名去调用,非静态属性和非静态方法要实例化对象之后才可以去调用
- 静态变量早于对象的创建
Static注意点:
静态方法中不能使用super,this关键字,这两个关键字是基于有对象的时候使用的,而静态方法是在类加载的时候就开始使用了。
static在开发中的使用场景:
1.属性被多个对象共享。
2.操作静态属性的方法,通常都是静态的。(普通方法也可以调用静态属性和静态方法,静态方法不可以调用普通方法和属性)
3.工具类中的方法通常声明为静态方法(应为要被多个对象和类使用)
单例模式:
对单例模式的理解:
只有一个实例,不能在main()里面声明对象,要在类里面创建对象,并且要用一个方法(静态方法)返回对象,创建对象和返回对象的方法都是静态的(static),饿汉是在返回对象之前就创建完成对象,new对象的时候令其为null,在返回对象方法中创建对象,要判断一下,有对象直接返回,没有对象创建对象,否则会多个对象指向同一个地址。
单例的实现通过两个步骤:
1.将该类的构造方法定义为私有方法,其它类无法调用该类构造方法区实例化该类对象,只能通过该类提供的静态方法得到该类实例。
2.在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。
单例模式优缺点:
优点:
-
在内存中只有一个对象,节省内存空间;
-
避免频繁的创建销毁对象,可以提高性能;
-
避免对共享资源的多重占用,简化访问;
-
为整个系统提供一个全局访问点。
缺点:
-
不适用于变化频繁的对象;
-
滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;
-
如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失;
单例模式之懒汉模式
public class Person{
//私有化构造器
private Person(){}
//声明static对象,但是没有初始化
private static Person person = null;
//以自己为实例为返回值的静态的共有方法,静态工厂方法
public static Person getPerson(){
//被动创建,当需要的时候才去创建,如果已经创建直接返回对象
if(person == null){
person = new Person();
}
return person;
}
}
单例模式之饿汉模式
public class Person{
//私有构造器
private Person(){}
//指向自己的私有静态引用,主动创建
private static Person person = new Person();
//静态的获取对象的方法
public static Person getPerson(){
return person;
}
}
饿汉是一上来就声明对象,懒汉是在方法中创建对象,如果有对象就直接返回对象。
饿汉和懒汉区别
懒汉式:
优点 :延迟对象的创建
缺点 :目前的写法线程不安全,(只能在单线程环境下使用懒汉模式,在多线程环境下,判断对象是否被创建,如果有多个线程去同时判断,可能会导致实例化多个对象)
饿汉是:
优点 :线程安全
缺点 :类一加载对象就被创建了,如果一直未被调用就会产生内存浪费,对象加载时间过长 ,周期过长
单例模式应用场景:只能由一个对象工作的情况时候使用,
比如打印机只能打印一个作业。
数据库连接池 ,限制数据库的连接数量,如果你有多个数据库连接池就不是单例模式,也就不能限制数据库的连接数量了
手机应用一个应用只能同时运行一个
windows只能打开一个任务管理器。
windows里面只有一个回收站
饿汉是一上来就声明对象,懒汉是在方法中创建对象,如果有对象就直接返回对象。
饿汉和懒汉区别
懒汉式:优点 延迟对象的创建
缺点 目前的写法线程不安全
饿汉是:缺点 对象加载时间过长 ,周期过长
优点 线程安全
单例模式应用场景于只能由一个对象工作的情况时候使用,
比如打印机只能打印一个作业。
数据库连接池 ,限制数据库的连接数量,如果你有多个数据库连接池就不是单例模式,也就不能限制数据库的连接数量了。
手机应用一个应用只能同时运行一个
windows只能打开一个任务管理器。
windows里面只有一个回收站
代码块
用{}括起来的成为代码块
代码块分为四种:
1.普通代码块:
类中方法的方法体
2.构造代码块:
构造块会在创建对象时被调用,每次创建时都会被调用,优先于类构造函数执行。
3.静态代码块:
用static{}包裹起来的代码片段,只会执行一次。静态代码块优先于构造块执行。
4.同步代码块:
使用synchronized(){}包裹起来的代码块,在多线程环境下,对共享数据的读写操作是需要互斥进行的,否则会导致数据的不一致性。同步代码块需要写在方法中。
一、静态代码块 vs非静态代码块
静态代码块
- 静态代码块随着类的加载而执行,而且只执行一次
- 作用:初始化类的信息
- 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
- 静态带代码块在非静态代码块之前执行
非静态代码块
- 非静态的代码块随着对象的创建而调用
- 非静态代码块每创建一个对象都会执行一次。
- 可以在创建对象时,对对象的属性等进行初始化
二.静态代码块和构造代码块(非静态代码块)的异同点
相同点:都是JVM加载类后且在构造函数执行之前执行,在类中可定义多个,一般在代码块中对一些static变量进行赋值。
不同点:静态代码块在非静态代码块之前执行。静态代码块只在第一次new时执行一次,之后不在执行。而非静态代码块每new一次就执行一次。
总结:先父类后子类,先静态后非静态然后再构造方法。
注意:
1.静态代码块不能存在于任何方法体内。
2.静态代码块不能直接访问实例变量和实例方法,需要通过类的实例对象来访问。
final关键字
字面意思就是最终的,可以声明成员变量、方法、类、本地变量。一旦引用声明为final之后,就不可以改变这个引用,编译器会报编译错误。
- 当用final来修饰一个类的时候,就不能被其他类继承了(可以理解为没有儿子了,也就是太监类,哈哈哈)比如 String System StringBuffer等包装类,它们的对象只是可读的,bunengjin
- 当用final来修饰一个方法的时候,方法就不能被重写
- 当用final来修饰一个变量的时候,变量就变成了常量
- final修饰属性的时候,可以在两个地方堆其进行赋值,在定义时,还有在构造函数中(构造器,代码块),final属性一旦赋值就不能更改。
注意:
- final方法在编译阶段绑定,称为静态绑定(static binding)。
- final不能修饰形参,因为形参是可变的,当形参用final修饰过后
- 就变为了常量,此方法就写死了。
- 方法中给final变量赋值是不可以的:创建完对象变量已经在对空间中生成,不能再修改,也就不能赋值了,所以是不可以的。
- static final修饰一个变量,此变量即为全局变量
- final和abstract这两个关键字是反相关的,final类就不可能是abstract的。(final和abstract就是一对冤家)
- 将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
abstract
抽象的
用来修饰类和方法
当用来修饰类的时候:
- 此类不可以实例化
- 此类必须有构造器,方便子类实例化
当用来修饰方法的时候:
- 抽象方法没有方法体
- 抽象方法只存在抽象类中
- 抽象类中可以没有抽象方法
- 子类继承父类,必须全部重写直接父类和间接父类中所有的方法,此子类才可实例化,否则要加abstract声明此类,才可实例化。
abstract class Animal{
String name;
public Animal(){}
public abstract void shout();
}
class Dog extends Animal{
@Override
public void shout() {
System.out.println("汪 汪 汪!");
}
}
Dog dog = new Dog();
dog.shout();
}
//输出结果就为汪汪汪
注意点:
- abstract不能用来修饰:属性 构造器
- abstract 不能用来修饰私有方法丶静态方法丶final的方法
- abstract 与 final 本身就是矛盾的
- final是不能被重写 abstract 是不重写就不能用
接口(interface)
- 接口是一种特殊的抽象类,但是接口却不可以实例化,这是和抽象类之间的差异。
- java的缺点:不支持多继承,接口可以实现多继承,一个类可以继承多个接口。
- 接口跟类是两个并列的结构。
- 接口属于一个规范:例如应用程序利用JDBC连接不同的数据库,都是有一条规范流程。如果不使用JDBC接口,连接数据库会有千变万化的操作。
注意:
- 接口中没有构造器
- 接口直接可以继承
-
接口实现多态性:
例子:把USB接口定义为一个接口,打印机与电脑进行交互需要把USB接口实例化重写USB里面的方法才可以跟电脑进行交互。
项目的需求是千变万化的,我们需要以不变应万变,不变的就是规范,而规范就是接口,我们实际上就是面对接口编程
JDK8以后可以在接口中定义静态方法和默认方法(default)
- 1 .接口中定义的静态方法,只能通过接口调用。
- 2 .实现类的对象不能调用静态方法,可以调用默认方法(default)。
- 实现类重写了接口中的默认方法,调用时仍然调用的是重写之后的方法。
- 3. 如果子类(过着继承类)继承的父类实现的接口中声明同名同参数的方法,在没有重写的情况下,默认调用父类中的方法(类优先原则)。
- 4.两个接口中都调用了同名同参数的方法,实现类没有重写,且没有方法体,会出现接口冲突。
- 5.在实现类中调用接口的默认方法 :
接口名称.super.默认方法名
在子类中调用父类中的方法(重写过的,没重写的不用super):
super.方法
public void myMethod(){
method3();//调用自己重写的方法
super.method();//调用的是父类中声明的
CompareA.super.method3();
CompareA.super.method3();
}
内部类
将a类生命在b类的内部,类a就是内部类,类b就是外部类。
内部类一共分为四种:成员内部类、局部内部类、匿名内部类、静态内部类
成员内部类:
类A定义在类B中,类B就称为内部类,类A就是外部类。
class A{
class B{
}
}
内部类随便访问外部类的属性和方法,但是外部类想要去访问内部类,必须创建内部类对象才可以进行操作。
当外部类的属性和方法与内部类的属性和方法同名(并没有明确说明属性的值相同,只是说名字相同),在内部类就会被隐藏,但是可以用此方法调用,外部类.this.属性/方法。
创建内部类对象:
在创建内部类之前必需先创建外部类,因为内部类是寄生于外部类里面。
创建方式有两种:
public static void main(String [] args){
//第一种创建方式
类名 别名 = new 类名();
/第二种是用反射方法创建
类名 别名 = new 类名getClass();
}
局部内部类
局部内部类存在于方法中(访问权限仅限于方法或作用域中)
注意:局部内部类就像局部变量一样,前面不可以添加访问修饰符以及static修饰符
匿名内部类
注意:匿名内部类没有构造器,是唯一没有构造器的内部类。匿名内部类和局部内部类只能访问外部的final变量。
静态内部类
静态内部类和成员内部类相比多了一个static修饰符。它与类的静态成员变量一般,是不依赖于外部类的。同时静态内部类也有它的特殊性。因为外部类加载时只会加载静态域,所以静态内部类不能使用外部类的非静态变量与方法。
同时可以知道成员内部类里面是不能含静态属性或方法的。
异常处理
说明:
- finally是可选的
- 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配。
- 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常处理。一旦处理完成,就跳出try-catch中,如果后面没有finally,还会接着执行后面的代码。
- 捕捉到异常,异常之间的子父类关系也会影响结果:当有子父类关系时,子类必须在上,否则会抛出异常。没有子父类关系时,不会对处理异常结果产生影响。
- 常用异常对象处理方式:1)String getMessage() 2)printStackTrace()
- 在try结构中声明的变量,在跳出try结构之后,不可以在被调用
- 编译时一定要进行异常的处理,运行时没必要处理,运行时有异常的话,是代码本身就有问题,捕捉异常也是无济于事。
- 子父类抛出异常的关键字需要一致,比如说 父类用了throws,子类也必须用throws。
面试题:
throw 与 throws 的区别
相同点:二者都只是把异常给抛出,并没有真正去处理异常
不同点:
- 前者用字方法体内,后面跟的是异常类对象名
- 后者用在方法声明后面,后面跟的是异常类名
线程
线程的生命周期:
- 新建(NEW) :创建后但是没有启动的状态。
- 就绪: 就绪就是获取了除CPU执行权以外的所有资源的一个状态(这个状态可以用一句话来描述,那就是万事俱备,只欠东风!)。
- 阻塞 (BLOCKED):线程被阻塞住了,通俗的理解就是被锁住了,就像一条狗被是拴住了不能动弹了。阻塞的下一个状态就是就绪。
- 运行(RUNNABLE):调用线程的start方法()线程开始运行。(Runnable状态包括两个状态(running和ready),这两种状态,又可能正在运行,也有可能在等待,等待的就是获取了除Cpu以外的所有资源,只有获取了CPU资源之后才是真正的运行状态。可能有人会有疑问,为什么不把这两种状态分为两种状态,因为两种状态之间的空档期非常的短暂,因此将他们合并成一种状态。)
- 死亡:线程已经结束执行。
线程中常用方法:
- start:java虚拟机调用该线程的run方法(就绪)
- run:需要重写thread类中的此方法,将创建的线程要执行的操作声明在此方法中(真正被执行的方法)
- currenThread() :静态方法,返回执行当前代码的线程
- getName():获取当前线程的名字
- setName():设置当前现成的名字
- yield():释放当前cpu的执行权(个人理解将线程从运行状态转换为就绪状态)使用场景:在大量工作量前可以使用此方法,避免一个线程长期占用CPU使用权,给其他线程运行机会。
- join():当线程A调用了线程B的join()方法时,线程A就必须等待线程B执行完之后才可以获得执行
- stop():强制停止线程
- sleep(long millitime):让当前线程就进入休眠状态,局部参数决定线程休眠多久。
- isAlive:判断线程是否“存活”。
- wait():启动此方法,线程进入阻塞状态,释放锁,其他线程可以获取锁。
- notify():唤醒被wait的线程,若唤醒多个线程,唤醒优先级高的线程(此处只唤醒一个线程)。
- notifyall():唤醒所有被wait的方法(此处是所有线程)
- 上面三个方法必须都在同步代码块或同步方法中,三者调用者必须是同步代码块或者同步方法中的同步监视器,否则会抛出异常
线程的优先级
MAX_PRIORITY:10
NORM_PROORITY:5
MIN_PRIORITY:1
获取当前和设置当前线程的优先级
getPriority()
setPriority(int p)
说明:高优先级的线程要抢占低优先级线程cpu的执行权,但是只是从概率上来讲,高优先级的线程高概率的情况下会优先执行,并不意味着只有当高优先级的线程执行完之后,才轮到低优先级的线程去执行。
创建线程的方式
一、继承Thread类创建线程:
- 继承之后的子类重写Thread里面的Run方法,Run方法又称为执行体。
- 子类实例化,线程对象创建成功
- 子类对象调用start方法()
二、通过Runnable接口创建线程类
- 创建接口的实现类,实现类实例化,重写接口中的Run方法,run同样也是执行体
- 调用线程对象的start方法
三、通过Callable和Future创建线程
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
- 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
- 使用FutureTask对象作为Thread对象的target创建并启动新线程。
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
三种创建线程的对比:
实现接口的方式:
- 优点:还可以继承其他类,体现面向对象的优点。
- 缺点:变成复杂,访问线程必须使用THread.currentThread方法()。
继承Thread方式:
- 优点:变成简单,调用线程时无需使用THread.currentThread方法(),直接使用this即可获得当前线程。
- 缺点:因为创建时已经继承了Thread类,不能再去继承其他类,也是java不能多继承的缺点。
优先选择实现接口方式
同步代码块:就是锁,锁唯一。
synchronized(这里面为定义的锁){
共享变量
}
同步的代码块中只能一个线程进行工作,因为锁是唯一的,一个进程拿到锁之后,其他进程只能阻塞等待当前线程做完事之后才可以拿到锁
面试题:synchronized 与 lock 的异同
相同:都是为了解决线程安全问题
不同:synchronized 自动结束同步
lock需要手动结束同步
synchronized隐式锁,出了作用域之后自动释放
lock是显示锁,(手动开启和关闭,别忘记关闭锁) 推荐使用Lock锁
String
位于java.lang包下,java语言核心类
当你查看String源码之后你就可以看出String类是final修饰的,这就意味着String类不能被其他类继承。此类还实现了Serializable 、CharSequence、Comparable接口,(可序列化、相互之间可以进行比较)。利用char数组进行存储字符串数据。
字符串常量池:jvm在实例化字符串的时候,为了提高性能和节省内幕草存用了字符串常量池,当创建字符串的时候,jvm会先去检查字符串常量池,如果字符串已经存在于常量池中,直接返回字符串的实例引用,如果不存在,就会实例化字符串存放于常量池中。这就是String类的final不可变性,当你要修改一个String字符串时,你表面上感觉它被修改了,其实不然而是常量池中再次创建了一个字符串。这也是字符串常量池中永远不会存在两个相同的字符串的原因。
String对象创建方式:
- 直接双引号赋值(数据在方法区的字符串常量池中)
- new对象赋值 (数据在堆空间中)
面试题:例如String s= new String("abc") 方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中的new结构,另一个是char[]数组对应的常量池中的数据。
面试一般会对这两种创建的对象地址值进行对比:
- 在字符串常量池中的,如果新创建两个值相同的对象,二者指向的是一个地址。
- 一个在字符串常量池中,另外一个堆空间中,虽然值相同但指向的不是同一个地址。
- 两个都在堆空间中,值相同但是指向的不是同一个地址。
还有一种面试题是基于上面的题的延伸,加上拼接字符串再次进行对比是否指向同一地址:
举例:
String s1 = "javaEE";
String s2 = "hh";
String s3 = "javEEhh";
String s4 = "javaEE" + "hh";
String s5 = s1 + "hh";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
判断:
s3 == s4 true
s3 == s5 false
s3 == s6 false
s3 == s7 false
s5 == s6 false
s5 == s7 false
s6 == s7 false
解释说明:
- 两个常量池的字符串拼接之后还是在常量池
- 变量加常量池在对空间中
- 拼接后调用intern方法:结果在常量池
String和StringBuffer 和StringBuilder比较
- String: 不可变的字符序列,底层使用char数组存储
- StringBuffer:可变的字符序列,线程安全,效率低,底层使用char数组存储
- StringBuilder:可变的字符序列,线程不安全,效率高,底层使用char数组存储
StringBuilder > StringBuffer >String (效率比较)
三个类使用场景:不频繁拼接字符串的时候使用String,频率高的时候使用StringBuilder
SimpleDateFormat
Dateformat子类
常用模式
yyyy:年 MM:月 dd:日
HH:时 mm:分 ss:秒
String format(Date date) :格式化
Date parse(String time):解析
枚举类
使用enum关键字
类的对象是有限的,确定的。
当定义一组常量时,建议使用枚举类
比如说一个订单的状态,订单有多个状态,就用枚举定义。
枚举类中只有一个对象,就能当做单例模式的实现方式。
自定义注解
在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口
利用注解@interface自定义注解
内部定义成员,使用value
数组
数组的特点:一旦初始化之后,长度不可变,元素类型也不变
数组存储数据的特点:有序,不重复。
数组的缺点:
- 初始化之后,长度不变。
- 数组提供的方法有限,增删改操作不方便。
- 获取数组中实际元素的个数的需求,数组没有现成的属性或者方法可以用。
- 对于无序不可重读的需求,不能满足
集合
集合框架
Collection框架
1.集合与数组的区别:
- 长度区别:集合长度可变,数组长度不可变
- 内容区别:数组可以存储基本类型和引用类型,集合只能存储引用类型
- 元素区别:集合可以存储不同数据类型,数组只能存储一种数据类型
2.List和Set的区别:
- 有序性:List保证取出和排序的顺序一致
- 唯一性:List可重复,Set不可重复
- 获取元素:List可以直接通过索引操作元素
3.常见集合分类:
Collection 接口的接口 对象的集合(单列集合)
├——-List 接口:元素按进入先后有序保存,可重复
│—————-├ LinkedList 接口实现类, 链表, 插入删除, 没有同步, 线程不安全
│—————-├ ArrayList 接口实现类, 数组, 随机访问, 没有同步, 线程不安全
│—————-└ Vector 接口实现类 数组, 同步, 线程安全
│ ———————-└ Stack 是Vector类的实现类
└——-Set 接口: 仅接收一次,不可重复,并做内部排序
├—————-└HashSet 使用hash表(数组)存储元素
│————————└ LinkedHashSet 链表维护元素的插入次序
└ —————-TreeSet 底层实现为二叉树,元素排好序
Map 接口 键值对的集合 (双列集合)
├———Hashtable 接口实现类, 同步, 线程安全
├———HashMap 接口实现类 ,没有同步, 线程不安全-
│—————–├ LinkedHashMap 双向链表和哈希表实现
│—————–└ WeakHashMap
├ ——–TreeMap 红黑树对所有的key进行排序
└———IdentifyHashMap
泛型
"参数化类型",可以理解为将固有的类型参数化,(就像在定义一个方法时,编辑一个形参),在具体调用的时候注入具体类型。
泛型可以用在泛型类、泛型接口、泛型方法。
泛型只在编译期间有效,在编译过程中,正确验证泛型结果后,会将泛型的相关信息擦除,泛型信息不会进入到运行阶段。例子:定义两个不同类型的List,在获取他们的class,判断是否相同,你会发现他们是相同的。