java OOP基础:类与对象(万字长文)

news2024/11/20 14:24:28

目录

类与对象

自定义类

对象的内存模型

“=”赋值

对象引用:this

作为常量的对象变量:final

判等操作

1. “重写(override)”基类的equals()方法

2. “重载(overload)”equals()方法

对象的构造

有参构造与无参构造

构造方法重载

初始化

类的静态成员

静态字段

静态常量

静态初始化块

静态方法

工厂方法

main 方法

静态成员的使用

 枚举类型

包装类

父类Number方法

手动装箱/手动拆箱

javap反编译

缓存

1. Integer 缓存

2. 超出缓存范围


类与对象

自定义类

编写类的 “模板”两种最基本的类成员存取权限:

1. public: 存取与访问不受限制,可以随意被外界读取
2. private: 类自己的方法读取和访问,外界代码不能存取和访问

/**
 * 自定义Java类的示例
 */
public class MyClass {
    //公有字段
    public String information = "";

    //自定义公有Java实例方法
    public void myMethod(String argu) {
        System.out.println(argu);
    }

    //region "属性 = 私有字段 + get方法 + set方法
    private int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
    //endregion
}


class UseMyClass {
    public static void main(String[] args) {
        //创建类的实例
        MyClass obj = new MyClass();
        //通过对象变量调用类的公有方法
        obj.myMethod("Hello");
        //给属性赋值
        obj.setValue(100);
        //输出属性的当前值
        System.out.println(obj.getValue());
        //直接访问对象公有字段
        obj.information = "Information";
        //输出对象公有字段的当前值
        System.out.println(obj.information);
    }
}

1. 我们需要定义一个对象变量
2. 然后“创建(new)”一个对象,赋值给这个对象变量
3. 现在就可以通过这个对象变量使用对象,主要方式有:
① 直接调用类定义的公有(public)方法
② 存取类定义的公有字段

tips:在IntelliJ中编写类的代码时,使用 Alt + Ins 组合键,会弹出一个快捷菜单,其中包容许多常用的命令,可以方便地生成各种框架代码,能够极大地提升编码效率。

对象的内存模型

“引用”一个对象的变量称为“引用类型”的变量,有时又简称为“对象变量” 。

诸如 int,float 之类的变量则称为“原始数据类型”的变量 。

当声明一个对象类型的变量时,实际上并没有创建一个对象,此变量值为默认值 null 。(虽然有默认值 null ,但是在未初始化的状态下不允许使用,所以给对象变量如果不引用一个真实的对象,必须声明为null。) 下面左边的是错误操作,右边的是正确的。

tips:对象变量的特殊值null:代表一个对象变量不引用任何对象。

下面等价的两种写法:

MyClass obj = new MyClass();

MyClass obj;

obj = new MyClass();

定义一个原始数据类型的变量时,则会马上给其分配内存并初始化(如果没有指定,则为默认值,通常是0)。

public class MyClass {

    private int value = 100;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
    //endregion
}

如果在MyClass类中对value给定了初始化的值,那么在创建一个新的变量时(执行 MyClass obj = new MyClass(); 语句时),会自动给value初始化为100。

new这一关键字的作用,就是通知JVM为创建出来的新对象分配足够量的内存(在堆中),并自动调用此对象的一个特定方法(称为“构造方法”)对这块内存进行初始化。

“=”赋值

对象变量之间的赋值,本质上是指向对象的那个内存地址值的复制(而不是对象本身的复制)。赋值结束之后,两个对象变量引用同一个对象。

对象引用:this

this关键字是Java中的一个特殊引用变量,它持有当前对象的引用。在类的非静态方法和构造函数中,可以使用this关键字访问当前对象的成员变量和方法。简单来说,this关键字可以理解为“当前对象自身”的代称。

同一类中的方法可以相互调用,或者直接存取本类定义的字段,可看成其中隐含了一个this调用。 

方法中的变量为局部变量,存储在中,作用范围是方法内;我们想通过构造器初始化的是成员变量,存储在中,作用范围是本类内部

public class MyClass {
    private int value;
    public void setValue1(int value) {
        this.value = value;
    }
    public void setValue2(int value) {
        value = value;
    }
    public String toString() {
        return "MyClass [value=" + value + "]";
    }
}


class UseMyClass {
    public static void main(String[] args) {
        //创建类的实例
        MyClass obj = new MyClass();
        obj.setValue1(100);
        System.out.println(obj.toString());
        obj.setValue2(200);
        System.out.println(obj.toString());
    }
}

  • 通过这个例子,你会发现 setValue2 方法没有实现赋值的功能,因为当成员变量&局部变量重名时,优先使用局部变量关键还是看有没有局部变量,有局部变量优先使用局部变量。

所以需要使用this.属性来表明这是一个成员变量,与局部变量以示区分。

作为常量的对象变量:final

final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。

final MyClass obj = new MyClass();

注意:“常量”对象变量不能引用另一个对象,但可以修改对象所包容的数据,比如修改它所引用的那个对象的某个公有字段值为另一个值。

1.修饰类

  当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。

在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。

2.修饰方法

  下面这段话摘自《Java编程思想》第四版第143页:

  “使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“

  因此,如果只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为final的。

  注:类的private方法会隐式地被指定为final方法。

3.修饰变量

  修饰变量是final用得最多的地方,也是本文接下来要重点阐述的内容。首先了解一下final变量的基本语法:

  对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

注意:“常量”对象变量不能引用另一个对象,但可以修改对象所包容的数据,比如修改它所引用的那个对象的某个公有字段值为另一个值

final MyClass obj = new MyClass();

还是前面的例子,如果定义了以上这个final对象变量,不能给obj重新赋值,但能更obj内的字段,比如value。

判等操作

对于原始数据类型的变量(比如int),可以直接使用“==”运算符判断两变量值是否相等:

当“==”施加于引用类型变量时,是比较这两个变量是否引用同一对象。

引用代表地址,所以“==”实际上相当于比较两个引用类型变量中保存的对象地址数值是否相同。

那么比较两个对象的“内容”是否一样(两个对象的“对应字段值”一致),有两种方法:

1. “重写(override)”基类的equals()方法

import java.util.Objects;

public class ObjectEquals {


    public static void main(String[] args) {
        MyTestClass obj1 = new MyTestClass(100);
        MyTestClass obj2 = new MyTestClass(100);

        System.out.println(obj1 == obj2);//false

        boolean equalsResult = Objects.equals(obj1, obj2);
        //以下这句的输出结果,取决于MyTestClass是否重写了equals方法
        System.out.println(equalsResult); //false 或 true
    }

}

class MyTestClass {
    public int Value;

//    注意:只有参数类型为Object的,才是重写了Object的equals方法
//    参数类型为MyTestClass的,仅仅是Overload了equals方法。
//    这个方法是否被注释,会影响到Objects.equals的结果
    @Override
    public boolean equals(Object obj) {
        return ((MyTestClass) obj).Value == this.Value;
    }

    public MyTestClass(int initValue) {
        Value = initValue;
    }
}

tips:左边这种重写equals()的方式是简化的,在实际开发中不应该这样写,至少应该加上对参数有效性的检测代码,另外还需要重写另一个获取对象hash值的方法。

"@Override"指明此方法是"重写基类的同名方法",Java中,这种以“@”打头的标记被称为“注解(Annotation)”。

当定义一个类时,如果不显式指明它的“父类” 是哪个,则默认是Object。

Object是Java的最顶层基类,其中定义了equals()方法。但是要使用这个判等的功能,需要对equals()方法重写,使其适用自己的类。

2. “重载(overload)”equals()方法

public class MyClass {


    public static void main(String[] args) {
        MyTestClass obj1 = new MyTestClass(100);
        MyTestClass obj2 = new MyTestClass(100);

        System.out.println(obj1 == obj2);//false
        System.out.println(obj1.equals(obj2));//true
        
    }

}

class MyTestClass {
    public int Value;

    public boolean equals(MyTestClass obj) {
        return obj.Value == this.Value;
    }

    public MyTestClass(int initValue) {
        Value = initValue;
    }
}

tips:与前面“重写”方式的代码相比,equals()方法的参数类型是MyTestClass而不是Object,并且方法本身也没有附加“@Override”注解。

对象的构造

public class MyClass {


    public static void main(String[] args) {
        MyTestClass obj1 = new MyTestClass(100);
        System.out.println(obj1.getValue());
    }

}

class MyTestClass {
    public int Value;

    public MyTestClass(int initValue) {
        Value = initValue;
    }

    public int getValue() {
        return Value;
    }

}

这个MyTestClass类内有一个特殊的方法:构造方法与类名相同,没有返回值。

这个方法被称为类的“构造方法(constructor)”,有时也习惯称为“构造函数”。

构造方法的特点:

  • 当使用new关键字创建一个对象时,它的构造方法会被自动调用。
  • 如果类没有定义构造函数,Java编译器在编译时会自动给它提供一个没有参数的“默认构造方法”。
  • 构造方法不能是抽象的,静态的,最终的,同步的也就是说,他不能通过abstract,static,final,synchronized关键字修饰
    • 这个有点难理解,解释一下,就是由于构造方法是不能被继承的,所以final和abstract是没有意义的,而且他是用于初始化一个对象的,所以static也是没有用的,多个线程也不会同时去创建内存地址相同的对象,所以锁也是没有意义的

 为什么要叫做构造方法,是因为在对象创建的时候,需要公共构造方法去初始化值,去描述对象的那些状态,去对应对象中的字段。

总的来讲Java中只有两种构造方法:有参构造和无参构造

有参构造与无参构造

我前面的这个例子就是有参构造,在new一个新的对象时可以传入一个参数来给对象内的字段赋值。

public class MyClass {
    public static void main(String[] args) {
        MyTestClass obj1 = new MyTestClass(100,"123");
        System.out.println("obj1.value: "+obj1.Value+"   obj1.strvalue: "+obj1.strvalue);
    }
}

class MyTestClass {
    public int Value;
    public String strvalue;

    public MyTestClass(int Value ,String strvalue) {
        this.Value = Value;
        this.strvalue = strvalue;
    }
}

像这样,可以在构造方法内传入多个参数。

如果类提供了一个自定义的构造方法,将导致编译器为其默认提供的无参构造方法失效。

上面这个例子中,就不能直接下面这样创建新对象了,需要另外重载一个无参构造方法

MyTestClass obj1 = new MyTestClass();

无参构造:

顾名思义,就是不往构造方法内传入参数,直接运行构造方法内的语句。可以在构造方法内对字段初始化。

构造方法重载

构造方法的重载和其他方法的重载一样,都是通过改变形参的 类型与数量来控制。

public class MyClass {
    public static void main(String[] args) {
        MyTestClass obj1 = new MyTestClass(100,"123");
        System.out.println("obj1.value: "+obj1.Value+"   obj1.strvalue: "+obj1.strvalue);
        MyTestClass obj2 = new MyTestClass();
        System.out.println("obj2.value: "+obj2.Value+"   obj2.strvalue: "+obj2.strvalue);
    }
}

class MyTestClass {
    public int Value;
    public String strvalue;

    public MyTestClass(int Value ,String strvalue) {
        this.Value = Value;
        this.strvalue = strvalue;
    }
    public MyTestClass() {
        this.Value = 0;
        this.strvalue = "";
    }
}

就像这个代码,重载了一个有参构造方法和一个无参构造方法。

同一个类的构造函数之间可以通过this关键字相互调用。

就像刚才的无参构造方法也可以写成这样

public MyTestClass() {
    this(0,"");
}

也能这样写:

public class MyClass {
    public static void main(String[] args) {
        MyTestClass obj1 = new MyTestClass();
        MyTestClass obj2 = new MyTestClass(1,2);
        MyTestClass obj3 = new MyTestClass(1,2,3,4);
        obj1.print();
        obj2.print();
        obj3.print();
    }
}

class MyTestClass {
    public int a;
    public int b;
    public int c;
    public int d;

    public MyTestClass() {
        this(0,0);
    }
    public MyTestClass(int c,int d) {
        this(0,0,c,d);
    }
    public MyTestClass(int a,int b,int c,int d) {
        this.a=a;
        this.b=b;
        this.c=c;
        this.d=d;
    }
    public void print() {
        System.out.println("a="+a+",b="+b+",c="+c+",d="+d);
    }
}

初始化

可以在类中使用“{”和“}”将语句包围起来,直接将其作为类的成员,称为“类的初始化块”。

public class MyClass {
    public static void main(String[] args) {
        MyTestClass obj1 = new MyTestClass();
        obj1.print();
    }
}

class MyTestClass {
    public int a;
    public int b;
    public int c;
    public int d;
    {
        a=1;
        b=2;
        c=3;
        d=4;
    }
    public void print() {
        System.out.println("a="+a+",b="+b+",c="+c+",d="+d);
    }
}

如果同时拥有构造函数与初始化块,会按照构造函数内的参数来赋值。

public class MyClass {
    public static void main(String[] args) {
        MyTestClass obj1 = new MyTestClass(5,6,7,8);
        obj1.print();
    }
}

class MyTestClass {
    public int a;
    public int b;
    public int c;
    public int d;
    {
        a=1;
        b=2;
        c=3;
        d=4;
    }
    public void print() {
        System.out.println("a="+a+",b="+b+",c="+c+",d="+d);
    }
    public MyTestClass(int a,int b,int c,int d) {
        this.a=a;
        this.b=b;
        this.c=c;
        this.d=d;
    }
}

实际开发中,应该尽量保证一个字段只初始化一次!不要同时使用构造函数和初始化块来初始化。

类的初始化块不接收任何的参数,而且只要一创建类的对象,它们就会被执行。因此,适合于封装那些“对象创建时必须执行的代码”。

创建一个子类的对象,会先调用父类的构造函数,再调用子类的构造函数。如果有多层的话,先调用最顶层的父类。不过继承部分之后再讲。(挖坑)

类的静态成员

静态字段

在类内可以用static关键字来修饰字段,这样的字段叫做静态字段。

可以用对象变量或类名作为前缀访问静态数据:

实际开发中,推荐使用类名而不是对象变量来访问静态字段(或调用静态方法)

静态常量

        我们还可以将静态字段声明为 final 类型的,这样就变成了静态常量。静态常量的值是在编译期间确定的,并且只能被读取。通常用来表示不可修改的值,如 pi、e、最大连接数等。

public class MathHelper {
    public static final double PI = 3.1415926;
    
    public static double calculateCircleArea(double radius) {
        return Math.pow(radius, 2) * PI;
    }
}
 
double area = MathHelper.calculateCircleArea(5.0); // 计算半径为 5 的圆的面积
System.out.println(area); // 输出 78.53981633974483

静态初始化块

可以使用“静态初始化块”初始化类的静态字段:

静态初始化块只执行一次。

创建子类型的对象时,也会导致父类型的静态初始化块的执行。

静态方法

        除了静态字段,Java 中还可以定义静态方法。静态方法是独立于任何对象的,可以访问静态字段和其他静态方法,但不能访问非静态成员变量和方法。静态方法常用于工具类和公用函数库中,它们和对象实例无关,可以直接通过类名来调用。

类的静态方法只能访问类的静态成员!

静态方法示例:

import java.util.Arrays;
 
public class ArrayHelper {
    public static int[] reverse(int[] arr) {
        int[] reversedArr = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            reversedArr[i] = arr[arr.length - 1 - i];
        }
        return reversedArr;
    }
}
 
int[] arr = {1, 2, 3, 4};
int[] reversed = ArrayHelper.reverse(arr);
System.out.println(Arrays.toString(reversed)); // 输出 [4, 3, 2, 1]

在静态方法中访问类的实例成员(即那些没有附加static关键字的字段或方法):

工厂方法

        另外一个常用的静态方法是工厂方法。工厂方法用于创建和返回新的对象实例,它通常被定义为 static 方法,可以不用通过类的实例化就可以访问它,并且会在内部调用私有构造函数创建需要的对象。工厂方法在设计模式中被广泛应用,它可以隐藏对象的具体创建过程,简化了代码的使用。

工厂方法示例:

import java.util.Arrays;
 
public class ArrayHelper {
    public static int[] reverse(int[] arr) {
        int[] reversedArr = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            reversedArr[i] = arr[arr.length - 1 - i];
        }
        return reversedArr;
    }
}
 
int[] arr = {1, 2, 3, 4};
int[] reversed = ArrayHelper.reverse(arr);
System.out.println(Arrays.toString(reversed)); // 输出 [4, 3, 2, 1]

main 方法

        在 Java 中,main 方法是程序的入口点,在执行时由 JVM 调用。main 方法必须声明为 public、static 和 void 类型,JVM 在启动程序时会首先加载包含 main 方法的类,并进行相应的初始化操作。main 方法也是一个典型的静态方法的例子,因为它定义在类中,而不需要通过类的实例来访问。

静态成员的使用

使用类的静态字段和构造函数,我们可以跟踪某个类所创建对象的个数。

public class ObjectCounter {
    // 静态字段,用于跟踪对象数量
    private static int objectCount = 0;

    // 构造函数
    public ObjectCounter() {
        objectCount++; // 每次创建对象时增加计数
    }

    // 静态方法,用于查询当前对象数量
    public static int getObjectCount() {
        return objectCount;
    }

    public static void main(String[] args) {
        // 创建一些对象
        ObjectCounter obj1 = new ObjectCounter();
        ObjectCounter obj2 = new ObjectCounter();
        ObjectCounter obj3 = new ObjectCounter();

        // 查询当前对象数量
        System.out.println("当前创建对象的个数: " + ObjectCounter.getObjectCount());
    }
}

但是这个类在多线程环境下可能会出现问题,因为多个线程同时访问和修改 objectCount 可能导致竞争条件,进而导致计数错误。为了在多线程环境中安全地使用这个类,可以使用同步机制来确保对 objectCount 的访问是线程安全的。

  • incrementCount() 方法使用 synchronized 关键字来确保只有一个线程可以在同一时间访问它,从而避免了竞争条件。
  • getObjectCount() 方法也被标记为 synchronized,以确保在获取对象计数时是线程安全的。
public class ObjectCounter {
    // 静态字段,用于跟踪对象数量
    private static int objectCount = 0;

    // 构造函数
    public ObjectCounter() {
        incrementCount(); // 每次创建对象时增加计数
    }

    // 增加计数的同步方法
    private synchronized void incrementCount() {
        objectCount++;
    }

    // 静态方法,用于查询当前对象数量
    public static synchronized int getObjectCount() {
        return objectCount;
    }

    public static void main(String[] args) {
        // 创建多个线程来测试
        Runnable task = () -> {
            for (int i = 0; i < 100; i++) {
                new ObjectCounter();
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 查询当前对象数量
        System.out.println("当前创建对象的个数: " + ObjectCounter.getObjectCount());
    }
}

多线程的内容之后再细讲(继续挖坑)

 枚举类型

枚举类型是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类 类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。

举两个栗子:

public class Test {
    public static void main(String[] args) {
        Size s =Size.SMALL;
        Size t = Size.MEDIUM;
        System.out.println("s="+s);
        System.out.println("s和t引用的是否同一个对象:" + (s == t));
        System.out.println("s与t内容是否相同:" + s.equals(t));
        System.out.println("s的类型: " + s.getClass());
        System.out.println("是否是原始数据类型:" + s.getClass().isPrimitive());
        //枚举Size内的所有值
        for(Size value : Size.values()){
            System.out.print(value+",");
        }
    }

}
enum Size{
    SMALL,
    MEDIUM,
    LARGE
};
enum Day {
    MONDAY("Monday"),
    TUESDAY("Tuesday"),
    WEDNESDAY("Wednesday"),
    THURSDAY("Thursday"),
    FRIDAY("Friday"),
    SATURDAY("Saturday"),
    SUNDAY("Sunday");

    private final String name;

    private Day(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public boolean isWeekend() {
        return this == SATURDAY || this == SUNDAY;
    }
}



public class Test{
    public static void main(String[] args) {
    for (Day day : Day.values()) {
        System.out.println(day);
    // System.out.println(day.isWeekend());
    }
    }
}

enum这个关键字,可以理解为跟class差不多,这也个单独的类。可以看到,上面的例子里面有属性,有构造方法,有getter,也可以有setter,但是一般都是构造传参数。还有其他自定义方法。

enum Size{ SMALL, MEDIUM, LARGE };

在大括号内的这部分叫做,这个枚举的实例。也可以理解为,class new 出来的实例对象。

这下就好理解了。只是,class,new对象,可以自己随便new,想几个就几个,而这个enum关键字,他就不行,他的实例对象,只能在这个enum里面体现。也就是说,他对应的实例是有限的。这也就是枚举的好处了,限制了某些东西的范围,比如:一周只能有七天,用枚举类型的话,你在enum的大括号里面把所有的选项,全列出来,那么这个一周的属性,对应的值,只能在里面挑。不能有其他的。

  • 枚举类型是引用类型!
  • 枚举不属于原始数据类型,它的每个具体值都引用一个特定的对象,相同的值则引用同一个对象。
  • 可以使用“==”和“equals()”方法直接比对枚举变量的值,换句话说,对于枚举类型的变量,“==”和“equals()”方法执行的结果是等价的。

包装类

因为Java是一个完全的面向对象的语言,几乎所有的方法都可以直接通过对象.方法()调用,然而8种基本数据类型的存在就很鸡肋,不能直接用int.   char.   double.来调用想用的功能,也没有继承封装等面向对象的思想,因此引入了包装类:

包装类 = 基本数据类型的数据 + 扩充的一些方法和字段

包装类是引用类型,可以使用new关键字创建相应的对象实例

除了int和char,其他的都是直接首字母大写,前6个数值型的包装类都有一个共同的父类Number,Number又继承于Object

父类Number方法

方法名作用
byte byteValue()以 byte 形式返回指定的数值
abstract double doubleValue()以 double 形式返回指定的数值
abstract float floatValue()以 float 形式返回指定的数值
abstract int intValue()以 int形式返回指定的数值
abstract long longValue()以 long形式返回指定的数值
short shortValue()以 short形式返回指定的数值

手动装箱/手动拆箱

public class IntegerTest02 {
   
    public static void main(String[] args) {
   

        // 123这个基本数据类型,进行构造方法的包装达到了:基本数据类型向引用数据类型的转换。
        // 基本数据类型 -(转换为)->引用数据类型(装箱)
        Integer i = new Integer(123);

        // 将引用数据类型--(转换为)-> 基本数据类型
        float f = i.floatValue();
        System.out.println(f); //123.0

        // 将引用数据类型--(转换为)-> 基本数据类型(拆箱)
        int retValue = i.intValue();
        System.out.println(retValue); //123
    }
}

int value = 100;
Integer obj = value; //装箱
int result = obj * 2;  //拆箱

 栗子:

public class MyClass {
public static void main(String[] args) {
    // 123这个基本数据类型,进行构造方法的包装达到了:基本数据类型向引用数据类型的转换。
    // 基本数据类型 -(转换为)->引用数据类型(装箱)
    Integer i = new Integer(123);
    Integer j = new Integer("123");
    System.out.println("i="+i);
    System.out.println("j="+j);

    // 将引用数据类型--(转换为)-> 基本数据类型
    float f = i.floatValue();
    System.out.println("f="+f); //123.0

    // 将引用数据类型--(转换为)-> 基本数据类型(拆箱)
    int k = i.intValue();
    System.out.println("k="+k); //123

	Boolean A = new Boolean(true);//加不加双引号都行,加了就自动String转boolean
	Boolean B = new Boolean("TrUe");//Boolean包装类定义构造的时候忽略大小写
	Boolean C = new Boolean("qwer123");//其他的所有情况都是false

	System.out.println("A="+A.toString());
    System.out.println("B="+B.toString());
    System.out.println("C="+C.toString());

    }
}

这8种包装类都有这类似的一句“提供String转换方法”,即在初始化构造的时候可以在()种输入String类型,并且每个包装类都有独自的转换方式

tips:编译器会提示从Java 9开始,new Integer(int)和 new Boolean(String) 这一类直接使用构造方法的方式已被弃用。这是因为这些构造方法没有提供比静态工厂方法(如 Integer.valueOf(int) 和 Boolean.valueOf(String))更好的性能或更清晰的意图,后者更建议使用。不过上面的代码仍能编译运行。但建议写成下面这样 ↓

public class MyClass {  
    public static void main(String[] args) {  
        // 使用静态工厂方法进行装箱  
        Integer i = Integer.valueOf(123);  
        Integer j = Integer.valueOf("123");  
        System.out.println("i=" + i);  
        System.out.println("j=" + j);  

        // 将引用数据类型--(转换为)-> 基本数据类型  
        float f = i.floatValue();  
        System.out.println("f=" + f); // 123.0  

        // 将引用数据类型--(转换为)-> 基本数据类型(拆箱)  
        int k = i.intValue();  
        System.out.println("k=" + k); // 123  

        // 使用静态工厂方法  
        Boolean A = Boolean.valueOf(true);  
        Boolean B = Boolean.valueOf("TrUe"); // 提示:这种方式是忽略大小写  
        Boolean C = Boolean.valueOf("qwer123"); // 其他的所有情况都是false  

        System.out.println("A=" + A.toString());  
        System.out.println("B=" + B.toString());  
        System.out.println("C=" + C.toString());  
    }  
}

javap反编译

写一个简单的拆装箱

public class BoxAndUnbox {
    public static void main(String[] args) {
        int value = 100;
        Integer obj = value; //装箱
        int result = obj * 2; //拆箱
        System.out.println(result);
    }
}

使用javap反编译class文件:javap -c 字节码文件

javap是JDK提供的一个反汇编工具。

装箱:
Integer.valueOf()
拆箱:
Integer.intValue()

缓存

public class MyClass {
     public static void main(String[] args) {

            Integer i1 = 100;
            Integer j1 = 100;
            System.out.println(i1 == j1);

            Integer i2 = 129;
            Integer j2 = 129;
            System.out.println(i2 == j2);
        }
}

编译运行这个代码,会发现前一个输出的为true,后一个输出为false,非常神奇。

这是因为java 对于小整数有一个特殊的优化,称为缓存

1. Integer 缓存

Java 对 Integer 实例在 -128 到 127 的范围内进行缓存。当你使用 Integer 的自动装箱(如 Integer i = 100;)时,如果该值落在缓存范围内,Java 会返回一个已经存在的 Integer 实例,而不是创建一个新的实例。因此,在这个范围内的整数,即使它们在不同的地方被赋值,也会指向同一个 Integer 对象。

Integer i1 = 100; // 100 在缓存范围内  
Integer j1 = 100; // 使用相同的缓存实例  
System.out.println(i1 == j1); // 输出 true,因为 i1 和 j1 引用同一个对象

2. 超出缓存范围

对于超出 127 的整数值,Java 不会使用缓存,而是每次都会创建一个新的 Integer 实例。

Integer i2 = 129; // 129 超出缓存范围,创建新的实例  
Integer j2 = 129; // 再次创建新的实例  
System.out.println(i2 == j2); // 输出 false,因为 i2 和 j2 引用不同的对象

  • 对于 -128 到 127 范围内的整数,Integer 实例会被缓存,因此相等比较(==)会返回 true。二者引用的是同一个实例。
  • 对于超出这个范围的整数,每次都会创建新的实例,导致相等比较返回 false

如果想要比较两个 Integer 的值是否相等,应该使用 equals 方法

System.out.println(i2.equals(j2)); // 输出 true,因为它们的值相同

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2185719.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

腾讯云轻量服务器+宝塔面板+基于springboot的web网页部署

经历了一段时间的折磨&#xff0c;近期也在看数据挖掘&#xff0c;还有最优化算法&#xff0c;现在基于我上一篇的的问题上&#xff0c;现在你的情况是不是&#xff1a;本地已经存在一个Springboot的项目&#xff0c;在本地能够良好运行。现在你要做的是把自己的项目部署到网上…

B树、B+树

前言 B树和B树都是平衡的多路搜索树&#xff0c;它们在数据库和文件系统中广泛使用&#xff0c;用于存储和检索数据。B是指balance&#xff0c;也就是平衡的意思。那这俩与平衡二叉树有啥区别&#xff1f;首先要知道AVL树与B树、B树他们都是自平衡搜索树。 ALV的子树间高度不会…

知识图谱入门——4:Protégé 5.6.4安装和主要功能介绍、常用插件(2024年10月2日):知识图谱构建的利器

Protg 是斯坦福大学开发的一款开放源代码的本体编辑工具。它为构建、共享和管理本体&#xff08;Ontologies&#xff09;提供了一个强大的平台&#xff0c;广泛应用于语义网、知识管理、自然语言处理等领域&#xff0c;特别是知识图谱的开发和管理。Protg 支持 OWL&#xff08;…

Android-Handle消息传递和线程通信

本文为作者学习笔记&#xff0c;如有误&#xff0c;请各位大佬指点 目录 一、同步异步 二、Java多线程通信 三、Handler是什么 四、Handler相关的类 五、Handler常用方法 1. 发送消息 2. 接收处理消息 3. 切换线程 六、使用Handler 使用Handler更新UI 使用Handler延…

【MAUI】CommunityToolkit社区工具包介绍

一、为什么需要声明式开发 .NET的MVVM,始于WPF,很古典,它甚至可能是现代前端框架“声明式开发”的鼻祖。声明式开发,之所以出现,是因为命令式开发在UI层和代码层上无法解耦的问题。如下图所示: 1、命令式开发:后台代码需要调用UI层的控件(label.Text),如果更新UI层…

植物病虫害检测数据集 7800张 病虫害 带标注 voc yolo 7类

植物病虫害检测数据集 7800张 病虫害 带标注 voc yolo label| pic_ num| box_ num 越橘: . (932&#xff0c;980) 粘虫: (1104&#xff0c; 1104) 稻苞虫: (1389&#xff0c; 2269) 蝗虫: (1198&#xff0c; 1563) 蝽象若虫: (1594&#xff0c; 2576) . 绿蝽象: (1166&#xf…

微服务nginx解析部署使用全流程

目录 1、nginx介绍 1、简介 2、反向代理 3、负载均衡 2、安装nginx 1、下载nginx 2、解压nginx安装包 3、安装nginx​编辑 1、执行configure命令 2、执行make命令 4、启动nginx 1、查找nginx位置并启动 2、常用命令 3、反向代理 1、介绍反向代理配置 1、基础配置…

渗透测试入门学习——编写python脚本实现对网站登录页面的暴力破解

进入靶场输入任意密码进行尝试 发现登陆失败的特征字&#xff1a;“Username and/or password incorrect” 推荐用谷歌浏览器&#xff0c;按F12继续查看请求地址、请求头参数等详细信息&#xff0c;着重关注是否需要Cookie 编写python脚本 import requests # 填入请求地址 u…

Pikachu-csrf-CSRF(POST)

发起请求 拦截抓包&#xff0c;在请求信息中&#xff0c; Engagement Tool --》generate CSRF PoC 得到以下 html 代码 &#xff0c;生成poc.html 文件&#xff0c;当用户点击 <html><!-- CSRF PoC - generated by Burp Suite Professional --><body><…

C++仿函数的介绍以及priority_queue的介绍和模拟实现

目录 1.仿函数 1.1仿函数的介绍 1.2自定义类型使用仿函数 1.3自定义支持比较大小&#xff0c;但是比较的逻辑不是自己想要的逻辑 2.优先级队列priority_queue 2.1priority_queue的介绍 2.2priority_queue的使用 2.3priority_queue的模拟实现 1.仿函数 1.1仿函数的介绍…

Redis中一些其他的数据类型渐进式遍历

我们之前说了redis中的五个类型 分别是&#xff1a;String List Hash Set ZSet&#xff0c;那除了这五个redis文档中还给我们提供了一些其他的数据类型 &#xff08;一&#xff09;一些其他的数据类型 1.stream 这里的数据类型我们只做简单的一些介绍&#xff0c;如果想了解具体…

C++ | Leetcode C++题解之第452题用最少数量的箭引爆气球

题目&#xff1a; 题解&#xff1a; class Solution { public:int findMinArrowShots(vector<vector<int>>& points) {if (points.empty()) {return 0;}sort(points.begin(), points.end(), [](const vector<int>& u, const vector<int>&…

[云] Hands-on with a sample application--DockerCoins 挖矿程序!

DockerCoins 挖矿程序&#xff01;&#x1f4b0;&#x1f433;&#x1f4e6;&#x1f6a2; 不&#xff0c;你不能用 DockerCoins 买咖啡。 DockerCoins 如何工作&#xff1a; 生成一些随机字节&#xff1a; 程序首先生成一串随机的字节数据。这些随机字节用于模拟挖矿过程中的…

Pytorch实现玉米基因表达量预测模型

一、实验要求 通过搭建残差卷积网络&#xff0c;实现对玉米基因表达量的预测 二、实验目的 理解基因表达量预测问题&#xff1a;基因表达预测是生物信息学和基因组学领域中的重要任务之一&#xff0c;促进学科交叉融合。熟悉深度学习框架PyTorch&#xff1a;通过实现基因表达量…

Golang | Leetcode Golang题解之第453题最小操作次数使数组元素相等

题目&#xff1a; 题解&#xff1a; func minMoves(nums []int) (ans int) {min : nums[0]for _, num : range nums[1:] {if num < min {min num}}for _, num : range nums {ans num - min}return }

nodejs --- 使用全球公认头像gravatar

目录 历史&#xff1a;阿凡达 什么是头像&#xff1f; 我为什么要添加 Gravatar&#xff1f; 我怎样才能得到一个Gravatar&#xff1f; 开发者使用 功能描述 安装和使用 实践应用 我们他们名字旁边的灰色图标是怎么出现的。那么这个灰色图标被称为“神秘人”gravatar。…

IT新秀系列:Go语言的兴起

Go语言&#xff08;Golang&#xff09;由谷歌于2007年发起&#xff0c;并于2009年正式开源。它的诞生背景可以追溯到互联网技术的高速发展时期。那时&#xff0c;软件开发面临着多核计算、大规模并发处理、部署和维护效率低下等挑战。作为一种新型的编程语言&#xff0c;Go主要…

Nginx基础详解5(nginx集群、四七层的负载均衡、Jmeter工具的使用、实验验证集群的性能与单节点的性能)

续Nginx基础详解4&#xff08;location模块、nginx跨域问题的解决、nginx防盗链的设计原理及应用、nginx模块化解剖&#xff09;-CSDN博客 目录 14.nginx集群&#xff08;前传&#xff09; 14.1如何理解单节点和集群的概念 14.2单节点和集群的比较 14.3Nginx中的负载均衡…

指纹定位的原理与应用场景

目录 原理 1. 信号特征收集 2. 定位算法 推导公式 距离估算公式 定位算法公式 使用场景 发展前景 指纹定位是一种基于无线信号强度(如Wi-Fi、RFID、蓝牙等)来实现室内定位的技术。它借助于环境中多个基站的信号特征来推断用户的位置。以下是对指纹定位的详细讲解,包…

Spring Boot技术交流平台的设计与实践

3 系统分析 3.1 可行性分析 为了研究问题并确定问题是否能够在最短的时间内以最低的成本解决&#xff0c;经过对该项目的详细调查研究&#xff0c;初步准备了系统的实施报告&#xff0c;面临的问题和解决方案在软件开发方面进行了初步设计和合理安排&#xff0c;确定了开发目标…