创建数组 初始化数组
数组下标
0~数组长度-1 都是整型
二维数组
数组的长度 length
遍历数组
循环遍历或者是增强for循环
数组工具类
Arrays
System.arrayCopy(原数组,起始位置,目的数组,目标起始位置,数组长度)
数组扩容
数组的长度是固定,一旦数组被创建长度就无法改变,因此在存储元素的时候超过了数组的长度,获取元素的下标长度超过数组的下标最大值,就会发生异常
ArraysIndexOutBoundsException
所以要对数组进行扩容
新数组最好是原来的数组 *200
oom异常 超出内存异常
NullpointerException
引用数据类型的变量 称为引用
jvm内存分配
运行时数据区
cpu只有把指令数据存储到寄存器才可以区执行
pc寄存器
class标识---->魔数
java虚拟机可以运行其他编程语言编写的class文件 只要满足虚拟机规范
加载 链接 初始化
至于这个类是否会被执行,由执行引擎去决定
运行时常量池
先加载到方法去 才能创建对象
Eden
s0
s1
创建对象一般都放在新生代
8:1:1 可以通过参数修改
重载发生在本类中,
方法名相同 只跟参数列表有关,跟其他无关
重写发生在子父类之间
方法名,参数列表与父类相同
重写的方法修饰符只能被扩大,抛出的异常只能被缩小
方法返回值:
基本类型 保持一致
引用类型 保持一致,要么就是父类返回类型的子类型
父类中静态方法,私有方法不能被子类重写
为什么在创建子类对象的时候会默认先调用父类构造器
方法有重写 调用都是重写后的方法
多态的好处
方法
编译看左边 运行看右边
如果想要多态 ,子类和父类要有同一个方法
属性 static 类
编译看左边 运行也看左边
在向下转型的过程中 可能会发生类型转换异常
所以向下转型之前最好是判断一下
创建子对象
1、加载父类静态代码
2、加载子类的静态代码
3、初始化父类对象的属性
4、执行父类对象的匿名代码块
5、调用父类对象的构造器
6、初始化子类对象属性
7、执行子类对象的匿名代码块
8、调用子类对象的构造器
同种类型的不同对象执行相同的行为表现出不同的形式
多态一定存在继承关系
只有对象才具有多态性
多态本质关注的是属于对象的方法
static
static 修饰的都是类
static修饰变量
可以通过类直接调用
也可以创建对象调用
静态变量属于类 比对象创建先加载
静态变量被这个对象共享
static修饰方法
静态方法不能直接使用非静态的属性和方法,如果想要使用就必须创建对象然后调用属性或者方法
static代码块
静态代码块,在类加载的时候自动执行
执行一次,无论创建多少个对象静态代码块只执行一次
匿名代码块、
{}存在类中
每创建一个对象就执行一次构造代码块
这个代码块一定是在构造方法执行之前执行
final 修饰非静态成员变量
jvm不会对final修饰地变量进行默认初始化
但是常量必须赋值且只能赋值一个
1、声明进行显示赋值
2、构造方法里面赋值一次,所有构造器都要
3、匿名代码块
(方法不行) 方法不一定执行
final 修饰静态成员变量
1、声明
2、静态代码块
final修饰引用变量
引用变量只能赋值一次,但是可以对引用指向的内存进行多次修改
abstract
可以修饰类和方法
abstract 修饰方法 代表方法是一个抽象方法
拥有抽象方法的类一定是抽象类
抽象类不一定有抽象方法
抽象方法没有方法体
修饰符 抽象关键字 返回类型 方法名(参数列表);
abstract
修饰类
不能创建对象
但是拥有自己的构造器
抽象类中可以拥有普通变量,常量,也可以拥有静态常量
抽象类里面可以拥有普通方法也可以拥有抽象方法,也可以拥有静态方法
接口里面的成员变量都是静态变量
方法都是抽象方法()JDK1.7
接口使用静态方法以及默认方法 1.8
接口中还存在私有的方法 1.9
接口是一个特殊的抽象类
类 数组 接口
interface A {
}
接口里面的方法都是公共的抽象方法
接口里面可以写默认方法和静态方法
接口里面可以写私有的方法
接口里面的成员变量都是public类型的静态常量
接口里面的静态方法,可以用接口名.静态方法()
默认方法只能用它的实现了实现
接口没有自己的构造器
接口可以继承多个接口
一个类可以实现多个接口
如果一个类实现了多个接口
那么多个接口的抽象方法必须实现
要想不实现或者实现一部分,将实现类定义为抽象类
如果一个类实现了多个接口
calss 文件 类加载器 方法区 虚拟机栈 堆(新生代eden区) 寄存器 执行引擎
类加载器
加载阶段 启动类加载器 拓展类加载器 应用类加载器
链接阶段 验证 准备 解析
初始化 初始化
类加载器只加载class文件,至于这个类是否被执行殷勤决定,每个class文件都要特定的表示,称为魔数
加载类的信息存放一块方法区的空间
方法区
类型信息
限定类名
类的直接父类的完整接口 Object
类的直接实现接口的有序列表·(这是因为一个类的直接是实现的接口可能不止一个,因此放到一个有序表中)
类的修饰符
继承 接口 名字
类型常量池(运行常量池)
每个Cla宿舍文件中,都维护这一个常量池(这个保存在类文件里面,不要与方法区的运行时常量池搞混),里面存放着编译时期生成的各种字面值和符号引用;这个常量池的内容,在类加载的时候,被复制到方法区的运行时常量池
字面值 基本数据类型,以及他们的包装类的值,以及final修饰的变量,简单说就是在编译期间,就可以去欸的那个下来的值
类变量
非final类变量
在Java虚拟机使用一个类之前,他们必须在方法区中为每个非final类变量分配空间,非final类变量存储在定义它的类中
字段信息
指向类加载器的引起
指向class实例的引用
方法表
堆
可以分为年轻代和老年代 jdk1.8之后永久代也将被移除
新生代 朝生夕死 eden s1 s0 8:1:1 充分利用内存空间,减少浪费
新生成的对象在Eden区分配 大对象直接进入老年代
当Eden区空间不足的时候,会发起一次Minor GC
Eden -s1区 s0根据年龄决定 没有达到阀值 赋值到s1区并年龄值+1 超过阀值的进入老年代 复制算法
老年代 在新生代中经历了多次GC后仍然存活下来的对象会进入老年代,
老年代的对象生命周期较长,存活率较高 在老年代中进行GC的频率相对而言较低,而且回收的速度也比较慢
垃圾回收:
新生代minor GC 频繁 回收速度比较快 ,当Eden不足以为对象分配空间是,会触发Minor GC
老年代GC Full GC 出现FullGC 会伴随这一次Minor GC System.gc()会触发Full
虚拟机栈
描述的是Java方法执行地内存模型
每个方法被执行地时候都会创建一个栈帧,用于存储局部变量表 包括参数,操作栈,方法出口等信息
栈帧:局部变量表 操作数栈 方法返回值 动态链接
本地方法栈
与虚拟机栈基本雷士,区别在于虚拟机栈·1是为java方法服务,本地方法栈是为Native方法服务
程序计数器 PC寄存器
是最小的一块内存区域,它的作用是当前线程所执行的字节码的行号治时期,在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,异常处理,线程恢复等基础功能都需要依赖计数器完成
存储下一条指令地地址
方法的重载
类中有多个方法,具有相同的方法名,但方法的参数各不相同,这种情况被称为重载
方法的重载必须是在同一个类中,并且要求如下
方法的名字必须相同
方法的参数列表必须不同 参数个数 参数类型 参数类型的顺序
方法的修饰符,返回类型,抛出的异常这些地方没有限制,可以相同也可以不同,但一般都是相同
当参数无法完全精准匹配到方法的时候,参数会尝试自动转换,然后再去尝试匹配的方法,自动转换都是会往离得近的去转换
public void test(int a,long b) {
System.out.println("方法1");
}
overloading.test(1,1);
//当出现这种情况的时候,会自动转换相对应的long类型
public void test(int a,long b) {
System.out.println("方法1");
}
public void test(long a,int b) {
System.out.println("方法2");
}
public void test(long a,long b) {
System.out.println("方法3");
}
overloading.test(1,1);
//当这种情况的时候,编译会报错,这是因为系统不知道该如何转换,因为类中定义的三个方法都匹配,但是不懂具体转换为long int 还是int long,然后也无法都转为long类型
封装
在类中定义属性的时候,一般需要把属性隐藏起来
如果外界需要访问这个属性,那么就提供公共方法对其访问
private修饰符
private修饰符时四种修饰符中权限最小的一种
private可以修饰成员变量和成员方法
private修饰的属性和方法,只有在当前类中才能访问
private修饰的属性称为私有属性,修饰的方法称为私有方法
封装的优点
1、提高代码的安全性,重要信息私有化,不对外暴露
2、提高代码的复用性,常用代码或者功能封装到方法中,可以在其他地方反复调用
3、封装代码的实现细节,便于修改内部代码,提高可维护性
简化外部的调用,便于调用者使用
创建和初始化对象
new关键字 给对象申请/分配堆内存空间,并把对象中的属性进行默认值的初始化
如果在代码中,还给属性进行了显示赋值,那么就会用显示赋值的值,把之前的默认值给覆盖掉
或者时匿名代码块,也可以给属性赋值
调用类中的构造器,在构造器中也可以堆属性进行赋值
执行完构造器,那么这个对象已经创建出来了(注意,创建子类对象不会去创建父类对象,但是会调用父类中的构造器,就比如,一个子类继承一个抽象类,首先抽象类是不能创建对象的,但是抽象类中有构造器,这里值得一提还有接口,接口类似于抽象类,但接口中是没有构造器的),此时如果还行在设置属性值,可以使用对象来访问属性,或者调用方法
局部变量和成员变量有什么区别
出现的位置不同
成员变量出现在类中
局部变量出现在方法中或者代码块中
成员变量随着对象的创建而初始化,随着对象的回收而消失,成员变量生命周期与对象的相同
成员变量随着方法的栈帧压栈而初始化,随着方法的弹栈而消失·1,生命周期与方法相同
成员变量会默认初始化,而局部变量需要手动赋值
构造器
必须和类的名字保持一致,必须没有返回类型
构造器的作用
使用new关键字来创建对象的时候,后面必须是类中存在的构造器
构造器中的代码,在对象创建后会被调用,从而可以完成对象的初始化工作
同时构造器也可以重载
系统会默认提供一个无参构造器
在一个构造器中可以调用另外一个构造器,用this(实参);这里要注意的是,this();必须是在代码的第一行,并且只能有一个this();其外还有super()
这个是用来调用父类构造器,也必须在第一行,所以super();和this();不能同时出现
当我们自己编写了一个构造器之后,就会覆盖掉系统默认提供的构造器
继承
类与类之间的关系有很多中,继承就是其中的一种关系,除此浙外还有依赖,组合,聚合等
继承描述的事物之间的所属关系,这种关系是is-a的关系
子类继承父类,就可继承服父类中的定义的非私有的属性和方法
父类更通用,子类更具体·1
把多个类中存在相同的属性和方法放到一个类中,在让那些类来继承这个一个类
继承的好处,提高代码的复用性
继承是多态的前提
继承的关键extends
从实际意义上来说 一个类与另外一个类存在is-a关系的时候才考虑使用继承,但只要句法正确,一个类就可以继承另外一个类,之间不需要有关系
一个子类继承父类,就可以继承父类的非私有化的属性和方法,并可以在子类中访问这些属性和方法,这里值得注意的是,父类中的构造器,子类是无法继承的,但是创建子类对象会先调用父类构造器,如果父类中默认给的无参构造器,那么就要调用其他的构造器
Java中,类的继承是单继承,也就是说一个类只有一个父类
如果一个类没有继承任何类,那么它就会默认继承Object类
所有的类都直接或者间接的继承了Object类
子类继承父类,会继承父类的属性和方法,那么就需要献给父类的构造器堆父类中的属性进行初始化,初始化完成后在给子类使用
构造器的作用之一就是进行初始化
super关键字
调用父类中的属性,方法,构造器
在子类的构造器中,会默认有一个隐式调用super();自动调用父类中的无参构造器,当然也可以通过super调用父类的有参构造器
super和this当用来调用构造器时候,不能同时出现
为什么要调用父类的构造器
如果生成子类对象的时候没有调用父类的构造器,那么当你调用父类的一些成员的时候,就会报变量未初始化的错误
方法的重写
出现在子类和父类当中,当子类中出现了父类中同样的方法就是重写
重写的要求
方法名必须相同、
参数列表必须相同 参数的个数 参数的类型 参数的类型顺序
访问控制修饰符只能被扩大,不能被缩小,可以相同
方法抛出异常类型的范围可以被缩小,但不能被扩大,可以i相同
返回类型可以相同也可以不同
- 如果父类型的返回类型是一个引用类型,子类重写之后的方法返回类型可以和父类型的保持一致,也可以时父类方法返回类型的子类型
- 如果返回的类型是基本数据类型,那么子类重写后的返回类型必须和父类的保持一致
方法的重写和重载
overload(重载)
只发生在本类中
方法名相同,只跟参数列表有关
overwirte
只发生在子父类之间
方法名和父类方法相同,参数列表和父类方法名相同
重写的方法修饰要么与父类方相同或者比父类方法大
抛出的异常要么和父类相同,要么比父类的要小
父类的静态方法,私有方法不能被子类重写,
当父类中的方法不能满足需求的时候,就需要被重写
多态
相同类型的不同对象,调用同一个方法,最终执行结果是不相同的
动物类型 变量名1 = new 🐱();
动物类型 变量名2 = new 🐕();
多态的体现
父类引用指向子类对象
父类引用也可以接收子类对象
多态的前提
子类继承父类
子类重写父类中的方法
父类的引用指向子类对象
这里注意的是接口的实现也是一种特殊的继承,也可以用多态来体现
多态只针对接口
一个父类型的引用可以指向它的任何一个子类对象
多态
左边 父类, 右边 子类
访问成员变量
编译看左边,运行看左边
访问成员方法
编译看左边 运行看右边
静态
编译看左边 运行看右边
父类引用无法调用子类中独有的方法
这种时候就要涉及到引用类型转换
父类类型 变量名1 = new 子类类型(); 这是向下转换 子类转为父类 自动转换
子类类型 变量名2 = (子类类型)变量名1;这是向上转型 类型强制转换
这还要涉及instanceof 当我使用多态的时候,引用可以指向子类类型是不唯一的,所以这时候就要进行判断
public class Person {
}
class Student extends Person{
}
class Teacher extends Person{
}
public void test(Person p){
if(p instanceof Student){
//说明p指向的是Student类型对象
}
else if(p instanceof Teacher){
//说明p指向的是Teacher类型对象
}
}
引用强制类型转换可能会出现的问题
比如有三个类 其中Person是父类 Student 和Worker是子类
创建一个对象
Person p = new Student();
这是后向下转型为Woeker类型
Woeker w = (Woeker) p;
假设两个子类中都要test方法
w.test();这时候编译还是可以通过,但是运行时候会报错 ClassCastException
虚拟机栈 堆 本地方法栈 程序计数器 PC寄存器 方法区
jvm分配内存空间
- 指针碰撞
- 空闲列表
数组
一组数据的集合就是数组,数组中的每一个数据称为元素
在java中数组也是对象,数组中的元素可以是任意类型,但是同一数组中只能存放类型相同的数据
数组----可以看作容器
数组类型 ---- 任意类型+[]
数组中要求存放的数组类型相同或者是兼容的,兼容------自动转换
声明数组
int[] a or int a[]; 声明一个数组变量
数组对象
使用new关键字,来创建数组对象
int[] arr = new int[4] 后面中括号代表的是数组的长度
数组对象,在内存中,就是一块连续的内存空间,在这个连续的空间中,可以存放多个类型相同的数据
数组对象中只有一个属性就是length,表示数组长度,数组对象中的方法只有弗雷西Object中继承过来的方法,除此之外,数组对象中就没有其他属性和方法了
数组长度
length 表示数组可以存放元素的数量,同一类型
数组的长度必须创建时候就明确指定,一旦确定就不能更改,数组的长度可以定义为0,但是不能为负数。因为数组长度确定,所以在内存中大小也是确定的
数组下标
数组对象在jvm的堆区中是一块连续的内存空间,并允许使用下标,设置或获取每一个内存空间的值
数组下标[0~数组长度-1],如果超过出这个范围堆数组进行赋值或者取值会报错,下标越界异常
默认值
当一个数组创建时,系统会自动为数组各个位置当初始化默认值,不同类型的数组,默认值也不相同
整型 0
浮点型 0.0
char类型 空格 “\u0000”
引用类型 null
boolean 类型 false
数组初始化
简单来说就是为数组元素进行赋值
动态初始化
指定长度,由系统给出默认值
int[] arr = new int[5] 创建对象时,没有赋值,都是系统给的默认值
静态初始化
给出初始化的值,数组长度由系统决定
int [] arr ={1,2,3,4,5} 创建对象时并进行赋值
错误的初始化
int[] arr;
arr={1 , 2, 3,4}
int[] arr = new int[4]{1,2,3,4}
数组常见的问题
ArraysIndexOutOfBoundsEXceptiom 数组索引越界异常
NullPointerException 空指针异常 (数组不指向堆内存,却还想通过数组去访问内存空间)
数组的遍历
三种基本循环和增强for循环
数组拷贝
数组对象的长度一经确定就不能修改,但可以通过赋值数组的内容变通实现改变数组的长度
int[] arr = new int[2];
// 硬拷贝
int[] copyarr = new int[arr.length*2];
System.arraycopy(arr, 0, copyarr, 2, arr.length);
//软拷贝
arr = Arrays.copyOf(arr, arr.length*2);
二维数组
二维数组就是在数组中包含多个一维数组,在内存空间中就是二维数组内存地址下标指向对应的一维数组内存地址
二位数组 的声明和创建
int [] [] arr = new int [4] [3];
int[] [] arr = new int [4] [];
arr[0] = new int[3];
int[] [] arr = {
{1,2,3},
{1,2,3},
{1,2,3},
}
赋值 指定到明确的下标进行赋值和取值
arr[1] [2] =0;
int a = arr [1] [1];
可变参数 int…a
可变参数可以穿的参数情况更多,但最终会变成一个数组,根据传过来来的参数数量来决定数组长度。如果有多个形参,并且其中包括可变参,那么,最多只能包含一个可变参数,并且可变参数要放在形参列表的最后
变量的作用
接收 保存 传递 操作数据
变量的类型必须和数据类型保持一致的
如果类型不同,就需要进行类型转换
变量的使用
先声明,在赋值,在使用
只声明变量不赋值,会报错;
变量的划分 基本类型变量 引用类型变量
范围划分 局部变量 成员变量
局部变量的作用范围就在方法,参数列表中的变量 也是局部变量
成员变量作用范围在类中,非静态的方法都可以直接访问,静态方法访问静态的成员变量。
赋值操作符,
这里值得注意的是 byte i +=1;并不会报错,因为这里系统会自动执行类型转换,并且是在自身的基础之上自增1;
a+=b -----a= a+b 其他赋值操作符类似
a++ 先执行 在加一
++a 先加一在执行
如果不执行,那么这两个语句都是自增一
比较操作符
这里值得注意的就是 instanceof 符 它的作用是用来判断当前这个对象是否属于某个类型 对象 instanceof 类型
相等操作符
== 这里比较的数值和地址是否相等 一般判断值相等 用得equals
equals是判断值相等 ==是判断对象是否相同 内存地址
算术操作符
这里值得注意的就是就是 +,+号不仅可以用来相加,还可以用来表示字符串的连接,但要注意的是
1+1+“s”+2+5 = 2s25 前面的1+1 进行的是数值运算 到后面的+“ ”才是变成字符串拼接的作用,并且后面的+号也会全变成拼接字符串的作用
移位操作符
**>>**算术右移位运算
这个操作的本就是原来的数除以2^n
移位之后,只保留正数部分
**<<**算术左移位运算
这个操作的本质就是乘以2^n,
如果超出范围,那么就会显示0,但移位的个数超出该类型的最大范围,那么实际上移位的个数位 原本移位数-最大范围位数
**>>>**逻辑右移位运算,也叫不带符号的右移位运算
这里不同的是不论是正数还是负数,逻辑右位移之后,位置都要补0
位运算符
& 有0则0
| 有1则1
^相同则0 不同则1
~取反
这里值得注意的是 一个数疑惑两外一个数两次等于它本身
逻辑运算符
逻辑与 && 逻辑或||
&&与&的区别
&&表示的是 如果在其中以一个表达为false 那么 后面的表达式将不再执行 而&就算出现了一个false 那么后面的表达式还会继续判断
条件运算符 三目运算符
z = 2 > 1 ? true : false; 如果判断结果为true 那么就会执行?后面的语句,否则会执行:后面的语句
类型转换
大范围的转小范围 为自动转换 系统会自动完成转换
小范围的转大范围的 是强制转换 需要自己手动转换
int a = 1;
byte b = (byte) a;
强制转换存在当我风险就是,当这个数超过要这个类型的范围 那么就会发生精度丢失
流程控制
if (判断表达式){
执行语句
}
if(判断表达式){
执行语句
}else{
执行语句
}
if(判断表达式){
执行语句
}else if (判断表达式){
执行语句
}else{
执行语句
}
只会执行其中一个执行语句,谁的表达式为true就执行谁的,else if 可以有多个
switch
注意,switch目前只支持byte、short、int、char这四种类型的比较,JDK8中也允许String类型的变量做对比。
switch(值){
case 值1:{执行语句;break;}
case 值2:{执行语句;break;}
case 值3:{执行语句;break;}
…
default:{执行语句}
}
与谁相同就执行谁的,并且执行之后跳出这个判断语句,如果没有相同的就会执行default语句,当然default语句也可以不写
如果不加break语句,如果执行其中一个case的值与switch中的值相等,那么这个case后面的其他case的语句都会执行,包括default的语句,这称为穿透
for循环
for(初始化表达式;条件表达式;循环后的操作表达式) {
循环体;
}
/*
a:执行初始化语句
b:执行判断条件语句,看其返回值是true还是false
如果是true,就继续执行
如果是false,就结束循环
c:执行循环体语句;
d:执行循环后的操作表达式
e:回到b继续
*/
其他写法
for(int i = 0 ;i<10){
i++;
}
int i =0;
for(;i<10;){
i++;
}
for(;;){
}//当然这是一个死循环
while循环
while(判断){};
//例如
int i =0;
while(i<10){
i++;
}
//类似于这种for循环
int i =0;
for(;i<10;){
i++;
}
do -while
do{
}while(判断);
while 与do-while不同的是 do-while不论判断的结果如何都会先执行一次
嵌套循环
就是可以在一个循环之间,嵌套一个或者多个循环
break
跳出当前整个循环
continue
跳出当前这一次循环,并执行下一个循环,不会跳出整个循环
label
指定跳出哪一个循环
test1:for(){
test2:for(){
test3:for(){
break test1;
}
}
}
//直接跳出test1循环
如果要跳出多层循环就需要label或者是return