面向对象的三大特性是什么?
- 封装: 封装就是将将数据和操作数据的方法捆绑在一起, 形成一个独立的, 可控的单位. 通过封装, 对象的内部实现细节就被隐藏起来了, 这样就只能通过对象提供的接口来访问对象的属性和方法, 这样就保证了数据的安全性和一致性.
// Person 类
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) {
// 年龄范围限制在 0 到 150 之间
if (age >= 0 && age <= 150) {
this.age = age;
} else {
System.out.println("年龄输入错误!");
}
}
}
// 测试类
public class Main {
public static void main(String[] args) {
// 创建 Person 对象
Person person = new Person("张三", 25);
// 获取姓名和年龄
System.out.println("姓名:" + person.getName());
System.out.println("年龄:" + person.getAge());
// 设置年龄为 160,超出范围,会提示错误信息
person.setAge(160);
}
}
- 继承 继承就是顾名思义"子承父业", 子类继承父类, 就可以使用父类的属性和方法, 同时还可以实现自己特有的属性和方法, 通过继承, 可以实现代码的复用和扩展, 减少了代码冗余, 也提高了代码可维护性和可扩展性.
// Animal 类
class Animal {
// Animal 类的方法
public void eat() {
System.out.println("动物正在吃饭");
}
}
// Dog 类,继承自 Animal
class Dog extends Animal {
// Dog 类的方法,重写了父类 Animal 中的 eat 方法
@Override
public void eat() {
System.out.println("狗正在吃骨头");
}
// Dog 类独有的方法
public void bark() {
System.out.println("汪汪汪!");
}
}
// 测试类
public class Main {
public static void main(String[] args) {
// 创建 Animal 对象
Animal animal = new Animal();
animal.eat(); // 输出:动物正在吃饭
// 创建 Dog 对象
Dog dog = new Dog();
dog.eat(); // 输出:狗正在吃骨头
dog.bark(); // 输出:汪汪汪!
}
}
- 多态多态就是同一个类的对象, 在不同的情况下表现出不同的行为, 多态可以提高代码的灵活性和可扩展性.
// Animal 类
class Animal {
// 动物发声的方法
public void makeSound() {
System.out.println("动物发出叫声");
}
}
// Dog 类,继承自 Animal
class Dog extends Animal {
// 重写父类的 makeSound 方法
@Override
public void makeSound() {
System.out.println("狗发出汪汪汪的叫声");
}
}
// Cat 类,继承自 Animal
class Cat extends Animal {
// 重写父类的 makeSound 方法
@Override
public void makeSound() {
System.out.println("猫发出喵喵喵的叫声");
}
}
// 测试类
public class Main {
public static void main(String[] args) {
// 创建 Dog 对象并向上转型为 Animal 类型
Animal animal1 = new Dog();
// 创建 Cat 对象并向上转型为 Animal 类型
Animal animal2 = new Cat();
// 调用 makeSound 方法,由于向上转型,会根据具体对象的类型调用对应的方法
animal1.makeSound(); // 输出:狗发出汪汪汪的叫声
animal2.makeSound(); // 输出:猫发出喵喵喵的叫声
}
}
怎么理解多态特性?
多态通俗点说就是多个对象调用同一个方法, 得到了不同的结果.
想要实现多态, 需要满足几个条件:
- 子类必须继承父类(有继承关系)
- 子类重写父类的方法
- 父类引用指向子类,就是 父类类名 引用名称 = new 子类类名(); 这个就涉及到类型转换的过程, 此处就是 向上转型. 后面会讲到.
通过上面的例子,
- 我们创建了一个 Animal 类和两个子类 Dog 和 Cat。
- 在测试类 Main 中,我们分别创建了一个 Dog 对象和一个 Cat 对象,并将它们向上转型为 Animal 类型。
- 然后,我们调用了这两个对象的 makeSound 方法。由于向上转型,编译器会将这些方法调用解释为 Animal 类型的方法,但在运行时,实际上会根据具体对象的类型调用对应的重写方法,实现了多态的特性。
什么是向上转型和向下转型?
- 向上转型就是把一个子类的对象引用赋给父类引用的过程
在向上转型中, 子类可以自动转化为父类对象, 无需显示转换, 这样就只能访问父类的成员变量和方法, 而不能访问子类中定义的成员变量和方法.
语法格式: 父类类型 引用名称 = new 子类对象(); - 向上转型就是把一个父类的对象引用赋给子类引用的过程
在向下转型中, 需要进行显示的类型转换, 这样父类就能访问子类的特有属性和方法了.
语法格式:子类类型 引用名称 = (子类类型)父类对象;
// Animal 类
class Animal {
public void eat() {
System.out.println("动物正在吃饭");
}
}
// Dog 类,继承自 Animal
class Dog extends Animal {
public void bark() {
System.out.println("汪汪汪!");
}
}
// 测试类
public class Main {
public static void main(String[] args) {
// 向上转型
Animal animal = new Dog();
animal.eat(); // 输出:动物正在吃饭
//animal.bark(); // 编译错误,无法调用子类独有的方法
// 向下转型
Dog dog = (Dog) animal;
dog.bark(); // 输出:汪汪汪!
}
}
Java 可以多继承吗?
Java 是不支持多继承的. 首先是一个菱形继承问题, 其次不支持多继承对于设计者来说, 实现起来也比较简单(简化语言设计), 对于学习者来说学习也比较简单. 还有就是避免了多继承的层次膨胀.
什么是菱形继承问题:
菱形继承问题是指, 一个类继承两个具有相同父类的子类, 这两个类又有着相同的属性和方法, 这时候这个类调用父类相同的方法时编译器就不知所措了.
接口和抽象类有什么区别?
接口和抽象类都是用来定义公共行为的.
- 接口是对行为的抽象, 是抽象方法的一个集合, 利用接口可以实现 API 的定义和实现分离.
- 抽象类是不能实例化的类, 用 abstract 修饰, 主要目的是复用代码, 除了不能实例化, 和普通类区别不大.
具体区别有: - 抽象类是单继承, 接口是多实现
- 抽象类方法和属性访问修饰符使用无限制, 只是抽象类中抽象方法, 不能被 private 修饰, 而接口默认都是 public , 不能被其它修饰符修饰
- 抽象类中普通方法必须要有实现, 抽象方法必须不能有实现, 而接口中的普通方法也不能有实现, 除了默认方法.
- 抽象类是为了复用代码, 而接口是为了定义规范.
方法重写和重载有什么区别?
- 方法重载就是在一个类中定义多个同名的方法, 这些方法具有相同的名字, 但是参数列表不同(参数列表包括:参数个数, 参数类型, 参数顺序).
- 方法重写发生在子父类之间, 就是子类对父类已有的方法进行重新定义, 方法名, 参数列表, 返回值都得一样.
具体区别有:
- 方法重载发生在一个类中, 而方法重写发生在两个类中.
- 方法重载要求参数列表不同, 方法重写要求参数列表相同.
- 方法重载不考虑返回值类型, 方法重写要求返回值相同或是其子类型.
- 方法重载是为了提供多个功能相似的方法, 可以选择不同的参数进行调用, 而方法重写是为了覆盖父类的方法实现具有自身特性的方法.
- 方法重载是静态绑定的, 在编译的时候就确定了调用的方法, 而方法重写是动态绑定的, 在运行的时候根据对象的实际类型选择调用的方法.
// 父类
class Animal {
// 父类方法
public void makeSound() {
System.out.println("动物发出叫声");
}
}
// 子类 Dog 继承自 Animal,并重写了 makeSound 方法
class Dog extends Animal {
// 子类重写父类方法
@Override
public void makeSound() {
System.out.println("狗发出汪汪汪的叫声");
}
}
// 测试类
public class Main {
// 方法重载,两个参数版本
public void printMessage(String message, int count) {
for (int i = 0; i < count; i++) {
System.out.println(message);
}
}
// 方法重载,一个参数版本
public void printMessage(String message) {
System.out.println(message);
}
public static void main(String[] args) {
Main obj = new Main();
// 方法重写示例
Animal animal = new Dog(); // 向上转型
animal.makeSound(); // 输出:狗发出汪汪汪的叫声
// 方法重载示例
obj.printMessage("Hello"); // 输出:Hello
obj.printMessage("Hi", 3); // 输出:Hi Hi Hi
}
}
Animal 类有一个 makeSound 方法,在子类 Dog 中被重写。
Main 类中有两个 printMessage 方法,它们的参数列表不同,构成了方法重载。