Java基础全套教程[尚硅谷宋红康] Summary for videos
Java基础全套教程[尚硅谷宋红康]
一、基础语法
- 文档注释:注释内容可以被JDK提供的工具javadoc(只提取文档注释)所解析,生成一套以网页文件形式体现的该程序的说明文档。
- 保留字(现在非关键字,但是以后新版本可能是):goto、const。关键字如下。
类型 | 内容 |
---|---|
用于定义数据类型的关键字 | class、interface、enum、byte、short、int、long、float、double、char、boolean、void |
用于定义流程控制的关键字 | if、else、switch、case、default、while、do、for、break、continue、return |
用于定义访问权限修饰符的关键字 | private、protected、public |
用于定义类、函数、变量修饰符的关键字 | abstract、final、static、synchronized |
用于定义类与类之间关系的关键字 | extends、implements |
用于定义建立实例及引用实例,判断实例的关键字 | new、this、super、instanceof |
用于异常处理的关键字 | try、catch、finally、throw、throws |
用于包的关键字 | package、import |
其他修饰符关键字 | native、strictfp、transient、volatile、assert |
用于定义数据类型值的字面值 | true、false、null |
标识符:由英文字母、数字、下划线、$组成。
标识符命名规范:
- 包名:所有单词都小写。
- 类名:大驼峰。
- 变量名、方法名:小驼峰。
- 常量名:单词之间用下划线,全部大写。
boolean类型数据只允许取值true和false,无null。不可以使用0或非0的整数替代false和true,这点和C语言不同。
自动类型转换:容量小的类型自动转换为容量大的数据类型。容量代表数的范围的大小,而不是内存的大小。
常用进制:
- 二进制:以0b或0B开头。
- 八进制:以数字0开头表示。
- 十六进制:0-9及A-F, 以0x或0X开头表示。此处的A-F不区分大小写。
>>>
代表无符号右移,无论最高位为0或者1,都由0来补。
- Scanner类使用步骤:
- 导包:
import java.util.Scanner
。 - Scanner的实例化:
Scanner scan = new Scanner(System.in)
。 - 调用Scanner类的相关方法,获取指定类型的变量:
int num = scan.nextInt()
。 - 获取字符串:
scan.next()
。 - 获取浮点数:
scan.nextDouble()
。
- 导包:
- 带标签的continue和break示例如下。标号语句必须紧接在循环的头部。标号语句不能用在非循环语句的前面,演示break如下。
- 第一例运行结果为如下。
- 演示continue如下。
- 第二例运行结果为如下。
- Eclipse缩写与常用快捷键:
main
:public static void main(String[] args) {}
。syso
:System.out.println()
。alt+/
:补全提示。Ctrl+鼠标点击
:查看源代码。alt+/
:补全代码。ctrl+/
:注释。ctrl+\
:取消斜杠。ctrl+o
:查找类中的结构。ctrl+d
:删除当前行。ctrl+1
:快速修复。ctrl+shif+o
:快速导包。
二、数组语法
- 数组属于引用数据类型的变量。数组的元素,既可以是基本数据类型,也可以是引用数据类型。
- 数组静态初始化:
int[] a = new int[]{1,2,3}
。int[] a = {1,2,3}
:类型推断,初始化和声明在一行时可以省略new。
- 数组动态初始化:
String[] names = new String[5]
。 - 数组长度:
a.length
。
数组元素类型 | 默认初始化值 |
---|---|
整型 | 0 |
浮点型 | 0.0 |
char型 | 0或'\u0000' 而非'0' |
布尔型 | false |
引用数据类型 | null |
- 内存模型如下。
- 一维数组的内存解析如下。
二维数组静态初始化:
int[][] a = new int[][] {{1,2},{1,2,3},{1,2}}
,a[0].length=2
,a[1].length=3
。二维数组动态初始化:
String[][] a = new String[3][2]
。String[][] a = new String[3][]
。
对于
int arr[][] = new int[4][3]
的默认值:arr[0]
:地址值。arr[0][0]
:0。
对于
int arr[][] = new int[4][]
的默认值:arr[0]
:null。arr[0][0]
:报错。
二维数组的内存解析如下。
a2=a1
:把a1的地址给了a2,这样两个变量指向的值一致,那么当修改a2时,a1的值也会改变,此操作不能被称为数组的复制。- 字符串比较:
a.equals(b)
。 - Arrays工具类如下。
- 数组中常见异常:
ArrayIndexOutOfBoundsException
:数组角标越界异常。NullPointerException
:空指针异常。
三、面向对象
- 面向过程:POP。面向对象:OOP。
Person p3=p1
:将p1变量保存的对象的地址赋值给p3,导致p1和p3指向了堆空间中的同一个对象实体。- 对象的内存解析如下。
属性类型 | 默认初始化值 |
---|---|
整形(byte、short、int、long) | 0 |
浮点型(float、double) | 0.0 |
字符型(char) | 0或'\u0000' 而非'0' |
布尔型(boolean) | false |
引用(类、数组、接口) | null |
在内存中加载的位置:
- 属性:加载到堆空间中(非static)。
- 局部变量:加载到栈空间。
引用类型的变量,只可能存储两类值:null或地址值。
匿名对象:创建的对象没有显式赋给一个变量名,即为匿名对象。匿名对象只能调用一次,
new Phone().send()
。可变个数形参的方法:jdk5.0新增的内容,格式:数据类型 … 变量名,
public void show(String ... strs) {}
,可以为0到无穷个参数。
- 这两种方法默认一样,不能共存,不构成重载。在一个方法的形参位置,最多只能声明一个可变个数形参,可变个数形参在方法中必须声明在末尾,可变参数方法的使用与方法参数部分使用数组是一致的,如下。
- 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参。
- 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参。
修饰符 | 内部类 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | ✅ | |||
空闲 | ✅ | ✅ | ||
protected | ✅ | ✅ | ✅ | |
public | ✅ | ✅ | ✅ | ✅ |
修饰类只能用空缺或public。public类可以在任意地方被访问,空缺类只可以被同一个包内部的类访问。
构造器:不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值,可以被权限修饰符修饰。一旦显式定义了构造器,则系统不再提供默认构造器。父类的构造器不可被子类继承。
JavaBean是一种Java语言写成的可重用组件,所谓JavaBean,是指符合如下标准的Java类:
- 类是公共的。
- 有一个无参的公共的构造器。
- 有属性,且有对应的get、set方法。
用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
this:它在方法内部使用,即这个方法所属对象的引用;它在构造器内部使用,表示该构造器正在初始化的对象。可以在类的构造器中使用
this(形参列表)
的方式,调用本类中重载的其他的构造器。构造器中不能通过this(形参列表)
的方式调用自身构造器。this(形参列表)
必须声明在类的构造器的首行。在类的一个构造器中,最多只能声明一个this(形参列表)
。
package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包(若缺省该语句,则指定为无名包)。它的格式为:
package 顶层包名.子包名
。每.
一次,就代表一层文件目录。同一个包下,不能命名同名的接口、类,不同的包下,可以命名同名的接口、类。包帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式。- 包可以包含类和子包,划分项目层次,便于管理。
- 解决类命名冲突的问题。
- 控制访问权限。
如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。如果已经导入所有java.a包下的类。那么如果需要使用a包的子包下的类的话,仍然需要导入。
import static组合的使用:调用指定类或接口下的静态的属性或方法。
java中的JUnit单元测试:
- 选中当前工程->build path->add libraries->JUnit4->finish。
- 类的要求:此类为public。此类提供公共的无参构造器。
- 声明单元测试方法,方法的权限为public,返回值类型为void,无形参。
- 此方法上需要声明注解@Test并导入org.junit.Test包。
- 双击方法名,选择运行方法。
1个类只能有一个父类。如果没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类。
子类中把父类中的private方法改为public方法不叫重写,不会覆盖private方法,如果在父类中调用方法,会调用private方法,而不会调用public方法。
子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符。
如父类是void返回值类型,则子类也只能是void;若父类是A类型,则子类的返回类型只能是A类或者A的子类。
子类重写的方法抛出异常类型不大于父类被重写的方法抛出的异常类型。
子类和父类同名同参数的方法都是非static,则考虑为重写;若都是static,则不是重写。
super可用于访问父类中定义的属性:
- super可用于调用父类中定义的成员方法。
- super可用于在子类构造器中调用父类的构造器。
关于super调用构造器:
- 我们可以在子类的构造器中显式的使用super(形参列表)的方式调用父类声明的指定的构造器。
- 必须生命在子类构造器的首行。
- 类的构造器中,super(形参列表)和this(形参列表)只能二选一,不能同时出现。
- 在构造器的首行,如果没有显式声明this或super,则默认为调用父类中的空参构造器。
- 在类的多个构造器中,至少有一个类使用了super(形参列表)调用父类中的构造器。
对象的多态性:父类的引用指向子类的对象,
Person p1 = new Man()
。多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法——虚拟方法的调用。有了对象的多态性后,我们在编译期只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。总结:编译看左边,运行看右边。对象的多态性只适用于方法,不适用于属性(编译和运行都看左边)。
对于重载:在方法调用之前,编译器就确定了要调用的方法,此为静态绑定。对于多态,在方法调用的那一刻,编译器才会确定要调用的具体方法,此为动态绑定。
x instanceof A
:检验x是否为类A的对象或子类,返回值为boolean型。为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,需要先进行instanceof的判断,为true则向下转型。
程序员可以通过System.gc()或者Runtime.getRuntime().gc()来通知系统进行垃圾回收,会有一些效果,但是系统是否进行垃圾回收依然不确定。垃圾回收机制回收任何对象之前,总会先调用它的finalize方法,永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用。
Object类只声明了一个空参构造器。
equals(Object obj)
:Object中equals()方法和==作用相同,我们一般需要对equals()进行重写。toString()
:在Object类中定义,其返回值是String类型,返回类名和它的引用地址。在进行String与其它类型数据的连接操作时,自动调用toString()方法。可以根据需要在用户自定义类型中重写toString()方法。
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
- 表格中的前六行的包装类属于父类Number的子类。
- 调用包装类的xxxValue()。
- 在JDK5之中增加了新特性:自动装箱与自动拆箱。
静态变量加载早于对象的创建,随着类的加载而加载。
静态方法中只能调用静态方法和静态属性。非静态方法中既可以调用非静态方法和属性,也可以调用静态的方法和属性。在静态方法中,不能使用this和super。
静态代码块:用static修饰的代码块。
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
- 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
- 静态代码块的执行要先于非静态代码块。
- 静态代码块随着类的加载而加载,且只执行一次。
非静态代码块:没有static修饰的代码块。
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 除了调用非静态的结构外,还可以调用静态的变量或方法。
- 若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
- 每次创建对象的时候,都会执行一次。且先于构造器执行。
赋值的先后顺序:
- 默认初始化。
- 显示初始化/在代码块中初始化。
- 构造器初始化。
- 通过对象赋值。
final关键字:
- final标记的类不能被继承。
- final标记的方法不能被子类重写。
- final标记的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次。
- final属性可以考虑的赋值位置有:显式初始化、代码块中初始化、构造器中初始化。
static final:用来修饰属性,为全局变量。
abstract关键字:
- 修饰类、方法。
- 修饰类:此类不能实例化,抽象类一定有构造器,便于子类实例化时调用。开发时会提供抽象类的子类,让子类对象实例化,完成相关的操作。
- 修饰方法:只有方法的声明,没有方法体。含有抽象方法的类一定是一个抽象类,反之抽象类中可以没有抽象方法。必须重写了全部的抽象方法后才可以实例化,否则仍是个abstract修饰的抽象类。
- abstract不能修饰私有方法、静态方法、final方法、final类。
匿名类如下。
- UML类图:+表示public类型,-表示private类型,#表示protected类型。方法的写法:
方法的类型(+、-) 方法名(参数名: 参数类型):返回值类型
。若方法有下划线表示为构造器。 - interface关键字:
- JDK7之前只能定义全局常量和抽象方法。public static final(可以省略),public abstract(可以省略)。
- JDK8 还可以定义静态方法、默认方法。
- 若实现了所有抽象方法,则为一个类,否则则为一个抽象类。
- 接口中不能定义构造器。
- java类可以实现多个接口,接口可以多继承,格式为
class AA extends BB implements CC,DD,EE {}
。 - 接口在使用上体现了多态性。例如:usb为接口,phone为实现接口的类,则可以往方法形参为usb的接口传入phone的对象。
- 接口中的静态方法,只能通过接口调用。通过实现类的对象,可以调用接口中的默认方法。如果实现类中重写了接口中的默认方法,则调用时,仍然调用的是重写的方法。
- 如果子类继承的父类和实现的接口中声明了同名同参数的方法,那么在子类没有重写该方法的情况下,默认调用的是父类中同名同参数的方法(类优先原则)。
- 如果实现了多个接口,而接口中有多个定义了同名同参数的默认方法,那么在实现类没有重写该方法的情况下,编译器会报错(接口冲突)。
- 运行结果如下。
- 内部类:
- 一方面,作为外部类的成员:
- 调用外部类的结构。
- 可以被static修饰。
- 可以被四种权限修饰。
- 一方面,作为一个类:
- 可以定义属性、方法、构造器等。
- 可以被final修饰,表示不能被继承。
- 可以被abstract修饰。
- 一方面,作为外部类的成员:
- 关于内部类中的方法中的重名变量调用如下。
- 关于内部类调用外部类重名重形参方法如下。
- 在局部内部类的方法中,如果调用局部内部类所声明的方法中的局部变量,要求此局部变量声明为final的。在JDK7及之前,要求显式声明为final;在JDK8及之后,可以省略final的声明。
四、设计模式
- MVC设计模式:MVC是常用的设计模式之一,将整个程序分为三个层次:视图模型层,控制器层,与数据模型层。这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。
- 模板方法设计模式:抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。 解决的问题:
- 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
- 接口的应用——代理模式:代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其 他对象提供一种代理以控制对这个对象的访问。
- 接口的应用——工厂模式:实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
- 单例设计模式:所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。 如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象, 静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
- 饿汉式如下。
- 懒汉式如下。
- 由于懒汉式存在线程安全问题,因此修改如下。
- 饿汉式:对象加载时间过长,是线程安全的。
- 懒汉式:延迟对象的创建,线程安全。
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的 产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
五、异常处理
- 捕获错误最理想的是在编译期间,但有的错误只在运行时才会发生,如下标越界等。因此异常的分类分为:编译时异常和运行时异常。
- 异常的处理:抓抛模型:
- 抛:程序在正在执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出。一单抛出对象以后,其后的代码就不再执行。关于异常对象的产生:系统自动生成的异常对象、手动生成一个异常对象,并抛出(throw)。
- 抓:try-catch-finally、throws。
- try-catch-finally:
- finally是可选的。
- 使用try将可能出现的异常包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,取catch中进行匹配。
- 一但try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一但处理完成,就跳出当前的try-catch结构。继续执行其后面的代码。
- catch中的异常类型如果没有子父类关系,则声明顺序无所谓。如果存在子父类关系,则要求子类先声明,父类后声明,否则会报错。
- 常用的异常对象处理的方式:String getMessage()、printStackTrace()。
- 在try中声明的变量,出了try结构后,就不能再被调用。
- 使用try-catch-finally处理编译时异常,是使得程序在编译时就不再报错,但是运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常延迟到运行时出现。
- 像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动回收的,我们需要自己手动的进行资源的释放。此时资源的释放,就需要声明在finally中。
- 由于运行时异常比较常见,我们就通常不真如运行时异常编写try-catch-finally了。针对编译时异常,我们一定要考虑异常的处理。
throws+异常类型:
- 写在方法的声明处,指明此方法执行中可能会抛出的异常类型。
- 一但方法执行时出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后的异常类型时就会抛出,异常代码后续的代码就不再执行。throws只是将异常抛出给了方法的调用者,并没有真正将异常处理掉。
如果父类中被重写的方法中没有throws处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,就必须用try-catch-finally方式处理。
执行的方法a中,又先后调用了另外几个方法,这几个方法是递进执行的。我们建议这几个方法使用throws的方式进行处理,而执行方法a时可以考虑用try-catch-finally方式进行处理。
自定义异常:
- 继承现有的异常结构。
- 提供全局常量serialVersionUID。
- 提供重载的构造器。
六、多线程
- 程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态对象。
- 进程(process):是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。程序是静态的,进程是动态的。进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。
- 线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。 若一个进程同一时间并行执行多个线程,就是支持多线程的。
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。一个进程中的多个线程共享相同的内存单元/内存地址空间。它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
- 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
- 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
- Java语言的JVM允许程序运行多个线程,它通过
java.lang.Thread
类来体现。 - 多线程的创建方式:
- 方法一:继承于Thread类。
- 方法二:实现Runnable接口。
- 方法三:实现Callable接口。
- 方法四:使用线程池。
- 继承于Thread类:
- 创建一个继承于Thread类的子类。
- 重写Thread的run()方法(将此线程执行的操作声明在run方法中)。
- 创建Thread类的子类的对象。
- 通过此对象调用start()方法(启动当前线程、调用当前线程的run方法)。
- 我们不能通过直接调用run()的方式启动线程。
- 再启动一个线程,不可以让已经start()的线程去执行,否则会报异常,我们需要重新创建一个线程的对象去执行。
- 创建Thread类的匿名子类的方式如下。
- 测试Thread中的常用方法:
- start():启动当前的线程,调用当前线程的run()方法。
- run():通常需要重写Thread的此方法,将创建的线程需要执行的操作生命在此方法中。
- currentThread():静态方法,返回执行当前代码的线程。
- getName():获取当前线程的名字。
- setName():设置当前线程的名字。
- yield():释放当前CPU的执行权。
- join():在线程a中调用线程b的join(),此时线程a就会进入阻塞状态,知道线程b完全执行完之后,线程a才结束阻塞状态。
- stop():已过时,当调用此方法时,强制结束当前进程。
- sleep(long millitime):让当前线程睡眠指定的millitime毫秒,在指定的millitime毫秒时间内,当前线程是阻塞状态。
- isAlive():判断当前线程是否存活。
- 线程的优先级:
- MAX_PRIORITY:10。
- MIN_PRIORITY:1。
- NORM_PRIORITY(默认优先级):5。
- 获取线程的优先级:getPriority()。
- 设置线程的优先级:setPriority(int p)。
- 说明:高优先级的线程要抢占低优先级的CPU执行权。但只是从概率上讲,高优先级的线程高概率的情况下被执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
- 实现Runnable接口的类:
- 创建一个实现了Runnable接口的类。
- 实现类去实现Runnable中的抽象方法run()。
- 创建实现类的对象。
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象。
- 通过Thread类的对象调用start()。
- 下图中三个线程共用一个runnableTest对象。
比较上述两种创建线程的方式:
- 开放中优先选择实现Runnable接口的方式。
- 实现接口的方式没有类的单继承的局限性。
- 实现的方式更适合来处理多个线程有共享数据的情况。
- 两种方法都要重写run()方法。
JDK中用Thread.State类定义了线程的几种状态。要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
- 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源。
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能。
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态。
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束。
synchronized关键字:
- 问题:线程安全问题。
- 原因:在某个线程操作的过程中,操作尚未完成,此时其他线程参与进来,也操作数据。
- 解决方法:让一个线程操作数据时其他线程不能参与进来,直到此线程操作完成后其他线程再进来。这种情况下即便此线程出现了阻塞,也不能改变。
- 方式一:同步代码块,
synchronized(同步监视器) {需要被同步的代码}
,说明:- 操作共享数据的代码,即为需要被同步的代码。
- 共享数据:多个线程共同操作的变量。
- 同步监视器:俗称锁,任何一个对象都可以充当锁,要求多个线程必须同用一把锁。
- 在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
- 方式二:同步方法,synchronized还可以放在方法声明中,表示整个方法为同步方法。
- 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
- 非静态的同步方法,同步监视器是this。
- 静态的同步方法,同步监视器是当前类本身。
- 同步的方式解决了线程安全问题,但是操作同步代码块时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
线程的死锁问题:
- 死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
- 解决方法:专门的算法、原则,尽量减少同步资源的定义,尽量避免嵌套同步。
- Lock(锁):
- 从JDK5开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
- ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
synchronized与lock的异同:
- 二者都可以解决线程安全问题。
- synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器。
- lock需要手动的启动同步(lock),同时结束同步也需要手动的实现(unlock)。
- Lock只有代码块锁,synchronized有代码块锁和方法锁。
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性
- 使用的优先顺序:Lock、同步代码块、同步方法。
线程的通信:
- wait()与notify()和notifyAll()
- wait():令当前线程挂起并放弃CPU、同步资源并等待,调用方法的必要条件为当前线程必须具有对该对象的监控权(加锁)。使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。
- notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待。当前线程必须具有对该对象的监控权(加锁)。
- notifyAll():唤醒正在排队等待资源的所有线程结束等待。
- 这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报 java.lang.IllegalMonitorStateException异常。
- 因为这三个方法必须为有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声明。
- wait()与notify()和notifyAll()
sleep和wait的异同:
- 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
- 不同点:
- 两个方法的声明位置不同,Thread类中声明sleep,Object类中声明wait。
- 调用的要求不同,sleep可以在任何需要的场景下调用,wait必须在同步代码块或同步方法中调用。
- 关于是否释放同步监视器:如果两个方法都是同在同步代码块或同步方法中,sleep不会释放同步监视器,而wait会释放同步监视器。
与使用Runnable相比,Callable功能更强大些:
- 可以存在返回值。
- 可以抛出异常。
- 可以支持泛型的返回值。
- 需要借助Future Task类,比如获取返回结果。
- Callable实现call方法。
实现Callable接口的类:
- 创建一个实现Callable的实现类。
- 实现call方法,将此线程需要执行的操作声明在call中。
- 创建Callable接口实现类的对象。
- 将此Callable接口实现类的对象作为参数传递到Future Task类的构造器中,创建Future Task的对象。
- 将Future Task的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法。
- 可以根据需要从Future Task获取返回值。
- Callable中的call方法:
- call可以有返回值。
- call可以抛出异常,被外层的操作捕获,获取异常的信息。
- Callable是支持泛型的。
- 使用线程池:
- 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
- 好处:
- 提高响应速度(减少了创建新线程的时间)。
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)。
- 便于线程管理。
- JDK5.0起提供了线程池相关API,ExecutorService和Executors。
- ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor。
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。
七、常用类
- String类:
- String是一个final类,不可被继承。
- String实现了Serializable接口:表示String是支持序列化的。
- String实现了Comparable接口:表示String可以比较大小。
- String内部定义了final char[] value用于存储字符串数据。
- String代表不可变的字符序列,简称不可变性。体现如下:
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
- 当对现有的字符串进行连接操作时,也需要重新制定内存区域赋值,不能使用原有的value进行赋值。
- 当调用String的replace方法修改指定字符或者字符串时,也需要重新制定内存区域赋值,不能使用原有的value进行赋值。
- 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
- 字符串常量池中不会存储相同内容的字符串。
- 常量和常量的拼接结果在常量池,且常量池中不会存在相同的常量。
- 如果拼接的结果调用intern方法,结构就在堆中。
- 只要其中有一个是变量,结果就在堆中。
String中的方法 | 作用 |
---|---|
int length() | 长度 |
char charAt(int index) | 索引处字符 |
boolean isEmpty() | 是否为空 |
String toLowerCase() | 转为小写 |
String toUpperCase() | 转为大写 |
String trim | 忽略开头结尾空白 |
boolean equals(Object obj) | 比较是否相等 |
boolean equalsIgnoreCase(String anotherString) | 忽略大小写比较相等 |
String concat(String str) | 拼接末尾 |
int compareTo(String anotherString) | 比较字符串大小 |
String substring(int beginIndex) | 截取 |
String substring(int beginIndex, int endIndex) | 截取指定位置 |
boolean contains(charSequence s) | 是否包含 |
int indexOf(String str) | 字符串第一次出现的位置 |
String replace(char oldChar, newChar) | 替换字符 |
String matches(String regex) | 检查是否匹配给定的正则表达式 |
- String、StringBuffer、StringBuilder三者的异同:
- String:不可变的字符序列,底层使用char[]存储。
- StringBuffer:可变的字符序列,线程安全,效率低,底层使用char存储。
- StringBuilder:可变的字符序列,JDK5新增的,线程不安全,效率高,底层使用char[]存储。
- 以StringBuffer为例:
- StringBuffer():初始容量为16的字符串缓冲区。
- StringBuffer(int capacity):指定长度,如果将来长度不够,默认情况下,扩容为原来容量的2倍加2,同时将原有数组的元素复制到新的数组中。
- StringBuffer的常用方法:
StringBuffer append(xxx)
:提供了很多的append()方法,用于进行字符串拼接。StringBuffer delete(int start,int end)
:删除指定位置的内容。StringBuffer replace(int start, int end, String str)
:把[start,end)位置替换为str。StringBuffer insert(int offset, xxx)
:在指定位置插入xxx。StringBuffer reverse()
:把当前字符序列逆转。public int indexOf(String str)
。public String substring(int start,int end)
。public int length()
。public char charAt(int n)
。public void setCharAt(int n ,char ch)
。
- Math类:abs、三角函数(acos、asin、atan、cos…)、sqrt、pow、log、exp、max、min、random、round等。
- BigInteger(String val)、 BigDecimal(String val)来使用高精度整数和小数。
- System类:
- 该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。
- System类内部包含in、out和err三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
- JDK8之前的日期和时间API:
- System类中的currentTimeMillis()。
- java.util.Date类(其中有java.sql.Date的子类):
- 构造器:Date()默认为当前时间、Date(指定毫秒数)。
- 方法:toSting显示当前的年月日时分秒、getTime获取当前Date对象对应的毫秒数(时间戳)。
- java.text.SimpleDateFormat类:不与语言环境有关的方式格式化和解析日期的具体类。
- java.util.calendar(日历)类:完成日期字段之间互相操作的功能。
- JDK8新出现的日期和时间API:
- LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。
- Instant(瞬时):时间线上的一个瞬时点。
- Java实现对象排序的方式有两种:
- 自然排序:java.lang.Comparable。
- 像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象的方法。
- 重写compareTo的规则:当前对象this大于形参对象,返回正整数;相等返回0;反之返回负整数。
- 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。
- 一但确定,在任何位置都可以比较大小。
- 定制排序:java.lang.Comparable。
- 当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用Comparator 的对象来排序,强行对多个对象进行整体排序的比较。
- 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
- 可以将Comparator传递给sort方法(如Collections.sort、Arrays.sort), 从而允许在排序顺序上实现精确控制。
- 还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象Collection提供排序。
- java.util.Comparator属于临时性比较。
- 自然排序:java.lang.Comparable。
- java.lang.Comparable示例如下。
- java.lang.Comparable示例如下。
八、IO流
- java.io.File类:
- 文件和文件目录路径的抽象表示形式,与平台无关。
- File能新建、删除、重命名文件和目录,但File不能访问文件内容本身。
- 如果需要访问文件内容本身,则需要使用输入/输出流。
- 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
- File对象可以作为参数传递给流的构造器
- 路径分隔符和系统有关:
- windows和DOS系统默认使用
\
来表示。 - UNIX和URL使用
/
来表示。 - Java程序支持跨平台运行,因此路径分隔符要慎用。
- File类提供了一个常量:
public static final String separator
代表分隔符。
- windows和DOS系统默认使用
- 流的分类:
- 按操作数据单位不同分为:字节流(8bit),字符流(16bit)。
- 按数据流的流向不同分为:输入流,输出流。
- 按流的角色的不同分为:节点流,处理流。
- 步骤:
- File类的实例化。
- FileReader流的实例化。
- 读入操作。
- 资源关闭。
九、枚举类与注解
- 枚举类:一组常量通常使用枚举类,如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
- 在JDK5之前,自定义枚举类;在JDK5之后,可以通过enum关键字定义枚举类。
- 自定义枚举类如下。
- enum关键字如下。
- enum添加接口:
- 实现接口,在enum类中实现接口方法。
- 让枚举类的对象分别实现接口中的抽象方法。
enum类中的常用方法:
values()
:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。valueOf(String str)
:可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常: IllegalArgumentException。toString()
:返回当前枚举类对象常量的名称。
注解:Annotation可以像修饰符一样被使用, 可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明。框架=注解+反射+设计模式。
@Override
:限定重写父类方法, 该注解只能用于方法。@Deprecated
:用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择。@SuppressWarnings
:抑制编译器警告。
十、泛型
- 泛型的类型必须是类,不能是基本数据类型,如果有需要用到基本数据类型的位置,需要拿包装类来替换。
- 如果实例化时没有指明泛型的类型。则默认为java.lang.Object类型。
泛型的构造器如下:
public classname() {}
。静态方法不能使用类的泛型。
不能使用new E[],但是可以使用
E[] elements = (E[]) new Object[capacity]
。异常类不能使用泛型。
通配符:
G<?>
,对于其不能添加数据,null除外,读取的数据类型为Object。? extends A
:A和A的子类。? super A
:A和A的父类。? extends Comparable
:只允许泛型为实现Comparable接口的实现类的引用调用。A是B的父类,但是
G<A>
和G<B>
不具备父子关系,二者为并列关系,A<G>
是B<G>
的父类。泛型的方法如下。
- 泛型方法可以声明为静态的,因为泛型参数是在调用时确定而非在实例化时确定。