美文网首页Java 杂谈
Java深入解析(一些问题)

Java深入解析(一些问题)

作者: 从菜鸟到老菜鸟 | 来源:发表于2019-02-02 17:17 被阅读2次

    1. Java标识符

      1、标识符由字母、数字、货币符号(¥、$等)、连接符号(_等)组成。(这里的字母为Unicode字符集,而不再局限于传统的26个英文字母。)
      2、标识符的首字符可以是字母、货币符号与连接符号,但不能是数字。
      3、标识符不能与Java中的关键字相同。
      4、标识符不能和Java中预定义的字面常量名称相同(true、false、null)。
      <font color="red">注意:尽量不使用$。</font>
      因为“$”被编译器使用,在源文件(.java文件)编译成字节码(.class文件)后,会成为顶层类型与嵌套类型之间的连接符。
      例如,存在一个顶层类A,在其内声明一个成员类B,那么编译之后,就会产生两个class文件,分别为A.class与A$B.class。

    2. 浮点数的表示

      任意一个非0并且非无穷大的浮点数V都可以表示成:v=s\times m\times 2^e,s为1或者-1,m为有效位数(小数),e为指数。

      注意指数和有效位数都是无符号的,但是指数确实需要表示正负,因此,指数采用偏移方式来存储数值,偏移量为2^x-1(比实际指数大2^x-1),其中x为指数位数。

      例如,float类型值8.1f的指数为3,在指数中实际存储的值为127+3,即130(1000 0010)。

    3. 方法的重载、重写与隐藏

      1、<font color="red">方法的重写要求子类与父类都是实例方法,而两个静态方法之间为隐藏,静态方法不能被重写。</font>
      2、静态方法和成员变量都只能隐藏,重写和隐藏的本质区别是:重写是动态绑定的,根据运行时引用所指对象的实际类型来决定调用相关类的成员。而隐藏是静态绑定的,根据编译时引用的静态类型来决定调用相关类的成员。
      3、<font color="red">同一个类中的方法重载也是静态绑定的。</font>
      4、对于继承的实例变量,如果子类没有隐藏父类的变量,则变量在父类与子类之间是共享的,其实对于同一个对象而言,就是同一个变量,静态变量也一样,继承而来的变量为父类及其所有的子类所共享。
    1、重载实例

    /**
     * @Description: 方法的重载是静态绑定,即在编译时期就已经确定了需要调用的方法。      方法重写需要运行时才能确定
     */
    public class OverLoadTest {
        public static void main(String[] args) {
            OverLoadTest ot = new OverLoadTest();
            
            Object obj = new String();
            // 虽然obj实际引用的是String类型的对象,但是由于重载在编译期确定,所以实际调用的还是obj类型对应的方法
            ot.fun(obj);
        }
        
        public void fun(String str) {
            System.out.println("fun(String str)!!!");
        }
        
        public void fun(Object obj) {
            System.out.println("fun(Object obj)!!!");
        }
    }
    

    2、重写与隐藏实例

    class SuperOverrideAndHidden {
        String name = "SuperOverrideAndHidden";
        
        public void mInstance() {
            System.out.println("mInstance() in SuperOverrideAndHidden");
        }
        
        public static void mStatic() {
            System.out.println("mStatic() in SuperOverrideAndHidden");
        }
    }
    
    /**
     * @Description: 只有实例方法之间才存在重写,静态方法以及成员变量之间都是隐藏关系(从Eclipse前面提示的小三角形也可以看出来)
     */
    public class SubOverrideAndHidden extends SuperOverrideAndHidden {
        String name = "SubOverrideAndHidden";
        
        @Override
        public void mInstance() {
            System.out.println("mInstance() in SubOverrideAndHidden");
        }
        
        // @Override注解加到这个方法上面会提示错误
        public static void mStatic() {
            System.out.println("mStatic() in SubOverrideAndHidden");
        }
        
        @SuppressWarnings("static-access")
        public static void main(String[] args) {
            SuperOverrideAndHidden superoah = new SuperOverrideAndHidden();
            SubOverrideAndHidden suboah = new SubOverrideAndHidden();
            
            System.out.println("父类SuperOverrideAndHidden的表现:");
            System.out.println(superoah.name);
            superoah.mInstance();
            superoah.mStatic();
            
            System.out.println("子类SubOverrideAndHidden的表现:");
            System.out.println(suboah.name);
            suboah.mInstance();
            suboah.mStatic();
            
            // 由运行结果可以知道重写在运行时由变量具体引用的类型决定,隐藏在编译期决定,由变量的类型决定
            superoah = suboah;
            System.out.println("父类的引用指向子类的引用之后,重载和隐藏的表现:");
            System.out.println(superoah.name);
            // 调用实例方法的类型是 SubOverrideAndHidden 类的实例
            superoah.mInstance();
            // superoah变量是SuperOverrideAndHidden类的变量
            superoah.mStatic();
        }
    }
    

    3、类型的嵌套隐藏

    package com.zxt.test;
    
    public class ClassHidden extends SuperClassHidden {
    
        public class ClassHidden2 { }
        
        public static void main(String[] args) {
            /**
             * 如果子类A继承父类B(或者实现接口),而父类中含有一个嵌套类型和子类同名(B类中嵌套类型A),则是允许的
             * 这时候,从父类继承的嵌套类型A就会隐藏子类的类型A。
             * 但是如果父类的嵌套类型不与子类同名(假设为C),而子类也同时声明了一个嵌套类型C,则子类又会隐藏父类中的嵌套类型C
             */
            // class com.zxt.test.SuperClassHidden$ClassHidden
            System.out.println(ClassHidden.class);
            // class com.zxt.test.ClassHidden$ClassHidden2
            System.out.println(ClassHidden2.class);
            
        }
    
    }
    
    class SuperClassHidden {
        public class ClassHidden { }
        
        public class ClassHidden2 { }
    }
    

    4. Java对象的创建与初始化

      <font color="red">Java中对象的创建是由new运算符完成的,并非构造器,</font>构造器的作用是在对象创建的时候进行类中实例化成员的初始化,而绝非创建对象,构造器也没有返回值。
      虚拟机运行程序的过程是:先创建对象,然后求解构造器形参表达式的值(如果形参表达式的求值出现错误,则构造器也不会调用),最后调用构造器对对象进行初始化。
      而实际上,在Java类初始化时,如果构造器内没有调用本类中其他的构造器,则所有的实例变量声明处初始化与实例初始化块都会被复制到构造器的最前面(这也就是为什么实例初始化块总是在构造器之前执行),而所有的静态变量声明处初始化以及静态初始化块,也可以看作是复制到一个静态初始化块中。
      由于这种复制的初始化工作,因此在实例初始化块中是不能使用return语句的,静态初始化块中也不能使用。此外静态初始化块不允许抛出任何受检查的异常。
      更加准确的说,JVM在进行初始化工作时,调用的是<init>与<clinit>方法,<init>是实例初始化,对非静态变量解析初始化;而<clinit>是class类构造器对静态变量,静态代码块进行初始化。
    1、实例1

    public class TestMain {
        static {
            System.out.println("static block1");
        }
        
        public static void main(String[] args) {
            // 在执行main方法之前会首先加载这个类,所以即使main方法中没有语句,静态代码块还是会执行
        }
        
        static {
            System.out.println("static block2");
        }
    }
    

    2、在实现继承的类被new的过程中,初始化执行顺序如下:实现父类的公共静态属性和静态块级代码。实现自身的静态属性和静态块级代码。实现父类的非静态属性和非静态代码块。执行父类的构造函数。实现自身的非静态属性和非静态代码块。执行自身的构造函数。

    public class Test1 extends Base {
        static {
            System.out.println("test static");
        }
    
        public Test1() {
            System.out.println("test constructor");
            super.a(); // super语句建议放在构造器的最前面
            System.out.println(super.aa);
        }
    
        public static void main(String[] args) {
            new Test1();
            
        }
    }
    
    class Base {
        public int aa = 0;
        public int a() {
            return 1;
        }
        static {
            System.out.println("base static");
        }
    
        public Base() {
            System.out.println("base constructor");
        }
    }
    
    public class Test2 {
        Person person = new Person("Test");
        static {
            System.out.println("test static");
        }
    
        public Test2() {
            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 Test2 {
        Person person = new Person("MyClass");
        static {
            System.out.println("myclass static");
        }
    
        public MyClass() {
            System.out.println("myclass constructor");
        }
    }
    

    3、静态的final变量在编译期就能确定

    // 静态的final变量在编译期就能确定
    public class Test3 {
        // 由于类只加载一次,所以看效果时只能执行一句
        public static void main(String[] args) {
            /**
             * 只输出classB
             * 但是当str没有final修饰时,会输出
             * A
             * B
             * classB
             */
            // System.out.println(B.str);
            
            /**
             * 输出
             * A
             * C
             * classC
             */
            // System.out.println(C.str);
            
            /**
             * 均输出
             * A
             * D
             * 100
             */
            // System.out.println(D.bb);
            System.out.println(D.aa);
            
            /**
             * 只输出200
             */
            // System.out.println(D.cc);
        }
    }
    
    class A {
        static {
            System.out.println("A");
        }
    }
    
    class B extends A {
        static {
            System.out.println("B");
        }
        
        public static final String str = "calssB";
    }
    
    class C extends A {
        static {
            System.out.println("C");
        }
        
        public static final String str = new String("classC");
    }
    
    class D extends A {
        static {
            System.out.println("D");
        }
        
        public static final int cc = 200;
        public static final Integer aa = 100;
        public static final Integer bb = new Integer(100);
    }
    

    4、被动使用:当访问的成员是通过父类(接口)继承而来的,就是被动使用。
      主动使用:当访问的成员是类(接口)中声明的成员时,就是主动使用。
      当类或接口调用某个成员时,只有当主动使用该成员时,才会初始化类或接口,如果仅仅是被动使用该成员,则不会初始化类或接口。

    /**
     * 
     * @Description: 当我们通过SubClass类来访问value变量时,是SubClass类对该变量的被动使用,因为value并不是在SubClass类中声明的,而是在SuperClass中声明的。因此仅父类SuperClass会得到初始化,而SubClass类不会
     * 
     */
    public class 被动使用与初始化  {
        public static void main(String[] args) {
            System.out.println(SubClass.value);
        }
    }
    
    class SuperClass {
        public static int value = 20;
        
        static {
            System.out.println("SuperClass类初始化....");
        }
    }
    
    class SubClass extends SuperClass {
        static {
            System.out.println("SubClass类初始化....");
        }
    }
    

    5. this的由来

      this从何而来?实际上是这样的,在构造器或者实例方法中,都会含有一个隐藏的参数,这个参数就是类的对象,当调用构造器或者实例方法的时候,就会将这个参数作为第一个参数传递进去。

    6. 编译器的贪心规则

    /**
     * @Description: 1、编译器在分析字符时,会尽可能多地结合有效字符。并且“过度”得贪心,而不管这种结合方式是不是符合语法规则
     *              2、贪心规则是有用的,因为这样可以对转义字符等进行特殊处理
     */
    public class 编译器贪心规则 {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            int num1 = 10;
            int num2 = 20;
            // 编译器会贪心结合++是一个有效的运算符,所以先++,然后再+
            int result = num1+++num2;
            System.out.println("num1 = " + num1); // 11
            System.out.println("num2 = " + num2); // 20
            System.out.println("result = " + result); // 31
            
            /**
             * num1 -- num2会出错,因为编译器会理解为(num1--)num2,并不是有效的表达式
             * num1 -- num2由于贪心规则,它并不会理解为num1 - (-num2),尽管这样理解才是有效的表达式
             */
            // num1 -- num2;
            
            /**
             * "\17"、"\171"都是一个有效的转义字符,所以长度都为1,"\1717"会识别出\171和7
             */
            String str1 = "\17";
            System.out.println(str1);
            System.out.println("长度:" + str1.length());
            String str2 = "\171";
            System.out.println(str2);
            System.out.println("长度:" + str2.length());
            String str3 = "\1717";
            System.out.println(str3);
            System.out.println("长度:" + str3.length());
            String str4 = "\17" + "17";
            System.out.println(str4);
            System.out.println("长度:" + str4.length());
        }
    }
    


    7. 运算顺序的确定

    /**
     * @Description: Java表达式是从左到右进行计算的,操作数从左到右的计算规则与运算符的结合性无关。
     */
    public class 运算顺序 {
    
        public static void main(String[] args) {
            int[] a = {0, 0, 0, 0, 0, 0};
            int i = 1;
            // 首先会确定左侧操作数为a[2]
            a[++i] = i++;
            System.out.println("i == " + i);
            System.out.println("array a:" + Arrays.toString(a));
            
            int j = 3;
            // 这里则首先会确定要操作的时a[3]
            a[j] = j = 4;
            System.out.println("j == " + j);
            System.out.println("array a:" + Arrays.toString(a));
            
            int[] b = {9, 9, 9, 9, 9, 9};
            int[] c = a;
            int k = 5;
            // 虽然a的指向在右侧改变了,但是这里左侧操作的还是原来的a数组,因为操作数从左到右确定,编译器会将左侧的操作数保存起来,因此不管右侧是否对他进行了修改
            a[--k] = (a = b)[k];
            System.out.println("k == " + k);
            System.out.println("array a:" + Arrays.toString(a));
            System.out.println("array b:" + Arrays.toString(b));
            System.out.println("array c:" + Arrays.toString(c));
            
            // 作为函数的参数,依旧符合从左到右确定的规则
            int num = 5;
            test(num, ++num, num = 2);
            test(num = 10, num++, num);
        }
        
        public static void test(int a, int b, int c) {
            System.out.println(a);
            System.out.println(b);
            System.out.println(c);
        }
    
    }
    

    相关文章

      网友评论

        本文标题:Java深入解析(一些问题)

        本文链接:https://www.haomeiwen.com/subject/kxossqtx.html