4、java的基本类型和包装类型(自动拆箱和装箱)、浮点数与精度丢失问题(BigDecimal、比较地址和比较值)、静态变量和静态方法(static修饰)、重载和重写(可变长参数)

基本类型和包装类型

基本类型

Java 中有 8 种基本数据类型,分别为:

  1. 6 种数字类型:
    • 4 种整数型:byte、short、int、long
    • 2 种浮点型:float、double
  2. 1 种字符类型:char
  3. 1 种布尔型:boolean

包装类型

如上8种基本数据类型都有对应的包装类型,这些包装类型都是不可变的类,它们提供了基本数据类型与对象之间的转换(解决基本数据类型无法直接应用于需要对象的环境中,例如在集合或泛型编程中的问题),以及一些有用的方法(字符串/数值转换,比值等)。以下是基本数据类型及其对应的包装类型:

  • byte 对应的包装类型是 java.lang.Byte
  • short 对应的包装类型是 java.lang.Short
  • int 对应的包装类型是 java.lang.Integer
  • long -对应的包装类型是 java.lang.Long
  • float 对应的包装类型是 java.lang.Float
  • double 对应的包装类型是 java.lang.Double
  • char 对应的包装类型是 java.lang.Character
  • boolean 对应的包装类型是 java.lang.Boolean

包装类型的缓存机制

Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。

  • Byte,Short,Integer,Long 这 4 种包装类创建了数值 [-128,127] 的缓存数据

  • Character 创建了数值在 [0,127] 范围的缓存数据

  • Boolean 直接返回 True or False。

  • Float,Double 并没有实现缓存机制。

基本类型和包装类型的区别?

  • 用途:除了定义一些常量和局部变量之外,我们在其他地方,比如方法参数、对象属性中很少会使用基本类型来定义变量。并且,包装类型可用于泛型,而基本类型不可以。
  • 存储方式:
    • 基本数据类型的局部变量存放在 Java 虚拟机的栈中的局部变量表中,基本数据类型的成员变量(未被static 修饰 ) 存放在 Java虚拟机的中。
    • 包装类型属于对象类型,我们知道几乎所有对象实例都存在于中。
  • 占用空间:相比于包装类型(对象类型), 基本数据类型占用的空间往往非常小。
  • 默认值:成员变量包装类型不赋值就是 null ,而基本类型有默认值且不是 null。
  • 比较方式:对于基本数据类型来说,== 比较的是值。对于包装数据类型来说,==比较的是对象的内存地址
public class Test {
    // 成员变量,存放在堆中
    int a = 10;
    // 被 static 修饰,也存放在堆中,但属于类,不属于对象
    // JDK1.7 静态变量从永久代移动了 Java 堆中
    static int b = 20;

    public void method() {
        // 局部变量,存放在栈中
        int c = 30;
        static int d = 40; // 编译错误,不能在方法中使用 static 修饰局部变量
    }
}

自动拆箱和装箱

  • 装箱:基本类型 -> 包装类型;底层调用的是包装类的valueOf()方法
  • 拆箱:包装类型 -> 基本类型;底层调用的是 xxxValue()方法

举例如下:

Integer i = 10;  //装箱,等价于 Integer i = Integer.valueOf(10)
int n = i;   //拆箱,等价于 int n = i.intValue();

所以如果频繁拆装箱的话,会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。

对象==比较

public static void main(String[] args) {
    Integer a = 1000;
    Integer b = 1000;
    Integer c = 100;
    Integer d = 100;
    System.out.println("a == b is " + (a == b));
    System.out.println(("c == d is " + (c == d)));
}

输出:

a == b is false
c == d is true

整型对象通过使用相同的对象引用实现了缓存和重用。适用于整数值区间-128 至 +127。只适用于自动装箱。使用构造函数创建对象不适用。

浮点数与精度丢失问题(BigDecimal)

我们都知道,计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。

为此,我们引入了BigDecimal,它可以实现对浮点数的运算,且不会造成精度丢失。比如涉及到钱的业务场景,都是通过 BigDecimal 来做的。

其比较内存地址用的是Objects.equals,比较值用的是compareTo

基本大部分包装类型比较值都用的是compareTo,因为基本都实现了Comparable接口,但是整数包装类型往往用的是equals,因为equals 方法和原始的比较操作符 == 的行为是一致的,而 compareTo 方法往往用于排序操作。注意这里的equals方法指的不是Objects.equals,而是对象a.equals(对象b)

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("1.00");
BigDecimal c = new BigDecimal("0.8");

BigDecimal x = a.subtract(c);
BigDecimal y = b.subtract(c);

System.out.println(x); /* 0.2 */
System.out.println(y); /* 0.20 */
// 比较内存地址,不是比较值
System.out.println(Objects.equals(x, y)); /* false */
// 比较值相等用相等compareTo,相等返回0
System.out.println(0 == x.compareTo(y)); /* true */

静态变量和静态方法(static修饰)

静态变量

静态变量也就是被 static 关键字修饰的变量。它可以被类的所有实例共享,这样可以节省内存。
静态变量是通过类名来访问的(除了该静态变量被 private关键字修饰的情况)。
通常情况下,静态变量会进一步被 final 关键字修饰成为常量。

静态方法

静态方法为什么不能调用非静态成员?
主要原因如下:静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。在类的非静态成员不存在的时候静态方法就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。

重载和重写

重载和重写的区别

重载就是同一个类的两个同名方法能够根据输入数据的不同,做出不同的处理。
重写就是当子类继承自父类的相同方法时对于内部逻辑的覆盖。

具体区别如下:

方法类型范围参数列表返回类型异常访问修饰符发生阶段
重载方法同一个类必须修改可修改可修改可修改编译期
重写方法子类一定不能修改子类方法返回值类型应比父类方法返回值类型更小或相等子类抛出的异常类应比父类抛出的异常类更小或相等可修改运行期

重写的注意点

关于重写,还有两点需要注意:

  • 构造方法无法被重写
  • 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
  • 我们可以使用@Override注释在想要重写的方法上,但它只起到检查作用,发现重写后的方法并没有达到重写的要求(譬如不小心修改了参数列表等),会使代码报错从而告警。

关于被 static 修饰的方法能够被再次声明的例子如下:

class Parent {
    public static void staticMethod() {
        System.out.println("Parent's static method");
    }
    public void parentMethod() {
        System.out.println("Parent's method");
    }
}

class Child extends Parent {
    // 子类可以声明一个与父类静态方法同名的方法,但这不是重写,它只是隐藏了父类的方法
    public static void staticMethod() {
        System.out.println("Child's static method");
    }
    // 如下才是重写
    @Override
    public void parentMethod() {
        System.out.println("Child's method");
    }
}

public class Test {
    public static void main(String[] args) {
        Parent parent = new Child();
        // 通过子类引用调用父类的静态方法,实际走的是Child类里面的staticMethod代码
        parent.staticMethod(); // 输出: Child's static method

        // 通过子类引用调用父类的普通方法,实际走的是Child类里面的parentMethod代码
        parent.parentMethod(); // 输出: Child's method
    }
}

重载的注意点

可变长参数

什么是可变长参数?
所谓可变长参数就是允许在调用方法时传入不定长度的参数,编译后实际会被转换成一个数组。就比如下面这个方法就可以接受 0 个或者多个参数。

public static void method1(String... args) {
   //......
}

另外,当可变参数和不可变参数一起时,可变参数只能作为函数的最后一个参数。

public static void method2(String arg1, String... args) {
   //......
}

遇到方法重载的情况怎么办呢?会优先匹配固定参数还是可变参数的方法呢?
答案是会优先匹配固定参数的方法,因为固定参数的方法匹配度更高。如下:

public class VariableLengthArgument {

    public static void printVariable(String... args) {
        for (String s : args) {
            System.out.println(s);
        }
    }

    public static void printVariable(String arg1, String arg2) {
        System.out.println(arg1 + arg2);
    }

    public static void main(String[] args) {
        printVariable("a", "b");
        printVariable("a", "b", "c", "d");
    }
}

输出:

ab
a
b
c
d

重载遇到泛型

public class GenericTypes {

    public static void method(List<String> list) {
        System.out.println("invoke method(List<String> list)");
    }

    public static void method(List<Integer> list) {
        System.out.println("invoke method(List<Integer> list)");
    }
}

如上代码,有两个重载的函数,因为他们的参数类型不同,一个是List<String>另一个是List<Integer> ,但是,这段代码是编译通不过的。因为泛型会擦除类型,将他们变成了一样的原生类型 List,擦除动作导致这两个方法的特征签名变得一模一样。具体可见本专栏p6的泛型一节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鸡鸭扣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值