java基础
1.堆,栈,方法区的作用
堆:所有对象实例以及数组都要在堆上分配
栈(虚拟机栈):存储局部变量,即:基本数据类型、对象引用
方法区:存储已被虚拟机加载的类信息,常量,静态变量,编译后的代码
2.Java中的值传递机制
java中引用类型的变量有:数组和对象,赋值时赋值的是地址。
若参数是基本数据类型,则实参将真实的数据值复制给形参
若参数是引用数据类型,则实参将引用数据类型的地址复制给形参
3.对Java中封装性的体现和理解
①程序设计追求高内聚低耦合,即类的内部操作细节自己完成,不允许外部干涉;仅对外暴露需要使用的少量方法。封装性要求隐藏对象内部的复杂性,只对外公开需要的接口,便于外界调用,从而提高系统的可维护性、可扩展性,符合程序设计的原则。
②类的成员有:属性、方法、构造器、代码块、内部类;使用java中的访问权限修饰符(private、缺省、protected、public)对类的成员进行修饰的过程以及使用(缺省、public)对类进行修饰的过程就体现了封装性。
4.JavaBean的满足条件和作用
满足条件:①是一个public的类 ②有一个public的空参构造器 ③有属性且有对应的get、set方法
作用:可以使用JavaBean将功能、处理、值、数据库访问以及任何可用Java创造的对象进行打包以供他人使用。
5.类图
6.this的用法
this可用在属性、方法、构造器上。理解为当前对象,当前正在创建的对象。
1.修饰属性和方法,指定当前对象的属性或方法。当类中属性和方法的形参重名时,使用“this.属性”代表该变量为属性而非形参。(this.方法也是一样的,但是一般省略this)
2.可以使用“this(形参)”的方式调用本类中指定的其它构造器,构造器中不能调用自己。(调用别的构造器必须定义在首行,一个构造器内部只能调用一个其他构造器)
7.局部变量和全局变量的区别
问题:成员变量和局部变量在声明的位置上、是否有默认初始化值上、 是否能有权限修饰符修饰上、内存分配的位置上有何不同?
①成员变量: 直接在类中声明的变量叫成员变量(又称全局变量、属性)
- 初始化:如果未对成员变量设置初始值,则系统会根据成员变量的类型自动分配初始值:int分配初始值0、boolean分配初始值false,而自定义类型则分配初始值null
- 作用范围:成员变量定义后,其作用域是其所在的整个类。且成员变量的定义没有先后顺序,但是最好将成员变量的定义集中在类的顶部。
②局部变量:方法中的参数、方法中定义的变量和代码块中定义的变量统称为局部变量。
- 初始化: (1)局部变量在使用以前必须显式初始化或赋值,局部变量没有默认值。
(2)声明局部变量时,数据类型前除final外不允许有其他关键字,即其定义格式为: [final] 数据类型 变量名 = 初始值;
- 作用范围: 局部变量的作用域范围从定义的位置开始到其所在语句块结束。
③另外注意:
(1)如果局部变量的名字与全局变量的名字相同,则在局部变量的作用范围内全局变量被隐藏,即这个全局变量在同名局部变量所在方法内暂时失效。
(2)所以如果在局部变量的作用域范围内访问该成员变量,则必须使用关键字this来引用成员变量。
8.类的属性的赋值方式有哪几种?
默认初始化
显式初始化
构造器中初始化
对象.方法或对象.属性
9.构造器的作用及使用规则
作用:创建、初始化对象
使用规则说明:
- 若没有显示定义类的构造器,系统会默认提供一个空参构造器
- 一旦显示定义了类的构造器,系统不再提供默认的空参构造器
- 一个类中至少有一个构造器
- 在类的构造器中,可以显式的使用"this(形参列表)"方式, 调用本类中指定的其他构造器;构造器中不能通过"this(形参列表)"方式调用自己
- 如果一个类中有n个构造器,则最多有n - 1构造器中使用了"this(形参列表)"
- 构造器内部,最多只能声明一个"this(形参列表)",来调用其他的构造器,且必须声明在当前构造器的首行
10.IntelliJ Idea 常用快捷键列表
Alt+回车 导入包,自动修正
Ctrl+N 查找类
Ctrl+Shift+N 查找文件
Ctrl+Alt+L 格式化代码
Ctrl+Alt+O 优化导入的类和包
Alt+Insert 生成代码(如get,set方法,构造函数等)
Ctrl+E或者Alt+Shift+C 最近更改的代码
Ctrl+R 替换文本
Ctrl+F 查找文本
Ctrl+Shift+Space 自动补全代码
Ctrl+空格 代码提示
Ctrl+Alt+Space 类名或接口名提示
Ctrl+P 方法参数提示
Ctrl+Shift+Alt+N 查找类中的方法或变量
Alt+Shift+C 对比最近修改的代码
Shift+click关闭当前文件
Shift+F6 重构-重命名
Ctrl+Shift+先上键
Ctrl+X 删除行
Ctrl+D 复制行
Ctrl+/ 或 Ctrl+Shift+/ 注释(// 或者/…/ )
Ctrl+J 自动代码
Ctrl+w 选中一个词,可以使用
Ctrl+E 最近打开的文件
Ctrl+H 显示类结构图
Ctrl+Q 显示注释文档
Alt+F1 查找代码所在位置
Alt+1 快速打开或隐藏工程面板
Ctrl+Alt+ left/right 返回至上次浏览的位置
Alt+ left/right 切换代码视图
Alt+ Up/Down 在方法间快速移动定位
Ctrl+Shift+Up/Down 代码向上/下移动。
F2 或Shift+F2 高亮错误或警告快速定位
ctrl+alt+t Surround With快捷键(选中想要包裹的代码,按住此快捷键,会出现以下内容:选中某个即可)
11.继承性
- 体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,
父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私的结构。只因为封装性的影响,使得子类不能直接调用父类的结构而已。 - 优点:子类继承父类以后,可以使用父类中的属性和方法,还可以声明自己特有的属性或方法:提高了代码的复用性,功能的可拓展性,并为之后多态性的使用,提供了前提。
12.方法的重写(重置、覆盖)
- 定义:在子类中可以根据需要对从父类中继承来的方法进行改造。在程序执行时,子类的方法将覆盖父类的方法。
- 要求:
1.子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
2.子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
父类被重写的方法的返回值类型是基本数据类型(比如: double), 则子类重写的方法的返回值类型必须是相同基本数据类型
3.子类重写的方法使用的访间权限不能小于父类被重写的方法的访间权限;子类不能重写父类中声明为private权限的方法
4.子类方法抛出的异常不能大于父类被重写方法的异常 - 注意:子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为
static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。
13.super关键字的使用
- super可以用来调用父类的:属性、方法、构造器
- 可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
- 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中未被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中未被重写的方法。
- 可以在子类的构造器中显式的使用"super (形参列表)"的方式,调用父类中声明的指定的构造器,且"super(形参列表 ) "的使用,必须声明在子类构造器的首行!
- 在类的构造器中,"this (形参列表) "或"super(形参列表)"只能二选一,不能同时出现。若构造器的首行没有显式的声明"this (形参列表) “或"super (形参列表)”,则默认调用的是父类中空参的构造
- 在类的多个构造器中,至少有一个类的构造器中使用了"super (形参列表)",调用父类中的构造器
14.子类对象实例化的全过程
- 从结果上看 (继承性):子类继承父类以后,就获取了父类中声明的属性或方法。创建子类的对象,就会在堆空间中加载所有父类中声明的属性和子类属性。
- 从过程上来看:当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中的空参构造器为止。正因为加载过所有的父类的结构,子类对象才可以考虑进行调用。
- 明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
15.多态(一个事物的多种形态。父类的引用指向子类的对象。)
多态的使用(虚拟方法调用):
有了对象的多态性以后,在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。总结:编译,看左边;运行,看右边。对象的多态性,只适用于方法,不适用于属性(属性一直调用的是父类的属性)
多态性的使用前提: ①类的继承关系②方法的重写
16.方法的重载和重写的区别
- 定义:
重载:在一个类中,允许存在参数个数和类型不同的同名方法
重写:在子类中重写的与父类中同名同参数的方法 - 从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法, 这称为“早绑定”或“静态绑定”;而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体 方法,这称为“晚绑定”或“动态绑定。
关于重写的练习
public class Base {
int count=10;
public void display(){
System.out.println(this.count);
}
}
class Sub1 extends Base{
int count=20;
@Override
public void display() {
super.display();
}
}
class Sub2 extends Base{
int count=20;
@Override
public void display() {
System.out.println(this.count);
}
}
public class BaseTest {
public static void main(String[] args) {
Sub1 s1 = new Sub1();
System.out.println(s1.count);//20
s1.display();//10
Base b1 = s1;
System.out.println(b1==s1);//true
System.out.println(b1.count);//10
b1.display();//10
System.out.println("****************");
Sub2 s2 = new Sub2();
System.out.println(s2.count);//20
s2.display();//20
Base b2 = s2;
System.out.println(b2==s2);//true
System.out.println(b2.count);//10
b2.display();//20
}
}
17.向上/下转型
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。如何才能调用子类特有的属性和方法?向下转型:使用强制类型转换符。
instanceof关键字的使用:
为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型(a instanceof A)判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
使用情境:
使用instanceof避免向下转型出现过程中出现异常
public class PersonTest {
public static void main(String[] args) {
Person p = new Person();
p.eat();
//p是父类的引用指向父类对象,不能向下转型为子类对象,编译时不错,运行时报错。
//Man m1 = (Man)p;
//m1.eat();
Person p1 = new Man("p1",11);//多态:p1是父类引用指向子类对象。
// 编译时看左边,运行时看右边,因此只能调用父类中的方法、属性。
System.out.println(p1.name);//调用的是父类对象的属性,返回的是默认初始化值null
System.out.println(p1.age);//调用的是父类对象的属性,返回的是默认初始化值0
//向下转型,由于上面使用了多态,所以对象p1不能使用子类中独有的方法,向下转型之后就可以了
Man m2 = (Man)p1;
m2.eat();
m2.smoke();
System.out.println("anme:"+m2.name+" age:"+m2.age);
if(p1 instanceof Woman){
Woman w1 = (Woman)p1;
w1.shopping();
}else if(p1 instanceof Man){
Man m3 = (Man)p1;
m3.smoke();
}
}
}
class Person {
public String name;
public int age;
public void eat(){
System.out.println("person啥都吃");
}
}
class Man extends Person{
public String name;
public int age;
public Man(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("man吃肉");
}
public void smoke(){
System.out.println("man要抽烟");
}
}
class Woman extends Person{
public String name;
public int age;
public Woman(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("woman吃青菜");
}
public void shopping(){
System.out.println("woman逛商场");
}
}
18.【java 构造函数】为什么子类一定要访问父类中的构造函数
- 原因:子类的所有构造函数中的第一行,其实都有一条隐身的语句super();
- 解释:super()表示父类的构造函数并会调用参数相对应的父类中的构造函数。子类中,它在调用父类中空参数的构造函数。因为子类继承父类,会继承到父类中的数据,所以必须要看父类是如何对自己的数据进行初始化的。所以子类在进行对象初始化时,先调用父类的构造函数,这就是子类的实例化过程。
- 特别注意:
1、子类中所有的构造函数都会默认访问父类中的空参数的构造函数,因为每一个子类构造内第一行都有默认的语句super();
2、若父类中没有空参数的构造函数,那么子类的构造函数内,必须通过super语句指定要访问的父类中的构造函数;
3、若子类构造函数中用this来指定调用子类自己的构造函数,那么被调用的构造函数也一样会访问父类中的构造函数。
19.Object类
Object类 是所有Java类的根父类,如果在类的声明中未使用extends关键字指明其父类,则默认父类
为java.lang.Object类。即 public class Person {} 等价于: public class Person extends Object {}
20.==和equals的区别
- ==运算符:
如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。 (不一定类型要相同)
如果比较的是引用数据类型变量:比较两个对象的地址值是否相同。即两个引用是否指向同一个对象实体。 - equals( )方法的使用: 只能适用于引用数据类型。Object类中 equals()的定义:
public boolean equals(Object obj) {return (this == obj);}
说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同。即两个引用是否指向同一个对象。String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们 就需要对Object类中的equals()进行重写。
import java.util.Date;
import java.util.Objects;
public class EqualsTest {
public static void main(String[] args) {
int i =10;
int j=10;
double d = 10.0;
System.out.println(i==j);//true
System.out.println(i==d);//true
char c = 10;
System.out.println(i==c);//true
char c1='A';
char c2=65;
System.out.println(c1==c2);//true
Customer customer1 = new Customer("Tom",21);
Customer customer2 = new Customer("Tom",21);
System.out.println(customer1==customer2);//false
//注释重写的equals后false:比的是地址
//使用重写的equals后true:比的是对象里实际的值
System.out.println(customer1.equals(customer2));
String str1=new String("suzhitong");
String str2=new String("suzhitong");
System.out.println(str1==str2);//false:比的是地址
System.out.println(str1.equals(str2));//true:重写了equals(String)方法,用于对比值
Date date1 = new Date(32432525324L);
Date date2 = new Date(32432525324L);
System.out.println(date1==date2);//false
System.out.println(date1.equals(date2));//true
}
}
class Customer {
private String name;
private int age;
public Customer(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Customer)) return false;
Customer customer = (Customer) o;
//说明同一个字符串在常量池只存在一个,地址唯一。??????
return age == customer.age && Objects.equals(name, customer.name);
}
public static boolean equals(Object o1, Object o2) {
if (o1 == o2) return true;
if (!(o1 instanceof Customer) || !(o1 instanceof Customer)) return false;
Customer customer1 = (Customer) o1;
Customer customer2 = (Customer) o2;
return customer1.age == customer2.age && Objects.equals(customer1.name, customer2.name);
}
@Override
public String toString() {
return "Customer{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
21.toString方法
22.JUnit单元测试(eclipes)
- 步骤:
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.如果执行结果出现异常:红条
23.包装类(Wrapper)
基本数据类型、包装类与String三者之间如何转换
基本数据类型<—>包装类: JDK 5.0新特性:自动装箱与自动拆箱
基本数据类型、包装类—>String:调用String重载的value0f(Xxx xxx)
String—>基本数据类型、包装类:调用包装类的parseXxx(String s)
public class WrapperTest {
public static void main(String[] args) {
WrapperTest wrapper = new WrapperTest();
wrapper.test1();
}
public void test1(){
Integer in1 = new Integer(12);
int i1 = in1.intValue();//将Integer转为int,老方法
System.out.println(in1+1);//不转换也可以输出,自动从包装类转换为基本数据类型
System.out.println(i1);
method(i1);//自动从基本数据类型转换为包装类 Object o = i1
//自动装箱
int i2 = 20;
Integer in2 = i2;
System.out.println(in2);
boolean b1 = true;
Boolean bo1 = b1;
System.out.println(bo1);
//自动拆箱
int i3 = in2;
System.out.println(i3);
//基本数据类型、包装类--->String value0f(Xxx xxx)
float f1 = 12.12f;
String str1 = String.valueOf(f1);
System.out.println(str1);
Double d1 = new Double(12.45);
String str2 = String.valueOf(d1);
System.out.println(str2);
//String类型--->基本数据类型、包装类 parseXxx(String s)
String str3 = "111";
int i4 = Integer.parseInt(str3);
System.out.println(i4);
String str4 = "true1";
boolean b2 = Boolean.parseBoolean(str4);
System.out.println(b2);
}
public void method(Object o){
System.out.println(o);
}
}
???
24.变量的分类
按照数据类型:
按照声明的位置:
25.static关键字
static(静态的):可用来修饰:属性、方法、代码块、内部类
1.使用static修饰属性:(静态变量)
① 按是否使用static修饰,又分为:静态属性(或类变量)Vs非静态属性(实例变量)
实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
静态变量:我们创建了类的多个对象,多个对象共享同一一个静态变量。当通过某一个对象修改静态变量时, 会导致其他对象调用此静态变量时,是修改过了的。
② 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用
③ 静态变量的加载要早于对象的创建。
④ 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
2.使用static修饰方法:(静态方法、类方法)
①随着类的加载而加载,可以通过"类·静态方法"的方式进行调用
②静态方法中,只能调用静态的方法或属性;
非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性,也可以调用静态的方法或属性
③ 在静态的方法内,不能使用this关键字、super关键字
3. 开发中,如何确定一个属性是否要声明为static的?
属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
类中的常量也常常声明为static
4. 开发中,如何确定一个方法是否要声明为static的?
操作静态属性的方法,通常设置为static的
工具类中的方法,习惯上声明为static的。 比如: Math、Arrays、Collections
26.单例设计模式
单例设计模式:
在整个的软件系统中,对某个类只能存在一个对象实例。
单例模式的优点:
由于单例模式只生成一一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
饿汉式:
坏处:对象加载时间过长。
好处:饿汉式是线程安全的
//单例模式:饿汉式
public class SingletonTest1 {
public static void main(String[] args) {
Bank bank1 =Bank.getBank();
Bank bank2 =Bank.getBank();
System.out.println(bank1==bank2);
}
}
class Bank{
//1.私有化构造器
private Bank() {}
//2.内部创建类的对象(由于以下静态的方法中只能调用静态的结构,因此这里需要将bank定义为静态对象)
private static Bank bank = new Bank();
//3.提供公共方法,返回类的对象
public static Bank getBank(){
return bank;
}
}
懒汉式:
好处:延迟对象的创建。(此处的懒汉式存在线程安全问题)
//单例模式:懒汉式
public class SingletonTest2 {
public static void main(String[] args) {
Order order1=Order.getOrder();
Order order2=Order.getOrder();
System.out.println(order1==order2);
}
}
class Order{
//1.私有化类的构造器
private Order() {}
//2.声明当前类的对象,没有初始化
private static Order order = null;
//3.声明public static的返回当前类对象的方法
public static Order getOrder(){
if (order==null){
order=new Order();
}
return order ;
}
}
单例(Singleton)设计模式-应用场景
1.网站的计数器,一般也是单例模式实现,否则难以同步。
2.应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
3.数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
4.项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
5.Application也是单例的典型应用
6.Windows的TaskManager(任务管理器)就是很典型的单例模式
7.Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一一个实例。
27.类的成员之四:代码块(或初始化块)
1.代码块的作用:用来初始化类、对象
2.代码块如果有修饰的话,只能使用static.
3.分类:静态代码块Vs非静态代码块
4.静态代码块
内部可以有输出语句
随着类的加载而执行,而且只执行一次
作用:初始化类的信息
如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
静态代码块的执行要优先于非静态代码块的执行
静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
5.非静态代码块
内部可以有输出语句
随着对象的创建而执行
每创建一个对象, 就执行一次非静态代码块
作用:可以在创建对象时,对对象的属性等进行初始化
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
6.实例化子类对象时,父类/子类中静态代码块、非静态代码块、构造器的加载顺序:
假设当前有父类和子类。在主函数中使用子类的构造器时,先执行父类的静态代码块,子类的静态代码块,父类的非静态代码块,父类的构造器,子类的非静态代码块,子类的构造器。
//总结:由父及子,静态先行
class Root{
static{
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root(){
super();
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid(){
super();
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg){
//通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:" + msg);
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf的静态初始化块");
}
{
System.out.println("Leaf的普通初始化块");
}
public Leaf(){
//通过super调用父类中有一个字符串参数的构造器
super("尚硅谷");
System.out.println("Leaf的构造器");
}
}
public class LeafTest{
public static void main(String[] args){
new Leaf();
System.out.println();
new Leaf();
}
}
Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器
对属性可以赋值的位置:
①默认初始化
②显式初始化/⑤在代码块中赋值
③构造器中初始化
④有了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值
执行的先后顺序:①-②/⑤-③-④
28.final :最终的
- final 可以用来修饰的结构:类、方法、变量
- final 用来修饰一个类:表明此类不能被其他类所继承。比如: String类、 System类、StringBuffer类
- final用来修饰方法:表明此方法不可以被重写。比如: Object类中getClass();
- final 用来修饰变量:此时的"变量"就称为是一个常量
4.1 final修饰属性:
可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
4.2 final修饰局部变量:
使用final修饰形参时,表明此形参是一个常量。 当我们调用此方法时,给常量形参赋一个实参。 一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
5.static final 用来修饰属性:全局常量
29.final 与 static
- static修饰的属性,相较于实例变量,有哪些特别之处(>=3点)?
随着类的加载而加载;
早于对象的创建;
只要权限允许,可以通过”对象.static属性”的方式进行调用;
存在于方法区的静态域 - final可以用来修饰哪些结构,分别表示什么意思。
- 类的属性赋值的位置有哪些?先后顺序为何?。
①默认初始化 、
②显式初始化 或 代码块中初始化、
③构造器中初始化、
④通过”对象.属性”或"对象.方法"的方式赋值,
30.abstract关键字
Java允许类设计者指定:超类声明一个方法但不提供实现,该方法的实现由子类提供。这样的方法称为抽象方法。有抽象方法的类称为抽象类。(抽象类不能实例化)
① abstract:抽象的;可以用来修饰:类、方法
② abstract修饰类:抽象类
此类不能实例化
抽象类中一-定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
③ abstract修饰方法:抽象方法
抽象方法只有方法的声明,没有方法体
包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法。
若子类重写了父类中的所有的抽象方法后,此子类方可实例化;
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类, 需要使用abstract修饰。
④思考
- 问题1:为什么抽象类不可以使用final关键字声明?
- 问题2:一个抽象类中可以定义构造器吗?
- 问题3:是否可以这样理解:抽象类就是比普通类多定义了抽象方法,除了不能直接进行类的实例化操作之外,并没有任何的不同?
1.final关键字修饰的类不能被子类继承,而abstract关键字修饰的类必须要使用子类来实现其类内部定义的抽象方法。因此修饰类时不能同时出现。
2.可以,子类实例化时需要访问父类的构造器。
3.可以,抽象类就是含有一个或几个抽象方法的类。
/* 抽象类的匿名子类*/
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
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){}
}
31.多态的应用:模板方法设计模式(TemplateMethod)
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础_上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:
➢当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:
●数据库访问的封装
●Junit单 元测试
●JavaWeb的Servlet中 关于doGet/doPost方法调用
●Hibernate中 模板程序
●Spring 中JDBC Temlate、Hibernate Template等
32.接口
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是"能不能"的关系。接口的本质是契约,标准,规范,就像我们的法律- -样。接口的使用
1.接口使用interface来定义
2.Java中,接口和类是并列的两个结构
3.如何定义接口:定义接口中的成员
3.1 JDK7及以前: 只能定义全局常量和抽象方法
>全局常量: public static final的 .但是书写时,可以省略不写
>抽象方法: public abstract的
3.2 JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法(略)
4.接口中不能定义构造器! 意味着接口不可以实例化
- Java开发中,接口通过让类去实现( implements )的方式来使用。如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化。如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类。
- Java类可以实现多个接口— >弥补了Java单继承性的局限性
格式: class AA extends BB implements CC,DD,EE
7.接口与接口之间可以继承,而且可以多继承。
8.接口的主要用途就是被实现类实现。( 面向接口编程)
9.与继承关系类似,接口与实现类之间存在多态性.
package com.szt.test4;
public class USBTest {
public static void main(String[] args) {
Computer computer = new Computer();
Flash flash = new Flash();
computer.transferData(flash);
}
}
class Computer{
public void transferData(USB usb){//USB usb = new Flash多态性
usb.start();
System.out.println("******chuangshuzhong*****");
usb.stop();
}
}
interface USB{
//常量
void start();
void stop();
}
//有usb接口的,都要实现interface中的方法
class Flash implements USB{
@Override
public void start() {
System.out.println("u盘开始工作");
}
@Override
public void stop() {
System.out.println("U盘停止工作");
}
}
class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开始工作");
}
@Override
public void stop() {
System.out.println("打印机停止工作");
}
}
Java 8中关于接口的改进
Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
- 静态方法:使用static
关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。 - 默认方法:默认方法使用default关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。 比如: java 8 API中对Collection、List、 Comparator等接 口提供了丰富的默认 方法。
接口中的默认方法
- 若一个接口中定义了一个默认方法,而另一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现:接口冲突。
➢解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。 - 若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有相同名称和参数的默认方法会被忽略。
public class subClassTest {
public static void main(String[] args) {
//知识点1:接口中定义的静态方法,只能通过接口来调用。
compareA.method1();
//知识点2:通过实现类的对象,可以调用接口中的默认方法。
//如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
subClass s = new subClass();
s.method2();
//知识点3:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,
//那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。-- >类优先原则
s.method3();
}
}
interface compareA{
public static void method1(){
System.out.println("compareA--method1");
}
public default void method2(){
System.out.println("compareA--method2");
}
public default void method3(){
System.out.println("compareA--method3");
}
}
class superClass{
public void method3(){
System.out.println("superClass--method3");
}
}
class subClass extends superClass implements compareA{
public void method2(){
System.out.println("subClass--method2");
}
}
compareA--method1
subClass--method2
superClass--method3
33.接口的应用:代理模式(Proxy)
概述:代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。●应用场景:
➢安全代理: 屏蔽对真实角色的直接访问。
➢远程代理:通过代理类处理远程方法调用(RMI)
➢延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。.
●分类
➢静态代理(静态定义代理类)
➢动态代理(动态生成代理类)
JDK自带的动态代理,需要反射等知识
34.接口的应用:工厂模式
工厂模式:实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。其实设计模式和面向对象设计原则都是为了使得开发项目更加容易扩展和维护,解诀方式就是一个“分工”。
- 简单工厂模式: 用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
- 工厂 方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
- 抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)
问题在于:px方法中的变量x分不清是接口的x还是父类的x。
解决办法2种:
①在接口和父类中使用不同名的变量
②在调用时使用super.x表示调用父类中的变量,使用A.x表示调用接口中的x。
问题:
①Ball中的方法play是对哪个接口中的paly的重写?答:对两个方法都进行了重写。
②Ball中不能再对接口Rollable中定义的对象进行赋值(修改)。接口中定义的属性和对象不能被修改。
35.类的成员之五:内部类
- ①当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
- ②Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。 ➢Inner
class的名字不能与包含它的外部类类名相同;
③内部类的分类:
成员内部类(static成员内部类和非static成员内部类) vs 局部内部类(方法内、代码块内、构造器内)
成员内部类:
一方面, 作为外部类的成员:
>调用外部类的结构
>可以被static修饰
>可以被4种不同的权限修饰
另一方面,作为一个类:
>类内可以定义属性、方法、构造器等
>可以被final修饰, 表示此类不能被继承。言外之意,不使用final,就可以被继承
>可以被abstract修饰
④如何创建成员内部类的对象? (静态的,非静态的) ;如何在成员内部类中调用外部类的结构?
public class innerClassTest {
public static void main(String[] args) {
//创建静态的Dog内部类的实例(静态的成员内部类):
Person.Dog dog = new Person.Dog();
dog.show();
//创建非静态的Bird内部类的实例(非静态的成员内部类):
Person p = new Person();
Person.Bird bird = p.new Bird();
bird.dispaly("sss");
}
}
class Person{
String name = "小明";
public void eat(){
System.out.println("Person---eat");
}
class Bird{
String name = "杜鹃";
public void dispaly(String name){
System.out.println(name);//方法的形参
System.out.println(this.name);//内部类的属性
System.out.println(Person.this.name);//外部类的属性
Person.this.eat();//调用外部类的非静态属性
}
}
static class Dog{
String name = "泰迪";
public void show(){
System.out.println(this.name);//内部类的属性
}
}
}
⑤局部内部类
在局部内部类的方法中(比如: show),如果调用局部内部类所声明的方法(比如: method)中的局部变量(比如: num)的话,要求此局部变量声明为final的。
jdk 7及之前版本:要求此局部变量显式的声明为final的
jdk 8及之后的版本:可以省略final的声明
⑥成员内部类和局部内部类,在编译以后,都会生成字节码文件。
格式: 成员内部类:外部类
内
部
类
名
.
c
l
a
s
s
局
部
内
部
类
:
外
部
类
内部类名.class 局部内部类:外部类
内部类名.class局部内部类:外部类数字内部类名. class
//在一个方法中定义一个内部实现类,实现Comparable接口,同时返回一个实现了接口的MyComparable对象。
//返回一个实现了Comparable接口的类的对象
public Comparable getComparable(){
//创建-一个实现了Comparable接口的类:局部内部类
class MyComparable implements Comparable{
@Override
public int compareTo(object o) {
return 0;
}
}
return new MyComparable();
}
36.接口 与 抽象类
- 接口是否能继承接口?抽象类是否能实现(implements)接口?抽象类是否能继承非抽象的类? 能,能,能
- 抽象类和接口有哪些共同点和区别?
相同点: 不能实例化,都可以被继承 不同点:
抽象类: 有构造器。接口:不能声明构造器。多继承vs单继承4 - 注意点:
1.abstract不能用来修饰:属性、构造器等结构
2.abstract不能用来修饰私方法、静态方法、final的方法、 final的类 - Java8中关于接口的新规范
**知识点1:**接口中定义的静态方法,只能通过接口来调用。
**知识点2:**通过实现类的对象,可以调用接口中的默认方法。 如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
**知识点3:**如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没重写此方法的情况下,默认调用的是父类中的同名同参数的方法。–>类优先原则
**知识点4:**如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没重写此方法的情况下,报错。–>接口冲突。 //这就需要我们必须在实现类中重写此方法
**知识点5:**如何在子类(或实现类)的方法中调用父类、接口中被重写的方法:
public void myMethod(){
method3();//调用自已定义的重写的方法
super.method3();//调用的是父类中声明的
//调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}
37.异常概述与异常体系结构
Java程序在执行过程中所发生的异常事件可分为两类:
➢Error: Java 虚拟机无法解决的严重问题。如: JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。 一般不编写针对性的代码进行处理。
➢Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如: 空指针访问、试图读取不存在的文件、网络连接中断、数组角标越界。一般有两种解决方法:一是遇到错误就终止程序的运行。另一种方法是由程序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生。比如:除数为0,数组下标越界等。
➢异常分类:编译时异常 、运行时异常。
运行时异常:
public class exceptionTest {
//Nul1PointerException
@Test
public void test1(){
int[]arr=null;
System.out.println(arr[3]) ;
}
//ArrayIndexOutOfBounds Exception
@Test
public void test2( ){
int[] arr = new int[10] ;
System.out.println(arr[10]) ;
}
//StringIndexOutOfBoundsException
@Test
public void test3(){
String str = "abc" ;
System.out.println(str.charAt(3));
}
//ClassCastException
@Test
public void test4(){
Object obj = new Date( );
String str = (String)obj ;
}
//NumberFormat Exception
@Test
public void test5() {
String str = "123";
str = "abc";
int num = Integer.parseInt(str);
}
// InputMismatchException
@Test
public void test6(){
Scanner scanner = new Scanner(System.in) ;
int score = scanner.nextInt() ;
System.out.println(score);
}
//Arithmetic Exception
@Test
public void test7(){
int a=10;
int b=0;
System.out.println(a/b);
}
}
编译时异常:
@Test
public void test7( ){
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read( );
while(data != -1){
System. out. print((char)data);
data = fis.read( );
}
fis.close();
}
38.Java异常处理的方式:
**
方式一: try-catch-finally
方式二: throws +异常类型
**
-
抓抛模型
Java程序的执行过程中如出现异常,会生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常。 -
过程一: “抛”:
程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。并将此对象抛出。一旦抛出对象以后,其后的代码就不再执行。关于异常对象的产生:①系统自动生成的异常对象、②手动的生成一个异常对象,并抛出(throw)。 -
过程二: “抓”:可以理解为异常的处理方式:①try-catch-finally ② throws
-
异常对象的生成
➢由虚拟机自动生成: 程序运行过程中,虚拟机检测到程序发生了问题,如果在当前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例对象并抛出一一自动抛出
➢由开发人员手动创建: Exception exception = new ClassCastException();——创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样。
39.异常处理方式一:try-catch-finally的使用
try{
/ /可能出现异常的代码
}catch(异常类型1变量名1){
/ /处理异常的方式1
}catch(异常类型2变量名2){
/ /处理异常的方式2
}catch(异常类型3变量名3){
/ /处理异常的方式3
finally{
//一定会执行的代码
}
public class tryCatchTest {
@Test
public void test1( ) {
String str = "123";
str = "abc";
try {
int num = Integer.parseInt(str);
System.out.println("he11o-----1");
} catch (NumberFormatException e) {
System.out.println("出现数值转换异常了,不要着急....");
//String getMessage()
System.out.println(e.getMessage());
//printStackTrace()
e.printStackTrace();
} catch (NullPointerException e) {
System.out.println("出现空指针异常了,不要着急....");
} catch (Exception e) {
System.out.println("出现异常了,不要着急....");
}
}
@Test
public void test2( ){
try{
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.print((char)data);
data = fis.read();
}
fis.close();
}catch(FileNotFoundException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
}
}
说明:
- finally是可选的。
- 使用try将可能出现异常代码包起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
- 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的
try-catch结构(在没有写finally的情况)。继续执行其后的代码。 - catch中 的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。
catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。 否则,报错 - 常用的异常对象处理的方式: ①String getMessage() ②printStackTrace()
- 在try结构中声明的变量,出了try结构后,就不能再被调用
- try-catch-finally结构可以嵌套
体会1:使用try-catch-finally处理编译时异常,使得程序在编译时就不再报错,但是运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时 可能出现的异常,延迟到运行时出现。
体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。针对于编译时异常,我们说一定要考虑异常的处理。
try-catch- finally中finally的使用:
- finally是可选的
- finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中有return语句,catch中有return语句等情况。
- 像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的
释放。此时的资源释放,就需要声明在finally中。
方法重写的规则之一:
- 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
40.异常处理的方式二: throws +异常类型
- "throws +异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws异常类型时,就会被抛出。异常代码后续的代码,就不再执行!
- 体会: try-catch- finally :真正的将异常给处理掉了。throws的方式只是将异常抛给了方法的调用者。并没有 真正将异常处理掉。
- 开发中如何选择使用try-catch-finally还是使用throws?
3.1如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
3.2执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throw的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
如何自定义异常类
- 继承于现有的异常结构: RuntimeException 、Exception
- 提供全局常量: serialVersionUID
public class myException extends RuntimeException{
static final long serialVersionUID = -7034897190745766939L ;
public myException(){}
public myException(String msg){
super(msg);
}
}
[ 面试题 ]
final、finally、 finalize三者的区别?
类似:
throw和throws
collection和collections ,
String、StringBuffer、 StringBuilder
ArrayList、LinkedList
HashMap、LinkedHashMap
重写、重载
结构不相似的:
抽象类、接口
==、
equals(),
sleep()、wait ( )
对比两种处理方式
try-catch-finally:真正的将异常给处理掉了。
throws:的方式只是将异常抛给了方法的调用者。并没真正将异常处理掉。
throw和throws
在程序执行中,除了自动抛出异常对象的情况之外,我们还可以手动的throw一个异常类的对象。
[面试题]
throw和throws 区别:
throw表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内。
throws属于异常处理的一种方式,声明在方法的声明处。
public class myException extends RuntimeException{
static final long serialVersionUID = -7034897190745766939L ;
private int id;
public myException(){}
public myException(String msg){
super(msg);
}
public void regist(int id) throws Exception {
if(id > 0){
this.id = id;
}else{
//手动抛出异常对象
// throw new RuntimeException(" 您输入的数据非法! ");
// throw new Exception("您输入的数据非法! ");
throw new myException("不能输入负数");
}
}
}
41.程序、进程、线程的概念
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象
进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。(生命周期)
- ➢如:运行中的QQ,运行中的MP3播放器
- ➢程序是静态的,进程是动态的
- ➢进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
- ➢若一个进程同一时间并行执行多个线程,就是支持多线程的。
- ➢线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。
- ➢一个进程中的多个线程共享相同的内存单元/内存地址空间—>它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
并行与并发
- ➢并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
- ➢并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
多线程程序的优点:
1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2.提高计算机系统CPU的利用率。
3.改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。
何时需要多线程
●程序需要同时执行两个或多个任务。
●程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
●需要一些后台运行的程序时。
线程的创建和启动
1.Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。
2.Thread类的特性
- ➢每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
- ➢通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
42.多线程的创建:继承Thread类
步骤:
- 创建一个继承于Thread类的子类
- 重写Thread类的run() --> 将此线程执行的操作声明在run()中
- 创建Thread类的子类的对象
- 通过此对象调用start()
Thread中的常用方法:
- start():启动当前线程:调用当前线程的run()
- run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
- currentThread(): 静态方法,返回执行当前代码的线程
- getName(): 获取当前线程的名字
- setName(): 设置当前线程的名字
- yield(): 释放当前cpu的执行权
- join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才
结束阻塞状态。 - stop():已过时。 当执行此方法时,强制结束当前线程。
- sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前
线程是阻塞状态。 - isAlive():判断当前线程是否存活。
●Java的调度方法
➢同优先级线程组成先进先出队列(先到先服务),使用时间片策略
➢对高优先级,使用优先调度的抢占式策略
●线程的优先级等级
- ➢MAX_ PRIORITY: 10
- ➢MIN_ PRIORITY: 1
- ➢NORM_ PRIORITY: 5
●涉及的方法
- ➢getPriority():返回线程优先值|
- ➢setPrjority(int newPriority):改变线程的优先级
●说明
- ➢线程创建时继承父线程的优先级
- ➢低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
- 说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率
的情况下被执行。并不意味着只当高优先级的线程执行完以后,低优先级的线程才执行。
public class ThreadDemo {
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
MyThread2 myThread2 = new MyThread2();
myThread1.start();
myThread2.start();
//创建thread类的匿名子类
new Thread(){
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i+"匿名");
}
}
}.start();
}
}
class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
public class ThreadTest{
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 100; i++) {
if(i%2!=0){
System.out.println(Thread.currentThread().getName()+ "---hello");
}
}
MyThread myThread1 = new MyThread();
myThread1.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+" = " + i);
}
}
}
}
43.创建多线程的方式二:实现Runnable接口
步骤:
- 创建一个实现了Runnable接口的类
- 实现类去实现Runnable中的抽象方法: run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start()
比较创建线程的两种方式
开发中优先选择:实现Runnable接口的方式原因:
1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况。
联系: public class Thread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
class Window implements Runnable{
private int ticket = 100;
// Object object = new Object();
// int i=0;
@Override
public void run() {
while (true){
synchronized (this){//用当前对象充当同步监视器
if (ticket>0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "___" + ticket);
ticket--;
}
}
}
}
}
public class WindowTest {
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("111");
t2.setName("222");
t3.setName("333");
t1.start();
t2.start();
t3.start();
}
}
线程通信: wait() / notify( / notifyA110 :此三个方法定义在0bject类中的。
44.线程的分类
Java中的线程分为两类:一种是守护线程,一种是用户线程。
●它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
●守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
●Java垃圾回收就是一个典型的守护线程。
●若JVM中都是守护线程,当前JVM将退出。
●形象理解:兔死狗烹,鸟尽弓藏。
45.线程的几种状态
Thread.State类定义了线程的几种状态,要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
- ➢新建:当一个Thread类或其 子类的对象被声明并创建时,新生的线程对象处于新建状态
- ➢就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
- ➢运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
- ➢阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
- ➢死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
46.线程的同步
●问题的提出
➢多个线程执行的不确定性引起执行结果的不稳定
➢多个线程对账本的共享,会造成操作的不完整性,会破坏数据。
方法:
- 方式一:同步代码块 synchronized(同步监视器){ // 需要被同步的代码 } 说明:
1.操作共享数据的代码,即为需要被同步的代码。-- >不能包含代码多了,也不能包含代码少了。
2.共享数据:多个线程共同操作的变量。比如: ticket 就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。 要求:多个线程必须要共用同一把锁。 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
class Window2 extends Thread{
private static int ticket=100;
// private static Object object = new Object();
@Override
public void run() {
while (true){
// synchronized (object){//用当前对象充当同步监视器
synchronized (Window2.class){//用当前对象充当同步监视器
if (ticket>0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "___" + ticket);
ticket--;
}
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 window1 = new Window2();
Window2 window2 = new Window2();
Window2 window3 = new Window2();
window1.start();
window2.start();
window3.start();
}
}
- 方式二:同步方法
如果操作共享数据的代码完整的声明在一 个方法中, 我们不妨将此方法声明为同步的。
关于同步方法的总结:
1.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
2.非静态的同步方法,同步监视器是: this ;静态的同步方法,同步监视器是:当前类本身。
class Window3 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
show();
}
}
private synchronized void show(){//同步监视器: this[
if (ticket>0){
try {
Thread.sleep(10);
} 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.start();
t2.start();
t3.start();
}
}
class Window4 extends Thread{
private static int ticket=100;
@Override
public void run() {
while (true){
show();
}
}
private static synchronized void show(){//同步监视器: Window4. class
if (ticket>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "___" + ticket);
ticket--;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 window1 = new Window4();
Window4 window2 = new Window4();
Window4 window3 = new Window4();
window1.start();
window2.start();
window3.start();
}
}
- 方式三: Lock锁— JDK5.0新增
●从JDK 5.0开始,Java提供 了更强大的线程同步机制一-通过 显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
●java.util.concurrentlocks.Lock接 口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
●ReentrantLock 类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock, 可以显式加锁、释放锁。
class Window5 implements Runnable{
private int ticket=100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try{
lock.lock();//调用锁定方法
if (ticket > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "___" + ticket);
ticket--;
}
}finally {
lock.unlock();//调用锁定方法
}
}
}
}
public class WindowTest5 {
public static void main(String[] args) {
Window5 w = new Window5();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.start();
t2.start();
t3.start();
}
}
使用的优先顺序:
Lock–>同步代码块(已经进入了方法体,分配了相应资源) —>同步方法 (在方法体之外)
利弊:
同步的方式,解决了线程的安全问题。—好处
操作同步代码时,只能一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
面试题: synchronized 与Lock的异同?
相同:二者都可以解决线程安全问题
不同: synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器。Lock需要手动的启动同步(Lock(), 同时结束同步也需要手动的实现(unLock() )
面试题: Java是 如何解决线程安全问题的,有几种方式?并对比几种方式的不同
面试题: synchronized和Lock方 式解决线程安全问题的对比
面试题:写-一个线程安全的单例模式。
class Bank{
private Bank(){}
private static Bank instance = null;
public static synchronized Bank getInstance(){
if(instance == null){
instance = new Bank();
}
return instance ;
}
}
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance(){
synchronized (Bank . class) {
if(instance == null){
instance =
new Bank();
}
return instance;
}
}
}
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance(){
//方式一:效率梢差
synchronized (Bank.class) {
if(instance == null){
instance = new Bank();
}
return instance;
}
//方式二:
if(instance ==null){
synchronized (Bank . class) {
if(instance == null){
instance = new Bank();
}
}
}
return instance ;
}
}
线程的死锁问题
●死锁
- ➢不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
- ➢出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
●解决方法
- ➢专门的算法、原则
- ➢尽量减少同步资源的定义
- ➢尽量避免嵌套同步