一阶段学习路径:
一: 基础语法Basic
1:环境的配置
1>安装JDK
可以安装多个JDK,我们使用的是1.8,环境变量配置哪个,哪个生效
2:环境变量的配置
1)JAVA_HOME:配置JDK安装的目录
2)Path:配置JDK的bin目录,不新建
3)CLASS_PATH配置的是JDK的lib目录
3:win+r打开DOS命令:Java-version,出现版本好即为成功
4:开发工具eclipse
5:开发工具IDEA需要设置字体大小和颜色
6:安装路径不能出现中文
二:JDK JRE JVM
JDK:Java开发工具包—开发时的最小单位
JRE:Java运行工具包—运行时的最小单位
JVM::Java虚拟机–负责加载并运行对应的字节码文件.class
1:运行过程:编写的源码以.Java为后缀,通过编译生成以.class为后缀的字节码文件,交给JVM虚拟机来执行
2:跨平台:在不同的操作系统上安装对应的JVM就可以实现跨平台--一行代码,处处执行
三:语法基础
1:关键字
有50个全小写的单词,在Java中有特殊意义,包含两个保留字const和goto
2:标识符
1)由字母,数字,下划线和美元符号组成,不能以数字开头,区分大小写,关键字+(true false null )不可以用作标识符,见名知意
2) 类名:要用驼峰命名法且每个代词的首字母都要大写
3)方法名:也使用驼峰命名法,除了第一个单词的首字母不用大写以外,其余每个单词的首字母都要大写
3:注释
注释分为 :单行注释,多行注释以及文档注释
文档注释可以添加一些额外信息,如:作者 时间 版本 参数 返回值类型等
注释可以注释内容,被注释的内容不被执行,我们可以用注释来做代码的测试
4:变量
成员变量:类里方法外,类消失成员变量显示
成员变量自己的默认值,可以不用手动赋值
局部变量:方法里,代码块里,当局部代码结束,局部变量也随之释放
1)局部变量使用时必须先赋值
2)声明的时候并且赋值,如 Dog dog = new Dog();
3)先声明再赋值 如:Dog dog; Dog dog = new Dog();
4)基本类型保存的是值,引用类型保存的是地址值
变量有就近原则,离谁近就用谁
1)使用this变量名来指定本类的成员变量
2)使用super变量名来指定父类的成员变量
5:Java中的数据类型有基本类型和引用类型
6:八大基本类型
7:字面值规则
1)整数类型的字面值类型为int,浮点型的字面值类型为double,byte short char 三种比int小的类型,可以在范围内直接赋值
2)三种字面值后缀为:L(long),D(double),F(float)
3)三种字面值前缀: 0b-二进制 0-八进制 0x-十六进制
<进制前缀练习>
8:运算规则
运算结果的数据类型与参与运算的最大类型保持一致 int+int->int double/int->double,整数运算会出现溢出的现象,一旦溢出,数据就不正确了(光年案例),byte short char三种比int小的类型,运算的时候需要先自动提升int类型,再参与运算,浮点数的特殊值:Infinity NaN,浮点数运算不精确
9:类型的转换关系
大转小,直接转,小转大,强制转,浮变整,小数没
1)布尔类型不参与类型间的转换
2)基本类型之间能否转换,不取决于字节数,字节数只能做参考,取决于类型的取值范围
3)基本类型之间的转换,引用类型之间的转换取决于是他们之间否有继承关系
如:可以说老虎是动物,但是不能说老虎是汽车,不然后面的这种错误的情况会报:类型转换异常
类型转换图
<类型间的转换与字面值练习>
9:运算符
1)普通的四则运算符+ - * /,普通的四则运算不能改变变量本身的值:除非i=i*10+5,取余%如 4%2=0 5%2=1(被整出结果为0)
2)自增自减运算符
(1)可以改变变量本身的值
(2)前缀式:符号在前,先改变变量本身的值(+1/-1)再使用(打印)
(3)后缀式:符号在后,先使用(打印),再改变变量本身的值(1-/1+)
(4)无论是前缀式或者后缀式都会改变变量本身的值,只是执行的时机不同
3)比较运算符
1)比较运算符最终的结果为布尔类型, < , > , >= , <=
2)== 比较的是左右两边的值是否相等 !=比较的是左右两边的值是否不相等
<==比较的练习>
4)逻辑运算符
<1>双与-短路与-&&:判断逻辑与&是否一致,添加短路功能:全真才真,有假则假
<2>双或-短路或-||:判断逻辑与|是否一致,添加短路功能:全假才假,有真则真
(短路可以提高程序的性能,不一定会用到)
5)三目运算符1?2:3;1是表达式,1真取2,1假取3
6)复合运算符:-=, +=, *= , /= 是一种简写的形式,运算时会自动进行类型转换
7)赋值运算符 = 左边给右边
8)拼接功能 +
9)位运算符:主要参与的是二进制运算
&与:全真才真; |或:全假才假; ^异域:相同为0,不同为1; ~非:非0为1,非1为0
10)优先级控制:表达式的运算比较复杂,需要控制优先级,可以使用小括号
11)instanceof--运算符
10:流程控制
1)顺序结构
程序按照顺序一行一行向下执行,可以用来进行输入 输出 计算等
顺序结构不可以完成先做判断,再做选择的流程,因为顺序结构中所有的代码都会被执行到
2)分支结构
1)单分支结构
if(判断条件){
若判断条件结果为true,就执行此处代码,不符合条件,就跳过
}
2)多分支结构
if(判断条件){
如果判断条件的结果为true,就执行此处的代码
}else{
如果不符合条件,执行else处的代码
}
3)嵌套分支结构
if(判断条件1){
符合判断条件1,执行此处代码,不符合,继续向下判断
}else if(判断条件2){
符合判断条件2,执行此处代码,不符合,继续向下判断
}else if(判断条件3){
符合判断条件3,执行此处代码,不符合,继续向下判断
}else{
保底选项,以上条件均不符合的情况下,执行此处代码
}
<嵌套分支的练习题>
3)选择结构
<1>小括号变量支持的类型:byte int string char;如果配置了default默认选项,没有任何case被匹配到,执行到就执行default后的语句;小括号中的变量类型必须与case后value的类型一致
<2>执行顺序:先拿着变量的值依次与case后的值做比较,如果相等就执行case后的语句,若case后未加break,就会继续执行下一个case后的语句,如果一直没遇到break,就会出现穿透现象,default也是一样
switch(变量名){
case value1 : 语句1;break;
case value2 : 语句2;break;
case value3 : 语句3;break;
case value4 : 语句4;break;
default: 保底选项
}
<switch结构的练习>
4)循环结构
可以多次帮我们重复做一件事
1)for循环
for(开始条件;循环条件;更改条件){符合条件,执行循环体里的内容}
我们定义循环变量是用来控制循环的,循环变量可以取到几个值,循环执行几次;开始条件只会执行一次;从哪里开始,到哪里结束,如何变化
for(开始条件;循环条件;更改条件){//外层循环
for(开始条件;循环条件;更改条件){//内层循环
循环体
}
}
外层循环控制的是行数,内层循环控制的是列数
外层循环控制的是论数,内层循环控制的是这一轮中执行的次数
2)while循环
while(判断条件){符合判断条件,继续循环}
常用来完成死循环,但死循环必须设置出口
<while循环的练习>
3)do-while循环
do{循环体}while(判断条件);
11:方法
1)格式: 修饰符 返回值类型 方法名(参数列表){ 方法体 }
2)我们用方法名+参数列表来调用这个方法
3)如果一个方法被调用,就会执行这个方法里的内容,执行完毕,再返回到调用的位置,从哪里调用,就返回到哪里,如果这个方法有返回值,我们有两种选择:
<1>选择只执行功能,不接收返回值,不再继续使用这个方法的结果
<2>选择在方法调用的位置接收这个方法的返回值,接收到的返回值可以在方法外继续使用
4)方法的重载
<1>在同一类中方法名相同,参数列表不同的方法
<2>方法是否构成重载,取决于参数列表中参数的个数与参数的类型,与参数的名字无关
<3>重载意义:重载不是为了程序员方便,而是为了方便外界调用这个名字的方法
<4>不管传入什么类型的参数,都可以匹配到对应的方法来执行,程序会更加的灵活
5)方法的传值
<1>方法的参数类型是基本类型,传入的是实际的字面值,若方法的参数类型是应用类型,传入的是地址值
<2>形参:形式意义上的参数,比如方法参数列表的参数名,光看参数名是无法确定这个变量的值是多少的
<3>实参:实际意义上的参数,比如我们的局部变量,比如成员变量,比如调用方法时传入的数字
6)方法的重写
子类继承了父类以后,对父类的方法不满意时,实现功能的修改与拓展
方法重写遵循的规则:两同 两小 一大
一大: 子类方法的修饰符权限 >= 父类方法的修饰符权限
一大: 子类方法的修饰符权限 >= 父类方法的修饰符权限
两小:子类方法的返回值类型 <= 父类方法的返回值类型
<=说的是继承关系,不是值的大小,子类方法抛出的异常类型 <= 父类方法抛出的异常类型
7)方法的递归
<方法的调用练习>
12:数组
1)数组的创建方式
静态创建 int[] a = new int[]{1,2,3,4,5};
静态创建 int[] a = new int[]{1,2,3,4,5};
动态创建 int[] a = new int[5]; 后续可以动态的给数组中的元素赋值
不管是什么样的创建方式,都需要指定数组的类型与长度
2)通过数组的下标来操作数组中的元素
数组下标从0开始,最大下标是数组的长度-1,如果访问到了不是这个数组的下标,会出现数组下标越界异常
3)数组的长度
数组一旦创建,长度不可改变,长度指的是数组中元素的个数a.length,并且数组的长度允许为0:[ ]
4)数组创建过程
<1>根据数组的类型与长度开辟一块连续的内存空间
<2>对数组中的每个元素进行初始化,比如int数组的默认值就是0
<3>生成数组唯一的一个地址值,交给应用类型变量a来保存
<4>可以根据数组的下标再来操作数组中的具体元素
<5>数组名a这个变量,保存的是数组的地址,不是数组中的具体元素
5) 数组的工具类Arrays
<1>toString--数组名: 除了char类型的数组以外,其他类型的数组想要查看具体元素,需要使用本方法,否则打印的是地址值
<2>copyOf--原数组名,新数组长度: 用来实现数组的复制 扩容 缩容
<3>如果新数组的长度 > 原数组长度,就扩容,反之,则缩容,如果两者长度一致,就是普通的复制数组
<4>一定是创建了新数组,而不是对原数组的长度做操作
6)数组的遍历:一般习惯使用for循环,循环变量代表的就是数组的下标,从0开始,最大值是a.length-1
7)冒泡排序
外层循环控制比较的轮数,所以循环变量从1到n-1轮,代表的是轮数
内层循环控制的是这一轮中比较的次数,而且是数组中两个相邻元素的比较,所以循环变量代表的是数组的下标
<数组练习>
二:面向对象oop
1)面向对象与面向过程
<1>两者都是一种编程的思想
<2>面向对象强调的是事情的结果,我们通过对象完成对应的功能
<3>面向过程强调的是事情的过程,做任何事情,都要亲力亲为,经过每一个步骤
<4>Java是一门面向对象的语言
2)类与对象
<1>定义类通过关键字class来定义,类是一类事物的抽象,它是抽象的,它是模板
<2>创建对象通过new关键字触发构造函数生成,对象是根据类创建出来的具体的内容
<3>一个类可以创建出多个对象,对象是根据类的设计来创建的,所以对象具有类的所有属性与功能
<4>对象之间是相互独立的,互不影响。我们把创建对象也称作“实例化”
3)面向对象的三大特性:封装 继承 多态
<1>封装
(1)前提:为了保证数据的安全,也为了程序的使用者能够按照我们预先设计好的方式来使用资源
(2)封装属性:用private修饰的属性,然后为属性提供对应的getXxx()【获取属性值】与setXxx()【设置属性值】
(3)封装方法:用private修饰方法,被修饰的方法只能在本类中使用,所以我们在本类的公共方法里调用这个私有方法,外界如果想要使用这个私有方法的功能,只需要调用这个公共方法就可以了
<2>继承
(1)前提 :继承可以实现程序的复用性,减少代码的冗余
(2)我们通过extends关键字建立子类与父类的继承关系:格式:子类 extends 父类
(3)继承相当于子类把父类的功能复制了一份,包括私有资源
(4)虽然私有资源继承了,但是私有资源不可用,原因是被private限制了访问,私有资源只能在本类使用
(5)构造方法不能被继承,因为构造方法要求名字是本类的类名,我们不能在子类中出现父类名字的构造方法
(6)继承具有传递性:比如:爷爷的功能会传给爸爸,爸爸的功能会传给孙子,爸爸从爷爷那里继承的功能,也会一起传给孙子
(7)Java的类是单继承,一个子类只能有一个父类,但是一个父类可以有多个子类
(8)子类继承父类 对继承过来的功能不满意时,在不修改父类功能的【满足OCP原则】前提下
在子类中可以重写继承过来的这个功能
重写的遵循的规则:两同 两小 一大 可以在重写的方法上加@Override注解来验证
(9)继承是一种is a的关系,强耦合,关联性特别强,而且类的继承机会只有一次,(谨慎使用)
(10)子类可以直接使用父类的所有非私有资源
<3>多态
(1)忽略子类型之间的差异,统一看作父类类型,写出更加通用的代码
(2)在同一时刻,同一个对象,代表的类型不同,拥有多种形态
(3)多态的要求:继承 + 重写
(4)父类引用指向子类对象:父类型的引用类型变量保存的是子类对象的地址值
(5)编译看左边,运行看右边
(6)父类中定义的功能,子类才能使用,否则报错,多态中,方法的定义看的是父类的,方法的实现看的是子类重写后的功能
(7)多态中资源的使用:
《1》成员变量:使用的是父类的
《2》成员方法:对于方法的定义看的都是父类的,对于方法实现,重写后使用的是子类的
《3》静态资源:静态资源属于类资源,不存在重写的概念,在哪个类中定义的,就属于哪个类
(8)向上造型与向下造型
《1》这两种都属于多态,只不过是多态的两种不同的表现形式
《2》向上造型【最常用】
《3》可以把不同的子类型都看作是父类型
《4》向下造型
前提:必须得先向上造型,才能向下造型
子类的引用指向子类的对象这个子类对象之前被看作是父类类型,需要强制类型转换
之前被看作是父类类型的子类对象,想使用子类的特有功能,那就需要向下造型
6:构造方法
1)格式:修饰符 类名当做方法名(){ } -----与类同名且没有返回值类型
2)作用:用于创建对象,每次new对象时,都会触发对应的构造函数,new几次,触发几次
3)一个类中默认存在无参构造,如果这个构造不被覆盖,我们可以不传参数,直接创建这个类的对象
4)如果这个类中提供了其他的构造函数,默认的无参构造会被覆盖,所以记得手动添加无参构造
5)构造方法也存在重载的现象:无参构造 含参构造 全参构造【创建对象+给所有的属性赋值】
7this与super
1)this但表的是本类,super代表的是父类
2)当本类的成员变量与局部变量同名时,可以通过this.变量名指定本类的成员变量,当父类的成员变量与子类的变量同名时,可以通过super.变量名指定父类的成员变量
3)在本类构造函数的第一行,使用this();调用本类的无参构造 / 使用this(参数); 调用本类对应参数的构造方法,构造函数的调用只有这一种方式,或者创建对象时被动触发,不能在外面自己主动调用,构造函数直接不能互相调用,否则会死循环
4)在子类构造函数的第一行,使用super();调用父类的无参构造 / 使用super(参数); 调用父类对应参数的构造方法,子类默认调用super();父类的无参构造,如果父类没有无参构造,需要手动指定调用哪个含参构造
7:对象的创建过程
1)对象是根据类的设定来创建的,目前我们可以在类中添加很多的元素:属性 方法 静态方法 构造代码块 静态代码块 局部代码块 构造方法…所以不限制类里具体写什么,取决于业务
2)对象创建的过程:Dog d = new Dog();
<1>需要在堆内存中开辟一块空间,用来存放对象
<2>对象需要完成初始化,比如对应的属性都有自己的对应类型的默认值
<3>对象创建完毕后,会生成一个唯一的地址值用于区分不同的对象
<5>将这个地址值交给引用类型变量来保存,后续如果想要使用这个类的功能,可以从引用类型变量中保存的地址值找到对应的对象做进一步的操作
3)匿名对象:new Dog();
匿名对象是没有名字的对象,所以创建过程:<1>需要在堆内存中开辟一块空间,用来存放对象
<2>对象需要完成初始化,如:对应的属性都有自己的对应类型的默认值
<3>对象创建完毕后,会生成一个唯一的地址值用于区分不同的对象,那么我们使用匿名对象只能使用一次,并且一次只能使用一个功能 如:new Phone().video();
4)方法的重写与重载
<1>方法的重写:
继承后,子类想在不改变父类代码的前提下,修改或者添加新功能
重写的规则:两同 两小 一大
(1)子类与父类的方法名要保持一致
(2)子类与父类的方法的参数列表要保持一致
(2)子类的返回值类型<=父类方法的返回值,这里说的是继承关系,不是值的大小
(4)子类的抛出异常类型<=父类方法抛出的异常类型
(5)子类方法的修饰符>=父类方法的修饰符
(6)可以使用@Override注解标记这是一个重写的方法
<2>方法的重载
(1)在同一个类中,存在两个或者两个以上的方法,方法名相同,但参数列表不同的现象
(2)作用:载提高程序的灵活性,只要用户调用这个方法,不管传入什么参数,都能匹配的上
(3)方法是否构成重载,除了前两项以外,看的是参数的个数与类型,与方法的参数名字没关系
8:代码块---执行顺序
1)静态代码块 static { }
<1>位置:类里方法外
<2>执行时机:当其所处的方法被调用时才会执行
<3>作用:用于限制变量的作用范围,出了局部代码块就失效
2)代码块之间的顺序
静态代码块 -> 构造代码块 -> 构造方法 -> 普通方法(如果普通方法里有局部代码块,局部代码块才会执行)
3)static--静态
<1>被static修饰的资源统称为静态资源,可以用来修饰变量、方法、代码块、内部类
<2>静态资源属于类资源,随着类的加载而加载,优先于对象进行加载,只加载一次
<3>静态资源可以不通过对象,使用类名直接调用,不需要创建对象
<4>静态资源只有一份,被全局所有对象共享
<5>静态的调用关系:静态资源只能调用静态资源
<6>静态资源是优先于对象的,所以静态资源不能与this和super共用
3)final
<1>final表示最终
<2>被final修饰的类是最终类,也称作叶子结点,所以不能被继承
<3>被final修饰的方法是这个方法的最终实现,不能被重写
<4>被final修饰的是常量,值不可以被修改,注意常量定义时必须赋值
4)抽象
<1>抽象的关键字是abstract
<2>被abstract修饰的方法是抽象方法,抽象方法没有方法体
<3>如果一个类中出现了一个抽象方法,那么这个类必须被abstract修饰
<4>抽象的特点
(1)抽象类中的方法不做限制 : 全普 / 全抽 / 半普半抽
(2)如果一个类中的方法都是普通方法,要声明成抽象类,为了不让外界创建本类的对象
(3)抽象类不可以创建对象,所以常用于多态
(4)抽象类中包含构造方法,但是不是为了自己创建对象时使用,而是为了子类的super()
(5)抽象类中也是可以定义成员变量的
<5>子类继承父类的抽象方法有两种解决方案
(1)作为抽象子类:不实现/实现部分 抽象父类中的抽象方法 : ”躺平”
(2)作为普通子类:实现抽象父类中的所有的抽象方法 : “父债子偿”
<6>面向抽象进行编程:后天重构的结果
5)接口
<1>接口不是类,定义接口的关键字interface
<2>如果一个类想要实现接口中定义的规则,需要使用implments与接口建立实现关系,如果有任何一个抽象方法没有实现,那么这个类就是一个抽象子类
<3>Java8中接口里的所有方法都是抽象方法
<4>接口中只有静态常量,没有普通变量,会自动拼接public static final
<5>接口中的方法也可以简写,会自动拼接public abstract
<6>接口不可以实例化
<7>接口中没有构造方法,实现类调用的是它自己父类的构造方法,如果没有明确指定父类,那就是Object的
<8>接口更多的是规则的制定者,不做具体的实现
<9>接口降低了程序的耦合性,更加方便项目的维护与拓展
<10>接口是先天设计的结果,这样可以省去后续的多次重构,节省资源
6)接口与类的关系
<1>类与类之间的关系
Java的类只支持单继承,类与类就是继承关系,并且一个子类只能有一个父类
<2>接口与接口之间的关系
Java的接口是不做限制的,可以多继承,如:例1-- interface Inter1 extends Inter2{ } – Inter1是子接口 Inter2 是父接口,例2--interface Inter1 extends Inter2,Inter3{ } – Inter1 是子接口 Inter2 和 Inter3 都是父接口,如果是例2的,接口1的实现类需要实现这三个接口(Inter1,2,3)的所有抽象方法
<3>接口与类之间的关系
Java中的类对于接口而言是多实现的,所以一个类可以实现多个接口
<4>接口与抽象类的关系
(1)接口是一种用interface定义的类型,抽象类是一种用class定义的类型
(2)抽象类是一种用class定义的类型,抽象类中的方法不做限制
(3)接口中的都是静态常量,抽象类中可以写普通的成员变量
(4)接口没有构造方法,不可实例化,抽象类有构造方法,但是也不可以实例化
(5)接口是先天设计的结果,抽象是后天重构的结果
(6)接口可以多继承,抽象类只能单继承
7)异常
(1)异常的继承结构;异常层次结构中的根是Throwable;Error:目前我们编码解决不了的问题;Exception:异常;编译异常:未运行代码就报错了,强制要求处理;运行时异常RunTimeException:运行代码才报错,可以通过编译,不强制要求处理
(2)异常的解决方案:
《1》捕获--try-catch--自己解决--格式:
try{
出现异常的代码
}catch(异常类型 异常名){
捕获到异常的处理方案
}finally{
异常处理结构中一定会被执行到的代码块,常用来关流
}
《2》向上抛出throws–交给别人解决,在方法定义的两个小括号之间throws,可抛出多个异常,用逗号隔开;不能直接把异常抛给main(),因为调用main()是JVM,没人解决了;是否抛出异常取决于自己的业务,比如暂时不处理或者处理不了需要交给别人处理
8)内部类
<1>可以把内部类看作是外部类的一个特殊的资源
<2>内部类可以直接使用外部类的所有资源,包括私有资源
<3>外部类想要使用内部类的资源,需要创建内部类的对象才能使用
<4>对象的普通创建方式:
外部类名.内部类名 对象名=外部类对象.内部类对象
如:Outer.Inner oi = new Outer().new Inner();
<5>成员内部类---位置:类里方法外
(1)被private修饰--被私有化的内部类在main()中是没有办法直接创建其对象的
可以在私有内部类所处的外部类中,创建一个公共的方法供外界调用,这个方法用来返回创建好的私有内部类对象
(2)被static修饰--静态内部类可以不创建外部类对象,直接创建静态内部类对象,格式:Outer3.Inner3 oi = new Outer3.Inner3();如果静态内部类中还有静态方法,那么我们可以不创建对象;直接通过链式加载的方式调用:Outer3.Inner3.show2();//表示通过外部类名直接找到静态内部类,再找到静态方法
<6>局部内部类--位置:方法里
直接创建外部类对象,调用局部内部类所处的方法,并不会触发局部内部类的功能;需要在外部类中创建局部内部类的对象并且进行调用局部内部类的功能,才能触发内部类的功能
<7>匿名内部类--位置:可运行的代码中:main()里
匿名内部类通常与匿名对象【没有名字的对象】一起使用,格式:new Inter1(){ 我这个大括号其实是一个匿名内部类,我来实现方法 }.eat();如果只是想使用一次接口/抽象类的某个功能,可以使用匿名内部类;匿名内部类+匿名对象的功能:创建实现类+实现方法+方法功能的一次调用【功能三合一】
三:基础API
1:Object--所有类的超类
1)Java中的类都直接或者间接的继承了Object
2)如果一个类没有明确指定父类,那么默认继承Object
3)Object处于java.lang包之下,不需要导包可以直接使用
4)toString()–我们日常使用最频繁的打印语句底层就调用了这个方法,如果没有重写这个方法,使用的是Object的默认实现,打印的是对象的地址值,如果重写以后,以重写的逻辑为准,比如String打印的是串的具体内容,比如ArrayList,打印的是[集合元素]
5)hashCode()–用于返回对象对应的哈希码值,如果是一个对象多次调用这个方法,返回的是同一个哈希码值,如果是不同的对象调用这个方法,应该返回的是不同的哈希码值
6)equals()–用于比较当前对象与参数对象是否相等,重写之前的默认实现比较的是两个对象的地址值,重写之后取决于重写的逻辑,比如String比较的是两个串的具体内容,比如自定义对象比较的是类型+属性值
7)equals()与hashCode()应该保持一致【要重写都重写】
equals()底层默认实现比较的是==比较,地址值,重写后我们一般比较的是对象的类型+属性值,
hashCode()不同的对象生成的哈希码值不同,那么与equals()的逻辑不匹配,所以也应该重写,重写后,是根据对象的类型与属性值来生成哈希码值,这样二者就一致了
2:String
1)String底层维护的是一个char[],而且String不可变,因为源码中的数组被final修饰了
2)创建方式:char[] vlaues = {‘a’,‘b’,‘c’}; String s = new String(values);String s = “abc”;有高效的效果,因为串存在堆中的常量池,第二次使用时就不再新建了
3)常用方法:
int hashCode() 返回此字符串的哈希码。
boolean equals(Object anObject) 将此字符串与指定的对象比较,比较的是重写后的串的具体内容
String toString() 返回此对象本身(它已经是一个字符串!)int length() 返回此字符串的长度。
String toUpperCase() 所有字符都转换为大写。
String toLowerCase() 所有字符都转换为小写
boolean startsWith(String prefix) 测试此字符串是否以指定的元素开头。
boolean endsWith(String suffix) 测试此字符串是否以指定的字符串结束。
char charAt(int index) 返回指定索引/下标处的 char 值/字符
int indexOf(int ch) 返回指定字符在此字符串中第一次出现处的索引。
int lastIndexOf(int ch) 返回指定字符在此字符串中最后一次出现处的索引。
String concat(String str) 将指定字符串连接/拼接到此字符串的结尾,注意:不会改变原串
String[] split(String regex) 根据给定元素来分隔此字符串。
String trim() 返回去除首尾空格的字符串
byte[] getBytes() 把字符串存储到一个新的 byte 数组中
String substring(int beginIndex) 返回一个新子串,从指定下标处开始,包含指定下标
String substring(int beginIndex, int endIndex) 返回一个新子串,从执定下标开始,到结束下标为止,但不包含结束下标
static String valueOf(int i) 把int转成String
3:StringBuilder与StringBuffer
1)String:
特点:创建之后长度内容是不可变的,每次拼接字符串,都会产生新的对象
优点:String类提供了丰富的关于操作字符串的方法,比如:拼接、获取对应下标处的字符、截取子串等等
缺点:在进行字符串拼接+=的时候,效率比较低
String转StringBuilder:String s = “abc”; StringBuilder sb = new StringBuilder(s);
2)StringBuilder:
特点:StringBuilder是一个长度可变的字符串序列,在创建的时候,会有一个长度为16的默认空间,当拼接字符串的时候,是在原对象的基础之上进行拼接,如果长度不够就扩容,所以StringBuilder在创建之后,对应的操作一直是用一个对象
优点:在拼接的时候,不会产生新的对象,就避免了因为拼接频繁生成对象的问题,提高了程序的效率,使用的是append()
缺点:对于字符串的操作,不太方便
创建方式:StringBuilder sb = new StringBuilder();创建一个长度为16的StringBuilder对象
StringBuilder sb = new StringBuilder(“abc”);以指定字符串内容为“abc”的方式创建一个StringBuilder对象
StringBuilder转String:StringBuilder sb = new StringBuilder();sb.append(“abc”);
String s = sb.toString();
拼接多用StringBuilder,用完转回String用String丰富的方法
4:包装类
基本类型只存值,也没有丰富的功能;包装类型是对基本类型做了包装,并提供了很多方便的方法,所以包装类的对象是引用类型的对象
创建方式:Integer i1 = Integer.valueOf();数据只要在-128~127有一个高效的效果
Integer i2 = new Integer(4);没有高效的效果,只是创建了一个包装类的对象
5:自动拆箱与自动封箱
1)自动拆箱:
编译器会自动把基本类型int5,包装成包装类型Integer;然后交给Integer类型的引用类型变量i3来保存;自动装底层发生的代码:Integer.valueOf(5);valueOf()的方向:int–>Integer
2)自动封箱:
编译器会自动把包装类型的i1拆掉”箱子“,变回基本类型的数据127;然后交给基本类型int的变量i4来保存;自动拆箱底层发生的代码:i1.intValue();intValue()的方向:Integer-> int
四:高级API
1:IO流
1)学习方式:学习抽象父级的公共方法 学习子类流对象的创建方式
2)流的分类:根据方向:输入流 输出流;根据操作单位:字节流 字符流
3)字节输入流InputStream:
InputStream--抽象父类--不能实例化
FileInputStream--文件字节输入流-FIS
BufferedInputStream--高效字节输入
FIS in = new FIS(new File(路径));
FIS in = new FIS(路径);
BIS in = new BIS( new FIS(new File(路径)));
BIS in = new BIS(new FIS(路径));
4)字节输出流OutputStream:
OutputStream--抽象父类,不能实例化
FileOutputStream--文件字节输出流--FOS
BufferedOutputStream--高效字节输出流-BOS
FOS out = new FOS(new File(路径));
FOS out = new FOS(路径);
BOS out = new BOS(new FOS(new File(路径)));
BOS out = new BOS(new FOS(路径));
5) 字符输入流Reader:
Reader--抽象父类--不能实例化
FileReader--文件字符输入流-FR
fferedReader--高效字符输入流-BR
FR in = new FR(new File(路径));
FR in = new FR(路径);
BR in = new BR(new FR(new File(路径)));
BR in = new BR(new FR(路径));
6) 字符输出流Writer:
Writer--抽象父类,不能实例化
FileWriter--文件字符输出流--FW
BufferedWriter--高效字符输出流--BW
FW out = new FW(File/File,append/String pathname/String pathname,append);
BW out = new BW(Writer--所以传的是子类FW(上面那4种));
这里的append参数表示流向文件输出数据的时候是追加还是覆盖,如果不写,默认false是覆盖,如果改为true,表示追加
7) 序列化与反序列化
序列化与反序列化的作用就是对象的保存与传输
序列化:把内存中的对象通过序列化流输出到磁盘中(比如文件里),使用的流是ObjectOutputStream【把数据写出到文件】
反序列化:通过反序列化流将磁盘中的数据恢复成对象,使用的流是ObjectInputStream【把之前写到文件里的数据读到程序中】
一个类的对象如果想被序列化,那么这个类必须实现可序列化接口,实现这个接口的目的是相当于给这个类做了一个标记,标记可以序列化
序列化时会自动生成一个UID,表示当前序列化输出的对象的版本信息,反序列化时会拿着当前的UID与之前序列化输出的UID做比较,一致,反序列化成功,不一致,报错
标准操作是一次序列化对应一次反序列化,如果目标对象所在的类没有做任何修改,一次序列化也可以对应多次反序列化(根本原因是UID没变)
2:集合
1)泛型--通常与集合一起使用,用来约束集合中元素的类型
通常与集合一起使用,用来约束集合中元素的类型,泛型方法 public static ==< E > == void get(E[] e){},两处位置都出现了泛型,缺一不可
2)集合被称作Collection,是一个可以存放多个数据的容器,而且集合中提供了丰富的方法来操作集合中的元素,是集合层次的根接口,学习抽象父级的公共方法
3)Collection集合方法总结:
<1>单个集合的操作:
boolean add(E e) 将指定元素添加到集合中
void clear() 清空集合
boolean contains(Object o) 判断本集合是否包含指定的元素
boolean equals(Object o) 比较集合对象与参数对象o是否相等
int hashCode() 返回本集合的哈希码值
boolean isEmpty() 判断本集合是否为空
boolean remove(Object o) 从本集合中移除指定元素o
int size() 返回本集合中元素的个数
<2>集合间的操作:
boolean addAll(Collection<> c) 将c集合中的所有元素添加到本集合中
boolean containsAll(Collection<> c) 判断本集合是否包含c集合的所有元素
boolean removeAll(Collection<> c) 移除本集合中属于参数集合c的所有元素
boolean retainAll(Collection<> c) 保留本集合与参数集合c的公共元素
<3>集合的迭代:
Iterator iterator() 返回本集合的迭代器
List接口的特点--List集合是有下标的,List集合是有顺序的,List集合可以存放重复的数据
<4>单个集合的操作:
void add(int index, E element) 在集合的指定下标index处插入指定元素element
E get(int index) 返回本集合中指定下标index处的元素
E remove(int index) 移除本集合中指定下标index处的元素
E set(int index, E element) 用参数元素element替换集合中指定下标index处的元素
int indexOf(Object o) 判断指定元素o在本集合中第一次出现的下标,如果不存在,返回-1
int lastIndexOf(Object o) 判断指定元素o在本集合中最后一次出现的下标,如果不存在,返回-1
List subList(int fromIndex, int toIndex) 截取子集合,包含formidex处的元素,不包含toIndex处的元素
<5>集合间的操作与集合的迭代:
boolean addAll(int index, Collection<> c) 将参数集合c中的所有元素,插入到本集合中指定的下标index处
ListIterator listIterator() 返回此列表元素的迭代器,这个是List自己的,不太常用,可以逆序迭代
<6>简单方法:
void addFirst(E e) 添加首元素
void addLast(E e) 添加尾元素
E removeFirst() 删除首元素
E removeLast() 删除尾元素
E getFirst() 获取首元素
E getLast() 获取尾元素
E element() 获取首元素
<7>功能一致但是名字不太好记的方法:
boolean offer(E e) 添加尾元素
boolean offerFirst(E e) 添加首元素
boolean offerLast(E e) 添加尾元素
E peek() 获取首元素
E peekFirst() 获取首元素
E peekLast() 获取尾元素
E poll() 返回并移除头元素
E pollFirst() 返回并移除头元素
E pollLast() 返回并移除尾元素
List接口的两个常用实现类--ArrayList与LinkedList
ArrayList的特点:
底层的数据结构是数组,内存空间是连续的;元素有下标,通常可以根据下标进行操作;增删操作比较慢,查询操作比较快【数据量大时】
LinkedList的特点:
底层的数据结构是链表,内存空间是不连续的;元素有下标,但是通常首尾节点操作比较多;增删操作比较快,查询操作比较慢【数据量大时】;LinkedList查询慢也不是都慢,首尾操作还是比较快的
<8>简单方法:
void addFirst(E e) 添加首元素
void addLast(E e) 添加尾元素
E removeFirst() 删除首元素
E removeLast() 删除尾元素
E getFirst() 获取首元素
E getLast() 获取尾元素
E element() 获取首元素
<9>功能一致但是名字不太好记的方法:
boolean offer(E e) 添加尾元素
boolean offerFirst(E e) 添加首元素
boolean offerLast(E e) 添加尾元素
E peek() 获取首元素
E peekFirst() 获取首元素
E peekLast() 获取尾元素
E poll() 返回并移除头元素
E pollFirst() 返回并移除头元素
E pollLast() 返回并移除尾元素
Map接口的特点:
map集合的结构是:键值对、KEY与VALUE、Map.Entry<K,V>的映射关系;map中key值不允许重复,如果重复,对应的value会被覆盖;map中的映射关系是无序的;map没有自己的迭代器,所以迭代时通常需要转成set集合来迭代
<10>简单方法:
void clear() 清空集合
boolean equals(Object o) 判断集合对象与参数o是否相等
int hashCode() 返回本集合的哈希码值
boolean isEmpty() 判断集合是否为空
int size() 返回本集合中键值对的个数
<11>map单个集合间的操作:
boolean containsKey(Object key) 判断map中是否包含指定的key
boolean containsValue(Object value) 判断map中是否包含指定的value
V get(Object key) 根据指定的key返回对应的value,如果不存在,返回null
V remove(Object key) 删除本集合中参数key对应的键值对
V put(K key, V value) 向集合中添加映射关系(键值对)
void putAll(Map<> m) 向本集合中添加m集合的所有映射关系(键值对)
<12>map的迭代:
Collection values() 把本map中的Value值取出放入一个Collection中并返回这个Collection
Set keySet() 把本map中的Key值取出放入一个Set集合中并返回这个Set集合
Set<Map.Entry<K,V>> entrySet()
把本map中的每一对KV都看成是一个Entry,把所有的Entry取出放入一个Set集合中并返回这个Set集合
Set接口的特点:
set集合没有重复的元素;set集合的元素是无序的;set集合可以存null值,并且null最多有一个;我
们自定义对象如果想去重,需要在自定义类中添加重写的equals()与hashCode()
集合学习的方法:--学习父级的公共方法,学习子类的创建方式,学习各种集合的特点
关于List大多都是与下标有关的操作;关于Set通常都是去重的操作;关于map通常都是映射关系,
也就是键值对;API要常练习,方法互相之间没有任何关系,用哪个,查哪个
3:进程与线程
<1>程序:数据与指令的集合,而且程序是静态的
<2>进程 :运行中的程序,给程序加入了时间的概念,不同时间进程有不同的状态,进程是动态
的,代表OS中正在运行的程序;进程有独立性,动态性,并发性
<3>并行:相对来说资源比较充足,多个CPU同时并发处理多个不同的进程
<4>并发:相对来说资源不太充足,多个资源同时抢占公共资源,比如CPU
<5>线程:线程是OS能够进行运算调度的最小单位
一个进程可以拥有多个线程,当然,也可以只拥有一个线程,只有一个线程的进程称作单线程程
序,每个线程也有自己独立的内存空间,当然也有一部分共享区域用来保存共享的数据
<6>线程的几种状态以及线程状态之间的切换
(1)新建状态:创建线程对象,申请PCB,对应的是new线程对象
(2)就绪状态/可运行状态:万事俱备,只欠CPU,刚刚创建好的线程对象所有资源已经准备好,并且加入到了就绪队列之中唯有等待操作系统的调度,只要分配了CPU,也就是时间片,当前线程可立即执行,对应的是start()--调用start()并不会立即执行线程对象,这个是由OS的调度规则决定的。我们控制不了
(3)执行/运行状态:就绪队列中的线程对象被OS选中,分配了时间片,正在执行--只有就绪状态才能变成运行状态
(4)阻塞状态:线程在执行过程中遇到了问题,比如锁阻塞、休眠阻塞、等待阻塞…--我们的阻塞状态,等问题解决了以后/获取了临界资源【要抢占的公共资源】后加入到就绪队列中的,转为就绪状态,而不是转为运行状态直接执行
(5)终止状态:线程成功执行完毕,释放资源,归还PCB
(6)线程的挂起:正在运行中的线程,由于CPU分配的时间片已经用完,所以需要冻结当前线程运行的状态与各项信息,把它插入到就绪队列中,直到下次这个线程被调度执行时,重新恢复现场,继续执行
<7>多线程编程实现方案一:extends Thread继承方式
(1)自定义一个多线程类用来继承Thread类
(2)重写run()里的业务【这个业务是自定义的】
(3)创建线程对象【子类对象】
(4)通过刚刚创建好的自定义线程类对象调用start()
(5)不能调用run(),因为这样调用只会把run()看作一个普通的方法,并不会以多线程的方式启动程序而且调用start()时,底层JVM会自动调用run(),执行我们自定义的业务
(6)除了可以调用默认的父类无参构造以外,还可以调用Thread(String name),给自定义的线程对象起名字,相当于super(name);
<8>多线程编程实现方案二:implements Runnable 实现方式
(1)自定义一个类实现接口Runnable
(2)实现接口中唯一一个抽象方法run()
(3)创建接口实现类的对象,这个对象是作为我们的目标业务对象【因为这个自定义类中包含了我们的业务】
(4)创建Thread类线程对象,调用的构造函数是Thread(Runnable target)
(5)通过创建好的Thread类线程对象调用start(),以多线程的方式启动同一个业务target
(6)由于Runnable是一个接口,无法创建对象,所以我们传入的目标业务类,也就是接口实现类的对象
(7)只有调用start()才能把线程对象加入到就绪队列中,以多线程的方式启动,但是:接口实现类与接口都没有这个start(),所以我们需要创建Thread类的对象来调用start(),并把接口实现类对象交给Thread(target);创建的是Thread类的线程对象,我们只需要把业务告诉Thread类的对象就好
使用方式二的优势:
(1)耦合性不强,没有继承,后续仍然可以继承别的类
(2)采用的是实现接口的方式,后续仍然可以实现别的接口
(3)可以给所有的线程对象统一业务,业务是保持一致的
(4)面向接口编程,有利于我们写出更加优雅的代码
<9>多线程编程实现方案三:Executors 创建线程池的方式
(1)创建线程池的工具类:Executors.newFixedThreadPool(int n);可以创建包含最多n个线程的线程池对象
(2)创建好的线程池对象:ExecutorService
(3)使用pool.excute()来讲线程池中的线程以多线程的方式启动,每次调用都会将一个线程对象加入到就绪队列之中,这个线程池对象负责: 新建/启动/关闭线程,而我们主要负责的是自定义的业务
(4)线程池是不关闭的,实现的效果就是线程池中线程对象的随取随用,这样就避免了频繁的创建与销毁线程,不会造成资源浪费
合理利用线程池可以拥有的优势:
(1)降低系统的资源消耗:减少系统创建与销毁线程对象的次数,每个线程都可以重复利用,执行多次任务
(2) 提高响应速度:当任务到达时,任务可以不用等待线程创建就能立即执行
(3)提高线程的可管理性:可以根据系统的承受能力,调整线程池中线程的数目
(4)防止因为创建多个线程消耗过多的内存导致服务器的崩溃【每个线程大约需要1MB的内存,线程开的越多,消耗的内存也就越大,最后死机】
<10>多线程数据安全隐患的解决方案
(1)出现数据安全问题的原因:多线程程序 + 多个线程拥有共享数据 + 多条语句操作共享数据
(2)解决方案:加锁synchronized同步关键字
同步方法【不太常用】,格式:在方法的定义上加synchronized
同步代码块,格式:
synchronized(唯一的锁对象){
可能会出现数据不安全问题的所有代码
}
锁对象必须唯一---锁对象在类中的字节码对象:类名.class
加锁时,同步代码块的范围需要考虑, 不能太大,太大效率太低;也不能太小,太小锁不住
加锁时,锁对象的类型不做限制,只要保证锁对象唯一即可
同步与异步:
同步---是多个线程抢占资源的效果,不排队,所以效率高,但是数据不安全
异同---每次只有一个线程独占资源,排队,所以效率低,但是安全,为了安全必要应该牺牲一部分资源
4:注解
1)JDK自带的注解有5个
@Override注解:可以加在方法上,用来表示这是一个重写的方法(常用)
其余四个不常用
@Deprecated标记就表明这个方法已经过时了,但我就要用,别提示我过期
@SuppressWarnings(“deprecation”) 忽略警告
@SafeVarargs jdk1.7出现,堆污染,不常用
@FunctionallInterface jdk1.8出现,配合函数式编程拉姆达表达式,不常用
2)元注解5个
元注解是用来定义其他注解的注解,也就是说,注解的语法与JAVA不同,是靠注解来定义的
定义注解的格式:@interface 注解名
可以根据元注解对注解进行设置:
表示被描述的注解可以使用的位置:值可以多选
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
表示被描述的注解的声明周期:注意值只能3选1
@Retention(RentionPolicy.RUNTIME/SOURCE/CLASS)
3)自定义注解
(1)可以根据自己的需求来定义个性化的注解:使用@interface 注解名来定义的,主要使用的就是上面的两个元注解
(2)除此之外,我们还可以给注解加功能,比如注解的属性:
《1》格式:属性类型 属性名(); 比如:int age();
《2》定义了注解的普通属性以后,使用注解时必须给属性赋值,格式:@Rice(age=10)
《3》如果给属性设置了默认值,那么使用注解时就不需要给属性赋值了,格式:int age() default 0;
(3)还可以给注解添加特殊的属性value,注意这个属性名字必须是value,类型不作限制
《1》特殊属性如果不设置默认值,使用注解时也需要赋值,不过赋值可以简写,比如@Rice(“apple”)
《2》特殊属性赋予默认值后,就可以直接使用注解了,赋予默认值的格式:String value() default “apple”;
《3》如果有多个属性,并且都没有赋予默认值,那么使用注解时的格式:@Rice(value=“apple”,age=10)
5:设计模式
1)概念:是一些前人总结出来的值得学习的编程“套路”,设计模式一共有23种
2)单例设计模式:确保代码中本类的实例只有一个
3)实现思路--方案一:饿汉式 方案二:懒汉式
饿汉式 :
<1>把本类的构造方法私有化–为了不让外界调用构造函数来创建对象
<2>通过本类的构造方法创建对象,并把这个对象也私有化,为了防止外界调用
<3>提供公共的全局访问点向外界返回本类的唯一的一个对象
<4>公共方法需要设置成静态–需要跳过对象,通过类名直接调用这个返回本类对象的公共方法
对象也需要设置成静态
的–这个对象需要在静态方法中被返回,而静态只能调用静态
懒汉式:
<1>==延迟加载的思想:==我们有的时候有些资源并不是需要第一时间就创建出来,所以需要延迟到
<2>需要时再创建,这样既可以提升性能,又可以节省资源
<3>把本类的构造方法私有化–为了不让外界调用构造函数来创建对象
<4>创建了一个本类类型的引用类型【这个变量后续用来保存创建出来的对象的地址值】
<5>提供公共的全局访问点向外界返回本类的唯一的一个对象
<6>这个公共的方法里,需要做判断
<7>如果引用类型的变量值为null,说明:之前没有创建过本类对象–创建后再赋值给引用类型变量,并把它返回
<8>如果引用类型的变量值不为null,说明:之前有创建过本类对象,这个引用类型变量保存就是地址值,本次不再新建对象,直接返回
6:反射
1)反射的概念
反射是Java这门语言中比较有特点的一个特征,反射非常强大,我们可以通过反射获取目标类当中的资源,甚至是私有资源;不仅仅如此,我们甚至还可以使用资源,并且创建对象,所以反射是一个经常被使用到的技术;开发过程中,我们有的时候并不能拿到源代码,但是又需要使用资源,那这个时候反射的出现就很有必要了
2)反射需要用到的API
<1>获取字节码对象
Class.forName(“类的全路径”); 传入的是类的全路径名,包含包名.类名,而且会抛出异常
类名.class 这个写法需要自己手动接一下获取到的字节码对象,不能用快捷方式的
对象.getClass(); 注意:经常与匿名对象一起使用
<2>常用方法
获取包名 类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名
获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)
获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)
获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)
反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.getConstructor(int.class,String.class)//要先获取构造方法
clazz.newInstance(666,”海绵宝宝”);//再执行含参构造创建对象
反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
field.setAccessible(true);//使私有成员允许访问
field.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
field.get(实例);//访问指定实例变量的值,静态变量,第一参数给null
反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法