1. java中静态变量,静态代码块,静态方法,实例变量,匿名代码块
在Java中,使用{}
括起来的代码称为代码块,代码块可以分为以下四种:
(1)普通代码块:就是类中方法的方法体
public void xxx(){
//code
}
(2)构造块:用{}
裹起来的代码片段,构造块在创建对象时会被调用,每次创建对象时都会被调用,并且优先于类构造函数执行。 构造块中定义的变量是局部变量。
{
//code
}
(3)静态块:用static{}
裹起来的代码片段,只会被执行一次(第一次加载此类时执行,比如说用Class.forName("")
加载类时就会执行static block
),静态块优先于构造块执行。
static{
//code
}
(4)同步代码块:使用synchronized(obj){}
裹起来的代码块,在多线程环境下,对共享数据进行读写操作是需要互斥进行的,否则会导致数据的不一致性。常见的是synchronized用来修饰方法,其语义是任何线程进入synchronized需要先取得对象锁如果被占用了,则阻塞,实现了互斥访问共享资源。而synchronized也是有代价的。一个常见的场景是,一个冗长的方法中,其实只有一小段代码需要访问共享资源,这时使用同步块,就只将这小段代码裹在synchronized block,既能够实现同步访问,也能够减少同步引入的开销。 同步代码块须写在方法中。
synchronized(obj){
//code
}
下面是一个实例:
public class Test { //1.第一步,准备加载类
public static void main(String[] args)
{
new Test(); //4.第四步,new一个类,但在new之前要处理匿名代码块
// 这里必须等待类加载完
System.out.println("done..");
Test.run();
}
static int num = 4; //2.第二步,静态变量和静态代码块的加载顺序由编写先后决定
static
{
System.out.println("num:"+num); // 3.第三步,静态块,然后执行静态代码块,因为有输出,故打印a
System.out.println("a");
}
{
num += 3;
System.out.println("b:"+num); //5.第五步,按照顺序加载匿名代码块,代码块中有打印
}
int a = 5; //6.第六步,按照顺序加载变量
{ // 成员变量第三个
System.out.println("c:"+a); //7.第七步,按照顺序打印c
}
Test() { // 类的构造函数,第四个加载
System.out.println("d"); //8.第八步,最后加载构造函数,完成对象的建立
}
static void run() // 静态方法,调用的时候才加载
{
System.out.println("e");
}
}
运行:
num:4
a
b:7
c:5
d
done..
e
一般顺序:静态块(静态变量)——>成员变量——>构造方法——>静态方法
1、静态代码块(只加载一次)
2、构造方法(创建一个实例就加载一次)
3、静态方法需要调用才会执行
继承类的静态变量,静态代码块,静态方法,实例变量之间的执行顺序:
例子1:
class Print {
public Print(String s){
System.out.print(s + " ");
}
}
class Parent{
public static Print obj1 = new Print("1");
public Print obj2 = new Print("2");
public static Print obj3 = new Print("3");
static{
new Print("4");
}
public static Print obj4 = new Print("5");
public Print obj5 = new Print("6");
public Parent(){
new Print("7");
}
}
class Child extends Parent{
static
{
//System.out.println(" problem...");
new Print("a");
}
public static Print obj1 = new Print("b");
public Print obj2 = new Print("c");
public Child (){
new Print("d");
}
public static Print obj3 = new Print("e");
public Print obj4 = new Print("f");
}
public class Test1 {
public static void main(String [] args){
Parent obj1 = new Child ();
Parent obj2 = new Child ();
}
}
运行:
1 3 4 5 a b e 2 6 7 c f d 2 6 7 c f d
输出结果表明,程序的执行顺序为:
如果类还没有被加载:
1、先执行父类的静态代码块和静态变量初始化,并且静态代码块和静态变量的执行顺序只跟代码中出现的顺序有关。
2、执行子类的静态代码块和静态变量初始化。
3、执行父类的实例变量初始化
4、执行父类的构造函数
5、执行子类的实例变量初始化
6、执行子类的构造函数
如果类已经被加载:
则静态代码块和静态变量就不用重复执行,再创建类对象时,只执行与实例相关的变量初始化和构造方法。
例子2:
class H1{
{
System.out.println("父类代码块");
}
public H1(){
System.out.println("父类构造");
}
static{
System.out.println("父类静态代码块");
}
}
class H2 extends H1{
static{
System.out.println("子类静态代码块");
}
{
System.out.println("子类代码块");
}
public H2(){
System.out.println("子类构造");
}
}
public class Test1 {
public static void main(String [] args){
new H2();
}
}
运行:
父类静态代码块
子类静态代码块
父类代码块
父类构造
子类代码块
子类构造
执行流程分析:
1.java程序中静态内容是随着类的加载而加载的,由于存在继承关系,因此先加载父类而后加载子类,相应的就是先执行父类静态代码块,再执行子类静态代码块
2.类加载完成后程序就开始执行main方法中,紧接着进行初始化工作,由于代码块执行优于构造方法,因此出现先执行父类代码块,再执行父类构造方法,紧接着子类代码块,子类构造方法。
3.类的初始化是分层初始化的,先对父类进行初始化,再对子类进行初始化。在目标类中执行顺序为:1.成员变量初始化:默认初始化----》显示初始化----》构造方法初始化
2. 普通内部类和静态内部类总结
- 普通内部类可以访问其外部类的各种类型成员,但是静态内部类只能访问静态成员
- 普通内部类里面不能定义各种静态的成员(包括静态变量、静态方法、静态代码块和静态内部类),而静态内部类中则可以;
(1)静态变量和静态方法会出现这个语法错误(static methods can only be declared in a static or top level type)意思就是static方法只能在静态或者顶级类型(顶级类型应该就是外部类中)中声明,当然static变量和static内部类也是一样的道理。原因在静态变量和静态方法都只需要通过类名就能访问,不必通过任何实例化对象;而普通内部类的初始化要利用外部类的实例化对象,这明显违背了static的设计初衷。
(2)静态代码块会出现这个语法错误(Cannot define static initializer in inner type Outer.Inner)意思是不能在内部类中定义静态的初始化程序。
原因跟以上的差不多,static声明的成员只能为类所共有,而不能仅属于一个实例化对象,通俗点来说就是不管有多少层的引用,都只能是类来引用而不能是对象。
3. 理解向上转型:父类引用指向子类对象A a = New B()
向上转型是JAVA中的一种调用方式,是多态的一种表现。向上转型并非是将B自动向上转型为A的对象,相反它是从另一种角度去理解向上两字的:它是对A的对象的方法的扩充,即A的对象可访问B从A中继承来的和B复写A的方法,其它的方法都不能访问,包括A中的私有成员方法。
例子:
class Animal{
public void sleep(){
System.out.println("Animal sleep");
}
public void eat() {
System.out.println("Animal eat");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("dog eat meat");//重写父类方法
}
//子类定义了自己的新方法
public void methods() {
System.out.println("dog method");
}
}
public class Demo {
public static void main(String[] args) {
Animal a = new Dog();
a.sleep();
a.eat();
//a.methods(); /*报错:The method methods() is undefined for the type Animal*/
}
}
运行:
Animal sleep
dog eat meat
可以看出:
向上转型后的a对象只能访问从Animal中继承来的和Dog复写Animal的方法,其它的方法都不能访问,包括Animal中的私有成员方法。但如果要访问Dog类自己的方法,必须强制向下转型 Dog c = (Dog)a;
。
另一个例子:
class Fu {
public int num = 100;
public void show() {
System.out.println("show Fu");
}
public static void function() {
System.out.println("function Fu");
}
}
class Zi extends Fu {
public int num = 1000;
public int num2 = 200;
public void show() {
System.out.println("show Zi");
}
public void method() {
System.out.println("method zi");
}
public static void function() {
System.out.println("function Zi");
}
}
public class DuoTaiDemo {
public static void main(String[] args) {
// 要有父类引用指向子类对象。
// 父 f = new 子();
Fu f = new Zi();
System.out.println(f.num);
// 找不到符号
// System.out.println(f.num2);
f.show();
// 找不到符号
// f.method();
f.function();
}
}
运行:
100
show Zi
function Fu
我们可以看到多态中的成员访问特点:
- 成员变量
编译看左边,运行看左边 - 构造方法
子类的构造都会默认访问父类构造 - 成员方法
编译看左边,运行看右边 - 静态方法
编译看左边,运行看左边
所以静态方法不能算方法的重写
参考:
https://blog.youkuaiyun.com/mrzhoug/article/details/51581994
https://blog.youkuaiyun.com/gh2391292/article/details/74421308