20240121
# OOP面向对象
当然可以,从三个方面来分析面向对象编程(Object-Oriented Programming, OOP):
What(是什么)
- 面向对象编程是一种计算机编程范式或方法论,它将程序设计组织为相互作用的对象集合。在OOP中,数据和操作这些数据的函数被封装在一起,形成一个称为“类”(Class)的模板。类是现实世界实体的抽象模型,包含了属性(即数据成员,描述对象的状态)和方法(即成员函数,描述对象的行为)。
- 对象则是类的一个实例,每个对象有自己的状态和行为。通过创建对象,程序员可以在程序中模拟现实生活中的各种实体及其交互关系。
Why(为什么存在)
- 结构化与模块化:OOP提高了代码的结构化程度,使得大型复杂软件项目的开发和维护更为方便。通过封装、继承和多态等机制,OOP有助于降低模块间的耦合度,增强模块内部的内聚性,从而提高软件的可读性、可维护性和扩展性。
- 复用与重用:通过定义类和继承机制,OOP允许子类继承父类的属性和方法,避免重复编写相同的代码,实现代码的重用,减少开发时间和成本。
- 问题解决模式:面向对象的方法符合人类理解和解决问题的自然方式,它能够更好地模拟现实世界的概念和逻辑,使得设计出的系统更加直观、易于理解,更接近于领域专家的语言和思考方式。
How(如何使用)
- 封装(Encapsulation):通过定义类,并将其属性设置为私有(private),只通过公共接口(public methods)来访问或修改属性值,隐藏了对象内部的具体实现细节。
- 继承(Inheritance):子类可以从父类继承已有的属性和方法,并可以添加新的属性和方法,或者覆盖(override)父类的方法以提供不同的实现。
- 多态(Polymorphism):同一消息可以根据发送对象的不同而产生不同的行为。例如,在Java中,可以通过接口实现多态,也可以通过重写父类方法实现运行时多态。
- 创建对象:通过类的构造函数创建对象实例,然后对对象进行操作,调用其方法完成特定任务。
- 设计原则与模式:遵循诸如单一职责原则、开闭原则等面向对象的设计原则,并应用如工厂模式、策略模式等设计模式,使代码更具灵活性和可扩展性。
举例说明:
// 定义一个Animal类
class Animal {
String name;
int age;
// 构造函数
Animal(String n, int a) {
this.name = n;
this.age = a;
}
// 方法:eat
public void eat() {
System.out.println(name + " is eating.");
}
}
// 创建Dog类,继承自Animal
class Dog extends Animal {
// 继承Animal的同时添加新属性
String breed;
// Dog类的构造函数
Dog(String n, int a, String b) {
super(n, a); // 调用父类构造函数初始化基础属性
this.breed = b;
}
// 重写父类的方法
@Override
public void eat() {
System.out.println(name + " (" + breed + ") is eating dog food.");
}
}
// 使用面向对象的方式
public class Main {
public static void main(String[] args) {
Animal animal = new Animal("Generic Animal", 5);
animal.eat();
Dog dog = new Dog("Buddy", 3, "Golden Retriever");
dog.eat(); // 这里体现了多态,虽然都是eat方法,但输出内容不同
}
}
# Java反射机制
- What(是什么)
- Java反射机制是在运行时对Java类的元数据进行动态访问和操作的能力。它允许程序在执行过程中获取类的信息(如类名、方法、字段等),并且可以创建类的对象,调用其方法或修改其字段值,而这些行为在编译期是未知的或者未确定的。
- 在Java中,通过
java.lang.Class
类及其相关API来实现反射功能。一旦获得了某个类的Class对象,就可以使用这个对象来获取类的所有公共/私有属性、方法、构造函数以及注解信息,并能够进行动态调用和修改。
# 为什么需要反射?(反射的作用/应用场景)
反射的作用可以用一句话概括:反射赋予了jvm动态编译的能力。动态编译可以最大限度的体现Java的灵活性(多态)。
否则类的元信息只能通过静态编译的形式实现(在编译期确定类型,绑定对象),而不能实现动态编译(在运行期确定类型,绑定对象)。也就是说在编译以后,程序在运行时的行为就是固定的了,如果要在运行时改变程序的行为,就需要动态编译,在Java中就需要反射机制。
情景一:不得已而为之
有的类是我们在编写程序的时候无法使用new一个对象来实例化对象的。例如:
- 调用的是来自网络的二进制.class文件,而没有其.java代码;
- 注解 - 注解本身仅仅是起到标记作用,它需要利用反射机制,根据注解标记去调用注解解释器,执行行为。如果没有反射机制,注解并不比注释更有用。
情景二:动态加载(可以最大限度的体现Java的灵活性,并降低类的耦合性:多态)
有的类可以在用到时再动态加载到jvm中,这样可以减少jvm的启动时间,同时更重要的是可以动态的加载需要的对象(多态)。例如:
- 动态代理 - 在切面编程(AOP)中,需要拦截特定的方法,通常,会选择动态代理方式。这时,就需要反射技术来实现了。
情景三:避免将程序写死到代码里
因为java代码是先通过编译器将.java文件编译成.class的二进制字节码文件,因此如果我们使用new Person()来实例化对象person会出现的问题就是如果我们希望更换person的实例对象,就要在源代码种更改然后重新编译再运行,但是如果我们将person的实例对象类名等信息编写在配置文件中,利用反射的Class.forName(className)方法来实例化java对象(因为实例化java对象都是根据全限定名查找到jvm内存中的class对象,并根据class对象中的累信息实例化得到java对象,因此xml文件中只要包含了权限定类名就可以通过反射实例化java对象),那么我们就可以更改配置文件,无需重新编译。例如:
- 开发通用框架 - 反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 JavaBean、Filter 等),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射——运行时动态加载需要加载的对象。
# 反射的缺点
- 性能开销 - 由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能要比非反射操作的性能要差,应该在性能敏感的应用程序中频繁调用的代码段中避免。
- 破坏封装性 - 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
- 内部曝光 - 由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,所以反射的使用可能会导致意想不到的副作用,这可能会导致代码功能失常并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为。
How(如何使用)
- 获取Class对象:
// 通过类名字符串 Class<?> clazz = Class.forName("com.example.MyClass"); // 直接从已知类引用 Class<?> clazz = MyClass.class;
- 获取Class对象:
- 创建对象:
```java
Constructor<?> ctor = clazz.getConstructor(String.class, int.class);
Object instance = ctor.newInstance("constructor arg", 42);
- 访问字段:
Field field = clazz.getDeclaredField("myField"); field.setAccessible(true); // 如果是私有字段需要此行 Object fieldValue = field.get(instance); field.set(instance, newValue);
- 调用方法:
```java
Method method = clazz.getMethod("myMethod", String.class);
Object result = method.invoke(instance, "method arg");
- 使用反射还可以检查和修改类的注解,获取泛型信息,处理数组类型等。
# 创建动态代理对象
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Service {
void doSomething();
}
class RealService implements Service {
@Override
public void doSomething() {
System.out.println("Real service doing something...");
}
}
class LoggingInvocationHandler implements InvocationHandler {
private final Object target;
public LoggingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before calling " + method.getName());
// 调用目标方法,并获取返回值
Object result = method.invoke(target, args);
System.out.println("After calling " + method.getName());
return result;
}
}
public class DynamicProxyExample {
public static void main(String[] args) {
// 创建真实服务对象
Service realService = new RealService();
// 创建代理对象
Service proxyService = (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class<?>[]{Service.class},
new LoggingInvocationHandler(realService)
);
// 通过代理对象调用方法
proxyService.doSomething();
}
}
在这个例子中,我们利用Java反射机制实现了动态代理。Proxy.newProxyInstance()
方法可以动态地创建一个实现了给定接口的新类实例,并指定一个InvocationHandler来处理所有接口方法的调用。当通过代理对象调用doSomething()
方法时,实际执行的是LoggingInvocationHandler
中的invoke()
方法,它在调用原始方法前后添加了日志输出。
这种动态代理技术广泛应用于AOP(面向切面编程)、权限控制、性能监控、事务管理等场景,这些功能在编译期往往是未知或不固定的,因此只能通过运行时反射机制来实现。
# Java8、17特性
# JDK 8(2014年发布)
主要新特性:
- Lambda 表达式和函数式接口 - 引入了一种新的简洁语法来表示匿名函数,简化了集合流操作和其他函数式编程场景的处理。
- Stream API - 提供了一种高效且易于理解的方式来处理集合数据,支持过滤、映射、排序、聚合等多种操作。
- Date-Time API (JSR-310) - 新的时间日期API替代了旧的
java.util.Date
和Calendar
类,提供了更易用且线程安全的日期时间类如LocalDate
、LocalTime
、LocalDateTime
等。 - 接口默认方法与静态方法 - 接口可以定义默认方法(default methods)和静态方法,允许向现有的接口添加功能而不破坏实现该接口的类。
- 方法引用与构造器引用 - 简化了对现有方法或者构造器的引用方式,增强了代码可读性。
- 类型注解(Type Annotations) - 可以在类型的声明上使用注解,提供更多的编译时信息,有利于编译器进行类型检查以及工具生成代码。
- Nashorn JavaScript引擎增强 - 改进了JavaScript执行环境,让Java应用能够更好地与JavaScript交互。
# JDK 17(2021年发布)
主要新特性与升级:
- 模块系统(Project Jigsaw) - 引入了JEP 200(模块系统),提供了更好的封装性和安全性,使得库和应用程序可以以模块化的形式构建和部署。
- 文本块(Text Blocks) - JEP 378,简化多行字符串和模板字符串的创建,特别适用于JSON或XML文档的编写。
- Records - JEP 395,简化不可变数据类的定义,自动产生equals()、hashCode()、toString()方法。
- Sealed Classes - JEP 400,引入密封类和接口,限制了子类化的可能性,提高了API设计的安全性和灵活性。
- Switch表达式 - JEP 361,改进switch语句,增加了模式匹配和yield关键字以返回值,使得switch更加类似于其他现代语言中的模式匹配语句。
- 移除和废弃功能 - 持续删除不再推荐使用的API,比如Applet API,并进一步完善了垃圾收集机制及其他性能优化。
- ZGC(低延迟垃圾回收器) - 虽非JDK 17独有的特性,但在JDK 17中得到进一步加强和稳定,提供了更低的暂停时间和更高的吞吐量。
- 并行全垃圾收集(Parallel Full GC) - 作为G1垃圾收集器的一部分进行了优化,提升了大堆内存下的性能表现。
总体来说,JDK 8奠定了Java在函数式编程和集合处理上的基础,而JDK 17则在模块化、类型系统、语法简化以及性能优化等方面做出了重大改进,同时保持了向后兼容性。随着Java版本的迭代,后续版本还引入了更多关于并发、内存管理和安全性的改进措施,旨在提升开发者生产力并保证Java平台的长期可持续发展。
# 设计模式、工厂模式,策略模式,单例模式等
# 工厂模式 (Factory Pattern)
What(是什么): 工厂模式是一种创建型设计模式,它提供了一种封装对象创建过程的方式,使得客户端无需知道具体的产品类是何时何地如何被实例化的。工厂模式定义了一个接口用于创建对象,但让子类决定实例化哪一个类。
Why(为什么存在):
- 解耦:将对象的创建与使用分离,客户端不直接创建产品对象,而是通过调用工厂方法来获取,这样可以减少代码间的耦合度。
- 灵活性:在不需要修改客户端代码的情况下,能够更换不同产品对象的具体实现,方便系统扩展和维护。
- 抽象复杂创建过程:当一个类的实例化逻辑较为复杂或者需要根据条件动态生成不同的实例时,工厂模式有助于隐藏这些细节。
使用场景
- 数据库连接管理:不同的数据库(如MySQL、Oracle)需要不同的连接对象,工厂模式可以根据配置信息返回对应数据库的连接实例。
Java
1public interface DatabaseConnectionFactory { 2 DatabaseConnection createConnection(); 3} 4 5public class MySQLConnectionFactory implements DatabaseConnectionFactory { 6 @Override 7 public DatabaseConnection createConnection() { 8 return new MySQLConnection(); // 创建MySQL数据库连接实例 9 } 10} 11 12public class OracleConnectionFactory implements DatabaseConnectionFactory { 13 @Override 14 public DatabaseConnection createConnection() { 15 return new OracleConnection(); // 创建Oracle数据库连接实例 16 } 17} 18 19// 客户端代码 20DatabaseConnectionFactory factory = getFactoryBasedOnConfig(); // 根据配置获取不同数据库的工厂 21DatabaseConnection conn = factory.createConnection(); // 获取数据库连接
- 日志框架创建日志记录器:例如,在Log4j或SLF4J等日志框架中,可以通过LoggerFactory来根据类名动态地获取一个日志记录器实例。
# 策略模式 (Strategy Pattern)
What(是什么): 策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。该模式定义了算法族,并分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用它的客户。
Why(为什么存在):
- 策略复用:多种可互换的算法可以在不同情况下复用,避免了重复代码。
- 开放封闭原则:允许在不修改现有代码的基础上增加新的行为策略。
- 灵活性:可以根据运行时环境或用户需求动态选择不同的策略进行执行。
# 策略模式
- 支付方式切换:在电商系统中,用户可以选择多种支付策略(如支付宝、微信支付、银行卡支付),每种支付方式对应一个策略类,客户端可以灵活选择并执行支付逻辑。
Java
1public interface PaymentStrategy { 2 void pay(Order order, double amount); 3} 4 5public class AlipayStrategy implements PaymentStrategy { 6 @Override 7 public void pay(Order order, double amount) { 8 // 执行支付宝支付逻辑 9 } 10} 11 12public class WeChatPayStrategy implements PaymentStrategy { 13 @Override 14 public void pay(Order order, double amount) { 15 // 执行微信支付逻辑 16 } 17} 18 19// 客户端代码 20PaymentStrategy strategy = getPaymentStrategyFromUserChoice(); // 根据用户选择获取支付策略 21strategy.pay(order, totalAmount); // 调用相应支付策略进行支付
- 排序算法的选择:在数据处理过程中,可能需要对一组数据应用不同的排序策略。策略模式可以封装各种排序算法,并允许在运行时轻松切换。
# 单例模式 (Singleton Pattern)
What(是什么): 单例模式确保一个类仅有一个实例,并提供一个全局访问点。在整个应用生命周期中,这个类只能创建唯一的一个对象实例。
Why(为什么存在):
- 资源控制:对于那些需要严格控制全局只有一个实例的对象(如数据库连接池、线程池、配置类等),单例模式保证了资源不会被多次初始化而浪费。
- 一致性要求:某些场景下,需要保持全局状态的一致性,例如应用程序的日志系统或缓存管理器。
使用场景
- 配置管理器:在整个应用程序生命周期内,配置信息通常只需要加载一次,通过单例模式确保配置类只有一个实例,避免重复加载和资源浪费。
Java
1public class AppConfig { 2 private static volatile AppConfig instance; 3 private Map<String, String> configData; 4 5 private AppConfig() { 6 // 从文件/数据库加载配置数据 7 } 8 9 public static AppConfig getInstance() { 10 if (instance == null) { 11 synchronized (AppConfig.class) { 12 if (instance == null) { 13 instance = new AppConfig(); 14 } 15 } 16 } 17 return instance; 18 } 19 20 public String getConfig(String key) { 21 return configData.get(key); 22 } 23} 24 25// 在客户端代码中获取配置 26String someValue = AppConfig.getInstance().getConfig("someKey");
- 线程池管理:Java中的
Executors
类就使用了单例模式来创建固定大小的线程池,保证了全局唯一且高效的线程复用。