• static关键字的探讨


    1、深度总结

      引用一位网友的话,说的非常好,如果别人问你static的作用;如果你说静态修饰 类的属性 和 类的方法 别人认为你是合格的;如果是说 可以构成 静态代码块,那别人认为你还可以; 如果你说可以构成 静态内部类, 那别人认为你不错;如果你说了静态导包,

    那别人认为你很OK;

      那我们就先在这几方面一一对static进行总结;然后说一些模糊的地方,以及一些面试中容易问道的地方;

    1)static方法

      static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。

      但是要注意的是,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的。举个简单的例子:

      在上面的代码中,由于print2方法是独立于对象存在的,可以直接用过类名调用。假如说可以在静态方法中访问非静态方法/变量的话,那么如果在main方法中有下面一条语句:

      MyObject.print2();

      此时对象都没有,str2根本就不存在,所以就会产生矛盾了。同样对于方法也是一样,由于你无法预知在print1方法中是否访问了非静态成员变量,所以也禁止在静态成员方法中访问非静态成员方法。

      而对于非静态成员方法,它访问静态成员方法/变量显然是毫无限制的。

      因此,如果说想在不创建对象的情况下调用某个方法,就可以将这个方法设置为static。我们最常见的static方法就是main方法,至于为什么main方法必须是static的,现在就很清楚了。因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。

      另外记住,关于构造器是否是static方法可参考:http://blog.csdn.net/qq_17864929/article/details/48006835

    2)static变量

      static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本【存放在方法区】,它当且仅当在类初次加载时会被初始化【加final和不加final的static变量初始化的位置不一样】。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。

      static成员变量的初始化顺序按照定义的顺序进行初始化。

    3)static代码块

      static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次【根据class加载原理 每个类加载一次 使用双亲委托加载】

      初始化的顺序 静态代码块 > 构造代码块 > 构造函数 

    public class Client {
        {//构造代码块
            System.out.println("执行构造代码块");
        }
    }

      

    为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。下面看个例子:

    复制代码
    class Person{
        private Date birthDate;
         
        public Person(Date birthDate) {
            this.birthDate = birthDate;
        }
         
        boolean isBornBoomer() {
            Date startDate = Date.valueOf("1946");
            Date endDate = Date.valueOf("1964");
            return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
        }
    }
    复制代码

      isBornBoomer是用来这个人是否是1946-1964年出生的,而每次isBornBoomer被调用的时候,都会生成startDate和birthDate两个对象,造成了空间浪费,如果改成这样效率会更好,其实就是利用了静态代码块在内存中值加载一次的机制:

    复制代码
    class Person{
        private Date birthDate;
        private static Date startDate,endDate;
        static{
            startDate = Date.valueOf("1946");
            endDate = Date.valueOf("1964");
        }
         
        public Person(Date birthDate) {
            this.birthDate = birthDate;
        }
         
        boolean isBornBoomer() {
            return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
        }
    }
    复制代码

      因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。

    4)静态内部类

    这个地方不单独写静态内部类,通过和普通的内部类对比来加深对静态内部类的理解:

    为何要用内部类?

       1.   内部类一般只为其外部类使用;【供外部类使用说的很好  举例 hashmap集合中 有一个内部类 Entry 就是 转为为 hashmap 存储来使用】

       2.   内部类提供了某种进入外部类的窗户,内部类存在外部类的引用,所以内部类可以直接访问外部类的属性;

       3.   也是最吸引人的原因,每个内部类都能独立地继承一个接口,而无论外部类是否已经继承了某个接口。因此,内部类使多重继承的解决方案变得更加完整。

    定义在一个类内部的类叫内部类,包含内部类的类称为外部类。内部类可以声明public、protected、private等访问限制,可以声明 为abstract的供其他内部类或外部类继承与扩展,或者声明为static、final的,也可以实现特定的接口。

    外部类按常规的类访问方式(以对象的方式)使用内部 类,唯一的差别是外部类可以访问内部类的所有方法与属性,包括私有方法与属性,外部类访问内部类,需要创建对象访问;有一点需要注意,内部类不能访问外部类所在的局部变量,只能访问final修饰的局部变量。

    举例: 在方法中 定义内部类  然后内部类 调用方法的的入参 则 入参必须是  final 修饰

    在方法内定义内部类时,如果内部类调用了方法中的变量,那么该变量必须申明为final类型,百思不得其解,后来想到应该是生命周期的原因,因为方法内定义的变量是局部变量,离开该方法,变量就失去了作用,也就会自动被消除,而内部类却不会离开它所在方法就失去作用,它有更广的生命周期,下面通过一个实例加以说明:

    image

     

    (1)创建实例

       OutClass.InnerClass obj = outClassInstance.new InnerClass(); //注意是外部类实例.new,内部类

       AAA.StaticInner in = new AAA.StaticInner();//注意是外部类本身,静态内部类

       (2)内部类中的this

       内部类中的this与其他类一样是指的本身。创建内部类对象时,它会与创造它的外围对象有了某种联系,于是能访问外围类的所有成员,不需任何特殊条件,可理解为内部类链接到外部类。 用外部类创建内部类对象时,此内部类对象会秘密的捕获一个指向外部类的引用,于是,可以通过这个引用来访问外围类的成员。

       (3)外部类访问内部类

       内部类类似外部类的属性,因此访问内部类对象时总是需要一个创建好的外部类对象。外部类对象通过‘外部类名.this.xxx’的形式访问内部类的属性与方法。如:
           System.out.println("Print in inner Outer.index=" + pouter.this.index);
           System.out.println("Print in inner Inner.index=" + this.index);

    (4)内部类向上转型

       内部类也可以和普通类一样拥有向上转型的特性。将内部类向上转型为基类型,尤其是接口时,内部类就有了用武之地。如果内部类是private的,只可以被它的外部类问,从而完全隐藏实现的细节。

    (5)方法内的类

       方法内创建的类(注意方法中也能定义类),不能加访问修饰符。另外,方法内部的类也不是在调用方法时才会创建的,它们一样也被事先编译了。

    (6)静态内部类

       定义静态内部类:在定义内部类的时候,可以在其前面加上一个权限修饰符static。此时这个内部类就变为了静态内部类。

    通常称为嵌套类,当内部类是static时,意味着:

       [1]要创建嵌套类的对象,并不需要其外围类的对象;

       [2]不能从嵌套类的对象中访问非静态的外围类对象(不能够从静态内部类的对象中访问外部类的非静态成员);

       嵌 套类与普通的内部类还有一个区别:普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段, 也不能包含嵌套类。但是在嵌套类里可以包含所有这些东西。也就是说,在非静态内部类中不可以声明静态成员,只有将某个内部类修饰为静态类,然后才能够在这 个类中定义静态的成员变量与成员方法。

       另外,在创建静态内部类时不需要将静态内部类的实例绑定在外部类的实例上。普通非静态内部类的 对象是依附在外部类对象之中的,要在一个外部类中定义一个静态的内部类,不需要利用关键字new来创建内部类的实例。静态类和方法只属于类本身,并不属于 该类的对象,更不属于其他外部类的对象。

    (7)内部类标识符

       每个类会产生一个.class文件,文件名即为类名。同样,内部类也会产生这么一个.class文件,但是它的名称却不是内部类的类名,而是有着严格的限制:外围类的名字,加上$,再加上内部类名字。

    代码具体:

    复制代码
    public class OutClassTest 
    {
    static int a; int b; public static void test() { System.out.println("outer class static function"); } public static void main(String[] args) { // new一个外部类 OutClassTest oc1 = new OutClassTest(); // 通过外部类的对象new一个非静态的内部类 OutClassTest.InnerClass no_static_inner = oc1.new InnerClass(); // 调用非静态内部类的方法 System.out.println(no_static_inner.getKey()); // 调用静态内部类的静态变量 System.out.println(OutClassTest.InnerStaticClass.static_value); // 不依赖于外部类实例,直接实例化内部静态类 OutClassTest.InnerStaticClass inner = new OutClassTest.InnerStaticClass(); // 调用静态内部类的非静态方法 System.out.println(inner.getValue()); // 调用内部静态类的静态方法 System.out.println(OutClassTest.InnerStaticClass.getMessage()); } private class InnerClass { // 只有在静态内部类中才能够声明或定义静态成员 // private static String tt = "0"; private int flag = 0; public InnerClass() { // 三.非静态内部类的非静态成员可以访问外部类的非静态变量和静态变量 System.out.println("InnerClass create a:" + a); System.out.println("InnerClass create b:" + b); System.out.println("InnerClass create flag:" + flag); // System.out.println("InnerClass call outer static function"); // 调用外部类的静态方法 test(); } public String getKey() { return "no-static-inner"; } } private static class InnerStaticClass { // 静态内部类可以有静态成员,而非静态内部类则不能有静态成员。 private static String static_value = "0"; private int flag = 0; public InnerStaticClass() { System.out.println("InnerClass create a:" + a); // 静态内部类不能够访问外部类的非静态成员 // System.out.println("InnerClass create b:" + b); System.out.println("InnerStaticClass flag is " + flag); System.out.println("InnerStaticClass tt is " + static_value); } public int getValue() { // 静态内部类访问外部类的静态方法 test(); return 1; } public static String getMessage() { return "static-inner"; } } public OutClassTest() { // new一个非静态的内部类 InnerClass ic = new InnerClass(); System.out.println("OuterClass create"); } }
    复制代码

    4)静态导包

     静态导包就是java包的静态导入,用import static代替import静态导入包是JDK1.5中的新特性。

    一般我们导入一个类都用 import com…..ClassName;而静态导入是这样:import static com…..ClassName.*;这里的多了个static,还有就是类名ClassName后面多了个.* ,意思是导入这个类里的静态方法。当然,也可以只导入某个静态方法,只要把 .* 换成静态方法名就行了。然后在这个类中,就可以直接用方法名调用静态方法,而不必用ClassName.方法名 的方式来调用。

    好处:这种方法的好处就是可以简化一些操作,例如打印操作System.out.println(…);就可以将其写入一个静态方

    法print(…),在使用时直接print(…)就可以了。但是这种方法建议在有很多重复调用的时候使用,如果仅有一到两次调用,不如直接写来的方便

    example:
    在Java 5中,import语句得到了增强,以便提供甚至更加强大的减少击键次数功能,虽然一些人争议说这是以可读性为代价的。这种新的特性成为静态导入。当你想使用static成员时,可以使用静态导入(在API中的类和你自己的类上,都可以使用该特性)。下面是静态导入前后的代码实例:


    在静态导入之前:

    public class TestStatic {

    public static void main(String[] args) {

    System.out.println(Integer.MAX_VALUE);

    System.out.println(Integer.toHexString(42));

    }

    }

    在静态导入之后:
    import static java.lang.System.out;

    import static java.lang.Integer.*;


    public class TestStaticImport {

    public static void main(String[] args) {

    out.println(MAX_VALUE);

    out.println(toHexString(42));

    }

    }

    让我们看一下使用静态导入特性的代码中将发生什么:
    1、虽然该特性通常称为“静态导入”,但语法必须是import static,后面跟你想导入的static成员的完全限定名称,或者通配符。在本例中,我们在System类的out对象上进行静态导入。

    2、在本例中,我们可能想使用java.lang.Integer类的几个static成员。该静态导入语句使用通配符来表达“我想在此类中的所有静态成员上进行静态导入”。

    3、现在我们终于看到静态导入特性的好处!我们不必在System.out.println中键入System。太好了!另外,我们不必在Integer.MAX_VALUE中键入Integer。因此,在这行代码中,我们能够将快捷方式用于静态方法和一个常量。

    4、最后,我们进行更多的快捷操作,这次针对Integer类的方法。

    关于该特性,我们已经有点儿讽刺意味儿了,但不仅我们是这样的。我们不认为节省少量的击键次数会让代码

    难于阅读一点,但许多开发人员要求将它添加到语言中。

    下面是使用静态导入的几条原则:

    你必须说import static, 不能说static import。

    提防含糊不清的命名static成员。例如,如果你对Integer类和Long类执行了静态导入,引用MAX_VALUE将导致一个编译器错误,因为Integer和Long都有一个MAX_VALUE常量,并且Java不会知道你在引用哪个MAX_VALUE。

    你可以在static对象引用、常量(记住,它们是static 或final)和static方法上进行静态导入。

    二.static关键字的误区

    1.static关键字会改变类中成员的访问权限吗?

      有些初学的朋友会将java中的static与C/C++中的static关键字的功能混淆了。在这里只需要记住一点:与C/C++中的static不同,Java中的static关键字不会影响到变量或者方法的作用域。在Java中能够影响到访问权限的只有private、public、protected(包括包访问权限)这几个关键字。看下面的例子就明白了:

      提示错误"Person.age 不可视",这说明static关键字并不会改变变量和方法的访问权限。

    2.能通过this访问静态成员变量吗?

      虽然对于静态方法来说没有this,那么在非静态方法中能够通过this访问静态成员变量吗?先看下面的一个例子,这段代码输出的结果是什么?

    复制代码
    public class Main {  
        static int value = 33;
     
        public static void main(String[] args) throws Exception{
            new Main().printValue();
        }
     
        private void printValue(){
            int value = 3;
            System.out.println(this.value);
        }
    }
    复制代码
    输出 33

      这里面主要考察队this和static的理解。this代表什么?this代表当前对象,那么通过new Main()来调用printValue的话,当前对象就是通过new Main()生成的对象。而static变量是被对象所享有的,因此在printValue中的this.value的值毫无疑问是33。在printValue方法内部的value是局部变量,根本不可能与this关联,所以输出结果是33。在这里永远要记住一点:静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。

    3.static能作用于局部变量么?

      在C/C++中static是可以作用域局部变量的,但是在Java中切记:static是不允许用来修饰局部变量。不要问为什么,这是Java语法的规定。

      具体原因可以参考这篇博文的讨论:http://www.debugease.com/j2se/178932.html

    三.常见的笔试面试题

      下面列举一些面试笔试中经常遇到的关于static关键字的题目,仅供参考,如有补充欢迎下方留言。

    1.下面这段代码的输出结果是什么?

    复制代码
    public class Test extends Base{
     
        static{
            System.out.println("test static");
        }
         
        public Test(){
            System.out.println("test constructor");
        }
         
        public static void main(String[] args) {
            new Test();
        }
    }
     
    class Base{
         
        static{
            System.out.println("base static");
        }
         
        public Base(){
            System.out.println("base constructor");
        }
    }
    复制代码
    base static
    test static
    base constructor
    test constructor

      至于为什么是这个结果,我们先不讨论,先来想一下这段代码具体的执行过程,在执行开始,先要寻找到main方法,因为main方法是程序的入口,但是在执行main方法之前,必须先加载Test类,而在加载Test类的时候发现Test类继承自Base类,因此会转去先加载Base类,在加载Base类的时候,发现有static块,便执行了static块。在Base类加载完成之后,便继续加载Test类,然后发现Test类中也有static块,便执行static块。在加载完所需的类之后,便开始执行main方法。在main方法中执行new Test()的时候会先调用父类的构造器,然后再调用自身的构造器。因此,便出现了上面的输出结果。

    2.这段代码的输出结果是什么?

    复制代码
    public class Test {
        Person person = new Person("Test");
        static{
            System.out.println("test static");
        }
         
        public Test() {
            System.out.println("test constructor");
        }
         
        public static void main(String[] args) {
            new MyClass();
        }
    }
     
    class Person{
        static{
            System.out.println("person static");
        }
        public Person(String str) {
            System.out.println("person "+str);
        }
    }
     
     
    class MyClass extends Test {
        Person person = new Person("MyClass");
        static{
            System.out.println("myclass static");
        }
         
        public MyClass() {
            System.out.println("myclass constructor");
        }
    }
    复制代码
    复制代码
    test static
    myclass static
    person static
    person Test
    test constructor
    person MyClass
    myclass constructor
    复制代码

      类似地,我们还是来想一下这段代码的具体执行过程。首先加载Test类,因此会执行Test类中的static块。接着执行new MyClass(),而MyClass类还没有被加载,因此需要加载MyClass类。在加载MyClass类的时候,发现MyClass类继承自Test类,但是由于Test类已经被加载了,所以只需要加载MyClass类,那么就会执行MyClass类的中的static块。在加载完之后,就通过构造器来生成对象。而在生成对象的时候,必须先初始化父类的成员变量,因此会执行Test中的Person person = new Person(),而Person类还没有被加载过,因此会先加载Person类并执行Person类中的static块,接着执行父类的构造器,完成了父类的初始化,然后就来初始化自身了,因此会接着执行MyClass中的Person person = new Person(),最后执行MyClass的构造器。

    3.这段代码的输出结果是什么?

    复制代码
    public class Test {
         
        static{
            System.out.println("test static 1");
        }
        public static void main(String[] args) {
             
        }
         
        static{
            System.out.println("test static 2");
        }
    }
    复制代码
    test static 1
    test static 2
  • 相关阅读:
    BZOJ 1202 狡猾的商人 差分约束or带权并查集
    BZOJ 1270 雷涛的小猫 dp
    Spring Cloud探路(三)REST 客户端Feign
    AWS Credentials 使用
    Spring Cloud探路(二) Erueka客户端的建立
    Spring Cloud探路(一) Erueka服务器的建立
    Spring-Mybatis 异常记录(1)
    Amazon Kinesis Producer Library 使用记录
    MQTT开发小记(一)
    利用VSCode进行.Net Core初尝试
  • 原文地址:https://www.cnblogs.com/huckleberry/p/12299780.html
Copyright © 2020-2023  润新知