本文共 10639 字,大约阅读时间需要 35 分钟。
参考博文:
大体上总结:
含义: 1.静态的含义可以理解为全局的,静态的。除此之外,也是唯一的。注意,唯一的并不代表不能改变!!!
所处位置:
2.在内存中,既不在栈中也不再堆中,位于数据区。作用时间:
3.凡是静态修饰的,也就表示了在写出的时候,即编译时期就已经在内存中存在了,并不需要在运行时期。注意了,这一点在面试的时候经常会考。并且静态是由java的gc自动清理:
在程序中任何变量或者代码都是在编译时由系统自动分配内存来存储的,而所谓静态就是指在编译后所分配的内存会一直存在,直到程序退出内存才会释放这个空间,也就是只要程序在运行,那么这块内存就会一直存在
服务对象:
4.只作用于一个类的全局变量和全局方法中。原因:之所以不作用于局部变量,是因为静态的数据值是在编译时期的时候就已经分配到内存中了,假设在局部方法中定义局部静态变量成立,那么也就是说其变量的作用时期是在类运行加载方法的时候使用的,那么这就有悖于静态前面的特点,即前面第三点:静态是在编译时期就分配好空间的。
所属层次:
5.基于前面的特点,静态所修饰的变量和方法,不像一般的变量和方法,是属于类层次的,而不是对象层次的;也就是说,静态的变量和成员方法,是所有的对象所共有的,并不是某一个对象特有的。例如对象A和对象B,都可以给类中的某个成员变量name赋值,A赋值为张三,B赋值为李四。但是如果有一个静态变量name赋值为王五。那么无论是对象A还是对象B其调用静态的name,所得到的值都是王五。在日常的使用中,静态就是作用在类的成员变量,成员方法,代码块中。那么在这三个地方的特点是什么?
这篇博文总结的很好!!!
参考:以下为引用:
静态变量:静态变量有两种情况:
静态变量是基本数据类型,这种情况下在类的外部不必创建该类的实例就可以直接使用
静态变量是一个引用。这种情况比较特殊,主要问题是由于静态变量是一个对象的引用,那么必须初始化这个对象之后才能将引用指向它。
因此如果要把一个引用定义成static的,就必须在定义的时候就对其对象进行初始化。
在这里有一个很关键的点,就是其静态的变量时可以实例化的!!!
public class TestForStaticObject{ /* 定义一个静态变量并实例化 */ static testObject o = new testObject (); public static void main(String args[]){ //在main中直接以“类名.静态变量名.方法名”的形式使用testObject的方法 } }静态方法:
静态方法与实例方法的不同主要有:
- 静态方法可以直接使用,而实例方法必须在类实例化之后通过对象来调用。
- 在外部调用静态方法时,可以使用“类名.方法名”或者“对象名.方法名”的形式。
- 实例方法只能使用这种方式对象名.方法名。
- 静态方法只允许访问静态成员。而实例方法中可以访问静态成员和实例成员。
- 静态方法中不能使用this(因为this是与实例相关的)。
所以上面的总结就是:
1.静态方法是直接调用的,不需要实例化; 2.静态方法中不能使用this,super这两个关键字;为什么? 因为静态方法先于实例化对象在内存中存在,而this和super是需要实例化的时候才存在。 3.静态方法中只能调用静态的方法,静态成员变量;为什么?跟第二点的思路是一样的。 静态代码块:静态代码块主要用于类的初始化。它只执行一次,并且在同属于一个类的main函数之前执行。
静态代码块的特点主要有:
- 静态代码块会在类被加载时自动执行。
- 静态代码块只能定义在类里面,不能定义在方法里面。
- 静态代码块里的变量都是局部变量,只在块内有效。
- 一个类中可以定义多个静态代码块,按顺序执行。
- 静态代码块只能访问类的静态成员,而不允许访问实例成员。
前面说了那么多静态的特点,这里就说其作用,当然也是其意义。
参考:
定义静态变量:定义静态方法:什么时候定义静态变量:
对象中出现共享数据时,该数据被static所修饰。如国家
什么时候定义静态方法:
当功能内部没有访问到非静态数据时,该方法可以定义成静态的
例如在一个工具类中,它定义的方法就可以是静态的。这样当其它类方法这个工具类的时候,不需要多次实例化对象,添加内存的负担,定义为静态的之后,这样每次都直接类名调用,并且不需要分配额外的内存。
定义静态代码块:当代码有以下三个特征的时候可以考虑使用:
1.用于类的初始化,也就是类的某个属性赋值
2.当一段代码,要先于类实例化加载,那么就可使用;
3.当一段代码,从始至终只执行一次的时候,也可使用,例如mysql的驱动,也就可以使用;
在对于静态理解的时候,其涉及到了一个类加载顺序的问题,这里同样将这个问题温习下。
其加载顺序为:1、父类的静态变量和静态块赋值(按照声明顺序)
2、自身的静态变量和静态块赋值(按照声明顺序) 3、main方法 4、父类的成员变量和块赋值(按照声明顺序) 5、父类构造器赋值 6、自身成员变量和块赋值(按照声明顺序) 7、自身构造器赋值 8、静态方法,实例方法只有在调用的时候才会去执行参考:
在这里有一个问题。
就是当带参数的子类和父类。不带参数的子类和父类。 他们的调用到底是怎样的?看一个案例吧,如下:
A父类package com.ee;public class A { private static int numA; private int numA2; static { System.out.println("A的静态字段 : " + numA); System.out.println("A的静态代码块"); } { System.out.println("A的成员变量 : " + numA2); System.out.println("A的非静态代码块"); } public A() { System.out.println("A的构造器"); } public A(int n) { System.out.println("A的有参构造"); this.numA2 = n; }}
B子类
package com.ee;public class B extends A{ private static int numB; private int numB2; static { System.out.println("B的静态字段 : " + numB); System.out.println("B的静态代码块"); } { System.out.println("B的成员变量 : " + numB2); System.out.println("B的非静态代码块"); } public B() { System.out.println("B的构造器"); } public B(int n) { System.out.println("B的有参构造"); this.numB2 = n; }}
输出:
public static void main(String[] args) { A a=new B(1);//思考有参构造的输出结果 }
其输出结果为
由上面的代码可以得到一个结论:
在实例化一个类的时候,如果这个类有父类,那么必然会调用父类的无参构造函数!
接下来,将上面的父类无参构造函数去掉:
这个时候,编译就会报错:
基于java的基础知识,咱们都知道,当让一个类C实例化的时候,很多时候我们并没有提供类C的默认构造函数,但是仍然能够实例化,这是因为其java会为我们提供一个默认的构造函数。但是在这里,它却报错了。在父类有子类的情况下,并且子类有无参和有参构造函数的时候,会报错。那么是因为有参构造函数的原因吗?如下:
从上面可以看出,跟是否子类为有参或者无参构造函数没有关系。所以可以得出以下结论:
正是因为基于第一点,所以当一个类由子类的时候,必须要为子类提供显示的无参构造函数,否则子类就会在编译时期就报错。
以上总结:
1.在实例化一个类的时候,如果这个类有父类,那么必然会调用父类的无参构造函数! 2.正是因为基于第一点,所以当一个类有子类的时候,必须要为子类提供显式的无参构造函数,否则子类就会在编译时期就报错。参考:
主要参考的博文:
结论如下:
结论1:外部类可以调用内部类的公私有方法 但是其他的类不能调用内部类的私有方法,这一点内部类是 跟一般类的特性一致的。结论2:内部类要想调用外部类的方法不需要实例化对象 只需要直接调用即可,要是跟内部类同名的方法, 则需要使用以下格式: 外部类名.this.内外部类的同名方法
证明代码:
package com.ee.niminglei;//外部类public class OutClass { // 外部类的私有方法 private void outMethod(){ System.out.println("this is private OutClassMethod"); } private void outMethod2(){ System.out.println("this is private OutClassMethod2"); } public static void main(String[] args) { /* * 结论1:外部类可以调用内部类的公私有方法 * 但是其他的类不能调用内部类的私有方法,这一点内部类是 * 跟一般类的特性一致的。 * */ OutClass oc=new OutClass(); InnerClass innerClass = oc.new InnerClass(); innerClass.innerMethod(); innerClass.innerMethod2(); innerClass.innerMethod3(); }// 内部类 public class InnerClass{ // 内部类的公有方法 public void innerMethod(){ System.out.println("this is inner publicClassMethod"); }// 内部类的私有方法 private void innerMethod2(){ System.out.println("this is inner privateClassMethod"); } /* * 结论2:内部类要想调用外部类的方法不需要实例化对象 * 只需要直接调用即可,要是跟内部类同名的方法,则需要使用以下格式: * 外部类名.this.内外部类的同名方法 * */ public void innerMethod3(){ // 这个表示调用的是外部类的方法 OutClass.this.outMethod();// 这个表示调用的是内部类的方法 outMethod(); outMethod2(); }// 这个是跟外部类同名的私有方法 private void outMethod(){ System.out.println("this is private InnerClassMethod"); } }}
内部类的调用方式:
直接使用外部类:
public static void main(String args[]){ Outer out = new Outer(); // 外部类实例化对象 Outer.Inner in = out.new Inner() ; // 实例化内部类对象 in.print() ; // 调用内部类的方法 }注意点
如果要在一个外部类中的非静态方法直接new内部类,那么这个内部类同样得要是静态的。
对于全局变量,这里不用讨论了。其实是跟上面的方法是一样的。
那就是讨论局部变量的事情了。
在此之前,先看下局部变量的两个特点,久不理真忘记了:
a.那就是不使用修饰符。这点差点忘记了。因为本来就是在方法的作用域中,也就是整个变量的使用只是局限于这个方法中使用,所以使用修饰符的话也就画蛇添足了。
b.必须要显示的初始化。
对于第二点,为啥要显示初始化,请参考文章:
也就是为了程序着想。让程序员忘记给予局部变量初始化的时候有更好的保障。
现在讨论其关系,请看代码:
我上面的代码之所以没有报错,是因为我使用的jdk版本是1.8版本,如果使用的是之前的版本,那么就会报错。这个报错是在运行时期的报错,也就是说在编译时期就会提示要加final。
现在就出现如下问题:
1.那么之前的jdk版本为什么要加final呢? 2.1.8之后为什么又不需要加了呢?问题一:
加final,我看到的有两种说法: 第一种说法就是,前面参考的博文说法。 当外部类的对象调用完一个方法的时候,使用到了这个局部变量,当方法使用完的时候,同时这个局部变量也使用完了,内存释放了。那么这个时候如果内部类同样要使用这个局部变量,就找不到这个局部变量。第二种说法,参考
这个文章可得知: 如果有外部类的对象A,同时有一个内部类的全局变量x,外部类方法中的局部变量y。这个时候无论是x还是y其指向的都是对象A。但是当有一个对象B的时候,就会出现y指向的为对象B,照理来说,此时x也应该为指向对象B,但是x仍然指向对象A。这样内部类和外部类的数据就有偏差。根据上面两种说法,如下:
其实无论是第一种说法还是第二种说法,其归根结底还是为了保持其内部类和外部类的数据一致性。这点跟在第二种说法的理解是不谋而合的。
那么,问题二:
1.8之后不用加,根据可知,是因为语法糖(底层java帮我们加上final了)。另附上文章证明:
来看一个案例,如下一个外部类以及内部类:
package com.ee.niminglei;//外部类public class OutClass { // 内部类 public class InnerClass{ // 内部类的公有方法 public void innerMethod(){ System.out.println("this is inner publicClassMethod"); }// 内部类的私有方法 private void innerMethod2(){ System.out.println("this is inner privateClassMethod"); } }}
另外一个类ExtendsFromInner继承这个内部类,如下:
如图所示,当我们继承内部类的时候会报错No enclosing instance of type ‘com.ee.niminglei.OutClass’ is in scope
没有封闭实例在com.ee.niminglei.OutClass的范围内。
那么为什么会有这个报错呢?
参考继承自内部类时,使用默认的构造器会报错,因为内部类会默认的获得指向其外部类对象的引用,所以继承内部类时应该在构造器参数中传递一个其外部类对象的引用(编译器要求你一定要这样做),然后在构造器中使用该外部类对象引用的super方法(该super方法调用的是这个外部类对象的内部类的构造方法)
所以,依据这个原因,如此做就不会报错了,如下:
所以得出结论是:
如果一个内部类为父类,那么就必须显式的将该内部类的外部类数据类型作为子类构造函数的参数,才能实现继承。在查看这篇文章的时候,提到了一个问题:
到一个外部类继承自另一个含有内部类的父类。然后在该类中重写了父类的内部类,这个时候会怎么样呢?父类的内部类会被覆盖吗?
来看一组代码:
一个类:package com.ee.niminglei;//外部类public class OutClass { public OutClass() { System.out.println("这是外部类OutClass的构造函数"); } // 内部类 public class InnerClass{ public InnerClass() { System.out.println("这是内部类InnerClass的构造函数"); } }}
有一个类继承该类,重写该类的内部类,如下:
package com.ee.niminglei;public class ExtendsFromInner extends OutClass { // 这个是重写父类的内部类构造方法 public class InnerClass{ public InnerClass() { System.out.println("这个是重写父类OutClass的内部类InnerClass的构造函数"); } } public static void main(String[] args) { new ExtendsFromInner(); }}
运行之后如下:
所以可以得出结论: 父类的内部类不会被重写其实这同样是论证了内部类是一个独立的类。
隐藏某些实现、多继承、单元测试、闭包问题(即如果一个类继承了某个父类,这个类还想实现一个接口,但是父类和这接口有些方法或属性有冲突,想把父类和接口的功能都保留下来,那么,就可以用内部类来配合,用接口加内部类来实现闭包)
也就是为类的多态提供了另一个延展。
参考
一篇详谈内部类,如下:
参考:
看一个小案例;
首先创建一个抽象类Person,其中有一个eat的方法,如下:package com.ee.nimingnblei;public abstract class Person { public abstract void eat();}
然后有一个类继承了这个抽象类,如下:
package com.ee.nimingnblei;public class Child extends Person { @Override public void eat() { System.out.println("child eat something"); }}
运行这个实现类中的方法。
package com.ee.nimingnblei;public class Test { public static void main(String[] args) { Person person=new Child(); person.eat(); }}
结果为:
这是一个很常见的处理手段,也就是当要实现一个抽象类的时候,我们就需要继承它,并且实现。
但是当这个抽象类只使用一次的时候,重新写一个类去继承抽象类来实现这个方法,就会显得很麻烦了。在这个时候,也就出现了匿名内部类。
代码如下:package com.ee.nimingnblei;public class Test { public static void main(String[] args) { // 匿名内部类的基本使用 Person person=new Person(){ @Override public void eat() { System.out.println("匿名child eat something"); } }; person.eat(); }}
运行如下:
说实话这就很方便了,比起重新去写一个子类实现抽象类,这样直接在抽象类后面加一个大括号直接将抽象类中的方法实现,方便很多。除了抽象类,其接口也可以也能如此。
定义一个接口,如下:package com.ee.nimingnblei;public interface Animal { public void eat();}
同时,直接使用内部类实现这个方法,如下:
package com.ee.nimingnblei;public class Test { public static void main(String[] args) { // 匿名内部类的接口使用 Animal animal=new Animal() { @Override public void eat() { System.out.println("匿名animal eat something"); } }; animal.eat(); }}
运行结果为:
这两段匿名类的方法,在我自己的印象里面用的最多的就是在线程类Thread中了。这里就不讨论其写法了。
参考:
通过上面的例子,我们可以得出以下结论:
1.匿名内部类适用某个接口或抽象类中的方法只使用一次,而多创建一个类的场景;简化了代码。
2.匿名内部类看似是使用了一个大括号{}来代替一个类。然鹅,从反编译中可以看到,实际上运行的时候java仍然会生成唯一的类名,并且仍然是一个内部类,因为仍然具有内部类的继承相同的做法。(内部类的继承总结)。
在这篇文章中有提到关于这个匿名内部类的使用问题。
请看一个案例,如下:
为什么不能调用匿名内部类中自己的方法呢?
由博文可知:其实匿名内部类连名字都没有,你咋实例对象去调用它的方法呢?但继承父类的方法和实现的方法是可以正常调用的。
因而言,这里就出现了两种解决方式。
1.就是在该匿名内部类继承的抽象类或者实现的接口中将要定义内部类自己的方法定义出来,然后再自己重写方法,即可调用。2.直接在大括号后面使用点调用,也就是这样:
从上面解决调用匿名内部类方法的方式,我们能够知道作为一个匿名内部类其本身的类名都是匿名的,那么其对象更加是匿名的。所以每一次创建匿名内部类都只是创建了所继承的父类或者实现的接口对象,而不是匿名内部类本身的对象。
对于匿名内部类本身的对象,我们只是知道其存在,却不晓得其具体。诶,这就跟我们面向对象的思想不谋而合了,晓得其抽象不知具体。
转载地址:http://mcgzi.baihongyu.com/