注释:对类中的代码进行说明解释
注释的代码不会编译,单行注释有例外:\u000d 换行
多行注释不能嵌套多行注释,可以嵌套单行注释
文档注释会生成API文档
分号:在Java中,分号代表一行语句结束,{}后面不需要跟分号,匿名内部类除外
空白:空白不会对代码有影响,关键字和单词除外
标识符:给类,方法,变量起的名字
标识符由字母,下划线,美元符号,数字组成
开头不能为数字
对大小写敏感
长度没有限制
不能使用Java关键字和保留字
起名字要见名知意
域名小写,其他驼峰命名法,常量全部大写,并用下划线连接两个单词
在Java中只要加法的概念,减法就是加上负数,在计算机中存储的就是补码,运算也采用的是补码
原码:最高位为符号位,1代表负数,0代表正数
反码:正数的反码就是原码,负数的反码就是,符号位不变,其他按位取反
补码:正数的补码就是原码,负数的补码是反码加一
正数按位取反加一就是对应的负数,负数的补码按位取反加一就是正数
基本数据类型:
byte short int long float double char boolean
boolean
布尔类型占一个字节,八个比特位,它的值只有false或者是true
char
char占两个字节,16个比特位,用单引号表示,可以存放,十进制,二进制,八进制,十六进制,Unicode编码,中文汉字,字母等在一定程度上char可以转化位相应的数值
整型
byte < short < int < long
1字节 2字节 4 字节 8字节
8位 16位 32位 64位
没有声明的情况下,默认是int类型
浮点型
在Java中没有小数的概念,所以要表示小数,只能采取科学计数法
范围 | float | double |
---|---|---|
字节 | 4 | 8 |
比特位 | 32 | 64 |
精度 | 6~7位 | 15~16位 |
在没有声明的情况。默认是double类型
数值 = 尾数 × 底数 ^ 指数,(附加正负号)
浮点数类型的计算,大多情况下是正常的,但也有少数情况之下,浮点数类型运算会由精度丢失的问题
所以为了保证运行结构的精度,可以使用BigDecimal
变量:
变量的作用:用来保存,接收,传递,操作数据
变量类型必须和数据类型是一致的
如果类型不一致,那么就需要转换
基本数据类型转换:
小范围向大范围转换为自动转换,系统会自动转换
大范围向小范围转换为手动转换,需要强制转换,超过范围会精度丢失
变量的使用:
- 变量必须先声明,在赋值,然后在使用
变量的划分
-
类型划分
基本数类型变量
引用类型变量 ----引用 类类型 接口类型 数组类型
-
范围划分
局部变量
成员变量
局部变量:
定义在方法中的变量就是局部变量
局部变量没有默认值,所以在声明的时候要进行赋值
局部变量的作用范围就是在当前方法中,值得注意的是,方法中的参数也是局部变量
实例变量----成员变量
实例变量定义在类中,是非静态的
实例变量是有默认值,如果声明后不进行赋值,也是可以使用,不同的类型,默认值也不相同
实例变量的作用范围是当前类中所有的非静态的方法
操作符
对常量和变量进行操作的符号
注意:
-
byte a +=1;在这里不会报错,这是因为这个操作其实是相当于加了一个自己本身类型的1,或者可以理解为系统自动完成了强制类型转换
-
++a 先自增1在使用 a++先使用在加1
-
instanceof 判断对象是都属于指定类型
-
+不仅可以用来加法运算,还可以用来拼接字符串,如果遇到+“ 字符串” 那么从这里开始,后面的+号都是拼接的用法
-
在左移位操作运算中,如果移位的位数超过了该类型的最大位数,那么会对移位数进行取余操作,最终位移的位数为余数
-
&和&&的区别
&既可以作为二进制数字的位移运算符,也可以作为布尔表达式的逻辑运算符,&&只能作为逻辑运算符
&与&&相比,&&如果在一个总的表达式中,出现了一个false,那么这个false后面的表达式将不会再执行,而&不论出现多少个false,都会对所有的表达式进行判断
-
boolean表达式 ? 表达式1 : 表达式2
思考1,一个子类型的变量,一定能自动转为它的父类型么?
一定
思考2,一个父类型变量,一定能强制转成它的一个子类型么?
不一定,一个父类可能会有多个子类
流程控制
-
if
if(表达式){ 执行语句}else if(表达式){ 执行语句}else{ 执行语句}
只要有一个的表达式结果为true就会执行{}中的内容,并且不会执行后面的语句,只会执行其中一个{}的执行语句。
else if 可以有零个或者多个
else 可以不写
-
swith
只能对byte,short,int,char和String(JDK1.8)变量进行比较
值得注意的事swith如果在case中不写break语句,那么就会有穿透效果-------就是如果其中一个case判断为true,执行语句被执行,那么就会跳过这个case后面的其他case判断,直接执行其他case的执行语句
default不一定要放在最后面,可以放在任意位置
-
for
for(初始化表达式;条件表达式;循环后的操作表达式) { 循环体; } /* a:执行初始化语句 b:执行判断条件语句,看其返回值是true还是false 如果是true,就继续执行 如果是false,就结束循环 c:执行循环体语句; d:执行循环后的操作表达式 e:回到b继续 */ //其他写法 int i = 0; for(;i < 1;i++){} int j = 0; for(;i<1){ i++; } for(;;){}//死循环
-
while
while(表达式)}{ 执行语句 }
-
do-while
do{ 执行语句 }while(表达式);
do-whlie和while的区别在于,do-while至少会执行一次
-
在一个循环语句中可以嵌套另外一个循环
break
用于结束当前的循环或switch代码,不能单独放在if语句中
continue
不执行本次循环,直接进入下一次循环
label
结束指定的循环
可以用label和return跳出多层循环
数组
数组,一组数据的集合,数据中的每一个数据被称为元素
数组中只能存放同种类型的元素,数组的类型可以是任意类型(基本数据类型,引用数据类型)
数组类型----->任意类型+[]
数组中存放的元素必须是同种类型或者是可以兼容的
数组变量 -------->为已有的数组类型 声明一个变量 int[] a 或者int a[]
引用类型变量,称为引用
创建数组对象
- new关键字------int[] a = new int [4]; 中括号中代表的是数组长度
- 创建对象并初始化 ------int a = {1,2,3,4};
数组对象,在内存中就是一块连续的内存空间,在连续的内存空间中,可存放多个数据
数组的下标,
数组有相应的下标,可以通过相应的下标访问指定数组对象中指定的一块内存空间
数组下标范围----[0,数组长度-1]
可以通过数组下标对数组相应位置进行取值和赋值
数组的属性和方法
- 数组只有一个熟悉-------->length,表示数组的长度
- 数组对象中的方法只有从其父类Object中继承下来的方法
数组的长度
- 数组的长度,指明一个数组对象可以存放多少个同一类型的数据
- 数组长度在数组对象创建的时候就确定下来
- 数组长度确定就不能更改,数组的长度可以为0,但不能为负数
数组默认值
数组对象创建时候,如果没有开创建的时候初始化,那么系统会为数组每一个位置的元素进行初始化赋默认值.数组的默认值与数组类型有关
数组初始化
数组开辟连续的内存空间,并为每一个数组元素赋予值
-
动态初始化
int[] a = new int[5];
指定数组长度,由系统给出初始化值(默认值)
-
静态初始化
int[] a = {1,2,3,4}; int[] b = new int[]{1,2,3,4}
给出初始化值,长度由系统确定
-
错误的初始化
int[] b = new int[4]{1,2,3,4}
int[] a;
a = {1,2,3,4}
数组的遍历
三种基本循环和增强for循环
数组的拷贝
数组创建完成之后,长度就固定不能修改,要想修改数组长度可以通过拷贝
硬拷贝—创建新的数组
软拷贝—不创建新的数组
int[] a;
a = new int[5];
//硬拷贝
int[] b = new int[a.length*2];
System.arraycopy(a, 0, b, 0, a.length);
//软拷贝
a = Arrays.copyOf(a, a.length*2);
数组常见的异常
ArraysIndexOutBoundsEXception 数组索引越界异常
NullPointerException 空指针异常
数组的工具类
-
toString方法
可以把一个数组变为对应的String形式 -
copyOf方法
可以把一个数组进行复制
该方法中也是采用了arraycopy方法来实现的功能 -
copyOfRange方法
也是复制数组的方法,但是可以指定从哪一个下标位置开始复制
该方法中也是采用了arraycopy方法来实现的功能 -
sort方法
可以对数组进行排序 -
binarySearch方法
在数组中,查找指定的值,返回这个指定的值在数组中的下标,但是查找之前需要在数组中先进行排序,可以使用sort方法先进行排序 -
equals方法
两个数组的内存地址是否相同
-
fill
可以使用一个特定的值,把数组中的空间全都赋成这个值 -
asList
可以把一组数据,封装到一个List集合中,并且把list集合对象返回。
二维数组
在一维数组中存放同种类型的一个数组
内存空间中,二维数组会开辟连续的内存空间,每一块内存空间中存放着对应的一维数组的内存地址
任何一个一维数组再加上一个中括号就变成二维数组
int[] [] a = new in[1] [];
二维数组的声明创建必须明确二维数组长度,但二维数组中的一维数组长度可以不在创建二维数组的时候确定,并且一维数组的长度可不相等
二维数组取值和赋值
赋值,在对应的下标位置上进行赋值和取值操作
arr[i] [j] = 7; 赋值
int a = arr[i] [j]; 取值
可变参数(jdk1.5之后)
可变参数,其实就是一个数组,不同的是,可变参数的数组长度由传入的参数决定,而不是像数组,要传一个具体的数组对象才可以
public static void test(int[] a){
}
//可变参数
public static void test1(int...a){
}
public static void main(String args){
int[] arr = new int[5];
test(arr);
test1();
test1(1);
test1(1,2,3);
test1(arr);
}
一个方法可以同时存在,普通参数和可变参数,但可变参数必须要放在最后,一个方法最多只能由一个可变参数
oop
OOP,面向对象编程
将构成问题的各种事物,抽象成各个对象,这些对象具有解决问题的行为,并对象还可以具有解决其他类型问题的行为,而不是解决一个问题
POP,面向过程编程
分析出问题所需要的步骤,然后用函数将步骤一一实现,然后依次调用
FP,函数式编程
类似于面向过程的程序设计方式,具有很高的抽象程度
类
java中对数据类型的描述和定义都是抽象的,每一种数据类型,都是对同一类数据的抽象描述,描述了这种数据的基本特点
类就是具备某些共同特征的实体的集合,它是一种抽象的数据类型,它是对所具有相同特征实体的抽象
对象
对象泛指现实中一切事物,包括由具体形态的和没有具体形态的事物,每一个事物都有自己的熟悉和行为
类与对象的关系
- 类是对一类事物的描述,是抽象的
- 对象是一类事物的实例,是具体的
- 类是对象的模板,对象是类的实体
比如人类,是一个类,包含了全世界的人共有的特点,行为,但是具体的一个人就是对象,这个人具有人类的特点,并且还有专属于自己的特点
引用
引用类型变量称为引用
引用可以指向对象
当创建一个对象之后,就可以对这个对象进行操作,为了方便我们操作对象,就需要给这个对象起名字,而这就是引用,引用指向对象,可以通过引用对这个对象进行操作
如果没有给对象起名字,对象也可以使用,但只能使用一次(我一般称为临时对象)
引用,对象,类之间的关系
通过类可以创建出一个具体的对象,而引用可以指向这个对象,方便这个对象进行多次调用和操作这个对象.
JVM内存
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EC5HHvgm-1598109873975)(D:/briup-java/day08/corejava-day8/note/corejava-day06.assets/b941a038303f37cfceb7d3b4d3f3d34646d.jpg)]
-
类加载器子系统
-
加载阶段
启动类加载器----->拓展类加载器------>应用类加载器
双亲委托机制
-
连接阶段
验证(字节码校验)------>准备-------->解析
-
初始化阶段
类加载器只负责加载class文件,而执行由执行引擎决定,每一个class文件都有特定的标识------魔数
-
-
方法区
- 类型信息
- 类的限定类名
- 类的直接父类限定类名
- 类的直接实现接口的有序列表
- 类的修饰符
- 类型常量池
- 每一个class文件中,都维护这一个常量池(保存再文件中),里面存放在编译时期生成的各种字面值和符号引用,在类加载的时候,这个常量池会被复制到方法区中的运行时常量池
- 字面值:在编译阶段就可以确定下来的值
- 类变量(静态变量)
- 非final类变量
- 再JVM加载一个类的时候,必须再方法区中为每一个非final类变量分配空间
- final变量由于在编译期间就已经确定了值,所以存放在常量池中,每一个使用它的类保存着一个对其的引用;
- 类型信息
-
堆
堆分为两部分 新生代和老年代 (jdk1.8之后移除了永久代,取而代之的是元空间)
-
新生代
新生成的对象优先存放在新生代中,新生代的对象都是生命周期都很短,常规的一次垃圾回收机制可以回收70%~95%的空间,
HotSpot将新生代分成三块Eden区,幸存者0区(from区,s0区),幸存者1区(to区,s1区),比例为8:1:1,这是为了充分利用内存空间
如果新生成的对象是大对象会存储到老年代
当Eden区没有足够的空间时会发起一个Minor GC
复制算法:首先会把Eden区中存活的对象复制到S1区,s0
区中年龄到达阈值的会被移到老年代,没有达到阈值的会被复制到s1区,并且年龄+1,接着回收掉Eden区和s0区中的对象,然后此时s1区变成s0区,s0区变成s1区
当然有时候年龄很大,没有到达阈值也可以移到老年代
-
老年代
经历新生代多次GC之后仍存活的对象会进入老年代,老年代的对象生命周期较长,存活率比较高,在老年代中发生GC的频率相对来说比较低
-
垃圾回收
新生代GC(Minor GC):Minor GC指发生在新生代的GC,因为新生代的Java对象大多都是朝生夕死,所以Minor GC非常频繁,一般回收速度也比较快。当Eden空间不足以为对象分配内存时,会触发Minor GC
老年代GC(Full GC/Major GC):Full GC指发生在老年代的GC,出现了Full GC一般会伴随着至少一次的Minor GC(老年代的对象大部分是Minor GC过程中从新生代进入老年代),比如:分配担保失败。Full GC的速度一般会比Minor GC慢10倍以上。当老年代内存不足或者显式调用System.gc()方法时,会触发Full GC。
-
-
虚拟机栈
每一个方法被执行的时候都会创建一个栈帧,用来存储局部变量,操作栈,方法出口等信息
每个方法被调用到执行完的过程,就对应这一个栈帧在虚拟机栈中从入栈到出栈的过程
栈帧由四部分组成
- 局部变量表(用来存放方法参数和方法内部定义的局部变量)
- 操作数栈
- 动态连接
- 方法返回
执行的都是当前的栈帧,谁在栈顶就是当前栈帧
-
本地方法栈
与虚拟机栈基本类型,区别在于虚拟机栈为虚拟机执行的Java放啊服务,而本地方法栈则是为Native方法服务()
栈的空间远远小于堆
-
程序计数器(PC寄存器)
是最小的的一块内存区域
作用:当前线程所执行的字节码的行号指示器,在虚拟机的模型里,字节码解释器工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,异常处理,线程恢复等基础功能依赖计数器完成
存储这下一条指令的地址,也就是下一个即将要执行的代码
然后由执行引擎勾出一行代码,对代码进行判断,如果热点代码会将其进行缓存,以便下一次可以直接执行,如果不是热点代码通过JIT解释器,解释一行CPU就执行一行
当一个类加载的时候,会先把class文件交付给类加载子系统进行加载
然后会把加载初始化好的类信息加载到方法区,
接着调用main方法,创建栈帧,在虚拟机栈中进行压栈,
在堆区中加载实例变量,main方法中创建对象时,在堆新生代中开辟一块内存,并且将内存地址赋给相应的引用
对象中的熟悉和方法,与类中定义的保持一致
然后main方法中还存在其他方法,那么就依次创建栈帧 ,压栈,出栈,在堆空间中创建对象
然后PC寄存器存储这下一条指令的代码,执行引擎获取PC寄存器中的代码,将其解释执行
方法
方法定义在类中,属于里的成员
修饰符 返回类型 方法名(参数列表)抛出异常的类型{
//code
}
修饰符:
-
public、static、abstract、final等这些都属于修饰符,可以用来修饰方法、属性、类
-
一个方法上,可以同时拥有多个不同的修饰符,例如程序入口main方法
public static void main(String[] args){...}
这里使用俩个修饰符public和static来修饰main方法 -
如果方法上有多个修饰符,这些修饰符是没顺序之分的
例如,这俩个写法最终的效果是一样的
public static void main(String[] args){}
static public void main(String[] args){}
-
方法上也可以不写修饰符
返回类型:
-
方法执行完,如果有要返回的数据,那么在方法上就一定要声明返回数据的类型是什么,如果没有要返回的数据,那么在方法上就必须使用void进行声明
public int getNum(){...}
public void print(){...}
-
只有一种特殊的方法没有返回类型,也不写void,那就是构造方法,也就是构造器
public class Student{ //构造方法 public Student(){} }
-
声明有返回类型的方法,就要使用
return
关键字在方法中,把指定类型的数据返回
如果一个方法返回类型声明时void,那么在这个方法中还是可以使用return,这代表这一个方法的结束.
方法名:
- 只要满足java中标识符的命名规则即可
- 推荐使用有意义的方法名
参数列表:
- 根据具体情况,可以定义为无参、1个参数、多个参数、可变参数。
抛出的异常类型:
- 在方法的参数列表后,可以使用
throws
关键字,表明该方法在j将来调用执行的过程中,【可能】会抛出什么类型的异常,也可以在代码中写try/catch语句 - 可以声明多种类型的异常,因为在方法执行期间,可能会抛出的异常类型不止一种。
参数传递
-
形参----形式上的参数
public void test(int a){}
其中,参数a就是test方法形式上的参数,它的作用就是接收外部传过来的实际参数的值
-
实参------实际上的参数
值传递:
方法的参数是基本类型,调用方法并穿参数,这时候进行的是值传递
值传递,实参把自己存储的值(基本类型都是简单的数字)赋值给形参,之后形参如何操作,对形参一点影响没有。
引用传递:
方法的参数是引用类型,调用方法并传参,这时候进行的是引用传递。
这时候之所以称之为引用传递,是因为参数和形参都是引用类型变量,其中保存都是对象在堆区中的内存地址。通过调用内存地址来操作对象
由于引用传递,是实参将自己存储的对象地址,赋值给了形参,这时候俩个引用(实参和形参)指向了同一个对象,那么任何一个引用(实参或形参)操作对象,例如属性赋值,那么另一个引用(形参或实参)都可以看到这个对象中属性的变量,因为俩个引用指向同一个对象。
这个时候就相当于,俩个遥控器,同时控制同一台电视机。
this
在类中的非静态方法中,可以使用this关键字,来表示当前类将来的一个对象
this:
- 区别成员变量和局部变量
- 调用类中的其他方法
- 调用类中的其他构造器
在调用其他构造器的时候,this必须要放在第一行,并且不能自我调用,不能再普通方法中调用类的构造器
this代表的是所在类的当前对象的引用,即代表了对象自己
如果创建了两个对象,那么每一个对象都有自己的一个this,被哪个对象调用,那么this就代表那一个对象
封装
指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。
在类中定义属性的时候,一般需要发属性隐藏起来
如果外界需要访问这个属性,那么就提供公共方法对其进行访问
get /set的方法
private修饰符的特点
- private是四种权限修饰符中的一种,并且是权限最小的一种
- private可以修饰成员变量和成员方法
- private修饰的属性和方法,只有在当前类中才可以访问,在当前类外的其他地方,不能访问
- private修饰的属性,称为类中的私有属性,修饰的方法称为私有方法
封装的优点
- 提高代码的安全性,重要信息私有化
- 提高代码的复用性,常用的代码或者功能封装到方法中,可以在其他地方反复调用
- 封装代码的实现细节,便于修改内部代码,提高可维护性
- 简化外部的调用,便于调用者使用
- 减少耦合,对成员变量进行精确控制
在定义一个类的时候,就是在完成封装的过程
方法重载
在同一个类中,具有相同的方法名,但方法的参数列表不同
重载要求:
- 同一个类中
- 方法名相同
- 方法的参数列表不同
- 参数的类型不同
- 参数的个数不同
- 参数的顺序不同
- 方法的修饰符,返回类型,抛出的依次这些地方没有限制
当调用一个方法,传入的参数无法精确匹配到相应的类型的时候,参数会尝试自动转换,然后再去尝试匹配,优先转换为近的类型
特殊情况
public class Test {
public void sdaa1(int a,long b){
}
public void sdaa1(long a,int b){
}
public void sdaa1(long a,long b){
}
public static void main(String[] args) {
Test sdaa = new Test();
sdaa.sdaa1(1, 1);//在这种情况这下,无法匹配
}
}
这是因为int类型可以向long ,int; int long; long long的三种类型都可以自动转换,这与三种方法都匹配,那么编译就会报错
创建和初始化对象
new 类名(); 该代码完成了两个过程,对象的创建和初始化
- new关键字,给对象申请/分配内存空间,并将对象中的属性进行默认值的初始化,根据属性类型的不同,默认值也不同
- 如果在代码中,进行了显示赋值,那么就会把默认值覆盖掉
- 代码块也可以进行赋值
- 调用类中的构造器,在构造器中也可以对属性进行赋值,
在调用完构造器之后,一个对象就创建出来了,并且都已经有了值,如果在想修改,就需要使用对象访问属性,或者调用方法
public —直接通过对象访问赋值
private ----调用方法来设置属性值
成员变量和局部变量的区别
- 出现的位置不同
- 成员变量直接出现在类中
- 局部变量出现在方法或者代码块中
- 生命周期不同
- 成员变量随着对象的创建而初始化,随着对象的回收而消失,成员变量生命周期跟对象一致
- 局部变量 随着方法的栈帧入栈而初始化 随着方法的栈帧出栈而消失,生命周期与方法生命周期一致
- 初始化
- 成员变量会默认初始化
- 局部变量必须要手动赋值
构造器
类中的构造器也称为构造方法,构造函数,是在创建对象的时候必须要调用的
构造器的特点:
- 必须与类的名字保持一致
- 必须没有返回类型,也不能写void
构造器的作用:
- 使用new关键字来创建对象的时候,后面跟的必须是类中存在的构造器
- 构造器中的代码,在对象创建后会被调用,从而可以完成对象的初始化工作
构造器的重载
系统会默认给每一个类提供一个无参构造器,当我们对构造器进行重载的时候就会覆盖掉原有的无参构造器,一般的都会在写上一个无参构造器.构造器重载让构造器可以接收一些参数,然后使用这些参数进行对象的初始化工作。
构造器之间的调用:
使用this关键字,可以在构造器中,调用另外一个构造器
继承
类与类之间的关系-----继承,除此之外还有依赖,组合,聚合
继承描述的是事物之间的所属关系------is-a的关系
子类继承父类,那么子类就可以继承父类中定义的属性和方法
父类更同用,子类更具体
当多个类中存在相同的属性和行为的时候,将公共的部分抽取出来放到另外一个类中(父类),那么这些类(子类)就不需要自己定义这些属性和行为,可以通过继承父类,来访问父类中没私有的属性和方法
继承的好处:
- 提高代码的复用性
- 类与类直接按产生关系(is-a),这是多态特性的前提
继承的关键字
extends
一个类可以继承任意一个类,但是从实际意义上来说,只有这两个类存在is-a关系的时候,才考虑使用继承
子类继承父类,继承了父类中的属性和方法,并可以在子类中访问这些属性和方法
父类的私有属性和方法,子类无法访问
父类中的构造器,子类是不能继承的
在Java中,类与类之间的继承是单继承的
一个类只能直接继承一个父类
如果一个类没有继承任何一个类,那么这个类就会默认继承Object类
Object类中没有属性
每一个类都直接或者间接继承了Object
子类继承父类,创建子类对象的时候,会先默认调用父类的构造器
但不会创建父类对象,只是调用父类的构造器
子类继承父类,会继承父类的属性和方法,那么需要先调用父类的构造器对父类中的属性进行初始化,初始化完成后再给子类使用
构造器的作用之一就是进行初始化
super关键字
super的作用:
-
访问父类中的属性
-
调用父类中的方法
-
调用父类中的构造器
- 子类构造器会隐式调用父类的无参构造器
- 子类构造器中也可以显示调用父类中的无参构造器
- 子类构造器显示调用父类的有参构造器
- 如果父类中没有无参构造器,必须要显示调用父类中有参构造器
- 在调用父类构造器时,super语句必须放在代码的第一行,这就意味不能再一个构造器中同时调用多个构造器,super和this在调用构造器时,不能同时出现
为什么子类构造器中,会先调用父类的构造器
如果你生成子类对象时候,没有调用父类构造器,那么,在我们使用父类的一些成员变量的时候,就会报变量未初始化的错误,构造器的作用之一就是为变量进行初始化赋值
创建一个对象,会调用其构造方法来初始化成员函数和成员变量,子类拥有父类的成员变量和成员方法,如果不调用,则从父类继承下来的成员变量和成员方法就得不到正确的初始化
方法的重写
如果子类中出现了和父类相同的方法,这就是重写,重写只会发生的子父类之间,重载发生再同一个类中
重写的要求
-
方法名必须相同
-
参数列表必须相同
-
访问控制修饰符要么相等,要么扩大,不能被缩小
public>protected>default>private
-
方法的抛出异常类型的范围要么相等,要么缩小,不能扩大
-
返回类型
- 如果是基本数据类型,那么重写后的方法返回类型必须与父类相同
- 如果返回类型是引用类型,那么必须是相同的类型或者是其子类型
重写和重载的区别
- 重载
- 只发生再本类中
- 方法名相同,只跟参数列表有关,与其他无关
- 重写
- 只发生子父类之间
- 方法名必须与父类方法相同,参数列表与父类方法名相同
- 重写的方法修饰符不能比父类的小
- 重写的方法抛出的异常不能比父类的大
- 返回类型如果是基本数据类型要保持一致
- 返回类中如果是引用类型,要么保持一致,要么是该类型的子类型
父类的静态方法不能被子类重写
父类的私有方法不能被子类重写
如果子类没有重写父类的方法,那么就会调用父类继承下来的放啊,如果子类重写了该方法,那么就会调用子类重写后的方法
多态
相同类型的不同对象,调用同一个方法,最终执行的结果是不同的
多态的体现
- 父类引用指向子类对象
- 父类引用也可以接收子类对象
多态的前提
- 子类继承父类
- 子类重写父类中的方法
- 父类的引用指向子类对象
接口的实现,这也是一种特殊形式的继承,多态也可以体现类和接口的关系中
多态只针对方法
一个父类型的引用,可以指向它的任何一个子类对象
多态访问成员变量
编译看左边(父类),运行看左边(父类),这是因为多态主要针对的是方法
多态访问成员方法
编译看左边(父类),运行看右边(子类),这必须是子类和父类有共同的放啊
多态访问静态方法
编译看左边(父类),运行看左边(父类),静态方法和类相关,算不上重写
instanceof
再使用多态的情况下,instanceof关键字显得特别重要,可以告诉我们父类引用指向了哪一个子类对象
类型的转换
类型的转换分为两种
-
向上转型
多态本身就是将子类对象赋值给父类引用,这就是向上转型(自动转换)
-
向下转型
父类类型向子类类型转换,是向下转型,需要手动转型(强制类型转换)
当我们想要调用子类中特有的方法的时候,就需要向下转型,这是因为父类引用是无法调用到子类中特有的方法的
向下转型可能会报异常
这是因为如果一个父类引用指向的子类对象并不是要转换的类型,那么就不能进行向下转型,所以这就需要instanceof进行判断,是不是属于该子类型
static
静态属性-----static修饰的属性就是静态属性
静态属性是属于类的,可以通过使用类名直接访问到,当然,也可以使用对象访问
非静态属性,是属于对象的,一定要使用对象来访问,没有其他方式
静态属性,是属于类的,并且是这个类所有对象共享的
静态属性的存储位置-----存储在方法区中
当创建对象的时候,对象中只会保存类中定义的非静态属性的信息,而静态属性是不会进到对象中的
在Java虚拟机使用一个类之前,它必须在方法区中为每一个非final类变量分配内存空间
静态属性初始化:
无论是静态属性还是非静态属性,都必须要进行初始化后才可以使用,要么是系统给属性初始化默认赋值,要么就是手动赋值
属性的初始化时间
- 静态属性:类加载到内存中的时候,系统就会给类中地静态属性做初始化赋默认值,所以,即使还没创建对象,只要这个类加载到了内存,就可以直接使用类名来访问静态属性
- 非静态属性:在创建对象的时候,系统会自动给对象中的非静态属性做初始化赋值,所以非静态属性,必须要创建对象后才可以访问,值得注意的是,只要实例变量才会保存在对象中,并作初始化操作
静态方法-------static修饰的方法就是静态方法
静态方法的调用:可以使用类名来调用,也可以使用对象来调用
静态方法中不能调用类中的非静态方法或非静态属性,但可以在静态方法里面定义非静态的属性
正因为如此,静态方法中,不能访问this,所以就不能访问类中的非静态属性和非静态方法
在类加载的时候,JVM会优先给类中的静态属性做初始化,给类中的静态方法分配内存空间。
而类中非静态属性的初始化,非静态方法的分配空间,是要等到创建对象之后才会进行的。
可以在非静态方法中,访问静态的属性和静态的方法,因此此时,静态属性和方法已经完成了类加载
在类中的构造器,必须要创建对象才可以调用,构造器可以给非静态属性做初始化操作,但不能给静态属性做初始化操作
静态代码块
静态代码块,也叫做静态初始化代码块,它的作用就是给类中的静态属性做初始化
静态代码块的执行时刻:
由于静态代码块没有名字,我们并不能主动调用,它会在 类加载的时候自动执行,所以静态代码块,可以更早的为类中的静态属性,进行初始化赋值操作
并且,静态代码块只会自动被执行一次,这是JVM在依次运行中,对一个类只会加载一次
静态变量和静态代码块谁在前,谁先执行
匿名代码块
匿名代码块的作用是给非静态属性做初始化操作的
匿名代码块的执行时刻:
由于匿名代码块没有名字,所以我们不能主动调用,它会在创建对象的时候,构造器执行之前,自动执行
并且每次创建对象,匿名代码块都会被自动执行
创建和初始化对象的过程(包含继承)
- 首先会加载父类的静态成员
- 加载子类的静态成员
- 父类属性初始化赋值
- 父类匿名代码块进行赋值
- 调用父类构造器
- 子类属性初始化赋值
- 子类匿名代码块进行赋值
- 调用子类构造器
- =号赋值操作,将对象地址赋给引用
静态导入(jdk1.5之后)
静态导入只能导入静态的方法和属性,静态导入之后,可以直接调用
这里值得注意的是,toString方法,如果我们静态导入toString方法,那么编译就会报错,这是因为所有的类都继承Object类,在Object中就存在了一个toString方法,这个时候,系统就不知道,我们的toString方法是哪一个,所以必须要指明是调用哪一个toString方法.
final
final修饰符,可以用来修饰类,变量,方法
final修饰的类不能被继承
final修饰的方法可以被子类继承,但是不能被子类改写
final修饰的变量就会变成常量,并且它只能赋一次值,第二次赋值就会报错
-
final修饰非静态成员变量
此时JVM不再为其进行默认赋值,我们需要手动在以下地方对其进行赋值
- 声名的同时赋值
- 匿名代码块赋值
- 构造器中赋值,这里值得注意的是,类中所有的构造器都要对这个变量进行赋值
-
final修饰静态成员变量
此时JVM不再为其进行默认赋值,我们需要手动在以下地方对其进行赋值
- 生命的同时赋值
- 静态代码块中赋值
注意,只要是被final修饰的变量都只有一次赋值的机会
final修饰引用类型变量
final修饰的引用类型变量,这就代表指向的对象不能再改变,只能指向一个对象,但是可以修改对象的属性和方法
abstract
abstract修饰符,可以修饰类和方法
修饰方法-----abstract修饰的方法是抽象方法
抽象方法的特点
- 只有方法的声明
- 没有方法的实现 ,没有方法体
修饰类—abstract修饰的类就是抽象类,
抽象类和非抽象类的区别
- 抽象类使用了abstract修饰符,而非抽象类没有使用
- 抽象类中可以编写抽象方法,而非抽象类中不能编写抽象方法
- 抽象类不能进行实例化创建对象,而非抽象类可以实例化创建对象
抽象类和抽象方法的关系
- 抽象类中可以没有抽象方法
- 有抽象方法的类一定是抽象类
意义
父类中的一个方法,如果被它的子类们重写,并且每个子类各自的实现又不相同,那么父类中的的这个方法,只有声明还有意义,而它的方法主体就变得没有任何存在的意义了。
这个时候,就可以把这个方法定义为抽象方法,只有方法的声明,没有方法的主体(也就是方法的实现)。
interface
三大引用类型----数组,类,接口
接口的内部主要封装了方法和静态常量
接口需要用interface关键字去定义
接口最红也会被编译成.class文件,但一定要明确接口并不是类,而是另外一种引用数据类型
定义类使用class 定义接口使用关键字interface
接口中的属性都是公共的静态常量
接口中的方法都是抽象方法(jdk8之后允许写静态方法和默认方法,jdk9中还允许编写私有方法)
接口中的抽象方法,不需要使用abstract修饰符修饰,因为接口中的默认方法就是抽象方法
再接口中 方法默认是public abstract 修饰,属性默认是public static final修饰
接口的实现
类和类之间的关系是继承,类和接口之间的关系是实现,一个类可以实现多个接口,只能继承一个父类,可以拥有多个子类
接口不可以实例化对象,只能通过其他类来实现
一个类实现了一个接口,那么该类可以称为这个接口的实现类.使用关键字implements,
一个类实现了接口,那么就要实现接口中的所有方法,否则这个类自己必须声明为抽象类
接口的继承
java中,类和类之间是单继承,接口和接口之间是多继承 一个接口可以继承多个接口
接口的多态
多态的前提的继承,实现接口其实也是继承的形式,实现类和接口也可以使用多态
接口 变量 = new 实现类();
调用的是实现类中重写的方法.
如果实现多个接口,那么,任意一个接口都可以是实现多态,并且不论使用哪个接口,最终指向的都是实现类对象,因此可以实现在类型转换
接口1 变量1 = new 实现类();
接口2 变量2 = (接口2) 变量1;
访问控制
对象中的属性和方法,是可以根据指定修饰符来来进行访问控制的,它可限定这个类在哪可以被访问,在什么地方不可被访问
四种修饰符:
public > protected >default >private
-
public 公共的,在所有地方可以访问
-
protected 受保护的 ,在当前类中,子类中,同一个包中其他类中可以访问(如果不同包的子类中,创建对象没有使用多态,那么就可以访问)
-
default 默认的,当前类中,同一个包中的子类可以访问
-
private 私有的,当前类中可以访问
包装类
Java中的八种基本数据类型,针对八种基本类型,Java提供了对应的类类型,目的就是为分别把这种八种基本数据的类型,包装成对应的类类型,这时候就变了对象了,就可以调用方法或者访问属性
自动装箱和拆箱
再jdk 1.5之后,可以支持基本类型和包装类型之间的自动装箱,自动拆箱
但基本数据类型只能对应自己的包装类,
Long a = new Integer(1);
类似于这种是不允许的
Object中常用的方法
toString方法
该方法可以返回一个对象的默认的字符串形式
子类可以对该方法进行重写
再print和println中默认调用toString方法[p]
getClass方法
该方法是非常重要的一种方法,它可以返回一个引用再运行时所指向的对象,距离类型是什么
该方法时native修饰的本地方法,不是Java语言实现的,不能重写
getClass(); class 限定类名
getClass.getname();//得到限定类名
getClass.getSimpleName(); 得到类名
equals方法
比较两对象是否相等
重写equals的规定
- 自反性:对任意引用obj,obj.equals(obj)的返回值一定为true.
- 对称性:对于任何引用o1、o2,当且仅当o1.equals(o2)返回值为true时,o2.equals(o1)的返回值一定为true;
- 传递性:如果o1.equals(o2)为true, o2.equals(o3)为true,则o1.equals(o3)也一定为true
- 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变
- 非空性:任何非空的引用obj,obj.equals(null)的返回值一定为false
equals和==的区别
- 两者都可以用来比较对象
- ==还可以用来比较基本数据类型,equals不能用来比较基本数据类型,因为equals 是Object的方法,基本数据类型不存在继承Object
- ==如果比较对象,比较的是地址,equals比较的是对象的内容,如果equals没有重写,那么比较的还是对象的地址
hashCode方法
该方法返回一个int值,该int值是JVM根据对象在内存的中的特征(地址值),通过hash算法计算出的一个结果。
Hash,就是把任意长度的数据输入,通过散列算法,变换成固定长度的输出,该输出就是哈希值
Object中的hashCode方法默认返回的是对象的内存地址,但是实际上可能并不是。
对于俩个对象的hashCode值:
- 相等的俩个对象,hashCode值一定相等
- hashCode值相同,俩个对象有可能相等,也可能不同等
- hashCode值不同,俩个对象一定不同
9 关于String对象
字符串String,是程序中使用最多的一种数据,JVM在内存中专门设置了一块区域(字符串常量池),来提高字字符串对象的使用效率。
9.1 概述
创建字符串对象,和其他普通对象一样,会占用计算机的资源(时间和空间),作为最常用的数据类型,大量频繁的创建字符串对象,会极大程度地影响程序的性能。
JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
- 为字符串开辟一个字符串常量池,类似于缓存区
Object中常用的方法
toString方法
该方法可以返回一个对象的默认的字符串形式
子类可以对该方法进行重写
再print和println中默认调用toString方法[p]
getClass方法
该方法是非常重要的一种方法,它可以返回一个引用再运行时所指向的对象,距离类型是什么
该方法时native修饰的本地方法,不是Java语言实现的,不能重写
getClass(); class 限定类名
getClass.getname();//得到限定类名
getClass.getSimpleName(); 得到类名
equals方法
比较两对象是否相等
重写equals的规定
- 自反性:对任意引用obj,obj.equals(obj)的返回值一定为true.
- 对称性:对于任何引用o1、o2,当且仅当o1.equals(o2)返回值为true时,o2.equals(o1)的返回值一定为true;
- 传递性:如果o1.equals(o2)为true, o2.equals(o3)为true,则o1.equals(o3)也一定为true
- 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变
- 非空性:任何非空的引用obj,obj.equals(null)的返回值一定为false
equals和==的区别
- 两者都可以用来比较对象
- ==还可以用来比较基本数据类型,equals不能用来比较基本数据类型,因为equals 是Object的方法,基本数据类型不存在继承Object
- ==如果比较对象,比较的是地址,equals比较的是对象的内容,如果equals没有重写,那么比较的还是对象的地址
hashCode方法
该方法返回一个int值,该int值是JVM根据对象在内存的中的特征(地址值),通过hash算法计算出的一个结果。
Hash,就是把任意长度的数据输入,通过散列算法,变换成固定长度的输出,该输出就是哈希值
Object中的hashCode方法默认返回的是对象的内存地址,但是实际上可能并不是。
对于俩个对象的hashCode值:
- 相等的俩个对象,hashCode值一定相等
- hashCode值相同,俩个对象有可能相等,也可能不同等
- hashCode值不同,俩个对象一定不同
9 关于String对象
字符串String,是程序中使用最多的一种数据,JVM在内存中专门设置了一块区域(字符串常量池),来提高字字符串对象的使用效率。
9.1 概述
创建字符串对象,和其他普通对象一样,会占用计算机的资源(时间和空间),作为最常用的数据类型,大量频繁的创建字符串对象,会极大程度地影响程序的性能。
JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
- 为字符串开辟一个字符串常量池,类似于缓存区
- 创建字符串常量时,首先会检查字符串常量池中是否存在该字符串,如果存在该字符串,则返回该实例的引用,如果不存在,则实例化创建该字符串,并放入池中