详谈c++智能指针!!!

文章目录

  • 前言
  • 一、智能指针的发展历史
    • 1.C++ 98/03 的尝试——std::auto_ptr
    • 2.std::unique_ptr
    • 3.std::shared_ptr
    • 4.std::weak_ptr
    • 5.智能指针的大小
    • 6.智能指针使用注意事项
  • 二、智能指针的模拟实现
  • 三、C++11和boost中智能指针的关系


前言

C/C++ 语言最为人所诟病的特性之一就是存在内存泄露问题,因此后来的大多数语言都提供了内置内存分配与释放功能,有的甚至干脆对语言的使用者屏蔽了内存指针这一概念。这里不置贬褒,手动分配内存与手动释放内存有利也有弊,自动分配内存和自动释放内存亦如此,这是两种不同的设计哲学。有人认为,内存如此重要的东西怎么能放心交给用户去管理呢?而另外一些人则认为,内存如此重要的东西怎么能放心交给系统去管理呢?在 C/C++ 语言中,内存泄露的问题一直困扰着广大的开发者,因此各类库和工具的一直在努力尝试各种方法去检测和避免内存泄露,如 boost,智能指针技术应运而生。


一、智能指针的发展历史

1.C++ 98/03 的尝试——std::auto_ptr

auto_ptr 是c++ 98定义的智能指针模板,其定义了管理指针的对象,可以将new 获得(直接或间接)的地址赋给这种对象。当对象过期时,其析构函数将使用delete 来释放内存!

class Date {
private:int year_;int month_;int day_;
public:Date(int year = 2024, int month = 1, int day =1):year_(year), month_(month), day_(day){}
};int main()
{auto_ptr<Date> d1(new Date(2008, 1, 1));return 0;}

通过上面的代码我们发现,智能指针实现了自动管理,在对象生命周期结束时,会自动释放内存,不需要程序员手动释放,减轻了程序员的负担。

但为什么auto_ptr会被抛弃呢?是因为它的实现原理有局限性:

  • 拷贝或赋值会改变资源的所有权
  • 在STL容器中使用auto_ptr存在着重大风险,因为容器内的元素必须支持可复制和可赋值
  • 不支持对象数组的内存管理

我们用调试的监视窗口可以看到,auto_ptr在赋值和拷贝时,是用控制权转移实现的,所以它不能用在STL 容器中,因为容器内的元素必须支持赋值和拷贝。

在这里插入图片描述

另外,auto_ptr 不能支持数组对象的管理,所以c++用更严谨的unique_ptr代替了auto_ptr

在这里插入图片描述

2.std::unique_ptr

std::unique_ptr 对其持有的堆内存具有唯一拥有权,即引用计数永远是1,std::unique_ptr 对象销毁时会释放其持有的堆内存。可以使用以下方式初始化一个 std::unique_ptr 对象:

//初始化方式1
std::unique_ptr<int> sp1(new int(123));//初始化方式2
std::unique_ptr<int> sp2;
sp2.reset(new int(123));//初始化方式3
std::unique_ptr<int> sp3 = std::make_unique<int>(123);

你应该尽量使用初始化方式 3 的方式去创建一个 std::unique_ptr 而不是方式 1 和 2,因为形式 3 更安全,原因在其《Effective Modern C++》中已经解释过了,有兴趣的读者可以阅读此书相关章节。

令很多人对 C++11 规范不满的地方是,C++11 新增了 std::make_shared() 方法创建一个 std::shared_ptr 对象,却没有提供相应的 std::make_unique() 方法创建一个 std::unique_ptr 对象,这个方法直到 C++14 才被添加进来。当然,在 C++11 中你很容易实现出这样一个方法来:

template<typename T, typename... Ts>
std::unique_ptr<T> make_unique(Ts&& ...params)
{return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}

鉴于 std::auto_ptr 的前车之鉴,std::unique_ptr 禁止复制语义,为了达到这个效果,它的类的拷贝构造函数和赋值运算符被标记为 delete。

template <class T>
class unique_ptr
{//省略其他代码...//拷贝构造函数和赋值运算符被标记为deleteunique_ptr(const unique_ptr&) = delete;unique_ptr& operator=(const unique_ptr&) = delete;
};

因此,下列代码是无法通过编译的:

在这里插入图片描述

禁止复制语义也存在特例,即可以通过一个函数返回一个 std::unique_ptr

#include <memory>std::unique_ptr<int> func(int val)
{std::unique_ptr<int> up(new int(val));return up;
}int main()
{std::unique_ptr<int> sp1 = func(123);return 0;
}

上述代码从 func 函数中得到一个 std::unique_ptr 对象,然后返回给 sp1。

既然 std::unique_ptr 不能复制,那么如何将一个 std::unique_ptr 对象持有的堆内存转移给另外一个呢?答案是使用移动构造,示例代码如下:

#include <memory>int main()
{std::unique_ptr<int> sp1(std::make_unique<int>(123));std::unique_ptr<int> sp2(std::move(sp1));std::unique_ptr<int> sp3;sp3 = std::move(sp2);return 0;
}

以上代码利用 std::move 将 sp1 持有的堆内存(值为 123)转移给 sp2,再把 sp2 转移给 sp3。最后,sp1 和 sp2 不再持有堆内存的引用,变成一个空的智能指针对象。并不是所有的对象的 move 操作都有意义,只有实现了移动构造函数(或移动赋值运算符的类才行,而 std::unique_ptr 正好实现了这二者,以下是实现伪码:

template<typename T, typename Deletor>
class unique_ptr
{//其他函数省略...
public:unique_ptr(unique_ptr&& rhs){this->m_pT = rhs.m_pT;//源对象释放rhs.m_pT = nullptr;}unique_ptr& operator=(unique_ptr&& rhs){this->m_pT = rhs.m_pT;//源对象释放rhs.m_pT = nullptr;return *this;}private:T*    m_pT;
};

自定义智能指针对象持有的资源的释放函数

默认情况下,智能指针对象在析构时只会释放其持有的堆内存(调用 delete 或者 delete[]),但是假设这块堆内存代表的对象还对应一种需要回收的资源(如操作系统的套接字句柄、文件句柄等),我们可以通过自定义智能指针的资源释放函数。假设现在有一个 Socket 类,对应着操作系统的套接字句柄,在回收时需要关闭该对象,我们可以如下自定义智能指针对象的资源析构函数,这里以 std::unique_ptr 为例:

#include <iostream>
#include <memory>class Socket
{
public:Socket() {}~Socket() {}//关闭资源句柄void close() {}
};int main()
{auto deletor = [](Socket* pSocket) {//关闭句柄pSocket->close();//TODO: 你甚至可以在这里打印一行日志...delete pSocket;};std::unique_ptr<Socket, void(*)(Socket * pSocket)> spSocket(new Socket(), deletor);return 0;
}

自定义 std::unique_ptr 的资源释放函数其规则是:

std::unique_ptr<T, DeletorFuncPtr>

其中 T 是你要释放的对象类型,DeletorPtr 是一个自定义函数指针。表示 DeletorPtr 有点复杂,我们可以使用 decltype(deletor) 让编译器自己推导 deletor 的类型:

std::unique_ptr<Socket, decltype(deletor)> spSocket(new Socket(), deletor);

3.std::shared_ptr

std::unique_ptr 对其持有的资源具有独占性,而 std::shared_ptr 持有的资源可以在多个 std::shared_ptr 之间共享,每多一个 std::shared_ptr 对资源的引用,资源引用计数将增加 1,每一个指向该资源的 std::shared_ptr 对象析构时,资源引用计数减 1,最后一个对象析构时,发现资源计数为 0,将释放其持有的资源。多个线程之间,递增和减少资源的引用计数是安全的。(注意:这不意味着多个线程同时操作 std::shared_ptr 引用的对象是安全的)

std::shared_ptr 提供了一个 use_count() 方法来获取当前持有资源的引用计数。除了上面描述的,std::shared_ptr 用法和 std::unique_ptr 基本相同。

在这里插入图片描述

实际开发中,有时候需要在类中返回包裹当前对象(this)的一个 std::shared_ptr 对象给外部使用,C++ 新标准考虑到了这一点,有如此需求的类只要继承自 std::enable_shared_from_this 模板对象即可。用法如下:

class A : public std::enable_shared_from_this<A>
{
public:std::shared_ptr<A> getSelf(){return shared_from_this();}
};int main()
{std::shared_ptr<A> sp1(new A());std::shared_ptr<A> sp2 = sp1->getSelf();std::cout << "use count: " << sp1.use_count() << std::endl;return 0;
}

上述代码中,类 A 的继承 std::enable_shared_from_this 并提供一个 getSelf() 方法返回自身的 std::shared_ptr 对象,在 getSelf() 中调用 shared_from_this() 即可。

std::enable_shared_from_this 用起来比较方便,但是也存在很多不易察觉的陷阱。

陷阱一:不应该共享栈对象的 this 给智能指针对象

int main()
{A a;std::shared_ptr<A> sp2 = a.getSelf();std::cout << "use count: " << sp2.use_count() << std::endl;return 0;
}

运行修改后的代码会发现程序在 std::shared_ptr sp2 = a.getSelf(); 产生崩溃。这是因为,智能指针管理的是堆对象,栈对象会在函数调用结束后自行销毁,因此不能通过 shared_from_this() 将该对象交由智能指针对象管理。切记:智能指针最初设计的目的就是为了管理堆对象的(即那些不会自动释放的资源)。

陷阱二:避免 std::enable_shared_from_this 的循环引用问题

class A : public std::enable_shared_from_this<A>
{
public:A(){m_i = 9;//注意://比较好的做法是在构造函数里面调用shared_from_this()给m_SelfPtr赋值//但是很遗憾不能这么做,如果写在构造函数里面程序会直接崩溃std::cout << "A constructor" << std::endl;}~A(){m_i = 0;std::cout << "A destructor" << std::endl;}void func(){m_SelfPtr = shared_from_this();}public:int                 m_i;std::shared_ptr<A>  m_SelfPtr;};int main()
{{std::shared_ptr<A> spa(new A());spa->func();}return 0;
}

运行上面的代码,我们发现在程序的整个生命周期内,只有 A 类构造函数的调用输出,没有 A 类析构函数的调用输出,这意味着 new 出来的 A 对象产生了内存泄漏了!

在这里插入图片描述

我们来分析一下为什么 new 出来的 A 对象得不到释放。spa 出了其作用域准备析构,在析构时其发现仍然有另外的一个对象即 A::m_SelfPtr 引用了 A,因此 spa 只会将 A 的引用计数递减为 1,然后就销毁自身了。现在留下一个矛盾的处境:必须销毁 A 才能销毁其成员变量 m_SelfPtr,而销毁 m_SelfPtr 必须先销毁 A。这就是所谓的 std::enable_shared_from_this 的循环引用问题。我们在实际开发中应该避免做出这样的逻辑设计,这种情形下即使使用了智能指针也会造成内存泄漏。也就是说一个资源的生命周期可以交给一个智能指针对象,但是该智能指针的生命周期不可以再交给整个资源来管理。

4.std::weak_ptr

std::weak_ptr 是一个不控制资源生命周期的智能指针,是对对象的一种弱引用,只是提供了对其管理的资源的一个访问手段,引入它的目的为协助 std::shared_ptr 工作。

std::shared_ptr 可以直接赋值给 std::weak_ptr ,也可以通过 std::weak_ptrlock() 函数来获得 std::shared_ptr。它的构造和析构不会引起引用计数的增加或减少。std::weak_ptr 可用来解决 std::shared_ptr 相互引用时的死锁问题。

在这里插入图片描述

既然,std::weak_ptr 不管理对象的生命周期,那么其引用的对象可能在某个时刻被销毁了,如何得知呢?std::weak_ptr 提供了一个 expired() 方法来做这一项检测,返回 true,说明其引用的资源已经不存在了;返回 false,说明该资源仍然存在,这个时候可以使用 std::weak_ptrlock() 方法得到一个 std::shared_ptr 对象然后继续操作资源,以下代码演示了该用法:

//tmpConn_ 是一个 std::weak_ptr<TcpConnection> 对象
//tmpConn_引用的TcpConnection已经销毁,直接返回
if (tmpConn_.expired())return;std::shared_ptr<TcpConnection> conn = tmpConn_.lock();
if (conn)
{//对conn进行操作,省略...
}

既然使用了 std::weak_ptrexpired() 方法判断了对象是否存在,为什么不直接使用 std::weak_ptr 对象对引用资源进行操作呢?实际上这是行不通的,std::weak_ptr 类没有重写 operator->operator* 方法,因此不能像 std::shared_ptrstd::unique_ptr 一样直接操作对象,同时 std::weak_ptr 类也没有重写 operator! 操作,因此也不能通过 std::weak_ptr 对象直接判断其引用的资源是否存在:

在这里插入图片描述

std::weak_ptr 的正确使用场景是那些资源如果可能就使用,如果不可使用则不用的场景,它不参与资源的生命周期管理。例如,网络分层结构中,Session 对象(会话对象)利用 Connection 对象(连接对象)提供的服务工作,但是 Session 对象不管理 Connection 对象的生命周期,Session 管理 Connection 的生命周期是不合理的,因为网络底层出错会导致 Connection 对象被销毁,此时 Session 对象如果强行持有 Connection 对象与事实矛盾。

5.智能指针的大小

在64位机器下,unique_ptr与普通指针大小一样,share_ptr / weak_ptr是普通指针的2倍:

在这里插入图片描述

6.智能指针使用注意事项

a. 一旦一个对象使用智能指针管理后,就不该再使用原始裸指针去操作;

b. 分清楚场合应该使用哪种类型的智能指针;
通常情况下,如果你的资源不需要在其他地方共享,那么应该优先使用 std::unique_ptr,反之使用 std::shared_ptr,当然这是在该智能指针需要管理资源的生命周期的情况下;如果不需要管理对象的生命周期,请使用 std::weak_ptr。

二、智能指针的模拟实现

我们只实现它最核心的部分:

  • 利用 RAII(一种利用对象生命周期来控制程序资源的简单技术)来管理指针
  • 像指针一样使用

auto_ptr:

template<class T>
class auto_ptr {
private:T* _ptr;
public:auto_ptr(T* ptr):_ptr(ptr){}~auto_ptr() { delete _ptr; }auto_ptr(auto_ptr<T>& sp):_ptr(sp._ptr){sp._ptr = nullptr;}T& operator*() { return *_ptr; }T* operator->() { return _ptr; }
};

unique_ptr:

template<class T>
class unique_ptr {
private:T* _ptr;
public:unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr() { delete _ptr; }unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;T& operator*() { return *_ptr; }T* operator->() { return _ptr; }
};

shared_ptr:

template<class T>
class shared_ptr {
private:T* _ptr;int* _pcount;std::mutex* _pmtx;public:shared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)),_pmtx(new std::mutex){}~shared_ptr() { Release(); }shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr),_pcount(sp._pcount),_pmtx(sp._pmtx){{_pmtx->lock();++(*_pcount);_pmtx->unlock();}}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr == sp._ptr) return *this;Release();_pcount = sp._pcount;_ptr = sp._ptr;_pmtx = sp._pmtx;{_pmtx->lock();++(*_pcount);_pmtx->unlock();}return *this;}void Release(){bool flag = false;{_pmtx->lock();if (--(*_pcount) == 0){delete _ptr;delete _pcount;flag = true;}_pmtx->unlock();}if (flag) delete _pmtx;}int use_count() const { return *_pcount; }T* get() const { return _ptr; }T& operator*() { return *_ptr; }T* operator->() { return _ptr; }
};

weak_ptr:

	template<class T>class weak_ptr{private:T* _ptr;public:weak_ptr():_ptr(nullptr){}weak_ptr(const weak_ptr<T>& wp):_ptr(wp._ptr){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}T& operator*() { return *_ptr; }T* operator->() { return _ptr; }};
}

三、C++11和boost中智能指针的关系

  1. C++ 98 中产生了第一个智能指针auto_ptr.
  2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
  3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
  4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/639753.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker是什么

docker本质 Docker 本质其实是 LXC 之类的增强版&#xff0c;它本身不是容器&#xff0c;而是容器的易用工具。容器是 linux 内核中的技术&#xff0c;Docker 只是把这种技术在使用上简易普及了。Docker 在早期的版本其核心就是 LXC 的二次封装发行版。 Docker 作为容器技术的…

开发第一个Flutter App需要注意什么

Flutter这些年发展的很快&#xff0c;特别是在 Google 持续的加持下&#xff0c;Flutter SDK 的版本号已经来到了 3开头&#xff0c;也正式开始对 Windows、macOS 和 Linux 桌面环境提供支持。如果从 Flutter 特有的优势来看&#xff0c;我个人认为主要是它已经几乎和原生的性能…

换手机后:旧手机备忘录怎么导入新手机里?

现在新手机层出不穷&#xff0c;大家都爱换手机来体验新功能&#xff0c;但在换手机的时候&#xff0c;数据传输是非常麻烦的一件事情。 每次换手机&#xff0c;就像是搬一次家。老房子里的点点滴滴&#xff0c;那些重要的、不重要的&#xff0c;都得一一打包&#xff0c;再在…

DSP Bootloader

DSP Bootloader Refer: DSP Bootloader开发思路讲解

字符串展开(Python)

展开字符串中用-压缩的连续小写字母或者数字&#xff0c;不是压缩形式的-不用理会&#xff0c;-没有压缩字符的去除-。 (笔记模板由python脚本于2024年01月21日 18:18:19创建&#xff0c;本篇笔记适合熟悉 p y t h o n python python字符串和列表的coder翻阅) 【学习的细节是欢…

SAP屏幕开发之Listbox下拉列表

文章目录 前言一、案例介绍二、静态下拉列表 a.绘制并设置属性 b.两种属性区别以及效果展示 三、动态下拉列表 a.绘制下拉列表 b.调用函数绑定 四、总结 前言 这篇文章给大家介绍一下SAP Dialog程序中 Listbox控件 的使用&#xf…

如何搭建MariaDB并实现无公网ip环境远程连接本地数据库

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 1. 配置MariaDB数据库1.1 安装MariaDB数据库1.2 测试局域网内远程连接 2. 内网穿透2.1 创建隧道映射…

多级缓存

一、多级缓存 传统的缓存策略一般是请求到达Tomcat后&#xff0c;先查询Redis&#xff0c;如果未命中则查询数据库&#xff0c;如图&#xff1a; 存在下面的问题&#xff1a; •请求要经过Tomcat处理&#xff0c;Tomcat的性能成为整个系统的瓶颈 •Redis缓存失效时&#xff…

【Ubuntu】Ubuntu安装微信

1. 优麒麟 Wine &#xff08;“Wine Is Not an Emulator(Wine不是一个模拟器)” 的缩写&#xff09;是一个能够在多种 POSIX-compliant 操作系统&#xff08;诸如 Linux&#xff0c;Mac OSX 及 BSD 等&#xff09;上运行 Windows 应用的兼容层。银河麒麟的操作系统也是基于Ubu…

Android状态栏布局隐藏的方法

1.问题如下&#xff0c;安卓布局很不协调 2.先将ActionBar设置为NoActionBar 先打开styles.xml 3.使用工具类 /*** StatusBar 工具类*/ public class StatusBarUtil {/*** 设置状态栏全透明** param activity 需要设置的activity*/public static void setTransparent(Activit…

【大数据】流处理基础概念(一):Dataflow 编程基础、并行流处理

流处理基础概念&#xff08;一&#xff09;&#xff1a;Dataflow 编程基础、并行流处理 1.Dataflow 编程基础1.1 Dataflow 图1.2 数据并行和任务并行1.3 数据交换策略 2.并行流处理2.1 延迟与吞吐2.1.1 延迟2.1.2 吞吐2.1.3 延迟与吞吐 2.2 数据流上的操作2.2.1 数据接入和数据…

【江科大】STM32:(超级详细)定时器输出比较

文章目录 输出比较单元特点 高级定时器&#xff1a;均有4个通道 PWM简介PWM&#xff08;Pulse Width Modulation&#xff09;脉冲宽度调制输出比较通道PWM基本结构基本定时器 参数计算捕获/比较通道的输出部分详细介绍如下&#xff1a; 舵机介绍硬件电路 直流电机介绍&#xff…

LLM自回归解码

在自然语言处理&#xff08;NLP&#xff09;中&#xff0c;大型语言模型&#xff08;LLM&#xff09;如Transformer进行推理时&#xff0c;自回归解码是一种生成文本的方式。在自回归解码中&#xff0c;模型在生成下一个单词时会依赖于它之前生成的单词。 使用自回归解码的公式…

SPE-Single Pair Ethernet单对以太网测试那些事儿

SPE-Single Pair Ethernet单对以太网测试哪些事&#xff1f;SPE标准IEEE802.3再网上溯源的话是从ISO/IEC11801-X series演变而来。 IEEE802.3cg 10Base-T1 10mbt/s 15m-1000m 0.1mHz-20mHz IEEE802.3bw 100Base-T1 100mbt/s 15m 0.3mHz-66mHz IEEE802.3bp 1000…

k8s-认证授权 14

Kubernetes的认证授权分为认证&#xff08;鉴定用户身份&#xff09;、授权&#xff08;操作权限许可鉴别&#xff09;、准入控制&#xff08;资源对象操作时实现更精细的许可检查&#xff09;三个阶段。 Authentication&#xff08;认证&#xff09; 认证方式现共有8种&…

Pandas.Series.describe() 统计学描述 详解 含代码 含测试数据集 随Pandas版本持续更新

关于Pandas版本&#xff1a; 本文基于 pandas2.1.2 编写。 关于本文内容更新&#xff1a; 随着pandas的stable版本更迭&#xff0c;本文持续更新&#xff0c;不断完善补充。 传送门&#xff1a; Pandas API参考目录 传送门&#xff1a; Pandas 版本更新及新特性 传送门&…

Java层序遍历二叉树

二叉树准备: public class TreeNode {int val;TreeNode left;TreeNode right;TreeNode() {}TreeNode(int val) {this.val val;}TreeNode(int val, TreeNode left, TreeNode right) {this.val val;this.left left;this.right right;} } 思路&#xff1a;我们需要创建一个队…

前后端分离,使用vue3整合SpringSecurity加JWT实现登录校验

前段时间写了一篇spring security的详细入门&#xff0c;但是没有联系实际。 所以这次在真实的项目中来演示一下怎样使用springsecurity来实现我们最常用的登录校验。本次演示使用现在市面上最常见的开发方式&#xff0c;前后端分离开发。前端使用vue3进行构建&#xff0c;用到…

算法每日一题: 分割数组的最大值 | 动归 | 分割数组 | 贪心+二分

Hello&#xff0c;大家好&#xff0c;我是星恒 呜呜呜&#xff0c;今天给大家带来的又是一道经典的动归难题。 题目&#xff1a;leetcode 410给定一个非负整数数组 nums 和一个整数 k &#xff0c;你需要将这个数组分成 k_ 个非空的连续子数组。设计一个算法使得这 k _个子数组…

Mybatis 动态SQL(set)

我们先用XML的方式实现 : 把 id 为 13 的那一行的 username 改为 ip 创建一个接口 UserInfo2Mapper ,然后在接口中声明该方法 package com.example.mybatisdemo.mapper; import com.example.mybatisdemo.model.UserInfo; import org.apache.ibatis.annotations.*; import jav…