Java细节基础题(一)

1、Java中的修饰符

Java的修饰符根据修饰的对象不同,分为类修饰符方法修饰符变量修饰符

其中每种修饰符又分为访问控制修饰符非访问控制修饰符

1.1 类修饰符

1.1.1 普通类修饰符

访问控制符:公共类修饰符public

非访问控制符:抽象类修饰符:abstract、最终类修饰符:final

 (1)公共类修饰符 public : Java 语言中类的访问控制符只有 public 即公共的。每个 Java 程序的有且只有一个类是 public,它被称为主类 ,其他外部类无访问控制修饰符,具有包访问性。注意:一个类的内部类可以被其他访问控制修饰符protected、default、private修饰,相当于类的成员。

(2)抽象类修饰符 abstract :用 abstract 修饰符修饰的类,被称为抽象类。

(3)最终类修饰符 final :当一个类不能被继承时可用修饰符final修饰为最终类。被定义为 final 的类通常是一些有固定作用、用来完成某种标准功能的类。

(4)类缺省访问控制符:如果一个类没有访问控制符,说明它具有缺省的访问控制符特性。此时,这个类只能被同一个包中的类访问或引用。这一访问特性又称为包访问性。

1.1.2 内部类修饰符

定义:将一个类的定义放在里另一个类的内部,这就是内部类

分类:成员内部类、静态内部类、局部(方法)内部类、匿名内部类。

成员内部类:

访问控制符:可以被public、protected、default、private修饰,相当于类的成员

非访问控制符:抽象类修饰符:abstract、最终类修饰符:final

静态内部类:

访问控制符:可以被public、protected、default、private修饰,相当于类的成员

非访问控制符:静态修饰符:static

局部(方法)内部类:

访问控制符:类前不能有访问控制符

匿名内部类:

访问控制符:类前不能有访问控制符

1.2 方法修饰符

访问控制修饰符:公共访问控制符public、保护访问控制符protected、缺省默认default、私有访问控制符private

非访问控制符:抽象方法控制符abstract 、静态方法控制符static 、最终方法控制符final 、本地方法控制符native 、同步方法控制符synchronized

(1)抽象方法控制符 abstract :用修饰符 abstract 修饰的方法称为抽象方法。抽象方法仅有方法头,没有方法体和操作实体。

(2)静态方法控制符 static :用修饰符 static 修饰的方法称为静态方法。静态方法是属于整个类的类方法;而不使用static 修饰、限定的方法是属于某个具体类对象的方法。由于 static方法是属于整个类的,所以它不能操纵和处理属于某个对象的成员变量,而只能处理属于整个类的成员变量,即static方法只能处理static的域。

(3)最终方法控制符 final :用修饰符 final修饰的方法称为最终方法。最终方法是功能和内部语句不能更改的方法,即最终方法不能重写覆盖。final固定了方法所具有的功能和操作,防止当前类的子类对父类关键方法的错误定义,保证了程序的安全性和正确性。所有被private修饰符限定为私有的方法,以及所有包含在final 类 ( 最终类) 中的方法,都被认为是最终方法。

(4)本地方法控制符 native :用修饰符 native 修饰的方法称为本地方法。为了提高程序的运行速度,需要用其它的高级语言书写程序的方法体,那么该方法可定义为本地方法用修饰符 native 来修饰。

(5)同步方法控制符 synchronized :该修饰符主要用于多线程程序中的协调和同步
 

1.3 变量修饰符

访问控制符:公共访问控制符public 、保护访问控制符protected 、缺省默认访问控制符default、私有访问控制符private 

非访问控制符:静态域修饰符static 、最终域修饰符 final 、易失 ( 共享 ) 域修饰符volatile 、暂时性域修饰符transient

(1)公共访问控制符public:用public修饰的域称为公共域。由于public修饰符会降低运行的安全性和数据的封装性,所以一般应减少 public 域的使用。

(2)私有访问控制符private:用private修饰的成员变量( 域 ) 只能被该类自身所访问,而不能被任何其它类 ( 包括子类 ) 所引用。

(3)保护访问控制符 protected :用protected修饰的成员变量可以被三种类所引用:①该类自身;②同一个包中的其它类;③其它包中的子类。使用修饰符 protected 的主要作用是允许其它包中的子类来访问父类的特定属性。

(4) 缺省默认修饰符 :没有访问控制修饰符或用修饰符 default修饰的成员变量可以被该类本身或同一包中的其他类访问

(5)静态域修饰符 static :用 static修饰的成员变量仅属于类的变量,而不属于任何一个具体的对象,静态成员变量的值是保存在类的内存区域的公共存储单元,而不是保存在某一个对象的内存区间。该类的任一对象访问它时取到的都是相同的数据;该类的任一对象修改它时 , 也都是对同一个内存单元进行操作。

(6)最终域修饰符 final :最终域修饰符 final 是用来定义常量的。一个类的域 ( 成员变量 ) 如果被修饰符 final 说明,则它的取值在程序的整个执行过程中都是不变的。

(7)易失 ( 共享 ) 域修饰符 volatile :易失 ( 共享 ) 域修饰符volatile是用来说明这个成员变量可能被几个线程所控制和修改。也就是说在程序运行过程中,这个成员变量有可能被其它的程序影响或改变它的取值。通常 volatile 用来修饰接受外部输入的域。

(8)暂时性域修饰符 transient :暂时性域修饰符 transient 用来定义一个暂时性变量。其特点是:用修饰符transient 限定的暂时性变量,将指定 Java虚拟机认定该暂时性变量不属于永久状态,以实现不同对象的存档功能。否则,类中所有变量都是对象的永久状态的一部分,存储对象时必须同时保存这些变量。

1.4 访问控制修饰符总结

private:只能被同一个类中的对象访问。

default:可以被同一个类的对象,同一个包中的类访问。

protected:可以被同一个包中的所有类访问,可以所有的子类访问,子类没有在同一个包中也可以访问。

public:同包,不同包都可以访问。

Java细节基础题(一)_第1张图片

2、Java类的加载机制(类加载和初始化顺序)

2.1 类初始化顺序

package cn.wyu.java;


public class CodeBlockForJava extends BaseCodeBlock {

    static {
        System.out.println("2.这里是子类的静态代码块");//2
    }
    {
        System.out.println("6.这里是子类的普通代码块");//6
    }
    Other o = new Other();
    public CodeBlockForJava() {
        System.out.println("8.这里是子类的构造方法");//8
    }

    public static void msg2() {
        System.out.println("这里是子类的静态方法");
    }

    @Override
    public void msg() {
        System.out.println("9.这里是子类的普通方法");//9
    }

    public static void main(String[] args) {
        BaseCodeBlock bcb = new CodeBlockForJava();
        bcb.msg();
    }

}

class BaseCodeBlock {

    Other2 o2 = new Other2();

    static {
        System.out.println("1.这里是父类的静态代码块");//1
    }

    {
        System.out.println("4.这里是父类的普通代码块");//4
    }


    public BaseCodeBlock() {
        System.out.println("5.这里是父类的构造方法");//5
    }

    public void msg() {
        System.out.println("这里是父类的普通方法");
    }

    public static void msg2() {
        System.out.println("这里是父类的静态方法");
    }


}

class Other {
    Other() {
        System.out.println("7.初始化子类的属性值");//7
    }
}

class Other2 {
    Other2() {
        System.out.println("3.初始化父类的属性值");//3
    }
}

运行结果:

1.这里是父类的静态代码块
2.这里是子类的静态代码块
3.初始化父类的属性值
4.这里是父类的普通代码块
5.这里是父类的构造方法
6.这里是子类的普通代码块
7.初始化子类的属性值
8.这里是子类的构造方法
9.这里是子类的普通方法
假设:
将Other o = new Other();变为static Other o = new Other();
将Other2 o2 = new Other2();变为static Other2 o2 = new Other2();

结果?

3.初始化父类的属性值
1.这里是父类的静态代码块
2.这里是子类的静态代码块
7.初始化子类的属性值
4.这里是父类的普通代码块
5.这里是父类的构造方法
6.这里是子类的普通代码块
8.这里是子类的构造方法
9.这里是子类的普通方法

注意:

类的静态属性静态代码块的执行级别是一样的,谁先执行取决于书写的先后顺序。

类的非静态属性普通代码块的执行级别是一样的,谁先执行取决于书写的先后顺序。

结论1:初始化父类的静态属性值/父类的静态代码块(自上而下的顺序执行) -> 初始化子类的静态属性值/子类的静态代码块(自上而下的顺序执行) -> 初始化父类的非静态属性值/父类的普通代码块(自上而下的顺序执行) -> 父类的构造方法 -> 初始化子类的非静态属性值/子类的普通代码块(自上而下的顺序执行) -> 子类的构造方法。

注:构造函数最后执行。

2.2 JAVA类的加载机制

2.2.1 下列代码的执行结果是什么?

package cn.wyu.java;

/**
 * @author linwillen
 * @create 2020-05-01-21:10
 */
public class ClassloadSort1 {

    public static void main(String[] args) {
        Singleton.getInstance();
        System.out.println("Singleton value1:" + Singleton.value1);
        System.out.println("Singleton value2:" + Singleton.value2);

        Singleton2.getInstance2();
        System.out.println("Singleton2 value1:" + Singleton2.value1);
        System.out.println("Singleton2 value2:" + Singleton2.value2);
    }
}

class Singleton {
    static {
        System.out.println(Singleton.value1 + "\t" + Singleton.value2 + "\t" + Singleton.singleton);
        //System.out.println(Singleton.value1 + "\t" + Singleton.value2);
    }
    private static Singleton singleton = new Singleton();
    public static int value1 = 5;
    public static int value2 = 3;

    private Singleton() {
        value1++;
        value2++;
    }

    public static Singleton getInstance() {
        return singleton;
    }

    int count = 10;

    {
        System.out.println("count = " + count);
    }
}

class Singleton2 {
    static {
        System.out.println(Singleton2.value1 + "\t" + Singleton2.value2 + "\t" + Singleton2.singleton2);
    }

    public static int value1 = 5;
    public static int value2 = 3;
    private static Singleton2 singleton2 = new Singleton2();
    private String sign;

    int count = 20;
    {
        System.out.println("count = " + count);
    }

    private Singleton2() {
        value1++;
        value2++;
    }

    public static Singleton2 getInstance2() {
        return singleton2;
    }
}

运行结果:为什么两次value的值不一样?

0	0	null
count = 10
Singleton value1:5
Singleton value2:3
0	0	null
count = 20
Singleton2 value1:6
Singleton2 value2:4

2.2.2 为什么有上面的结果?

Java类加载分为5个过程,分别为:加载,连接(验证,准备,解析),初始化,使用,卸载。(加链初使卸)

(1)加载

1)通过classloader在classpath中获取XXX.class文件,将其以二进制流的形式读入内存。

2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构

3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

(2)链接 

① 验证:主要确保加载进来的字节流符合JVM规范。验证阶段会完成以下4个阶段的检验动作:

1)文件格式验证

2)元数据验证(是否符合Java语言规范)

3)字节码验证(确定程序语义合法,符合逻辑)

5)符号引用验证(确保下一步的解析能正常执行)

② 准备:主要为静态变量在方法区分配内存,并设置默认初始值。

③ 解析:主要是虚拟机将常量池内的符号引用替换为直接引用的过程。

(3)初始化:初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值(赋值顺序参考上面2.1)。

(4)使用:程序之间的相互调用。

(5)卸载:即销毁一个对象,一般情况下中有JVM垃圾回收器完成。代码层面的销毁只是将引用置为null。

 

è¿éåå¾çæè¿°

通过上面的整体介绍后,再来看Singleton2.getInstance()的执行分析:
1)类的加载。运行Singleton2.getInstance(),JVM在首次并没有发现Singleton类的相关信息。所以通过classloader将Singleton.class文件加载到内存中。
2)类的验证。略
3)类的准备。将Singleton2中的静态资源转化到方法区。value1,value2,singleton在方法区被声明分别初始为0,0,null。
4)类的解析。略(将常量池内的符号引用替换为直接引用的过程)
5)类的初始化。执行静态属性的赋值操作。按照顺序先是value1 = 5,value2 = 3,接下来是private static Singleton2 singleton2 = new Singleton2();
这是个创建对象操作,根据 结论1 在执行Singleton2的构造方法之前,先去执行static资源和非static资源。但由于value1,value2已经被初始化过,所以接下来执行的是非static的资源,最后是Singleton2的构造方法:value1++;value2++。
所以Singleton2结果是6和4。

结论2:静态资源在类的初始化中只会执行一次。不要与第3个步骤混淆。

有了以上的这个结论,再来看Singleton.getInstance()的执行分析:
1)类的加载。将Singleton类加载到内存中。
2)类的验证。略
3)类的准备。将Singleton2的静态资源转化到方法区。
4)类的解析。略(将常量池内的符号引用替换为直接引用的过程)
5)类的初始化。执行静态属性的赋值操作。按照顺序先是private static Singleton singleton = new Singleton(),根据 结论1结论2,value1和value2不会在此层执行赋值操作。所以singleton对象中的value1,value2只是在0的基础上进行了++操作。此时singleton对象中的value1=1,value2=1。
然后, public static int value1 = 5; public static int value2 = 3; 这两行代码才是真的执行了赋值操作。所以最后的结果:5和3。
如果执行的是public static int value1; public static int value2;结果又会是多少?结果: 1和1。要理解初始化和赋值区别

注:为什么 Singleton singleton = new Singleton()不会对value1,value2进行赋值操作?因为static变量的赋值在类的初始化中只会做一次。
程序在执行private static Singleton singleton = new Singleton()时,已经是对Singleton类的static变量进行赋值操作了。这里new Singleton()是一个特殊的赋值

结论3:非静态资源会随对象的创建而执行初始化。每创建一个对象,执行一次初始化。

3、try-catch-finally块执行顺序

3.1 try/catch/finally 中没有return

package cn.wyu.java;

/**
 * @author linwillen
 * @create 2020-05-01-23:41
 */
public class TryCatchFinally {
    public static void main(String[] args) {
        System.out.println("method:x = "+method());//step:1     //step:8  x = 10
    }

    public static int method(){
        int x = 0;//step:2  x = 0
        try {
            x = 1;//step:3  x = 1
            System.out.println("try:x = "+x);//step:4  x = 1
        } catch (Exception e) {
            x = 2;
            System.out.println("catch:x = "+x);
        } finally {
            x = 10;//step:5  x = 10
            System.out.println("finally:x = "+x);//step:6  x = 10
        }
        return x;//step:7  x = 10
    }
}

运行结果:

try:x = 1
finally:x = 10
method:x = 10

总结:按顺序执行,先执行try块,在没有异常的情况下,接下来执行finally块,随后return

3.2 try/catch 中有return

package cn.wyu.java;

/**
 * @author linwillen
 * @create 2020-05-01-23:41
 */
public class TryCatchFinally {
    public static void main(String[] args) {
        System.out.println("method:x = "+method());//step:1     //step:9  x = 1
    }

    public static int method(){
        int x = 0;//step:2  x = 0
        try {
            x = 1;//step:3  x = 1
            System.out.println("try:x = "+x);//step:4  x = 1
            return x;//step:5  x = 1    //step:8  x = 1
        } catch (Exception e) {
            x = 2;
            System.out.println("catch:x = "+x);
            return x;
        } finally {
            x = 10;//step:6  x = 10
            System.out.println("finally:x = "+x);//step:7  x = 10
        }

    }
}

运行结果:

try:x = 1
finally:x = 10
method:x = 1

总结:代码先执行try块,再执行finally块。finally块总会执行。try语句中的return返回的引用变量并不是try语句外定义的引用变量x,而是系统重新定义了一个局部引用x1,这个引用指向了引用x对应的值,也就是1,即使在finally语句中把引用x指向了值10,但是return已经不是x,而是x1,所以引用x的值和try语句中的返回值无关了

3.3 try/catch/finally 中有return

package cn.wyu.java;

/**
 * @author linwillen
 * @create 2020-05-01-23:41
 */
public class TryCatchFinally {
    public static void main(String[] args) {
        System.out.println("method:x = "+method());//step:1     //step:9  x = 10
    }

    public static int method(){
        int x = 0;//step:2  x = 0
        try {
            x = 1;//step:3  x = 1
            System.out.println("try:x = "+x);//step:4  x = 1
            return x;//step:5  x = 1    
        } catch (Exception e) {
            x = 2;
            System.out.println("catch:x = "+x);
            return x;
        } finally {
            x = 10;//step:6  x = 10
            System.out.println("finally:x = "+x);//step:7  x = 10
            return x;//step:8  x = 10
        }

    }
}

运行结果:

try:x = 1
finally:x = 10
method:x = 10

总结:执行finally块中的return语句,JVM忽略了try块中的return语句。

3.4 try块有异常

package cn.wyu.java;

/**
 * @author linwillen
 * @create 2020-05-01-23:41
 */
public class TryCatchFinally {
    public static void main(String[] args) {
        System.out.println("method:x = "+method());//step:1     //step:12  x = 10
    }

    public static int method(){
        int x = 0;//step:2  x = 0
        try {
            x = 1;//step:3  x = 1
            Integer.parseInt(null);//step:4
            System.out.println("try:x = "+x);
            return x;//step:5  x = 1
        } catch (Exception e) {
            x = 2;//step:6  x = 2
            System.out.println("catch:x = "+x);//step:7  x = 2
            return x;//step:8  x = 2
        } finally {
            x = 10;//step:9  x = 10
            System.out.println("finally:x = "+x);//step:10  x = 10
            return x;//step:11  x = 10
        }

    }
}

运行结果:

catch:x = 2
finally:x = 10
method:x = 10

总结:执行finally块中的return语句。finally中含return语句,导致try/catch中的异常信息被忽略,而我们最初使用try/catch就是为了捕获异常信息,所以这种情况与之产生矛盾,因此编译器会有警告提示

3.5 try/catch/finally 均有异常

package cn.wyu.java;

/**
 * @author linwillen
 * @create 2020-05-01-23:41
 */
public class TryCatchFinally {
    public static void main(String[] args) {
        System.out.println("method:x = "+method());//step:1     //step:12  x = 10
    }

    public static int method(){
        int x = 0;//step:2  x = 0
        try {
            x = 1;//step:3  x = 1
            Integer.parseInt(null);//step:4
            System.out.println("try:x = "+x);
            return x;
        } catch (Exception e) {
            x = 2;//step:5  x = 2
            String.valueOf(null);  //step 6
            System.out.println("catch:x = "+x);//step:7  x = 2
            return x;
        } finally {
            x = 10;//step:7  x = 10
            int y = x / 0;//step:8
            System.out.println("finally:x = "+x);
        }

    }
}

运行结果:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at cn.wyu.java.TryCatchFinally.method(TryCatchFinally.java:26)
	at cn.wyu.java.TryCatchFinally.main(TryCatchFinally.java:9)

总结:如果finally发生异常,那么try/catch中的异常信息就会被忽略,达不到异常信息处理的目的

综上所述:

  1. finally语句总会执行。
  2. 如果try/catch中有return语句,finally中没有return语句,那么finally中修改数据(除集合类、静态变量、全局变量)对try/catch中返回的变量没有任何影响,返回的是try/catch中的变量的值。
  3. 在finally中使用return,会忽略try/catch中的返回语句,也会忽略try/catch中的异常,返回的是finally中的变量的值。
  4. finally中如果发生异常,代码执行将会抛出finally中的异常信息。

你可能感兴趣的:(JavaSE)