目录
细节
- 自增自减运算符不会改变原数据的类型,而
num = num + 1
则可能会出现自动类型提升 - 逻辑与或(&和|条件全部判断一遍,里面可能有递增自减运算)同短路与或(&&和||只有能够得出结论便不再往后判断)
- byte char short 的运算以及自己和自己的运算都会返回int类型
- 凡是可以使用三元运算符的地方都可以改成if else,能用if else写的不一定能够改成三元,如果程序既可以使用if else 又可以使用三运运算符,选择三元,效率更高。
- instanceof() 也是比较运算符的一种
- 位运算是对二进制进行的运算,整数才考虑位运算,& | 根据左右两边的数据类型来判断是逻辑运算符还是位运算符
// \uXXXX,其中XXXX代表一个16进制整数,一个X代表四位,一共十六位,char就是16位
char c1 = '\u000a' //表示\n
//char 类型是可以进行运算的。因为他们都有对应的Unicode码
- 源码-反码-补码
- 位移运算:根据最高位符号位的值,去填补空白位,移动几位填写几位同符号位相同的值
- 15 的二进制码为 1111
- 字符char是可以进行运算的,String只能进行拼接
- Math.random();//获取 [0,1)之间的一个数字double类型
- 三元运算符编译的时候要求 左右类型一致否则无法比较,可能会出现自动类型提升的情况
@Test
public void test1() {
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);// 1.0
}
- 数组元素的初始默认值
元素为整形byte int short long :0
浮点型:0.0
char类型:0(ASCII码值为零)或者 Unicode的一个符号 ‘\u0000’ 不是‘0’(6:00)分钟不熟的话可以看看
boolean类型:false
引用数据类型:null (空值,没有赋值的意思) 不是“null”
\\静态初始化
int [][] ids = new int[][] {{1,2,3},{1,2},{1,2,3}};
\\动态初始化
int[][] ids = new int[3][2];
int[][] ids = new int[3][];
- 类的成员:属性、方法、构造器、代码块、内部类
- OOB的三大特征:封装、继承、多态(抽象)
- 局部变量不可以使用权限修饰符:public protected 缺省 private ,在声明的时候不会默认初始化
- 修饰类或其中的方法属性 访问控制修饰符 : default, public , protected, private 非访问控制修饰符 : final, abstract, static, synchronized
- 重载:在一个类当中允许有多个同名方法的存在,但参数列表的个数或者类型需要不一样
- 重写: 在子类中可以根据需要对从父类中继承来的方法进行改造,也称 为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
- 重写的规定:
-
方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{ \\方法体 }
- ① 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
- ② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
-
>特殊情况:子类不能重写父类中声明为private权限的方法
- ③ 返回值类型:
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
- 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是ouble)
- ④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)
- 子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
- 可变个数的形参:JDK5.0提供了,用一种更简单的方式来传递个数可变的实参,一个方法最多只有一个,且必须放在最后
public void show(String ... strs){
System.out.println("show(String ... strs)");
}
//调用,注意需要都是String类型
show();
show(new String[]{"AA","BB","CC"});
//jdk5.0之前的为,注意这两种不构成重载
public void show(String[] strs){
System.out.println("show(String ... strs)");
}
- 四种权限修饰符,可以修饰类及类的内部结构:属性、方法、构造器、内部类(类中类);修饰类只能:public 缺省
- 构造器的特征 :它具有与类相同的名称;定义构造器的格式:权限修饰符 类名(形参列表){};构造器的作用:创建对象;给对象进行初始化
➢它不声明返回值类型。( 与声明为void不同)
➢不能被static、final、 synchronized、 abstract、 native修饰, 不能有return语句返回值
➢如: Order 0 = new Order(); Person p = new Person(“Peter’ ,15);
-
默认提供的构造器的权限同类的权限一致
-
二、说明:
-
1.如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器
-
2.定义构造器的格式:权限修饰符 类名(形参列表){}
-
3.一个类中定义的多个构造器,彼此构成重载
-
4.一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
-
5.一个类中,至少会有一个构造器。
- 属性赋值的先后顺序
- ① 默认初始化
- ② 显式初始化/在代码块中的赋值
- ③ 构造器中初始化
- ④ 通过"对象.方法" 或 "对象.属性"的方式,赋值
- 符合某种要求的类JavaBean
- JavaBean是一种Java语言写成的可重用组件。
- 所谓javaBean, 是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器.
- 有属性,且有对应的get、set方法
-
UML类图
-
this 属性和形参重名了比如: this.name = name;在方法或构造器当中使用
-
this调用构造器
- ① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
- ② 构造器中不能通过"this(形参列表)"方式调用自己
- ③ 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)"
- ④ 规定:"this(形参列表)"必须声明在当前构造器的首行
- ⑤ 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器
class Person{
private String name;
private int age;
public Person(){
// this.eat();
String info = "Person初始化时,需要考虑如下的1,2,3,4...(共40行代码)";
System.out.println(info);
}
public Person(String name){
this();
this.name = name;
}
public Person(int age){
this();
this.age = age;
}
public Person(String name,int age){
this(age);
this.name = name;
//this.age = age;
//Person初始化时,需要考虑如下的1,2,3,4...(共40行代码)
}
public void eat(){
System.out.println("人吃饭");
this.study();
}
public void study(){
System.out.println("人学习");
}
}
public String getName(){
return this.name;
}
- P240-
- java有一些核心包,就不需要import就可以直接用,比如java.lang.String; 如果使用的类或接口是本包下定义的,则可以省略import结构;
- 不同包下的类名相同 ,如果想同时使用,如下(全类名);
- 使用"xxx.*"方式表明可以调用xxx包下的所有结构。
- 但是如果使用的是xxx子包下的结构,则仍需要显式导入eg:import com.atguigu.java2.java3.Dog;在Java2中的一个类文件需要使用Dog类,仍需要显示导入
- import static:导入指定类或接口中的静态结构:属性或方法。落脚点是结构不是一个类
import com.atguigu.exer4.Account;
//不可以 import com.atguigu.exer3.Account;
public static void main(String[] args) {
Account acct = new Account(1000);
//全类名的方式显示
com.atguigu.exer3.Account acct1 = new com.atguigu.exer3.Account(1000,2000,0.0123);
}
\\import static:导入指定类或接口中的静态结构:属性或方法。
System.out.println(p2.getAge());
import static java.lang.System.*;
out.println("hello");
import static java.lang.Math.*;
long num = round(123.434);
- 继承
public class Student extends Person{
}
- run configuration 的设置P271
- super关键字的使用
- 1.super理解为:父类的
- 2.super可以用来调用:属性、方法、构造器
- 3.super的使用:调用属性和方法
- 3.1 我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用
- 父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
- 3.2 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的
- 使用"super.属性"的方式,表明调用的是父类中声明的属性。
- 3.3 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的
- 使用"super.方法"的方式,表明调用的是父类中被重写的方法。
- 4.super调用构造器
- 4.1 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
- 4.2 "super(形参列表)"的使用,必须声明在子类构造器的首行!
- 4.3 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现
- 4.4 在构造器的首行,没有显式的声明"this(形参列表)“或"super(形参列表)”,则默认调用的是父类中空参的构造器:super()
- 4.5 在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)",调用父类中的构造器
public Student(String name,int age,String major){
// this.name = name;
// this.age = age; 注意name和age的权限
super(name,age);
this.major = major;
}
- 多态P280: 父类的引用指向子类的对象,如果想调用子类特有的方法通常使用强制类型转换(向下转型),
instanceof
常用来判断
- 1.理解多态性:可以理解为一个事物的多种形态。
- 2.何为多态性:
- 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
- 3.多态的使用:虚拟方法调用
- 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
- 总结:编译,看左边;运行,看右边。
- 4.多态性的使用前提: ① 类的继承关系 ② 方法的重写
- 5.对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
- == 和 equals()的区别P295
*一、回顾 == 的使用:
*== :运算符
*1. 可以使用在基本数据类型变量和引用数据类型变量中
*2. 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
- 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
*补充: == 符号使用时,必须保证符号左右两边的变量类型一致。
*二、equals()方法的使用:
*1. 是一个方法,而非运算符
*2. 只能适用于引用数据类型
*3. Object类中equals()的定义: - public boolean equals(Object obj) {
return (this == obj);
} - 说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
*4. 像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是 - 两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。
*5. 通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们 - 就需要对Object类中的equals()进行重写.
- 重写的原则:比较两个对象的实体内容是否相同.
- 单元测试
- Java中的JUnit单元测试
- 步骤:
- 1.选中当前工程 - 右键选择:build path - add libraries - JUnit 4 - 下一步
- 2.创建Java类,进行单元测试。
- 此时的Java类要求:① 此类是public的 ②此类提供公共的无参的构造器
- 3.此类中声明单元测试方法。
- 此时的单元测试方法:方法的权限是public,没有返回值,没有形参
- 4.此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
- 5.声明好单元测试方法以后,就可以在方法体内测试相关的代码。
- 6.写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
- 说明:
- 1.如果执行结果没有任何异常:绿条
- 2.如果执行结果出现异常:红条
- 包装类
不同进制的表示方式
- 二进制:0b 或 0B开头(数字0)
- 八进制:必须以数字零0开头
- 十进制:
- 十六进制:以数字0x开头 或者0X开头,a-f不区分大小写
0x21af + 1 = 0X21B0
基本数据类型、包装类型、String类型的转换
- 基本类型、包装类、String三者的相互转换
//自动装箱 JDK5.0新特性:自动装箱、自动拆箱
//所以就把包装类和基本数据类型看成一个整体了,再进行下面的分析
int num = 10;
Integer in = num;
boolean b1 = true;
Boolean b2 = b1;
//自动拆箱,同理类似
int num2 = in;
@Test
public void test4(){
//基本数据类型、包装类--->String类型:调用String重载的valueOf(Xxx xxx)
int num1 = 10;
//方式1:连接运算
String str1 = num1 + "";
//方式2:调用String的valueOf(Xxx xxx)
float f1 = 12.3f;
String str2 = String.valueOf(f1);//"12.3"
Double d1 = new Double(12.4);
String str3 = String.valueOf(d1);
System.out.println(str2);
System.out.println(str3);//"12.4"
}
@Test
public void test5(){
//String类型 --->基本数据类型、包装类:调用包装类的parseXxx(String s)
String str1 = "123";
//错误的情况:
// int num1 = (int)str1;
// Integer in1 = (Integer)str1;
//可能会报NumberFormatException
int num2 = Integer.parseInt(str1);
System.out.println(num2 + 1);
String str2 = "true1";
boolean b1 = Boolean.parseBoolean(str2);
System.out.println(b1);
}
- 面试题
public class InterviewTest {
@Test
public void test1() {
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);// 1.0
}
@Test
public void test2() {
Object o2;
if (true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
System.out.println(o2);// 1
}
@Test
public void test3() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);//false
//Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],
//保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在
//-128~127范围内时,可以直接使用数组中的元素,不用再去new了。目的:提高效率
Integer m = 1;
Integer n = 1;
System.out.println(m == n);//true
Integer x = 128;//相当于new了一个Integer对象
Integer y = 128;//相当于new了一个Integer对象
System.out.println(x == y);//false
}
}
- static 关键字可以用来修饰:属性、方法、代码块、内部类
静态方法中,只能调用静态的方法或属性
非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
在静态的方法内,不能使用this关键字、super关键字
- 开发中,如何确定一个属性是否要声明为static的?
*>属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
*>类中的常量也常常声明为static - 开发中,如何确定一个方法是否要声明为static的?
*> 操作静态属性的方法,通常设置为static的
*> 工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections
- 静态代码块只能由static修饰或者缺省,
4.静态代码块
*>随着类的加载而执行,而且只执行一次
*>作用:初始化类的信息
*>静态代码块的执行要优先于非静态代码块的执行
*>静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
5.非静态代码块
*>每创建一个对象,就执行一次非静态代码块
*>作用:可以在创建对象时,对对象的属性等进行初始化
*>如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
class Person{
//属性
String name;
int age;
age=1; //编译会报错,相当于对属性进行了操作,是实例的工作,必须放在一行int age=1;
}
String类型、char[]类型转换
String --> char[]:调用String的toCharArray()
char[] --> String:调用String的构造器
- final 可以用来修饰的结构:类、方法、变量
* 1. final可以用来修饰的结构:类、方法、变量
*
* 2. final 用来修饰一个类:此类不能被其他类所继承。
* 比如:String类、System类、StringBuffer类
*
* 3. final 用来修饰方法:表明此方法不可以被重写
* 比如:Object类中getClass();
*
* 4. final 用来修饰变量:此时的"变量"就称为是一个常量
* 4.1 final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
* 4.2 final修饰局部变量:
* 尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值
* 以后,就只能在方法体内使用此形参,但不能进行重新赋值。
*
* static final 用来修饰属性:全局常量
- abstract
* abstract关键字的使用
* 1.abstract:抽象的
* 2.abstract可以用来修饰的结构:类、方法
* 3. abstract修饰类:抽象类
* > 此类不能实例化
* > 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
* > 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
* 4. abstract修饰方法:抽象方法
* > 抽象方法只有方法的声明,没有方法体
* > 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
* > 若子类重写了父类中的所有的抽象方法后,此子类方可实例化
* 若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
* abstract使用上的注意点:
* 1.abstract不能用来修饰:属性、构造器等结构
* 2.abstract不能用来修饰私有方法、静态方法、final的方法、final的类
- 匿名子类对象
abstract class Creature{
public abstract void breath();
}
abstract class Person extends Creature{
String name;
int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
public abstract void eat();
public void walk(){
System.out.println("人走路");
}
}
另外一个文件
public class PersonTest {
public static void main(String[] args) {
method(new Student());//匿名对象
Worker worker = new Worker();
method1(worker);//非匿名的类非匿名的对象
method1(new Worker());//非匿名的类匿名的对象
System.out.println("********************");
//创建了一匿名子类的对象:p
此处就不用写extends了(匿名的原因)
Person p = new Person(){
@Override
public void eat() {
System.out.println("吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸");
}
};
method1(p);
System.out.println("********************");
//创建匿名子类的匿名对象
method1(new Person(){
@Override
public void eat() {
System.out.println("吃好吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸新鲜空气");
}
});
}
public static void method1(Person p){
p.eat();
p.breath();
}
public static void method(Student s){
}
}
class Worker extends Person{
@Override
public void eat() {
}
@Override
public void breath() {
}
}
- interface
* 接口的使用
* 1.接口使用interface来定义
* 2.Java中,接口和类是并列的两个结构
* 3.如何定义接口:定义接口中的成员
*
* 3.1 JDK7及以前:只能定义全局常量和抽象方法
* >全局常量:public static final的.但是书写时,可以省略不写
* >抽象方法:public abstract的
*
* 3.2 JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法(略),Note: 此时需要方法体,JDK9以后又提供了私有方法
*
* 4. 接口中不能定义构造器的!意味着接口不可以实例化
*
* 5. Java开发中,接口通过让类去实现(implements)的方式来使用.
* 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
* 如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
*
* 6. Java类可以实现多个接口 --->弥补了Java单继承性的局限性
* 格式:class AA extends BB implements CC,DD,EE
*
* 7. 接口与接口之间可以继承,而且可以多继承
*
* *******************************
* 8. 接口的具体使用,体现多态性
* 9. 接口,实际上可以看做是一种规范
*
* 面试题:抽象类与接口有哪些异同?
*
package com.atguigu.java1;
public class Interface Test {
public static void main(String[] args) {
System.out.println(Flyable.MAX_SPEED);
System.out.println(Flyable.MIN_SPEED);
// Flyable.MIN_SPEED = 2;
Plane plane = new Plane();
plane.fly();
}
}
interface Flyable{
//全局常量
public static final int MAX_SPEED = 7900;//第一宇宙速度
int MIN_SPEED = 1;//省略了public static final
//抽象方法
public abstract void fly();
//省略了public abstract
void stop();
//Interfaces cannot have constructors
// public Flyable(){
//
// }
}
interface Attackable{
void attack();
}
class Plane implements Flyable{
@Override
public void fly() {
System.out.println("通过引擎起飞");
}
@Override
public void stop() {
System.out.println("驾驶员减速停止");
}
}
abstract class Kite implements Flyable{
@Override
public void fly() {
}
}
class Bullet extends Object implements Flyable,Attackable,CC{
@Override
public void attack() {
// TODO Auto-generated method stub
}
@Override
public void fly() {
// TODO Auto-generated method stub
}
@Override
public void stop() {
// TODO Auto-generated method stub
}
@Override
public void method1() {
// TODO Auto-generated method stub
}
@Override
public void method2() {
// TODO Auto-generated method stub
}
}
//************************************
interface AA{
void method1();
}
interface BB{
void method2();
}
interface CC extends AA,BB{
}
- 代理模式
package com.atguigu.java1;
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
// server.browse();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();
}
}
interface NetWork{
public void browse();
}
//被代理类
class Server implements NetWork{
@Override
public void browse() {
System.out.println("真实的服务器访问网络");
}
}
//代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work){
this.work = work;
}
public void check(){
System.out.println("联网之前的检查工作");
}
@Override
public void browse() {
check();
work.browse();
}
}
- dat15-java8
调用接口中 的静态方法: 接口.方法
调用接口中的默认方法: 接口.super.方法
调用父类的方法: super.方法()
当然也可以自己重写方法,然后: 方法()
package com.atguigu.java8;
public class SubClassTest {
public static void main(String[] args) {
SubClass s = new SubClass();
// s.method1();
// SubClass.method1();
//知识点1:接口中定义的静态方法,只能通过接口来调用。
CompareA.method1();
//知识点2:通过实现类的对象,可以调用接口中的默认方法。
//如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
s.method2();
//知识点3:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,
//那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。-->类优先原则
//知识点4:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,
//那么在实现类没有重写此方法的情况下,报错。-->接口冲突。
//这就需要我们必须在实现类中重写此方法
s.method3();
s.myMethod();
}
}
class SubClass extends SuperClass implements CompareA,CompareB{
public void method2(){
System.out.println("SubClass:上海");
}
public void method3(){
System.out.println("SubClass:深圳");
}
//知识点5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
public void myMethod(){
method3();//调用自己定义的重写的方法
super.method3();//调用的是父类中声明的
//调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}
}
* 在局部内部类的方法中(比如:show)如果调用局部内部类所声明的方法(比如:method)中的局部变量(比如:num)的话,
* 要求此局部变量声明为final的。
*
* jdk 7及之前版本:要求此局部变量显式的声明为final的
* jdk 8及之后的版本:可以省略final的声明
public void method(){
//局部变量
int num = 10;
class AA{
public void show(){
// num = 20;
System.out.println(num);
}
}
}
- 异常处理
1异常概述与异常体系结构
2常见异常
3异常处理机制一: try-catch-finally
4异常处理机制二: throws
5手动抛出异常: throw
6用户自定义异常类: * Error * Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。
* 一、异常的处理:抓抛模型
*
* 过程一:"抛":程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。
* 并将此对象抛出。
* 一旦抛出对象以后,其后的代码就不再执行。
*
* 关于异常对象的产生:① 系统自动生成的异常对象
* ② 手动的生成一个异常对象,并抛出(throw)
*
* 过程二:"抓":可以理解为异常的处理方式:① try-catch-finally ② throws
*
*
* 二、try-catch-finally的使用
*
* try{
* //可能出现异常的代码
*
* }catch(异常类型1 变量名1){
* //处理异常的方式1
* }catch(异常类型2 变量名2){
* //处理异常的方式2
* }catch(异常类型3 变量名3){
* //处理异常的方式3
* }
* ....
* finally{
* //一定会执行的代码
* }
*
* 说明:
* 1. finally是可选的。
* 2. 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象
* 的类型,去catch中进行匹配
* 3. 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的
* try-catch结构(在没有写finally的情况)。继续执行其后的代码
* 4. catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。
* catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错
* 5. 常用的异常对象处理的方式: ① String e.getMessage() ② e.printStackTrace()
* 6. 在try结构中声明的变量,再出了try结构以后,就不能再被调用,通常将其声明在try catch 外部做初始化,然后在try catch 内部重新赋值
* 7. try-catch-finally结构可以嵌套
*
* 体会1:使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错。
* 相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
*
* 体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。
* 针对于编译时异常,我们说一定要考虑异常的处理。
finally
* try-catch-finally中finally的使用:
* 1.finally是可选的
* 2.finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中有return语句,catch中有 return语句等情况,
* 在return之前会先执行finally中的代码
* 3.像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的释放。
* 此时的资源释放,就需要声明在finally中。
便捷操作
选中代码块,右键选择 Surround with 选择 try-catch block 可自动捕获异常
- throws +异常类型
* 1. "throws + 异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。
* 一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常
* 类型时,就会被抛出。异常代码后续的代码,就不再执行!
* 2. 体会:try-catch-finally:真正的将异常给处理掉了。
* throws的方式只是将异常抛给了方法的调用者。 并没有真正将异常处理掉。
* 3. 开发中如何选择使用try-catch-finally 还是使用throws?
* 3.1 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果
* 子类重写的方法中有异常,必须使用try-catch-finally方式处理。
* 3.2 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws
* 的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
public class ExceptionTest2 {
public static void main(String[] args){
try{
method2();
}catch(IOException e){
e.printStackTrace();
}
method3();
}
public static void method3(){
try {
method2();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void method2() throws IOException{
method1();
}
public static void method1() throws FileNotFoundException,IOException{
File file = new File("hello1.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.print((char)data);
data = fis.read();
}
fis.close();
System.out.println("hahaha!");
}
}
- 重写方法同异常的关系
* 方法重写的规则之一:
* 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
public class OverrideTest {
public static void main(String[] args) {
OverrideTest test = new OverrideTest();
test.display(new SubClass());
}
public void display(SuperClass s){
try {
s.method();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class SuperClass{
public void method() throws IOException{
}
}
class SubClass extends SuperClass{
public void method()throws FileNotFoundException{
}
}
- throw 主动抛出一个异常\用户自定义异常类
class Student{
private int id;
public void regist(int id) throws RuntimeException {
if(id > 0){
this.id = id;
}else{
// System.out.println("您输入的数据非法!");
//手动抛出异常对象
throw new RuntimeException("您输入的数据非法!");
// throw new Exception("您输入的数据非法!");
//错误的
// throw new String("不能输入负数");
}
}
@Override
public String toString() {
return "Student [id=" + id + "]";
}
}
* 如何自定义异常类?
* 1. 继承于现有的异常结构:RuntimeException 、Exception(常用的)注意运行时异常和编译时异常,
* 2. 提供全局常量:serialVersionUID
* 3. 提供重载的构造器
public class MyException extends Exception{ //此处<-在手动抛出异常的时候,在类中的方法时需要注意,如果是RuntimeException,下面此处编译的时候就不需要throws Exception
static final long serialVersionUID = -7034897193246939L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
class Student{
private int id;
public void regist(int id) throws Exception {//<-此处在手动抛出异常的时候,在类中的方法时需要注意
if(id > 0){
this.id = id;
}else{
// System.out.println("您输入的数据非法!");
//手动抛出异常对象
// throw new RuntimeException("您输入的数据非法!");
// throw new Exception("您输入的数据非法!");
throw new MyException("不能输入负数");
//错误的
// throw new String("不能输入负数");
}
}
}
- 常见的面试题,结构相似的不相似的
throw throws
Collection Collections
String StringBuffer StringBulider
ArrayList LinkedList
HashMap LinkedHashMap
重写重载
结构不相似的
抽象类、接口
==,equals()
sleep() waite()
接下来就是IDEA部分了
多线程创建方式一:继承于Thread类
* 多线程的创建,方式一:继承于Thread类
* 1. 创建一个继承于Thread类的子类
* 2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
* 3. 创建Thread类的子类的对象
* 4. 通过此对象调用start()
* 问题
* 1. 如果单纯的通过实例.run()方法,说明根本没有启动多线程,一直是按照以前的顺序在main()中运行
* 2.:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException,可以在创建一个实例
常用API
Thread.currentThread().getName()
* 测试Thread中的常用方法:
* 1. start():启动当前线程;调用当前线程的run()
* 2. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
* 3. currentThread():静态方法,返回执行当前代码的线程
* 4. getName():获取当前线程的名字
* 5. setName():设置当前线程的名字
* 6. yield():释放当前cpu的执行权
* 7. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才
* 结束阻塞状态。 thread1.join();
* 8. stop():已过时。当执行此方法时,强制结束当前线程。
* 9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前
* 线程是阻塞状态。
* 10. isAlive():判断当前线程是否存活
* 线程的优先级:
* 1.
* MAX_PRIORITY:10
* MIN _PRIORITY:1
* NORM_PRIORITY:5 -->默认优先级
* 2.如何获取和设置当前线程的优先级:
* getPriority():获取线程的优先级
* setPriority(int p):设置线程的优先级
*
* 说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下
* 被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
创建多线程的方式二:实现Runnable接口
* 1. 创建一个实现了Runnable接口的类
* 2. 实现类去实现Runnable中的抽象方法:run()
* 3. 创建实现类的对象
* 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5. 通过Thread类的对象调用start()
两种创建方式的比较
* 比较创建线程的两种方式。
* 开发中:优先选择:实现Runnable接口的方式
* 原因:1. 实现的方式没有类的单继承性的局限性
* 2. 实现的方式更适合来处理多个线程有共享数据的情况。
* 联系:public class Thread implements Runnable
* 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
实现Callable接口(JDK5.0新增的)
与使用Runnable相比,Callable功 能更强大些
➢相比run()方法,可以有返回值
➢方法可以抛出异常
➢支持泛型的返回值
➢需要借助Future Task类, 比如获取返回结果
Future接口
➢可以对具体Runnable、Callable任 务的执行结果进行取消、查询是
否完成、获取结果等。
➢FutrueTask是 Futrue接口的唯- -的实现类
➢FutureTask 同时实现了Runnable, Future接口。它既可以作为
Runnable被线程执行,又可以作为Future得到Callable的返回值
* 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
* 1. call()可以有返回值的。
* 2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
* 3. Callable是支持泛型的
eg:
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread); //FutureTask是一个类实现了实现了Future接口以外,FutureTask还实现了Runnabl接口
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
使用线程池(JDK5.0新增的)
背景好处:
背景:
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
➢提高响应速度(减少了创建新线程的时间)
➢降低资源消耗(重复利用线程池中线程,不需要每次都创建)
➢便于线程管理
corePoolSize: 核心池的大小
maximumPoolSize:最大线程数
keepAliveTime: 线程没有任务时最多保持多长时间后会终止
eg:
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
System.out.println(service.getClass()); //ThreadPoolExecutor继承于AbstractExecutorService,
//AbstractExecutorService实现了ExecutorService 接口
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
线程的生命周期
➢新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
➢就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
➢运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
➢阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
➢死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
wait() 是Object类中的一个方法
Java的同步机制解决线程安全问题
同步代码块
* 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
* 1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
* 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
* 3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他
* 线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
* 4.在Java中,我们通过同步机制,来解决线程的安全问题。
* 方式一:同步代码块
* synchronized(同步监视器){
* //需要被同步的代码
* }
* 说明:1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
* 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
* 3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
* 要求:多个线程必须要共用同一把锁。
* 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
eg: 实现Runnable接口的同步(一个窗口全卖光)
class Window1 implements Runnable{
private int ticket = 100;
// Object obj = new Object();
// Dog dog = new Dog();
@Override
public void run() {
// Object obj = new Object();
while(true){
synchronized (this){//此时的this:唯一的Window1的对象 //方式二:synchronized (dog) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Dog{ }
eg: 继承Thread类的同步(当同步代码块包住while(true)的时候,是一个窗口全卖光,否则是随机卖)
Class clazz = Window2.class,Window2.class只会加载一次
在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
class Window2 extends Thread{
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while(true){
//正确的
// synchronized (obj){
synchronized (Window2.class){//Class clazz = Window2.class,Window2.class只会加载一次
//错误的方式:this代表着t1,t2,t3三个对象
// synchronized (this){
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":卖票,票号为:" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 t1 = new Window2();
Window2 t2 = new Window2();
Window2 t3 = new Window2();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步方法
* 使用同步方法解决实现Runnable接口的线程安全问题
* 关于同步方法的总结:
* 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
* 2. 非静态的同步方法,同步监视器是:this
* 静态的同步方法,同步监视器是:当前类本身
eg: 同步方法解决实现Runnable接口的线程
class Window3 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show(){//同步监视器:this
//synchronized (this){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
//}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 w = new Window3();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
eg:同步方法解决继承Thread类的线程安全问题
class Window4 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private static synchronized void show(){//同步监视器:Window4.class
//private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 t1 = new Window4();
Window4 t2 = new Window4();
Window4 t3 = new Window4();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
解决线程安全问题三Lock锁-----JDK5.0新增
注意:下面的例子是实现Runnable接口用Lock解决线程安全问题的,如果采用继承Thread类的方式,需要注意将private static ReentrantLock lock = new ReentrantLock();
声明为静态的
class Window implements Runnable{
private int ticket = 100;
//1.在实现实Runnable接口的类中, 例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
//2.调用锁定方法lock()
lock.lock();
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally {
//3.调用解锁方法:unlock()
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
面试题:Lock和Synchronized的异同
* 相同:二者都可以解决线程安全问题
* 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
* Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
* 2.优先使用顺序:
* Lock -> 同步代码块(已经进入了方法体,分配了相应资源) -> 同步方法(在方法体之外)
* 3 面试题:如何解决线程安全问题?有几种方式
* 同步方法、同步代码块、JDK5.0新增的Lock接口
使用同步机制将单例模式中的懒汉式改写为线程安全的
day14 不考虑线程的懒汉式饿汉式
//饿汉式
class Bank{
//1.私有化类的构造器
private Bank(){
}
//2.内部创建类的对象
//4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
//3.提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return instance;
}
class Order{
//1.私有化类的构造器
private Order(){
}
//2.声明当前类对象,没有初始化
//4.此对象也必须声明为static的
private static Order instance = null;
//3.声明public、static的返回当前类对象的方法
public static Order getInstance(){
if(instance == null){
instance = new Order();
}
return instance;
}
}
传统的懒汉式
class Bank{
private Bank(){ }
private Bank bank = null;
public Bank getInstance(){
if(bank==null){
bank = new Bank();
}
return bank;
}
}
同步方法解决
class Bank{
private Bank(){ }
private Bank bank = null;
public Bank synchronized getInstance(){//静态方法的锁是类Bank.class
if(bank==null){
bank = new Bank();
}
return bank;
}
}
同步代码块解决(效率稍差)
class Bank{
private Bank(){ }
private Bank bank = null;
public Bank getInstance(){
synchronized (Bank.class) {
if(bank==null){
bank = new Bank();
}
}
return bank;
}
}
同步代码块解决(效率稍高)
class Bank{
private Bank(){ }
private Bank bank = null;
public Bank getInstance(){
if(bank==null){
synchronized (Bank.class) {
if(bank==null){
bank = new Bank();
}
}
}
return bank;
}
}
死锁
* 用到同步方法,就会涉及到同步监视器,如果用到了两不同类的同步方法,就是两个不同类的对象作为同步监视器,
* 如果一个线程是先调A后调B,另外一个 是先调B后调A,里面就一定会出现死锁的问题,
* 解决方法:专门的算法原则(银行家算法);尽量减少同步资源的定义,尽量避免嵌套同步
线程的通讯
* 线程通信的例子:使用两个线程打印 1-100。线程1, 线程2 交替打印
* 涉及到的三个方法:
* wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
* notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
* notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
* 说明:
* 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
* 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
* 否则,会出现IllegalMonitorStateException异常
* 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
* 面试题:sleep() 和 wait()的异同?
* 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
* 2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
* 2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
* 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
* @author shkstart
* @create 2019-02-15 下午 4:21
class Number implements Runnable{
private int number = 1;
private Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj) {
obj.notify(); //需要注意的是必须是声明的同步监视器调用该notify wait 方法,否则会报异常,但是如果显示声明的同步监视器是this的话,则可以省略写为
//notify(); 以为此时默认调用的就是this Number的实例number
if(number <= 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使得调用如下wait()方法的线程进入阻塞状态
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
System.out.println("abc");
}
}
面试题:sleep()和wait()方法的不同
* 面试题:sleep() 和 wait()的异同?
* 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
* 2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
* 2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
* 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。