JavaSE
1. 自增变量
理解参考博客园博主【叮咚叮咚】的文章
从 i++ 和 ++i 说起局部变量表和操作数栈
public class Main {
public static void main(String[] args) {
//两步:先把 int 类型的1 压入到操作数栈,然后再把操作数栈的值赋值给变量 i
int i = 1;
// 把i的值1压入到操作数栈中,然后局部变量i自增变为2,最后再把操作数栈的值1赋值给局部变量i,因此此时 i = 1
i = i++;
// 把i的值1压入到操作数栈中,然后局部变量i自增变为2,最后再把操作数栈的值1赋值给局部变量j,因此此时 j = 1
int j = i++;
//i + ++i * i++
// i:把i值2压入到操作数栈中; 操作数栈 值为 2
// ++i:然后局部变量i先自增变为3,然后再把3压入到操作数栈中; 此时局部变量表中 i= 3; 操作数栈 值为 3
// i++:把i的值3压入到操作数栈中,然后局部变量i先自增变为4; 操作数栈 值为 3
// 此时按公式计算操作数栈的值 2+3*3=11,再把11赋值给等号左边的 k; 因此最后 i=4;j=1;k=11
int k = i + ++i * i++;
System.out.println("i的值:" + i); // i的值:4
System.out.println("j的值:" + j); // j的值:1
System.out.println("k的值:" + k); // k的值:11
}
}
2. 单例设计模式
设计模式概述
:
设计模式是在大量的实践中总结
和理论化
之后优选的代码结构、编程风格、以及解决问题的思考方式。
设计模式免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。"套路"
经典的设计模式一共有23种
单例模式(Singleton):
所谓类的单例设计模式
,就是采取一定的方法保证在整个的软件系统中,
对某个类只能存在一个对象实例
,并且该类只提供一个取得其对象实例的方法
。
要点:
Ⅰ、某个类只能有一个实例
(构造器必须私有化
)
Ⅱ、这个类必须自行创建这个实例
(
含有一个该类的静态变量来保存这个唯一的实例
)Ⅲ、必须
自行的向整个系统来提供整个实例
- 对外提供
获取该实例对象的方式
:
- 使用
public
方法直接暴露
- 使用
静态变量的get方法
获取
分类 | 饿汉式 单例模式 | 懒汉式 单例模式 |
优点 | 写法简单,由于内存中较早加载,使用更方便、更快。 是线程安全的。 | 在需要的时候进行创建,节省内存空间。 |
缺点 | 内存中占用时间较长 | 线程不安全 |
① 饿汉式单例模式
理解参考(腾讯云-开发者社区)文章:
为什么饿汉式单例是线程安全的?
1、饿汉式单例模式:
calss EagerSingleton{
//1、构造器私有化
pravite EagerSingleton (){};
//2、在类的内部创建当前类的实例 //4、静态方法中只能调用静态的属性或方法,因此属性也必须声明为私有的
pravite static EagerSingleton eager = new EagerSingleton();
//3、提供公共方法返回当前类的实例,必须声明为static的方法
public static EagerSingleton getInstace(){
return eager;
}
}
/*要点:
* Ⅰ、某个类只能有一个实例(构造器必须私有化)
* Ⅱ、这个类必须自行创建这个实例(含有一个该类的静态变量来保存这个唯一的实例)
* Ⅲ、必须自行的向整个系统来提供整个实例(对外提供获取该实例对象的方式:①使用public方法直接暴露;②使用静态变量的get方法获取)
*
* 饿汉式:在类初始化的时候直接创建对象:不存在线程安全问题
* 1、直接实例化饿汉式(简洁直观)
* 2、枚举(最简洁)
* 3、静态代码块(适合复杂实例化)
*
* 懒汉式:延迟创建对象
* 1、线程不安全(适合单线程)
* 2、线程安全(适合多线程)
* 3、静态内部类形式(适用于多线程)
* */
public class HungrySingleton {
public static final HungrySingleton INSTANCE= new HungrySingleton();
private HungrySingleton() {}
public static void main(String[] args) {
//调用饿汉式单例1
HungrySingleton hungrySingleton = HungrySingleton.INSTANCE;//通过类名直接调用
System.out.println(hungrySingleton);//HungrySingleton@4eec7777
//调用饿汉式单例2 枚举本身就是私有化的,枚举重写的toString方法,所以输出的是常量对象的名字
HungrySingleton2 hungrySingleton2 = HungrySingleton2.INSTANCE;
System.out.println(hungrySingleton2);//INSTANCE
//调用饿汉式单例3
HungrySingleton3 hungrySingleton3 = HungrySingleton3.INSTANCE;
System.out.println(hungrySingleton3);//HungrySingleton3@404b9385 HungrySingleton3{info='beijing'}
//调用饿汉式单例4
System.out.println(HungrySingleton4.getInstance());//HungrySingleton4@3feba861
}
}
//--------------------------------------------------------------------
/*
* 枚举:
* 表示类型的对象是有限的几个,在此我们限定一个,就是单例的了*/
public enum HungrySingleton2 {
INSTANCE
}
//--------------------------------------------------------------------
/*
* 静态代码块适用于处理复杂的实例化时用
* */
public class HungrySingleton3 {
public static final HungrySingleton3 INSTANCE;
static {
INSTANCE = new HungrySingleton3();
}
private HungrySingleton3() {
}
}
//*******************************************
/*
* 静态代码块适用于处理复杂的实例化时用;比如需要读取很多初始化文件才能创建实例对象
*
* 配置文件参考下图
* */
import java.io.IOException;
import java.util.Properties;
public class HungrySingleton3 {
public static final HungrySingleton3 INSTANCE;
private String info;
static {
try {
Properties properties = new Properties();
properties.load(HungrySingleton3.class.getClassLoader().getResourceAsStream("singleInfo.properties"));
INSTANCE = new HungrySingleton3(properties.getProperty("info"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private HungrySingleton3(String info) {
this.info=info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public String toString() {
return "HungrySingleton3{" +
"info='" + info + '\'' +
'}';
}
}
//--------------------------------------------------------------------
/*
* 常规例子
* */
public class HungrySingleton4 {
//1.构造器私有化
public HungrySingleton4() {
}
//2.创建一个类的实例
private static final HungrySingleton4 instance = new HungrySingleton4();
//3.提供get 方法返回这个实例
public static HungrySingleton4 getInstance() {
return instance;
}
}
② 懒汉式单例模式
2、 懒汉式单例模式:
class LazySingleton{
//1、构造器私有化
pravite LazySingleton (){};
//2、在类的内部声明当前类的实例 //4、静态方法中只能调用静态的属性或方法,因此属性也必须声明为私有的
pravite static LazySingleton lazy = null;
//3、提供公共方法返回当前类的实例,必须声明为static的方法
public static LazySingleton getInstace(){
if(lazy == null){
lazy = new LazySingleton();
}
return lazy;
}
}
Ⅰ. 线程不安全,适合于单线程
/*
*线程不安全,适合于单线程
* */
public class LazySingleton1 {
//1.使用静态变量保存唯一实例,必须用private修饰
// 如果用public修饰的话,外部可以直接通过【类名.INSTANCE】获取到这个实例,拿到的实例可能是null;
// 如果用private修饰的话,外部只能通过【类名.getInstance方法】获取这个实例,这样就不是null了
private static LazySingleton1 INSTANCE;
//2.构造器私有化
private LazySingleton1() {
}
//3.提给外部供获取这个实例的get方法
public static LazySingleton1 getInstance() {
if (INSTANCE == null) {
INSTANCE = new LazySingleton1();
}
return INSTANCE;
}
}
//----------------------------------------------------------------------
public class TestLazySingleton {
public static void main(String[] args) {
System.out.println(LazySingleton1.getInstance());//LazySingleton1@3b07d329
}
}
//----------------------------------------------------------------------
/*
* 测试多线程下验证上述写法的问题
*
*
* 假如多线程环境下,多个线程会争夺CPU的执行权:
* 如果第一个线程进来判断对象为null时,开始实例化对象,但是还没有实例化成功的时候,
* 另一个线程也进来判断对象为null,也开始实例化对象,就会导致两个对象不同
* */
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable callable = new Callable() {
@Override
public LazySingleton1 call() throws Exception {
return LazySingleton1.getInstance();
}
};
//创建包含2个线程的线程池对象
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future future1 = executorService.submit(callable);
Future future2 = executorService.submit(callable);
LazySingleton1 lazySingleton1 = (LazySingleton1) future1.get();
LazySingleton1 lazySingleton2 = (LazySingleton1) future2.get();
System.out.println(lazySingleton1); //LazySingleton1@7ef20235
System.out.println(lazySingleton2); //LazySingleton1@27d6c5e0
System.out.println(lazySingleton1 == lazySingleton2); //false
}
Ⅱ. synchronized 关键字线程安全(适合多线程)
import java.util.concurrent.*;
/*
*【synchronized 关键字】线程安全【适合多线程】
*
*
* 优缺点说明:
* 解决了线程不安全问题,
* 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。
* 而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低
* 结论:在实际开发中,不推荐使用这种方式
* */
public class LazySingleton2 {
//1.使用静态变量保存唯一实例,必须用private修饰
// 如果用public修饰的话,外部可以直接通过【类名.INSTANCE】获取到这个实例,拿到的实例可能是null;
// 如果用private修饰的话,外部只能通过【类名.getInstance方法】获取这个实例,这样就不是null了
private static LazySingleton2 INSTANCE;
//2.构造器私有化
private LazySingleton2() {
}
//3.提给外部供获取这个实例的get方法
public static LazySingleton2 getInstance() {
//给 对象上锁
synchronized (LazySingleton2.class) {
if (INSTANCE == null) {
try {
//让前面的线程休眠等待第二个线程进来,看是否重新实例化对象
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
INSTANCE = new LazySingleton2();
}
}
return INSTANCE;
}
//----------------------------------------------------------------------
/*
*【synchronized 关键字】线程安全【适合多线程】
*
* 可以进一步优化,在锁外面先判断实例是否为null
* */
public class LazySingleton2 {
//1.使用静态变量保存唯一实例,必须用private修饰
// 如果用public修饰的话,外部可以直接通过【类名.INSTANCE】获取到这个实例,拿到的实例可能是null;
// 如果用private修饰的话,外部只能通过【类名.getInstance方法】获取这个实例,这样就不是null了
private static LazySingleton2 INSTANCE;
//2.构造器私有化
private LazySingleton2() {
}
//3.提给外部供获取这个实例的get方法
public static LazySingleton2 getInstance() {
if (INSTANCE == null) {
//给 对象上锁
synchronized (LazySingleton2.class) {
if (INSTANCE == null) {
try {
//让前面的线程休眠等待第二个线程进来,看是否重新实例化对象
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
INSTANCE = new LazySingleton2();
}
}
}
return INSTANCE;
}
//----------------------------------------------------------------------
public class TestLazySingleton {
public static void main(String[] args) {
System.out.println(LazySingleton1.getInstance());//LazySingleton1@3b07d329
System.out.println(LazySingleton2.getInstance());//LazySingleton2@404b9385
}
}
//----------------------------------------------------------------------
/*
* 测试多线程下验证上述写法的问题
* 假如多线程环境下,多个线程会争夺CPU的执行权:
* 如果第一个线程进来判断对象为null时,开始实例化对象,但是还没有实例化成功的时候,
* 另一个线程也进来判断对象为null,也开始实例化对象,就会导致两个对象不同
* */
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable callable = new Callable() {
@Override
public LazySingleton2 call() throws Exception {
return LazySingleton2.getInstance();
}
};
//创建包含2个线程的线程池对象
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future future1 = executorService.submit(callable);
Future future2 = executorService.submit(callable);
LazySingleton2 lazySingleton1 = (LazySingleton2) future1.get();
LazySingleton2 lazySingleton2 = (LazySingleton2) future2.get();
System.out.println(lazySingleton1); //LazySingleton2@27d6c5e0
System.out.println(lazySingleton2); //LazySingleton2@27d6c5e0
System.out.println(lazySingleton1 == lazySingleton2); //true
}
}
Ⅲ. 静态内部类形式
参考站内博主 houjibofa2050 文章
: 类加载器以及常见面试题
import java.util.concurrent.*;
/*
*静态内部类形式
*
* 在内部类被加载和初始化时候才会创建INSTANCE实例对象
* 静态内部类不会随着外部类的加载和初始化而初始化,它是要单独加载和初始化的
* 因为实例对象是在内部类中加载和初始化的,因此是线程安全的
*
* */
public class LazySingleton3 {
//1.构造器私有化
public LazySingleton3() {
}
//2.创建一个内部类,在内部类里面保存这个唯一实例
private static class Inner{
private static final LazySingleton3 INSTANCE = new LazySingleton3();
}
//3.向外提供get方法
public static LazySingleton3 getInstance(){
return Inner.INSTANCE;
}
}
//----------------------------------------------------------------------
public class TestLazySingleton {
public static void main(String[] args) {
System.out.println(LazySingleton1.getInstance());//LazySingleton1@3b07d329
System.out.println(LazySingleton2.getInstance());//LazySingleton2@404b9385
System.out.println(LazySingleton3.getInstance());//LazySingleton3@3d075dc0
}
}
//----------------------------------------------------------------------
Ⅳ. 【双重检查锁(再加volatile关键字)禁止指令重排】线程安全【适合多线程 】(优化版)
假如
多线程环境
下,多个线程会争夺CPU的执行权:
如果第一个线程进来
判断对象为null时,开始实例化对象,但是还没有
实例化成功的时候,另一个线程
也进来判断对象为null,也开始实例化
对象,就会导致两个对象不同
import java.util.concurrent.*;
/**
* @Description :懒汉式:延迟创建这个实例对象
* (1)构造器私有化
* (2)用一个静态变量保存这个唯一的实例
* (3)提供一个静态方法,获取这个实例对象
* 使用synchronized 关键字
*【优化版】【双重检查锁(再加volatile关键字)】线程安全【适合多线程 】
* 如果已经有线程new出来对象,后面的对象没必要在等待
* @Author : Mr Huang
* @CreateDate : 2021/1/10 17:22
* @Version :
*/
public class Singleton06 {
/**
* 必须用private修饰,如果用public修饰,直接可以通过
* 【类名.常量名singleton06】直接访问,拿到的实例可能为 null值;
* 但是用private修饰的话,只能通过【类名.getInstance方法】来获取这个实例对象
*/
private static Singleton06 singleton06;
private Singleton06(){};
public static Singleton06 getInstance() {
if (singleton06 == null){
synchronized (Singleton06.class) {
if (singleton06 == null) {
singleton06 = new Singleton06();
}
}
}
return singleton06;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable c = new Callable(){
@Override
public Singleton06 call() throws Exception {
return Singleton06.getInstance();
}
};
ExecutorService es = Executors.newFixedThreadPool(2);
Future future1 = es.submit(c);
Future future2 = es.submit(c);
Singleton06 s1 = (Singleton06) future1.get();
Singleton06 s2 = (Singleton06) future2.get();
System.out.println(s1); // Singleton06@27d6c5e0
System.out.println(s2); // Singleton06@27d6c5e0
System.out.println(s1 == s2); // true
}
}
3. 类的初始化和实例化(含代码示例)
类的初始化过程:
① 一个类要创建实例需要 先加载并初始化 该类
main方法所在的类
需要先加载和初始化
② 一个子类要初始化需要 先初始化其父类
③ 一个类初始化就是执行clinit
方法
Ⅰ、clinit
方法由静态类变量
显示赋值代码和静态代码块
组成
Ⅱ、类变量显示赋值代码
和静态代码块代码
从上到下顺序执行
Ⅲ、clinit
方法只执行一次
实例初始化过程:
实例初始化就是执行init
方法
①init
方法可能重载多个,有几个构造器
就有几个init
方法
②init
方法由非静态实例变量显示赋值代码
和非静态代码块
、对应构造器代码
组成
③ 非静态实例变量显示赋值代码和非静态代码块从上到下顺序执行
,而对于构造器的代码最后执行
④ 每次创建实例对象,调用对应构造器是的,执行的就是对应的init
方法
⑤init
方法的首行是super()或super(实参列表)
,即对应父类的init
方法
方法重写:
1、哪些方法不可被重写
①final
方法
②静态
方法
③private
等子类中不可见的方法
2、对象的多样性
① 子类如果重写了父类的方法
,通过子类对象调用的一定是子类重写的方法
②非静态方法
默认的调用对象是this
③this对象
在构造器或者说init
方法 中就是正在创建的对象
代码理解示例
:
/**
* @Description :
* 父类的初始化:
* (1)b = method();
* (2)父类的静态代码块
*
* 父类的实例化方法:
* (1)super()(最前)
* (2) a = test();
* (3)父类的非静态代码块
* (4)父类的无参构造(最后)
*
* 非静态方法前面其实有一个默认的对象this
* this在构造器(或)它表示的是正在创建的对象,因为这里是在创建Son对象,
* 所以test(()执行的是子类重写的代码(面向对象多态)
* 这里 a =test()执行的是子类重写的test()方法
*
* @Author : Mr Huang
* @CreateDate : 2021/1/10 21:13
* @Version :
*
*/
public class Father {
private int a = test();
private static int b = method();
static {
System.out.println("1");
}
Father() {
System.out.println("2");
}
{
System.out.println("3");
}
public int test() {
System.out.println("4");
return 1;
}
public static int method() {
System.out.println("5");
return 1;
}
}
//**************************************************
//**************************************************
//**************************************************
/**
* @Description :
* @Author : Mr Huang
* @CreateDate : 2021/1/10 21:13
* @Version :
* 子类的初始化:
* (1)b = method(); 5 1
* (2)子类的静态代码块 10 6
* 先初始化父类,后初始化子类
*
* 子类的实例化方法:
* (1) super(最前) 9 3 2
* (2) a = test(); 9
* (3)子类的非静态代码块 8
* (4)子类的无参构造《最后) 7
*
* 因为创建了两个Son对象,因此实例化方法执行两次
* 9 3 2 9 8 7
*/
public class Son extends Father {
private int a = test();
private static int b = method();
static {
System.out.println("6");
}
Son() {
//super;写或不写都在,在子类构造器中一定会调用父类的构造器
System.out.println("7");
}
{
System.out.println("8");
}
@Override
public int test() {
System.out.print("9");
return 1;
}
public static int method() {
System.out.println("10");
return 1;
}
public static void main(String[] args) {
//Father father = new Father(); 4 3 2
Son son1 = new Son();
System.out.println();
Son son2 = new Son();
}
}
4. Java中的参数传递机制
参考站内博主 yuanbinquan 文章
: 数组存放位置
参考站内博主 Arya_2 文章
: Integer存放位置
参考站内博主 it_withpassion 文章
: java对象真的都存储在堆
方法的参数的传递机制:****
值传递机制
实参给形参赋值的过程
如果形参是基本数据类型
的变量,则将实参保存的数据值
赋给形参。
如果形参是引用数据类型
的变量,则将实参保存的地址值
赋给形参。
特殊类型:String、包装类
等对象不可变性
执行结果:
不同变量的内存图
public class Test1 {
static int s;
int i;
int j;
{
int i = 1;
i++;
j++;
s++;
}
public void test(int j) {
j++;
i++;
s++;
}
public static void main(String[] args) {
Test1 test1 = new Test1();
Test1 test2 = new Test1();
test1.test(10);
test1.test(20);
test2.test(30);
System.out.println(test1.i + " " + test1.j + " " + test1.s); //执行结果: 2 1 5
System.out.println(test2.i + " " + test2.j + " " + test2.s); //执行结果:1 1 5
}
}