Java基础


1.

2.几种常见的单例模式

转载于:https://www.cnblogs.com/ycheng/p/7169381.html

单例模式:是一种常用的软件设计模式,在它的核心结构中值包含一个被称为单例的特殊类。一个类只有一个实例,即一个类只有一个对象实例。

  对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;售票时,一共有100张票,可有有多个窗口同时售票,但需要保证不要超售(这里的票数余量就是单例,售票涉及到多线程)。如果不是用机制对窗口对象进行唯一化将弹出多个窗口,如果这些窗口显示的都是相同的内容,重复创建就会浪费资源。

  应用场景(来源:《大话设计模式》):

    需求:在前端创建工具箱窗口,工具箱要么不出现,出现也只出现一个

    遇到问题:每次点击菜单都会重复创建“工具箱”窗口。

    解决方案一:使用if语句,在每次创建对象的时候首先进行判断是否为null,如果为null再创建对象。

    需求:如果在5个地方需要实例出工具箱窗体

    遇到问题:这个小bug需要改动5个地方,并且代码重复,代码利用率低

    解决方案二:利用单例模式,保证一个类只有一个实例,并提供一个访问它的全局访问点。

  通过以下几种方式,我们会发现,所有的单例模式都是使用静态方法进行创建的,所以单例对象在内存中静态共享区中存储。(可参考:https://zhidao.baidu.com/question/2206072272164938188.html)

   单例模式可以分为懒汉式和饿汉式:

    懒汉式单例模式:在类加载时不初始化。

    饿汉式单例模式:在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。

  第一种(懒汉,线程不安全):

public class SingletonDemo1 {
    private static SingletonDemo1 instance;
    private SingletonDemo1(){}
    public static SingletonDemo1 getInstance(){
        if (instance == null) {
            instance = new SingletonDemo1();
        }
        return instance;
    }
}

这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。 

    第二种(懒汉,线程安全):

public class SingletonDemo2 {
    private static SingletonDemo2 instance;
    private SingletonDemo2(){}
    public static synchronized SingletonDemo2 getInstance(){
        if (instance == null) {
            instance = new SingletonDemo2();
        }
        return instance;
    }
}

这种写法在getInstance()方法中加入了synchronized锁。能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是效率很低(因为锁),并且大多数情况下不需要同步。

    第三种(饿汉):

public class SingletonDemo3 {
    private static SingletonDemo3 instance = new SingletonDemo3();
    private SingletonDemo3(){}
    public static SingletonDemo3 getInstance(){
        return instance;
    }
}

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,这时候初始化instance显然没有达到lazy loading的效果。

   第四种(饿汉,变种):

public class SingletonDemo4 {
    private static SingletonDemo4 instance = null;
    static{
        instance = new SingletonDemo4();
    }
    private SingletonDemo4(){}
    public static SingletonDemo4 getInstance(){
        return instance;
    }
}

表面上看起来差别挺大,其实更第三种方式差不多,都是在类初始化即实例化instance

    第五种(静态内部类):

public class SingletonDemo5 {
    private static class SingletonHolder{
        private static final SingletonDemo5 instance = new SingletonDemo5();
    }
    private SingletonDemo5(){}
    public static final SingletonDemo5 getInsatance(){
        return SingletonHolder.instance;
    }
}

这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方法就显得更合理。

第六种(枚举):

public enum SingletonDemo6 {
    instance;
    public void whateverMethod(){
    }
}

这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。

第七种(双重校验锁):

public class SingletonDemo7 {
    private volatile static SingletonDemo7 singletonDemo7;
    private SingletonDemo7(){}
    public static SingletonDemo7 getSingletonDemo7(){
        if (singletonDemo7 == null) {
            synchronized (SingletonDemo7.class) {
                if (singletonDemo7 == null) {
                    singletonDemo7 = new SingletonDemo7();
                }
            }
        }
        return singletonDemo7;
    }
}

这个是第二种方式的升级版,俗称双重检查锁定,详细介绍请查看:http://www.ibm.com/developerworks/cn/java/j-dcl.html

在JDK1.5之后,双重检查锁定才能够正常达到单例效果。

3.类初始化和实例初始化

public class Father {
    private int i = test();
    private static int j = 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;
    } 
}
/**
 * 类初始化:初始化静态方法和静态代码块,谁在前面先初始化谁
 *         首先初始化父类:(5)(1)
 *         接着初始化子类:(10)(6)
 *         
 *  实例初始化:实例初始化首先初始化非静态方法和非静态代码块,最后初始化参数构造
 *  *         父类的重写方法调用子类的
 *            子类的实例化方法
 *            (1)super()( 最前):(9)(3)(7)
 *            (2)i = test();      (9)
 *            (3)子类的非静态代码块: (8)
 *            (4)子类的无参构造:    (7)
 *   因为创建了两个Son对象,因此执行两次实例化方法                  
 */
public class Son extends Father {
    private int i = test();
    private static int j = method();
    static {
        System.out.println("(6)");
    }
    Son(){
        System.out.println("(7)");
    }
    {
        System.out.println("(8)");
    }
    public int test(){
        System.out.println("(9)");
        return 1;
    }
    public static int method(){
        System.out.println("(10)");
        return 1;
    }
    public static void main(String[] args) {
        /**
         * 输出结果:
         *
         */
        Son s1 = new Son();
        System.out.println();
        Son s2 = new Son();
    }
}

  输出结果:

(1)
(5)
(10)
(6)
(9)
(3)
(2)
(9)
(8)
(7)

(9)
(3)
(2)
(9)
(8)
(7)

3.1 类初始化过程

1.一个类要创建实例需要先加载并初始化该类

         main方法所在的类需要先加载和初始化

2.一个子类要初始化需要先初始化父类

3.一个类初始化就是执行()方法

        ()方法由静态类变量显示赋值代码和静态代码块组成

       类变量显示赋值和静态代码块从上到下顺序执行

       ()方法只执行一次

3.2 实例初始化过程

1.实例初始化就是执行()方法

        ()方法可能重载有多个,有几个构造器就有几个()方法

       ()方法由非静态实例变量显示赋值和非静态代码块,对应的构造器组成

      非静态实例变量显示赋值和非静态代码块,而对应构造器的代码最后执行

      每次创建实例对象,调用对应构造器,执行的就是对应的()方法

      ()方法的首行是super()或super(实参列表),即对应父类的()方法

3.3 方法的重写

1.哪些方法不可以被重写

       final方法

       静态方法

       private等子类中不可见方法

2.对象的多态性

       子类如果重写了父类的方法,通过子类调用的一定是子类重写过的方法

        非静态方法默认的调用对象是this

        this对象在构造器或者说()方法中就是正在创建的对象

4. 方法的参数传递机制

public class Demo4 {
    public static void main(String[] args) {
        int i = 1;
        String str = "hello";
        Integer num = 2;
        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;
}

输出结果:

i = 1
str = hello
num = 2
arr =[2, 2, 3, 4, 5]
my.a =11

方法的参数传递机制

1.形参是基本数据类型

     传递数据值

2.实参是引用数据类型

     传递地址值

     特殊的类型:String,包装类等对象不可变性

5.递归与迭代

6.成员变量与局部变量

public class Demo5 {
    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) {  //局部变量
        Demo5 demo1 = new Demo5(); //局部变量
        Demo5 demo2 = new Demo5(); //局部变量
        demo1.test(10);
        demo1.test(20);
        demo2.test(30);
        System.out.println(demo1.i+"==="+demo1.j+"==="+demo1.s);
        System.out.println(demo2.i+"==="+demo2.j+"==="+demo2.s);
    }
}

输出结果:

2===1===5
1===1===5

局部变量和成员变量的区别:

1.声明的位置

      局部变量:方法体中,形参,代码块中

      成员变量:类中方法外

                       类变量:有static修饰

                       实例变量:没有static修饰

2.修饰符

     局部变量:final 

     成员变量:public,protected,private,final,static,volatile,trnasient

3.值存储的位置

      局部变量:栈

      实例变量:堆

      类变量:方法区中

4.作用域

     局部变量:从声明处开始,到所属的 } 结束

     实例变量:在当前类中  “this” (有时this.可以缺省),在其它类中 ”对象名.“  访问

     类变量:在当前类中  ”类名.“ (有时类名.可以省略),在其它类中 ”类名.“ 或 ”对象名.“  访问

5.生命周期

  局部变量:每一个线程,每一次调用执行都是新的生命周期

  实例变量:随着对象的创建而初始化,随着对象的被回收而消亡,每一个对象的实例变量是独立的

  类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量是共享的

当局部变量与xx变量重名时,如何区分:

 1.局部变量与实例变量重名:在实例变量前加  ”this.“

 2.局部变量与类变量重名:在类变量前加 ”类名“

JVM中堆,栈,方法区中存储的内容:

1.堆:此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在java虚拟机规范中描述的是:送有的对象实例以及数组都要在堆上分配

2.栈:通常所说的栈,是指虚拟机栈。虚拟机栈用于存储局部变量表等。局部变量表存放了编译器可知长度的各种基本数据类型(boolean,byte,char,short,int,long,float,double),对象引用(reference类型,他不等同于对象本身,是对象在堆内存的首地址)。方法执行完,自行释放。

3.方法区:用于存储被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。