Watcher机制(三)之ZooKeeper 一、前言

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
简介: 本文深入分析ZooKeeper客户端源码,重点解析其内部类结构与核心功能。包括ZKWatchManager的监听注册机制、各类WatchRegistration的工厂模式实现、States状态枚举,以及create、delete、exists等核心操作的同步与异步实现原理,揭示ZooKeeper客户端与服务端通信的底层逻辑。(238字)

一、前言
  前面已经分析了Watcher机制中的大多数类,本篇对于ZKWatchManager的外部类Zookeeper进行分析。
二、ZooKeeper源码分析
2.1 类的内部类
  ZooKeeper的内部类框架图如下图所示
  
  
说明:
● ZKWatchManager,Zookeeper的Watcher管理者,其源码在之前已经分析过,不再累赘。
● WatchRegistration,抽象类,用作watch注册。
● ExistsWatchRegistration,存在性watch注册。
● DataWatchRegistration,数据watch注册。
● ChildWatchRegistration,子节点注册。
● States,枚举类型,表示服务器的状态。

  1. WatchRegistration
      接口类型,表示对路径注册监听。  
    abstract class WatchRegistration {
    // Watcher
    private Watcher watcher;
    // 客户端路径
    private String clientPath;

    // 构造函数
    public WatchRegistration(Watcher watcher, String clientPath)
    {

     this.watcher = watcher;
     this.clientPath = clientPath;
    

    }
    // 获取路径到Watchers集合的键值对,由子类实现
    abstract protected Map> getWatches(int rc);
    /**

      * Register the watcher with the set of watches on path.
      * @param rc the result code of the operation that             attempted to
      * add the watch on the path.
      */
    

    // 注册
    public void register(int rc) {

     if (shouldAddWatch(rc)) { // 应该添加监听
         // 获取路径到Watchers集合的键值对,工厂模式
         Map<String, Set<Watcher>> watches = getWatches(rc);
         synchronized(watches) { // 同步块
             // 通过路径获取watcher集合
             Set<Watcher> watchers = watches.get(clientPath);
             if (watchers == null) { // watcher集合为空
                 // 新生成集合
                 watchers = new HashSet<Watcher>();
                 // 将路径和watchers集合存入
                 watches.put(clientPath, watchers);
             }
             // 添加至watchers集合
             watchers.add(watcher);
         }
     }
    

    }
    /**

      * Determine whether the watch should be added based on return code.
      * @param rc the result code of the operation that attempted to add the
      * watch on the node
      * @return true if the watch should be added, otw false
      */
    

    // 判断是否需要添加,判断rc是否为0
    protected boolean shouldAddWatch(int rc) {

     return rc == 0;
    

    }
    }
    说明:可以看到WatchRegistration包含了Watcher和clientPath字段,表示监听和对应的路径,值得注意的是getWatches方式抽象方法,需要子类实现,而在register方法中会调用getWatches方法,实际上调用的是子类的getWatches方法,这是典型的工厂模式。register方法首先会判定是否需要添加监听,然后再进行相应的操作,在WatchRegistration类的默认实现中shouldAddWatch是判定返回码是否为0。

  2. ExistsWatchRegistration 

class ExistsWatchRegistration extends WatchRegistration {
// 构造函数
public ExistsWatchRegistration(Watcher watcher, String clientPath) {
// 调用父类构造函数
super(watcher, clientPath);
}

@Override
protected Map<String, Set<Watcher>> getWatches(int rc) {
    // 根据rc是否为0确定返回dataWatches或existsWatches
    return rc == 0 ?  watchManager.dataWatches : watchManager.existWatches;
}
@Override
protected boolean shouldAddWatch(int rc) {
    // 判断rc是否为0或者rc是否等于NONODE的值
    return rc == 0 || rc == KeeperException.Code.NONODE.intValue();
}

}
说明:ExistsWatchRegistration 表示对存在性监听的注册,其实现了getWatches方法,并且重写了shouldAddWatch方法,getWatches方法是根据返回码的值确定返回dataWatches或者是existWatches。

  1. DataWatchRegistration

class DataWatchRegistration extends WatchRegistration {
// 构造函数
public DataWatchRegistration(Watcher watcher, String clientPath) {
// 调用父类构造函数
super(watcher, clientPath);
}
@Override
protected Map> getWatches(int rc) {
// 直接返回dataWatches
return watchManager.dataWatches;
}
}
说明:DataWatchRegistration表示对数据监听的注册,其实现了getWatches方法,返回dataWatches。

  1. ChildWatchRegistration

class ChildWatchRegistration extends WatchRegistration {
// 构造函数
public ChildWatchRegistration(Watcher watcher, String clientPath) {
// 调用父类构造函数
super(watcher, clientPath);
}
@Override
protected Map> getWatches(int rc) {
// 直接返回childWatches
return watchManager.childWatches;
}
}
说明:ChildWatchRegistration表示对子节点监听的注册,其实现了getWatches方法,返回childWatches。

  1. States

public enum States {
// 代表服务器的状态
CONNECTING, ASSOCIATING, CONNECTED, CONNECTEDREADONLY,
CLOSED, AUTH_FAILED, NOT_CONNECTED;
// 是否存活
public boolean isAlive() {
// 不为关闭状态并且未认证失败
return this != CLOSED && this != AUTH_FAILED;
}
/**

     * Returns whether we are connected to a server (which
     * could possibly be read-only, if this client is allowed
     * to go to read-only mode)
     * */
// 是否连接
public boolean isConnected() {
    // 已连接或者只读连接
    return this == CONNECTED || this == CONNECTEDREADONLY;
}

}
说明:States为枚举类,表示服务器的状态,其有两个方法,判断服务器是否存活和判断客户端是否连接至服务端。
2.2 类的属性  
public class ZooKeeper {
// 客户端Socket
public static final String ZOOKEEPER_CLIENT_CNXN_SOCKET = "zookeeper.clientCnxnSocket";

// 客户端,用来管理客户端与服务端的连接
protected final ClientCnxn cnxn;

// Logger日志
private static final Logger LOG;
static {
    //Keep these two lines together to keep the initialization order explicit
    // 初始化
    LOG = LoggerFactory.getLogger(ZooKeeper.class);
    Environment.logEnv("Client environment:", LOG);
}

 private final ZKWatchManager watchManager = new ZKWatchManager();
}

  说明:ZooKeeper类存维护一个ClientCnxn类,用来管理客户端与服务端的连接。  
2.3 类的构造函数

  1. ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly)型构造函数    

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
boolean canBeReadOnly) throws IOException
{
LOG.info("Initiating client connection, connectString=" + connectString

            + " sessionTimeout=" + sessionTimeout + " watcher=" + watcher);
    // 初始化默认Watcher
    watchManager.defaultWatcher = watcher;
    // 对传入的connectString进行解析
    // connectString 类似于127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002未指定根空间的字符串
    // 或者是127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a指定根空间的字符串,根为/app/a
    ConnectStringParser connectStringParser = new ConnectStringParser(
            connectString);

    // 根据服务器地址列表生成HostProvider
    HostProvider hostProvider = new StaticHostProvider(
            connectStringParser.getServerAddresses());
    // 生成客户端管理
    cnxn = new ClientCnxn(connectStringParser.getChrootPath(),
            hostProvider, sessionTimeout, this, watchManager,
            getClientCnxnSocket(), canBeReadOnly);
    // 启动
    cnxn.start();
}

  说明:该构造函数会初始化WatchManager的defaultWatcher,同时会解析服务端地址和端口号,之后根据服务端的地址生成HostProvider(其会打乱服务器的地址),之后生成客户端管理并启动,注意此时会调用getClientCnxnSocket函数,其源码如下  

private static ClientCnxnSocket getClientCnxnSocket() throws IOException {
// 查看是否在系统属性中进行了设置
String clientCnxnSocketName = System
.getProperty(ZOOKEEPER_CLIENT_CNXN_SOCKET);
if (clientCnxnSocketName == null) { // 若未进行设置,取得ClientCnxnSocketNIO的类名
clientCnxnSocketName = ClientCnxnSocketNIO.class.getName();
}
try {
// 使用反射新生成实例然后返回
return (ClientCnxnSocket) Class.forName(clientCnxnSocketName)
.newInstance();
} catch (Exception e) {
IOException ioe = new IOException("Couldn't instantiate "

                                      + clientCnxnSocketName);
    ioe.initCause(e);
    throw ioe;
}

}
说明:该函数会利用反射创建ClientCnxnSocketNIO实例

  1. public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd, boolean canBeReadOnly) throws IOException型构造函数  

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
long sessionId, byte[] sessionPasswd, boolean canBeReadOnly)
throws IOException
{
LOG.info("Initiating client connection, connectString=" + connectString

            + " sessionTimeout=" + sessionTimeout
            + " watcher=" + watcher
            + " sessionId=" + Long.toHexString(sessionId)
            + " sessionPasswd="
            + (sessionPasswd == null ? "<null>" : "<hidden>"));
    // 初始化默认Watcher
    watchManager.defaultWatcher = watcher;
    // 对传入的connectString进行解析
    // connectString 类似于127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002未指定根空间的字符串
    // 或者是127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a指定根空间的字符串,根为/app/a
    ConnectStringParser connectStringParser = new ConnectStringParser(
            connectString);

    // 根据服务器地址列表生成HostProvider
    HostProvider hostProvider = new StaticHostProvider(
            connectStringParser.getServerAddresses());
    // 生成客户端时使用了session密码
    cnxn = new ClientCnxn(connectStringParser.getChrootPath(),
            hostProvider, sessionTimeout, this, watchManager,
            getClientCnxnSocket(), sessionId, sessionPasswd, canBeReadOnly);

    // 设置客户端的seenRwServerBefore字段为true(因为用户提供了sessionId,表示肯定已经连接过)
    cnxn.seenRwServerBefore = true; // since user has provided sessionId
    // 启动
    cnxn.start();
}

  说明:此型构造函数和之前构造函数的区别在于本构造函数提供了sessionId和sessionPwd,这表明用户已经之前已经连接过服务端,所以能够获取到sessionId,其流程与之前的构造函数类似,不再累赘。
2.4 核心函数分析

  1. create函数  
    函数签名:
    public String create(final String path, byte data[], List acl, CreateMode createMode)
    throws KeeperException, InterruptedException

public String create(final String path, byte data[], List acl,
CreateMode createMode)
throws KeeperException, InterruptedException
{
final String clientPath = path;

    // 验证路径是否合法
    PathUtils.validatePath(clientPath, createMode.isSequential());
    // 添加根空间
    final String serverPath = prependChroot(clientPath);
    // 新生请求头
    RequestHeader h = new RequestHeader();
    // 设置请求头类型
    h.setType(ZooDefs.OpCode.create);
    // 新生创建节点请求
    CreateRequest request = new CreateRequest();
    // 新生创建节点响应
    CreateResponse response = new CreateResponse();
    // 设置请求的数据
    request.setData(data);
    // 设置请求对应的Flag
    request.setFlags(createMode.toFlag());
    // 设置服务器路径
    request.setPath(serverPath);
    if (acl != null && acl.size() == 0) { // ACL不为空但是大小为0,抛出异常
        throw new KeeperException.InvalidACLException();
    }
    // 设置请求的ACL列表
    request.setAcl(acl);
    // 提交请求
    ReplyHeader r = cnxn.submitRequest(h, request, response, null);
    if (r.getErr() != 0) { // 请求的响应的错误码不为0,则抛出异常
        throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                clientPath);
    }
    if (cnxn.chrootPath == null) { // 根空间为空
        // 则返回响应中的路径
        return response.getPath();
    } else {
        // 除去根空间后返回
        return response.getPath().substring(cnxn.chrootPath.length());
    }
}

说明:该create函数是同步的,主要用作创建节点,其大致步骤如下
  ① 验证路径是否合法,若不合法,抛出异常,否则进入②
  ② 添加根空间,生成请求头、请求、响应等,并设置相应字段,进入③
  ③ 通过客户端提交请求,判断返回码是否为0,若不是,则抛出异常,否则,进入④
  ④ 除去根空间后,返回响应的路径
  其中会调用submitRequest方法,其源码如下  

public ReplyHeader submitRequest(RequestHeader h, Record request,
Record response, WatchRegistration watchRegistration)
throws InterruptedException {
// 新生响应头
ReplyHeader r = new ReplyHeader();
// 新生Packet包
Packet packet = queuePacket(h, r, request, response, null, null, null,
null, watchRegistration);
synchronized (packet) { // 同步
while (!packet.finished) { // 如果没有结束
// 则等待
packet.wait();
}
}
// 返回响应头
return r;
}
说明:submitRequest会将请求封装成Packet包,然后一直等待packet包响应结束,然后返回;若没结束,则等待。可以看到其是一个同步方法。

  1. create函数
    函数签名:
    public void create(final String path, byte data[], List acl, CreateMode createMode, StringCallback cb, Object ctx)  

public void create(final String path, byte data[], List acl,
CreateMode createMode, StringCallback cb, Object ctx)
{
final String clientPath = path;

// 验证路径是否合法
PathUtils.validatePath(clientPath, createMode.isSequential());
// 添加根空间
final String serverPath = prependChroot(clientPath);
// 新生请求头
RequestHeader h = new RequestHeader();
// 设置请求头类型
h.setType(ZooDefs.OpCode.create);
// 新生创建节点请求
CreateRequest request = new CreateRequest();
// 新生创建节点响应
CreateResponse response = new CreateResponse();
// 新生响应头
ReplyHeader r = new ReplyHeader();
// 设置请求的数据
request.setData(data);
// 设置请求对应的Flag
request.setFlags(createMode.toFlag());
// 设置服务
request.setPath(serverPath);
// 设置ACL列表
request.setAcl(acl);
// 封装成packet放入队列,等待提交
cnxn.queuePacket(h, r, request, response, cb, clientPath,
                 serverPath, ctx, null);

}
说明:该create函数是异步的,其大致步骤与同步版的create函数相同,只是最后其会将请求打包成packet,然后放入队列等待提交。

  1. delete函数  
    函数签名:public void delete(final String path, int version) throws InterruptedException, KeeperException

public void delete(final String path, int version)
throws InterruptedException, KeeperException
{
final String clientPath = path;
// 验证路径的合法性
PathUtils.validatePath(clientPath);
final String serverPath;
// maintain semantics even in chroot case
// specifically - root cannot be deleted
// I think this makes sense even in chroot case.
if (clientPath.equals("/")) { // 判断是否是"/",即zookeeper的根目录,根目录无法删除
// a bit of a hack, but delete(/) will never succeed and ensures
// that the same semantics are maintained
//
serverPath = clientPath;
} else { // 添加根空间
serverPath = prependChroot(clientPath);
}

    // 新生请求头
    RequestHeader h = new RequestHeader();
    // 设置请求头类型
    h.setType(ZooDefs.OpCode.delete);
    // 新生删除请求
    DeleteRequest request = new DeleteRequest();
    // 设置路径
    request.setPath(serverPath);
    // 设置版本号
    request.setVersion(version);
    // 新生响应头
    ReplyHeader r = cnxn.submitRequest(h, request, null, null);
    if (r.getErr() != 0) { // 判断返回码
        throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                clientPath);
    }
}

说明:该函数是同步的,其流程与create流程相似,不再累赘。

  1. delete函数
    函数签名:public void delete(final String path, int version, VoidCallback cb, Object ctx)
    public void delete(final String path, int version, VoidCallback cb,

         Object ctx)
    

    {

     final String clientPath = path;
    
     // 验证路径是否合法
     PathUtils.validatePath(clientPath);
     final String serverPath;
     // maintain semantics even in chroot case
     // specifically - root cannot be deleted
     // I think this makes sense even in chroot case.
     if (clientPath.equals("/")) { // 判断是否是"/",即zookeeper的根目录,根目录无法删除
         // a bit of a hack, but delete(/) will never succeed and ensures
         // that the same semantics are maintained
         serverPath = clientPath;
     } else {
         serverPath = prependChroot(clientPath);
     }
    
     // 新生请求头
     RequestHeader h = new RequestHeader();
     // 设置请求头类型
     h.setType(ZooDefs.OpCode.delete);
     // 新生删除请求
     DeleteRequest request = new DeleteRequest();
     // 设置路径
     request.setPath(serverPath);
     // 设置版本号
     request.setVersion(version);
     // 封装成packet放入队列,等待提交
     cnxn.queuePacket(h, new ReplyHeader(), request, null, cb, clientPath,
             serverPath, ctx, null);
    

    }

  说明:该函数是异步的,其流程也相对简单,不再累赘。

  1. multi函数  

public List multi(Iterable ops) throws InterruptedException, KeeperException {
for (Op op : ops) { // 验证每个操作是否合法
op.validate();
}
// reconstructing transaction with the chroot prefix
// 新生事务列表
List transaction = new ArrayList();
for (Op op : ops) { // 将每个操作添加根空间后添加到事务列表中
transaction.add(withRootPrefix(op));
}
// 调用multiInternal后返回
return multiInternal(new MultiTransactionRecord(transaction));
}
说明:该函数用于执行多个操作或者不执行,其首先会验证每个操作的合法性,然后将每个操作添加根空间后加入到事务列表中,之后会调用multiInternal函数,其源码如下  
protected List multiInternal(MultiTransactionRecord request)
throws InterruptedException, KeeperException {
// 新生请求头
RequestHeader h = new RequestHeader();
// 设置请求头类型
h.setType(ZooDefs.OpCode.multi);
// 新生多重响应
MultiResponse response = new MultiResponse();
// 新生响应头
ReplyHeader r = cnxn.submitRequest(h, request, response, null);
if (r.getErr() != 0) { // 判断返回码是否为0
throw KeeperException.create(KeeperException.Code.get(r.getErr()));
}
// 获取响应的结果集
List results = response.getResultList();

ErrorResult fatalError = null;
for (OpResult result : results) { // 遍历结果集
    if (result instanceof ErrorResult && ((ErrorResult)result).getErr() != KeeperException.Code.OK.intValue()) { //判断结果集中是否出现了异常
        fatalError = (ErrorResult) result;
        break;
    }
}
if (fatalError != null) { // 出现了异常
    // 新生异常后抛出
    KeeperException ex = KeeperException.create(KeeperException.Code.get(fatalError.getErr()));
    ex.setMultiResults(results);
    throw ex;
}
// 返回结果集
return results;

}
说明:multiInternal函数会提交多个操作并且等待响应结果集,然后判断结果集中是否有异常,若有异常则抛出异常,否则返回响应结果集。

  1. exists函数  
    函数签名:public Stat exists(final String path, Watcher watcher) throws KeeperException, InterruptedException
    public Stat exists(final String path, Watcher watcher)

     throws KeeperException, InterruptedException
    

    {

     final String clientPath = path;
    
     // 验证路径是否合法
     PathUtils.validatePath(clientPath);
     // the watch contains the un-chroot path
     WatchRegistration wcb = null;
     if (watcher != null) { // 生成存在性注册
         wcb = new ExistsWatchRegistration(watcher, clientPath);
     }
     // 添加根空间
     final String serverPath = prependChroot(clientPath);
     // 新生请求头
     RequestHeader h = new RequestHeader();
     // 设置请求头类型
     h.setType(ZooDefs.OpCode.exists);
     // 新生节点存在请求
     ExistsRequest request = new ExistsRequest();
     // 设置路径
     request.setPath(serverPath);
     // 设置Watcher
     request.setWatch(watcher != null);
     // 新生设置数据响应
     SetDataResponse response = new SetDataResponse();
     // 提交请求
     ReplyHeader r = cnxn.submitRequest(h, request, response, wcb);
     if (r.getErr() != 0) { // 判断返回码
         if (r.getErr() == KeeperException.Code.NONODE.intValue()) {
             return null;
         }
         throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                 clientPath);
     }
    
     // 返回结果的状态
     return response.getStat().getCzxid() == -1 ? null : response.getStat();
    

    }
    说明:该函数是同步的,用于判断指定路径的节点是否存在,值得注意的是,其会对指定路径的结点进行注册监听。

  2. exists
    函数签名:public void exists(final String path, Watcher watcher, StatCallback cb, Object ctx) 
    public void exists(final String path, Watcher watcher,
         StatCallback cb, Object ctx)
    
    {
    final String clientPath = path;
    // 验证路径是否合法
    PathUtils.validatePath(clientPath);
    // the watch contains the un-chroot path
    WatchRegistration wcb = null;
    if (watcher != null) { // 生成存在性注册
     wcb = new ExistsWatchRegistration(watcher, clientPath);
    
    }
    // 添加根空间
    final String serverPath = prependChroot(clientPath);
    // 新生请求头
    RequestHeader h = new RequestHeader();
    // 设置请求头类型
    h.setType(ZooDefs.OpCode.exists);
    // 新生节点存在请求
    ExistsRequest request = new ExistsRequest();
    // 设置路径
    request.setPath(serverPath);
    // 设置Watcher
    request.setWatch(watcher != null);
    // 新生设置数据响应
    SetDataResponse response = new SetDataResponse();
    // 将请求封装成packet,放入队列,等待执行
    cnxn.queuePacket(h, new ReplyHeader(), request, response, cb,
                  clientPath, serverPath, ctx, wcb);
    
    }
    说明:该函数是异步的,与同步的流程相似,不再累赘。
    之后的getData、setData、getACL、setACL、getChildren函数均类似,只是生成的响应类别和监听类别不相同,大同小异,不再累赘。
相关文章
|
2天前
|
存储 监控 Java
整合切面,参数拦截+过滤
该类基于Spring AOP实现请求参数日志记录,通过`@Before`、`@Around`和`@After`切面拦截Controller层方法,自动记录请求来源、URL、方式、参数及执行耗时,便于调试与监控,日志通过LogProxy输出,提升系统可观测性。(238字)
|
1天前
|
XML SQL 监控
整合Logback,滚动记录+多文件
`logback-spring.xml` 配置了多模块日志分离输出,按类别将支付、任务、SQL、错误等日志写入不同文件,支持滚动策略与UTF-8编码。通过 `LogProxy.getLogger(&quot;LOG_NAME&quot;)` 获取指定日志器,实现精准日志记录,便于问题追踪与系统监控。(236字符)
|
1天前
|
JSON Java Maven
SpringBoot使用汇总
Spring Boot是Spring框架的延伸,旨在简化Spring应用的初始搭建与开发过程。它通过自动配置、内嵌服务器、开箱即用的依赖等方式,极大减少了项目配置和编码量,提升开发效率。支持快速构建微服务,是Java EE开发的主流趋势。
|
1天前
|
SQL Java 关系型数据库
分页
本文介绍了五种分页实现方式:MyBatis自带RowBounds内存分页、PageHelper插件分页、SQL原生分页、数组分页及拦截器分页。对比了逻辑分页(内存处理)与物理分页(数据库层处理)的优劣,指出大数据量下应优先选用物理分页以避免内存溢出,提升性能。
|
1天前
|
XML JSON Java
映射关系(1-1 1-n n-n)
MyBatis中通过resultMap实现关联映射:一对一使用resultMap解决字段与属性名不一致;一对多在“一”方配置&lt;collection&gt;,如用户包含多个角色;多对一通过&lt;association&gt;关联,如博客关联作者;多对多借助中间类,双方均用&lt;collection&gt;维护集合关系。
|
1天前
|
存储 NoSQL Linux
MongoDB单机部署
提供Win32/64位MongoDB安装包,支持命令行或配置文件启动,Linux与Windows系统均可部署。建议选择y为偶数的稳定版本,通过官网下载并解压,配置data目录及mongod.conf,使用mongod启动服务,mongo命令连接。可选Compass图形化工具管理数据库。注意端口、路径格式与防火墙设置。
|
1天前
|
Java 调度
线程池
线程池通过复用线程提升性能,避免频繁创建销毁的开销。Java中由Executor框架实现,核心为ThreadPoolExecutor,管理线程生命周期与任务调度。通过Executors工厂创建,支持提交异步任务、定时执行等。关键组件包括工作队列、线程工厂与拒绝策略,实现高效并发控制。(238字)
|
2天前
|
存储 安全 Java
Java泛型类型擦除以及类型擦除带来的问题
Java泛型在编译时会进行类型擦除,所有泛型信息被移除,替换为原始类型(如Object或限定类型)。例如,List&lt;String&gt;和List&lt;Integer&gt;在运行时均为List,导致无法通过instanceof判断泛型类型。类型检查在编译期完成,基于引用而非实际对象。擦除后,编译器自动插入强制转换保证类型安全。但这也引发多态冲突、静态成员限制等问题,需通过桥方法等机制解决。基本类型不能作为泛型参数,静态上下文中也不能使用类级别泛型参数。
|
1天前
|
Java
常见加载顺序
本示例展示了Java中各类代码块的执行顺序:静态代码块随类加载仅执行一次,优先于主函数;局部代码块在方法内直接运行;构造代码块每次创建对象前自动执行,早于构造器。输出结果体现三者优先级:静态 &gt; 局部 &gt; 构造。
|
2天前
|
Java 大数据
ArrayList扩容机制
ArrayList添加元素时,先调用ensureCapacityInternal()确保容量,首次添加时默认扩容至10。每次扩容通过grow()实现,新容量为原容量的1.5倍(oldCapacity + (oldCapacity &gt;&gt; 1)),提升性能。add第11个元素时再次触发扩容。length为数组属性,length()是字符串方法,size()用于集合获取元素数。