Singleton:在Java中即指单例设计模式,它是软件开发中最常用的设计模式之一。
单:唯一
例:实例
单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。
例如:代表JVM运行环境的Runtime类
一、是某个类只能有一个实例;
1.构造器私有化
二、是它必须自行创建这个实例;
2.含有一个该类的静态变量来保存这个唯一的实例
三、是它必须自行向整个系统提供这个实例;
3.对外提供获取该实例对象的方式:
(1)直接暴露(2)用静态变量的get方法获取
饿汉式:直接创建对象,不存在线程安全问题
/* * 饿汉式: * 在类初始化时直接创建实例对象,不管你是否需要这个对象都会创建 * * (1)构造器私有化 * (2)自行创建,并且用静态变量保存 * (3)向外提供这个实例 * (4)强调这是一个单例,我们可以用final修改 */ public class Singleton1 { public static final Singleton1 INSTANCE = new Singleton1(); private Singleton1(){ }
/* * 枚举类型:表示该类型的对象是有限的几个 * 我们可以限定为一个,就成了单例 */ public enum Singleton2 { INSTANCE }
public class Singleton3 { public static final Singleton3 INSTANCE; private String info; static{ try { Properties pro = new Properties(); //加载属性 pro.load(Singleton3.class.getClassLoader().getResourceAsStream("single.properties")); INSTANCE = new Singleton3(pro.getProperty("info")); } catch (IOException e) { throw new RuntimeException(e); } } private Singleton3(String info){ this.info = info; } public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } @Override public String toString() { return "Singleton3 [info=" + info + "]"; } }
懒汉式:延迟创建对象
/* * 懒汉式: * 延迟创建这个实例对象 * * (1)构造器私有化 * (2)用一个静态变量保存这个唯一的实例 * (3)提供一个静态方法,获取这个实例对象 */ public class Singleton4 { private static Singleton4 instance; private Singleton4(){ } public static Singleton4 getInstance(){ if(instance == null){ instance = new Singleton4(); } return instance; } }
/* * 懒汉式: * 延迟创建这个实例对象 * * (1)构造器私有化 * (2)用一个静态变量保存这个唯一的实例 * (3)提供一个静态方法,获取这个实例对象 */ public class Singleton5 { private static Singleton5 instance; private Singleton5(){ } public static Singleton5 getInstance(){ if(instance == null){ synchronized (Singleton5.class) { if(instance == null){ instance = new Singleton5(); } } } return instance; } }
/* * 在内部类被加载和初始化时,才创建INSTANCE实例对象 * 静态内部类不会自动随着外部类的加载和初始化而初始化,它是要单独去加载和初始化的。 * 因为是在内部类加载和初始化时,创建的,因此是线程安全的 */ public class Singleton6 { private Singleton6(){ } private static class Inner{ private static final Singleton6 INSTANCE = new Singleton6(); } public static Singleton6 getInstance(){ return Inner.INSTANCE; } }
public static void main(String[] args) { int i = 1; i = i++; int j = i++; int k = i + ++i * i++; System.out.println("i=" + i); System.out.println("j=" + j); System.out.println("k=" + k); }
1.执行到 i=i++
①由于++在后面,要先把i的值压入操作数栈
局部变量i | 操作数栈 |
---|---|
1 | 1 |
②i变量自增1
局部变量i | 操作数栈 |
---|---|
2 | 1 |
③把操作数栈中的值赋值给i,所以此时i=1
局部变量i | 操作数栈 |
---|---|
1 |
2.执行到int j = i++;
①由于++在后面,要先把i的值压入操作数栈
局部变量i | 局部变量j | 操作数栈 |
---|---|---|
1 | 1 |
②i变量自增1
局部变量i | 局部变量j | 操作数栈 |
---|---|---|
2 | 1 |
③把操作数栈中的值赋值给j,所以此时i=2,j=1;
局部变量i | 局部变量j | 操作数栈 |
---|---|---|
2 | 1 |
3.执行到int k = i + ++i * i++;
①把i的值压入操作数栈
局部变量i | 局部变量k | 操作数栈 |
---|---|---|
2 | 2+ ++i * i++ |
②i变量自增1,由于++在前面,所以i要先自增再压入操作数栈,此时int k = 2+ 3 * i++;
局部变量i | 局部变量k | 操作数栈 |
---|---|---|
3 | 2+ 3 * i++ |
③把i的值压入操作数栈
局部变量i | 局部变量k | 操作数栈 |
---|---|---|
3 | 2+ 3 * 3 |
④i变量自增1
局部变量i | 局部变量k | 操作数栈 |
---|---|---|
4 | 2+ 3 * 3 |
⑥把操作数栈中前两个弹出求乘积结果再压入栈
局部变量i | 局部变量k | 操作数栈 |
---|---|---|
4 | 2+9 |
⑦把操作数栈中的值弹出求和再赋值给k
局部变量i | 局部变量k | 操作数栈 |
---|---|---|
4 | 11 |
所以结果为:i=4,j=1,k=11;
1.赋值=,最后计算
2.=右边的从左到右加载值依次压入操作数栈
3.实际先算哪个,看运算符优先级
4.自增、自减操作都是直接修改变量的值,不经过操作数栈
5.最后的赋值之前,临时结果也是存储在操作数栈中
public class Father{ private int i = test(); private static int j = method(); static{ System.out.print("(1)"); } Father(){ System.out.print("(2)"); } { System.out.print("(3)"); } public int test(){ System.out.print("(4)"); return 1; } public static int method(){ System.out.print("(5)"); return 1; } }
public class Son extends Father{ private int i = test(); private static int j = method(); static{ System.out.print("(6)"); } Son(){ // super();//写或不写都在,在子类构造器中一定会调用父类的构造器 System.out.print("(7)"); } { System.out.print("(8)"); } public int test(){ System.out.print("(9)"); return 1; } public static int method(){ System.out.print("(10)"); return 1; } public static void main(String[] args) { Son s1 = new Son(); System.out.println(); Son s2 = new Son(); } }
要点:
1、一个类要创建实例需要先加载并初始化该类
2、一个子类要初始化需要先初始化父类
3、一个类初始化就是执行clinit()方法,clinit是在字节码
结论:
1.要先初始化父类,所以先执行父类的clint()方法,所以结果为(5)(1)
2.然后初始话子类,所以执行子类的clint()方法,所以结果为(10)(6)
实例初始化就是执行init()方法,init()方法是字节码中的
1.哪些方法不可以被重写
2.对象的多态性
结论:
子类的实例化方法init:
1.先执行super(),所以先执行父类的init方法,由于方法的重写,所以结果为(9)(3)(2)
2.然后执行子类的init方法,所以结果为(9)(8)(7)
3.由于创建了两个Son对象,因此实例化方法init方法执行了两次,所以结果为(9)(3)(2)(9)(8)(7)
(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)
如下的运行结果为:
public class Test{ public static void main(String[] args) { int i = 1; String str = "hello"; Integer num = 200; int[] arr = {1,2,3,4,5}; MyData my = new MyData(); change(i,str,num,arr,my); System.out.println("i = " + i); System.out.println("str = " + str); System.out.println("num = " + num); System.out.println("arr = " + Arrays.toString(arr)); System.out.println("my.a = " + my.a); } public static void change(int j, String s, Integer n, int[] a,MyData m){ j += 1; s += "world"; n += 1; a[0] += 1; m.a += 1; } } class MyData{ int a = 10; }
1.方法的参数传递机制
2.String、包装类等对象的不可变性
i=1 str = hello num=201 arr=[2,2,3,4,5] my.a=11
方法调用自身称为递归,利用变量的原值推出新值称为迭代。
递归
代码
public class TestStep{ @Test public void test(){ long start = System.currentTimeMillis(); System.out.println(f(100));//165580141 long end = System.currentTimeMillis(); System.out.println(end-start);//586ms } //实现f(n):求n步台阶,一共有几种走法 public int f(int n){ if(n<1){ throw new IllegalArgumentException(n + "不能小于1"); } if(n==1 || n==2){ return n; } return f(n-2) + f(n-1); } }
迭代
代码
public class TestStep2 { @Test public void test(){ long start = System.currentTimeMillis(); System.out.println(loop(100));//165580141 long end = System.currentTimeMillis(); System.out.println(end-start);//<1ms } public int loop(int n){ if(n<1){ throw new IllegalArgumentException(n + "不能小于1"); } if(n==1 || n==2){ return n; } int one = 2;//初始化为走到第二级台阶的走法 int two = 1;//初始化为走到第一级台阶的走法 int sum = 0; for(int i=3; i<=n; i++){ //最后跨2步 + 最后跨1步的走法 sum = two + one; two = one; one = sum; } return sum; } }
public class Exam5 { static int s;//成员变量,类变量 int i;//成员变量,实例变量 int j;//成员变量,实例变量 { int i = 1;//非静态代码块中的局部变量 i i++; j++; s++; } public void test(int j){//形参,局部变量,j j++; i++; s++; } public static void main(String[] args) {//形参,局部变量,args Exam5 obj1 = new Exam5();//局部变量,obj1 Exam5 obj2 = new Exam5();//局部变量,obj1 obj1.test(10); obj1.test(20); obj2.test(30); System.out.println(obj1.i + "," + obj1.j + "," + obj1.s); System.out.println(obj2.i + "," + obj2.j + "," + obj2.s); } }
就近原则
变量的分类
非静态代码块的执行:每次创建实例对象都会执行
方法的调用规则:调用一次执行一次
1、声明的位置
(1).局部变量:方法体{}中,形参,代码块{}中
(2).成员变量:类中方法外
2、修饰符
局部变量:final
成员变量:public、protected、private、final、static、volatile、transient
3、值存储的位置
局部变量:栈
实例变量:堆
类变量:方法区
4、作用域
局部变量:从声明处开始,到所属的}结束
实例变量:在当前类中“this.”(有时this.可以缺省),在其他类中“对象名.”访问
类变量:在当前类中“类名.”(有时类名.可以省略),在其他类中“类名.”或“对象名.”访问
5、生命周期
局部变量:每一个线程,每一次调用执行都是新的生命周期
实例变量:随着对象的创建而初始化,随着对象的被回收而消亡,每一个对象的实例变量是独立的
类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量是共享的
局部变量与实例变量重名
局部变量与类变量重名
2,1,5
1,1,5