-
格式:访问控制符 返回值类型 方法名(参数列表){ 方法体 }
-
如何确定我们要调用哪个方法呢?方法名+参数类型
-
返回到调用位置/方法的返回值返回给谁?在哪里调用这个方法,就可以在哪里接到这个方法的返回值
解释:比如main()中调用一个方法,如果这个方法有返回值,我们有两种选择:
1)忽略这个方法的返回值,我只是单纯调用一下这个方法的功能
2)接收这个方法的返回值,接收后可以在main()中继续使用这个方法执行后的结果
- 方法的重载
在同一个类中出现方法名相同但参数列表不同的现象
注意:方法之间能否构成重载,取决于方法的参数个数与类型,与方法的参数名无关
重载的意义:
是为了方便外界对方法进行调用,什么样的参数程序都可以找到对应的方法来执行,体现的是程序的灵活性
方法的传值:基本类型传递的是实际值,引用类型传递的是地址
而且方法的参数属于形参,只是格式上需要定义,但是调用方法时起不到限制的作用
形参:定义方法的时候的参数列表
实参:使用方法的时候传入的数据
- 方法的重写@Override:
子类继承父类以后,如果子类对父类的功能不满意,可以重写父类的方法
但是重写的时候需要注意如下的规则:两同两小一大
一大:子类方法的修饰符范围 >= 父类方法的修饰符范围–指的是访问控制符
两同:方法名相同,参数列表相同
两小: 子类方法的返回值类型 <= 父类方法的返回值类型【这个大小是继承关系,不是数值的大小】
子类方法抛出的异常类型 <= 父类方法抛出的异常类型
注意:如果父类方法的返回值类型是void,子类保持一致即可
注意:子类不可以重写父类的私有方法,还是因为不可见
重写的意义:是在不修改源码的前提下,进行功能的修改和拓展(OCP原则:面向修改关闭,面向拓展开放)
拓展:方法的递归 ,难度较大
权限修饰符的图示
12. 数组 Array 标志[ ]
- 数组的创建方式:
动态创建:需要指定数组的类型与长度即可
int[] a = new int[5];
静态创建:需要指定数组的类型与每一个具体元素(暗含了数组的长度)
int[] a = {1,2,3,4,5};
int[] a = new int[]{1,2,3,4,5};
- 我们需要通过数组的下标index来操作数组中的元素
数组下标从0开始,最大下标是数组的长度-1,a.length-1
比如:a[2]–表示的是数组的第3个元素,数组容易出现数组下标越界的问题,其实就是访问了不存在的位置
- 数组的打印
char[]底层做了处理,所以可以直接打印数组的元素内容,但是其他任何类型的数组想要查看具体元素需要:
使用数组的工具类Arrays.toString(数组名);
- 数组的长度
数组的长度指的是数组中元素的个数,数组一旦创建,长度不可改变,a.length是数组的一个属性
- 数组的创建过程,比如int[] a = {1,2,3,4,5};
1)在内存中开辟一块连续的内存空间,长度为5
- 给数组完成初始化的过程,给每个位置上的元素赋予对应类型的默认值,此处是0
3)数组完成初始化后,会给数组分配一个唯一的地址值
4)把数组的地址值交给引用类型int[]的变量a来保存
5)后面可以根据下标给数组的每个元素赋值,比如1,2,3,4,5对应的下标是a[0]。。。a[4]
- 数组的遍历
我们使用for循环遍历数组,所以for循环的循环变量代表的是数组的下标,最小值是0,最大值是a.length-1
注意数组下标越界的问题
- 数组的工具类Arrays
1)toString(数组名) : 打印数组中所有的元素
2)sort(数组名) :对数组中的元素进行排序
3)copyOf(原来数组的名字,新数组的长度)
如果新长度=旧长度–普通复制
如果新长度>旧长度–扩容
如果新长度<旧长度–缩容
注意:本方法并不会改变原来数组,而是创建指定长度的新数组
- 冒泡排序
1)如果有n个数,最多比较n-1轮
2)每一轮中如何比较?
从头开始,把所有数过一遍【相邻比较,位置不对就互换】
3)之前轮中比较出来的最大值,不需要参与下一轮的比较
4)写法:外层循环控制的是比较的轮数,内层循环控制的是比较的次数
拓展:可以了解一些其他的排序算法,比如二分排序,快速排序,归并排序,全排序
02 面向对象
1.面向对象与面向过程
两者都是一种思想,面向过程强调的是过程,凡事亲力亲为,面向对象,强调的是结果,我们是指挥者而不是执行者
Java就是一门面向对象的语言
类是抽象的,类似于设计图纸与模板,指的是一类事物
对象是具体的,是根据设计图纸制作出来一个个独立的实例,拥有自己独立的属性与功能
2.面向对象的封装
-
当我们想通过代码描述一种类型的时候,我们可以抽象这个类的特点与属性,把这些内容封装到一个类里
-
封装还可以封装属性,通过private关键字修饰后,属性只能在本类中使用,需要对外提供公共的getXxx()与setXxx()
-
封装还可以封装方法,如果一个方法被private修饰,外界想要调用这个功能,需要在本类中提供一个公共的方法调用这个私有方法
比如封装属性与方法,主要是对资源进行了访问限制,想让外界按照我们提供的方式来使用
3.面向对象的继承
-
继承的关键字是extends,格式:子类型 extends 父类型
-
继承相当于子类对象把父类的功能复制了一份,注意私有资源也可以继承,只不过私有不可见,所以不能使用
-
构造方法不能继承,原因:构造方法要求名字必须与本类类名一致,所以不能在子类中出现父类名字的构造方法
-
继承可以传递,爷爷的功能会传给爸爸,爸爸的功能会传给儿子
-
Java要求类必须单继承,也就是说,一个子类只能有一个父类,但是一个父类可以有多个子类
-
子类继承父类以后,也可以对父类的功能做修改【方法的重写】与拓展【在子类中添加新功能】
-
继承是一种is a ,强耦合的关系,所以谨慎使用
-
继承之后,子类可以直接使用父类的所有非私有资源
4.面向对象的多态
- 概念:
指同一个同一个对象,在不同时刻,代表的对象不一样,拥有多种形态。
- 作用:
可以把不同的子类对象都当作父类来看,进而屏蔽不同子类对象之间的差异,写出通用的代码,统一调用标准。
-
多态的前提:继承+重写
-
口诀1:父类引用指向子类对象:创建的子类对象的地址值交给父类类型的引用类型变量来保存
口诀2:编译看左边,运行看右边:只有父类中定义了这个资源才能通过编译,但运行时执行的是子类的方法体
- 多态中成员的使用:
1)成员变量:使用的是父类的
2)成员方法:由于存在重写,所以是父类的声明,子类的方法体
3)静态资源:静态资源属于类资源,是谁的,就返回谁的,而多态把自己看作父类类型,所以使用的是父类的
- 向上造型/转型 与 向下造型转型:
多态的应用存在两种转型方式,分别是:向上转型和向下转型。
向上转型:【最长使用的方式】
可以把不同的子类对象都当作父类来看
比如:父类Parent,子类Child
父类的引用指向子类对象:Parent p=new Child();
说明:向上转型时,子类对象当成父类对象,只能调用父类的功能,如果子类重写了父类中声明过的方法,方法体执行的就是子类重过后的功能。但是此时对象是把自己看做是父类类型的,所以其他资源使用的还是父类型的。
比如:花木兰替父从军,大家都把花木兰看做她爸,但是实际从军的是花木兰,而且,花木兰只能做她爸能做的事,在军营里是不可以化妆的。
向下转型(较少):子类的引用的指向子类对象,过程中必须要采取到强制转型。这个是之前向上造型过的子类对象仍然想执行子类的特有功能,所以需要重新恢复成子类对象
Parent p = new Child();//向上转型,此时,p是Parent类型
Child c = (Child)p;//此时,把Parent类型的p转成小类型Child
其实,相当于创建了一个子类对象一样,可以用父类的,也可以用自己的
说明:向下转型是由于父类对象不能调用子类的特有功能,所以为了使用子类的特有功能,需要再转回子类类型
比如:花木兰打仗结束,就不需要再看做是她爸了,就可以”对镜贴花黄”了
5.构造方法
-
格式:修饰符 类名(){ }
-
构造方法是与本类类名同名且没有返回值类型的方法
-
构造方法的作用是:用于创建对象,也就是说,每次创建对象时,都会触发这个类的构造方法
-
一个类默认存在一个无参构造,我们可以直接不传参数创建这个类的对象
-
如果这个类提供了其他的构造函数,默认的无参构造会被覆盖掉,所以需要手动提供无参构造
-
构造方法也存在重载的现象:无参构造 含参构造 全参构造【创建对象+属性赋值】
6.this与super
- this代表的是本类,也可以理解成:Son this = new Son();
1)当本类的成员变量与本类的局部变量同名时,可以通过this.变量名指定成员变量
2)实现构造方法间的调用:
this(); --代表的是调用本类的无参构造 this(参数);–代表的是调用本类的对应参数的构造方法
构造方法的这个调用语句,必须写在构造方法的第一行,而且构造方法的调用不能互相调用,只能选一个
注意:构造方法是new对象时被动触发的,不能自己主动调用,如果想要主动调用构造方法,需要按照上面的格式来写
-
super代表的是父类,也可以理解成:Father super = new Father();
-
当本类的成员变量与父类的成员变量同名时,可以通过super.变量名 指定父类的成员变量
-
实现父类构造方法的调用
子类构造函数的第一行默认存在一句super();
这代表着我们每次在创建子类对象时,会先调用父类的构造函数,再调用子类的构造函数
如果父类的构造函数被覆盖/删了,那我们必须:
提供父类的其他构造函数,并且在子类中通过==super(参数);==来调用父类的其他含参构造
这是由于,子类根本不关心调用的是父类的哪个构造函数,它只是根据语法需要调用父类的一个构造函数而已
7.对象的创建过程
我们现在可以在类中添加:属性 构造方法 方法 代码块 内部类。。。这些都是可选的,可以根据自己的业务来决定
比如:Phone p = new Phone();对象p的创建过程是:
-
在栈内存中开辟一块空间用来存放Phone类型的引用类型变量p,并把p压入栈底
-
在堆内存中开辟一块空间用来存放Phone类型的对象
-
给Phone类型的对象进行初始化,比如属性需要赋予对应类型的默认值
-
初始化完成后生成当前对象唯一的地址值并将地址值交给引用类型的变量p来保存
-
后续可以根据p中保存的地址找到对象,并对对象做操作
注意:对象与对象之间是互不影响的,因为各自都有自己独立的内存空间,比如我们修改p手机的颜色,p2手机并不受影响
注意:如果代码是:new Phone();这样表示创建的是匿名对象
匿名对象没有自己的名字,只能使用一次,并且一次只能调用一个资源
8.静态static
-
static是一个关键字,这个关键字可以修饰变量、方法、代码块、内部类
-
静态资源是类资源,随着类的加载而加载,只加载一次,并且优先于对象进行加载,直到类消失,它才会消失
-
由于静态资源是类资源,所以静态资源可以不通过对象,直接通过类名调用
-
静态资源全局被所有对象共享,值只有一份
-
由于静态比对象先加载,所以static不能与this或者super公用,因为有static的时候,还没有对象呢
-
静态资源只能调用静态资源,非静态资源不做限制,静态与非静态都能使用
9.final
-
final是一个关键字,表示最终的意思,可以用来修饰类 方法 属性
-
被final修饰的类是最终类,相当于叶子节点,不能被继承
-
被final修饰的方法是这个方法的最终实现,不能被重写
-
被final修饰的属性是常量,值不能被修改,格式:final 数据类型 常量名 = 值;
注意:常量定义时必须赋值,否则报错
10.代码块与它们的顺序
- 静态代码块 static{ }
位置:类里方法外
执行时机:在类加载时就加载,并且只加载一次
作用:一般用于资源的初始化
- 构造代码块 { }
位置:类里方法外
执行时机:创建对象时才会被触发,并且每次创建对象时都会执行,而且优先于构造方法执行
作用:用于提取所有构造方法的共性功能
- 局部代码块 { }
位置:在方法里
执行时机:调用这个局部代码块所在的方法时才会执行
作用:用于限制变量的作用范围,作用范围越小越好
- 顺序:静态代码块->构造代码块->构造方法【创建对象】->普通方法【如果方法里有局部代码块,执行局部代码块】
11.抽象
-
抽象的关键字是abstract,抽象其实代表的是有一部分“不确定”
-
被abstract修饰的方法是抽象方法,抽象方法没有方法体
-
被abstract修饰的类是抽象类,抽象类有如下特点:
1)抽象类的方法不做限制:全是抽象方法/全是普通方法/普通方法与抽象方法混合
2)如果一个类中包含了一个抽象方法,那么这个类必须被声明成一个抽象类
3)如果一个类全是普通方法,却还要声明成抽象类,是因为抽象类不可以实例化,不想让外界创建它的对象
- 如果一个子类继承了一个抽象父类,那么有两种方案:
1)抽象子类:“躺平”,不去实现/实现部分父类中的抽象方法
2)普通子类:“父债子偿”,实现父类中的所有抽象方法
-
抽象类中是可以定义成员变量的
-
抽象类是有构造方法的,这个构造方法是为了子类创建对象时调用
-
抽象常用于多态
-
抽象是后天重构的结果
-
abstract关键字通常不与private static final一起使用
12.接口
-
接口不是类,定义接口的关键字是interface
-
如果一个类想要实现接口,格式:实现类 implements 接口 ,注意实现类还是两种方案【抽象子类/普通实现类】
-
在Java8中,接口里所有的方法都是抽象方法
-
接口里只有静态常量,没有普通变量的,变量定义时,默认拼接 public static final
-
接口中抽象方法的定义也可以简写,默认拼接public abstarct
-
接口中是没有构造方法的,如果一个实现类没有明确指定父类,构造用的是Object();
-
我们可以把接口理解成是一个全部都是抽象方法的抽象类
-
普通的类包含的是一类事物的属性与功能,而接口更多的是规则的制定
-
接口是可以多继承【一个接口可以继承多个接口】
-
接口是可以多实现【一个类可以实现多个接口】
-
接口其实是对外暴露的一套规则,是一套开发规范,以后要面向接口进行编程
-
接口降低了程序的耦合性,更加方便程序的功能拓展
-
接口是先天设计的结果,这样可以省去后续多次重构的资源浪费
-
接口不能实例化
13.接口与类的复杂关系
- 类与类的关系
继承关系,只支持单继承
比如:class A extends B,A是子类,B是父类,子类具备父类的所有功能
- 类与接口的关系
实现关系,既可以单实现,也可以多实现
比如:class A implements Inter1{}
比如:class A implements Inter2,Inter3{}
A是实现类,Inter1,Inter2,Inter3是被实现的接口
注意1:实现类去实现接口必须实现接口中的所有抽象方法,如果有任何一个没有实现,就得声明成抽象子类
注意2:创建实现类对象时,一般使用实现类对象,而不是多态对象,因为效果一样
- 接口与接口的关系
继承关系,既可以单继承,也可以多继承
比如:interface A extends Inter1{}
比如:interface A2 extends Inter2,Inter3{}
A,A2是子接口,Inter1,Inter2,Inter3是被继承的父接口
注意接口A2的实现类需要实现接口A2继承自Inter2和Inter3的所有抽象方法
- 抽象类与接口的区别
抽象类是一个特殊的类,使用class定义,特殊在这个类中可以定义没有方法体的方法(抽象方法)
接口可以理解成一个特殊的抽象类,特殊在接口中所有的方法都是抽象方法,但注意接口不是类,用interface定义
抽象类中有构造方法,为了给子类创建对象时调用
接口中没有构造方法的,子类调用的是父类的构造方法
接口可以多继承,但抽象类只能单继承
抽象类可以定义普通的成员变量,但接口只能定义静态常量
接口与抽象类均不可以实例化/创建对象
抽象是后天重构的结果,接口是先天设计的结果
14.内部类
-
我们可以把内部类看作是外部类的一个特殊的资源
-
内部类可以直接使用外部类的所有资源,包括私有资源
-
外部类如果想要使用内部类的资源,需要创建内部类的对象才能使用
-
对象的普通创建方式:
/外部类名.内部类名 对象名 = 外部类对象.内部类对象/
Outer.Inner oi = new Outer().new Inner();
成员内部类
位置:类里方法外
1)被private修饰
被私有化的内部类在main()中是没有办法直接创建其对象的
可以在私有内部类所处的外部类中,创建一个公共的方法供外界调用,这个方法用来返回创建好的私有内部类对象
- 被static修饰
静态内部类可以不创建外部类对象,直接创建静态内部类对象,格式:Outer3.Inner3 oi = new Outer3.Inner3();
如果静态内部类中还有静态方法,那么我们可以不创建对象
直接通过链式加载的方式调用:Outer3.Inner3.show2();//表示通过外部类名直接找到静态内部类,再找到静态方法
局部内部类
位置:方法里
直接创建外部类对象,调用局部内部类所处的方法,并不会触发局部内部类的功能
需要在外部类中创建局部内部类的对象并且进行调用局部内部类的功能,才能触发内部类的功能
匿名内部类
位置:可运行代码中,比如 main()中
匿名内部类通常与匿名对象【没有名字的对象】一起使用
格式:new Inter1(){ 我这个大括号其实是一个匿名内部类,我来实现方法 }.eat();
如果只是想使用一次接口/抽象类的某个功能,可以使用匿名内部类
匿名内部类+匿名对象的功能:创建实现类+实现方法+方法功能的一次调用【功能三合一】
15.异常
- 异常的继承结构
异常层次结构中的根是Throwable
Error:目前我们编码解决不了的问题
Exception:异常
编译异常:未运行代码就报错了,强制要求处理
运行时异常RunTimeException:运行代码才报错,可以通过编译,不强制要求处理
-
异常的解决方案
-
捕获处理try-catch–自己解决
-
格式:
try{
可能会出现异常的代码
}catch(预测的异常类型 异常的名字){
预先设计的,捕获到异常的处理方案
}finally{
异常处理结构中一定会被执行到的代码块,常用来关流
}
-
向上抛出throws–交给别人解决,在方法定义的两个小括号之间throws,可抛出多个异常,用逗号隔开
-
不能直接把异常抛给main(),因为调用main()是JVM,没人解决了
注意:是否抛出异常取决于自己的业务,比如暂时不处理或者处理不了需要交给别人处理
03 基础API
1.Object
-
是所有类的超类,Java中的类都直接或者间接的继承了Object
-
如果一个类没有明确指定父类,那么默认继承Object
-
Object处于java.lang包之下,不需要导包可以直接使用
-
toString()–我们日常使用最频繁的打印语句底层就调用了这个方法
如果没有重写这个方法,使用的是Object的默认实现,打印的是对象的地址值
如果重写以后,以重写的逻辑为准,比如String打印的是串的具体内容,比如ArrayList,打印的是[集合元素]
- hashCode()–用于返回对象对应的哈希码值
如果是一个对象多次调用这个方法,返回的是同一个哈希码值
如果是不同的对象调用这个方法,应该返回的是不同的哈希码值
- equals()–用于比较当前对象与参数对象是否相等
重写之前的默认实现比较的是两个对象的地址值
重写之后取决于重写的逻辑,比如String比较的是两个串的具体内容,比如自定义对象比较的是类型+属性值
- equals()与hashCode()应该保持一致【要重写都重写】
解释:equals()底层默认实现比较的是==比较,地址值,重写后我们一般比较的是对象的类型+属性值
hashCode()不同的对象生成的哈希码值不同,那么与equals()的逻辑不匹配,所以也应该重写
重写后,是根据对象的类型与属性值来生成哈希码值,这样二者就一致了
2.String
-
String底层维护的是一个char[],而且String不可变,因为源码中的数组被final修饰了
-
创建方式:
char[] vlaues = {‘a’,‘b’,‘c’}; String s = new String(values);
String s = “abc”;有高效的效果,因为串存在堆中的常量池,第二次使用时就不再新建了
- 常用方法:
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
2.StringBuilder与StringBuffer
String的:
- 特点:
创建之后长度内容是不可变的,每次拼接字符串,都会产生新的对象
- 优缺点:
优点:String类提供了丰富的关于操作字符串的方法,比如:拼接、获取对应下标处的字符、截取子串等等
缺点:在进行字符串拼接+=的时候,效率比较低
- String转StringBuilder:
String s = “abc”; StringBuilder sb = new StringBuilder(s);
StringBuilder的:
- 特点:
StringBuilder是一个长度可变的字符串序列,在创建的时候,会有一个长度为16的默认空间
当拼接字符串的时候,是在原对象的基础之上进行拼接,如果长度不够就扩容
所以StringBuilder在创建之后,对应的操作一直是用一个对象
- 创建方式:
StringBuilder sb = new StringBuilder();//创建一个长度为16的StringBuilder对象
StringBuilder sb = new StringBuilder(“abc”);//以指定字符串内容为“abc”的方式创建一个StringBuilder对象
- 优缺点:
优点:在拼接的时候,不会产生新的对象,就避免了因为拼接频繁生成对象的问题,提高了程序的效率,使用的是append()
缺点:对于字符串的操作,不太方便
- StringBuilder转String:
StringBuilder sb = new StringBuilder();
sb.append(“abc”);
String s = sb.toString();
总结一句话,拼接多用StringBuilder,用完转回String用String丰富的方法
3.包装类
- 基本类型只存值,也没有丰富的功能
所以包装类型是对基本类型做了包装,并提供了很多方便的方法,所以包装类的对象是引用类型的对象
- 创建方式:
Integer i1 = Integer.valueOf();数据只要在-128~127有一个高效的效果
Integer i2 = new Integer(4);没有高效的效果,只是创建了一个包装类的对象
4.自动装箱与自动拆箱
- 自动装箱:
编译器会自动把基本类型int5,包装成包装类型Integer
然后交给Integer类型的引用类型变量i3来保存
自动装底层发生的代码:Integer.valueOf(5)
valueOf()的方向:int–>Integer
- 自动拆箱:
编译器会自动把包装类型的i1拆掉”箱子“,变回基本类型的数据127
然后交给基本类型int的变量i4来保存
自动拆箱底层发生的代码:i1.intValue();
intValue()的方向:Integer-> int
package cn.tedu.api;
/本类用于测试自动装箱与自动拆箱/
public class TestNumber2 {
public static void main(String[] args) {
//1.定义包装类型的数据
Integer i1 = new Integer(127);
Integer i2 = Integer.valueOf(127);
//2.现在的方式:
Integer i3 = 5;//不会报错,这个现象就是自动装箱
int i4 = i1;//不会报错,这个现象就是自动拆箱
}
}
04 高级API
04-1 IO流
-
学习方式:学习抽象父级的公共方法 学习子类流对象的创建方式
-
流的分类
根据方向:输入流 输出流
根据操作单位:字节流 字符流
- 字节输入流InputStream:
InputStream--抽象父类--不能实例化
FileInputStream--文件字节输入流-FIS
BufferedInputStream--高效字节输入流-BIS
FIS in = new FIS(new File(路径));
FIS in = new FIS(路径);
BIS in = new BIS( new FIS(new File(路径)));
BIS in = new BIS(new FIS(路径));
字节输出流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(路径));
字符输入流Reader:
Reader--抽象父类--不能实例化
FileReader--文件字符输入流-FR
BufferedReader--高效字符输入流-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(路径));
字符输出流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,表示追加
4. 序列化与反序列化
序列化与反序列化的作用就是对象的保存与传输
序列化:把内存中的对象通过序列化流输出到磁盘中(比如文件里),使用的流是ObjectOutputStream【把数据写出到文件】
反序列化:通过反序列化流将磁盘中的数据恢复成对象,使用的流是ObjectInputStream【把之前写到文件里的数据读到程序中】
注意1:一个类的对象如果想被序列化,那么这个类必须实现可序列化接口
实现这个接口的目的是相当于给这个类做了一个标记,标记可以序列化
注意2:序列化时会自动生成一个UID,表示当前序列化输出的对象的版本信息
反序列化时会拿着当前的UID与之前序列化输出的UID做比较,一致,反序列化成功,不一致,报错
注意3: 所以,标准操作是一次序列化对应一次反序列化
如果目标对象所在的类没有做任何修改,一次序列化也可以对应多次反序列化(根本原因是UID没变)
04-2 集合
- 泛型
泛型通常与集合一起使用,用来约束集合中元素的类型
泛型< type >必须写引用类型而不是基本类型
泛型方法 public static ==< E > == void get(E[] e){},两处位置都出现了泛型,缺一不可
2.集合被称作Collection,是一个可以存放多个数据的容器,而且集合中提供了丰富的方法来操作集合中的元素
是集合层次的根接口,学习抽象父级的公共方法
- Collection集合方法总结
单个集合的操作:
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() 返回本集合中元素的个数
Object[] toArray() 将本集合转为数组
集合间的操作:
boolean addAll(Collection<> c) 将c集合中的所有元素添加到本集合中
boolean containsAll(Collection<> c) 判断本集合是否包含c集合的所有元素
boolean removeAll(Collection<> c) 移除本集合中属于参数集合c的所有元素
boolean retainAll(Collection<> c) 保留本集合与参数集合c的公共元素
集合的迭代:
Iterator iterator() 返回本集合的迭代器
-
List接口的特点
-
List集合是有下标的
-
List集合是有顺序的
-
List集合可以存放重复的数据
单个集合的操作:
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处的元素
集合间的操作与集合的迭代
boolean addAll(int index, Collection<> c) 将参数集合c中的所有元素,插入到本集合中指定的下标index处
ListIterator listIterator() 返回此列表元素的迭代器,这个是List自己的,不太常用,可以逆序迭代
- List接口的两个常用实现类
ArrayList的特点:
1)底层的数据结构是数组,内存空间是连续的
2)元素有下标,通常可以根据下标进行操作
3)增删操作比较慢,查询操作比较快【数据量大时】
LinkedList的特点:
1)底层的数据结构是链表,内存空间是不连续的
2)元素有下标,但是通常首尾节点操作比较多
3)增删操作比较快,查询操作比较慢【数据量大时】
注意:LinkedList查询慢也不是都慢,首尾操作还是比较快的
简单方法:
void addFirst(E e) 添加首元素
void addLast(E e) 添加尾元素
E removeFirst() 删除首元素
E removeLast() 删除尾元素
E getFirst() 获取首元素
E getLast() 获取尾元素
E element() 获取首元素
功能一致但是名字不太好记的方法:
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接口的特点
-
map集合的结构是:键值对、KEY与VALUE、Map.Entry<K,V>的映射关系
-
map中key值不允许重复,如果重复,对应的value会被覆盖
-
map中的映射关系是无序的
-
map没有自己的迭代器,所以迭代时通常需要转成set集合来迭代
简单方法:
void clear() 清空集合
boolean equals(Object o) 判断集合对象与参数o是否相等
int hashCode() 返回本集合的哈希码值
boolean isEmpty() 判断集合是否为空
int size() 返回本集合中键值对的个数
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集合的所有映射关系(键值对)
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集合的元素是无序的
-
set集合可以存null值,并且null最多有一个
-
我们自定义对象如果想去重,需要在自定义类中添加重写的equals()与hashCode()
-
集合学习的方法
学习父级的公共方法,学习子类的创建方式,学习各种集合的特点
-
关于List大多都是与下标有关的操作
-
关于Set通常都是去重的操作
-
关于map通常都是映射关系,也就是键值对
-
API要常练习,方法互相之间没有任何关系,用哪个,查哪个
04-3 进程与线程
-
程序:数据与指令的集合,而且程序是静态的
-
进程:运行中的程序,给程序加入了时间的概念,不同时间进程有不同的状态,进程是动态的,代表OS中正在运行的程序
进程有独立性,动态性,并发性
-
并行:相对来说资源比较充足,多个CPU同时并发处理多个不同的进程
-
并发:相对来说资源不太充足,多个资源同时抢占公共资源,比如CPU
-
线程:线程是OS能够进行运算调度的最小单位
一个进程可以拥有多个线程,当然,也可以只拥有一个线程,只有一个线程的进程称作单线程程序
注意:每个线程也有自己独立的内存空间,当然也有一部分共享区域用来保存共享的数据
- 线程的几种状态以及线程状态之间的切换
1)新建状态:创建线程对象,申请PCB,对应的是new线程对象
2)就绪状态/可运行状态:万事俱备,只欠CPU,刚刚创建好的线程对象所有资源已经准备好,并且加入到了就绪队列之中
唯有等待操作系统的调度,只要分配了CPU,也就是时间片,当前线程可立即执行,对应的是start()
注意:调用start()并不会立即执行线程对象,这个是由OS的调度规则决定的。我们控制不了
3)执行/运行状态:就绪队列中的线程对象被OS选中,分配了时间片,正在执行
注意:只有就绪状态才能变成运行状态
4)阻塞状态:线程在执行过程中遇到了问题,比如锁阻塞、休眠阻塞、等待阻塞…
注意:我们的阻塞状态,等问题解决了以后/获取了临界资源【要抢占的公共资源】后
是加入到就绪队列中的,转为就绪状态,而不是转为运行状态直接执行
5)终止状态:线程成功执行完毕,释放资源,归还PCB
6)线程的挂起:正在运行中的线程,由于CPU分配的时间片已经用完,所以需要冻结当前线程运行的状态与各项信息
把它插入到就绪队列中,直到下次这个线程被调度执行时,重新恢复现场,继续执行
- 多线程编程实现方案一:extends Thread继承方式
1)自定义一个多线程类用来继承Thread类
2)重写run()里的业务【这个业务是自定义的】
3)创建线程对象【子类对象】
4)通过刚刚创建好的自定义线程类对象调用start()
注意1:不能调用run(),因为这样调用只会把run()看作一个普通的方法,并不会以多线程的方式启动程序
而且调用start()时,底层JVM会自动调用run(),执行我们自定义的业务
注意2:我们除了可以调用默认的父类无参构造以外,还可以调用Thread(String name),给自定义的线程对象起名字,相当于super(name);
- 多线程编程实现方案二:implements Runnable 实现方式
1)自定义一个类实现接口Runnable
-
实现接口中唯一一个抽象方法run()
-
创建接口实现类的对象,这个对象是作为我们的目标业务对象【因为这个自定义类中包含了我们的业务】
4)创建Thread类线程对象,调用的构造函数是Thread(Runnable target)
5)通过创建好的Thread类线程对象调用start(),以多线程的方式启动同一个业务target
注意1:由于Runnable是一个接口,无法创建对象,所以我们传入的目标业务类,也就是接口实现类的对象
注意2:只有调用start()才能把线程对象加入到就绪队列中,以多线程的方式启动,但是:
接口实现类与接口都没有这个start(),所以我们需要创建Thread类的对象来调用start(),并把接口实现类对象交给Thread(target);
大家可以理解成“抱大腿”,创建的是Thread类的线程对象,我们只需要把业务告诉Thread类的对象就好啦
使用方式二的优势:
1)耦合性不强,没有继承,后续仍然可以继承别的类
2)采用的是实现接口的方式,后续仍然可以实现别的接口
3)可以给所有的线程对象统一业务,业务是保持一致的
4)面向接口编程,有利于我们写出更加优雅的代码
- 多线程编程实现方案三:Executors 创建线程池的方式
1)创建线程池的工具类:Executors.newFixedThreadPool(int n);可以创建包含最多n个线程的线程池对象
2)创建好的线程池对象:ExecutorService
使用pool.excute()来讲线程池中的线程以多线程的方式启动,每次调用都会将一个线程对象加入到就绪队列之中
这个线程池对象负责: 新建/启动/关闭线程,而我们主要负责的是自定义的业务
注意:线程池是不关闭的,实现的效果就是线程池中线程对象的随取随用,这样就避免了频繁的创建与销毁线程,不会造成资源浪费
3)合理利用线程池可以拥有的优势:
1. 降低系统的资源消耗:减少系统创建与销毁线程对象的次数,每个线程都可以重复利用,执行多次任务
2. 提高响应速度:当任务到达时,任务可以不用等待线程创建就能立即执行
3. 提高线程的可管理性:可以根据系统的承受能力,调整线程池中线程的数目
防止因为创建多个线程消耗过多的内存导致服务器的崩溃
【每个线程大约需要1MB的内存,线程开的越多,消耗的内存也就越大,最后死机】
- 多线程数据安全隐患的解决方案
1)出现数据安全问题的原因:多线程程序 + 多个线程拥有共享数据 + 多条语句操作共享数据
2)解决:加锁synchronized同步关键字
1. 同步方法【不太常用】,格式:在方法的定义上加synchronized
2. 同步代码块,格式:
synchronized(唯一的锁对象){
可能会出现数据不安全问题的所有代码
}
注意1:锁对象必须唯一!!!
比如:如果是实现接口的方式,只需要创建一个接口实现类对象。而这个对象当做的是目标业务对象,类中定义的锁对象自然也只有一个
比如:如果是继承Thread类的方式,我们需要创建多个子类对象作为线程对象
那这个时候不同的线程对象间应该共享同一把锁,所以需要把锁设置为静态,被全局所有对象共享
而且建议,此种方式使用的锁对象是本类的字节码对象:类名.class
注意2:加锁时,同步代码块的范围需要考虑, 不能太大,太大效率太低;也不能太小,太小锁不住
注意3:加锁时,锁对象的类型不做限制,只要保证锁对象唯一即可
同步与异步
异步:是多个线程抢占资源的效果,不排队,所以效率高,但是数据不安全
同步:每次只有一个线程独占资源,排队,所以效率低,但是安全,为了安全必要应该牺牲一部分资源
04-4 注解
1.JDK自带的注解(5个)
要求大家掌握的是@Override注解,这个注解可以加在方法上,用来表示这是一个重写的方法
2.元注解5个
元注解是用来定义其他注解的注解,也就是说,注解的语法与JAVA不同,是靠注解来定义的
-
定义注解的格式:@interface 注解名
-
可以根据元注解对注解进行设置:
要求大家掌握的是
表示被描述的注解可以使用的位置:值可以多选
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
表示被描述的注解的声明周期:注意值只能3选1
@Retention(RentionPolicy.RUNTIME/SOURCE/CLASS)
3.自定义注解
- 我们也可以根据自己的需求来定义个性化的注解
使用的是@interface 注解名来定义的,主要使用的就是上面的两个元注解
- 除此之外,我们还可以给注解加功能,比如注解的属性:
格式:属性类型 属性名(); 比如:int age();
注意:定义了注解的普通属性以后,使用注解时必须给属性赋值,格式:@Rice(age=10)
如果给属性设置了默认值,那么使用注解时就不需要给属性赋值了,格式:int age() default 0;
3.我们还可以给注解添加特殊的属性value,注意这个属性名字必须是value,类型不作限制
注意:特殊属性如果不设置默认值,使用注解时也需要赋值,不过赋值可以简写,比如@Rice(“apple”)
特殊属性赋予默认值后,就可以直接使用注解了,赋予默认值的格式:String value() default “apple”;
注意:如果有多个属性,并且都没有赋予默认值,那么使用注解时的格式:@Rice(value=“apple”,age=10)
04-5 设计模式
-
概念:是一些前人总结出来的值得学习的编程“套路”,设计模式一共有23种
-
单例设计模式:确保代码中本类的实例只有一个
-
实现思路:
方案一:饿汉式
1)把本类的构造方法私有化–为了不让外界调用构造函数来创建对象
2)通过本类的构造方法创建对象,并把这个对象也私有化,为了防止外界调用
3)提供公共的全局访问点向外界返回本类的唯一的一个对象
注意:公共方法需要设置成静态
–需要跳过对象,通过类名直接调用这个返回本类对象的公共方法
对象也需要设置成静态
的–这个对象需要在静态方法中被返回,而静态只能调用静态
方案二:懒汉式
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://i-blog.csdnimg.cn/blog_migrate/ce866026bfe166d733e4d50bd8b52c21.jpeg)
难道这样就够了吗?不,远远不够!
提前多熟悉阿里往年的面试题肯定是对面试有很大的帮助的,但是作为技术性职业,手里有实打实的技术才是你面对面试官最有用的利器,这是从内在散发出来的自信。
备战阿里时我花的最多的时间就是在学习技术上,占了我所有学习计划中的百分之70,这是一些我学习期间觉得还是很不错的一些学习笔记
我为什么要写这篇文章呢,其实我觉得学习是不能停下脚步的,在网络上和大家一起分享,一起讨论,不单单可以遇到更多一样的人,还可以扩大自己的眼界,学习到更多的技术,我还会在csdn、博客、掘金等网站上分享技术,这也是一种学习的方法。
今天就分享到这里了,谢谢大家的关注,以后会分享更多的干货给大家!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
(value=“apple”,age=10)
04-5 设计模式
-
概念:是一些前人总结出来的值得学习的编程“套路”,设计模式一共有23种
-
单例设计模式:确保代码中本类的实例只有一个
-
实现思路:
方案一:饿汉式
1)把本类的构造方法私有化–为了不让外界调用构造函数来创建对象
2)通过本类的构造方法创建对象,并把这个对象也私有化,为了防止外界调用
3)提供公共的全局访问点向外界返回本类的唯一的一个对象
注意:公共方法需要设置成静态
–需要跳过对象,通过类名直接调用这个返回本类对象的公共方法
对象也需要设置成静态
的–这个对象需要在静态方法中被返回,而静态只能调用静态
方案二:懒汉式
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-HnSlDyM8-1713291664253)]
[外链图片转存中…(img-2xx2i4vX-1713291664254)]
[外链图片转存中…(img-iunoeTPq-1713291664254)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://i-blog.csdnimg.cn/blog_migrate/ce866026bfe166d733e4d50bd8b52c21.jpeg)
难道这样就够了吗?不,远远不够!
提前多熟悉阿里往年的面试题肯定是对面试有很大的帮助的,但是作为技术性职业,手里有实打实的技术才是你面对面试官最有用的利器,这是从内在散发出来的自信。
备战阿里时我花的最多的时间就是在学习技术上,占了我所有学习计划中的百分之70,这是一些我学习期间觉得还是很不错的一些学习笔记
我为什么要写这篇文章呢,其实我觉得学习是不能停下脚步的,在网络上和大家一起分享,一起讨论,不单单可以遇到更多一样的人,还可以扩大自己的眼界,学习到更多的技术,我还会在csdn、博客、掘金等网站上分享技术,这也是一种学习的方法。
今天就分享到这里了,谢谢大家的关注,以后会分享更多的干货给大家!
[外链图片转存中…(img-NtTpzjPo-1713291664255)]
[外链图片转存中…(img-I4xlEcvh-1713291664255)]
[外链图片转存中…(img-Ov7n588v-1713291664255)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!