Java基础回顾

43 阅读18分钟

一、数据类型


基本类型:byte/8、short/16、int/32、long/64、float/32、double/64、char/8、boolean/~

小数化为整数

  • Math.floor(x) 返回小于等于 x 的最接近整数,返回类型为 double;
  • Math.round(x) 相当于四舍五入,返回值为 long 或 int;
  • Math.ceil(x) 返回大于等于 x 的最接近整数,返回类型为 double

包装类型

1、运算符

三目运算符的类型转换问题

三目运算符里的类型必须一致,比如下面的代码:

int i = 40;
String s1 = String.valueOf(i < 50 ? 233 : 666);
String s2 = String.valueOf(i < 50 ? 233 : 666.0);
assertEquals(true, s1.equals(s2));

结果是测试不通过,这里就涉及到三元操作符的转换规则:

  1. 如果两个操作数无法转换,则不进行转换,返回 Object 对象
  2. 如果两个操作数是正常的类型,那么按照正常情况进行类型转换,比如int => long => float => double
  3. 如果两个操作数都是字面量数字,那么返回范围较大的类型

2、缓存池

  1. 缓存池:Integer举例

    1. valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。
    2. 在 Java 8 中,Integer 缓存池的大小默认为 -128~127。
    3. 编译器会在自动装箱过程调用 valueOf() 方法,因此多个值相同且值在缓存池范围内的 Integer 实例使用自动装箱来创建,那么就会引用相同的对象。
    Integer x = new Integer(123);
    Integer y = new Integer(123);
    System.out.println(x == y);    // false
    Integer z = Integer.valueOf(123);
    Integer k = Integer.valueOf(123);
    System.out.println(z == k);   // true
    
  2. 基本类型对应的缓冲池如下:

    • boolean values true and false
    • all byte values
    • short values between -128 and 127
    • int values between -128 and 127
    • char in the range \u0000 to \u007F

    在使用这些基本类型对应的包装类型时,如果该数值范围在缓冲池范围内,就可以直接使用缓冲池中的对象。

    在 jdk 1.8 所有的数值类缓冲池中,Integer 的缓冲池 IntegerCache 很特殊,这个缓冲池的下界是 - 128,上界默认是 127,但是这个上界是可调的,在启动 jvm 的时候,通过 -XX:AutoBoxCacheMax= 来指定这个缓冲池的大小,该选项在 JVM 初始化的时候会设定一个名为 java.lang.IntegerCache.high 系统属性,然后 IntegerCache 初始化的时候就会读取该系统属性来决定上界。

  3. Integer 类的 valueOf 和 parseInt 的对比

    这个问题是在 StackOverflow 上看到的。以下三个表达式:

    System.out.println(Integer.valueOf("127") == Integer.valueOf("127"));
    System.out.println(Integer.valueOf("128") == Integer.valueOf("128"));
    System.out.println(Integer.parseInt("128") == Integer.valueOf("128"));
    结果分别是:
    true
    false
    true
    

    为什么是这样的结果呢?我们看一下 valueOf 方法的源码:

    public static Integer valueOf(String s) throws NumberFormatException {    
    		return Integer.valueOf(parseInt(s, 10));
    }
    public static Integer valueOf(int i) {    
    		if (i >= IntegerCache.low && i <= IntegerCache.high)        
    					return IntegerCache.cache[i + (-IntegerCache.low)];    
    		return new Integer(i);
    }
    

    可以看到 valueOf 方法是在 parseInt 方法的基础上加了一个读取缓存的过程。我们再看一下 IntegerCache 类的源码:

    /**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];
        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }
        private IntegerCache() {}
    }
    

    原来 JVM 会缓存一部分的 Integer 对象(默认范围为 -128 - 127),在通过 valueOf 获取 Integer 对象时,如果是缓存范围内的就直接返回缓存的 Integer 对象,否则就会 new 一个 Integer 对象。返回的上限可通过 JVM 的参数 -XX:AutoBoxCacheMax=<size> 设置,而且不能小于 127(参照 JLS 5.1.7)。这样我们就可以解释 Integer.valueOf("127") == Integer.valueOf("127") 为什么是 true 了,因为它们获取的都是同一个缓存对象,而默认情况下 Integer.valueOf("128") == Integer.valueOf("128") 等效于 new Integer(128) == new Integer(128),结果自然是 false。

    我们再来看一下 parseInt 方法的原型,它返回一个原生 int 值:

    public static int parseInt(String s) throws NumberFormatException
    

    由于一个原生值与一个包装值比较时,包装类型会自动拆包,因此 Integer.parseInt("128") == Integer.valueOf("128") 就等效于 128 == 128,结果自然是 true。

    Long 类型同样也有缓存。

3、instanceof 操作符的注意事项

instanceof 操作符左右两边的操作数必须有继承或派生关系,否则不会编译成功。因此,instanceof 操作符只能用于对象,不能用于基本类型(不会自动拆包)。

下面是一些典型的例子:

public class FuckingIOF {
    @Test
    public void test() {
        List<Object> list = new ArrayList<>();
        list.add("String" instanceof Object);
        list.add(new String() instanceof Object);
        list.add(new Object() instanceof String);
        //list.add('a' instanceof Character); //此句会编译错误
        list.add(null instanceof String);
        list.add((String)null instanceof String);
        list.add(null instanceof Object);
        list.add(new Generic<String>().isDataInstance(""));
        list.forEach(System.out::println);
    }
}
class Generic<T> {
    public boolean isDataInstance(T t) {
        return t instanceof Date;
    }
}

运行结果和分析:

true => StringObject 的子类
true => 同上
false => 同上
false => Java 语言规范规定 null instanceof ? 都是 false
false => 同上,无论怎么转换还是 null
false => 同上
false => 由于 Java 泛型在编译时会进行类型擦除,因此这里相当于 Object instanceof Date

二、String类


String 被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承)

1、不可变的好处

1. 可以缓存 hash 值

因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。

2. String Pool 的需要

如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。

3. 安全性

String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。

4. 线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

2、String, StringBuffer and StringBuilder 的差异

1. 可变性

  • String 不可变
  • StringBuffer 和 StringBuilder 可变

2. 线程安全

  • String 不可变,因此是线程安全的
  • StringBuilder 不是线程安全的
  • StringBuffer 是线程安全的,内部使用 synchronized 进行同步

三、Object类


1、clone()

  1. clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。还需要实现implements Cloneable 接口。

    public class CloneExample implements Cloneable {
        private int a;
        private int b;
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
  2. 浅拷贝

    浅拷贝就是指只实现了Cloneable接口,并没有彻底重写clone()方法——将内部引用属性的一切属性克隆

    Untitled

    • 浅拷贝代码演示

      public class Constant implements Cloneable {
          private Solution solution = new Solution();
      
          public Solution getSolution() {
              return solution;
          }
      
          @Override
          public Object clone() throws CloneNotSupportedException {
              return super.clone();
          }
          
          public static void main(String[] args) throws CloneNotSupportedException {
              Constant constant = new Constant();
              Constant clone = (Constant) constant.clone();
              System.out.println("constant = " + constant);
              System.out.println("constant = " + constant.getSolution());
              System.out.println("clone = " + clone);
              System.out.println("clone = " + clone.getSolution());
      
          }
      }
          
      class Solution { // 内部类,用于Constant类的引用变量;
          static boolean[] hash = new boolean[128];
          static char[] vowels = new char[]{'a', 'e', 'i', 'o', 'u'};
      
          static {
              for (char c : vowels) {
                  hash[c - ' '] = hash[Character.toUpperCase(c) - ' '] = true;
              }
          }
      
          public String reverseVowels(String s) {
              char[] cs = s.toCharArray();
              int n = s.length();
              int l = 0, r = n - 1;
              while (l < r) {
                  if (hash[cs[l] - ' '] && hash[cs[r] - ' ']) {
                      swap(cs, l++, r--);
                  } else {
                      if (!hash[cs[l] - ' ']) l++;
                      if (!hash[cs[r] - ' ']) r--;
                  }
              }
              return String.valueOf(cs);
          }
      
          void swap(char[] cs, int l, int r) {
              char c = cs[l];
              cs[l] = cs[r];
              cs[r] = c;
          }
      }
      
  3. clone() 的替代方案

    使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。

    public class CloneConstructorExample {
    
        private int[] arr;
    
        public CloneConstructorExample() {
            arr = new int[10];
            for (int i = 0; i < arr.length; i++) {
                arr[i] = i;
            }
        }
    
        public CloneConstructorExample(CloneConstructorExample original) {
            arr = new int[original.arr.length];
            for (int i = 0; i < original.arr.length; i++) {
                arr[i] = original.arr[i];
            }
        }
    
        public void set(int index, int value) {
            arr[index] = value;
        }
    
        public int get(int index) {
            return arr[index];
        }
    }
    
    CloneConstructorExample e1 = new CloneConstructorExample();
    CloneConstructorExample e2 = new CloneConstructorExample(e1);
    e1.set(2, 222);
    System.out.println(e2.get(2)); // 2
    

2、equals()

1. 等价关系

两个对象具有等价关系,需要满足以下五个条件:

Ⅰ 自反性

x.equals(x); // true

Ⅱ 对称性

x.equals(y) == y.equals(x); // true

Ⅲ 传递性

if (x.equals(y) && y.equals(z))
    x.equals(z); // true;

Ⅳ 一致性

多次调用 equals() 方法结果不变

x.equals(y) == x.equals(y); // true

Ⅴ 与 null 的比较

对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false

x.equals(null); // false;

2. 等价与相等

  • 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
  • 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。
Integer x = new Integer(1);
Integer y = new Integer(1);
System.out.println(x.equals(y)); // true
System.out.println(x == y);      // false

3. 实现

  • 检查是否为同一个对象的引用,如果是直接返回 true;
  • 检查是否是同一个类型,如果不是,直接返回 false;
  • 将 Object 对象进行转型;
  • 判断每个关键域是否相等。
public class EqualExample {

    private int x;
    private int y;
    private int z;

    public EqualExample(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        EqualExample that = (EqualExample) o;

        if (x != that.x) return false;
        if (y != that.y) return false;
        return z == that.z;
    }
}
  • @Data注解自动生成的内容

    package com.jowto.userSrv.domain;
    
    import lombok.Data;
    
    import javax.validation.constraints.NotBlank;
    
    /**
     * InspurLogin 浪潮登录验证实体
     *
     * @author zhangxinlong
     * @version 2024/05/27 15:51
     **/
    
    @Data
    public class InspurLogin {
    
        @NotBlank
        private String accessToken;
    
        private String state;
    }
    
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    package com.jowto.userSrv.domain;
    import javax.validation.constraints.NotBlank;
    
    public class InspurLogin {
        private @NotBlank String accessToken;
        private String state;
    
        public InspurLogin() {
        }
    
        public String getAccessToken() {
            return this.accessToken;
        }
    
        public String getState() {
            return this.state;
        }
    
        public void setAccessToken(final String accessToken) {
            this.accessToken = accessToken;
        }
    
        public void setState(final String state) {
            this.state = state;
        }
    
        public boolean equals(final Object o) {
            if (o == this) {
                return true;
            } else if (!(o instanceof InspurLogin)) {
                return false;
            } else {
                InspurLogin other = (InspurLogin)o;
                if (!other.canEqual(this)) {
                    return false;
                } else {
                    Object this$accessToken = this.getAccessToken();
                    Object other$accessToken = other.getAccessToken();
                    if (this$accessToken == null) {
                        if (other$accessToken != null) {
                            return false;
                        }
                    } else if (!this$accessToken.equals(other$accessToken)) {
                        return false;
                    }
    
                    Object this$state = this.getState();
                    Object other$state = other.getState();
                    if (this$state == null) {
                        if (other$state != null) {
                            return false;
                        }
                    } else if (!this$state.equals(other$state)) {
                        return false;
                    }
    
                    return true;
                }
            }
        }
    
        protected boolean canEqual(final Object other) {
            return other instanceof InspurLogin;
        }
    
        public int hashCode() {
            int PRIME = true;
            int result = 1;
            Object $accessToken = this.getAccessToken();
            result = result * 59 + ($accessToken == null ? 43 : $accessToken.hashCode());
            Object $state = this.getState();
            result = result * 59 + ($state == null ? 43 : $state.hashCode());
            return result;
        }
    
        public String toString() {
            String var10000 = this.getAccessToken();
            return "InspurLogin(accessToken=" + var10000 + ", state=" + this.getState() + ")";
        }
    }
    

四、继承


1、里式替换原则

里式替换原则(Liskov Substitution Principle,LSP)是面向对象设计中的一个重要原则,由Barbara Liskov提出。它指出,子类对象必须能够替换其父类对象而不影响程序的正确性。换句话说,所有引用基类(父类)的地方必须能够透明地使用其子类的对象。

具体来说,里式替换原则包含以下几个要点:

  1. 子类必须能够替换掉它们的父类并出现在父类能够出现的任何地方。
  2. 子类可以增加自己特有的行为(扩展)。
  3. 子类可以在不改变原有功能的基础上修改父类的方法(覆盖或实现)。

遵循里式替换原则可以提高代码的灵活性和可扩展性,同时确保继承体系的稳定性和逻辑正确性。

2、访问权限

Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。

可以对类或类中的成员(字段和方法)加上访问修饰符。

  • 类可见表示其它类可以用这个类创建实例对象。
  • 成员可见表示其它类可以用这个类的实例对象访问到该成员;

protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。

设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。

如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例去代替,也就是确保满足里氏替换原则。

字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中,AccessExample 拥有 id 公有字段,如果在某个时刻,我们想要使用 int 存储 id 字段,那么就需要修改所有的客户端代码。

3、抽象类与接口

1. 抽象类

抽象类和抽象方法都使用 abstract 关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。

抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。

public abstract class AbstractClassExample {

    protected int x;
    private int y;

    public abstract void func1();

    public void func2() {
        System.out.println("func2");
    }
}
public class AbstractExtendClassExample extends AbstractClassExample {
    @Override
    public void func1() {
        System.out.println("func1");
    }
}

// AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated

AbstractClassExample ac2 = new AbstractExtendClassExample();
ac2.func1();

2. 接口

接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。

从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类,让它们都实现新增的方法。

接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。从 Java 9 开始,允许将方法定义为 private,这样就能定义某些复用的代码又不会把方法暴露出去。

接口的字段默认都是 static 和 final 的。

public interface InterfaceExample {

    void func1();

    default void func2(){
        System.out.println("func2");
    }

    int x = 123;
    // int y;               // Variable 'y' might not have been initialized
    public int z = 0;       // Modifier 'public' is redundant for interface fields
    // private int k = 0;   // Modifier 'private' not allowed here
    // protected int l = 0; // Modifier 'protected' not allowed here
    // private void fun3(); // Modifier 'private' not allowed here
}
public class InterfaceImplementExample implements InterfaceExample {
    @Override
    public void func1() {
        System.out.println("func1");
    }
}

// InterfaceExample ie1 = new InterfaceExample(); // 'InterfaceExample' is abstract; cannot be instantiated
InterfaceExample ie2 = new InterfaceImplementExample();
ie2.func1();
System.out.println(InterfaceExample.x);

3. 比较

  • 从设计层面上看,抽象类提供了一种 IS-A 关系,需要满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
  • 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
  • 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
  • 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。

4. 使用选择

使用接口:

  • 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Comparable 接口中的 compareTo() 方法;
  • 需要使用多重继承

使用抽象类:

  • 需要在几个相关的类中共享代码
  • 需要能控制继承来的成员的访问权限,而不是都为 public。
  • 需要继承非静态和非常量字段

在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。

4、super

  • 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。应该注意到,子类一定会调用父类的构造函数来完成初始化工作,一般是调用父类的默认构造函数,如果子类需要调用父类其它构造函数,那么就可以使用 super() 函数。
  • 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
public class SuperExample {

    protected int x;
    protected int y;

    public SuperExample(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public void func() {
        System.out.println("SuperExample.func()");
    }
}
public class SuperExtendExample extends SuperExample {

    private int z;

    public SuperExtendExample(int x, int y, int z) {
        super(x, y);
        this.z = z;
    }

    @Override
    public void func() {
        super.func();
        System.out.println("SuperExtendExample.func()");
    }
}
SuperExample e = new SuperExtendExample(1, 2, 3);
e.func();

SuperExample.func()
SuperExtendExample.func()

五、异常

Throwable可以用来表示任何可以作为异常抛出的类,分为两种: **Error**和 Exception。其中 Error 用来表示 JVM 无法处理的错误,Exception 分为两种:

  • 受检异常 :需要用 try...catch... 语句捕获并进行处理,并且可以从异常中恢复;
  • 非受检异常 :是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序崩溃并且无法恢复。

Untitled

牛课题

  1. 粉红色的是受检查的异常(checked exceptions),其必须被 try{} catch语句块所捕获,或者在方法签名里通过throws子句声明.受检查的异常必须在编译时被捕捉处理,命名为 Checked Exception 是因为Java编译器要进行检查,Java虚拟机也要进行检查,以确保这个规则得到遵守.
  2. 绿色的异常是运行时异常(runtime exceptions),需要程序员自己分析代码决定是否捕获和处理,比如:空指针,被0除...
  3. 而声明为Error的,则属于严重错误,如系统崩溃、虚拟机错误、动态链接失败等,这些错误无法恢复或者不可能捕捉,将导致应用程序中断,Error不需要捕捉。

作者:大河儿马

链接:www.nowcoder.com/exam/test/8…

Untitled

Untitled

六、关键字

1、static

  1. 静态变量
  • 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。
  • 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
public class A {

    private int x;         // 实例变量
    private static int y;  // 静态变量

    public static void main(String[] args) {
        // int x = A.x;  // Non-static field 'x' cannot be referenced from a static context
        A a = new A();
        int x = a.x;
        int y = A.y;
    }
}
  1. 静态方法

静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。

public abstract class A {
    public static void func1(){
    }
    // public abstract static void func2();  
    // Illegal combination of modifiers: 'abstract' and 'static'
}

只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字,因为这两个关键字与具体对象关联。

public class A {

    private static int x;
    private int y;

    public static void func1(){
        int a = x;
        // int b = y;  // Non-static field 'y' cannot be referenced from a static context
        // int b = this.y;     // 'A.this' cannot be referenced from a static context
    }
}
  1. 静态语句块

静态语句块在类初始化时运行一次。

public class A {
    static {
        System.out.println("123");
    }

    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new A();
    }
}

》123

4. 静态内部类

非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。

public class OuterClass {

    class InnerClass {
    }

    static class StaticInnerClass {
    }

    public static void main(String[] args) {
        // InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
        OuterClass outerClass = new OuterClass();
        InnerClass innerClass = outerClass.new InnerClass();
        StaticInnerClass staticInnerClass = new StaticInnerClass();
    }
}

静态内部类不能访问外部类的非静态的变量和方法。

5. 静态导包

在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。

import static com.xxx.ClassName.*

6. 初始化顺序

静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。

public static String staticField = "静态变量";
static {
    System.out.println("静态语句块");
}
public String field = "实例变量";
{
    System.out.println("普通语句块");
}

最后才是构造函数的初始化。

public InitialOrderTest() {
    System.out.println("构造函数");
}

存在继承的情况下,初始化顺序为:

  • 父类(静态变量、静态语句块)
  • 子类(静态变量、静态语句块)
  • 父类(实例变量、普通语句块)
  • 父类(构造函数)
  • 子类(实例变量、普通语句块)
  • 子类(构造函数)