跳至主要內容

Java基础概念(三)

CodeShouhu大约 38 分钟使用指南Markdown

面向对象

1,在java中创建一个对象采用什么运算符? ,并说明对象实体和对象引用的不同点

在Java中,我们使用new运算符来创建一个对象。new运算符用于创建一个新的对象实例。例如,如果我们有一个名为Person的类,我们可以使用new运算符创建一个新的Person对象,如下所示:

Person person = new Person();

对象实体是一个具体的实体,包含了对象的所有属性和方法。而对象引用是一个指向对象实体的变量,用来操作对象的属性和方法。

对象实体的大小取决于对象的属性和方法,是动态的。而对象引用的大小固定,通常是4字节或8字节(根据操作系统的位数而定)

对象实体的生命周期由JVM管理,当对象实体被赋予了null值或超出其作用域时,对象实体仍然存在于内存中,但无法再被访问。对象引用的生命周期由程序控制,当引用被赋予了null值或超出其作用域时,对象实体可能被垃圾回收

一个对象引用可以指向 0 个或 1 个对象;一个对象可以有 n 个引用指向它。

public class ObjectExample {  
    public static void main(String[] args) {  
        // 使用new运算符创建Person对象  
        Person person = new Person("Alice", 25);  
          
        // 输出对象实体的属性值  
        System.out.println("Person Name: " + person.getName());  
        System.out.println("Person Age: " + person.getAge());  
          
        // 修改对象实体的属性值  
        person.setName("Bob");  
        person.setAge(30);  
          
        // 输出修改后的属性值  
        System.out.println("Modified Person Name: " + person.getName());  
        System.out.println("Modified Person Age: " + person.getAge());  
    }  
}  
  
class Person {  
    private String name;  
    private int age;  
      
    public Person(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
      
    public String getName() {  
        return name;  
    }  
      
    public void setName(String name) {  
        this.name = name;  
    }  
      
    public int getAge() {  
        return age;  
    }  
      
    public void setAge(int age) {  
        this.age = age;  
    }  
}

在这个例子中,我们创建了一个Person类的对象,并使用new运算符分配了内存空间。person变量是一个对象引用,它指向了创建的Person对象实体。通过person引用,我们可以访问对象的属性和方法,包括获取属性值、修改属性值等。

速记

对象实体是本体,属性方法都集齐;对象引用是变量,指向实体来操办。实体大小看属性,动态变化不定型;引用大小较固定,四八字节按位定。实体周期JVM管,赋空越界仍留盘;引用周期程序控,赋空越界回收中。引用指向零或一,对象可被多引指。

2,在java面向对象中对象的相等和引用相等的概念与不同点?

(1)引用相等(Reference Equality):
引用相等是指两个对象引用的是同一个内存地址,即它们指向的是同一个对象。在Java中,可以使用==运算符来判断两个对象引用是否相等。

Object obj1 = new Object();  
Object obj2 = obj1; // obj2引用的是obj1所指向的对象  
if (obj1 == obj2) {  
    System.out.println("obj1和obj2引用相等");  
}
//输出结果为:"obj1和obj2引用相等",因为obj2引用的是obj1所指向的对象,即它们引用相等。

(2)对象相等(Object Equality):
对象相等是指两个对象的属性值相等,即它们的内容相同。在Java中,可以使用equals()方法来判断两个对象是否相等。例如:

String str1 = "Hello";  
String str2 = "Hello";  
if (str1.equals(str2)) {  
    System.out.println("str1和str2对象相等");  
}
//输出结果为:"str1和str2对象相等",因为str1和str2的内容相同,即它们对象相等。
public class EqualityExample {  
    public static void main(String[] args) {  
        String str1 = new String("hello");  
        String str2 = new String("hello");  
        String str3 = str1;  
  
        // 对象相等性比较  
        System.out.println("str1和str2对象内容是否相等: " + str1.equals(str2)); // 输出: true  
  
        // 引用相等性比较  
        System.out.println("str1和str2引用是否相等: " + (str1 == str2)); // 输出: false  
        System.out.println("str1和str3引用是否相等: " + (str1 == str3)); // 输出: true  
    }  
}

速记

引用相等,指向同一个对象,同一个内存地址,==; 对象相等,两个对象的属性值相等,他们的内容相同 equals();

3,请详细说明java中的深拷贝和浅拷贝的区别,并举例?

(1)浅拷贝:浅拷贝是指创建一个新对象,然后将原对象的非静态成员变量的引用复制到新对象中。这样,新对象和原对象的成员变量指向同一个内存地址,当原对象的成员变量发生改变时,新对象的成员变量也会跟着改变。在Java中,可以通过实现Cloneable接口并重写clone()方法来实现浅拷贝。

(2)深拷贝:深拷贝是指创建一个新对象,并将原对象的非静态成员变量的复制到新对象中。这样,新对象和原对象的成员变量指向不同的内存地址,它们之间不会相互影响。在Java中,可以通过序列化和反序列化的方式来实现深拷贝。首先,需要让原对象实现Serializable接口;然后,通过将原对象序列化为字节数组,再将字节数组反序列化为新对象,从而实现深拷贝。

class Person {  
    String name;  
    int age;  
    List<String> hobbies;  
  
    //构造函数、getter和setter省略  
}  
  
public class Main {  
    public static void main(String[] args) {  
        Person person = new Person("John", 25, Arrays.asList("Reading", "Traveling"));  
          
        // 浅拷贝  
        Person shallowCopy = new Person(person.getName(), person.getAge(), person.getHobbies());  
        shallowCopy.getHobbies().add("Swimming");  
        System.out.println(person.getHobbies()); // 输出:[Reading, Traveling, Swimming]  
          
        // 深拷贝  
        Person deepCopy = new Person(person.getName(), person.getAge(), new ArrayList<>(person.getHobbies()));  
        deepCopy.getHobbies().add("Swimming");  
        System.out.println(person.getHobbies()); // 输出:[Reading, Traveling]  
    }  
}

在这个例子中,Person类有一个List类型的成员hobbies。当我们进行浅拷贝时,shallowCopyperson实际上共享同一个hobbies列表。因此,当我们向shallowCopyhobbies列表添加一个新元素时,personhobbies列表也会受到影响。然而,当我们进行深拷贝时,deepCopy有一个全新的hobbies列表,它是personhobbies列表的副本。因此,修改deepCopyhobbies列表不会影响personhobbies列表。

速记

浅拷贝来创新体,引用复制内存齐,成员一变都看齐,Cloneable 接口重写起。深拷贝也建新区,变量值拷不一起,地址不同互无羁,序列化反序来助力,Serializable 先开启,字节数组转换毕。

4,在java中的面向对象具体代表的是什么含义,详细介绍?

​ 首先,面向对象主要针对面向过程。面向过程的基本单元是函数,而面向对象的基本单元是对象。在Java语言中,万物皆可以看作对象。每个对象都是一个独立的实体,有其自己的属性和行为。

​ (1)对象是类的实例,类是对象的抽象。每个对象都有其自己的属性和行为,这些属性和行为是通过类来定义的。类是一个模板,它定义了对象的基本结构和行为方式

​ (2)对象的属性是对对象的描述,而行为则体现了对象的功能和行为。一个类的使用是通过对该类实例化来完成的。

​ (3)面向对象编程的特点在于将现实世界中的事物抽象成程序中的对象,通过对象之间的关系来模拟现实世界中的各种关系。例如,可以创建一个“汽车”类,该类具有“颜色”、“速度”等属性,以及“加速”、“减速”等行为。然后可以创建“汽车”的实例(对象),通过调用这些实例的行为来模拟真实世界中汽车的行为。

​ (4)面向对象的另一个重要特点是继承和多态。继承是指一个类可以继承另一个类的属性和行为,从而实现代码的重用和扩展。多态是指一个接口可以有多种实现方式,从而可以在运行时根据需要选择不同的实现方式。

速记

面向对象对过程,过程单元是函数,对象基本是主体,Java万物皆可属。对象乃是类实例,类作模板来定义,属性描述行显力,实例化后把类使。现实事物抽象起,对象关系模拟齐,汽车例子来演绎,属性行为真清晰。继承多态很重要,代码重用方式妙,接口实现有多种,运行时刻按需挑。

5,在java中面向对象的三大特征是什么,请分别说明并介绍?

(1)封装性:这是面向对象编程的核心思想之一,它主要表现在将类的属性和方法绑定在一起,形成一个独立的实体。这样做的目的是隐藏对象的内部实现细节,同时保护对象的数据安全,只通过特定的方式(通常是公有的方法)让外界访问和操作对象的数据。在Java中,可以通过关键字private来实现封装的特性,只将必要的方法设置为公有(public),其他所有的属性和方法都被隐藏起来,从而保证数据的安全和程序的稳定。

public class Person {  
    private String name;  
    private int age;  
  
    public Person(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public int getAge() {  
        return age;  
    }  
}

(2)继承性:这是面向对象编程的一个重要的特性,它允许在已有的类(父类或超类)的基础上定义新的类(子类)。子类可以继承父类的属性和方法,而且可以在这个基础上进行扩展或修改。这样可以实现代码的重用和扩展,提高开发效率。在Java中,可以通过关键字extends来实现继承,一个类通过这个关键字继承另一个类的属性和方法,同时添加自己特有的属性和方法。

public class Student extends Person {  
    private int grade;  
  
    public Student(String name, int age, int grade) {  
        super(name, age);  
        this.grade = grade;  
    }  
  
    public int getGrade() {  
        return grade;  
    }  
}

(3)多态性:这是面向对象编程的另一个重要特性,它允许不同的对象对同一消息或方法的响应不同。简单来说,就是**相同类型的变量调用同一个方法时呈现出多种不同的行为特征,这就是多态。**多态的存在大大提高了代码的复用性和可读性。在Java中,多态可以通过接口实现。一个类可以实现多个接口,从而实现不同的功能。

public class Main {  
    public static void main(String[] args) {  
        Person person = new Person("John", 30);  
        System.out.println(person.getName()); // 输出 "John"  
  
        Person student = new Student("Alice", 20, 1);  
        System.out.println(student.getName()); // 输出 "Alice"  
    }  
}

速记

封装:隐藏对象的内部实现细节,对象的安全性,通过特定或者公有地方法让外界对数据进行访问

继承性:在已有的类的基础上定义新的类,子类可以继承父类的方法和属性,通过关键字extends实现继承

多态性:允许不同的对象对同一消息或方法的响应不同,相同类型的变量调用同一个方法时呈现出多种不同的行为特征,提高代码的服用性

6,在Java面向对象中封装的目的是什么,为什么要有封装?

封装的目的是将数据和函数等集合在一个单元(类)中,将对象的属性和方法组织起来并隐藏不想暴露的属性和方法及实现细节。

通过封装,用户或其他对象不能看到或修改其实现细节,只能通过接口去调用对象的方法,达到互相通信的目的。

另外,封装也能减少代码之间的耦合,使类具有清晰的对外接口,提高代码的可读性和可维护性,以及对代码的安全性和稳定性提供保障。

速记

封装目的真奇妙,数据函数类里包,属性方法藏猫猫,接口调用才有效。耦合减少质量高,安全稳定又易搞。

7,在Java中的多态特性是怎么实现的,举例说明?

Java中的多态是通过接口、继承和方法重写来实现的。

接口和继承可以让一个类具有多种形态,例如一个动物类可以继承哺乳动物和爬行动物两个接口,这样它就具有了两种形态。

方法重写则可以让子类覆盖父类的方法,从而实现多态。例如一个猫类可以继承动物类,并重写它的叫声方法,这样当我们调用猫的叫声方法时,就会调用猫类自己的实现,而不是动物类的实现。

在Java中,多态是通过动态绑定来实现的,即在运行时根据对象的实际类型来确定调用哪个方法。例如当我们调用一个动物对象的叫声方法时,Java会根据实际对象的类型来动态选择调用哪个方法,从而实现了多态。

public class PolymorphismExample {  
    public static void main(String[] args) {  
        Animal animal = new Dog(); // 对象向上转型  
        animal.makeSound(); // 调用子类重写的方法  
    }  
}  
  
class Animal {  
    public void makeSound() {  
        System.out.println("The animal makes a sound");  
    }  
}  
  
class Dog extends Animal {  
    @Override  
    public void makeSound() {  
        System.out.println("The dog barks");  
    }  
}

速记

Java多态真奇妙,接口继承方法妙,类具多形形态俏,重写父法来改造。动态绑定运行找,方法调用实时挑。

8,在Java什么是单继承,多继承为什么不能有?

Java设计为单继承的原因主要是为了简化面向对象编程的复杂性。多继承会带来某些问题,例如钻石问题(diamond problem),这种问题在C++等支持多继承的语言中可能会出现。

钻石问题是一个在面向对象编程中常见的问题,它在多继承的环境下产生。假设我们有四个类:A、B、C和D。A是B和C的父类,B和C是D的父类。在这种情况下,D类会有两个版本的A类实例:一个通过B继承,另一个通过C继承。当D类试图访问A类的方法或属性时,它应该使用哪个版本?这就是钻石问题。

Java通过强制使用单继承避免了这个问题。每个类只能有一个父类,这就意味着不会出现多个版本的父类实例。这大大简化了面向对象编程,并减少了可能的错误。

然而,Java通过接口实现了多继承的一种形式一个类可以实现多个接口,每个接口可以包含方法的声明(但没有实现)。然后,类需要提供每个接口中所有方法的实现。这是一种多继承的形式,但避免了钻石问题,因为接口只包含方法的声明,不包含实现,所以不会存在多个版本的实现

interface Interface1 {  
    public void method1();  
}  
  
interface Interface2 {  
    public void method2();  
}  
  
class MyClass implements Interface1, Interface2 {  
    public void method1() {  
        System.out.println("Method 1");  
    }  
  
    public void method2() {  
        System.out.println("Method 2");  
    }  
}

在这个例子中,我们定义了两个接口Interface1Interface2,它们分别包含一个方法method1method2。然后我们创建了一个类MyClass,它实现了这两个接口,并实现了它们的方法。这样,我们就可以通过MyClass类来使用这两个接口的方法,实现了多继承。

速记

Java单继承设计妙,编程复杂简化掉,多继承存钻石扰,C++里问题冒。单继承来实例少,错误减少没烦恼。接口多实形式好,声明无实没困扰。

9,在Java中不同对象的构造方法能不能重写?

Java中构造方法不能被重写的原因主要有两个:

  1. 构造方法的目的是用于创建对象,其执行时间是在对象创建时,而不是在对象运行时。而重写的目的是为了在运行时动态地改变行为。因此,构造方法与重写的目标不一致。
  2. 如果允许子类重写父类的构造方法,那么在创建子类对象时,父类的构造方法也会被调用,这可能会导致父类对象的不正确初始化。为了避免这种情况,Java不允许子类重写父类的构造方法

综上所述,Java中构造方法不能被重写是为了保证对象的正确创建和初始化

虽然构造方法不能被重写,但我们可以使用super关键字在子类的构造方法中调用父类的构造方法。这样可以帮助我们在不改变父类构造逻辑的同时,添加或改变某些行为。

速记

Java构造不重写,俩大原因要记住。创建对象时机固,重写动态不相符。子类若把父类覆,父类初始化添堵。正确创建要守护,super调用有帮助。

接口与抽象类

10,详细说明在接口中能否有构造函数的存在?

在Java中,接口中不能有构造函数。Java中的接口是一种引用类型,它包含的是抽象方法的声明,而不是实现。接口中的方法都是默认公开的(public),你不能在接口中定义构造函数,因为接口不能被实例化。接口里可以包含**成员变量(只能是静态常量)、方法(只能是抽象实例方法、类方法、默认方法)、内部类(包括内部接口、枚举)**定义。

速记

Java接口真奇妙,构造函数不能要。抽象方法来声明,默认公开没商量。实例化它不可行,静态常量成员明。抽象类默类方法,内部接口枚举行。

11,详细介绍接口和抽象类有什么区别,各自有什么作用?

接口体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务;对于接口的调用者而言,接口规定了调用者可以调用哪些服务,以及如何调用这些服务。当在一个程序中使用接口时,接口是多个模块间的耦合标准;当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。

抽象类体现的是一种模板式设计。抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能

(1)默认方法实现:抽象类可以包含具有实际实现的方法,这意味着你可以在抽象类中定义方法并编写方法体。然而,对于接口,直到Java 8以前,它只能包含方法签名,而不能包含方法的实现。从Java 8开始,接口中可以有默认方法和静态方法的实现。

(2)状态存储:抽象类可以有字段(即内部变量),因此可以保存状态。接口不能有字段。接口里只能定义静态常量,不能定义普通成员变量;抽象类里则既可以定义普通成员变量,也可以定义静态常量。

(3)继承和实现:一个类可以实现多个接口,但只能继承一个抽象类。这意味着接口可以用来实现多继承,而抽象类不能。

(4)构造函数:抽象类可以有构造函数,而接口不能有构造函数。

(5)访问修饰符:抽象类的方法可以有public、protected和default这些修饰符,而接口中的方法默认修饰符是public,不可以是protected或default。

(6)兼容性:向后兼容性对于接口来说是个问题,如果在接口中添加新的方法,那么所有实现了这个接口的类都必须实现这个新方法,否则编译会报错。对于抽象类,这样的问题就不存在,因为新添加的方法可以在抽象类中提供默认的实现。

接口和抽象类很像,它们都具有如下共同的特征:

接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。

接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。

interface MyInterface {  
    default void method() {  
        System.out.println("Interface method");  
    }  
}  
  
abstract class MyAbstractClass {  
    private int field;  
  
    public MyAbstractClass(int field) {  
        this.field = field;  
    }  
  
    public void method() {  
        System.out.println("Abstract class method");  
    }  
}  
  
class MyClass implements MyInterface {  
    public static void main(String[] args) {  
        MyClass myClass = new MyClass();  
        myClass.method(); // 输出 "Interface method"  
  
        MyAbstractClass myAbstractClass = new MyAbstractClass(10) {  
            @Override  
            public void method() {  
                System.out.println("Subclass method");  
            }  
        };  
        myAbstractClass.method(); // 输出 "Subclass method"  
    }  
}

速记

接口规范像标杆,实现调用有指南,模块程序标准传。抽象类是模板板,子类继承中间产。默认方法类能办,Java8后接口添。状态存储类可占,接口常量静不变。类单继承接多干,构造类有接不见。类法修饰多样选,接口默认是公版。接口加新麻烦现,类有默认好兼容。接口抽象共同点,实例不行被继办,抽象方法要实现。

异常处理机制

12,在运行程序时遇到过异常吗,如何利用java处理?

在Java编程中,异常是不可避免的,因为有许多因素可能导致程序出错,例如用户输入错误、文件不存在、网络延迟等。在Java中,异常处理主要通过五个关键字实现:try、catch、finally、throw和throws。

下面是一些处理异常的常见方法:

  1. try-catch块: 这是最常用的异常处理方法。try块包含可能会引发异常的代码,而catch块包含处理这些异常的代码。catch块后面的括号中应包含所要捕获的异常类型。如果try块中的代码引发了一个异常,那么catch块中的代码就会执行。
try {
    //可能会引发异常的代码
} catch (ExceptionType1 e) {
    //处理ExceptionType1的代码
} catch (ExceptionType2 e) {
    //处理ExceptionType2的代码
}
  1. finally块: finally块包含无论是否发生异常都需要执行的代码。无论try或catch块中的代码如何结束,finally块的代码都会执行。这对于关闭在try块中打开的资源等操作非常有用。
try {
    //可能会引发异常的代码
} catch (Exception e) {
    //处理异常的代码
} finally {
    //无论是否发生异常都需要执行的代码
}
  1. throw关键字: 如果代码中有一个问题,而且你希望这个问题作为一个异常被抛出,那么可以使用throw关键字。一旦抛出了异常,程序就会寻找处理这个异常的代码。
if (condition) {
    throw new ExceptionType("Exception message");
}
  1. throws关键字: 如果一个方法可能会引发某种类型的异常,而且它不想处理这个异常,那么它可以使用throws关键字将这个异常传递给调用它的方法。调用方法必须处理或者再次传递这个异常。
public void myMethod() throws ExceptionType {
    //可能会引发ExceptionType的代码
}

以上是Java处理异常的基本方式,对于具体的问题,可能需要结合实际情况选择最合适的处理方式。

速记

try块包含可能会引发异常的代码,而catch块包含处理这些异常的代码。catch块后面的括号中应包含所要捕获的异常类型。finally块包含无论是否发生异常都需要执行的代码。throw将代码中的问题作为异常抛出,throws将异常传递给调用它的方法。

13,说一说Java的异常机制有什么特点,并举例

Java的异常机制是Java编程语言中的一个重要特性,它旨在处理在程序执行过程中可能出现的错误。这个机制主要基于五个关键词:try、catch、finally、throw和throws。

  1. try: 这个关键字用来创建一个监控区域,用于捕获异常。这是一个保护代码的区块,它会监控在其中可能出现的异常。
  2. catch: 这个关键字用于捕获try代码块中抛出的异常。每个try代码块后面至少有一个与之关联的catch块。当try中的代码抛出异常时,相应的catch块就会执行。
  3. finally: 这个关键字用于创建一个代码块,无论是否出现异常,这个代码块都将被执行。这常常用于资源清理,例如关闭文件、网络连接等。
  4. throw: 这个关键字用在方法内部,用于主动抛出一个异常。执行throw语句后,方法的执行立即停止,并返回到调用者。
  5. throws: 这个关键字用在方法签名中,用于声明该方法可能会抛出的异常类型。调用者需要处理这些异常。

Java的异常机制是基于"面向对象"的思想设计的,所有的异常都继承自Throwable类。Throwable类有两个重要的子类:Error和Exception。Error通常表示系统级错误,如OutOfMemoryError;而Exception表示程序可以处理的异常,如IOException、SQLException等。

import java.io.FileReader;  
import java.io.IOException;  
  
public class ExceptionExample {  
    public static void main(String[] args) {  
        try {  
            FileReader fileReader = new FileReader("file.txt");  
            // 执行可能引发异常的代码  
        } catch (IOException e) {  
            // 处理IOException异常  
            System.out.println("文件读取异常:" + e.getMessage());  
        } catch (Exception e) {  
            // 处理其他异常  
            System.out.println("发生异常:" + e.getMessage());  
        } finally {  
            // 在finally块中执行清理操作  
            System.out.println("清理资源");  
        }  
    }  
}

在这个例子中,我们尝试打开一个文件并读取它的内容。由于文件读取可能引发IOException异常,因此我们使用try-catch语句块来处理它。如果发生IOException异常,我们将在控制台输出异常信息。如果发生其他类型的异常,我们将在控制台输出通用的异常信息。最后,在finally块中执行清理操作,确保资源被正确释放。这个示例展示了Java的异常分类和异常处理的特点。

此外,Java 7引入了多异常捕获,可以在一个catch块中捕获多种类型的异常。而Java 8引入了Optional类,它是一种更为灵活和安全的处理异常的方式,允许在没有值的时候返回一个空的Optional对象,而不是抛出一个NoSuchElementException异常。

速记

Java异常机制妙,处理错误有高招,五词撑起大框架,try、catch、finally、throw、throws记牢靠。try块监控异常冒,catch捕获别跑掉,finally执行不能少,资源清理它来搞。throw抛错方法了,throws声明调者搞。面向对象设计好,Throwable类是根苗,Error系统错难逃,Exception程序能自保。

14,请介绍Java的异常接口以及各自的作用和分类

Java的异常处理机制是通过使用java.lang.Throwable类及其子类来实现的。Throwable类是所有Java异常的基类,它定义了异常的基本行为和属性。在Java中,异常可以是检查型异常(Checked Exceptions)或非检查型异常(Unchecked Exceptions)。

  1. 检查型异常(Checked Exceptions):这些是必须在编译时处理的异常。对于检查型异常,编程人员必须显式地处理它们,要么通过trycatch块来捕获它们,要么通过在方法签名中使用throws关键字来声明它们。一些常见的检查型异常包括:

    • java.io.IOException: 表示输入/输出操作失败或中断的异常。
    • java.sql.SQLException: 用于处理与数据库交互时可能出现的异常。
    • java.net.SocketException: 表示网络I/O操作失败的异常。
  2. 非检查型异常(Unchecked Exceptions):这些是运行时异常的子类,编译器不会强制程序员处理它们。非检查型异常都是RuntimeException类的子类,或者是Error类的子类。一些常见的非检查型异常包括:

    • java.lang.RuntimeException: 所有非检查型异常的基类。
    • java.lang.NullPointerException: 当应用程序试图在需要对象的地方使用null时抛出的异常。
    • java.lang.ArithmeticException: 当出现异常的算术条件时抛出的异常,例如除以零。
    • java.lang.NegativeArraySizeException: 当试图创建大小为负的数组时抛出的异常。

速记

异常处理机制通过Throwable类来实现的,其中包含在编译时处理的异常检查型异常或者是非检查型异常,主要是RuntimeException或者Error类型的异常。

15,在Java中,Throwable 类常用方法有哪些特点?

Throwable 类是 Java 中所有错误和异常的基类。它提供了以下一些常用的方法:

  1. public String getMessage(): 返回关于发生的异常的详细信息。
  2. public Throwable getCause(): 返回一个 Throwable 对象,该对象是由 getCause() 方法调用链中最近的方法引发的。
  3. public String toString(): 返回简短描述异常的字符串。
  4. public void printStackTrace(): 在控制台上打印堆栈跟踪信息。
  5. public StackTraceElement[] getStackTrace(): 返回一个包含堆栈跟踪元素的数组,每一个元素代表一个堆栈帧。
  6. public Throwable fillInStackTrace(): 用当前调用堆栈的堆栈跟踪信息填充此 Throwable 对象。

注意,这些方法中的大多数也存在于其子类 ExceptionError 中,它们也是 Java 中常见的异常类型。

速记

Throwable是根基,错误异常都归依。getMessage详情提,getCause源头觅。toString描述齐,printStackTrace追踪记。getStackTrace帧分析,fillInStackTrace来补齐。子辈Excep、Error里,这些方法常相遇。

16,在Java面向对象中,finally的输出是无条件执行的吗?

是的,Java中的finally块是无条件执行的。无论try块中的代码是否出现异常,finally块中的代码总是会执行。这是finally块的主要用途,也是它区别于其他控制流语句(如if语句、switch语句等)的主要特征。

在Java中,finally块通常用于清理资源,如关闭文件、断开网络连接等。由于finally块总是会被执行,所以它是一个很好的地方来释放或关闭可能会被异常中断的资源。

请注意,当finally块中的代码执行完后,控制权会立即返回到执行finally块的try-catch语句之后的下一条语句,而不是返回到抛出异常的原始try块。

public class FinallyExample {  
    public static void main(String[] args) {  
        try {  
            int result = 10 / 0; // 这将引发ArithmeticException  
        } catch (ArithmeticException e) {  
            System.out.println("捕获到算术异常:" + e.getMessage());  
        } finally {  
            System.out.println("finally块执行");  
        }  
    }  
}

速记

Java里的finally块,无论异常都执行,区别控制流显著。资源清理它来做,文件网络都能关,执行完毕控制权,try-catch后接着办,原始try块不回返。

17,在finally中执行return方法会发生什么?

在Java中,finally块中的return语句会覆盖try或catch块中的return语句。这意味着,无论try或catch块中的代码是否返回一个值,只要finally块中有一个return语句,那么该return语句的值将成为方法的返回值。

这是一个示例:

public int test() {
    try {
        return 1;
    } catch (Exception e) {
        return 2;
    } finally {
        return 3;
    }
}

在上述代码中,尽管try和catch块都有return语句,但实际上方法test的返回值将是3,因为finally块中的return语句会覆盖其他所有return语句。

速记

finally块中的return语句会覆盖try或catch块中的return语句

泛型

18,在Java中什么是泛型?有什么作用?

泛型(Generics)是Java 5引入的一个特性,用于在编译期间提供更严格的类型检查泛型允许程序员在类、接口和方法中使用类型参数。这意味着你可以创建一些能在多种数据类型上操作的代码,而这些代码在编译时仍然保持类型安全。

例如,考虑一个简单的类,它保存了一个对象,并允许你获取和设置这个对象:

public class Box {
    private Object object;

    public void set(Object object) {
        this.object = object;
    }

    public Object get() {
        return object;
    }
}

这个类的问题在于,你可以将任何类型的对象放入Box中,然后在取出时可能需要进行类型转换。如果尝试转换的类型不对,就会在运行时抛出ClassCastException

使用泛型,你可以创建一个只接受特定类型的Box

public class Box<T> {
    private T object;

    public void set(T object) {
        this.object = object;
    }

    public T get() {
        return object;
    }
}

现在,你可以创建一个只接受String类型的Box,编译器会在编译时检查你是否始终按照这个方式使用Box。如果你试图将一个非String对象放入Box,编译器就会报错。这就减少了在运行时出现ClassCastException的可能性。

泛型的主要作用是提高代码的可重用性和可读性,同时保持类型安全

Java 8中的ArrayList类是一个泛型的例子。你可以创建一个指定类型的ArrayList,比如ArrayList<String>,其中String是类型参数,表示这个ArrayList只能存储String类型的对象。

import java.util.ArrayList;  
  
public class Main {  
    public static void main(String[] args) {  
        ArrayList<String> list = new ArrayList<>();  
        list.add("Hello");  
        list.add("World");  
        for (String str : list) {  
            System.out.println(str);  
        }  
    }  
}

速记

在编译期间提供更严格的类型检查,泛型允许程序员在类、接口和方法中使用类型参数,提高代码的可重用性和可读性,同时保持类型安全

19,项目中哪里用到了泛型?

泛型在Java项目中有很多应用,它可以提供类型安全,同时在编译期间就能发现类型错误,而不是等到运行时。以下是一些Java项目中可能会使用到泛型的地方:

  1. 集合类:这可能是泛型最常见的用途。Java的集合类,如ListSetMap等,都支持泛型。例如,你可以创建一个List<String>,它只能存储String对象。
  2. 自定义类:你也可以在自定义的类中使用泛型。例如,你可能有一个名为Box<T>的类,它可以持有任何类型的对象。
  3. 方法:泛型也可以用在方法上。例如,你可能有一个名为printArray(Array<T> array)的方法,它可以打印任何类型的数组。
  4. 接口:泛型接口与泛型类类似,只是接口中定义的是一组方法签名,而类定义的是实际的实现。例如,Java的Comparable接口就使用了泛型。
  5. 静态方法和静态变量:在Java 5之后,泛型也可以用于静态方法和静态变量。
  6. 异常处理:你也可以创建泛型异常类,例如public class MyException<T> extends Exception
  7. 泛型的通配符:有时候,你可能想要创建一个可以处理多种类型的泛型类。例如,你可能有一个名为Box<T>的类,它可以持有任何类型的对象,但是你可能想要限制它只能持有某种特定类型的对象。在这种情况下,你可以使用泛型的通配符。

这只是泛型在Java项目中的一些可能用途,实际上,使用泛型的地方可能会更多。

速记

Java泛型用处广,类型安全编译防。集合类里最常见,List、Set、Map都能管。自定义类可实现,Box
把对象揽。方法接口也能占,Comparable泛型现。Java 5后有改变,静态方法变量泛。异常处理也能办,泛型类里来拓展。通配符也不简单,类型限制它来管。泛型应用还在添,更多场景等发现。

20,说一说Java的四种引用方式

Java中有四种引用方式:强引用、软引用、弱引用和虚引用。

强引用是最普遍的引用类型,它直接指向对象,并且只要存在强引用,垃圾收集器就不会回收该对象。例如:Object obj = new Object()。只要强引用还在,垃圾回收器永远不会回收掉被引用的对象,即使系统内存空间不足,JVM宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足问题。

软引用是一种比较灵活的引用类型,它用来描述一些还有用但并非必须的对象。如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。

弱引用也是用来描述非必需对象的,它的生命周期只能存活到下一次垃圾收集之前,即只要被垃圾收集器扫描到,就会被回收。例如:WeakReference weakRef = new WeakReference(obj)。

虚引用是最弱的引用类型,无法通过虚引用访问对象本身,仅用于跟踪对象被垃圾回收的状态。例如:ReferenceQueue queue = new ReferenceQueue(); PhantomReference phantomRef = new PhantomReference(obj,queue)。

速记

Java引用有四强,强软虚弱各登场。强引不倒对象扛,内存不足也不亡。软引灵活看情况,内存够时它还在,不足回收把路让。弱引见了垃圾忘,下次回收就离场。虚引最弱用途广,跟踪回收状态详。

21,List<? super T>和List<? extends T>有什么区别?

在Java中,List<? super T>List<? extends T>是两种通配符限定的形式,它们用于处理泛型类型的灵活性和类型安全。

  • List<? super T>:这个表示法表示的是一个列表,它包含的元素是T类型或者T类型的超类型。例如,如果T是类A,那么List<? super A>可以代表一个包含A类型或者A的超类型(比如Object)的列表。这种形式的限定被称为下界通配符(lower-bounded wildcard)。
  • List<? extends T>:这个表示法表示的是一个列表,它包含的元素是T类型或者T类型的子类型。例如,如果T是类A,那么List<? extends A>可以代表一个包含A类型或者A的子类型(比如B)的列表。这种形式的限定被称为上界通配符(upper-bounded wildcard)。
import java.util.ArrayList;  
import java.util.List;  
  
public class Main {  
    public static void main(String[] args) {  
        List<? super T> list = new ArrayList<>();  
        list.add(new T()); // 可以添加T类型的元素  
        Object obj = list.get(0); // 获取元素时只能当作Object类型来处理  
    }  
}

总结一下,? super T? extends T的区别在于:

  • ? super T表示的是T类型或者T类型的超类型,而? extends T表示的是T类型或者T类型的子类型
  • ? super T用于当你希望方法能够处理T类型及其超类型的参数或者返回值,而? extends T用于当你希望方法能够处理T类型及其子类型的参数或者返回值。
  • 在灵活性上,? extends T? super T更灵活,因为它可以处理更多的类型。但是,使用? extends T时需要注意类型安全,因为无法确定具体的类型信息。
import java.util.ArrayList;  
import java.util.List;  
  
public class Main {  
    public static void main(String[] args) {  
        List<? extends T> list = new ArrayList<>();  
        // list.add(new T()); // 编译错误,不允许添加元素  
        T t = list.get(0); // 可以获取T类型或者T的父类型的元素  
    }  
}

速记

? super T 表示T类型或者T类型的超类型,用于处理T类型或者超类型的返回值或者参数,? extends T 表示T类型或者T类型的子类型,用于处理T类型或者其子类型的参数或者返回值,? extends T比? super T可以处理更多的类型。

22,说一说你对Java反射机制的理解

Java的反射机制是一种强大且高级的功能,它允许在运行时访问和修改类、方法、字段和其他元素的信息。简单来说,反射机制使得Java代码能够自我检查和自我修改。

以下是对Java反射机制的深入理解:

  1. 获取类的信息:反射机制可以用于获取类的信息,例如类的名称,类的父类,类实现的接口,类的成员变量,类的方法等。

    程序运行时,可以通过反射获得任意一个类的Class对象,并通过这个对象查看这个类的信息;

    程序运行时,可以通过反射创建任意一个类的实例,并访问该实例的成员;

  2. 动态构造对象:通过反射,我们可以在运行时动态地创建对象,即使我们不知道这个对象的具体类型。我们只需要知道这个对象的类名和构造方法就可以使用反射来创建对象。

  3. 动态调用方法:反射还可以用于动态地调用对象的方法,即使我们不知道这个方法的具体参数类型。我们只需要知道这个方法的名称和参数类型就可以使用反射来调用方法。

  4. 改变字段值:反射可以用于改变对象的字段值,即使我们不知道这个字段的具体类型。我们只需要知道这个字段的名称和类型就可以使用反射来改变字段值。

  5. 动态代理:反射还可以用于实现动态代理,例如在AOP(面向切面编程)中,我们可以通过反射来实现方法的拦截和增强。

虽然反射机制提供了强大的功能,但也存在一些缺点。例如,反射机制会破坏类的封装性,因为通过反射我们可以访问和修改类的私有成员。此外,反射机制也会降低代码的性能和可读性。因此,在使用反射时需要谨慎考虑。

速记

反射本领强,类信它来抢,名称父类接口亮,成员方法也能访。运行实例可创建,动态对象不犯难,类名构造记心间。方法调用真灵活,参数不知也能做。字段值改没话说,名称类型别错过。动态代理作用多,AOP里展风波。不过缺点也挺多,封装破坏性能弱,使用之前多斟酌。

23,Java反射在实际项目中有哪些应用场景?

Java反射在实际项目中有很多应用场景,以下是一些常见的应用场景:

  1. 框架设计:在框架设计中,通常需要使用反射技术来解耦,使框架可扩展和灵活。例如,Spring框架中的依赖注入就是通过反射实现的。
  2. 单元测试:在单元测试中,可以使用反射技术来访问私有或受保护的类成员,使测试更加全面。例如,JUnit等测试框架中,测试类会通过反射获取被测试类的信息并执行测试方法。
  3. 动态代理:使用反射技术可以创建动态代理对象,从而可以在运行时期代理任意一个实现了接口的对象,实现AOP(面向切面编程)等功能。例如,Spring AOP就是基于动态代理实现的。
  4. 框架中对象关系映射:例如,在Hibernate等框架中,对象持久化就需要将对象转化为实体类,然后存储到数据库中,就需要通过反射来动态创建实体类对象、获取类的属性和方法等。
  5. 序列化和反序列化:在Java中,序列化和反序列化都需要使用到反射技术。序列化会将对象转化成字节流,反序列化则将字节流还原为对象。在这个过程中,需要借助反射技术来获取对象的属性信息。
  6. 注解处理器:注解是Java中非常重要的特性,而注解处理器正是通过反射实现的。通过反射技术可以解析注解信息,并执行相应的操作。
  7. 动态的加载类:Java中的ClassLoader就是通过反射实现的。ClassLoader可以在程序运行期间动态的加载类,从而扩展程序功能。

以上是Java反射技术在项目中的一些应用场景,还有很多其他的应用场景,可以根据实际需求进行灵活运用。

速记

反射应用场景多,框架设计解耦合,Spring注入靠它做。单元测试好处多,私有成员能访问。动态代理功能活,AOP里显成果。对象映射要靠我,Hibernate来操作。序列反序别错过,属性信息它掌握。注解处理有奇策,反射解析好处多。动态加载类灵活,程序功能可扩展。实际需求灵活用,反射技术真不错。