Java基础-知识点2(面试|学习)

发布于:2024-04-18 ⋅ 阅读:(23) ⋅ 点赞:(0)

Java为什么是单继承的

  Java选择单继承的设计主要是为了简化类之间的关系,避免多重继承可能带来的复杂性和问题。Java中类不能多继承类可以提高安全性,因为无论是抽象类还是非抽象类都包含非抽象的方法(非抽象类也可能没有),当类可以多继承类时,不同的父类可能会有同名同参的方法,如果子类也没有重写这个同名同参的方法,则在子类的实例调用这个方法的时候就会出现冲突。

equals与==的区别

  对于基本数据类型,“==”判断两个数值是否相等,基本数据类型没有equals()方法

  对于引用类型,“= =” 判断两个变量是否引用同一个对象,及比较对象的内存地址。

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2); // 输出 false
//str1和str2是两个不同的对象,即使它们的内容相同,
//但是它们的引用不同,所以使用==比较会返回false。

  Object类中的equals()在没有重写equals()方法前,equals()方法是直接调用 “= =”,因此重写前的equals()方法与 “==”没有区别。

  String 中的 equals() 方法是被重写过的,比较的是 String 字符串的各个字符是否相等。 Object 的 equals 方法是比较的对象的地址。

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2)); // 输出 true
//这里调用了String类中重写的equals方法来比较字符串对象的内容是否相同,输出结果为true。

equals的等价关系

1、自反性 ;2、对称型 ;3、传递性;

//自反性
x.equals(x); // true

//对称型
x.equals(y) == y.equals(x); // true

//传递性
if (x.equals(y) && y.equals(z))
    x.equals(z); // true;


//与 null 的比较 	对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false
x.equals(null); // false;

Java 访问修饰符

  在Java中,访问修饰符用于控制类、变量、方法以及构造函数的访问权限。Java提供了四种访问修饰符,分别是:publicprotecteddefaultprivate

public

public(公共的):被public修饰的成员可以被任何类访问,无论是同一个包内的类还是不同包的类。public是Java中访问控制级别最低的控制符。

public class MyClass {
    public void myMethod() {
        // 可以在任何地方访问
    }
}

  一般情况下,我们将数据成员(变量、属性)声明为private,行为成员(方法、函数)声明为public,这样可以确保数据安全性和行为的可访问性。

protected

protected(受保护的):被protected修饰的成员可以被同一个包内的类访问,也可以被其他包中的子类访问。

class MyParent {
    protected int protectedVar;
    protected void protectedMethod() {
        // 可以在同一包内和子类中访问
    }
}

default

default(默认的,不使用任何修饰符):默认访问修饰符(没有显式指定访问修饰符)只允许同一个包内的类访问。

class DefaultClass {
    int defaultVar; // 默认访问修饰符
    void defaultMethod() {
        // 只能在同一包内访问
    }
}

private

private(私有的):private是Java中访问控制级别最高的控制符,被声明为private的成员只能在声明它的类内部访问,其他类无法访问。这样可以确保数据的封装性和安全性。

class PrivateClass {
    private int privateVar; // 私有访问修饰符
    private void privateMethod() {
        // 只能在该类内部访问
    }
}

区别速记:

修饰符 当前类 同包 子包 其他包
public
protected X
default X X
private X X X

Final、Static、this、super

Final

  final 关键字,意思是最终的、不可修改的 。用来修饰类、方法和变量,分别表示不可继承、不可重写和常量。具有以下特点:

  1. final 修饰的类不能被继承,final 类中的所有成员方法都会被隐式的指定为 final 方法;
    public final class FinalClass {
     // 类内容
    }
    
  2. final 修饰的方法不能被重写
    public class ParentClass {
    public final void finalMethod() {
        // 方法内容
    	}
    }
    
    
  3. final 修饰的变量是常量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象。
    public class MyClass {
    public final int MAX_VALUE = 100;
    }
    

使用 final 方法的原因

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

总结:final 关键字用于表示不可变性或只读性,可以应用于类、方法、变量、参数以及引用,有助于编写更加安全和稳定的代码。

Static

  static 是 Java 中的一个关键字,用于表示静态成员或静态方法。静态成员和方法与类相关,因此它们可以直接通过类名来访问,无需创建类的实例。下面是 static 的几种常见用法和含义:

1、修饰静态变量

  • 静态变量属于类,而不是类的实例。所有实例共享同一个静态变量的值。
  • 静态变量在类加载时初始化,并且只会初始化一次。
  • 可以直接通过类名访问静态变量,无需创建类的实例。
    public class MyClass {
    public static int count = 0; // 静态变量
    
    public static void main(String[] args) {
        MyClass.count++; // 直接通过类名访问静态变量
        System.out.println(MyClass.count);
    	}
    }
    

2、修饰静态方法

  • 静态方法属于类,而不是类的实例。它们可以直接通过类名调用,无需创建类的实例。
  • 静态方法不能访问类的实例变量,因为它们没有隐含的 this 引用。
  • 静态方法可以在类加载时直接调用,不需要先创建类的实例。
    public class Utility {
    public static void doSomething() { // 静态方法
        // 方法内容
    }
    
    public static void main(String[] args) {
        Utility.doSomething(); // 直接通过类名调用静态方法
    	}
    }
    

3、修饰静态代码块

  • 静态代码块在类加载时执行,只执行一次,并且在类的静态变量初始化之后执行。
  • 静态代码块用于执行静态初始化操作,比如初始化静态变量或执行一些静态操作。
    public class MyClass {
    static {
        // 静态代码块内容
    	}
    }
    

4、修饰静态内部类

  • 静态内部类是定义在另一个类内部并且被 static 修饰的类。
  • 静态内部类可以直接访问外部类的静态成员,但不能直接访问外部类的实例成员。
    public class OuterClass {
    private static int staticVar = 10;
    
    public static class StaticInnerClass { // 静态内部类
        public void display() {
            System.out.println(staticVar); // 可以访问外部类的静态变量
        	}
    	}
    }
    

总结:static 关键字用于表示静态成员或静态方法,与类相关而不是实例相关。静态成员可以通过类名直接访问,有助于共享数据和实现工具类等功能。

this

  this java 中关键字,用于表示当前对象的引用。它通常在类的实例方法中使用,指代当前正在执行方法的对象。下面是 this 的几种常见用法:
1、引用当前对象:

  • 在实例方法中,使用 this 可以引用当前对象,即调用该方法的对象。
  • 可以使用 this 访问当前对象的属性或调用当前对象的方法。
    public class MyClass {
    private int value;
    
    public void setValue(int value) {
        this.value = value; // 使用 this 引用当前对象的属性
    }
    
    public void displayValue() {
        System.out.println(this.value); // 使用 this 引用当前对象的属性
    }
    
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.setValue(10); // 调用实例方法并传入值
        obj.displayValue(); // 调用实例方法显示值
    	}
    }
    

2、在构造函数中调用其他构造函数

  • 在一个类的构造函数中,可以使用 this 来调用同一类中的其他构造函数,实现代码的重用和简化。
    public class MyClass {
    private int value;
    
    public MyClass() {
        this(0); // 调用带参数的构造函数
    }
    
    public MyClass(int value) {
        this.value = value; // 使用 this 引用当前对象的属性
    	}
    }
    

3、区分实例变量和局部变量

  • 当实例变量和局部变量同名时,可以使用 this 来区分它们,以便访问实例变量。
    public class MyClass {
    private int value;
    
    public void setValue(int value) {
        this.value = value; // 使用 this 引用当前对象的属性
    }
    
    public void displayValue(int value) {
        System.out.println("Local value: " + value); // 访问局部变量
        System.out.println("Instance value: " + this.value); // 使用 this 访问实例变量
       }
    }
    

总结:this 关键字用于引用当前对象,常用于实例方法中访问对象的属性或调用对象的方法,以及在构造函数中调用其他构造函数。它可以帮助在类的内部正确引用当前对象,避免歧义和错误。

super

  super关键字用于引用父类的成员(包括构造方法、成员变量和成员方法)。它可以在子类中使用,用于调用父类的构造方法或访问父类的成员。super的用法如下:
1、调用父类的构造方法

  • 在子类的构造方法中,可以使用 super 关键字调用父类的构造方法。这样可以在子类的构造方法中执行父类的初始化操作。
    public class ParentClass {
    public ParentClass(int value) {
        // 父类构造方法
      }
    }
    
    public class ChildClass extends ParentClass {
    public ChildClass(int value) {
        super(value); // 调用父类的构造方法
        // 子类构造方法
     }
    }
    

2、调用父类的成员方法

  • 在子类的方法中,可以使用 super 关键字调用父类的成员方法。这样可以在子类中重写父类的方法,并在子类中调用父类的实现。
    public class ParentClass {
    public void parentMethod() {
        // 父类方法实现
    	}
    }
    
    public class ChildClass extends ParentClass {
    @Override
    public void parentMethod() {
        super.parentMethod(); // 调用父类的方法
        // 子类方法实现
     	}
    }
    
    3、访问父类的成员变量
  • 在子类中,可以使用 super 关键字访问父类的成员变量。这样可以在子类中直接访问父类的属性。
    public class ParentClass {
    protected int value;
    }
    
    public class ChildClass extends ParentClass {
    public void displayParentValue() {
        System.out.println(super.value); // 访问父类的成员变量
    	}
    }
    

总结:super 关键字用于在子类中引用父类的成员,包括构造方法、成员变量和成员方法。它可以帮助子类调用父类的功能,实现代码的重用和扩展。

使用 this 和 super 要注意的问题

  • 在构造器中使用 super() 调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。
  • this、super 不能用在 static 方法中。原因:
    被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, this 和 super 是属于对象范畴的东西,而静态方法是属于类范畴的东西。

深拷贝、浅拷贝

浅拷贝

  浅拷贝是指复制对象时只复制对象本身和基本数据类型成员变量的值对于引用类型的成员变量,只复制引用而不复制引用指向的对象Java 中默认的拷贝方式就是浅拷贝,可以通过实现 Cloneable 接口并重写 clone() 方法来实现浅拷贝。

浅拷贝使用场景
1、简单对象结构:当对象的成员变量都是基本数据类型或不可变对象(如字符串)时,使用浅拷贝可以满足复制的需求。
2、共享部分状态:如果希望复制的对象和原始对象共享部分状态或共享引用类型成员变量指向的对象
3、性能要求较高:由于浅拷贝只复制对象本身和基本数据类型成员变量的值,而不复制引用类型成员变量指向的对象,因此在性能要求较高的场景下,浅拷贝可以更快速地完成复制操作。

浅拷贝的实现

//浅拷贝的实现
class MyClass implements Cloneable {
    private int number;
    private String text;
    private List<String> list;

    public MyClass(int number, String text, List<String> list) {
        this.number = number;
        this.text = text;
        this.list = list;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // 默认浅拷贝
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        List<String> originalList = new ArrayList<>();
        originalList.add("One");
        originalList.add("Two");

        MyClass original = new MyClass(10, "Hello", originalList);
        MyClass shallowCopy = (MyClass) original.clone();

        System.out.println("Original List: " + original.getList());
        System.out.println("Shallow Copy List: " + shallowCopy.getList());

        // 修改浅拷贝对象的列表
        shallowCopy.getList().add("Three");

        // 原始对象和浅拷贝对象共享同一个列表对象
        System.out.println("Modified Original List: " + original.getList());
        System.out.println("Modified Shallow Copy List: " + shallowCopy.getList());
    }
}

在上面的代码中,MyClass 实现了 Cloneable 接口并重写了 clone() 方法,通过调用 super.clone() 来实现浅拷贝。但需要注意的是,浅拷贝只会复制引用,而不会复制引用指向的对象

深拷贝

  深拷贝将对象及其所有相关的对象都完全复制一份,形成一份全新的对象,新对象和原对象不共享内存区域。深拷贝需要遍历所有相关对象,并复制它们的值,所以可能比较耗时,但可以避免意外的副作用。实现深拷贝通常需要手动编写代码来复制引用类型成员变量指向的对象。

深拷贝使用场景
1、复杂对象结构:当对象的成员变量包含引用类型,并且希望复制的对象与原始对象完全独立,不共享任何状态时,使用深拷贝可以满足这种需求,避免共享状态带来的影响。
2、修改独立性:如果复制的对象需要在修改时不影响原始对象,或者需要保持对象的独立性。
3、安全性:在多线程环境下,如果希望每个线程操作的对象都是独立的,避免线程间共享状态带来的安全性问题,可以使用深拷贝来复制对象。

深拷贝的实现

import java.util.ArrayList;
import java.util.List;

class MyClass implements Cloneable {
    private int number;
    private String text;
    private List<String> list;

    public MyClass(int number, String text, List<String> list) {
        this.number = number;
        this.text = text;
        this.list = list;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        MyClass clone = (MyClass) super.clone();
        clone.list = new ArrayList<>(list); // 创建新的列表对象
        return clone; // 深拷贝
    }

    public List<String> getList() {
        return list;
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        List<String> originalList = new ArrayList<>();
        originalList.add("One");
        originalList.add("Two");

        MyClass original = new MyClass(10, "Hello", originalList);
        MyClass deepCopy = (MyClass) original.clone();

        System.out.println("Original List: " + original.getList());
        System.out.println("Deep Copy List: " + deepCopy.getList());

        // 修改深拷贝对象的列表
        deepCopy.getList().add("Three");

        // 原始对象和深拷贝对象使用不同的列表对象
        System.out.println("Modified Original List: " + original.getList());
        System.out.println("Modified Deep Copy List: " + deepCopy.getList());
    }
}

在上面的代码中,实现了深拷贝的关键是在 clone() 方法中手动复制引用类型成员变量指向的对象。在这个例子中,创建了一个新的 ArrayList 对象来存储新的列表,从而实现了深拷贝。

总结: 浅拷贝适用于简单对象结构、共享部分状态和性能要求较高的场景,而深拷贝适用于复杂对象结构、修改独立性和安全性要求较高的场景。

引用类型有哪些?

  引用类型主要分为强引用软引用弱引用虚引用
1、强引用(Strong Reference)

  • 强引用是最常见的引用类型,在Java中默认使用的引用类型就是强引用。
  • 当一个对象被强引用关联时,即使内存空间不足,Java垃圾收集器也不会回收该对象。例如:Object obj = new Object();

2、软引用(Soft Reference)

  • 软引用是一种相对弱化的引用类型,它在内存空间不足时,可能会被垃圾收集器回收。
  • 当垃圾收集器决定回收一个对象时,如果该对象只有软引用关联,且系统内存空间不足时,会回收这个对象以释放内存。
  • 用 SoftReference 类来创建软引用,例如:SoftReference softRef = new SoftReference<>(obj);

3、弱引用(Weak Reference)

  • 弱引用是一种比软引用更弱的引用类型,它的生存期更短。
  • 当垃圾收集器进行垃圾回收时,无论系统内存是否充足,只要一个对象只有弱引用关联,就会被回收。
  • 用 WeakReference 类来创建弱引用,例如:WeakReference weakRef = new WeakReference<>(obj);

4、虚引用(Phantom Reference)

  • 虚引用是一种最弱的引用类型,它不能单独支持对象的生存期,必须与引用队列(ReferenceQueue)结合使用。
  • 当一个对象只有虚引用关联时,无论系统内存是否充足,垃圾收集器都会立即将其回收,并将其放入引用队列中,用于跟踪对象被回收的状态。
  • 用 PhantomReference 类来创建虚引用,例如:PhantomReference phantomRef = new PhantomReference<>(obj, referenceQueue);

使用场景
强引用主要用于一般对象的引用,不易被垃圾收集器回收。
软引用适用于对内存敏感的缓存,允许在内存不足时释放缓存中的对象。
弱引用适用于临时对象的引用,垃圾收集器会更容易回收这些对象。
虚引用适用于需要追踪对象被回收状态的场景,例如在对象被销毁时做一些清理操作。

Java 泛型

  Java 泛型是一种在编译时提供类型安全性和更严格的类型检查的机制。泛型的本质是参数化类型,它将类型 作为 参数 传递给 类或方法,使得代码可以更加通用和灵活。以下为泛型的实现方式及用法

泛型类(Generic Class):

泛型类是使用类型参数的类。在声明类时,可以在类名后面使用尖括号(<>)指定一个或多个类型参数。

public class Box<T> {
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

在上面的例子中,Box 是一个泛型类,使用了类型参数 T,可以用来存储任意类型的数据。

泛型方法(Generic Method):

泛型方法是在方法声明时使用类型参数的方法。在方法的返回类型前面使用尖括号(<>)指定类型参数。

public <T> void printArray(T[] array) {
    for (T item : array) {
        System.out.println(item);
    }
}

在上面的例子中,printArray 是一个泛型方法,可以接受任意类型的数组并打印数组元素。

通配符(Wildcard):

通配符用于表示未知类型或不确定类型的情况。通配符包括上界通配符(<? extends 类型>)和下界通配符(<? super 类型>)。

public void processList(List<? extends Number> list) {
    for (Number item : list) {
        // 处理列表中的元素
    }
}

在上面的例子中,<? extends Number> 表示接受任何继承自 Number 的类型的列表。

泛型限定(Generic Bounds):

泛型限定用于限制类型参数的范围。可以使用上界限定(extends)和下界限定(super)来约束类型参数。

public class Box<T extends Number> {
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

在上面的例子中, 表示类型参数 T 必须是 Number 或其子类。

泛型的类型擦除(Type Erasure):

Java 的泛型是通过类型擦除实现的,即在编译时会擦除类型参数信息。编译器会将泛型代码转换为非泛型代码,并进行必要的类型转换和检查。

Box<Integer> box = new Box<>(10);
Integer value = box.getValue(); // 编译时会转换为 Integer value = (Integer) box.getValue();

在上面的例子中,Box 在编译时会被擦除为 Box,并进行类型转换。

泛型的好处

1、类型安全性
  泛型可以在编译时提供更严格的类型检查,避免了因类型转换错误而导致的运行时异常。
使用泛型可以让代码更加可靠,减少类型相关的编程错误。
2、代码重用性
  泛型可以实现对不同类型的通用处理,提高了代码的重用性和灵活性。使用泛型可以避免为不同类型编写相似的代码,减少了代码的冗余和重复。
3、可读性和可维护性
  使用泛型可以使代码更加清晰和易读,减少了类型转换和强制类型声明的代码。泛型提高了代码的可维护性,当需要修改或扩展代码时,可以更容易地理解和调整泛型代码。
4、类型安全的集合
  Java 中的集合框架(如 ArrayList、HashMap 等)都是使用泛型实现的,可以保证集合中存储的元素类型是一致的。泛型集合提供了更安全和可靠的数据存储和操作方式,避免了因类型不匹配而导致的运行时异常。
5、性能优化
  泛型在编译时会进行类型擦除,最终会生成没有类型参数的代码,因此不会对程序的性能造成影响。使用泛型可以提高代码的可读性和可维护性,从而间接地提高了代码的执行效率。