• java基础不牢固容易踩的坑


    java基础不牢固容易踩的坑

      经过一年java后端代码以及对jdk源码阅读之后的总结,对java中一些基础中的容易忽略的东西写下来,给偏爱技术热爱开源的Coder们分享一下,避免在写代码中误入雷区。

     (注:如无特殊说明,均以jdk8为基础,本文所有例子均已通过编译器通过,且对输出进行了验证)。

    1.关于基本类型的包装类的。

      基本类型boolean、char、byte、short、int、long、float、double。是java的特殊类型,特殊性在于区别于对象的存储,对象存储的是引用,引用指向在jvm堆中分配的值,基本类型直接存储的就是值,能提高效率。

      同时java遵循面向对象思想为每个基本类型都提供了封装类:Boolean、Character、Byte、Short、Integer、Float、Double。

    坑1:变量赋值与类型转换。

      变量赋值其实并不算是个坑,因为编译器会自动检查,例如long var = 2;编译器会报错。

      类型转换分为自动转换和强制转换,这里不在赘述,具体转换规则自行查询。

      赋值的时候 = 和+=的区别,+=会自动转换类型。

      short num;
      num = num + 1; //error
      num += 1; //ok

    坑2:计算

      整数相除默认只保留整数,即使赋值给浮点类型也不行。

      double d = 5 / 2;
      System.out.println(d); //2

      byte相加超出长度后数值会变得很怪异。

      byte num = 127;
      num += 1;
      System.out.println(num);//-128

      两个float相加结果会存在一定的误差等等。

      float a1 = 1.001f;
      float a2= 1.819f;
      float a3 = a1 + a2;
      System.out.println(a3);//jdk8: 2.8200002
      System.out.println(12.0 - 11.9 == 0.1) //false

    坑3:装箱与拆箱 

      int a =100;
      Integer b = 100;
      Integer c= 100;
      System.out.println(a==b); //true
      System.out.println(b==a); //true
      System.out.println(b==c); //true
    基本类型和包装类型运算时会自动拆箱,所以ab相等;
    当把100换成200 b==c会返回false。因为==比较的是引用;
    b==c为true的原因是Integer采用了缓存,对-128到127之间的数据不再自动生成,而是直接引用(请看Integer中的IntegerCache内部类),类似于String;

    2. null值

      1.null关键字,大小写敏感

      2.null是引用类型的默认值

      4.null既不是对象也不是类型,可以强制转换成任何引用类型。

        String s = (String) null; //ok

        int a = (int) null;  //error

      4.null值的引用类型变量,instance会返回false,如下:

      Integer iAmNull = null;
      System.out.println(iAmNull instanceof Integer); //false

      5.null值的引用变量调用非静态方法,会抛npe,调用静态方法是可以的。

    3 void,Void

      void在逻辑上是一种数据类型,但不是基本类型,也不是引用类型。我们暂且不管它到底是什么类型,因为很多人都说不清。

      void提供了包装类Void,看源码我们会发现它被定义成final,而且构造方法是private,也就是说不能实例化。

      Void类型只能赋值为null,而void不能赋值,仅仅用来作为方法返回值输出。

      Void能作为方法输入参数当做占位符,只能传值为null。

    4.多态

      1.父类引用能指向子类对象,调用的方法具体取决于引用的对象,而不是取决于引用。

      public class A {
        public String show(D obj){
          return ("A and D");
        }  
        public String show(A obj){
          return ("A and A");
        }
      }
      class B extends A{
        public String show(B obj)...{
          return ("B and B");
        }
        public String show(A obj){
          return ("B and A");
        }
      }
      class C extends B{}
      class D extends B{}

     A a1 = new A();  
      A a2 = new B();
    B b = new B(); C c = new C(); D d = new D(); System.out.println(a1.show(b)); ① System.out.println(a1.show(c)); ② System.out.println(a1.show(d)); ③ System.out.println(a2.show(b)); ④ System.out.println(a2.show(c)); ⑤ System.out.println(a2.show(d)); ⑥ System.out.println(b.show(b)); ⑦ System.out.println(b.show(c)); ⑧ System.out.println(b.show(d)); ⑨
      结果
      ①   A and A
      ②   A and A
      ③   A and D
      ④   B and A
      ⑤   B and A
      ⑥   A and D
      ⑦   B and B
      ⑧   B and B
      ⑨   A and D
      
      2.子类对父类方法不可见的情况下是不会覆盖的,而是重新定义了一个方法。
      3.继承关系只有方法会覆盖,成员变量不会被覆盖。  
      public class A {
       protected int i = 1;
      public void show(){
       System.out.println(i);
      }
      }
      public class B extends A{
          private int i = 10;
      public void show(){
       System.out.println(i);
      }
      }
      A a = new A();
      A a1 = new B();
      B b = new B();
      a.show(); //1
      a1.show(); //10
      b.show(); //10

    5. super

      super并没有代表超类的一个引用的能力,只是代表调用父类的方法而已。

      public class Test extends Number{
        public static void main(String[] args) {
          new Test().test();
        }
        private void test(){
          System.out.println(super.getClass().getName());  //获取父类方法名getClass().getSuperClass().getName(); 
        }
      }

      这里应结合多态的override来理解上面的输出。

    6.字符串

      老生常谈的问题了,字符串采用常量池缓存,不宜创建太多字符串,subString、new、+、等操作慎用,会创建很多字符串常量无法回收,当运行久了之后会占用越多越多的内存。

      字符串做参数,并不会改变改变实参的值。 

    7.多线程

       线程安全的问题建议单独去看。充分考虑到线程安全问题,不会出现死锁问题。

      Object提供的wait、notify、notify不建议对多线程了解不深入的人去用。

      建议使用可重入锁替代synchronized。

      多线程知识较多,这里不做详细说明。

    8.异常处理

       1.异常处理块中可以继续抛异常。

      2.try块可以不需要catch或finally,但二者必须至少有一个

        3.finially块中return 语句会覆盖try块中的return,finally块在try块代码执行完后,return语句之前执行。

      public class MyClass {
      public static void main(String[] args) {
       System.out.print(new MyClass().getNum()); //4
          }
      int getNum(){
       try {
       System.out.println("try block");
       return 3;
       } finally {
       System.out.println("finally block");
       return 4;
      }
       }
      }

       输出结果是:

        try block
        finally block
      4.碰到事务方法,异常处理要特别注意。

      5.finially不一定必执行,当在try块中有system.exit(1);

        try {
        System.out.println("try block begin");
        System.exit(1);
        System.out.println("try block end");
        } catch (Exception e){
         System.out.println("catch block begin");
         System.exit(1);
         System.out.println("catch block end");
        } finally {
         System.out.println("finally block");
        }

        以上代码输出try block begin  

      6.异常不建议往上抛,特殊情况除外。

    9.正则表达式

       replace、split等所有以正则表达式作为参数的方法

    一定要注意正则表达式的含义,例如如下输入
      String a = "acb..";
      System.out.println(a.replaceAll(".","b")); //bbbbb

      转义字符串,尤其是路径问题

    10.静态相关

      static可以修饰类(包含内部类)、成员方法、成员变量、类中代码块。

      static只能修饰类变量,不能修饰局部变量,编译器会报错。

      类启动加载顺序。静态>非静态,成员变量>代码块>构造方法,父类>子类。

      静态方法和静态变量会随类的加载而加载,静态内部类只有在使用时才会加载。

      static 不能和abstract同时使用,可以和final同时使用。

    11 循环删除

       在list中删除a,看起来一切正常。如下所示。

      List<String> list = new LinkedList<>();
      list.add("a");
      list.add("b");
      list.add("c");
      for (String str : list){
       if ("b".equals(str)){
       list.remove(str);
       }
      }
      System.out.println(list); //[a, c]
     假如删除a呢?ConcurrentModificationException,自己模拟下流程思考下原因。

    12. 特殊关键字

      1.volatile:一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

        1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

        2)禁止进行指令重排序。

      2.transient:修饰成员变量。当对象被序列化时(写入字节序列到目标文件)时,transient阻止实例中那些用此关键字声明的变量持久化。

      3.strictfp:一旦使用了关键字strictfp来声明某个类、接口或者方法时,那么在这个关键字所声明的范围内所有浮点运算都是精确的,符合IEEE-754规范的。

        例如一个类被声明为strictfp,那么该类中所有的方法都是strictfp的。

      4.native:原生态方法,可以调用其他语言。这里不做详细说明。

    13 枚举

      1.枚举为每个枚举对象创建一个实例,在首次使用时初始化。

      public static void main(String[] args) {
       weekday mon = weekday.mon;
      }
      public enum weekday {
       mon, tue, wes, thus, fri;
      private weekday() {
       System.out.println("hello"); //输出hello五次
      }
      }
      2.构造方法可以传值。
      
      public static void main(String[] args) {
       weekday mon = weekday.mon;
      }
      public enum weekday {
       mon, tue(1), wes(2), thus, fri;
      private weekday() {
       System.out.print("hello ");
      }
       private weekday(int a) {
       System.out.print("ok ");
      }
      }
      输出:hello  ok  ok  hello  hello

    14 泛型

     1. 泛型类、泛型方法,用<>表示,<>内的内容只要符合变量命名规范即可,不要求是T、K、E、V

     2. 泛型可以有多个变量,例如public Class MyClass<T1,T2,T3,T4>,一般1到2个。

     3. Set<Integer> 不是Set<Number>的子类,逻辑上不具备任何继承关系,二者都属于Set类。Set<Integer>赋值给Set<Number>会报错。

     4. 上面一行的解决方式是泛型通配符。

     5. 泛型的类型参数只能是引用类型,如Set<int>编译报错。

     6. 不能对确切的泛型类型使用instance操作,

     7. <T extends Number>作用于方法或者类上,而 <? extends Number> 则不可以。

     8. 泛型运行期即被擦除,所以不能通过Type type = new TypeToken<TestGeneric<String>>(){}.getType(); 这种方式在运行期动态获取泛型类型。

     9. 泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

      

     
  • 相关阅读:
    Codeforces Round #365 Div.2
    Codeforces Round #363 Div.2[111110]
    花花的礼物 (huahua)
    FOI2019算法冬令营D1
    树(tree)
    noip2018
    1972: 最短路(shortest)
    2462: 收集(collecting)
    1282: 排列计数 perm
    1425: 数列(seq)
  • 原文地址:https://www.cnblogs.com/sjlian/p/7686534.html
Copyright © 2020-2023  润新知