Java
Java易错总结
基础知识
数据和数据类型
1.与c++区别
- 取消隐式的强制类型转换
- 对象赋值为值传递
2.变量与数据类型
数据类型 | 关键字 | 字节 | 取值 |
---|---|---|---|
字节型 | byte | 1 | -128-127 |
短整型 | short | 2 | -32768-32767 |
整型 | int(默认) | 4 | -231~231-1 |
长整型 | long | 8 | -263~263-1 |
单精度浮点数 | float | 4 | 1.4013E-45~3.4028E+38 |
双精度浮点数 | double(默认) | 8 | 4.9E-324~1.7977E+308 |
字符型 | char | 2 | 0-65535 |
布尔类型 | boolean | 1 | true,false |
3.数据类型转换
-
自动类型转换
将取值范围小的类型自动提升为取值范围大的类型 。
byte、short、char‐‐>int‐‐>long‐‐>float‐‐>double
-
强制转换
将取值范围大的类型强制转换成 取值范围小的类型 。
public static void main(String[] args) { //short类型变量,内存中2个字节
short s = 1;
/*出现编译失败 s和1做运算的时候,1是int类型,s会被提升为int类型 s+1后的结果是int类型, 将结果在赋值会short类型时发生错误 short内存2个字节,int类型4个字节 必须将int强制转成 short才能完成赋值 */
s = s + 1;//编译失败
s = (short)(s+1);//编译成功 }
4.字符串
- 可以使用“+”来连接字符串,但是会产生新的String实例(在栈内存中,本质是通过StringBuffer来构造的)
5.成员变量默认值
boolean :false
char:\u0000
byte: 0
short: 0
int: 0
long: 0
float: 0.0
double: 0.0
类
1.类的描述
public
: 可以被其他类使用- 无修饰(default) : 仅仅能在同一个包中的其他类引用
abstract
:宣布该类不能被实例化final
:宣布该类不能有子类,若是加在方法前面,表示该方法不能被重写
2.类成员变量的访问
public
:被所有类访问defalut
:同一个包的类private
:被该类自身访问protect
:同一个包或者其他包的子类
3.Java的交叉引用
- 因为Java的复合变量不会自动创建,因此成员变量的类型允许交叉引用
4.静态方法
静态方法调用的注意事项:
- 静态方法可以直接访问类变量和静态方法。
- 静态方法不能直接访问普通成员变量或成员方法,只能访问静态成员。反之,成员方法可以直接访问类变量或静态方法。
- 静态方法中,不能使用this关键字。
5.继承
- 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
- 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
- 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
- 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个 super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。
- 如果父类成员非private修饰,则子类可以随意使用父类成员。
- 子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。 super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
- 无修饰的父类成员,仅在本包中才能被子类继承
- 使用new时代吗执行顺序
- 如果当前类未加载过,检查父类是否加载,直到找到加载的父类
- 从继承线先加载父类的静态代码块再加载子类的静态代码
- 从当前类直到Object,按继承线顺序执行指定的构造函数(先执行父类的构造函数)
class Fu {
private int n;
Fu(){
System.out.println("Fu()"); } }
class Zi extends Fu {
Zi(){ // super(),调用父类构造方法
super();
System.out.println("Zi()"); } }
6.抽象类与抽象方法
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
7.接口,多态
接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并 不是类,而是另外一种引用数据类型。
-
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的默认方法重名,子类就近选择执 行父类的成员方法。
-
成员变量其实是常量,格式:
[public] [static] [final] 数据类型 常量名称 = 数据值;
注意:
常量必须进行赋值,而且一旦赋值不能改变。
常量名称完全大写,用下划线进行分隔。 -
接口中最重要的就是抽象方法,格式:
[public] [abstract] 返回值类型 方法名称(参数列表);
注意:
实现类必须覆盖重写接口所有的抽象方法,除非实现类是抽象类。 -
从Java 8开始,接口里允许定义默认方法,格式:
[public] default 返回值类型 方法名称(参数列表) { 方法体 },
注意:
默认方法也可以被覆盖重写 -
从Java 8开始,接口里允许定义静态方法,格式:
[public] static 返回值类型 方法名称(参数列表) { 方法体 },
注意:
应该通过接口名称进行调用,不能通过实现类对象调用接口静态方法 -
从Java 9开始,接口里允许定义私有很乏,格式:
普通私有方法:private 返回值类型 方法名称(参数列表) { 方法体 },
静态私有方法:private static 返回值类型 方法名称(参数列表) { 方法体 }
注意:
private的方法只有接口自己才能调用,不能被实现类或别人使用。
私有方法:只有默认方法可以调用。 私有静态方法:默认方法和静态方法可以调用。 -
子接口重写默认方法时,default关键字可以保留;
-
子类重写默认方法时,default关键字不可以保留。
-
接口中,没有构造方法,不能创建对象,没有静态代码块。
8.final关键字
被final修饰的常量名称,一般都有书写规范,所有字母都大写。
-
类:被修饰的类,不能被继承。
-
方法:被修饰的方法,不能被重写。
-
变量:被修饰的变量,不能被重新赋值。
9.权限修饰符
public | protected | (default) | private | |
---|---|---|---|---|
同一类 | √ | √ | √ | √ |
同一包(子类与无关类) | √ | √ | √ | |
不同包的子类 | √ | √ | ||
不同包的无关类 | √ |
10.内部类
- 在类中定义的类称为内部类
- 在内部类之外的类称为外部类
- 内部类可以访问其外部类的所有变量和方法,并能够以和外部类的其他非静态成员相同的方式直接引用它们
- 内部类完全在其包围类的范围之内
- 内部类还可以定义在一个方法里面
- 匿名类没有构造方法,但可以在声明成员变量时初始化和进行实例初始化
- 内部类可以访问外部类的局部变量,但是不能修改其局部变量的引用
11.外部类调用内部类
- 非静态内部类必须通过外部类对象进行构造
- 每一个内部类对象一定默认含有一个外部类对象的引用
12.自动装箱与自动拆箱
集合
集合是Java中提供的一种容器,可以用来存储多个数据。
- 数组的长度是固定的。集合的长度是可变的。
- 数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。
1.List子类
ArrayList集合
java.util.ArrayList
集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList
是最常用的集合。
LinkedList集合
java.util.LinkedList
集合数据存储的结构是链表结构。方便元素添加、删除的集合。LinkedList是一个双向链表。LinkedList提供了大量首尾操作的方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2xDe1k2R-1624366169091)(asset/image-20200324200652134.png)]
list的性能比较
存储方式 | 随机访问排名 | 迭代操作排名 | 插入操作排名 | 删除操作排名 | |
---|---|---|---|---|---|
数组 | 连续内存保存元素 | 1 | 不支持 | 不支持 | 不支持 |
ArrayList | 数组方式 | 2 | 2 | 2 | 2 |
Vector | 数组方式 | 3 | 3 | 3 | 3 |
LinkedList | 双向链表方式 | 4 | 1 | 1 | 1 |
2.Set子类
HashSet集合
java.util.HashSet
是Set
接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。java.util.HashSet
底层的实现其实是一个java.util.HashMap
支持。
HashSet
是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于:hashCode
与equals
方法。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。
HashSet集合存储数据的结构(哈希表),在JDK1.8之前,哈希表底层采用数组+链表实现。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值8时,将链表转换为红黑树。
简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iw9C8lUn-1624366169105)(asset/image-20200324201121825.png)]
LinkedHashSet
在HashSet下面有一个子类java.util.LinkedHashSet
,它是链表和哈希表组合的一个数据存储结构。
3.可变参数
定义一个方法需要接受多个参数,并且多个参数类型一致
public class ChangeArgs {
public static void main(String[] args) {
int[] arr = { 1, 4, 62, 431, 2 };
int sum = getSum(arr);
System.out.println(sum);
// 求 这几个元素和 6 7 2 12 2121
int sum2 = getSum(6, 7, 2, 12, 2121);
System.out.println(sum2);
}
//可变参数写法
public static int getSum(int... arr) {
int sum = 0;
for (int a : arr) {
sum += a;
}
return sum;
}
}
4.Comparator比较器
在JAVA中提供了两种比较实现的方式,一种是比较死板的采用java.lang.Comparable
接口去实现,一种是灵活的当我需要做排序的时候在去选择的java.util.Comparator
接口完成。
public class CollectionsDemo3 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("cba");
list.add("aba");
list.add("sba");
list.add("nba");
//排序方法 按照第一个单词的降序
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.charAt(0) - o1.charAt(0);
}
});
System.out.println(list);
}
}
Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。
Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。
public class Student implements Comparable<Student>{
....
@Override
public int compareTo(Student o) {
return this.age-o.age;//升序
}
}
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 年龄降序
int result = o2.getAge()-o1.getAge();//年龄降序
if(result==0){//第一个规则判断完了 下一个规则 姓名的首字母 升序
result = o1.getName().charAt(0)-o2.getName().charAt(0);
}
return result;
}
});
泛型
1.泛型的定义
泛型的上限:
- 格式:
类型名称 <? extends 类 > 对象名称
- 意义:
只能接收该类型及其子类
泛型的下限:
- 格式:
类型名称 <? super 类 > 对象名称
- 意义:
只能接收该类型及其父类型
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement(list1);
getElement(list2);//报错
getElement(list3);
getElement(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
2.泛型成员的限制
- 在泛型类中的泛型成员不能直接实例化,其实例化必须通过方法的参数传递给泛型成员,不可以定义泛型数组。
public class Test<T>{
private T[] array; //此处不能用new T[]实例化array
private T var; //此处不能用new T()实例化var public void setArray(T[] array) {
this.array = array;
}
public T[] getArray() {
return array;
}
- 在泛型类中的泛型类型不能使用instance of T
public class Generic3<T> {
public void isT(Object o) {
System.out.println(o instanceof T) //illegal
}
}
3. 需要注意的事项
-
Java中的普通方法,构造方法,和静态方法都可以使用泛型,方法使用泛型之前必须先对泛型进行声明,可以使用任意字母,一般都要大写。
-
由于泛型类型只有在类实例化后才能确定,类中的泛型成员只能使用Object类型中的方法:
class Generic<T>{
T f;
void setF(T f){ this.f=f; }
//....
void doSome(){
/*
getClass和toString都是Object中的方法
*/
System.out.println(f.getClass().getName());
System.out.println(f.toString());
}
}
- extends关键字用来指定泛型的父类或者实现的接口,如果有多个,不同的之间使用&分割,在实例化泛型类时,为该泛型指定的实际类型必须是指定类的⼦类或指定接口的子接⼝
- 泛型类型的上限⼀经限定,类中的泛型成员就可使⽤上限类型中的方法和其他可用成员
import java.util.List;
import static java.lang.System.out;//静态导入
public class ListGeneric<T extends List> {
private T list;
public void setList(T list) {
this.list = list;
}
public void doSome(){
//add、get方法都是List接又中定义的方法 list.add(new Integer(0));
out.println(list.get(0));//此处省略了System }
}
- 根据同一个泛型类衍生出来的多个类之间没有任何关系,不可以互相赋值。泛型只在编译器有效
- Object是所有类的父类,因此,所有的类型的实例都可赋值给声明为Object类型的变量
- 在实例化泛型类时,将泛型指定为Object类型却不存在着和其他类型之间的兼容性,如
Generic<Boolean> f1 = new Generic<Boolean>();
Generic<Integer> f2 = new Generic<Integer>();
Generic<Object> f=f1; //f1和f类型并不兼容,发生编译错误
f=f2; //f2和f类型同样不兼容,也会发生编译错误
- 由于一个泛型类所有的实例都具有相同的运行类, 所以一个泛型的静态变量和静态⽅法是所有实例共享的.静态成员不可以使⽤
- 如果要建⽴泛型类的数组,需要注意new关键字后面不要加入泛型的实际类型名,如下所示:
Generic<String>[] gs; //声明泛型类的数组 //先对泛型数组进行初始化
gs=new Generic[5]; //不可写成new Generic<String>[5] //再分别为每一个数组元素进行初始化
gs[0]=new Generic<String>();//为第一个数组元素赋值 //此处的String是为了保持和声明一致。
- 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
//定义一个泛型接口
public interface Generator<T> {
public T next();
}
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
*/
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
- 静态方式使用泛型
/**
* 泛型方法的基本介绍
* @param tClass 传入的泛型实参
* @return T 返回值为T类型
* 说明:
* 1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
* 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
* 3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
* 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
*/
public static <T> T genericMethod(Class<T> tClass)throws InstantiationException,IllegalAccessException{
T instance = tClass.newInstance();
return instance;
}
4.泛型通配符总结
- 限定通配符总是包括自身
- 上界类型通配符:传⼊泛型类型(set)⽅法受限(extend),带有子类(extend)限定的可以从泛型读取(get)
- 下界类型通配符:返回泛型类型(get)方法受限(super),带有超类(super)限定的可以从泛型写入(set)
- 如果从一个数据类型里获取数据,使⽤ ? extends 通配符
- 如果把对象写入一个数据结构里,使⽤ ? super通配符
- 如果既要存,又要取,通配符会使泛型失去意义。
- 不能同时声明泛型通配符上界和下界用
异常
1.定义
异常的根类是java.lang.Throwable
,其下有两个子类:java.lang.Error
与java.lang.Exception
,平常所说的异常指java.lang.Exception
。
2.分类
Error
:由Java虚拟机生成并抛出,Java程序不做处理RuntimeException
:(除0、数组越界):由系统检测,用户的Java程序可以不做处理,系统将它们交给缺省的异常处理程序Exception
:Java编译器要求Java程序必须捕获或者声明所有的非运行异常
3.异常处理方式
抛出异常throw
throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。对于调用者来说,一种是进行捕获处理,另一种就是继续讲问题声明出去,使用throws声明处理。
public class ThrowDemo {
public static void main(String[] args) {
//创建一个数组
int[] arr = {2,4,52,2};
//根据索引找对应的元素
int index = 4;
int element = getElement(arr, index);
System.out.println(element);
System.out.println("over");
}
/*
* 根据 索引找到数组中对应的元素
*/
public static int getElement(int[] arr,int index){
//判断 索引是否越界
if(index<0 || index>arr.length-1){
/*
判断条件如果满足,当执行完throw抛出异常对象后,方法已经无法继续运算。
这时就会结束当前方法的执行,并将异常告知给调用者。这时就需要通过异常来解决。
*/
throw new ArrayIndexOutOfBoundsException("哥们,角标越界了~~~");
}
int element = arr[index];
return element;
}
}
声明异常throws
关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).
public class ThrowsDemo {
public static void main(String[] args) throws FileNotFoundException {
read("a.txt");
}
// 如果定义功能时有问题发生需要报告给调用者。可以通过在方法上使用throws关键字进行声明
public static void read(String path) throws FileNotFoundException {
if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
// 我假设 如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常 throw
throw new FileNotFoundException("文件不存在");
}
}
}
捕获异常try…catch
多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。
try{
编写可能会出现异常的代码
}catch(异常类型A e){ 当try中出现A类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}catch(异常类型B e){ 当try中出现B类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}finally{
}
如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类不抛出异常。
父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出。
4.自定义异常类
自定义一个编译期异常: 自定义类 并继承于java.lang.Exception
。
自定义一个运行时期的异常类:自定义类 并继承于java.lang.RuntimeException
。
// 业务逻辑异常
public class RegisterException extends Exception {
/**
* 空参构造
*/
public RegisterException() {
}
/**
*
* @param message 表示异常提示
*/
public RegisterException(String message) {
super(message);
}
}
public class Demo {
// 模拟数据库中已存在账号
private static String[] names = {"bill","hill","jill"};
public static void main(String[] args) {
//调用方法
try{
// 可能出现异常的代码
checkUsername("nill");
System.out.println("注册成功");//如果没有异常就是注册成功
}catch(RegisterException e){
//处理异常
e.printStackTrace();
}
}
//判断当前注册账号是否存在
//因为是编译期异常,又想调用者去处理 所以声明该异常
public static boolean checkUsername(String uname) throws LoginException{
for (String name : names) {
if(name.equals(uname)){//如果名字在这里面 就抛出登陆异常
throw new RegisterException("亲"+name+"已经被注册了!");
}
}
return true;
}
线程类
1.步骤
Java中通过继承Thread类来创建并启动多线程的步骤如下:
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
代码如下:
public class MyThread extends Thread {
//定义指定线程名称的构造方法
public MyThread(String name) {
//调用父类的String参数的构造方法,指定线程的名称
super(name);
}
/**
* 重写run方法,完成该线程执行的逻辑
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在执行!"+i);
}
}
}
public class Demo01 {
public static void main(String[] args) {
//创建自定义线程对象
MyThread mt = new MyThread("新的线程!");
//开启新线程
mt.start();
//在主方法中执行for循环
for (int i = 0; i < 10; i++) {
System.out.println("main线程!"+i);
}
}
}
采用 java.lang.Runnable
也是非常常见的一种,我们只需要重写run方法即可。 步骤如下:
-
定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
-
创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
-
调用线程对象的start()方法来启动线程。
代码如下
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i); } } }
public class Demo {
public static void main(String[] args) {
//创建自定义类对象 线程任务对象
MyRunnable mr = new MyRunnable(); //创建线程对象
Thread t = new Thread(mr, "小强");
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("旺财 " + i); } } }
public class NoNameInnerClassThread {
public static void main(String[] args) {
Runnable r = new Runnable(){
public void run(){ for (int i = 0; i < 20; i++) {
System.out.println("张宇:"+i); } } };
new Thread(r).start();}
总结: 实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源。
- 可以避免Java中的单继承的局限性。
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
扩充:在Java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用 java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。
2.线程同步
同步代码块
synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
public class Ticket implements Runnable{
private int ticket = 100;
Object lock = new Object();
/** 执行卖票操作 */
@Override public void run() {
//窗口 永远开启
while(true){
synchronized (lock) {
if(ticket>0){//有票 可以卖
try {
Thread.sleep(50); }
catch (InterruptedException e) {
e.printStackTrace(); }
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket‐‐); } }}}}
同步方法
同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外 等着。
对于非static方法,同步锁就是this。 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
public class Ticket implements Runnable{
private int ticket = 100; /** 执行卖票操作 */
@Override
public void run() {
while(true){ sellTicket(); } }
/** 锁对象 是 谁调用这个方法 就是谁 * 隐含 锁对象 就是 this **/
public synchronized void sellTicket(){
if(ticket>0){
try {
Thread.sleep(100); }
catch (InterruptedException e) {
e.printStackTrace();
}//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket‐‐); } } }
Lock锁
java.util.concurrent.locks.Lock
机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下: public void lock() :加同步锁。 public void unlock() :释放同步锁。
public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
@Override public void run() {
while(true){
lock.lock();
if(ticket>0){
try {
Thread.sleep(50); }
catch (InterruptedException e) {
e.printStackTrace(); }
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket‐‐); }
lock.unlock(); } } }
3.线程状态
线程状态 | 条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操 作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个 状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
TimedWaiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态 将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、 Object.wait。 |
Terminated(被 终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡 |
4.等待与唤醒
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。
wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
5.线程池
Java里面线程池的顶级接口是java.util.concurrent.Executor
,但是严格意义上讲Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
。
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我要一个教练");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教练来了: " + Thread.currentThread().getName());
System.out.println("教我游泳,交完后,教练回到了游泳池");
}
}
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
// 创建Runnable实例对象
MyRunnable r = new MyRunnable();
//自己创建线程对象的方式
// Thread t = new Thread(r);
// t.start(); ---> 调用MyRunnable中的run()
// 从线程池中获取线程对象,然后调用MyRunnable中的run()
service.submit(r);
// 再获取个线程对象,调用MyRunnable中的run()
service.submit(r);
service.submit(r);
// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
// 将使用完的线程又归还到了线程池中
// 关闭线程池
//service.shutdown();
}
}
字节流与字符流
1.比较
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流 InputStream | 字节输出流 OutputStream |
字符流 | 字符输入流 Reader | 字符输出流 Writer |
public class Copy {
public static void main(String[] args) throws IOException {
// 1.创建流对象
// 1.1 指定数据源
FileInputStream fis = new FileInputStream("D:\\test.jpg");
// 1.2 指定目的地
FileOutputStream fos = new FileOutputStream("test_copy.jpg");
// 2.读写数据
// 2.1 定义数组
byte[] b = new byte[1024];
// 2.2 定义长度
int len;
// 2.3 循环读取
while ((len = fis.read(b))!=-1) {
// 2.4 写出数据
fos.write(b, 0 , len);
}
// 3.关闭资源
fos.close();
fis.close();
}
}
2. Stream流
public class Demo03StreamFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
list.stream()
.filter(s ‐> s.startsWith("张"))
.filter(s ‐> s.length() == 3)
.forEach(System.out::println); } }
序列化
1.基本概念
- Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程;
-
序列化 : 对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。
-
反序列化: 客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。
- 本质上讲,序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态。
2.算法步骤
(1)将对象实例相关的类元数据输出。
(2)递归地输出类的超类描述直到不再有超类。
(3)类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
(4)从上至下递归输出实例的数据
序列化操作代码
1、JDK类库中序列化和反序列化API
(1)java.io.ObjectOutputStream:表示对象输出流;
它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中;
(2)java.io.ObjectInputStream:表示对象输入流;
它的readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回;
2、实现序列化的要求
只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常!
3、实现Java对象序列化与反序列化的方法
假定一个User类,它的对象需要序列化,可以有如下三种方法:
(1)若User类仅仅实现了Serializable接口,则可以按照以下方式进行序列化和反序列化
ObjectOutputStream采用默认的序列化方式,对User对象的非transient的实例变量进行序列化。
ObjcetInputStream采用默认的反序列化方式,对对User对象的非transient的实例变量进行反序列化。
(2)若User类仅仅实现了Serializable接口,并且还定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),则采用以下方式进行序列化与反序列化。
ObjectOutputStream调用User对象的writeObject(ObjectOutputStream out)的方法进行序列化。
ObjectInputStream会调用User对象的readObject(ObjectInputStream in)的方法进行反序列化。
(3)若User类实现了Externalnalizable接口,且User类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化。
ObjectOutputStream调用User对象的writeExternal(ObjectOutput out))的方法进行序列化。
ObjectInputStream会调用User对象的readExternal(ObjectInput in)的方法进行反序列化
public class Employee implements java.io.Serializable {
public String name;
public String address;
public transient int age; // transient瞬态修饰成员,不会被序列化
public void addressCheck() {
System.out.println("Address check : " + name + " -- " + address);
}
}
public class SerializeDemo{
public static void main(String [] args) {
Employee e = new Employee();
e.name = "zhangsan";
e.address = "beiqinglu";
e.age = 20;
try {
// 创建序列化流对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));
// 写出对象
out.writeObject(e);
// 释放资源
out.close();
fileOut.close();
System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
} catch(IOException i) {
i.printStackTrace();
}
}
}
3.反序列化
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
public class DeserializeDemo {
public static void main(String [] args) {
Employee e = null;
try {
// 创建反序列化流
FileInputStream fileIn = new FileInputStream("employee.txt");
ObjectInputStream in = new ObjectInputStream(fileIn);
// 读取一个对象
e = (Employee) in.readObject();
// 释放资源
in.close();
fileIn.close();
}catch(IOException i) {
// 捕获其他异常
i.printStackTrace();
return;
}catch(ClassNotFoundException c) {
// 捕获类找不到异常
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
// 无异常,直接打印输出
System.out.println("Name: " + e.name); // zhangsan
System.out.println("Address: " + e.address); // beiqinglu
System.out.println("age: " + e.age); // 0
}
}
**另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException
异常。**发生这个异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
Serializable
接口给需要序列化的类,提供了一个序列版本号。serialVersionUID
该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
该版本号serialVersionUID在反序列化期间用于验证序列化对象的发送方和接收方是否已加载与该序列化兼容的该对象的类。如果接收者已经为对象加载了一个类,该类serialVersionUID与对应的发送者类的对象不同,那么反序列化将导致一个 InvalidClassException。可序列化类可以serialVersionUID通过声明名为serialVersionUID必须为static,final和type 的字段
建议明确serialVersionUID声明在可能的情况下使用private修饰符,因为此类声明仅适用于立即声明的类serialVersionUID字段作为继承成员无用。
public class Employee implements java.io.Serializable {
// 加入序列版本号
private static final long serialVersionUID = 1L;
public String name;
public String address;
// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
public int eid;
public void addressCheck() {
System.out.println("Address check : " + name + " -- " + address);
}
}
4.相关注意事项
- 序列化时,只对对象的状态进行保存,而不管对象的方法;
- 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;而子类实现了 Serializable 接口,父类也需要实现Serializable 接口。
- 当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
- 并非所有的对象都可以序列化
- 声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据。
- Java有很多基础类已经实现了serializable接口,比如String,Vector等。但是也有一些没有实现serializable接口的;
- 如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!
网络通信协议
1.定义
TCP/IP协议:传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TQIhO9do-1624366169107)(asset/image-20200325173120575.png)]
上图中,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能。
链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
运输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。
2.IP地址
- IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”。
IP地址分类
-
IPv4:是一个32位的二进制数,通常被分为4个字节,表示成
a.b.c.d
的形式,例如192.168.65.100
。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。 -
IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。
为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成
ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。
3.端口号
网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?
如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。
- **端口号:用两个字节表示的整数,它的取值范围是065535**。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。
利用协议
+IP地址
+端口号
三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。
4.Socket类
在Java中,提供了两个类用于实现TCP通信程序:
客户端:java.net.Socket
类表示。创建Socket
对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
服务端:java.net.ServerSocket
类表示。创建ServerSocket
对象,相当于开启一个服务,并等待客户端的连接。
5.TCP通信分析图解
- 【服务端】启动,创建ServerSocket对象,等待连接。
- 【客户端】启动,创建Socket对象,请求连接。
- 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
- 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
- 【服务端】Socket对象,获取InputStream,读取客户端发送的数据。
自此,服务端向客户端回写数据。
- 【服务端】Socket对象,获取OutputStream,向客户端回写数据。
- 【客户端】Socket对象,获取InputStream,解析回写数据。
- 【客户端】释放资源,断开连接。
客户端实现:
public class FileUpload_Client {
public static void main(String[] args) throws IOException {
// 1.创建流对象
// 1.1 创建输入流,读取本地文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
// 1.2 创建输出流,写到服务端
Socket socket = new Socket("localhost", 6666);
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//2.写出数据.
byte[] b = new byte[1024 * 8 ];
int len ;
while (( len = bis.read(b))!=-1) {
bos.write(b, 0, len);
}
// 关闭输出流,通知服务端,写出数据完毕
socket.shutdownOutput();
System.out.println("文件发送完毕");
// 3. =====解析回写============
InputStream in = socket.getInputStream();
byte[] back = new byte[20];
in.read(back);
System.out.println(new String(back));
in.close();
// ============================
// 4.释放资源
socket.close();
bis.close();
}
}
回写实现
public class FileUpload_Server {
public static void main(String[] args) throws IOException {
System.out.println("服务器 启动..... ");
// 1. 创建服务端ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
// 2. 循环接收,建立连接
while (true) {
Socket accept = serverSocket.accept();
/*
3. socket对象交给子线程处理,进行读写操作
Runnable接口中,只有一个run方法,使用lambda表达式简化格式
*/
new Thread(() -> {
try (
//3.1 获取输入流对象
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
//3.2 创建输出流对象, 保存到本地 .
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
BufferedOutputStream bos = new BufferedOutputStream(fis);
) {
// 3.3 读写数据
byte[] b = new byte[1024 * 8];
int len;
while ((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
}
// 4.=======信息回写===========================
System.out.println("back ........");
OutputStream out = accept.getOutputStream();
out.write("上传成功".getBytes());
out.close();
//================================
//5. 关闭 资源
bos.close();
bis.close();
accept.close();
System.out.println("文件上传已保存");
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
方法引用
1.定义
双冒号 :: 为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方 法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。
2.通过对象名引用成员方法
public class MethodRefObject {
public void printUpperCase(String str) {
System.out.println(str.toUpperCase()); } }
@FunctionalInterface
public interface Printable {
void print(String str); }
public class Demo04MethodRef {
private static void printString(Printable lambda) {
lambda.print("Hello"); }
public static void main(String[] args) {
MethodRefObject obj = new MethodRefObject();
printString(obj::printUpperCase); } }
3.通过类名称引用静态方法
@FunctionalInterface
public interface Calcable {
int calc(int num); }
public class Demo06MethodRef {
private static void method(int num, Calcable lambda) {
System.out.println(lambda.calc(num)); }
public static void main(String[] args) {
method(‐10, Math::abs); } }
4. 通过super引用成员方法
@FunctionalInterface
public interface Greetable {
void greet(); }
public class Human {
public void sayHello() {
System.out.println("Hello!"); } }
public class Man extends Human {
@Override
public void sayHello() { S
ystem.out.println("大家好,我是Man!"); }
//定义方法method,参数传递Greetable接口
public void method(Greetable g){ g.greet(); }
public void show(){ method(super::sayHello); } }
5.通过this引用成员方法
@FunctionalInterface
public interface Richable { void buy(); }
public class Husband {
private void buyHouse() {
System.out.println("买套房子"); }
private void marry(Richable lambda) {
lambda.buy(); }
public void beHappy() { marry(this::buyHouse); } }
6. 类的构造器引用
public class Person {
private String name;
public Person(String name) {
this.name = name; }
public String getName() {
return name; }
public void setName(String name) {
this.name = name; } }
public interface PersonBuilder {
Person buildPerson(String name); }
public class Demo10ConstructorRef {
public static void printName(String name, PersonBuilder builder) {
System.out.println(builder.buildPerson(name).getName()); }
public static void main(String[] args) {
printName("赵丽颖", Person::new); } }
7.数组的构造器引用
@FunctionalInterface
public interface ArrayBuilder {
int[] buildArray(int length); }
public class Demo12ArrayInitRef {
private static int[] initArray(int length, ArrayBuilder builder) {
return builder.buildArray(length); }
public static void main(String[] args) { i
nt[] array = initArray(10, int[]::new); } }
反射
1.定义
反射:将类的各个组成部分封装为其他对象,这就是反射机制
好处:
- 可以在程序运行过程中,操作这些对象。
- 可以解耦,提高程序的可扩展性。
2.获取Class对象的方式:
- Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象
多用于配置文件,将类名定义在配置文件中。读取文件,加载类
- 类名.class:通过类名的属性class获取
多用于参数的传递
- 对象.getClass():getClass()方法在Object类中定义着。
多用于对象的获取字节码的方式
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
3.Class对象功能:
- 获取成员方法们:
-
Method[] getMethods() 获得当前类以及超类的public方法
-
Method getMethod(String name, 类<?>… parameterTypes)
-
Method[] getDeclaredMethods() 获取当前类声明的所有方法
-
Method getDeclaredMethod(String name, 类<?>… parameterTypes)
- 获取全类名
String getName() - 获取成员变量们
-
Field[] getFields() :获取当前类以及超类的所有public修饰的成员变量
-
Field getField(String name) 获取指定名称的 public修饰的成员变量
-
Field[] getDeclaredFields() 获取当前类所有的成员变量,不考虑修饰符
-
Field getDeclaredField(String name)
- 获取构造方法们
- Constructor<?>[] getConstructors() 获取类的public类型的构造方法
- Constructor< T > getConstructor(类<?>… parameterTypes)
- Constructor< T > getDeclaredConstructor(类<?>… parameterTypes)
- Constructor< ? >[] getDeclaredConstructors()
- Constructor<?>[]newInstance() 通过类的不带参数的构造方法创建这个类的一个对象
4. Field:成员变量
-
设置值:void set(Object obj, Object value)
-
获取值:get(Object obj)
- 忽略访问权限修饰符的安全检查:setAccessible(true):暴力反射
5.Constructor:构造方法
-
创建对象:T newInstance(Object… initargs)
-
如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
Class.newInstance();
Class.getConstructor(new Class[]{}).newInstance(new Object[]{});
6.Method:方法对象
-
执行方法:Object invoke(Object obj, Object… args)
-
获取方法名称:String getName
7.使用方法
创建图形用户界面
1. Java中常用的布局
- FlowLayout
- BorderLayout
- GridLayout
- GridBagLayout
- CardLayout
2.事件监听和处理
- 低级事件
- ComponentEvent(组件事件:组件尺寸的变化,移动)
- ContainerEvent(容器事件:组件增加,移动)
- WindowEvent(窗口事件:关闭窗口,窗口闭合,图标化)
- FocusEvent(焦点事件:焦点的获得和丢失)
- KeyEvent(键盘事件:键按下、释放)
- MouseEvent(鼠标事件:鼠标单击,移动)
- 高级事件
- ActionEvent(动作事件:按钮按下,TextField中按Enter键)
- AdjustmentEvent(调节事件:在滚动条上移动滑块以调节数值)
- ItemEvent(项目事件:选择项目,不选择,“项目改变”)
- TextEvent(文本事件,文本对象改变)
3. 注册和注销监听器
- 注册监听器:
public void add<ListenerType>
(<ListenerType>listener);
- 注销监听器:
public void remove<ListenerType>
(<ListenerType>listener);
JDBC
1.使用
//1. 导入驱动jar包
//2.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3", "root", "root");
//4.定义sql语句
String sql = "update account set balance = 500 where id = 1";
//5.获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//6.执行sql
int count = stmt.executeUpdate(sql);
//7.处理结果
System.out.println(count);
//8.释放资源
stmt.close();
conn.close();
2.步骤
- DriverManager:驱动管理对象
功能:- 注册驱动:告诉程序该使用哪一个数据库驱动jar
static void registerDriver(Driver driver) :注册给定的驱动程序
写代码使用: Class.forName(“com.mysql.jdbc.Driver”);
通过查看源码发现:在com.mysql.jdbc.Driver类中存在静态代码块
- 注册驱动:告诉程序该使用哪一个数据库驱动jar
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
注意:mysql5之后的驱动jar包可以省略注册驱动的步骤。
2. 获取数据库连接:
* 方法:static Connection getConnection(String url, String user, String password)
3. Connection:数据库连接对象
功能:
1. 获取执行sql 的对象
* Statement createStatement()
* PreparedStatement prepareStatement(String sql)
2. 管理事务:
* 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为 false,即开启事务
* 提交事务:commit()
* 回滚事务:rollback()
3. Statement:执行sql的对象
1. boolean execute(String sql) :可以执行任意的sql 了解
2. int executeUpdate(String sql) :执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句
3. ResultSet executeQuery(String sql) :执行DQL(select)语句
- ResultSet:结果集对象,封装查询结果
while(rs.next()){//循环判断游标是否是最后一行末尾。
//获取数据
int id = rs.getInt(1);
String name = rs.getString("name");
double balance = rs.getDouble(3);}
- PreparedStatement:执行sql的对象
预编译的SQL:参数使用?作为占位符
步骤: - 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
- 注册驱动
- 获取数据库连接对象 Connection
- 定义sql
- 注意:sql的参数使用?作为占位符。
- 获取执行sql语句的对象 Connection.prepareStatement(String sql)
- 给?赋值:
- 方法: setXxx(参数1,参数2)
- 参数1:?的位置编号 从1 开始
- 参数2:?的值
- 方法: setXxx(参数1,参数2)
- 执行sql,接受返回结果,不需要传递sql语句
- 处理结果
- 释放资源
3.JDBCUtils
public class JDBCUtils {
private static String url;
private static String user;
private static String password;
private static String driver;
/**
* 文件的读取,只需要读取一次即可拿到这些值。使用静态代码块
*/
static{
try {
//1. 创建Properties集合类。
Properties pro = new Properties();
//获取src路径下的文件的方式--->ClassLoader 类加载器
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
URL res = classLoader.getResource("jdbc.properties");
String path = res.getPath();
pro.load(new FileReader(path));
//3. 获取数据,赋值
url = pro.getProperty("url");
user = pro.getProperty("user");
password = pro.getProperty("password");
driver = pro.getProperty("driver");
//4. 注册驱动
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取连接
* @return 连接对象
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
}
/**
* 释放资源
* @param stmt
* @param conn
*/
public static void close(Statement stmt,Connection conn){
close(null,stmt,conn);
}
/**
* 释放资源
* @param stmt
* @param conn
*/
public static void close(ResultSet rs,Statement stmt, Connection conn){
if( rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if( conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
使用工具类
public boolean login(String username ,String password){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
String sql = "select * from user where username = '"+username+"' and password = '"+password+"' ";
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
return rs.next();//如果有下一行,则返回true
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.close(rs,stmt,conn);
}
return false;}
4. JDBC控制事务
public class JDBCDemo10 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt1 = null;
PreparedStatement pstmt2 = null;
try {
//1.获取连接
conn = JDBCUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
//2.定义sql
//2.1 张三 - 500
String sql1 = "update account set balance = balance - ? where id = ?";
//2.2 李四 + 500
String sql2 = "update account set balance = balance + ? where id = ?";
//3.获取执行sql对象
pstmt1 = conn.prepareStatement(sql1);
pstmt2 = conn.prepareStatement(sql2);
//4. 设置参数
pstmt1.setDouble(1,500);
pstmt1.setInt(2,1);
pstmt2.setDouble(1,500);
pstmt2.setInt(2,2);
//5.执行sql
pstmt1.executeUpdate();
// 手动制造异常
int i = 3/0;
pstmt2.executeUpdate();
//提交事务
conn.commit();
} catch (Exception e) {
//事务回滚
try {
if(conn != null) {
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtils.close(pstmt1,conn);
JDBCUtils.close(pstmt2,null);
}
}}
5. 数据库连接池
实现:
-
标准接口:DataSource javax.sql包下的
-
方法:
获取连接:getConnection()
归还连接:Connection.close()。如果连接对象Connection是从连接池中获取的,那么调用Connection.close()方法,则不会再关闭连接了,而是归还连接。
-
-
一般我们不去实现它,有数据库厂商来实现
- C3P0:数据库连接池技术
- Druid:数据库连接池实现技术,由阿里巴巴提供的
-
C3P0:数据库连接池技术
-
步骤:
- 导入jar包 (两个) c3p0-0.9.5.2.jar mchange-commons-java-0.2.12.jar ,
不要忘记导入数据库驱动jar包
2. 定义配置文件:名称: c3p0.properties 或者 c3p0-config.xml
路径:直接将文件放在src目录下即可。
- 创建核心对象 数据库连接池对象 ComboPooledDataSource
- 获取连接: getConnection
-
代码:
DataSource ds = new ComboPooledDataSource();
Connection conn = ds.getConnection();
- Druid:数据库连接池实现技术,由阿里巴巴提供的
- 步骤:
-
导入jar包 druid-1.0.9.jar
-
定义配置文件:
是properties形式的
可以叫任意名称,可以放在任意目录下
-
加载配置文件。Properties
-
获取数据库连接池对象:通过工厂来来获取 DruidDataSourceFactory
-
获取连接:getConnection
Properties pro = new Properties();
InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(is);
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
Connection conn = ds.getConnection();
6.JDBCUtils连接池工具类
public class JDBCUtils {
//1.定义成员变量 DataSource
private static DataSource ds ;
static{
try {
//1.加载配置文件
Properties pro = new Properties();
pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
//2.获取DataSource
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取连接
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
/**
* 释放资源
*/
public static void close(Statement stmt,Connection conn){
close(null,stmt,conn);
}
public static void close(ResultSet rs , Statement stmt, Connection conn){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();//归还连接
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 获取连接池方法
*/
public static DataSource getDataSource(){
return ds;
}
}