数据类型

包装类型

8个基本数据类型,为什么有它的包装类型?因为基本类型不够用了,满足不了了。

  • boolean/1
  • byte/8
  • char/16
  • short/16
  • int/32
  • float/32
  • long/64
  • double/64

    1. int a = 1;
    2. Integer b = a; // 自动装箱

    缓存池

    可以看到下方的代码,同样是一种Integer 类型。为什么这么多结果呢?
    Integer.valueOf() 会调用缓存池中的对象,有就调用,没有就创建。 默认范围是 -128~127 刚好一个字节的容量。
    Integer a = 100; 只要不超过默认的范围,都会调用缓存池中的对象。
    new Integer(100); 这种是直接声明了一个对象了,不归缓存池管了。
    Integer e = 200; 已经超过了缓存池的大小。

          Integer a = 100;
          Integer b = Integer.valueOf(100);
          System.out.println(a == b); // true
    
          Integer a1 = new Integer(100);
          Integer b1 = Integer.valueOf(100);
          System.out.println(a1 == b1); // false
    
          Integer c = 100;
          Integer d = 100;
          System.out.println(c == d); // true
    
          Integer e = 200;
          Integer f = 200;
          System.out.println(e == f); // false
    
          Integer h = new Integer(100);
          Integer i = new Integer(100);
          System.out.println(h == i); // false
    

    Integer 缓存池的大小,源码中可以看到, -128~127

      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() {}
      }
    

String

这个String 看了文章一对比,这也太硬核了。深入解析String#intern ,我感觉都不好解释了,写的很好。但是到Java8 结果不一样了。

概览

首先他不可变,内部使用 char 数组来存储值,有唯一的 hash。
image.png
为什么说不可变呢,那我用 substring() replace()替换了值呀!
可以看到,调用方法确实又 new 了一个出来。
image.png
image.png

不可变的好处

著作权归https://www.pdai.tech所有。 链接:https://www.pdai.tech/md/java/basic/java-basic-lan-basic.html
大佬总结的好,我直接引用了。

1. 可以缓存 hash 值

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

2. String Pool 的需要

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

3. 安全性

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

4. 线程安全

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

String, StringBuffer and StringBuilder

1. 可变性

  • String 不可变
  • StringBuffer 和 StringBuilder 可变

    2. 线程安全

  • String 不可变,因此是线程安全的

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

String.intern()

这个跟 字符串常量池 有关系。大小是可以定义了,默认大小还不知道。
a.intern(); 相当于把 a 的值丢进了常量池,然后把对象给了b
a == b ; // false 因为a 始终是 a 它是不可变的,b 引用的是 常量池中的值,它们不一样。
b == c; // true 因为 b 引用的是处理池中的c 使用双引号初始化的,这样初始化会默认用常量池中的值。

        String a = new String("hello");
        String b = a.intern();
        String c = "hello";

        System.out.println(a == b); // false
        System.out.println(b == c); // true

Object 通用方法

public final native Class<?> getClass()

public native int hashCode()

public boolean equals(Object obj)

protected native Object clone() throws CloneNotSupportedException

public String toString()

public final native void notify()

public final native void notifyAll()

public final native void wait(long timeout) throws InterruptedException

public final void wait(long timeout, int nanos) throws InterruptedException

public final void wait() throws InterruptedException

protected void finalize() throws Throwable {}

equals(Object obj)

== 确实比对的是地址值,一般用于基本类型的比较,因为基本类型都是直接存储在 内存中的内存上。
equals 因为所有的内都继承了Object,所以每个类都会有equals方法,根据自己的情况需要重写equals方法

第一个结果:因为Integer 后面的值都在常量池中,地址都是一样的。
第二个结果:因为2000 超过了常量池的范围,必须 创建新对象,并且每次都要建。所有每个的地址值不一样。

        Integer a = 100;
        Integer b = 100;
        System.out.println(a == b); // true


        Integer c = 2000;
        Integer d = 2000;
        System.out.println(c == d); // false
        System.out.println(c.equals(d)); // true

第三个结果可以看源码:

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

其中的 intValue() 就是 转 int 的方法,转完后其实他们 更下面的一样了。因为int是基本类型,存储在栈中。你们也可以去看看String类型的equals方法,挺有意思的。

        int a = 2000;
        int b = 2000;
        System.out.println(a == b); // true

hashCode()

Integer重写了hashCode方法,String也重写了hashCode,但是自己建立的对象没有呀😤,这就是为什么不一样了。

        Integer a = 2000;
        Integer b = 2000;
        System.out.println(a.hashCode()); // 2000
        System.out.println(b.hashCode()); // 2000

        String c = "hello";
        String d = "hello";
        System.out.println(c.hashCode()); // 99162322
        System.out.println(d.hashCode()); // 99162322


        Person person = new Person();
        Person person1 = new Person();
        System.out.println(person.hashCode()); // 1845137754
        System.out.println(person1.hashCode()); // 2013613908

啥原因嘛,我还不懂,博主解释了我也看不懂😅 https://www.jianshu.com/p/2c1b6dd312eb
没看错,hashCode 就只有这么点东西,啥都没有实现,native 是什么,了解到这是系统底层的东西。我就止步到这了,因为继续不下去了呀。

public native int hashCode();

关键字

final

单词的意思是 最终|决赛|最后 值不可改变。通常用来修饰变量。
修饰 方法 : 声明方法不能被子类重写。
修饰 :声明类不允许被继承

        final int a = 3;
        final Person person = new Person();

        a = 4; // 报错
        person = new Person(); // 报错

        person.setId(1); // 可以改变对象内部的值

static

**

静态变量

又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它;静态变量在内存中只存在一份

著作权归https://www.pdai.tech所有。
链接:https://www.pdai.tech/md/java/basic/java-basic-lan-basic.html

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;
    }
}

静态方法

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

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

只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字。

著作权归https://www.pdai.tech所有。
链接:https://www.pdai.tech/md/java/basic/java-basic-lan-basic.html

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
    }
}

静态语句块

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

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

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

// 123

静态内部类

非静态内部类依赖于外部类的实例,而静态内部类不需要。
静态内部类不能访问外部类的非静态的变量和方法。

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();
    }
}

静态导包

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

import static com.xxx.ClassName.*

反射

反射难以说明呀,最好实践实践。
深入解析Java反射(1) - 基础

异常

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

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

碰到异常不要怕,微笑的面对它👊。 其中Exception 是我们需要避免和处理的,如果啥都不处理,发生错误的时候,前端会报 500 。用户会一脸懵逼。 所以要在有可能发生异常的地方用 try…catch… 捕获它,返回可以给用户看的。
知识点 - 图5