跳至主要內容

Java基础概念(二)

CodeShouhu大约 34 分钟使用指南Markdown

方法访问与常量

1,分别Java中介绍continue,break和return 的特点与不同

(1)continue:当在循环中使用 continue 时,当前迭代会立即结束,并开始下一个迭代。需要注意的是,continue 不会终止整个循环,而只是跳过当前迭代的剩余部分。

public class Main {  
    public static void main(String[] args) {  
        for (int i = 0; i < 10; i++) {  
            if (i % 2 == 0) {  
                continue; // 当i为偶数时,跳过当前迭代,不执行System.out.println语句  
            }  
            System.out.println(i);  
        }  
    }  
}
输出:
    1  
    3  
    5  
    7  
    9

(2)breakbreak 用于完全终止一个循环,不论是 for, while 还是 do-while 循环。当循环中执行到 break 时,循环将立即结束,不再进行下一次迭代。

public class Main {  
    public static void main(String[] args) {  
        for (int i = 0; i < 10; i++) {  
            if (i == 5) {  
                break; // 当i等于5时,中断整个循环  
            }  
            System.out.println(i);  
        }  
    }  
}
输出:
    0  
    1  
    2  
    3  
    4

(3)returnreturn 用于从当前的方法中返回,并可能返回一个值。当执行到 return 时,当前方法会立即结束,不再执行后面的代码。如果 return 后面跟了一个值,那么这个值会作为方法的返回值。

public class Main {  
    public static int findFirstNegative(int[] numbers) {  
        for (int i = 0; i < numbers.length; i++) {  
            if (numbers[i] < 0) {  
                return i; // 当找到第一个负数时,返回它的索引并结束方法执行  
            }  
        }  
        return -1; // 如果数组中没有负数,则返回-1表示未找到  
    }  
    public static void main(String[] args) {  
        int[] numbers = {2, 4, -6, 8, 10};  
        int index = findFirstNegative(numbers);  
        System.out.println("Index of first negative number: " + index);  
    }  
}
输出:
	Index of first negative number: 2

速记

continue 结束当前迭代,开始下一轮迭代;break 用于完全终止一个循环;return 用于从当前的方法中返回,当前方法会立即结束

2,请说明java中有多少中访问权限,以及每种访问权限的作用

(1)Public权限表示所有类都可以访问该成员或类。

(2)Protected权限表示同一包中的所有类和子类可以访问该成员或类。

(3)Default权限表示只有同一包中的类可以访问该成员或类。

(4)Private权限表示只有在定义该成员或类的类内部才能访问。

(5)类和接口只能使用public和default两种访问权限修饰符。

(6)类和接口中的方法和变量可以使用所有四种访问权限修饰符。

(7)如果没有指定访问权限修饰符,则默认为default权限。

速记

Java权限有门道,Public谁都能来靠;Protected也挺好,同包子类没烦恼;Default权限较保守,同包访问才足够;Private最自闭,类内使用别越矩。类和接口两选择,Public Default来定夺;方法变量选择多,四种权限任你挪;没写修饰别犯错,Default默认不会错。

3,说明在java中静态变量的的作用以及特点

(1)共享数据:静态变量是属于类的,而不是属于类的任何实例。这意味着类的所有实例都共享同一个静态变量。因此,如果你有一个需要在多个实例之间共享的值,静态变量是一个很好的选择。例如,你可能有一个名为sharedData的静态变量,所有类的实例都可以访问和修改这个变量。

public class SharedDataClass {  
    public static String sharedData = "Initial data";  
}

(2)持久性数据:静态变量的生命周期是整个程序的运行期间,从程序开始运行时就被分配内存,直到程序运行结束时才被释放。因此,如果你需要一个在程序运行期间始终存在的值,静态变量是一个很好的选择。

(3)全局访问:静态变量可以被类的任何方法访问,即使没有创建类的实例也可以访问。这使得静态变量在程序中可以作为全局变量使用。

(4)初始化:静态变量会在类加载时进行初始化,而不是在创建类的实例时进行初始化。这种特性使得静态变量非常适合于需要在类首次使用时就已经准备好的数据。

(5)内存优化:使用静态变量可以节省内存。因为无论创建多少类的实例,静态变量都只有一份,所有实例共享同一份数据,从而节省了内存空间。

(6)配置参数:静态变量可以用于存储类的配置参数。例如,你可能有一个名为DEFAULT_PORT的静态变量,用于定义你的网络服务的默认端口。

public class Server {  
    public static int DEFAULT_PORT = 8080;  
}

(7)常量:静态变量也可以用于定义常量,这些常量在整个程序中都是相同的。例如,你可能有一个名为MAX_CONNECTIONS的静态变量,用于定义你的数据库能处理的最大连接数。

public class Database {  
    public static final int MAX_CONNECTIONS = 100;  
}
public class StaticExample {  
    static int count = 0; // 静态变量  
  
    public static void main(String[] args) {  
        StaticExample example1 = new StaticExample();  
        StaticExample example2 = new StaticExample();  
  
        example1.incrementCount(); // 调用incrementCount方法,增加count的值  
        example2.incrementCount(); // 再次调用incrementCount方法,增加count的值  
  
        System.out.println("Count: " + StaticExample.count); // 输出count的值  
    }  
  
    public void incrementCount() {  
        StaticExample.count++; // 静态变量可以通过类名直接访问和修改  
    }  
}

在这个例子中,我们创建了两个StaticExample对象,并通过incrementCount方法增加了count的值。因为count是静态变量,它被两个对象共享,所以最终输出的count值是2。这个例子展示了静态变量的作用和特点,即它们被所有对象共享,并且可以通过类名直接访问。

速记

类的所有实例可以共享数据,在整个程序运行期间都存在,在类加载的时候初始化,可以节省内存,加上final当做常量

4,在Java中,静态方法和实例方法之间的区别与作用,并举例。

1,静态方法:

(1)静态方法属于类本身,而不属于类的任何实例。因此,可以直接通过类名来调用静态方法,而无需创建类的实例。

(2)静态方法通常用于执行不依赖于对象状态的操作,例如工具方法或数学函数等。

(3)静态方法只能访问静态成员变量或其他静态方法。它们不能访问非静态成员变量或非静态方法。

(4)静态方法可以在没有创建类的实例的情况下调用,因此它们通常用于类的通用操作或初始化操作。

2,实例方法:

(1)实例方法属于类的实例。因此,需要通过类的实例来调用实例方法。

(2)实例方法可以访问类的非静态成员变量和非静态方法。它们可以执行依赖于对象状态的操作。

(3)实例方法通常用于执行与对象状态相关的操作,例如修改对象的状态或返回对象的状态。

(4)实例方法需要在创建类的实例后才能调用。

public class MethodExample {  
    static int count = 0; // 静态变量  
  
    public static void staticMethod() { // 静态方法  
        count++;  
        System.out.println("Static method called, count: " + count);  
    }  
  
    public void instanceMethod() { // 实例方法  
        count++;  
        System.out.println("Instance method called, count: " + count);  
    }  
  
    public static void main(String[] args) {  
        MethodExample example1 = new MethodExample();  
        MethodExample example2 = new MethodExample();  
  
        example1.staticMethod(); // 调用静态方法,增加count的值  
        example1.instanceMethod(); // 调用实例方法,增加count的值  
        example2.staticMethod(); // 再次调用静态方法,增加count的值  
        example2.instanceMethod(); // 再次调用实例方法,增加count的值  
    }  
}
输出:
    Static method called, count: 1  
    Instance method called, count: 2  
    Static method called, count: 3  
    Instance method called, count: 4
 

在这个例子中,我们创建了两个MethodExample对象,并分别调用了静态方法和实例方法。因为静态方法是类级别的,被所有对象共享,所以调用两次静态方法后count的值变为2。而实例方法是对象级别的,每个对象都有自己的一份实例方法,所以调用两次实例方法后count的值变为4。这个例子展示了静态方法和实例方法之间的区别与作用。

速记

静态方法属类身,类名调用不费劲,状态无关常执行,工具数学好帮手。静态只能访静态,无实例也能启动,通用初始化有用。实例方法属实例,实例调用才可以,非静成员能触及,状态操作它在行,创建实例才能用。

5,在Java语言中static关键字的作用与特点

在Java中,static是一个关键字,它表示一种成员(变量或方法)的特定类型。当一个成员被声明为static时,它被称为静态成员。下面是关于static关键字的一些关键点:

  1. 类级别的存储:静态成员属于类本身,而不是类的实例。这意味着无论你创建多少个类的实例,静态成员只有一个副本。
  2. 静态成员的生命周期:静态成员在类被加载到JVM时创建,直到类被卸载时才被销毁。这意味着静态成员的生命周期与应用程序的生命周期相同。
  3. 静态成员的访问:你可以通过类名直接访问静态成员,而无需创建类的实例。例如,如果你有一个名为MyClass的类,其中有一个名为myStaticVariable的静态变量,你可以通过MyClass.myStaticVariable访问它。
  4. 静态方法和变量在内存中的位置:静态方法和变量存储在Java的方法区中,这是Java内存模型的一部分。
  5. 静态方法和变量的初始化:静态变量在类加载时进行初始化,只会被初始化一次。静态方法也是在类加载时初始化,但是可以在运行时多次调用。
  6. 线程安全:静态方法和变量是线程安全的,因为它们在所有线程之间共享。
  7. 静态代码块:静态代码块在类加载时执行,并且只执行一次。这些代码块常用于初始化静态变量。
  8. 静态导入:从Java 5开始,可以使用import static来导入特定类的所有静态成员。这使得在代码中可以直接使用这些静态成员,而无需使用类名来限定它们。
public class StaticExample {  
    static int count = 0; // 静态变量  
  
    public static void staticMethod() { // 静态方法  
        count++;  
        System.out.println("Static method called, count: " + count);  
    }  
  
    public static void main(String[] args) {  
        StaticExample.staticMethod(); // 通过类名直接调用静态方法  
        StaticExample example = new StaticExample();  
        example.staticMethod(); // 也可以通过对象调用静态方法,但实际上还是通过类名来访问  
    }  
}
输出:
Static method called, count: 1  
Static method called, count: 2

速记

Java静态关键字妙,成员类型有门道。类级存储实例少,加载创建卸载消。类名访问真高效,方法区里存可靠。加载初始化一次,线程安全共享好。静态代码块执行,静态导入更轻巧。

6,static修饰的类能不能被其他的类继承?

在Java中,静态内部类(static nested class)不能被继承。这是因为静态内部类并不是外部类的一部分,它们只是和外部类在同一作用域内。实际上,它们更像是外部类的“兄弟”,而不是“子类”。

静态内部类可以有自己的方法和字段,并且它们可以访问外部类的静态方法和字段。但是,它们不能访问外部类的实例方法和字段,除非它们自己有一个外部类的实例。

因此,静态内部类不能被继承,因为它们更像是外部类的“兄弟”,而不是“子类”。

速记

静态内类挺特殊,继承之事行不通;并非外部类一部,更像兄弟非子属。自有方法和字段,静态成员能访问;实例成员若要触,外部实例得持有。

7,static和final有什么不同之处,举例说明?

staticfinal是Java中两个非常常用的关键字,它们的含义和应用场景各不相同。

static关键字的主要作用是用来修饰类、方法和内部类,表示其属于类级别,不依赖于具体的对象实例。它可以被用来创建类的全局变量,或者定义静态方法,或者表示类的静态内部类静态成员不随着对象实例的变化而变化,而是随着类的加载而加载,只会有一个副本。

final关键字则可以修饰类、方法和变量,表示其不能被进一步修改或者继承。具体来说:

  1. final用于修饰类时,表示这个类不能被继承,没有子类。
  2. final用于修饰方法时,表示该方法不能被子类的方法覆盖或重写
  3. final用于修饰变量时,表示该变量是常量,只能赋值一次,赋值后不能再被改变。

总的来说,staticfinal都是Java语言中非常重要的关键字,但它们的应用场景和意义不同。static用于表示与类相关而不依赖于具体实例的属性和方法,而final则用于表示不能被进一步修改或继承的类、方法或变量。

public class StaticFinalExample {  
    static int staticVariable = 10; // 静态变量  
    final int finalVariable = 20; // 常量  
  
    public static void staticMethod() { // 静态方法  
        System.out.println("Static method called, staticVariable: " + staticVariable);  
    }  
  
    public final void finalMethod() { // 最终方法  
        System.out.println("Final method called, finalVariable: " + finalVariable);  
    }  
  
    public static void main(String[] args) {  
        StaticFinalExample example1 = new StaticFinalExample();  
        StaticFinalExample example2 = new StaticFinalExample();  
  
        example1.staticMethod(); // 调用静态方法  
        example1.finalMethod(); // 调用最终方法  
  
        example2.staticVariable++; // 修改静态变量的值  
        // example2.finalVariable++; // 尝试修改常量的值,编译错误  
    }  
}
输出:
Static method called, staticVariable: 10  
Final method called, finalVariable: 20

速记

Java两宝static和final,含义场景各不同。static属类级别,变量方法内部类,全局唯一随类启。final修饰类方法变量,类不可继承,方法不可重写,变量成常量,一次赋值不能变。

8,在java中字符常量和字符串常量的不同之处,并计算字符串常量字节数

(1)字符串常量:字符串常量在Java中用双引号括起来。例如,"Hello"、"World"等都是字符串常量。字符串常量在Java中实际上是String类的对象

(2)字符型常量:字符型常量在Java中用单引号括起来。例如,'a'、'B'、'1'等都是字符型常量。字符型常量在Java中是char类型的数据

(3)字符型常量(char):在Java中,char类型的数据占2个字节(16位)。

(4)字符串常量(String):在Java中,字符串常量实际上是String类的对象,而String类在Java中是一个引用类型。这意味着字符串常量本身不直接占用内存空间,而是指向一个String对象,这个对象在堆内存中占用多少空间取决于具体的字符串长度和内容

(5)以UTF - 8编码为例,计算字符串常量的字节数可以按照以下步骤进行:

​ 1,统计字符串中的字符数

​ 2,对于每个字符,查找其对应的UTF - 8编码,并统计编码所占用的字节数

​ 3,将每个字符的字节数相加,得到字符串的字节数。例如:"Hello, World!"在UTF - 8编码下占用13个字节(包含单词之间的空格)

速记

字符串双引号,Java 类对象;字符单引号,char 类型属。Char 俩字节,String 引用驻。内存看长度,UTF - 8 有步数。数符查编码,字节相加出。

9,详细介绍为什么静态方法不能调用非静态的成员和方法,

在Java中,静态方法和非静态方法是属于不同的概念。静态方法是属于类本身的,而非静态方法属于类的实例

非静态方法需要依赖于具体的对象实例才能被调用,因为它们可以访问和修改实例的成员变量。而静态方法是在类被加载到JVM时就被加载,不依赖于任何对象实例,所以静态方法不能直接访问非静态的成员和方法,因为它们不知道应该使用哪个对象的成员和方法

速记

Java方法分两种,静态类属非实例。非静依赖对象起,实例变量可改取。静态加载随类启,对象无关缺联系,非静成员难触及,不知该用谁的戏。

10,在java中重载方法与重写方法之间的区别,并举例说明

方法重载(Overloading)

方法重载是在同一个类中定义多个方法名相同,但参数类型不同或者参数个数不同的方法。要求:

(1)方法的名称必须相同。

(2)方法的参数类型不同,或者参数的个数不同,或者参数的顺序不同。

(3)方法的返回类型可以相同也可以不相同

(4)仅能通过方法签名(方法名+参数列表)区分重载的方法。

class Demo {  
    void test() {  
        //...  
    }  
  
    void test(int a) {  
        //...  
    }  
  
    void test(int a, int b) {  
        //...  
    }  
}

方法重写(Overriding)

方法重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回类型和形参列表都不能改变。要求:

(1)方法名、参数列表和返回类型必须与父类方法完全相同。

(2)访问修饰符不能比父类方法更严格(可以降低限制,比如父类方法是private,子类方法可以是protected)。

(3)抛出的异常不能比父类方法更广泛。

(4)重写的方法不能是static或final的,因为这两个修饰符表示该方法不能被重写。

(5)声明为final的类不能被继承,因此final类中的方法也不能被重写。

(6)构造方法不能被重写,因为构造方法是用于创建对象的。

(7)如果父类方法被声明为final,则不能被重写。

(8)如果父类方法被声明为private,则不能被重写,因为private方法只能在本类中访问。

(9)如果父类方法被声明为protected,则可以在子类和相同包的其他类中被重写。

(10)如果父类方法被声明为public,则可以在任何地方被重写。

class Parent {  
    void test() {  
        System.out.println("Parent's test method");  
    }  
}  
  
class Child extends Parent {  
    @Override  
    void test() {  
        System.out.println("Child's test method");  
    }  
}
class Animal {  
    void makeSound() {  
        System.out.println("Animal makes a sound");  
    }  
}  
  
class Dog extends Animal {  
    @Override  
    void makeSound() {  
        System.out.println("Dog barks");  
    }  
      
    void makeSound(int times) {  
        for (int i = 0; i < times; i++) {  
            System.out.println("Dog barks");  
        }  
    }  
}  
  
public class Main {  
    public static void main(String[] args) {  
        Animal animal = new Animal();  
        animal.makeSound(); // 输出 "Animal makes a sound"  
          
        Dog dog = new Dog();  
        dog.makeSound(); // 输出 "Dog barks"  
        dog.makeSound(3); // 输出 "Dog barks" 三次  
    }  
}

在这个例子中,Animal类有一个makeSound方法,而Dog类重写了makeSound方法并新增了一个重载方法makeSound(int times)。当我们调用dog.makeSound()时,它调用的是重写的方法,输出"Dog barks";而当我们调用dog.makeSound(3)时,它调用的是重载的方法,输出"Dog barks"三次。这个例子展示了重载方法和重写方法之间的区别。

速记

方法重载:同一个类中,方法名相同,但参数类型不同或者参数个数不同,返回类型可以相同也可以不相同

方法重写:子类对父类的允许访问的方法,方法名、参数列表和返回类型完全相同,static,final,private和构造方法不能被重写,protected子类或者同一个包其他类,public所有类

Java的基类-Object

11,详细介绍一些Object类中使用的方法

Java中的Object类是Java类层次结构的根类,所有Java类都是Object类的子类。Object类包含了一些常用的方法,如toString()、equals()、hashCode()等。

以下是Object类中一些常用方法的介绍:

(1)toString()方法:返回一个字符串,表示该对象的字符串表示形式。默认情况下,toString()方法返回一个包含对象的类名和散列码的字符串。

(2)equals()方法:用于比较两个对象是否相等。默认情况下,equals()方法比较的是两个对象的内存地址,即引用是否相同。可以重写equals()方法来实现自定义的对象比较逻辑。

(3)hashCode()方法:返回该对象的哈希码。默认情况下,hashCode()方法返回的哈希码是基于对象的内存地址计算出来的。可以重写hashCode()方法来实现自定义的哈希码计算逻辑。

(4)clone()方法:创建一个新对象,该对象是当前对象的副本。默认情况下,clone()方法返回的是一个浅拷贝对象,即新对象中的引用类型成员变量与原对象中的引用类型成员变量指向的是同一个对象。可以重写clone()方法来实现深拷贝。

(5)getClass()方法:返回该对象的运行时类。该方法用于在运行时动态地获取对象的类信息。

(6)notify()、notifyAll()和wait()方法:用于实现对象的线程同步和通信。这些方法只能在同步代码块或同步方法中使用,否则将抛出IllegalMonitorStateException异常。(线程中会详细介绍)

package com.jiawa.wiki.meeting;

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        Person person = (Person) obj;
        return age == person.age && name.equals(person.name);
    }
    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + name.hashCode();
        result = 31 * result + age;
        return result;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    public static void main(String[] args) {
        Person person = new Person("John", 25);
        System.out.println(person.toString()); // 输出:Person [name=John, age=25]
        Person person1 = new Person("John", 25);
        Person person2 = new Person("John", 25);
        System.out.println(person1.equals(person2)); // 输出:true
        Person person3 = new Person("John", 25);
        System.out.println(person3.hashCode()); // 输出对象的哈希码值

    }
}

速记

Java根类是Object,所有类都它的徒。常用方法有很多,toString串来表述,equals比对象等否,hashCode码求出。clone副本浅或深,getClass类信可捕捉,线程同步有三法,同步块中才靠谱。

12,可以详细解释hashCode()和equals()之间的联系

hashCode()用于获取哈希码(散列码),eauqls()用于比较两个对象是否相等,它们应遵守如下规定:

如果两个对象相等,则它们必须有相同的哈希码

如果两个对象有相同的哈希码,则它们未必相等

在实际应用中,当我们将对象作为键存储在哈希表中时,hashCode()方法会计算出每个对象的哈希值,然后根据这个哈希值在哈希表中查找相应的槽位。如果两个对象的hashCode()方法返回相同的值,它们就被认为是相等的,但在某些情况下,如果equals()方法未被重写或重写不正确,可能会导致不正确的结果。如果重写了equals()方法,也应该重写hashCode()方法,以确保它们的一致性。

package com.jiawa.wiki.meeting;

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        if (age != person.age) return false;
        return name != null ? name.equals(person.name) : person.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

}
public class feature {
    public static void main(String[] args) {
        Person p1 = new Person("John", 30);
        Person p2 = new Person("John", 30);
        System.out.println("p1.equals(p2): " + p1.equals(p2)); // 输出 true
        System.out.println("p1.hashCode(): " + p1.hashCode()); // 输出一个哈希值,比如 12345
        System.out.println("p2.hashCode(): " + p2.hashCode()); // 输出相同的哈希值,比如 12345
    }
}

速记

hashCode求码现,equals比对象见。对象相等码同现,码同对象未必全。哈希表中键来填,俩法重写保一贯,不然结果易跑偏。

13,详细说明==和equals()有什么主要的不同之处?

(1)对象类型不同:==是操作符,用于比较基本数据类型和引用数据类型的内存地址,而equals()是Object类中的方法,用于比较两个对象的内容是否相等

(2)比较的对象不同:当比较基本数据类型时,==操作符比较的是它们的值是否相等,而当比较引用数据类型时,==操作符比较的是引用的地址是否相同。而equals()方法在比较两个对象时,比较的是两个对象的内容是否相等。

(3)运行速度不同:由于==操作符只需要比较引用,因此运行速度比equals()方法更快。

(4)自动重写不同:==操作符不能被自动重写,而equals()方法可以被重写来实现自定义的对象比较逻辑。

public class Person {  
    private String name;  
    private int age;  
  
    public Person(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
  
    @Override  
    public boolean equals(Object o) {  
        if (this == o) return true; // 如果引用相同,返回true  
        if (o == null || getClass() != o.getClass()) return false;  
  
        Person person = (Person) o;  
  
        // 比较属性是否相等  
        if (age != person.age) return false;  
        return name != null ? name.equals(person.name) : person.name == null;  
    }  
}
public static void main(String[] args) {  
    Person p1 = new Person("John", 30);  
    Person p2 = new Person("John", 30);  
  
    System.out.println("p1 == p2: " + (p1 == p2)); // 输出 false,因为不是同一个对象实例  
    System.out.println("p1.equals(p2): " + p1.equals(p2)); // 输出 true,因为对象的内容相同  
}

速记

==操作符多样,基引类型地址量;equals方法别样,对象内容来较量。基本比较看值详,引用就查地址向。==速度比较爽,equals慢些别心慌。==重写没希望,equals定制有良方。

14,在Object类中为什么会常重写hashCode()和equals()两个方法?

在Java中,hashCode()和equals()是Object类中的两个方法,它们用于比较对象的相等性和计算对象的哈希值。在实际应用中,我们通常需要重写这两个方法来实现自定义的对象比较逻辑和哈希值计算逻辑。

为什么要重写hashCode()和equals()呢?

(1)自定义对象比较逻辑:默认情况下,equals()方法比较的是两个对象的内存地址,即引用是否相同。但在实际应用中,我们可能需要根据对象的属性来比较它们的相等性。例如,对于一个Person类,我们可能认为只要两个人的姓名和年龄相同,就认为它们相等。因此,我们需要重写equals()方法来实现自定义的对象比较逻辑。

(2)自定义哈希值计算逻辑:默认情况下,hashCode()方法返回的哈希码是基于对象的内存地址计算出来的。但在实际应用中,我们可能需要根据对象的属性来计算哈希值。例如,对于一个Person类,我们可能将姓名和年龄的哈希值组合起来,作为该对象的哈希值。因此,我们需要重写hashCode()方法来实现自定义的哈希值计算逻辑。

(3)哈希表操作:hashCode()和equals()方法在哈希表操作中起着重要的作用。在哈希表中,对象的哈希值用于确定该对象在哈希表中的存储位置。如果两个对象的hashCode()方法返回相同的值,则它们被认为是相等的。如果两个对象的equals()方法相等,则它们的hashCode()方法应该返回相同的值。因此,为了正确地使用哈希表,我们需要重写hashCode()和equals()方法

public class Person {  
    private String name;  
    private int age;  
  
    public Person(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
  
    @Override  
    public boolean equals(Object o) {  
        if (this == o) return true;  
        if (o == null || getClass() != o.getClass()) return false;  
  
        Person person = (Person) o;  
  
        if (age != person.age) return false;  
        return name != null ? name.equals(person.name) : person.name == null;  
    }  
  
    @Override  
    public int hashCode() {  
        int result = name != null ? name.hashCode() : 0;  
        result = 31 * result + age;  
        return result;  
    }  
}
public static void main(String[] args) {  
    Person p1 = new Person("John", 30);  
    Person p2 = new Person("John", 30);  
  
    System.out.println("p1.equals(p2): " + p1.equals(p2)); // 输出 true  
    System.out.println("p1.hashCode(): " + p1.hashCode()); // 输出一个哈希值,比如 12345  
    System.out.println("p2.hashCode(): " + p2.hashCode()); // 输出相同的哈希值,比如 12345  
}

速记

Java里有两方法,hashCode和equals啦;对象比等算哈希,常需重写别犯傻。为何重写有因由,自定逻辑解需求;对象比较看属性,默认地址别强求。哈希计算也能改,属性组合巧安排。哈希表中作用大,两法配合别弄差;重写之后才好用,编程路上没疙瘩。

String类

15,解释在String类有哪些常用的方法?

(1)charAt(int index): 返回指定索引位置的字符。

String str = "Hello";  
char ch = str.charAt(1);  
System.out.println("Character: " + ch); // 输出:Character: e

(2)length(): 返回字符串的长度。

String str = "Hello";  
int length = str.length();  
System.out.println("Length: " + length); // 输出:Length: 5

(3)substring(int beginIndex, int endIndex): 返回一个新字符串,它是此字符串的一个子字符串。

String str = "Hello";  
String subStr = str.substring(1, 4);  
System.out.println("Substring: " + subStr); // 输出:Substring: ell

(4)indexOf(String substring): 返回指定子字符串在此字符串中第一次出现的索引。

String str = "Hello World";  
int index = str.indexOf("World");  
System.out.println("Index: " + index); // 输出:Index: 6

(5)lastIndexOf(String substring): 返回指定子字符串在此字符串中最右边出现的索引。

String str = "Hello World";  
int lastIndex = str.lastIndexOf("o");  
System.out.println("Last Index: " + lastIndex); // 输出:Last Index: 7

(6)equals(Object anObject): 比较两个字符串的内容是否相同。

(7)equalsIgnoreCase(String anotherString): 忽略大小写比较两个字符串的内容是否相同。

(8)startsWith(String prefix): 测试此字符串是否以指定的前缀开始。

String str = "Hello";  
boolean startsWithH = str.startsWith("H");  
System.out.println("Starts with H: " + startsWithH); // 输出:Starts with H: true

(9)endsWith(String suffix): 测试此字符串是否以指定的后缀结束。

String str = "Hello";  
boolean endsWithO = str.endsWith("o");  
System.out.println("Ends with O: " + endsWithO); // 输出:Ends with O: true

(10)replace(CharSequence target, CharSequence replacement): 使用指定的字面替换序列替换此字符串所有匹配字面目标序列的子字符串。

String str = "Hello";  
String replacedStr = str.replace('l', 'w');  
System.out.println("Replaced String: " + replacedStr); // 输出:Replaced String: Hewwo

(11)trim(): 返回一个字符串,其值为此字符串,并删除任何前导和尾随空格。

(12)toLowerCase(): 将所有字符转换为小写。

(13)toUpperCase(): 将所有字符转换为大写。

(14)concat(String str): 将指定的字符串连接到此字符串的末尾。

String str1 = "Hello";  
String str2 = "World";  
String concatStr = str1.concat(str2);  
System.out.println("Concatenated String: " + concatStr); // 输出:Concatenated String: HelloWorld

(15)split(String regex): 根据给定的正则表达式的匹配拆分此字符串。

String str = "apple,banana,orange";  
String[] fruits = str.split(",");  
System.out.println("Fruits: " + Arrays.toString(fruits)); // 输出:Fruits: [apple, banana, orange]

(16)join(CharSequence delimiter, Iterable<? extends CharSequence> elements): 使用指定的分隔符将一个可迭代的字符序列元素连接起来。

速记

charAt返回指定索引的字符,length返回字符串的长度,substring返回一个子字符串,indexOf子字符串在此字符串中第一次出现的索引

equals比较两个字符串的内容是否相同,startsWith,endsWith,replace,concat,split

16,对于String,它可以被其他类继承吗?

String类由final修饰,所以不能被继承。Java 9引入了一个新的不可变字符串类String.StringBuilder,它是可变的,并且可以被继承。如果你的需求是需要一个可变的字符串类,你可以考虑使用String.StringBuilder

速记

String类被final罩,继承这事行不通了;Java 9里有新招,StringBuilder可变又能靠,可变需求它来搞。

17,请详细说明String和StringBuffer有什么不同之处

String类是不可变类,即一旦一个 String 对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。

StringBuffer 对象则代表一个字符序列可变的字符串,当一个 StringBuffer 被创建以后,通过 StringBuffer 提供的 append()、insert()、reverse()、setCharAt()、setLength() 等方法可以改变这个字符串对象的字符序列。一旦通过 StringBuffer 生成了最终想要的字符串,就可以调用它的 toString() 方法将其转换为一个 String 对象。

速记

String 不可变,创建之后难改变;StringBuffer 真灵活,append 插入方法多,生成结果转 String 妥。

18,请详细说明StringBuffer和StringBuilder有什么不同之处

Java中的StringBuffer和StringBuilder都是用于处理字符串的类,它们都是可变的,但有一些重要的区别。

  1. 线程安全:StringBuffer是相比StringBuilder是线程安全的,因为它的大多数方法都是同步的。如果你在多线程环境中使用StringBuffer,你可以避免使用synchronized关键字。相比之下,StringBuilder不是线程安全的。
  2. 性能:由于StringBuilder不是线程安全的,它比StringBuffer更加高效。如果你不需要考虑线程安全,那么使用StringBuilder会比使用StringBuffer有稍微好点的性能。

总的来说,选择使用哪个类取决于你的具体需求。如果你需要在多线程环境中使用字符串,那么应该使用StringBuffer。如果你需要对字符串进行大量修改操作,但不需要考虑线程安全,那么应该使用StringBuilder。

public class StringVsStringBufferVsStringBuilder {  
    public static void main(String[] args) {  
        // String示例  
        String str1 = "Hello";  
        String str2 = str1 + " World";  
        System.out.println("String: " + str2); // 输出:String: Hello World  
          
        // StringBuffer示例  
        StringBuffer sb1 = new StringBuffer("Hello");  
        sb1.append(" World");  
        System.out.println("StringBuffer: " + sb1.toString()); // 输出:StringBuffer: Hello World  
          
        // StringBuilder示例  
        StringBuilder sb2 = new StringBuilder("Hello");  
        sb2.append(" World");  
        System.out.println("StringBuilder: " + sb2.toString()); // 输出:StringBuilder: Hello World  
    }  
}

速记

StringBuffer 与 Builder,处理字符串都可变;Buffer 线程更安全,同步方法保周全;Builder 线程不安全,性能高效跑得欢;多线环境用 Buffer,单线操作 Builder 赞。

19,对于在使用字符串时,new和""使用哪种方式更好?

在Java中使用字符串时,可以使用new和""(空字符串)来创建字符串对象。

使用new关键字可以创建一个新的字符串对象,例如:

String str1 = new String("Hello");

这将创建一个新的字符串对象,并将其引用赋值给变量str1。

另一种方式是使用空字符串""来创建字符串对象,例如:

String str2 = "Hello";

这种方式将创建一个新的字符串对象,并将其引用赋值给变量str2。

两种方式的区别在于,使用new关键字创建字符串对象时,每次都会创建一个新的对象,即使对象代表的字符串是相同的。而使用""创建字符串对象时,Java会检查字符串常量池中是否已经存在相同内容的字符串对象,如果存在,则直接返回该对象的引用,否则创建一个新的对象

因此,如果需要在程序中频繁地使用字符串,建议使用""来创建字符串对象,这样可以避免重复创建相同的对象,提高程序的性能。

public class StringCreationExample {  
    public static void main(String[] args) {  
        // 使用new关键字创建字符串对象  
        String s1 = new String("Hello");  
        System.out.println("Using new keyword: " + s1);  
  
        // 使用字符串字面量创建字符串对象  
        String s2 = "Hello";  
        System.out.println("Using string literal: " + s2);  
    }  
}

速记

创建一个新的字符串对象,每次会创建一个新的对象,空字符串""来创建字符串对象,检查字符串常量池中是否已经存在相同内容的字符串对象,如果存在,则直接返回该对象的引用,否则创建一个新的对象。

20,详细说明String a = "abc"; 会进行哪些步骤,并将最终的值存放在哪里?

在Java中,当你使用 String a = "abc"; 这行代码时,会进行以下步骤:

  1. 创建字符串:首先,"abc" 是一个字符串。在Java中,所有的字符串都是存放在Java的字符串常量池(String Constant Pool)中的。
  2. 分配内存:当这行代码执行时,JVM会在堆内存中为变量 a 分配内存空间。
  3. 引用字符串字面量:然后,a 变量会引用字符串常量池中的 "abc" 字面量。此时,a 变量实际上保存的是一个指向字符串常量池中 "abc" 的引用。
  4. 赋值完成:最后,这行代码将 a 变量的引用指向 "abc" 字符串字面量,赋值过程完成。

所以,这个过程会创建一个字符串字面量 "abc" 并放在字符串常量池中,然后在堆内存中为变量 a 分配空间,并将 a 的引用指向 "abc"

速记

Java 里用 String a 等于 abc,先创字符串进常量池,堆中为 a 分配内存忙,a 引常量池里的 abc 不放,赋值完成没商量。

21,详细说明字符串拼接的含义

在Java中,字符串拼接通常是通过将多个字符串实例连接在一起以创建一个新的字符串实例。这个过程通常涉及到将一个字符串实例的方法调用结果赋值给另一个字符串实例。

在Java中,字符串是不可变的,这就意味着一旦一个字符串实例被创建,它的内容就不能被改变。因此,每当我们尝试修改一个字符串时,实际上都会创建一个新的字符串实例。

例如,如果我们有两个字符串s1和s2,我们可以通过以下方式拼接这两个字符串:

String s1 = "Hello, ";
String s2 = "World!";
String s3 = s1 + s2;

在这个例子中,+操作符被用来连接s1和s2。这会创建一个新的字符串实例s3,它的内容是"Hello, World!"。

另一种方式是使用StringBuilder或者StringBuffer类。这两个类都是可变的,可以更有效地处理大量字符串拼接操作,因为它们可以预分配一定的内存空间,而不是每次拼接都创建新的字符串实例。例如:

StringBuilder sb = new StringBuilder();
sb.append("Hello, ");
sb.append("World!");
String s3 = sb.toString();

在这个例子中,我们首先创建了一个StringBuilder实例,然后通过调用append方法两次来拼接两个字符串。最后,我们调用toString方法将StringBuilder实例转换为字符串。这种方式在处理大量字符串拼接操作时会更有效率。

拼接字符串有很多种方式,其中最常用的有4种,下面列举了这4种方式各自适合的场景。

+ 运算符:如果拼接的都是字符串直接量,则在编译时编译器会将其直接优化为一个完整的字符串,则适合使用 + 运算符实现拼接;

如果拼接的字符串中包含变量,则在编译时编译器采用StringBuilder对其进行优化,即自动创建StringBuilder实例并调用其append()方法,将这些字符串拼接在一起,效率也很高。

StringBuilder:如果拼接的字符串中包含变量,并不要求线程安全,则适合使用StringBuilder;

StringBuffer:如果拼接的字符串中包含变量,并且要求线程安全,则适合使用StringBuffer;

String类的concat方法:如果只是对两个字符串进行拼接,并且包含变量,则适合使用concat方法;

先创建一个足以容纳待拼接的两个字符串的字节数组,然后先后将两个字符串拼到这个数组里,最后将此数组转换为字符串。

速记

将多个字符串实例连接在一起创建一个新的实例,可以使用+操作符进行连接,也可以创建StringBuilder实例,调用append方法两次来拼接,但是要求线程安全的话,可以使用StringBuffer,或者是String类的concat方法。

22,在两个字符串相加的底层逻辑是如何实现的?

在Java中,字符串相加的操作对应于Java的字符串连接。这是通过Java的字符串构建器(StringBuilder)类实现的。

例如,当我们写如下的代码:

String str1 = "Hello, ";
String str2 = "World!";
String str3 = str1 + str2;

实际上,编译器会将其转换为以下代码:

String str1 = "Hello, ";
String str2 = "World!";
StringBuilder sb = new StringBuilder();
sb.append(str1);
sb.append(str2);
String str3 = sb.toString();

在上述代码中,StringBuilder类用于连接两个字符串。StringBuilder类在内部维护了一个可变的字符数组,这使得字符串连接操作更加高效,因为不需要像直接使用"+"操作符那样创建新的字符串对象。

请注意,虽然这种转换通常对程序员是透明的,但在处理大量字符串连接操作时,直接使用StringBuilder类可能会更有效率,因为这样可以避免不必要的对象创建。

速记

通过Java的字符串构建器(StringBuilder)类实现的。