zoukankan      html  css  js  c++  java
  • Java隐蔽问题

    1、基本类型与引用类型的比较

    1.1、如下四个变量,哪两个比较为 false

    Integer i01 = 59;
    int i02 = 59;
    Integer i03 =Integer.valueOf(59);
    Integer i04 = new Integer(59);

    (1)Integer 为了节省空间和内存会在内存中缓存 -128~127 之间的数字;
    (2)valueOf():调用该方法时,内部实现作了个判断,判断当前传入的值是否在-128~127之间且 IntergCache是否已存在该对象如果存在,则直接返回引用,如果不存在,则创建一个新对象
    (3)基本类型存在内存的栈中,与引用类型比较时, 引用类型会自动装箱,比较数值而不比较内存地址;

    Integer a=123;
    Integer b=123;
    System.out.println(a==b);        // 输出 true
    System.out.println(a.equals(b));  // 输出 true
    a=1230;
    b=1230;
    System.out.println(a==b);        // 输出 false
    System.out.println(a.equals(b));  // 输出 true

    1.2、自动装箱拆箱机制是编译特性还是虚拟机运行时特性?分别是怎么实现的?
    自动装箱机制是编译时自动完成替换的。装箱阶段自动替换为了 valueOf 方法,拆箱阶段自动替换为了 xxxValue 方法;
    对于 Integer 类型的 valueOf 方法参数如果是 -128~127 之间的值会直接返回内部缓存池中已经存在对象的引用,参数是其他范围值则返回新建对象;
    而 Double 类型与 Integer 类型类似,一样会调用 Double 的 valueOf 方法,但是 Double 的区别在于不管传入的参数值是多少都会 new 一个对象来表达该数值(因为在指定范围内浮点型数据个数是不确定的,整型等个数是确定的,所以可以Cache)
    注意:Integer、Short、Byte、Character、Long 的 valueOf 方法实现类似,而 Double 和 Float 比较特殊,每次返回新包装对象,对于两边都是包装类型的:== 比较的是引用,equals 比较的是值;对于两边有一边是表达式(包含算数运算): == 比较的是数值(自动触发拆箱过程),对于包装类型 equals 方法不会进行类型转换;
    1.3.Integer i = 1; i += 1; 做了哪些操作

    • Integer i = 1; 做了自动装箱:使用 valueOf() 方法将 int 装箱为 Integer 类型
    • i += 1; 先将 Integer 类型的 i 自动拆箱成 int(使用 intValue() 方法将 Integer 拆箱为
      int),完成加法运行之后的 i 再装箱成 Integer 类型

    2、关于String +和StringBuffer的比较

    在 String+写成一个表达式的时候(更准确的说,是写成一个赋值语句的时候)效率其实比 Stringbuffer更快

    public class Main{  
    	public static void main(String[] args){ 
    		String string = "a" + "b" + "c";
    		StringBuffer stringBuffer = new StringBuffer();
    		stringBuffer.append("a").append("b").append("c");
    		string = stringBuffer.toString();
    	}
    }

    2.1、String+的写法要比 Stringbuffer 快,是因为在编译这段程序的时候,编译器会进行常量优化。
    它会将a、b、c直接合成一个常量abc保存在对应的 class 文件当中{},看如下反编译的代码:

    public class Main{}
    	public static void main(String[] args){
    	      String string = "abc";
    	      StringBuffer stringBuffer = new StringBuffer();
    	      stringBuffer.append("a").append("b").append("c");
    	      string = stringBuffer.toString();
    	}
    }

    原因是因为 String+其实是由 Stringbuilder 完成的,而一般情况下 Stringbuilder 要快于 Stringbuffer,这是因为 Stringbuilder 线程不安全,少了很多线程锁的时间开销,因此这里依然是 string+的写法速度更快;

    /*   1   */
    String a = "a";
    String b = "b";
    String c = "c";
    String string = a + b + c;
    /*   2   */
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer.append(a);
    stringBuffer.append(b);
    stringBuffer.append(c);
    string = stringBuffer.toString();

    下面我们举个例子:

     public static void main(String[] args)
      {
        String a = "a";
        String b = "b";
        String c = "c";
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
             String string = a + b + c;
             if (string.equals("abc")) {}
        }
        System.out.println("string+ cost time:" + (System.currentTimeMillis() - start) + "ms");
        start = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append(a);
            stringBuffer.append(b);
            stringBuffer.append(c);
            String string = stringBuffer.toString();
            if (string.equals("abc")) {}
        }
        System.out.println("stringbuffer cost time:" + (System.currentTimeMillis() - start) + "ms");
      }

    我们每个进行了1亿次,我们会看到string+竟然真的快于stringbuffer,是不是瞬间被毁了三观,我们来看下结果。

    2.2、字符串拼接方式:+、concat() 以及 append() 方法,append()速度最快,concat()次之,+最慢

    • 编译器对+进行了优化,它是使用 StringBuilder 的 append() 方法来进行处理的,编译器使用 append()方法追加后要同 toString() 转换成 String 字符串,变慢的关键原因就在于new StringBuilder()和toString(),这里可是创建了 10 W 个 StringBuilder 对象,而且每次还需要将其转换成 Stringconcat:
    • concat() 的源码,它看上去就是一个数字拷贝形式,我们知道数组的处理速度是非常快的,但是由于该方法最后是这样的: return new String(0, count + otherLen, buf);这同样也创建了 10 W个字符串对象,这是它变慢的根本原因
    • append() 方法拼接字符串:并没有产生新的字符串对象;

    3、静态代码块、静态变量

    其作用级别为类;构造代码块、构造函数、构造,其作用级别为对象
    (1)静态代码块,它是随着类的加载而被执行,只要类被加载了就会执行,而且只会加载一次,主要用于给类进行初始化。
    (2)构造代码块,每创建一个对象时就会执行一次,且优先于构造函数,主要用于初始化不同对象共性的初始化内容和初始化实例环境。
    (3)构造函数,每创建一个对象时就会执行一次;同时构造函数是给特定对象进行初始化,而构造代码是给所有对象进行初始化,作用区域不同;
    ==> 通过上面的分析,他们三者的执行顺序应该为:静态代码块 > 构造代码块 > 构造函数。
    3.1、Java 类初始化过程

    • 首先,初始化父类中的静态成员变量和静态代码块,按照在程序中出现的顺序初始化;
    • 然后,初始化子类中的静态成员变量和静态代码块,按照在程序中出现的顺序初始化;
    • 其次,初始化父类的普通成员变量和代码块,在执行父类的构造方法;
    • 最后,初始化子类的普通成员变量和代码块,在执行子类的构造方法;

    3.2、不要在构造器里调用可能被重载的虚方法
    父类构造器执行的时候,调用了子类的重载方法,然而子类的类字段还在刚初始化的阶段,刚完成内存布局:

    public class Base {
          private String baseName = "base";
          public Base(){
             callName();
          }
          public void callName(){
             System. out. println(baseName);
          }
          static class Sub extends Base{
             private String baseName = "sub";
             @Override
             public void callName(){
                System. out. println (baseName) ;
             }
          }
          public static void main(String[] args){
             Base b = new Sub();
          }
    
    }

    3.3、Java 中赋值顺序
    (1)父类的静态变量赋值
    (2)自身的静态变量赋值
    (3)父类成员变量赋值
    (4)父类块赋值
    (5)父类构造函数赋值
    (6)自身成员变量赋值
    (7)自身块赋值
    (8)自身构造函数赋值
    3.4、Java 代码执行顺序

    public class TestExecuteCode {
       public static void main(String[] args) {
          System.out.println(new B().getValue());
       }
       static class A {
          protected int value;
          public A(int v){
             setValue(v);
          }
          public void setValue(int value) {
             this.value = value;
          }
          public int getValue() {
             try {
                value++;
                return value;
             } finally {
                this.setValue(value);
                System.out.println(value);
             }
          }
       }
       static class B extends A {
          public B(){
             super(5);
             setValue(getValue() - 3);
          }
          @Override
          public void setValue(int value){
             super.setValue(2 * value);
          }
       }
    }
    • 执行结果:22,34,17
      (1)子类 B 中重写了父类 A 中的setValue方法:super(5) // 调用了父类构造器,其中构造函数里面的setValue(value),调用的是子类的setValue方法finally块中的:this.setValue(value) //调用的也是子类的setValue方法而子类setValue方法中的:super.setValue(2*value); //调用的是父类A的setValue方法。
      (2)try…catch…finally块中有return返回值的情况:finally 块中虽然改变了value的值,但try块中返回的应该是 return 之前存储的值
    • 父类执行时如果有子类的方法重写了父类的方法,调用的子类的重写方法
      4、给出一个表达式计算其可以按多少进制计算
      式子7*15=133成立,则用的是几进制?可以通过解方程来解决,上述式子可以转换为方程:
    7 * (1 * x + 5) = 1 * x^2 + 3 * x + 3
    x^2 -4x - 32 = 0
    x = -4 或 x = 8

    如果下列的公式成立:78+78=123,则采用的是___进制表示的:

    7 * x + 8 + 7 * x + 8 = 1 * x^2 + 2 * x + 3
    x^2 - 12 * x - 13 = 0
    x = -1, x = 13

    5、表达式的数据类型

    5.1、基本类型中类型转换

    • 所有的 byte,short,char 型的值将被提升为 int 型;
    • 如果有一个操作数是 long 型,计算结果是 long 型;
    • 如果有一个操作数是 float 型,计算结果是 float 型;
    • 如果有一个操作数是 double 型,计算结果是 double 型;
    • final 修饰的变量是常量,如果运算时直接是已常量值进行计算,没有final修饰的变量相加后会被自动提升为int型
     byte b1=1,b2=2,b3,b6;
    final byte b4=4,b5=6;
    b6=b4+b5;// b4, b5是常量,则在计算时直接按原值计算,不提升为int型
    b3=(b1+b2);// 编译错误
    System.out.println(b3+b6);

    记住一点:JDK中关于任何整型类型的运算,都是按照int来的

    private static final long mil_seconds = 24 * 60 * 60 * 1000;
    private static final long micro_seconds = 24 * 60 * 60 * 1000 * 1000;
    public static void main(String[] args) {
     System.out.println(micro_seconds / mil_seconds);
    }

    上面代码中 micro_seconds 在运算时,其已超过 int 类型的最大值,溢出了。另外,如果在基本类型与对应的包装类型进行比较或者运算的时候,都会将包装类型自动拆箱,例如下面的代码:
    5.2、三目运算中类型转换问题
    在使用三目运算符时,尽量保证两个返回值的类型一致,不然会触发类型转换,转换规则如下:
    (1)如果返回值X和返回值Y是同种类型,那么返回类型毫无疑问就是这种类型;
    (2)如果两个返回值X和Y的类型不同,那么返回值类型为他们两最接近的父类。举例:

    // String 和 Boolean 都实现了 Serializable 接口
    Serializable serializable = a == b ? "true" : Boolean.FALSE;
    // 所有类都继承了 Object 类
    Object o = a == b ? new ArrayList<>() : new TernaryOperatorDemo();

    (3)对于基本数据类型,如果其中一个返回值X类型为byte、short或者char,另一个返回值Y类型为int:

    • 若在编译期就能判断出Y的取值范围在X的取值范围之内,则返回类型为X的类型,反之则为Y的类型。
    • 如果返回值X类型不为以上几种,则会触发隐藏类型转换;
      (4)当基本数据类型和对象数据类型相遇时,三目运算默认返回结果为基本数据类型;
      例子:
    private static void test1(int a, int b) {
        // 触发隐藏类型转换,int 类型 9 转为 9.0D
        System.out.println(a == b ? 9.9 : 9);
        // 编译期判断,98 在 char 之内,转为 b
        System.out.println(a == b ? 'a' : 98);
        // 编译期判断,超出char范围,统一转 int
        System.out.println(a == b ? 'a' : Integer.MAX_VALUE);
        // 编译期时无法判断 b 的取值,触发隐藏类型转换,统一转 int
        System.out.println(a == b ? 'a' : b);
        System.out.println(a != b ? 'a' : b);
        Map<String, Long> map = new HashMap<>();
        map.put("b", 1L);
        // 基本数据类型和对象数据类型相遇时,默认转为基本数据类,
        // map.get("a") 返回 null,转为基本数据类型时,报空指针异常
        System.out.println(map == null ? -1L : map.get("a"));
      }

    6、按照目录结构打印当前目录及子目录

    public class PrintDirectory {
       public static void main(String[] args) {
          File file = new File("E:\下载");
          PrintDirectory pd = new PrintDirectory();
          pd.listDirectory(file,0);
       }
       //列出该目录的子目录
       private void listDirectory(File dir,int level){
          System.out.println(getSpace(level) + dir.getName());
          level++;
          File[] files = dir.listFiles();
          for(int i=0;i<files.length;i++){
             if(files[i].isDirectory()){
                listDirectory(files[i],level);
             }else{
                System.out.println(getSpace(level)+files[i].getName());
             }
          }
       }
       //按照目录结构打印目录
       private String getSpace(int level){
          StringBuilder sb = new StringBuilder();
          for(int i=0;i<level;i++){
             sb.append("|--");
          }
          return sb.toString();
       }
    }
    

    7、boolean占用字节数

    • 在Java虚拟机中没有任何供 boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替。
    • Java虚拟机直接支持 boolean类型的数组,虚拟机的navarra指令参见newarray小节可以创建这种数组。boolean类型数组的访问与修改共用byte类型数组的baload和bastore指令;
    • 因为在虚拟机规范中说了,boolean值在编译之后都使用Java虚拟机中的int数据类型来代替,而int是4个字节,那么boolean值就是4个字节。
    • boolean类型数组的访问与修改共用byte类型数组的baload和bastore指令,因为两者共用,只有两者字节一样才能通用呀,所以byte数组中一个byte是1个字节,那么boolean数组中boolean是1个字节。
      总结:boolean在数组情况下为1个字节,单个boolean为4个字节
      Java规范中,没有明确指出boolean的大小。在《Java虚拟机规范》给出了单个boolean占4个字节,和boolean数组1个字节的定义,具体 还要看虚拟机实现是否按照规范来,所以1个字节、4个字节都是有可能的。
    class LotsOfBooleans{
        boolean a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af;
        boolean b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf;
        boolean c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf;
        boolean d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, da, db, dc, dd, de, df;
        boolean e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, ea, eb, ec, ed, ee, ef;
    }
    class LotsOfInts{
        int a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af;
        int b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf;
        int c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf;
        int d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, da, db, dc, dd, de, df;
        int e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, ea, eb, ec, ed, ee, ef;
    }
    public class Test{
        private static final int SIZE = 100000;
        public static void main(String[] args) throws Exception{
            LotsOfBooleans[] first = new LotsOfBooleans[SIZE];
            LotsOfInts[] second = new LotsOfInts[SIZE];
            System.gc();
            long startMem = getMemory();
            for (int i=0; i < SIZE; i++) {
                first[i] = new LotsOfBooleans();
            }
             System.gc();
            long endMem = getMemory();
    
    	System.out.println ("Size for LotsOfBooleans: " + (endMem-startMem));
            System.out.println ("Average size: " + ((endMem-startMem) / ((double)SIZE)));
            System.gc();
            startMem = getMemory();
            for (int i=0; i < SIZE; i++) {
                second[i] = new LotsOfInts();
            }
            System.gc();
            endMem = getMemory();
            System.out.println ("Size for LotsOfInts: " + (endMem-startMem));
            System.out.println ("Average size: " + ((endMem-startMem) / ((double)SIZE)));
            // Make sure nothing gets collected
            long total = 0;
            for (int i=0; i < SIZE; i++) {
                total += (first[i].a0 ? 1 : 0) + second[i].a0;
            }
            System.out.println(total);
        }
        private static long getMemory(){
            Runtime runtime = Runtime.getRuntime();
            return runtime.totalMemory() - runtime.freeMemory();
        }
    }

    另外,大部分指令都没有支持整数类型byte、char、short。编译器在编译期或运行期将byte和short类型的数据带符号扩展为相应的int类型数据,将boolean和char类型数据零位扩展为相应的int类型数据;

    别废话,拿你代码给我看。
  • 相关阅读:
    Hibernate
    Mysql
    JavaWeb
    Springboot
    spring MVC
    spring
    mybatis学习
    ftp客户端封装
    win10子系统 wsl开机启动ssh服务
    eclipse 终于官方支持代码模糊提示了
  • 原文地址:https://www.cnblogs.com/lvxueyang/p/13707561.html
Copyright © 2011-2022 走看看