提示:本人在学习中结合网上大佬们的资料和自己的理解对于部分概念知识的小结,还是个菜鸟如有不对的地方,还请谅解并告知。(一直更新)
文章目录
- 1.Jdk,jre,jvm是什么
- 2. java数据类型
- 3 常用编码介绍
- 4.逻辑运算符
- 5.类和对象的区别和联系
- 6.jvm的内存分区
- 7.方法的调用机制原理
- 8.成员方法传参机制
- 9.属性(成员变量)和局部变量
- 10.构造器(构造方法)
- 11.对象的创建流程
- 12.this的注意事项和使用细节
- 13.package和import
- 14 访问修饰符
- 15. oop三大特征(封装,继承,多态)
- 16 super关键字
- 17 super与this的对比
- 18 重写(覆盖)与重载的对比
- 19 ==和equals有什么区别
- 20.toString方法
- 21 finalize 方法
- 22 类变量和类方法
- 23. 什么情况下类会被加载
- 24.代码块
- 25 final关键字
- 26 抽象类
- 27.接口
- 28.抽象类和接口对比
- 29 内部类(是我们类的第五大成员)
- 30. 异常和异常处理
- 31.自定义异常
- 32.throw和thows的区别
- 33. String
- 34.String、StringBuffer、StringBuilder的对比
- 35.可变参数
- 36.日期类
- 37.泛型
- 38.自定义泛型
- 39.多线程基础
- 40 线程生命周期
- 41. 线程同步
- 42.druid数据库连接池工具类写法(JDBC)
1.Jdk,jre,jvm是什么
JDK 的全称(Java Development Kit Java 开发工具包)
JDK = JRE + java 的开发工具 [java, javac,javadoc,javap 等]
JDK 是提供给 Java 开发人员使用的,其中包含了 java 的开发工具,也包括了 JRE。所以安装了 JDK,就不用在单独安装 JRE 了。
JRE 基本介绍
JRE(Java Runtime Environment Java 运行环境)
JRE = JVM + Java 的核心类库[类]
包括 Java 虚拟机(JVM Java Virtual Machine)和 Java 程序所需的核心类库等,如果想要运行一个开发好的 Java 程序,计算机中只需要安装 JRE 即可。
JVM基本介绍
JVM(java virtual machine java虚拟机)
JVM 是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指令,管理数据、内存、寄存器,包含在JDK 中.
对于不同的平台,有不同的虚拟机。
Java 虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,到处运行”
JDK、JRE 和 JVM 的包含关系
JDK = JRE + 开发工具集(例如 Javac,java 编译工具等)
JRE = JVM + Java SE 标准类库(java 核心类库)
如果只想运行开发好的 .class 文件 只需要 JRE
2. java数据类型
3 常用编码介绍
4.逻辑运算符
- 短路与 && , 短路或 ||,取反 !
- 逻辑与 &,逻辑或 |,^ 逻辑异或
&& 和 & 使用区别
- &&短路与:如果第一个条件为 false,则第二个条件不会判断,最终结果为 false,效率高
- & 逻辑与:不管第一个条件是否为 false,第二个条件都要判断,效率低
- 开发中, 我们使用的基本是使用短路与&&, 效率高
|| 和 | 使用区别
- ||短路或:如果第一个条件为 true,则第二个条件不会判断,最终结果为 true,效率高
- | 逻辑或:不管第一个条件是否为 true,第二个条件都要判断,效率低
- 开发中,我们基本使用 ||
5.类和对象的区别和联系
- 类是抽象的,概念的,代表一类事物,比如人类,猫类…, 即它是数据类型.
- 对象是具体的,实际的,代表一个具体事物, 即 是实例.
- 类是对象的模板,对象是类的一个个体,对应一个实例
6.jvm的内存分区
JVM内存区域分为五个部分,分别是堆,方法区(元空间),虚拟机栈,本地方法栈,程序计数器。
堆。 堆是Java对象的存储区域,任何用new字段分配的Java对象实例和数组,都被分配在堆上,Java堆可使用-Xms -Xmx进行内存控制,值得一提的是从JDK1.7版本之后,运行时常量池从方法区移到了堆上。
方法区。它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,方法区在JDK1.7版本及以前被称为永久代,从JDK1.8永久代被移除。
虚拟机栈。虚拟机栈中执行每个方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
本地方法栈。与虚拟机栈发挥的作用相似,相比于虚拟机栈为Java方法服务,本地方法栈为虚拟机使用的Native方法服务,执行每个本地方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
程序计数器。指示Java虚拟机下一条需要执行的字节码指令。
以上五个区域是Java虚拟机内存划分情况,其中方法区和堆被JVM中多个线程共享,比如类的静态常量就被存放在方法区,供类对象之间共享,虚拟机栈,本地方法栈,pc寄存器是每个线程独立拥有的,不会与其他线程共享。
所以Java在通过new创建一个类对象实例的时候,一方面会在虚拟机栈中创建一个该对象的引用,另一方面会在堆上创建类对象的实例,然后将对象引用指向该对象的实例。对象引用存放在每一个方法对应的栈帧中。
7.方法的调用机制原理
8.成员方法传参机制
1.基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参!
2.引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参!
9.属性(成员变量)和局部变量
1.在java编程中,主要的变量就是属性(成员变量)和局部变量。
2.我们说的局部变量一般是指在成员方法中定义的变量。全局变量:也就是属性,作用域为整个类体类
局部变量:也就是除了属性之外的其他变量,作用域为定义它的代码块中!
全局变量(属性)可以不赋值,直接使用,因为有默认值;
局部变量必须赋值后,才能使用,因为没有默认值。
属性和局部变量可以重名,访问时遵循就近原则。
在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名。
属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。
局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁。即在一次方法调用过程中。作用域范围不同
全局变量/属性:可以被本类使用,或其他类使用(通过对象调用)
局部变量:只能在本类中对应的方法中使用修饰符不同
全局变量/属性可以加修饰符
局部变量不可以加修饰符
10.构造器(构造方法)
1.一个类可以定义多个不同的构造器,即构造器重载
比如:我们可以再给Person类定义一个构造器,用来创建对象的时候,只指定人名,不需要指定年龄
2.构造器名和类名要相同
3.构造器没有返回值
4.构造器是完成对象的初始化,并不是创建对象
5.在创建对象时,系统自动的调用该类的构造方法
6.如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器).
7.一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,除非显式的定义一下。
11.对象的创建流程
class Person{/类Person
int age=90;
String name;
Person(String n,int a){/构造器
name=n;//给属性赋值
age=a;
}
}
Person p=new Person("小倩",20);
流程分析(面试题)
1.加载Person类信息(Person.class),只会加载一次2.在堆中分配空间(地址)
3.完成对象初始化
3.1默认初始化 age=0 name=null
3.2显式初始化age=90,name=null,
3.3构造器的初始化 age =20, name=小倩
4.在对象在堆中的地址,返回给p(p是对象名,也可以理解成是对象的引用)
12.this的注意事项和使用细节
- this 关键字可以用来访问本类的属性、方法、构造器。
- this 用于区分当前类的属性和局部变量。
- 访问成员方法的语法:this.方法名(参数列表);
- 访问构造器语法:this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一条语句)。
- this 不能在类定义的外部使用,只能在类定义的方法中使用。
13.package和import
-
package关键字表示打包 ,后面跟的是包名
-
import是导包语句,引入包里面的类,供我们使用
-
package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package
-
import指令位置放在package的下面,在类定义前面,可以有多句且没有顺序要求。
-
建议:我们需要使用到哪个类,就导入哪个类即可,不建议使用 *导入
//import java.util.Scanner; //表示只会引入 java.util 包下的 Scanner
//import java.util.*;//表示将 java.util 包下的所有类都引入(导入)
14 访问修饰符
java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
- 公开级别:用 public 修饰,对外公开
- 受保护级别:用 protected 修饰,对子类和同一个包中的类公开
- 默认级别:没有修饰符号,向同一个包的类公开.
- 私有级别:用 private 修饰,只有类本身可以访问,不对外公开.
15. oop三大特征(封装,继承,多态)
封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作。
封装的实现步骤 (三步)
- 将属性进行私有化private【不能直接修改属性】
- 提供一个公共的(public)set方法,用于对属性判断并赋值
- 提供一个公共的(public)get方法,用于获取属性的值
继承当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。
- 子类就会自动拥有父类定义的属性和方法(包括私有的)
- 父类又叫超类,基类。
- 子类又叫派生类。
- 子类可以访问的父类成员
可直接访问:
public 或 protected 修饰的成员
同包中缺省访问权限修饰符的成员
不可以直接访问:(通过父类提供公共的方法去访问)
不同包中缺省访问权限修饰符的成员
private 修饰成员
更多细节看这里
继承之属性(变量,方法)的调用
- 首先看子类是否有该属性
- 如果子类有这个属性,并且可以访问,则返回信息
- 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息…)
- 如果父类没有就按照(3)的规则,继续找上级父类,直到 Object…
多态是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
- 方法的多态
重写和重载就体现多态 - 对象的多态
一个对象的编译类型和运行类型可以不一致。
编译类型在定义对象时,就确定了,不能改变。
运行类型是可以变化的。
编译类型看定义时=号的左边。
运行类型看=号的右边。
//体验对象多态特点
class Animal {
public void cry() {
System.out.println("Animal cry() 动物在叫....");
}
}
class Cat extends Animal {
public void cry() {
System.out.println("Cat cry() 小猫喵喵叫...");
}
}
class Dog extends Animal {
public void cry() {
System.out.println("Dog cry() 小狗汪汪叫...");
}
}
//animal 编译类型就是 Animal , 运行类型 Dog
Animal animal = new Dog();
//因为运行时 , 执行到改行时,animal 运行类型是 Dog,所以 cry 就是 Dog 的 cry
animal.cry(); //小狗汪汪叫
//animal 编译类型 Animal,运行类型就是 Cat
animal = new Cat();
animal.cry(); //小猫喵喵叫
-
多态的前提是:两个(多个)对象(类)存在继承关系
多态的向上转型1) 本质:父类的引用指向了子类的对象 2) 语法:父类类型 引用名= new 子类类型(); 3) 特点:编译类型看左边,运行类型看右边。 可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员;最终运行效果看子类的具体实现!
多态的向下转型
1) 语法:子类类型 引用名=(子类类型) 父类引用; 2) 只能强转父类的引用,不能强转父类的对象 3) 要求父类的引用必须指向的是当前目标类型的对象 4) 当向下转型后,可以调用子类类型中所有的成员
class Animal {
public void eat(){
System.out.println("吃");
}
public void show(){
System.out.println("hello,你好");
}
}
class Cat extends Animal {
public void eat(){//方法重写
System.out.println("猫吃鱼");
}
public void catchMouse(){//Cat 特有方法
System.out.println("猫抓老鼠");
}
}
class Dog extends Animal {
//Dog 是 Animal 的子类
}
//向上转型: 父类的引用指向了子类的对象
//语法:父类类型引用名 = new 子类类型();
Animal animal = new Cat();
Object obj = new Cat();//可以吗? 可以 Object 也是 Cat 的父类
//多态的向下转型
//(1)语法:子类类型 引用名 =(子类类型)父类引用;
//问一个问题? cat 的编译类型 Cat,运行类型是 Cat
Cat cat = (Cat) animal;
//调用Cat的独有方法,猫抓老鼠(方法的调用,仍是从子类开始向上查找)
cat.catchMouse();
//(2)要求父类的引用必须指向的是当前目标类型的对象
Dog dog = (Dog) animal; //可以吗?不可以,是错误的
属性没有重写之说!属性的值看编译类型
public class MyTest{
public static void main(String[] args) {
//属性没有重写之说!属性的值看编译类型
Base base = new Sub();//向上转型
System.out.println(base.count);// ? 看编译类型 10
Sub sub = new Sub();
System.out.println(sub.count);//? 20
}
}
class Base { //父类
int count = 10;//属性
}
class Sub extends Base {//子类
int count = 20;//属性
}
instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型
16 super关键字
super 代表父类的引用,用于访问父类的属性、方法、构造器。
- 访问父类的属性,但不能访问父类的private属性
super.属性名; - 访问父类的方法,不能访问父类的private方法
super.方法名(参数列表); - 访问父类的构造器(这点前面用过):
super(参数列表);只能放在构造器的第一句,只能出现一句! - super 的访问不限于直接父类
如果多个基类(上级类)中都有同名的成员,使用 super 访问遵循就近原则。 - 当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this、直接访问是一样的效果。
17 super与this的对比
18 重写(覆盖)与重载的对比
19 ==和equals有什么区别
20.toString方法
- 基本介绍
默认返回:全类名+@+哈希值的十六进制,
子类往往重写 toString 方法,用于返回对象的属性信息 - 重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString 形式
- 当直接输出一个对象时,toString 方法会被默认的调用, 比如 System.out.println(monster); 就会默认调用monster.toString()。
21 finalize 方法
- 当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作。
- 什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用 finalize 方法。
- 垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制
22 类变量和类方法
类变量也就是静态变量(static修饰的)
-
static变量是同一个类所有对象共享
-
static类变量,在类加载的时候就生成了.
-
访问(推荐使用:类名.类变量名)
类名.类变量名
或者对象名.类变量名 -
类变量与实例变量(普通属性)区别
类变量是该类的所有对象共享的,而实例变量(成员变量)是每个对象独享的。 -
类变量的生命周期是随类的加载开始,随着类消亡而销毁。
类方法:(加static修饰的方法)
- 使用方式:
类名.类方法名(推荐)
对象名.类方法名 - 在程序员实际开发,往往会将一些通用的方法,设计成静态方法,
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:
类方法中无this的参数
普通方法中隐含着this的参数 - 类方法可以通过类名调用,也可以通过对象名调用。
- 普通方法和对象有关,需要通过对象名调用,比如 对象名.方法名(参数),不能通过类名调用。
- 类方法中不允许使用和对象有关的关键字,比如this和super。普通方法(成员方法)可以。
- 类方法(静态方法)中只能访问静态变量或静态方法。
- 普通成员方法,既可以访问非静态成员,也可以访问静态成员。
23. 什么情况下类会被加载
-
定义了main的类,启动main方法时该类会被加载
-
创建类的实例,即new对象的时候
-
创建子类的实例
在创建子类实例的时候,在加载子类之前会优先加载其父类。 -
访问类的静态方法
-
访问类的静态变量
-
反射 Class.forName()
总结
java类在以上6种情况下会被加载。
在jvm生命周期中每个类如果存在,则不会重复加载。
在加载子类的时候会优先加载其父类。
类被加载的时候,其中的静态代码块、静态方法及静态变量也会被加载。
在初始化某个类时,如果这个类的静态代码块、静态方法或静态变量引用到了另一个类,则这个类也会被加载。
24.代码块
- 代码化块又称为初始化块,属于类中的成员[即是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{ }包围起来。
- 但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用(创建对象时会进行类加载)。
- 语法:
[static]{
//方法体
};
/*
1)修饰符可选,要写的话,也只能写static
2)代码块分为两类,
使用static修饰的叫静态代码块,
没有static修饰的,叫普通代码块/非静态代码块
3)逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
4);号可以写上,也可以省略。
*/
- 理解:相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
- static代码块也叫静态代码块:
作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。
普通代码块,每创建一个对象,就会被执行一次。 - 在创建对象时(静态代码块,普通代码块,构造器的调用顺序)
创建一个对象时,在一个类调用顺序是:(重点,难点)∶
1、先调用静态代码块和静态属性初始化
(注意:静态代码块和静态属性初始化调用的优先级一样,
如果有多个静态代码块和多个静态变量初始化,
则按他们定义的顺序调用)
2、再调用普通代码块和普通属性的初始化
(注意:普通代码块和普通属性初始化调用的优先级一样,
如果有多个普通代码块和多个普通属性初始化,
则按定义顺序调用)
3、最后调用构造方法。
25 final关键字
final放在静态变量前,有两次赋值机会:声明时赋值、在static代码块中赋值。
final放在成员变量前,有三次赋值机会:声明时赋值、普通代码块中赋值、构造器中赋值。
final放在方法参数前,只有传参时的一次机会。
final放在局部变量前,有两次赋值机会:声明时赋值、后面方法的代码中赋值(只一次)。
final修饰的属性又叫常量,一般用XX XX XX来命名
26 抽象类
- 用abstract关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract class 类名{
抽象方法;}
- 用abstract关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名 (参数列表); //没有方法体
- 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()
- 抽象类不能被实例化(也就是不能被new,可以被继承)
- 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
- 一旦类包含了abstract方法,则这个类必须声明为abstract
- abstract 只能修饰类和方法,不能修饰属性和其它的。
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。
- 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。
27.接口
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。
接口体现了程序设计的多态和高内聚低偶合的设计思想。
//定义接口类
interface 接口名{
//静态属性(常量)
//抽象方法(不能有方法体)
//静态方法(可以有方法体)
}
//实现接口
class 类名implements 接口{
自己属性;
自己方法;
必须实现的接口的抽象方法;
}
小结
:接口是更加抽象的抽象的类,抽象类里的方法可以有方法体。
jdk8.0之前(不包含8.0)接口里的所有方法都是抽象方法都没有方法体。
Jdk8.0后(包含8.0)接口类可以有静态方法(static),默认方法(default),也就是说接口中可以有方法的具体实现。
接口细节:
-
接口不能被实例化(也就是不能被new)
-
接口中所有的方法是 public方法,接口中抽象方法,可以不用abstract修饰
示例:
void aaa();实际上是public abstract void aa(); -
一个普通类实现接口,就必须将该接口的所有方法都实现。
-
抽象类实现接口,可以不用实现接口的方法。
-
一个类同时可以实现多个接口
-
接口中的属性,只能是final的,而且是 public static final修饰符(也就是常量了)。
比如:int a=1;实际上是 public static final int a=1;(必须初始) -
接口中属性的访问形式:接口名.属性名
-
接口不能继承其它的类,但是可以继承多个别的接口
interface A extends B,C
28.抽象类和接口对比
相同:
1.不能够实例化
2.可以将抽象类和接口类型作为引用类型
3.一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。
不同:
抽象类(可以理解为和普通类一样,只是多了抽象方法):
- 抽象类中可以定义构造器
- 可以有抽象方法和具体方法
- 抽象类中的成员可以是private、默认、protected、Public
- 抽象类中可以定义成员变量(属性)
- 有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法
- 抽象类中可以包含静态方法
- 一个类只能继承一个抽象类
接口:
- 接口中不能定义构造器
- 方法全部默认都是抽象方法
jdk8.0之前(不包含8.0)接口里的所有方法都是抽象方法都没有方法体。
Jdk8.0后(包含8.0)接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现。 - 接口中的成员全都是默认public修饰的,也只能是public
- 接口中定义的成员变量实际上都是常量(public static final)
- 一个类可以实现多个接口
- 一个普通类实现接口,就必须将该接口的所有方法都实现。
- 抽象类实现接口,可以不用实现接口的方法。
29 内部类(是我们类的第五大成员)
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。
思考:类的五大成员是哪些?
[属性、方法、构造器、代码块、内部类]
内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系
- 如果定义类在局部位置(方法中/代码块) :
(1) 局部内部类 (有类名)
(2) 匿名内部类(表面上无类名,底层还是有自动分配的的类名的(含有$的类名)) - 定义在成员位置 :
(1) 成员内部类 (没有static修饰)
(2) 静态内部类(有static修饰)
局部内部类
- 可以直接访问外部类的所有成员,包含私有的。
- 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final修饰。
- 作用域:仅仅在定义它的方法或代码块中。
- 局部内部类----访问---->外部类的成员[访问方式:直接访问]
- 外部类----访问---->局部内部类的成员
访问方式:创建对象,再访问(注意:必须在作用域内)
(1)局部内部类定义在方法中/代码块
(2)作用域在方法体或者代码块中
(3)本质仍然是一个类- 外部其他类—不能访问----->局部内部类(因为局部内部类地位是一个局部变量)。
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,
- 如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
匿名内部类
- 本质是类( 内部类),该类没有名字,同时还是一个对象
说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
匿名内部类的基本语法new 类或接口(参数列表){ 类体 }
- 匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征
- 可以直接访问外部类的所有成员,包含私有的
- 不能添加访问修饰符,因为它的地位就是一个局部变量。
- 作用域:仅仅在定义它的方法或代码块中。
- 匿名内部类—访问---->外部类成员[访问方式:直接访问]
- 外部其他类—不能访问----->匿名内部类(因为匿名内部类地位是一个局部变量)
- 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,
- 如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
举例:
匿名内部类有多种形式,其中最常见的一种形式莫过于在方法参数中新建一个接口对象 / 类对象,并且实现这个接口声明 / 类中原有的方法
/**
* @author hyl
* @version 1.0
* @date 2022/10/30-20:45
*/
public class Java02 {
public int field1 = 1;
protected int field2 = 2;
int field3 = 3;
private int field4 = 4;
public Java02() {
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
}
// 自定义接口
interface OnClickListener {
void onClick(Object obj);
}
private void anonymousClassTest() {
// 在这个过程中会新建一个匿名内部类对象,
// 这个匿名内部类实现了 OnClickListener 接口并重写 onClick 方法
OnClickListener clickListener = new OnClickListener() {
// 可以在内部类中定义属性,但是只能在当前内部类中使用,
// 无法在外部类中使用,因为外部类无法获取当前匿名内部类的类名,
// 也就无法创建匿名内部类的对象
//field1与外部的field重名,就近原则
int field1 = 11;
@Override
public void onClick(Object obj) {
System.out.println("对象 " + obj + " 被点击");
System.out.println("其匿名内部类的 field1 字段的值为: " + field1);
System.out.println("其外部类的 field1 字段的值为: " + Java02.this.field1);
System.out.println("其外部类的 field2 字段的值为: " + field2);
System.out.println("其外部类的 field3 字段的值为: " + field3);
System.out.println("其外部类的 field4 字段的值为: " + field4);
}
};
// new Object() 过程会新建一个匿名内部类,继承于 Object 类,
// 并重写了 toString() 方法
clickListener.onClick(new Object() {
@Override
public String toString() {
return "obj1";
}
});
}
public static void main(String[] args) {
Java02 outObj = new Java02();
outObj.anonymousClassTest();
}
}
成员内部类
- 成员内部类是定义在外部类的成员位置,并且没有static修饰。
- 可以直接访问外部类的所有成员,包含私有的
- 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
- 作用域和外部类的其他成员一样,为整个类体.
- 成员内部类----访问---->外部类成员(比如:属性)[访问方式:直接访问]
- 外部类----访问------>成员内部类[访问方式:创建对象,再访问]
- 外部其他类—访问---->成员内部类
- 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则
- 如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
静态内部类
说明:静态内部类是定义在外部类的成员位置,并且有static修饰
- 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
- 可以添加任意访问修饰符(public.protected、默认、private),因为它的地位就是一个成员。
- 作用域:同其他的成员,为整个类体
- 静态内部类—访问---->外部类(比如:静态属性)[访问方式:直接访问所有静态成员]
- 外部类—访问------>静态内部类访问方式:创建对象,再访问
- 外部其他类—访问----->静态内部类
- 如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则
- 如果想访问外部类的成员,则可以使用(外部类名.成员)去访问
30. 异常和异常处理
Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)
执行过程中所发生的异常事件可分为两大类
- Error(错误):Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如: StackOverflowError[栈溢出]和OOM(out of memory). Error是严重错误,程序会崩溃。
- Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等
Exception 分为两大类:
运行时异常[程序运行时,发生的异常]
编译时异常[编程时,编译器检查出的异常]。
可查(编译)异常: CheckedException
可查(编译)异常即必须进行处理的异常,要么try catch住,要么往外抛,谁调用,谁处理。如果不处理,编译器,就不让你通过。
常见查看(编辑)异常:
- SQLException//操作数据库时,查询表可能发生异常
- IOException //操作文件时,发生的异常
- FileNotFoundException //当操作一个不存在的文件时,发生异常
- ClassNotFoundException /加载类,而该类不存在时,异常
- EOFException//操作文件,到文件未尾,发生异常
- IllegalArguementException//参数异常
运行时异常:RuntimeException
运行时异常不是必须进行try catch的异常。在编写代码的时候,依然可以使用try catch throws进行处理,与可查异常不同之处在于,即便不进行try catch,也不会有编译错误。
常见运行时异常:
- NullPointerException 空指针异常
- ArithmeticException 数学运算异常
- ArrayIndexOutOfBoundsException 数组下标越界异常
- ClassCastException 类型转换异常
- NumberFormatException 数字格式不正确异常
异常的处理方式
- try-catch-finally
程序员在代码中捕获发生的异常,自行处理 - throws
将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM
- 如果异常发生了,则异常发生后面的代码不会执行,直接进入到catch块.
- 如果异常没有发生,则顺序执行try的代码块,不会进入到catch.
- 如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等)则使用如下代码finally {}
- 可以有多个catch语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前,比如(Exception在后,NullPointerException在前),如果发生异常,只会匹配一个catch.
- 如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
- 在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
小结
- 对于编译异常,程序中必须处理,比如try-catch或者throws
- 对于运行时异常,程序中如果没有处理,默认就是throws的方式处理
- 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型
- 在throws 过程中,如果有方法 try-catch,就相当于处理异常,就可以不必throws
31.自定义异常
当程序中出现了某些“错误”,但该错误信息并没有在Throwable子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息。
自定义异常的步骤
- 定义类:自定义异常类名(程序员自己写)继承Exception或RuntimeException
- 如果继承Exception,属于编译异常
- 如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException)
举例:
/*
当我们接收Person对象年龄时,要求范围在18-120之间,
否则抛出一个自定义异常(要求继承RuntimeException),并给出提示信息。
*/
public class CustomException {
public static void main(String[] args){
int age = 180;
//要求范围在 18 – 120 之间,否则抛出一个自定义异常
if(!(age >= 18 && age <= 120)) {
//这里我们可以通过构造器,设置信息
throw new AgeException("年龄需要在 18~120 之间");
}
System.out.println("你的年龄范围正确.");
}
}
//1. 一般情况下,我们自定义异常是继承 RuntimeException
//2. 即把自定义异常做成 运行时异常,好处时,我们可以使用默认的处理机制
//3. 即比较方便
class AgeException extends RuntimeException {
public AgeException(String message) {//构造器
super(message);
}
}
32.throw和thows的区别
33. String
-
String对象用于保存字符串,也就是一组字符序列
-
字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节。
-
String 类有很多构造器,构造器的重载
-
String 是 final 类,不能被其他的类继承
-
String 类实现了
接口 Serializable【String 可以串行化:可以在网络传输】
接口 Comparable [String 对象可以比较大小] -
String 有属性 private final char value[]; 用于存放字符串内容
-
一定要注意:value 是一个 final 类型, 不可以修改:即 value 不能指向新的地址,但是单个字符内容是可以变化
-
字符串是不可变的。一个字符串对象一旦被分配,其内容是不可变的,可变的是它的指向。
创建 String 对象的两种方式
- 方式一:直接赋值 String s = “hspedu”;
- 方式二:调用构造器 String s = new String(“hspedu”);
两种创建 String 对象的区别
方式一:直接赋值 String s = “hello”;
方式二:调用构造器String s2 = new String(“hello”);
- 方式一:先从常量池查看是否有"hello”数据空间,如果有,直接指向;如果没有则重新创建,然后指向。S最终指向的是常量池的空间地址
- 方式二:先在堆中创建空间,里面维护了value属性,指向常量池的hello空间。如果常量池没有"hello",重新创建,如果有,直接通过value指向。最终指向的是堆中的空间地址。
- 画出两种方式的内存分布图
对于String c1=a+b;的理解
String a = "hello";//创建a对象
String b ="abc";//创建b对象
String c="hello"+"abc";
String c1=a+b;
//创建了几个对象?画出内存图?
//关键就是要分析 String c1 =a +b;到底是如何执行的
底层是 StringBuilder sb = new StringBuilder();
sb.append(a);
sb.append(b);
String c1=sb.toString();
sb是在堆中,并且append是在原来字符串的基础上追加的
重要规则:
String c = "ab" + "cd";常量相加,看的是池。
String c1 = a+b;变量相加,是在堆中
34.String、StringBuffer、StringBuilder的对比
StringBuffer、StringBuilder底层是数组扩容。
- StringBuilder 和 StringBuffer非常类似,均代表可变的字符序列,而且方法也一样
- String:不可变字符序列,效率低,但是复用率高。
- StringBuffer:可变字符序列、效率较高(增删)、线程安全,看源码4) StringBuilder:可变字符序列、效率最高、线程不安全。
String、StringBuffer 和 StringBuilder 的选择
1.如果字符串存在大量的修改操作,一般使用StringBuffer 或StringBuilder
2.如果字符串存在大量的修改操作,并在单线程情况下,使用StringBuilder
3.如果字符串存在大量的修改操作,并在多线程的情况,使用 StringBuffer
4.如果我们字符串很少修改,被多个对象引用,使用String, 比如配置信息等
35.可变参数
- 可变参数的实参可以为0个或任意多个。
- 可变参数的实参可以为数组。
- 可变参数的本质就是数组.
- 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
- 一个形参列表中只能出现一个可变参数。
public class Java01 {
public static void main(String[] args) {
new B().test(1,2,3,4);
}
}
class B{
public void test(int... a){
int sum=0;
for (int i : a) {
sum+=i;
}
System.out.println(sum);
System.out.println(a.length);
}
}
36.日期类
第一代日期类Date,精确到毫秒,代表特定的瞬间。
public static void main(String[] args) {
Date date = new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//不进行格式,即默认输出方式
System.out.println(date);//Wed Nov 02 13:33:38 CST 2022
//进行格式化
String format = sdf.format(date);
System.out.println(format);//2022-11-02 13:33:38
//通过指定毫秒数得到时间
Date d2 = new Date(0);
System.out.println(sdf.format(d2));//1970-01-01 08:00:00
}
第二代日期类 Calendar
- Calendar 是一个抽象类, 并且构造器是 private
- 可以通过 getInstance() 来获取实例
- Calendar 没有提供对应的格式化的类,因此需要程序员自己组合来输出(灵活)
- 如果我们需要按照 24 小时进制来获取时间,
Calendar.HOUR ==改成=>Calendar.HOUR_OF_DAY
public static void main(String[] args) {
//创建日历类对象
Calendar c = Calendar.getInstance();
//2.获取日历对象的某个日历字段
System.out.println("年:" + c.get(Calendar.YEAR));
// 这里为什么要 + 1, 因为 Calendar 返回月时候,是按照 0 开始编号
System.out.println("月:" + (c.get(Calendar.MONTH) + 1));
System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
//12小时制
System.out.println("12小时:" + c.get(Calendar.HOUR));
//24小时制
System.out.println("24小时:" + c.get(Calendar.HOUR_OF_DAY));
System.out.println("分钟:" + c.get(Calendar.MINUTE));
System.out.println("秒:" + c.get(Calendar.SECOND));
//Calender 没有专门的格式化方法,所以需要程序员自己来组合显示
System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" +
c.get(Calendar.DAY_OF_MONTH));
}
小结:前面两代日期类的不足分析
JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。
而Calendar也存在问题是:
- 可变性:像日期和时间这样的类应该是不可变的。
- 偏移性:Date中的年份是从1900开始的,而月份都从0开始。
- 格式化:格式化只对Date有用,Calendar则不行。
- 此外,它们也不是线程安全的;不能处理闰秒等(每隔2天,多出1s)。
第三代日期类LocalDateTime
JDK8加入:
LocalDate(日期/年月日)、LocalTime(时间/时分秒)、LocalDateTime(日期时间/年月日时分秒)
LocalDate只包含日期,可以获取日期字段
LocalTime只包含时间,可以获取时间字段
LocalDateTime包含日期+时间,可以获取日期和时间字段
public static void main(String[] args) {
LocalDateTime ldt = LocalDateTime.now();
//LocalDate.now();//LocalTime.now()
System.out.println(ldt);
//2. 使用 DateTimeFormatter 对象来进行格式化
// 创建 DateTimeFormatter 对象
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(ldt);
System.out.println("格式化的日期=" + format);
//提供 plusXXX 和 minusXXX 方法可以对当前时间进行加或者减
//看看 890 天后,是什么时候 年月日-时分秒
LocalDateTime localDateTime = ldt.plusDays(890);
System.out.println("890 天后 " + dateTimeFormatter.format(localDateTime));
//看看在 3456 分钟前是什么时候,把 年月日-时分秒输出
LocalDateTime localDateTime2 = ldt.minusMinutes(3456);
System.out.println("3456 分钟前 " + dateTimeFormatter.format(localDateTime2));
System.out.println("年=" + ldt.getYear());
System.out.println("月=" + ldt.getMonth());
System.out.println("月=" + ldt.getMonthValue());
System.out.println("日=" + ldt.getDayOfMonth());
System.out.println("时=" + ldt.getHour());
System.out.println("分=" + ldt.getMinute());
System.out.println("秒=" + ldt.getSecond());
//可以获取年月日
LocalDate now = LocalDate.now();
System.out.println(now);
//获取到时分秒
LocalTime now2 = LocalTime.now();
System.out.println(now2);
}
时间戳Instant
public static void main(String[] args) {
//1.通过 静态方法 now() 获取表示当前时间戳的对象
Instant now = Instant.now();
System.out.println(now);
//2. 通过 from 可以把 Instant 转成 Date
Date date = Date.from(now);
System.out.println(date);
//3. 通过 date 的 toInstant() 可以把 date 转成 Instant 对象
Instant instant = date.toInstant();
System.out.println(instant);
}
37.泛型
- 泛型又称参数化类型,是Jdk5.0出现的新特性,解决数据类型的安全性问题
- 在类声明或实例化时只要指定好需要的具体的类型即可。
- Java泛型可以保证代码更加简洁、健壮。同时,如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常(是向下类型转换发生错误)。
- 泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型。
ArrayList<Dog> arrayList = new ArrayList<Dog>();
arrayList.add(new Dog("旺财", 10));
不使用泛型
Dog -加入-> Object -取出-> Dog//放入到ArrayList 会先转成Object,在取出时,还需要转换成Dog
使用泛型
Dog ->Dog -> Dog //放入时,和取出时,不需要类型转换,提高效率
使用细节:
- interface List0 , public class HashSet.等等说明:T,E只能是引用类型
看看下面语句是否正确?:
List list = new ArrayList<Integer>();//正确
List list2 = new ArrayList<int>();//错误 - 在给泛型指定具体类型后,可以传入该类型或者其子类类型
- 泛型使用形式
List<lnteger> list1 = new ArrayList<lnteger>();
List<lnteger> list2 =new ArrayList<>(); - 如果我们这样写 List list3 = new ArrayList();默认给它的泛型是[<E>E就是Object]
补充:
- 泛型不具备继承性
List<Object> list = new ArrayList<String>();//这样写是不对的 - <?>:支持任意泛型类型
- <? extends A>:支持A类以及A类的子类,规定了泛型的上限(最高到A)
- <? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限(最低是A)
举例:
public class GenericExtends {
public static void main(String[] args) {
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<AA> list3 = new ArrayList<>();
List<BB> list4 = new ArrayList<>();
List<CC> list5 = new ArrayList<>();
//如果是 List<?> c ,可以接受任意的泛型类型
printCollection1(list1);
printCollection1(list2);
printCollection1(list3);
printCollection1(list4);
printCollection1(list5);
//List<? extends AA> c: 表示 上限,可以接受 AA 或者 AA 子类
// printCollection2(list1);//×
// printCollection2(list2);//×
printCollection2(list3);//√
printCollection2(list4);//√
printCollection2(list5);//√
//List<? super AA> c: 支持 AA 类以及 AA 类的父类,不限于直接父类
printCollection3(list1);//√
//printCollection3(list2);//×
printCollection3(list3);//√
//printCollection3(list4);//×
//printCollection3(list5);//×
}
// ? extends AA 表示 上限,可以接受 AA 或者 AA 子类
public static void printCollection2 (List < ? extends AA > c){
for (Object object : c) {
System.out.println(object);
}
}
//说明: List<?> 表示 任意的泛型类型都可以接受
public static void printCollection1 (List < ? > c){
for (Object object : c) { // 通配符,取出时,就是 Object
System.out.println(object);
}
}
// ? super 子类类名 AA:支持 AA 类以及 AA 类的父类,不限于直接父类,
//规定了泛型的下限
public static void printCollection3 (List < ? super AA > c){
for (Object object : c) {
System.out.println(object);
}
}
}
class AA {
}
class BB extends AA {
}
class CC extends BB {
}
38.自定义泛型
自定义泛型类:
class类名<T,R..>{//表示可以有多个泛型成员
成员
}
注意细节
- 普通成员可以使用泛型(属性、方法)
- 使用泛型的数组,不能初始化
- 静态方法中不能使用类的泛型
- 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
- 如果在创建对象时,没有指定类型,默认为Object
举例:
class Tiger<T, R, M>{
String name;
R r;
M m;
T t;
}
//实例化举例
//T=Double, R=String, M=Integer
Tiger<Double,String,Integer> g = new Tiger<>();
自定义泛型接口:
interface 接口名<T,R..> {
}
注意细节
- 接口中,静态成员也不能使用泛型(这个和泛型类规定一样)
- 泛型接口的类型,在继承接口或者实现接口时确定
- 没有指定类型,默认为Object
举例:
interface IUsb<U, R> {
}
//在继承接口 指定泛型接口的类型
interface IA extends IUsb<String, Double> {
}
//实现接口时,直接指定泛型接口的类型
//给 U 指定 Integer 给 R 指定了 Float
//所以,当我们实现 IUsb 方法时,会使用 Integer 替换 U, 使用 Float 替换 R
class BB implements IUsb<Integer, Float> {
}
//没有指定类型,默认为 Object
//建议直接写成 IUsb<Object,Object>
class CC implements IUsb { //等价 class CC implements IUsb<Object,Object> {
}
自定义泛型方法:
修饰符 <T,R..> 返回类型 方法名(参数列表){
}
注意细节
- 泛型方法,可以定义在普通类中,也可以定义在泛型类中
- 当泛型方法被调用时,类型会确定
- public void eat(E e){ },修饰符后没有<T,R…> ,
则eat方法不是泛型方法,而是使用了泛型。
举例:
public class CustomMethodGeneric {
public static void main(String[] args) {
Car car = new Car();
//当调用方法时,传入参数,编译器,就会确定类型
car.fly("宝马", 100);
System.out.println("=======");
//当调用方法时,传入参数,编译器,就会确定类型
car.fly(300, 100.1);
//测试
//T->String, R-> ArrayList
Fish<String, ArrayList> fish = new Fish<>();
fish.hello(new ArrayList(), 11.3f);
}
}
//泛型方法,可以定义在普通类中, 也可以定义在泛型类中
class Car {//普通类
public void run() {//普通方法
}
//说明 泛型方法
//1. <T,R> 就是泛型
//2. 是提供给 fly 使用的
public <T, R> void fly(T t, R r) { //泛型方法
System.out.println(t.getClass());//String
System.out.println(r.getClass());//Integer
}
}
class Fish<T, R> {//泛型类
public void run() {//普通方法
}
public <U, M> void eat(U u, M m) {//泛型方法
}
//说明
//1. 下面 hi 方法不是泛型方法
//2. 是 hi 方法使用了类声明的 泛型
public void hi(T t) {
}
//泛型方法,可以使用类声明的泛型,也可以使用自己声明泛型
public <K> void hello(R r, K k) {
System.out.println(r.getClass());//ArrayList
System.out.println(k.getClass());//Float
}
}
39.多线程基础
5个基本知识(程序,进程,线程,并发,并行)
- 程序:
是为完成特定任务、用某种语言编写的一组指令的集合。简单的说:就是我们写的代码。 - 进程:
1.进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
2.进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程. - 线程:
线程由进程创建的,是进程的一个实体;一个进程可以拥有多个线程。
1.单线程:同一个时刻,只允许执行一个线程
2.多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件。 - 并发与并行:
解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
解释三:在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。
创建线程的两种方式:
main方法本身就是一个线程;
main方法的结束并不意味着进程的结束(还可以有其他线程仍在继续执行)
线程与线程在自然情况下是互不干涉的(一旦被创建,就各自跑各自的内容)
start底层会创建新的线程,调用run, run 就是一个简单的方法调用,不会启动新线程
继承Thread类,重写run方法
- 当一个类继承了 Thread 类, 该类就可以当做线程使用
- 我们会重写 run 方法,写上自己的业务代码
- Thread类里面是的run方法,实际上是重写了Runable接口的run方法。
- 线程的调用,实例化继承Thread类的类,调用start方法。
举例(这个栗子很重要):
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
//创建 Cat 对象,可以当做线程使用
Cat cat = new Cat();
/**
* 底层源码
public synchronized void start() {
start0();
}
//start0() 是本地方法,是 JVM 调用, 底层是 c/c++实现
//真正实现多线程的效果, 是 start0(), 而不是 run
private native void start0();
*/
cat.start();//启动线程-> 最终会执行 cat 的 run 方法
/**
* cat.run();
* 用上述代码调用run()方法时,实质是main线程调用了Cat类里面的run方法
* (就理解成普通的对象调用方法,并不是多线程)
* 此时run 方法就是一个普通的方法, 就没有真正的启动一个线程
* 代码执行时:就是正常的顺序执行
* 按顺序就先会把 run 方法执行完毕,再向下执行
*/
//说明: 当 main 线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
//这时 主线程和子线程是交替执行.. System.out.println("主线程继续执行" + Thread.currentThread().getName());//名字 main
for (int i = 0; i < 60; i++) {
System.out.println("主线程 i=" + i);
//让主线程休眠
Thread.sleep(1000);
}
}
}
class Cat extends Thread {
int times = 0;
//重写 run 方法,写上自己的业务逻辑
@Override
public void run() {
while (true) {
//该线程每隔 1 秒。在控制台输出 “喵喵, 我是小猫咪”
System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());
//让该线程休眠 1 秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times == 80) {
break;//当 times 到 80, 退出 while, 这时线程也就退出.. }
}
}
}
实现Runable接口,重写run方法
- 线程的调用,实例化Thread类,将实现了Runable接口的类实例化放入其中。
new Thread( new 实现Runable接口的类());
举例:
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
//dog.start(); 这里不能调用 start
//创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread
Thread thread = new Thread(dog);
// 开启线程
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程"+Thread.currentThread().getName()+"=" + i);
//让主线程休眠
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Dog implements Runnable { //通过实现 Runnable 接口,开发线程
int count = 0;
@Override
public void run() { //普通方法
while (true) {
System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
//休眠 0.5 秒
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
继承 Thread vs 实现 Runnable 的区别
- 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
- 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable
线程终止:
当线程完成任务后,会自动退出。
还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式。
用户线程和守护线程:
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束3.常见的守护线程:垃圾回收机制
40 线程生命周期
状态转换图:
41. 线程同步
在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
同步的方法:加Synchronized锁(互斥锁)
//同步代码块
//得到对象的锁,才能操作同步代码
synchronized(对象){
//需要被同步的代码;
}
//同步方法
//synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void m (String name){
//需要被同步的代码
}
举例:(用多线程实现卖票)
public class Thread03 {
public static void main(String[] args) {
SellTickets tickets = new SellTickets();
new Thread(tickets).start();
new Thread(tickets).start();
new Thread(tickets).start();
}
}
class SellTickets implements Runnable{
private static int num = 100;
Object obj = new Object();
boolean loop=true;
private /*synchronized */void sell(){
synchronized (/*this*/obj) {
if (num <= 0) {
System.out.println("票已卖完");
loop=false;
return;
}
System.out.println("窗口" + Thread.currentThread().getName() + "正在售卖第" + num-- + "张票");
}
}
@Override
public void run() {
while (loop) {
try {
Thread.sleep(100);
sell();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 同步方法如果没有使用static修饰:默认锁对象为this
- 如果方法使用static修饰,默认锁对象:当前类.class
死锁和释放锁
死锁:多个线程都占用了对方的锁资源,但不肯相让,导致了双方都无达到所需条件,都不能正常执行,造成了死锁状态。
比如:
妈妈:你先完成作业,才让你玩手机
小明:你先让我玩手机,我才完成作业
释放锁:
- 当前线程的同步方法、同步代码块执行结束
案例:上厕所,完事出来 - 当前线程在同步代码块、同步方法中遇到break、return.
案例:没有正常的完事,经理叫他修改bug,不得已出来 - 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
案例:没有正常的完事,发现忘带纸,不得已出来 - 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停并释放锁。
案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去
不释放锁:
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep().Thread.yield()方法暂停当前线程的执行,不会释放锁。
案例:上厕所,太困了,在坑位上眯了一会 - 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
42.druid数据库连接池工具类写法(JDBC)
借助Apache的JDBC工具类库comments-dbutils
-
commons-dbutils 是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量。
-
QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理。
-
ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式。
/*
QreryRunner类(org.apache.commons.dbutils.QueryRunner) 是Dbutils的核心类之一,
它显著的简化了SQL查询,并与ResultSetHandler协同工作将使编码量大为减少。它包含以下几个方法:
*/
query(Connection conn, String sql, Object[] params, ResultSetHandler rsh)
//执行选择查询,在查询中,对象阵列的值被用来作为查询的置换参数。
query(String sql, Object[] params, ResultSetHandler rsh)
//方法本身不提供数据库连接,执行选择查询,在查询中,对象阵列的值被用来作为查询的置换参数。
query(Connection conn, String sql, ResultSetHandler rsh)
//执行无需参数的选择查询。
update(Connection conn, String sql, Object[] params)
//被用来执行插入、更新或删除(DML)操作。
/*
其中ResultSetHandler接口(org.apache.commons.dbutils.ResultSethandler)
执行处理一个结果集对象,将数据转变并处理为任何一种形式,供其他应用使用。实现类如下:
*/
------------------------------------------
ArrayHandler:把结果集中的第一行数据转成对象数组。
------------------------------------------
ArrayListHandler:把结果集中的每一行数据都转成一个对象数组,再存放到List中。
------------------------------------------
BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
------------------------------------------
BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。//重点
------------------------------------------
MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。//重点
------------------------------------------
MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
------------------------------------------
ColumnListHandler:将结果集中某一列的数据存放到List中。
------------------------------------------
KeyedHandler(name):将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,
其key为指定的key.MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,
value就是对应的值。
------------------------------------------
ScalarHandler:将结果集第一行的某一列放到某个对象中。//重点
------------------------------------------
Jdbc_Druid_Utils.java
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileReader;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* 德鲁伊连接池工具类
* 连接池工具类,连接数据库
* @author hyl
*/
public class Jdbc_Druid_Utils {
private static DataSource source;
//初始化连接
static {
Properties properties = new Properties();
try {
//使用ClassLoader加载配置文件,获取字节输入流
InputStream is = Jdbc_Druid_Utils.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(is);
source = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection() throws SQLException {
return source.getConnection();
}
/**
* 释放连接(不是断掉连接,而是归还释放)
* @param resultSet
* @param statement
* @param connection
*/
public static void Close(ResultSet resultSet, Statement statement, Connection connection){
try {
if (resultSet!=null) {
resultSet.close();
}
if (statement!=null) {
statement.close();
}
if (connection!=null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
BasicDao.java
import com.hyl.sams.utils.Jdbc_Druid_Utils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/**
* 是其它dao的父类
* 有update(增删改) 多行返回,单行返回,单行单列返回
* @author hyl
*/
public class BasicDao {
private QueryRunner qr=new QueryRunner();
//统一说明:Object... parameters
// 是动态传参,参数个数0-n
/**
* 插入、更新或删除
* @param sql
* @param parameters
* @return
*/
public int update(String sql,Object... parameters){
Connection connection=null;
try {
connection= Jdbc_Druid_Utils.getConnection();
int update = qr.update(connection, sql, parameters);
return update;
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
Jdbc_Druid_Utils.Close(null,null,connection);
}
}
/**
* 返回多行记录
* @param sql
* @param tClass
* @param parameters
* @param <T>
* @return
*/
public <T> List<T> queryMulti(String sql, Class<T> tClass, Object... parameters){
Connection connection=null;
try {
connection= Jdbc_Druid_Utils.getConnection();
return qr.query(connection, sql, new BeanListHandler<T>(tClass), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
Jdbc_Druid_Utils.Close(null,null,connection);
}
}
/**
* 返回单行
* @param sql
* @param tClass
* @param parameters
* @param <T>
* @return
*/
public <T> T querySingle(String sql, Class<T> tClass, Object... parameters){
Connection connection=null;
try {
connection= Jdbc_Druid_Utils.getConnection();
return qr.query(connection, sql, new BeanHandler<T>(tClass), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
Jdbc_Druid_Utils.Close(null,null,connection);
}
}
/**
* 返回单行单列
* @param sql
* @param parameters
* @return
*/
public Object queryScalar(String sql,Object...parameters){
Connection connection=null;
try {
connection= Jdbc_Druid_Utils.getConnection();
return qr.query(connection, sql, new ScalarHandler<>(),parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
Jdbc_Druid_Utils.Close(null,null,connection);
}
}
}
druid.properties
#key=value
driverClassName=com.mysql.jdbc.Driver
# rewriteBatchedStatements=true 允许批处理
url=jdbc:mysql://localhost:3306/table_03? rewriteBatchedStatements=true
username=
password=
#initial connection Size
initialSize=10
#min idle connecton size
minIdle=5
#max active connection size
maxActive=50
#max wait .time (5000 mil seconds)
maxWait=5000