【Java面试】Java面经-语法基础部分

1. 面向对象特性之封装性

Java中的封装性指的是将数据和方法封装在一个类中,并通过访问修饰符控制对它们的访问。封装的主要目的是隐藏类的内部实现细节,从而使类更易于使用和维护。

通过将类的数据和方法封装起来,并将其访问权限限制在合适的范围内,可以有效地控制类的行为和使用。这有助于提高代码的安全性和可维护性,同时也使代码更加易于理解和重用。

优点:

  • 提高代码的可维护性:通过封装类的数据和方法,可以将类的内部实现细节隐藏起来,使得类的使用者无法直接访问它们。
  • 提高代码的安全性:通过限制对类成员的访问,可以防止类成员被误用或者滥用。例如,将某些敏感数据成员设置为private,可以防止外部程序直接访问这些数据,从而提高了代码的安全性。
  • 提高代码的可重用性:封装类的数据和方法可以将其作为一个独立的模块,可以方便地在其他程序中进行重用。
  • 简化代码的复杂度:通过封装数据和方法,可以将一些复杂的操作封装起来,从而简化代码的复杂度。
  • 降低代码耦合度:通过封装类的数据和方法,可以使不同的模块之间的耦合度降低。

举例说明:
在下面这个例子中,Person类封装了两个数据成员name和age,以及一个方法sayHello()。这些数据成员被声明为private,意味着只能在Person类内部被访问。外部程序不能直接访问这些数据成员,而只能通过Person类的公共接口来访问它们。

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 void setName(String name) {
        this.name = name;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
    
    public void sayHello() {
        System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
    }
}

2. 面向对象特性之继承性

在Java中,继承是面向对象编程的核心概念之一,它是一种定义新类的方式,这个新类可以继承一个或多个已存在的类的属性和方法。通过继承,子类可以获得父类的属性和方法,并且可以在此基础上进行修改或扩展。

继承的主要优点是可以减少代码的重复,提高代码的可维护性和可扩展性。子类可以重写父类的方法,以实现自己的特定需求,从而在代码的复用性和灵活性方面提供更多的选择。

在Java中,继承是通过extends关键字来实现的,子类通过extends关键字来继承父类,如下所示:

class Rectangle {
    protected int width;
    protected int height;
    
    public void setWidth(int w) {
        width = w;
    }
    
    public void setHeight(int h) {
        height = h;
    }
    
    public int getArea() {
        return width * height;
    }
}

class Square extends Rectangle {
    public void setWidth(int w) {
        width = w;
        height = w;
    }
    
    public void setHeight(int h) {
        width = h;
        height = h;
    }
}

public class Main {
    public static void main(String[] args) {
        Rectangle rect = new Rectangle();
        rect.setWidth(5);
        rect.setHeight(10);
        System.out.println("Rectangle area: " + rect.getArea());
        
        Square square = new Square();
        square.setWidth(5);
        System.out.println("Square area: " + square.getArea());
    }
}

在上面这个例子中,Rectangle类表示矩形,包括宽度和高度两个属性,以及计算矩形面积的方法getArea()。Square类继承了Rectangle类,并重写了setWidth()和setHeight()方法来保证正方形的特性。在main方法中,我们创建了一个Rectangle对象和一个Square对象,并分别计算它们的面积。通过继承,Square类不仅可以访问Rectangle类的属性和方法,还能够在此基础上添加新的方法和属性。

3. 面向对象特性之多态性

多态性是指一个对象在不同的情况下表现出不同的形态。

具体来说,多态性包括两种形式:编译时多态和运行时多态。

编译时多态是通过方法重载和方法重写实现的。方法重载是指在同一个类中定义多个同名方法,但参数类型或个数不同,编译器在编译时会根据不同的参数类型或个数来选择调用哪一个方法。方法重写是指子类重写了父类的方法,但方法名、返回类型和参数列表都必须和父类的方法相同,编译器在编译时不知道具体调用哪一个方法,而是在运行时根据实际类型来决定。

运行时多态是通过Java中的动态绑定机制来实现的。动态绑定是指程序在运行时根据对象的实际类型来调用相应的方法,而不是根据对象的引用类型来调用方法。例如:

class Animal {
    public void makeSound() {
        System.out.println("The animal makes a sound");
    }
}

class Dog extends Animal {
    public void makeSound() {
        System.out.println("The dog barks");
    }
}

class Cat extends Animal {
    public void makeSound() {
        System.out.println("The cat meows");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal a1 = new Dog();
        Animal a2 = new Cat();
        a1.makeSound(); // The dog barks
        a2.makeSound(); // The cat meows
    }
}

在上面这个例子中,Animal是一个基类,包含一个makeSound()方法。Dog和Cat是Animal的子类,它们分别重写了makeSound()方法。在main方法中,我们创建了一个Dog对象和一个Cat对象,并将它们赋值给Animal类型的引用变量a1和a2。由于a1和a2的实际类型分别是Dog和Cat,所以调用makeSound()方法时,程序会自动选择调用Dog和Cat的makeSound()方法,而不是Animal的makeSound()方法。这就是运行时多态的体现。

多态性的主要优点是提高了代码的可扩展性和灵活性。当我们在编写程序时,通常不知道具体要处理哪些对象,因此我们需要一个通用的代码框架来处理不同类型的对象。多态性正是为我们提供了这样一个框架,让我们能够在不知道对象类型的情况下调用相应的方法,从而提高代码的复用性和可维护性。

4. a = a + b 与 a += b 的区别

+= 隐式的将加操作的结果类型强制转换为持有结果的类型。如果两个整型相加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法操作。

byte a = 127;
byte b = 127;
b = a + b; // error : cannot convert from int to byte
b += a; // ok

因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错

5. 3*0.1 == 0.3 将会返回什么? true 还是 false?

在 Java 中,3 * 0.1 == 0.3 将会返回 false。

这是因为浮点数在计算机中是以二进制表示的,并且不能精确地表示像 0.1 这样的分数。所以,3 * 0.1 的计算结果可能会略微偏离 0.3,因此它与 0.3 的比较将会返回 false。

在使用浮点数时,应该注意它们的舍入误差问题。如果需要比较两个浮点数的大小或相等性时,最好使用带有容差范围的比较,例如:

double a = 3 * 0.1;
double b = 0.3;
double epsilon = 0.0001;
if (Math.abs(a - b) < epsilon) {
    // 两个数近似相等
}

这里的 epsilon 变量是一个非常小的值,表示容忍的舍入误差范围,可以根据具体情况进行调整。

6. 能在 Switch 中使用 String 吗?

在Java 7之前,switch语句只支持基本数据类型(如int、char等)和枚举类型作为判断条件。因此,不能直接在switch语句中使用String类型。

但是,从Java 7开始,Java支持在switch语句中使用String类型。这意味着您可以使用String类型来替换较旧的枚举类型或整数类型。例如:

String fruit = "apple";
switch (fruit) {
    case "apple":
        System.out.println("Selected fruit is apple");
        break;
    case "banana":
        System.out.println("Selected fruit is banana");
        break;
    case "orange":
        System.out.println("Selected fruit is orange");
        break;
    default:
        System.out.println("Invalid fruit selection");
}

在这个例子中,switch语句中的判断条件是一个String类型的变量。每个case语句中的值也是一个String类型的常量。

需要注意的是,在使用String类型作为switch语句的判断条件时,它会使用equals()方法来比较字符串的值,而不是使用==运算符。这意味着如果两个字符串的值相等,但是它们不是同一个对象,switch语句也会将它们视为相等的。本质上,仅仅是一个语法糖,内部实现在 switch 中使用字符串的 hash code。

7. 为什么在重写 equals 方法的时候需要重写 hashCode 方法?

原生的hashcode是根据对象的内存地址经哈希算法得来的。
equals方法是严格判断一个对象是否相等的方法(object1 == object2),比较的是两个对象的内存地址。
在我们的业务系统中判断对象时有时候需要的不是一种严格意义上的相等,而是一种业务上(内容)的对象相等。在这种情况下,原生的equals方法就不能满足我们的需求了,所以这个时候我们需要重写equals方法,来满足我们的业务系统上的需求。

如果只重写了equals方法而没有重写hashCode方法的话,则会违反约定:相等的对象必须具有相等的散列码(hashCode)。

在Java中,散列表(如HashMap和HashSet)使用对象的哈希码来确定它们在散列表中的位置,从而快速地查找和访问它们。如果两个对象在equals()方法的比较中被视为相等,那么它们必须具有相同的哈希码,否则它们可能被散列表错误地视为不同的对象。

8. final、finalize 和 finally 的不同之处?

final:final是一个修饰符,可以用来修饰类、方法和变量。如果一个类被声明为final,则表示该类不能被继承;如果一个方法被声明为final,则表示该方法不能被重写;如果一个变量被声明为final,则表示该变量只能被赋值一次,即变成常量。

finalize:finalize是一个方法,它是在Java中的Object类中定义的一个受保护的方法,当垃圾收集器确定不再有对该对象的引用时,垃圾收集器在回收该对象之前会调用该方法。但是,应该注意到,由于垃圾回收的时间是不确定的,因此使用finalize方法进行资源释放是不可靠的,不应该依赖于它。

finally:finally是一个关键字,用于定义一个代码块,在try-catch块中使用,无论异常是否被捕获,finally中的代码总是会被执行。通常,finally块用于清理资源,例如关闭文件或数据库连接等。

9. String、StringBuffer与StringBuilder的区别?

String是一个不可变类,一旦创建就不能被修改。每次对String进行修改都会创建一个新的对象,这对于频繁的字符串操作会带来性能问题。String适用于存储不可变的、固定长度的字符串,例如数据库连接URL或文件路径等。

StringBuffer是一个线程安全的、可变的类。它的内部使用了同步锁来保证线程安全性。StringBuffer支持在原字符串上直接进行修改,而不是每次都创建新的对象。因此,它适用于频繁的字符串操作和多线程环境下的字符串操作。

StringBuilder是一个非线程安全的、可变的类,它是StringBuffer的轻量级实现。与StringBuffer不同,StringBuilder不支持同步锁,因此在单线程环境下运行速度更快。由于StringBuilder不支持同步,因此它不适用于多线程环境。

10. this() & super()在构造方法中的区别?

this()是一个特殊的构造方法调用,用于调用本类中的其他构造方法。使用this()关键字时,它必须是构造方法中的第一条语句,用于调用同一个类中的其他构造方法。

super()是一个特殊的构造方法调用,用于调用父类中的构造方法。使用super()关键字时,它也必须是构造方法中的第一条语句,用于调用父类的构造方法。如果在子类的构造方法中没有显式地调用super()关键字,则Java编译器会自动插入一个调用父类默认构造方法的super()语句。

11. 接口与抽象类的区别?

实现方式不同:
抽象类使用abstract关键字定义,可以包含抽象方法和具体方法,具体方法可以有实现代码。
接口使用interface关键字定义,只能包含抽象方法和常量,方法没有实现代码。

继承方式不同:
一个类只能继承一个抽象类,但可以实现多个接口。

构造方法不同:
抽象类可以有构造方法,接口没有构造方法。

成员变量不同:
抽象类可以有实例变量和静态变量,而接口只能有常量。

访问修饰符不同:
抽象类中的方法可以有不同的访问修饰符,而接口中的方法只能是public的。

默认实现不同:
抽象类中的抽象方法可以有默认的实现,而接口中的方法没有实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

天`南

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

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

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

打赏作者

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

抵扣说明:

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

余额充值