【Java基础 | 第二篇】面向对象 抽象类 接口
目录
面向对象
面向对象的概念
-
面向对象是一种编程思想,它将现实世界中的事物抽象为对象,对象具有属性和方法。通过对象之间的交互来完成程序的功能,具有灵活性和可扩展性,相比面向过程,它更符合人类的思维习惯,也更适合开发大型复杂的系统。
面向对象的三大特性
-
封装:把对象的属性和方法包装在类中,通过访问修饰符,比如 private,来隐藏内部的实现细节,只对外提供公开的接口,像 getter、setter 方法来进行交互。这样做的好处是可以保证数据的安全性,避免属性被随意修改,同时也提高了代码的可维护性,修改内部逻辑不会影响外部的调用。
-
继承:继承允许一个子类通过 extends 关键字,去复用父类的属性和方法,同时子类还可以扩展自己独有的功能。它体现的是 is-a 的关系,比如 Dog 是 Animal 的子类。Java 只支持单继承,但是可以通过实现接口来达到类似多继承的效果。继承的核心好处是代码复用,减少冗余,方便系统的扩展。
-
多态:同一个行为,在不同的对象上表现出不同的形态。一种是编译时多态,也就是方法的重载,指的是同一个类中,方法名相同,但参数列表不同;一种是运行时多态,也就是方法的重写,子类重写父类的方法,然后通过父类引用指向子类对象的方式来触发(编译看左,执行看右)。多态的好处是提高了代码的灵活性和可扩展性,符合开闭原则。
面向对象的设计原则
核心是为了提高代码的可维护性、可扩展性和复用性。SOLID 五大原则,再加上另外两个常用的原则
1. 单一职责原则(Single Responsibility Principle, SRP)
一个类只负责一项职责,不应该身兼多职。比如用户管理类,就只负责用户的增删改查,而不应该同时处理订单逻辑。这样的好处是类的复杂度降低,可读性和可维护性提高。
2. 开闭原则(Open Closed Principle, OCP)
类(软件实体)应该对扩展开放,对修改关闭。简单说,当需要增加新功能时,我们应该通过新增代码来实现,而不是修改已有代码。比如我们有一个图形绘制类,要新增圆形绘制功能,应该新增一个圆形子类,而不是去修改原有的图形绘制类。
3. 里氏替换原则(Liskov Substitution Principle, LSP)
所有引用基类的地方,必须能透明地使用其子类的对象。也就是说,子类可以扩展父类的功能,但不能改变父类原有的功能。比如父类是 “鸟”,有 “飞” 的方法,子类 “鸵鸟” 就不应该继承这个父类,因为鸵鸟不会飞,强行继承会违反里氏替换原则。
4. 接口隔离原则(Interface Segregation Principle, ISP)
客户端不应该依赖它不需要的接口,应该将大接口拆分成多个小的、专用的接口。比如一个 “多功能设备接口”,包含打印、扫描、复印方法,对于只需要打印功能的客户端来说,就应该提供一个单独的 “打印接口”,而不是让它依赖整个大接口。
5. 依赖倒置原则(Dependency Inversion Principle, DIP)
核心业务逻辑的上层代码,不能直接绑死在做具体实现的下层代码上,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。简单说,就是要面向接口编程,而不是面向实现编程。比如业务逻辑层(高层)不应该直接依赖数据访问层的 MySQL 实现类(低层),而是依赖一个数据访问接口,MySQL 实现类再去实现这个接口。
// 抽象:支付接口(只定义规则,不写具体实现)
interface Payment {
void pay(double amount);
}
// 低层模块1:微信支付(实现抽象接口)
class WeChatPay implements Payment {
@Override
public void pay(double amount) {
System.out.println("使用微信支付" + amount + "元");
}
}
// 低层模块2:支付宝支付(实现同一个抽象接口)
class Alipay implements Payment {
@Override
public void pay(double amount) {
System.out.println("使用支付宝支付" + amount + "元");
}
}
// 高层模块:订单支付(只依赖抽象的Payment接口)
class OrderPayment {
// 不再直接依赖具体类,而是依赖抽象接口
private Payment payment;
// 通过构造方法传入具体的支付实现(这叫“依赖注入”)
public OrderPayment(Payment payment) {
this.payment = payment;
}
public void payOrder(double amount) {
payment.pay(amount); // 只调用抽象接口的方法,不管底层是微信还是支付宝
}
}
6. 迪米特法则(Law of Demeter, LoD)
也叫最少知识原则,一个对象应该对其他对象保持最少的了解。也就是说,只和直接的朋友通信,不要和陌生人说话。比如类 A 依赖类 B,类 B 依赖类 C,类 A 不应该直接调用类 C 的方法,而是通过类 B 来间接调用。
7. 组合 / 聚合复用原则(Composite/Aggregate Reuse Principle, CARP)
优先使用组合或聚合关系复用代码,而不是继承。因为继承会导致类之间的耦合度太高,而组合或聚合可以降低耦合。比如我们要给一个类增加日志功能,不需要让这个类继承日志类,而是可以将日志类作为成员变量组合到这个类中。
多态体现的场景
-
方法重载(编译时多态):
-
方法重载是指同一类中可以有多个同名方法,它们具有不同的参数列表(参数类型、数量或顺序不同)。虽然方法名相同,但根据传入的参数不同,编译器会在编译时确定调用哪个方法。
-
示例:对于一个
add方法,可以定义为add(int a, int b)和add(double a, double b)。
-
-
方法重写(运行时多态):
-
方法重写是指子类能够提供对父类中同名方法的具体实现。在运行时,JVM会根据对象的实际类型确定调用哪个版本的方法。这是实现多态的主要方式。
-
示例:在一个动物类中,定义一个
sound方法,子类Dog可以重写该方法以实现bark,而Cat可以实现meow。
-
-
接口与实现:
-
多态也体现在接口的使用上,多个类可以实现同一个接口,并且用接口类型的引用来调用这些类的方法。这使得程序在面对不同具体实现时保持一贯的调用方式。
-
示例:多个类(如
Dog,Cat)都实现了一个Animal接口,当用Animal类型的引用来调用makeSound方法时,会触发对应的实现。
-
-
向上转型和向下转型:
-
在Java中,可以使用父类类型的引用指向子类对象,这是向上转型。通过这种方式,可以在运行时期采用不同的子类实现。
-
向下转型是将父类引用转回其子类类型,但在执行前需要确认引用实际指向的对象类型以避免
ClassCastException
-
重载与重写
-
重载(Overloading)指的是在同一个类中,可以有多个同名方法,它们具有不同的参数列表(参数类型、参数个数或参数顺序不同),编译器根据调用时的参数类型来决定调用哪个方法。
-
重写(Overriding)指的是子类可以重新定义父类中的方法,方法名、参数列表和返回类型必须与父类中的方法一致,通过@override注解来明确表示这是对父类方法的重写。
Java 创建对象的方式
-
反射:通过 Java 的反射机制可以在运行时动态地创建对象。可以使用 Class 类的 newInstance() 方法或者通过 Constructor 类来创建对象。
class User {
private String name;
// 无参构造
}
public class ReflectCreate {
public static void main(String[] args) throws Exception {
// 方式1:通过Class.newInstance()(仅支持无参构造,Java9后已标记过时)
User u1 = User.class.getDeclaredConstructor().newInstance();
// 方式2:通过Constructor(支持无参/有参构造,推荐)
Constructor<User> constructor = User.class.getConstructor(String.class);
User u2 = constructor.newInstance("张三");
System.out.println(u2.getName()); // 输出:张三
}
}
-
反序列化:通过将对象序列化(保存到文件或网络传输)然后再反序列化(从文件或网络传输中读取对象)的方式来创建对象,对象能被序列化和反序列化的前提是类实现Serializable接口。
// 必须实现Serializable接口
class User implements Serializable {
private String name;
}
public class DeserializeCreate {
public static void main(String[] args) throws Exception {
// 1. 先序列化对象到字节流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(new User("李四"));
// 2. 反序列化(创建新对象)
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
User u = (User) ois.readObject();
System.out.println(u.getName()); // 输出:李四
}
}
-
clone创建对象:所有 Java 对象都继承自 Object 类,Object 类中有一个 clone() 方法,可以用来创建对象的副本,要使用 clone 方法,我们必须先实现 Cloneable 接口并实现其定义的 clone 方法。
class User implements Cloneable {
private String name;
// 重写clone方法(浅拷贝)
@Override
protected Object clone() {
return super.clone();
}
}
public class CloneCreate {
public static void main(String[] args) throws Exception {
User u1 = new User("王五");
User u2 = (User) u1.clone(); // 创建副本
System.out.println(u2.getName()); // 输出:王五
System.out.println(u1 != u2); // 输出:true(是新对象)
}
}
获取私有对象的方法
-
公共访问器方法(getter 方法):如果类的设计者遵循良好的编程规范,通常会为私有成员变量提供公共的访问器方法(即
getter方法),通过调用这些方法可以安全地获取私有对象。 -
反射机制:反射机制允许在运行时检查和修改类、方法、字段等信息,通过反射可以绕过
private访问修饰符的限制来获取私有对象。
class MyClass {
private String privateField = "私有字段的值";
private String privateMethod(String param) {
return "私有方法被调用,参数:" + param + ",私有字段值:" + privateField;
}
}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
// 获取 Class 对象
Class<?> clazz = obj.getClass();
// 获取私有字段
Field privateField = clazz.getDeclaredField("privateField");
// 设置可访问性
privateField.setAccessible(true);
// 获取私有字段的值
String value = (String) privateField.get(obj);
System.out.println(value);
// ========== 新增逻辑:反射调用私有方法 ==========
// 获取私有方法(参数1:方法名,参数2:方法参数类型)
Method privateMethod = clazz.getDeclaredMethod("privateMethod", String.class);
// 关闭访问权限检查
privateMethod.setAccessible(true);
// 调用私有方法(参数1:实例对象,参数2:方法入参)
String methodResult = (String) privateMethod.invoke(obj, "测试参数");
System.out.println("私有方法调用结果:" + methodResult);
}
}
抽象类和接口
抽象类和普通类
-
实例化:普通类可以直接实例化对象,而抽象类不能被实例化,只能被继承。
-
方法实现:普通类中的方法可以有具体的实现,而抽象类中的方法可以有实现也可以没有实现。
-
继承:一个类可以继承一个普通类或者一个抽象类,但可以同时实现多个接口。
-
实现限制:普通类可以被其他类继承和使用,而抽象类一般用于作为基类,被其他类继承和扩展使用。
抽象类和接口
-
设计目的:接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
-
继承和实现:一个类只能继承一个类(包括抽象类),因为 Java 不支持多继承。但一个类可以实现多个接口,一个接口也可以继承多个其他接口。
-
成员变量:接口中的成员变量只能是
public static final类型的,不能被修改且必须有初始值。抽象类的成员变量可以有任何修饰符(private,protected,public),可以在子类中被重新定义或赋值。 -
方法:
Java 8 之前,接口中的方法默认是 public abstract ,也就是只能有方法声明。自 Java 8 起,可以在接口中定义 default(默认) 方法和 static (静态)方法。 自 Java 9 起,接口可以包含 private 方法。
抽象类可以包含抽象方法和非抽象方法。抽象方法没有方法体,必须在子类中实现。非抽象方法有具体实现,可以直接在抽象类中使用或在子类中重写。
抽象类能否被实例化
不能。不能使用new关键字直接创建一个抽象类的对象。抽象类的存在主要是为了被继承。
抽象类可以有构造器,这些构造器只是为了子类实例化时被调用,以便进行必要的初始化工作。然而,这个过程并不是直接实例化抽象类,而是创建了子类实例的过程中,间接使用了抽象类的构造器。
接口里面的方法类型
-
抽象方法:抽象方法是接口的核心部分,所有实现接口的类都必须实现这些方法。抽象方法默认是
public abstract,这些修饰符可以省略。 -
默认方法:默认方法是在 Java 8 中引入的,允许接口提供具体实现。实现类可以选择重写默认方法。
-
静态方法:静态方法也是在 Java 8 中引入的,它们属于接口本身,可以通过接口名直接调用,而不需要实现类的对象。
-
私有方法:私有方法是在 Java 9 中引入的,用于在接口中为默认方法或其他私有方法提供辅助功能。这些方法不能被实现类访问,只能在接口内部使用。
上述内容也同步在我的飞书,欢迎访问
https://my.feishu.cn/wiki/QLauws6lWif1pnkhB8IcAvkhncc?from=from_copylink
如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,你们的支持就是我坚持下去的动力!
更多推荐




所有评论(0)