Java基础
一个想当厨子的码农 Lv2

Java语言

1. 语言基础

1.1 基础语法

  • 数据类型

数据类型分为两类:基本数据类型、引用数据类型。

1
2
3
4
5
6
7
8
9
Java中的基本数据类型只有**四类八种**

整数型:byte、short、int、long

short占两字节

int 占用四个字节

long 占用八个字节
  • 浮点型
1
2
浮点型有两种:float、double
float占用四位,double占用八位
  • 字符型
1
字符型为char,char类型是一个单一的16位Unicode字符。
  • 布尔型
1
2
3
 The boolean data type has only two possible values: true and false. Use this data type for simple flags that track true/false conditions. This data type represents one bit of information, but its "size" isn't something that's precisely defined.

翻译:布尔数据类型只有两种可能的值:true和false。使用这个数据类型用于追踪true/false状况的简单标识。这个数据类型表示1bit的信息,但是它的”大小“没有被精确定义。

image-20231228151136962

  • 引用数据类型
1
只要不是基本数据类型就是引用数据类型。
  • 自动类型转换(隐式)
1
转换规则: 数据范围必须是**从小到大**,即由小的转换成大的,自动转换。
  • 强制类型转换(显式)
1
2
3
4
5
6
注意事项:
1. 强制类型转换一般不推荐使用,因为有可能发生精度损失、数据溢出。
2. byte/short/char这三种类型都可以发生数学运算,例如加法“+”。
3. byte/short/char这三种类型在运算的时候都会首先提升成为int类型,然后再计算。
例:byte+byte->int+int->int
4. boolean类型不能发生数据类型转换
  • 运算符的优先级
1
单目(一元)>算术运算符>移位>关系>位>条件(三元)>赋值
  • 基础语法
1
2
3
4
5
6
7
8
9
10
1. 大小写敏感: Java是大小写敏感的语言,例如Hello和hello是不同的,这其实就是Java的字符串表示方式。
2. 类名:类名使用 UpperCamelCase 风格,但以下情形例外: DO / BO / DTO / VO / AO /
PO / UID 等。
正例: ForceCode / UserDO / HtmlDTO / XmlService / TcpUdpDeal / TaPromotion
反例: forcecode / UserDo / HTMLDto / XMLService / TCPUDPDeal / TAPromotion
3. 包名:包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用
单数形式,但是类名如果有复数含义,类名可以使用复数形式。
正例: 应用工具类包名为 com.alibaba.ei.kunlun.aap.util、类名为 MessageUtils(此规则参考 spring 的框架结构)
4. 方法名、变量名、参数名:都统一使用 lowerCamelCase 风格
正例: localValue / getHttpMessage() / inputUserId
  • 方法
  • 构造方法
1
2
3
4
5
6
7
作用:用来创建对象的
注意事项:
1. 构造方法的名称必须和所在类的名称完全一样,就连大小写也要一样
2. 构造方法不要写返回值类型,连void都不写
3. 构造方法不能return一个具体的返回值
4. 如果没有编写任何构造方法,那么编译器会默认分配一个构造方法
5. 一旦编写了一个构造方法,那么编译器就不会再分配任何构造方法
  • 静态方法
1
静态方法又叫类方法。
  • 非静态方法(成员方法)
1
2
3
4
5
方法之间的调用注意事项:
1. 同类型的方法之间可以直接调用
2. 静态方法调用非静态方法需要先new一个对象,然后通过对象进行调用。
3. 非静态方法可以直接调用静态方法。

  • 方法重载
1
2
3
4
5
6
7
8
9
多个方法的名称相同,但是参数列表不一样。
方法重载与下列因素相关:
1. 参数个数不同
2. 参数类型不同
3. 多参数类型顺序不同
方法重载与下列因素无关:
1. 与参数名称无关
2. 与方法的返回值类型无关
3.
  • 参数传递机制
1
2
Java的参数传递机制都是:值传递

  • 数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java的内存划分成为5个部分:
1. 栈内存(stack):存放的都是方法中的局部变量。 方法的运行一定要在栈中运行。
局部变量: 方法的参数、方法{}内部的变量
2. 堆内存(heap): 凡是new出来的东西都在堆当中。
堆内存里面的东西都有一个地址值:16进制
堆内存中的数据都有默认值。规则:
整数 默认0
浮点数 默认0.0
字符 默认'\u0000'
布尔 默认false
引用类型 默认null
3. 方法区(): 存储.class相关信息,包含方法的信息。

4. 本地方法栈(): 与操作系统相关
5. 寄存器(): 与cpu相关

1.2 面向对象

  • 特点
1
面向对象思想是一种更符合我们思考习惯的思想,它可以将复杂的事情简单化,并将我们从执行者变成指挥者。

面向对象语言包含三大基本特征:

  • 封装

    就是将一些细节信息隐藏起来,对外界不可见。

    1. 方法就是一种封装
    2. private关键字也是一种封装

    一旦使用private进行修饰,那么本类当中仍然可以随意访问,但是超出了本类范围之外就不能再直接访问了。

    可以使用getter/setter方法间接访问private成员变量。

  • 继承

​ 继承是多态的前提,如果没有继承,就没有多态。

​ 继承主要解决的问题就是:共性抽取

  **变量重名的解决方法**

​ 局部变量: 直接写成员变量名

​ 本类的成员变量: this.成员变量名

​ 父类的成员变量: super.成员变量名

 **重写(override)**    

​ 概念:在继承关系中,方法的名称一样,参数列表一样。

​ 方法重写(override):方法名称一样,参数列表也一样。(覆盖重写)

​ 方法重载(overload):方法名称一样,参数列表不一样。

 **覆盖重写的注意事项:**

          1. 必须保证父子类之间的方法名称相同,参数列表也相同。
          2. 子类方法的权限必须大于等于父类方法的权限修饰符。

​ public>protected>(default)>private

​ 备注:(default)不是关键字default,而是什么修饰符也不用。

继承关系中,父子类构造方法的访问特点:

1. super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造

​ 注意事项:

​ 子类必须调用父类构造方法,不写则默认super(),写了则用写的指定的super调用,super只能有一个,还必须是第一个。

Java继承的三大特点

  1. Java语言是单继承的,即一个类的直接父类只能有唯一一个。
  2. Java语言可以多级继承。
  3. 一个子类只能有一个父类,但是一个父类可以有多个子类。

如果父类当中的方法不确定如何进行{}方法体实现,那么这就应该是一个抽象方法。

抽象类:抽象方法所在的类必须是抽象类才行。在class之前写上abstract即可。

1
2
3
4
public abstract class Animal{
//这是一个抽象方法,代表吃东西,但是具体吃什么不确定。
public abstract void eat();
}

如何使用抽象类和抽象方法

  1. 不能直接创建new抽象类对象

  2. 必须用一个子类来继承抽象父类。

  3. 子类必须覆盖重写抽象父类当中的所有抽象方法。

    覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号

    1. 创建子类对象进行使用。
    1
    2
    3
    4
    5
    6
    public  class Cat extends Animal{
    @Override
    public void eat(){

    }
    }
  • 多态

一个对象拥有多种形态,这个就叫对象的多态性。

代码当中体现多态性,其实就是一句话,父类引用指向子类对象。

格式:

父类名称 对象名 = new 子类名称();

或者

接口名称 对象名 = new 实现类名称();

访问成员变量和方法的规则:

访问成员方法:等号右边new的谁,优先用谁,没有则向上找。

直接通过对象名称访问成员变量:等号左边是谁,优先用谁,没有则向上找。

间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找。

对象的转型

  1. 对象的向上转型,其实就是多态写法:

格式:父类名称 对象名 = new 子类名称();

  1. 对象的向下转型,其实是一个【还原】的动作

格式:子类名称 对象名 = (子类名称) 父类对象

注意事项:

  1. 必须保证对象本来创建的时候就是A,才能向下转型成为A。
  2. 如果创建的对象本来不是A,非要向下转型为A就会报错。

四种权限修饰符

内部类

  1. 成员内部类
  2. 局部内部类(包含匿名内部类)

成员内部类的定义格式:

1
2
3
4
5
6
修饰符 class 外部类名称{
修饰符 class 内部类名称{

}
}
注意:内用外,随意访问; 外用内,需要内部类对象。

局部内部类的定义格式:

1
2
3
4
5
6
7
8
修饰符 class 外部类名称{
修饰符 返回值类型 方法名称(参数列表){
class 局部内部类名称{

}
}

}

匿名内部类的定义格式:

1
2
3
4
5
6
接口名称 对象名称 =  new 接口名称(){
覆盖重写所有抽象方法
};
注意事项:
1. 匿名内部类在创建对象的时候只能使用唯一一次

  • 类和对象

什么是类

  • 类:是一组相关属性和行为的集合。

现实中,描述一类事物:

  • 属性:就是该事物的特征信息。(是什么)(成员变量)

  • 行为:就是该事物能够做什么。(成员方法)成员方法不要写static关键字。

什么是对象

  • 对象:是一类事物的具体体香。对象是类的一个实例。

类与对象的关系

  • 类是对一类事物的描述,是抽象的。
  • 对象是一类事物的实例,是具体的。
  • 类是对象的模板,对象是类的实体。

成员变量和局部变量的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. 定义的位置不一样
局部变量:在方法的内部
成员变量:在方法的外部,直接写在类当中。

2. 作用范围不一样
局部变量:只有方法当中才可以使用
成员变量:整个类全可以通用

3. 默认值不一样
局部变量:没有默认值,如果要想使用,必须手动赋值
成员变量:如果没有赋值,会有默认值,规则和数组一样。

4. 内存的位置不一样
局部变量:位于栈内存
成员变量:位于堆内存

5. 生命周期不一样
局部变量:随着方法进栈而生,随着方法出栈而亡
成员变量:随着对象创建而生,随着对象被垃圾回收而亡。
  • 对象数组

数组有一个缺点:一旦创建,程序运行期间长度不可以发生改变。

  • 字符串
1
2
3
4
5
6
7
8
9
10
1. 字符串的内容不可变
2. 字符串可以共享使用
3. 字符串效果上相当于char[]字符数组,但是底层原理是byte[]字节数组
在堆内存中有一个字符串常量池。
常用方法:
equals
length()
concat:连接字符串
charAt:
indexOf:

StringBuilder类:字符串缓冲区,可以提高字符串的效率

常用方法:

append

toString

  • 静态关键字static

一旦用了static关键字,那么这样的内容不再属于对象自己,

而是属于类的,所以凡是本类的对象,都共享同一份。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
一旦使用static修饰成员方法,那么就成为了静态方法,静态方法不属于对象,而是属于类的。

如果没有static关键字,那么必须首先创建对象,然后通过对象才能使用它。

如果有了static关键字,那么不需要创建对象,直接就能通过类名称来使用它。

无论是成员变量还是方法,如果有了static,都推荐使用类名称进行调用。

静态变量:类名称.静态变量

静态方法:类名称.静态方法()

注意事项:
1. 静态方法当中不能使用this

静态代码块

特点:当第一次用到本类时,静态代码块执行唯一的一次。

静态内容总是优先于非静态,所以静态代码块比构造方法先执行。

静态代码块的用途:

用来一次性的对静态成员变量进行赋值。

Arrays工具类

toString(数组):将参数数组变成字符串(按照默认格式:[元素1,元素2,…])

1.3 接口

接口就是多个类的公共规范

接口是一种引用数据类型,最重要的内容就是其中的:抽象方法

从Java 8开始,接口 里允许定义默认方法。

备注:接口当中的默认方法可以解决接口升级的问题。

什么是接口升级?

就是已经投入使用的接口,想在其中添加新的方法,这时如果直接添加,会导致所有实现该接口的类报错,但是使用default默认方法可以解决此问题,并且该方法也可以被其他接口实现类覆盖重写。

格式:

1
2
3
public default 返回值类型 方法名称(参数列表){
方法体
}

从Java 9 开始,接口中允许定义私有方法。

  1. 普通私有方法,解决多个默认方法之间的重复代码问题

格式:

1
2
3
private 返回值类型 方法名称(参数列表){

}
  1. 静态私有方法:解决多个静态方法之间重复代码问题

格式:

1
2
3
4
5
private static 返回值类型 方法名称(参数列表){

方法体

}

接口与接口之间是多继承的。

  • 日期时间类

date

  • DateFormat类

日期时间格式化类

  • 包装类
1
2
3
4
5
6
7
8
9
基本类型             对应的包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

装箱与拆箱

装箱:从基本类型转换成对应的包装类对象

拆箱:从包装类对象转换为对应的基本类型

1
2
Integer i= Integer.valueOf(4)//装箱
int num = i.intValue();//拆箱

1.4 容器

  • collection集合

**集合:**集合是Java中提供的一种容器,可以用来存储多个数据。

集合和数组既然都是容器,它们有啥区别?

  1. 数组的长度是固定的,集合的长度是可变的
  2. 数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象,而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。

常用共性方法

1
2
3
4
5
6
7
public boolean add(E e): 添加对象
public void clear(): 清空集合
public boolean remove(E e): 把给定对象从当前集合删除
public boolean contains(E e): 判断当前集合中是否包含给定的对象
public boolean isEmpty():
public int size(): 返回集合中的元素个数
public Object[] toArray(): 把集合中的元素,存储到数组中

List

  1. 有序的集合(存储和取出元素顺序相同)
  2. 允许存储重复的元素
  3. 有索引,可以使用普通的for循环遍历
  • ArrayList(查询快,增删慢)
1
ArrayList<E> list = new ArrayList<>();

常用方法:

add,get,remove,size

  • LinkedList(查询慢,增删快)
1
LinkedList<E> linked = new LinkerdList<>();

Set

  1. 不允许存储重复的元素
  2. 没有索引
  • HashSet
1
HashSet<E> set = new HashSet<>();
  1. 是一个无序集合
  2. 底层是一个哈希表结构

哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑i地址,是模拟出来的地址,不是数据实际存储的物理地址)

1
int hashCode():获取对象的哈希码值

哈希表(查询快)

哈希表的结构,jdk1.8之后:

  1. 哈希表=数组+链表;
  2. 哈希表=数组+红黑树;
  • LInkedHashSet

LInkedHashSet特点:

底层是一个哈希表+链表,多了一条链表(记录元素的存储顺序),保证元素有序。

HashSet存储自定义类型元素

给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一。

可变参数

格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
修饰符 返回值类型 方法名(数据类型...变量名){
}
实例
public static int add(int... arr) {
int sum = 0;
for (int i : arr) {
sum += i;
}
return sum;
}
注意事项
1. 一个方法的参数列表只能有一个可变参数
2. 如果方法的参数有多个,那么可变参数必须写在参数列表的末尾。
可变参数的特殊(终极)写法
实例
public static void method(Object...obj){
}

Map

特点

  1. map集合是一个双列集合,一个元素包含两个值(key:value)
  2. map集合中的元素,key和value的数据类型可以相同,也可以不同
  3. map中key是不允许重复的,value可以重复
  4. map中的key和value一一对应
  • HashMap(初始长度16)

特点:

  1. HashMap集合底层是哈希表:查询速度特别快
  2. hashMap是一个无序集合,存储和取出元素的顺序可能不一致
  • LinkedHashMap

特点:

  1. 底层是哈希表+链表(保证迭代的顺序)
  2. 是一个有序的集合,存储和取出元素的顺序是一致的
  • TreeMap
1

Collections工具类

常用方法

1
2
3
4
5
public static <T> boolean addAll(Collection<? super T> c, T... elements) :往集合中添加一些元素
public static void shuffle(List<?> list) :打乱集合顺序
public static <T extends Comparable<? super T>> void sort(List<T> list)
:将集合按照默认规则排序
public static <T> void sort(List<T> list, Comparator<? super T> c):将集合中元素按照指定规则排序

1.4.1 Iterator迭代器

迭代:Collection集合元素的通用获取方式。

迭代器的使用案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {

Collection<String> coll = new ArrayList<>();
coll.add("姚明");
coll.add("科比");
coll.add("詹姆斯");
coll.add("库里");

//获取一个迭代器对象
Iterator<String> iterator = coll.iterator();
while (iterator.hasNext()){
String s = iterator.next();
System.out.println(s);
}
}

1.5 异常

异常:指的是程序在执行过程中,出现非正常的情况,最终导致JVM非正常停止。

Java处理异常的方式是中断处理。

异常产生的过程解析

1.5.1 异常的处理

Java异常处理的五个关键字:try、catch、finally、throw、throws

  1. 抛出异常throw

throw作用:

可以使用throw关键字在指定的方法中抛出指定的异常

使用格式:

throw new xxxException(“异常产生的原因”);

注意:

  1. throw关键字必须写在方法的内部
  2. throw后面new的对象必须是Exception或者Exception的子类对象
  3. throw抛出指定的异常对象,我们必须处理这个异常对象,要么用throws,要么try…catch
1
2
3
以后(工作中)我们首先必须对方法传递过来的参数进行合法性校验

如果参数不合法,那么我们就必须使用抛出异常的方式,告知方法的调用者,传递的参数有问题。
  1. 声明异常throws(异常处理的第一种方式)

作用:

当方法内部抛出异常对象的时候,那么我们就必须处理这个异常对象

可以使用throws处理异常对象,把异常声明抛出给方法的调用者处理(自己不处理,谁调用我让谁处理),最终交给jvm处理–>中断异常

使用格式:在方法声明时使用

1
2
3
4
5
6
7
8
9
10
11
修饰符 返回值类型 方法名(参数列表) throws xxxException,.....{
throw new xxxException("产生原因");
}
注意
1. throws 必须写在方法声明处
2. throws 后边声明的异常必须是Exception或者其子类
3. 方法内部如果抛出多个异常,那么throws后边也必须声明多个异常
如果多个异常之间有子父类关系,直接声明父类异常就行
4. 调用了一个声明抛出异常的方法,我们就必须处理声明的异常
要么继续使用 throws 声明抛出,交给方法的调用者处理,最终交给 JVM
要么 try....catch 自己处理。
  1. 捕获异常try…catch(异常处理的第二种方式)

格式:

1
2
3
4
5
6
7
8
9
try{

}catch(xxxException e){ //try中抛出什么异常,catch就定义什么异常对象,用来接收这个异常。
一般在工作中,会把异常的信息记录在一个日志中。
}
注意
1. try 可能会抛出多个异常,那么可以使用多个 catch 来处理
2. 如果 try 中产生了异常,就会执行 catch 中的异常处理逻辑,执行完后,继续执行 try...catch 后的代码。
3. 如果 try 中没有异常,就不执行 catch ,执行完 try 后,继续执行 try...catch 后的代码。

1.6 泛型

泛型是一种未知的数据类型,当我们不知道使用什么数据类型的时候,可以使用泛型。

泛型的数据类型在创建对象的时候确定

创建集合对象,使用泛型

好处:

  1. 避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型
  2. 把运行期异常(代码运行之后抛出的异常)提升到了编译期

弊端:

  1. 泛型是什么类型,只能存储什么类型的数据

含有泛型的方法

定义格式

1
2
3
4
5
6
7
 修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)){
方法体;
}
实例
public <H> void genericMethod(H h){
System.out.println(h);
}

含有泛型的接口

定义格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
第一种使用方式,定义接口的实现类,实现接口并指定接口的泛型
实例
public interface Iterator<E> {
E next();
}
public final class Scanner implements Iterator<String>{

}
第二种使用方式:接口使用什么泛型,实现类就使用什么泛型,类跟着接口走
public interface List<E>{
boolean add(E e);
}
public class ArrayList<E> implements List<E>{
public boolean add(E e) {
}
}

泛型通配符

​ ?:代表任意的数据类型

使用方式:

​ 不能创建对象使用

​ 只能作为方法的参数使用

高级使用-受限泛型(要求只要看源码能看懂就行)

作用:限定数据的使用类型。

泛型的上限限定:?extends E 代表使用的泛型只能是E类型的子类/本身

泛型的下限限定:?super E 代表使用的泛型只能是E类型的父类/本身

  • 数据结构

红黑树

特点:

  1. 趋近于平衡树,查询的速度非常快,查询叶子节点最大次数和最小次数不能超过2倍

约束:

  1. 节点可以是红色或者黑色
  2. 根节点是黑色
  3. 叶子节点(空节点)是黑色
  4. 每个红色的节点的子节点都是黑色
  5. 任何一个节点到其每一个叶子节点所有路径上的黑色节点数相等

1.7 反射

作用

  1. 基本作用:可以得到一个类的全部成分然后操作
  2. 可以破坏封装性
  3. 最重要的用途是:适合做Java的框架,基本上,主流的框架都会基于反射设计出一些通用的功能。

1.8 注解

就是Java的特殊标记,作用是:让其他程序根据注解信息来决定怎么执行该程序

特殊属性名:value

如果注解只有一个value属性,使用注解时,value名称可以不写。

元注解

修饰注解的注解

常见的元注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1. @Target
作用:声明被修饰的注解只能在哪些位置使用
@@Target(ElementType.TYPE)
1. TYPE,类,接口
2. FIELD, 成员变量
3. METHOD,成员方法
.....


2. @Retention
作用:声明注解的保留周期
@Retention(RetentionPolicy.RUNTIME)
1. SOURCE
只作用在源码阶段,字节码文件中不存在
3.RUNTIME(开发常用)
一直保留到运行阶段

注解的解析

要解析谁上面的注解,就先拿到谁

1.9 I/O

重点:记住三个单词

​ file: 文件

​ directory:文件夹/目录

​ path: 路径

递归:指在当前方法内部调用自己的这种现象

递归的分类:

  1. 直接递归

​ 方法自身调用自身

  1. 间接递归

​ A方法调用B方法,B方法调用C方法,C方法调用A方法。

Java中的I/O操作主要是指使用Java.io包下的内容,进行输入、输出操作。

  1. 字节流(InputStream/OutputStream)
1
2
3
4
5
6
FileInputStream 
文件字节输入流
作用:把硬盘文件中的数据,读取到内存中使用
FileOutputStream
文件字节输出流
作用:把内存中的数据写入到硬盘的文件中

使用字节流读取中文文件容易产生乱码

一个中文

​ GBK: 占用两个字节

	UTF-8:占用三个字节
  1. 字符流(reader/writer)

作用:读取字符信息,一次读取一个字符

jdk7的新特性

在try后边可以增加一个(),在括号中可以定义流对象

那么这个流对象的作用域就在try中有效

try中的代码执行完毕会自动把流对象释放,不用写finally

格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
try(定义流对象;定义流对象...){
可能会产生异常的代码
}catch(异常类变量 变量名){
异常的处理逻辑
}
实例
public class ThreadPoll {
public static void main(String[] args) {

try(//创建一个字符输出流对象,构造方法绑定需要写入的目的地
FileWriter fw = new FileWriter("test.txt",true);) {
// //创建一个字符输入流对象,构造方法绑定需要读取的数据源
// FileReader fr = new FileReader("test.txt");

for (int i = 0; i < 10; i++) {
fw.append("helloword"+i+"\r\n");
}
}catch (Exception e){
e.printStackTrace();
}
}
}
  1. 缓冲流

作用:提高文件读写的效率

字节缓冲流(BufferedInputStream & BufferedOutputStream)

字符缓冲流(BufferedReader & BufferWriter)

  1. 转换流(OutputStreamWriter & InputStreamReader)

作用:能够转换编码

  1. 序列化流(ObjectOutputStream & ObjectInputStream )

作用:能够持久化存储对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Serializable 接口也叫标记型接口
要进行序列化和反序列化的类必须实现 Serializable 接口,就会给类添加一个标记
当我们进行序列化和反序列化的时候,就会检测是否有这个标记
有:就可以序列化和反系列化
没有:就会抛出 NotSerializableException 异常
transient关键字:瞬态关键字
transient 修饰的成员变量,不能被序列化
问题:
每次修改类的定义,都会给class文件生成一个新的序列号,这样在进行序列化与反序列化时就会报invalidClassException异常
解决方法:
无论是否对类的定义进行修改,都不重新生成新的序列号
可以手动给类添加一个序列号
格式:
private static final long serialVersionUID = 6161825669842708241L;
等号后面的常量可以为任意值,不可改变。

序列化集合

当我们想在文件中保存多个对象的时候,可以把多个对象存储到一个集合中,然后对集合进行序列化和反序列化。

2. JVM

2.1 类加载机制

2.2 字节码执行机制

2.3 jvm内存模型

2.4 gc垃圾回收机制

2.5 jvm性能监控和故障定位

2.6 jvm调优

3. 并发/多线程

3.1 并发编程

3.2 多线程

创建线程的步骤:

  1. 声明Thread类的子类
  2. 重写Thread类的run方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
实例
public class MyThread extends Thread{
@Override
public void run() {
//设置线程任务
}
}
创建线程的第二种方式
1. 创建一个runnable接口的实现类
2. 在实现类中重写runnable接口的run方法,设置线程任务
实例
public class MyRunnableThreadImpl implements Runnable{
@Override
public void run() {
//设置线程任务
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}

}
}
public class RunnableThreaTest {
public static void main(String[] args) {
//多态写法
//Runnable runnable= new MyRunnableThreadImpl();
MyRunnableThreadImpl runnable= new MyRunnableThreadImpl();
Thread t = new Thread(runnable);
t.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
使用runnable接口创建多线程的好处:
1. 避免了单继承的局限性
2. 增强了程序的扩展性,降低了程序的耦合性(解耦)

Thread类中的常用方法

1
2
3
4
5
6
public final String getName():获取;当前线程名
public synchronized void start() :此线程开始执行,Java虚拟机调用此线程的run方法
public static void sleep(long millis, int nanos):使当前线程暂停指定多长时间
public void run():线程要执行的任务都在此处编写
public static native Thread currentThread():返回当前正在执行的线程对象的引用

并发与并行

并发:指两个或多个事件在同一时间段内发生

并行:指两个或多个事件在同一时刻发生(同时发生)

线程与进程

进程: 是指一个内存中运行的应用程序

线程: 是指进程中的一个执行单元,负责当前进程中程序的执行,一个进程至少有一个线程。一个进程中是可以有多个线程的,这个应用程序称为多线程程序

线程调度

分时调度: 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。

抢占式调度: 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

3.2.1 线程安全

解决线程安全问题

  1. 同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

使用synchronized关键字容易产生死锁(如何解决,在后续内容中写)

格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
synchronized(同步锁(锁对象)){
//需要同步操作的代码即:可能出现线程安全问题的代码(访问了共享数据的代码)
}
同步锁(锁对象)注意事项:
1. 同步代码块中的锁对象,可以使用任意的对象
2. 必须保证多个线程使用的锁对象是同一个
3. 锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
实例
public class MyRunnableThreadImpl implements Runnable{

private Integer ticket = 100;
Object obj = new Object();
@Override
public void run() {
//同步锁
synchronized (obj){
while (true){
if (ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticket);
ticket--;
}
}
}
}
}
  1. 同步方法

格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
修饰符 synchronized 返回值类型 参数名(参数列表){
//需要同步操作的代码即:可能出现线程安全问题的代码(访问了共享数据的代码)
}
实例
public class MyRunnableThreadImpl implements Runnable{

private Integer ticket = 100;
@Override
public void run() {
while (true){
payTicket();
}
//同步方法
public synchronized void payTicket(){
if (ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticket);
ticket--;
}
}
}
**静态同步方法**

锁对象是谁?

不能是this

this是创建对象之后产生的,静态方法优先于对象

静态方法的锁对象是本类的class属性-->class文件对象(反射)
  1. Lock锁

Lock接口中的方法:

void lock() 获取锁

void unlock() 释放锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
实例
public class RunnableImpl implements Runnable{
private Integer ticket = 100;
Lock l = new ReentrantLock();

@Override
public void run() {
while (true) {

l.lock();
if (ticket > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "-->" + ticket);
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
l.unlock();
}
}
}
}
}

3.3 线程池

线程池: 其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作。

3.4 锁

3.5 并发容器

3.6 原子类

3.7 juc并发工具类

4. 网络编程

软件结构:c/s,b/s

4.1 网络通信

网络通信协议的分类

  1. UDP:用户数据报协议

特点:消耗资源小,通信效率高

通常用于音频、视频和普通数据的传输例如视频会议都使用udp协议,即使丢一两个数据包,也不会对接受结果产生太大影响。

  1. TCP:传输控制协议

是面向连接的通信协议。在TCP连接中必须要明确客户端与服务器端,每次连接的创建都需要经过“三次握手”。

特点:传输安全

网络编程的三要素

  1. 协议
  2. IP地址
  • IPv4:32位的二进制数,通常被分为4个字节,表示a.b.c.d的形式,每个字节的范围都是0~255.
  • IPv6:
  1. 端口号

4.2 函数式接口

函数式接口:只有一个抽象方法的接口,称之为函数式接口

当然接口中可以包含其他的方法(默认,静态,私有)

Lambda表达式

函数式编程思想:

​ 只要能获取到结果,谁去做,怎么做的都不重要,重视的是结果,不重视过程。

使用前提

  1. 使用Lambda必须具有接口,且要求接口中有且只有一个抽象方法。
  2. 使用Lambda必须具有上下文推断

有些场景的代码执行后结果不一定被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,正好可以作为解决方案,提升性能。

常用函数式接口

  1. Supplier接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Supplier:被称为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据
public class Demo01Test {

/**
* 定义一个方法,用于获取int类型数组中元素的最大值,
* 方法的参数传递Supplier接口,泛型使用Integer
* @param sup
* @return
*/
public static int getMax(Supplier<Integer> sup){
return sup.get();
}

public static void main(String[] args) {
int[] arr = {100,0,-50,88,99};

int max= getMax(()->{
int temp= arr[0];
for (int i = 1; i < arr.length; i++) {
if (temp<arr[i]){
temp=arr[i];
}
}
return temp;
});

System.out.println(max);
}
}
  1. Consumer接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Consumer:消费型接口,泛型执行什么类型,就可以使用accept方法使用什么类型的数据
至于怎么消费(使用),需要自定义(输出,计算)
默认方法:andThen
作用:将两个Consumer接口连接到一起,然后再对数据进行消费(使用)
public class DemoConsumer {

public static void printMessage(String[] arr, Consumer<String> con1,Consumer<String> con2){
for (String s : arr) {
con1.andThen(con2).accept(s);
}
}

public static void main(String[] args) {
String[] arr = {"迪丽热巴,女","古力娜扎,女","沈腾,男"};
printMessage(arr,
(s)->{
System.out.print("姓名:"+s.split(",")[0]+"\t");
},
(s)->{
System.out.println("性别:"+s.split(",")[1]+"。");
});
}
}
  1. Predicate接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
对某种类型的数据进行判断,从而得到一个Boolean值结果。
public class DemoPredicate {
/**
* 定义一个方法,方法的参数传递一个字符串
* 传递两个Predicate接口
* 一个用于判断字符串的长度大于5
* 一个用于判断字符串中是否包含a
* 两个条件必须同时满足
* @param pre1
* @return
*/

public static boolean checkString(String s, Predicate<String> pre1, Predicate<String> pre2){
// return pre1.test(s) && pre2.test(s);
//return pre1.test(s) || pre2.test(s);
// return pre1.or(pre2).test(s);
return pre1.and(pre2).test(s);
}

public static void main(String[] args) {
String test = "Hello world!!";
boolean b= checkString(test,
(s)->{
return s.length()>5;
},
(s)->{
return s.contains("H");
});
System.out.println(b);

}
}

4.3 Stream流

IO流和Stream流是两个概念,IO流是用于数据的读写,而Stream流可以用来对集合和数组进行简化操作。

 评论
评论插件加载失败
正在加载评论插件