我的第一个 Next.js + ChatGPT 项目,24 小时内爆炸 10000 用户!

Hi,大家好我是 ssh,这两天冲浪的时候,我发现了一个特别有意思的项目Linkedin Post Generator [1],作者发现他每天需要花大量的时间来写 Linkedin 帖子,所以想利用 chatGPT 帮他检测、优化这些破事儿,简单来说:

只需要把你自己写的小段落输进去,chatGPT 就会自动帮你润色成一篇网红贴!

我自己试用了一下,帮 Dan 写了一篇求职信:

嘿,还真有那么点意思。这个图里的中英文混排是因为我用了「沉浸式翻译」这个 Chrome 插件,很好用。

正巧,他本人写了一篇文章来介绍他使用 Next.js 和 ChatGPT 构建这个应用的心路历程,发布了 如何使用 ChatGPT 和 Next.js 构建我的第一个开源项目:24 小时内获得 10,000 用户[2],我来整理翻译给大家:

正文

小小介绍一下,我以前从来没有编程过,对我来说,编程看起来非常难,但在两个月前,我决定现在就开始

我的朋友建议我从开源项目开始,并在设置的第一步中帮助我进行了一些简单的指导。

我几乎在每一步中都使用了 ChatGPT,包括弄清楚如何设置某些东西,安装、连接 API,以及代码的含义、如何重写函数或更改大小。

现在我要更详细地介绍我建立的第一个项目。

What am I doing GIF

我从 变成

第 1 步:设置环境

第 2 步:寻找开源项目并在其基础上构建

第 3 步:弄清楚代码

第 4 步:构建项目

第 5 步:推送项目

第 6 步:在社交平台上分享(此处显示统计信息)

我花了一个星期的时间弄清楚了一切,启动了该项目。从准备工作开始(很快,只需要 1-2 小时)。然后管理开源项目中的现有代码(3-4 天)。最后将其推送到 GitHub 和 Vercel(花了一天的时间)。

我建立的项目是什么?

Linkedin 帖子生成器 - 用于在 LinkedIn 上通过 AI 生成帖子。

在 GitHub 上

使用它来构建您自己的帖子生成器。

来 fork 和 star 吧 :)

Linkedin 帖子生成器

下面分享在推出初日和在 LinkedIn 上迅速火了期间的所有统计数据。

为什么要做 LinkedIn 帖子生成器?

首先,我在 LinkedIn 平台上进行了一段时间的实验。

我花了很多时间在 LinkedIn 上撰写帖子,每篇至少需要花费 1 个小时,所以我在思考是否有捷径可走。

因此,我分析了 100 多位不同的创作者和 300 多个不同的提示,以找到更快生成更有效帖子的方法。

现在,我将逐步分享我所做的基本步骤。

第 1 步:设置环境设置 Tea

在开始之前,我朋友告诉我设置一个包管理器(如 Tea)来处理开发环境。

在这个时候,“包管理器”这个词对我来说还是未知的。

sh <(curl https://tea.xyz)

# --- 或者 ---
# 使用 brew
brew install tea

据我了解,Tea 帮助安装了我在开发中所需的 node、npm、vercel 和其他任何包等一揽子服务。

使用 TypeScript 和 Tailwindcss 设置 Next.js

我基本了解到我需要某种前端工具。

别人告诉我,可以创建一个新的 Next.js 项目来开始。并且使用 TypeScript 和 Tailwind CSS,所以我按照以下步骤进行操作:

npx create-next-app

# ---
# 你将会被问到以下提示信息
What is your project named?  my-app
Would you like to add TypeScript with this project?  Y/N
# 选择 `Y` 来添加 TypeScript
Would you like to use ESLint with this project?  Y/N
# 选择 `Y` 来使用 ESLint
Would you like to use Tailwind CSS with this project? Y/N
# 选择 `Y` 来使用 Tailwind CSS
Would you like to use the `src/ directory` with this project? Y/N
# 选择 `N` 来使用 `src/` 目录
What import alias would you like configured? `@/*`
# 输入 `@/*` 作为导入别名

第 2 步:找到开源项目并在其基础上进行构建

我使用了两个开源项目:

Twitter 算法 ,这样我可以根据 LinkedIn 的算法评价用户输入的帖子。

Twitter 算法

Twitter Bio 生成器 ,这个项目帮助我弄清楚如何连接 OpenAI 并生成适用于 LinkedIn 的帖子。在当前代码中,它生成了 BIO。

Twitter Bio 生成器

为了单独研究每个项目,我把 zip 文件下载下来了。

从开源项目开始 第 3 步:弄清楚代码

刚开始,我对所见到的东西感到震惊,一无所知。

于是,我问 ChatGPT 应用的基本结构。

我将每个页面的代码复制到 ChatGPT 中,并询问它的功能,基本上是询问如何进行更改。这样我开始对应用程序的前端和 CSS 的位置有了更好的了解。

我仍不完全了解所有的东西,但我认为这样的学习效率确实很高。

我向 ChatGPT 提了一些很愚蠢的问题,当然现在对我来说都很清晰了。

在 ChatGPT 中更改颜色请求 第 4 步:构建项目

在搞定了一些基础知识后,我开始自己改项目,构建应用程序了。

由两个部分组成:排名和生成器。

LinkedIn 帖子生成器算法

排名是根据不同的标准对您的帖子进行排名,以提高性能。

我根据 LinkedIn 已知的算法进行了适应,使用了以下函数:

检测多个 hashtags 的函数

检测帖子中的图像或视频的函数

检测帖子中的 URL 的函数

偏爱使用表情符号的帖子的函数

推广负面内容的函数

优先考虑换行符的帖子格式的函数

减少行长度的函数

提出问题的函数

和 Twitter 算法不一样,LinkedIn 的算法不是公开的。

// 检测多个标签的函数
function multipleHashtags({ post }: PostData): Rank {
  const regex = /#[\w-]+/g;
  const hashtags = post.match(regex);
  const lowerCasePost = post.toLowerCase();

  if (hashtags && hashtags.length > 3) {
    return {
      score0.5,
      message"使用了太多的标签。",
    };
  }
  if (hashtags && hashtags.length <= 3) {
    if (
      lowerCasePost.includes("#follow") ||
      lowerCasePost.includes("#comment") ||
      lowerCasePost.includes("#like")
    ) {
      return {
        score0.5,
        message"避免使用类似“follow”、“comment”或“like”的标签。",
      };
    }
    return {
      score1,
      message"结合一般性和特定性的标签。",
    };
  }

  return {
    score1.0,
  };
}

// 检测帖子中是否有图像或视频的函数
function imageVideoBoost({ postMedia }: PostData): Rank {
  const has_media = postMedia;
  if (has_media) {
    return {
      score2.0,
      // message: "包含图像或视频。",
    };
  }
  return {
    score1.0,
  };
}

// 检测帖子中的网址的函数
function postHasUrl({ post }: PostData): Rank {
  const regex =
    /https?:\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?/g;
  const urls = post.match(regex);
  if (urls && urls.length > 0) {
    return {
      score0.5,
      message"从帖子中删除链接并在评论中添加链接。",
    };
  }
  return {
    score1.0,
  };
}

/**
 * 偏好使用表情符号的帖子的函数
 */

function emojis({ post, sentiment }: PostData): Rank {
  const regex = new RegExp("[\uD800-\uDBFF][\uDC00-\uDFFF]""g");
  const emojis = post.match(regex) || [];
  const totalMatches = emojis.length;
  if (totalMatches > 0) {
    return {
      score1.5,
      // message: `在帖子中包含了 ${totalMatches}个表情符号。`,
    };
  }
  return {
    score1,
    message"帖子中没有找到表情符号。",
    type"negative"
  };
}

/**
 * 促进消极内容,因为它更有可能传播。
 * 隐藏任何积极或令人振奋的内容。
 */

function sentiment({ post, sentiment }: PostData): Rank {
  if (sentiment.comparative >= 0.5) {
    if (sentiment.comparative > 1.5) {
      return {
        score1.5,
        // message: "极其积极。",
      };
    } else {
      return {
        score1.1,
        // message: "积极情感。",
      };
    }
  } else if (sentiment.comparative <= -0.5) {
    if (sentiment.comparative < -1.5) {
      return {
        score0.5,
        // message: "极其消极。",
      };
    } else {
      return {
        score0.9,
        // message: "消极情感。",
      };
    }
  } else {
    return {
      score1,
    };
  }
}

/**
 * 优先考虑帖子格式的断行。
 */

function lineBreaks({ post, sentiment }: PostData): Rank {
  const breaks = post.split(/\n\s*\n/);
  const totalBreaks = breaks.length - 1;
  if (totalBreaks >= 1) {

    return {
      score1.5,
      // message: `使用了 ${totalBreaks}个断行。`,
    };
  } else {
    return {
      score1,
      message"在行之间添加断行。",
      type"negative"
    };
  }
}

/**
 * 减少行长度
 */

function lineLength({ post }: PostData): Rank {
  const lines = post.split('\n');
  let score = 1.0;
  for (let i = 0; i < lines.length; i++) {
    if (lines[i].length > 200) {
      return {
        score0.9,
        message"缩短行长度以提高可读性(200个字符)。",
      };
    }
  }
  return {
    score1,
    // message: "很好,保持行长度在200个字符或以下。",
    type"positive"
  };
}
/**
* 提问的函数
*/

function questions({ post, sentiment }: PostData): Rank {
  if (post.includes("?")) {
    return {
      score1.5,
      // message: "很棒!问题可以帮助激活讨论"
    };
  } else {
    return {
      score1,
      message"添加问题以激活讨论",
      type"negative"
    };
  }
}

算法的 UI 界面

检测上述代码中的所有函数,并对其中一些函数展示改进建议。我没有针对所有函数进行调整。

return (
  <>
    <div>
      <div className="slider bg-gray-300 h-4 rounded-full relative overflow-hidden">
        <div
          className={classNames(
            "absolute top-0 transition-width duration-250 ease-linear h-20",
            sliderColor
          )}
          style={{ width: percentage }}
        />

      div>

      {/* <p className="explanation text-gray-600 italic text-sm mt-2">
        Positive rankings result in greater reach
      p> */}

      <ul className="mt-5 p-0">
        {positive.map((item, index) => (
          <li
            className="positive text-green-600 flex items-center space-x-2 list-style-none my-5 text-sm"
            key={`positive-${index}`}
          >

            <span>span>
            <span>{item.message.replace(/\(\s*[+-]?\d+\s*\)/, '')}span>
          li>
        ))}
        {negative.map((item, index) => (
          <li
            className="negative text-red-600 flex items-center space-x-2 list-style-none my-1 text-sm"
            key={`negative-${index}`}
          >

            <span>span>
            <span>{item.message.replace(/\(\s*[+-]?\d+\s*\)/, '')}span>
          li>
        ))}
      ul>
    div>
    <style jsx>{`
      .slider:after {
        content: " ";
        display: block;
        width: 2px;
        height: 20px;
        position: absolute;
        top: 0;
        left: calc(25% - 1px);
        background: #000;
      }
    `}style>
  
);
};

帖子中的每个函数都被检测,并根据其得分显示相应的信息。正向的建议带有 图标,而负向的建议带有 图标。

Open AI Api 和 Prompts 生成

我使用了 Prompt 来生成这篇帖子。还有类型的过滤器,所以我根据类型有 5 个不同的提示。我只是连接了我的 OpenAI API。

const handlePrompt = () => {
    let prompt;
    switch (vibe) {

下面是 Prompt 的内容:

prompt = `Generate post using this prompt, based on ${post}.  You are a LinkedinGPT, a large language model that generates viral posts for Linkedin. You are given a prompt of a post and must generate a post that is more likely to be liked and reposted than the original post.
The Linkedin algorithm contains boosts and demotions based on what you are writing. Positive boosts are:

- in each post add emoji
- 200 characters in sentence maximum
- Start each sentecnce from new line and ad numbers in first 2 lines
- add 3 hashtags which 2 are generic and one very specific (at the end) Tags relate to post theme
- add a question at the end of the post to start a discussion. Before the hashtags
- first two lines should be catchy
- Dont add links - links are not good.
- If post copied in the field contain some numbers keep them the same.

Add idea about which image or visual can be added at the end of the post (this text is not counted as part of post)
${post}
---
Generated post length must be more than 800-1200 characters
---
Between each line must be a space
---
Keep all mentions of people in there
---
Start the firs line from smth like: I did smth, In year, I do, Tired of, Sometimes it is just, A path toward, Because this is not,I've been struggling,  (change the begginign depends on the context )
---
Add emoji if it fits
---
It should be a story`
;

生成器主界面

这是我的索引文件,是帖子生成器的主要界面。


  <nav className="bg-blue-900 text-white ">
    <div className="px-5">
      <div className="max-w-5xl mx-auto">
        <div className="flex justify-between items-center h-16 ">
          <div className="flex items-center text-base ">
            <a
              target="_blank"
              href="https://www.linkedin.com/in/iuliia-shnai/"
              rel="noreferrer"
              className="text-white flex max-w-fit items-center justify-center space-x-2 text-xl"
            >

              <p>p>

            a>
          div>
        div>
      div>
    div>
  nav>
  <section className="py-10 lg:py-20 ">
    {/* bg-[url('/image1.svg')] */}
    <div className="px-4">
      <div className="max-w-5xl mx-auto">
        <div className="w-full mx-auto">
          <h1 className="text-6xl text-center font-bold pb-1 text-slate-900">
            Linkedin Post Generator 
          h1>

          <p className="mt-3 mb-10 text-center">
            See how your post performs and generate a better one with AI. Time
            to go viral. <br />
          p>
          <div className="flex flex-col md:flex-row w-full md:space-x-20">
            <div className="flex md:w-1/2 flex-col">
              <h2
                className="

text-xl font-bold"

              >

                Your Ranking
              h2>
              <div className="pt-1">
                <Ranking ranking={ranking} />
              div>
              <div className="w-full my-1 mx-auto">
                <Post
                  post={post}
                  setPost={setPost}
                  media={media}
                  setMedia={setMedia}
                />

              div>
              <div className="flex mb-5 items-center space-x-3">div>
              <div className="block">
                <DropDown vibe={vibe} setVibe={setVibe} />
              div>
              <div className="my-4">
                <button
                  disabled={loading}
                  onClick={(e) =>
 optimizePost(e)}
                  className="bg-blue-800 font-medium rounded-md w-full text-white px-4 py-2 hover:bg-blue-600 disabled:bg-blue-800"
                >
                  {loading && <LoadingDots color="white" style="large" />}
                  {!loading && `Generate new post `}
                button>
              div>
            div>
            <div className="flex md:w-1/2 md:flex-col">
              <Toaster
                position="top-right"
                reverseOrder={false}
                toastOptions={{ duration: 2000 }}
              />

              {optimizedPost && (
                <div className="my-1">
                  <div className="flex justify-between items-center pb-2 border-b border-gray-300">
                    <h2 className="text-xl font-bold">Your Generated Posth2>
                  div>
                  <div className="max-w-2xl my-4 mx-auto">
                    <div
                      className="bg-white rounded-xl shadow-md p-4 hover:bg-gray-100 transition cursor-copy border"
                      onClick={() =>
 {
                        navigator.clipboard.write([
                          new ClipboardItem({
                            "text/html": new Blob([optimizedPost], {
                              type: "text/html",
                            }),
                          }),
                        ]);
                        toast("Post copied to clipboard", {
                          icon: "",
                        });
                      }}
                      key={optimizedPost}
                    >
                      <p
                        className="text-black-700"
                        dangerouslySetInnerHTML={{ __html: optimizedPost }}
                      />

                    div>
                  div>
                div>
              )}
            div>
          div>
        div>
      div>
    div>
  section>
  <div className="max-w-5xl mx-auto">
    <Footer />
  div>

</main>

Main generator page 第 5 步:推送项目

最后一步,我准备好推送了。

我在 GitHub 上创建了一个仓库:

$ git remote add origin ..
git branch -M main
git push -u origin main

然后,我在 Vercel 上创建了一个帐户,以便使用 Vercel 发布并检查错误。

之后的每个更新都通过以下命令进行推送:

git add .

git commit -m “fix type”

git push

为了检查错误,我用了下面的命令,这样我就不会在错误解决之前进行推送。ChatGPT 在帮助我找出错误这方面帮了很大的忙。

npm run build

Apache commons工具包——让你的java鸟枪换炮(一)

大家好,我是吉森。

前面给大家安利了谷歌的guava工具包和国产的hutool工具包,收到了热烈的讨论,在这里先谢谢你们的热情。

我们都知道java的官方类库——jdk的内容不够丰富,不能完全满足各式各样的开发需求。为了提升效率,我们通常会引入一些第三方的工具包,这也符合java小官方、大社区的惯例。但是对于引入什么样的工具包,却有不少讨论和争议。以前面提到的hutool为例,有的同学认为类库的代码质量一般,表示不用;有的同学认为没有经过充分的测试,表示不用;有的同学认为开源的协议过于小众有风险,表示不用。

那么今天开始我们介绍的这款大名鼎鼎Apache Commons工具包,可以说完全没有上述风险。想必大部分做java开发的同学都听说过它的名字,它是apache基金会负责维护的,代码质量高,经过了充分的测试,它的开源协议Apache License 2.0可以说是最宽松的开源协议。总之,大家可以放心使用。

Apache commons

Apache Commons是一个非常庞大的工具包,有多达43个模块,我计划分几次为大家整理出最常用的模块的脑图和api,可以方便大家用最少的时间熟悉它。

今天我们先从一个核心模块——lang模块开始。顾名思义,它是语言模块,是对java.lang包的一个丰富和补充。目前最新版本为3.12,包名为commons-lang3。先来看一下思维导图:

commons-lang3思维导图

再来看一下常用的API:

基础工具——数组处理

ArrayUtils类提供方便地处理数组的工具,包括对数组进行添加元素、删除元素和翻转等操作。

使用场景:对数组进行简单高效地处理。

 // 向数组尾端插入元素,得到的是一个新建的数组,原数组为null则新建数组
int[] add0 = ArrayUtils.add(new int[]{1}, 3); // [1, 3]
int[] add1 = ArrayUtils.add((int[]) null, 3); // [3]
// 像数组指定位置插入若干个元素,得到的是一个复制的数组,原数组为null则直接返回null
int[] insert0 = ArrayUtils.insert(0, new int[]{0}, 1, 2, 3); // [0, 1, 2, 3]
int[] insert1 = ArrayUtils.insert(0, (int[]) null, 1, 2, 3); // null
// 移除下标为2的元素
int[] remove = ArrayUtils.remove(new int[]{1, 2, 3, 4}, 2); // [1, 2, 4]
// 根据值移除元素
int[] removeElement = ArrayUtils.removeElement(new int[]{1, 2, 3, 4}, 2); // [1, 3, 4]
// 原地翻转数组,注意此方法改变了输入参数
int[] array = {1, 2, 3, 4, 5}; 
ArrayUtils.reverse(array); // [5, 4, 3, 2, 1]

基础工具——随机工具

RandomUtils提供了生成随机整数、浮点数等的方法,RandomStringUtils提供了由数字、字母、其他符号组成的指定长度的随机字符串的方法。

使用场景:生成随机数、生成随机密码、生成序列号等。

/* RandomUtils */
// 生成[0, 5)之间的随机数,含头不含尾
int randomInt = RandomUtils.nextInt(0, 5);
// 生成[0.0, 5.0)之间的随机数,含头不含尾
double randomDouble = RandomUtils.nextDouble(0.0, 5.0);
/* RandomStringUtils */
// 生成由6位随机数字组成的字符串
String randomNumeric = RandomStringUtils.randomNumeric(6);
// 生成由6-9位数字或字母组成的字符串
String randomAlphanumeric = RandomStringUtils.randomAlphanumeric(6, 10);

基础工具——字符串处理

StringUtils提供了方便的处理字符串的方式,RegExUtils提供了基于正则表达式处理字符串的方式,特别是基于正则表达式删除字符串一部分的功能。

使用场景:字符串拼接、字符串判空、字符串删除内容以及其他字符串处理

/* StringUtils */
String s = "  ";
// 判断字符串是否为空(null或空串)
boolean empty = StringUtils.isEmpty(s); // false
// 判断字符串是否为空白字符串(null、空串或只包含空格)
boolean blank = StringUtils.isBlank(s); // true
// 拼接由分隔符分隔的字符串
String join = StringUtils.joinWith(",", "a", null, "b"); // "a,,b"
/*  RegExUtils */
// 按正则表达式移除字符串的内容,removePattern启动了DOTALL模式,则.号也可以匹配\n
String s1 = RegExUtils.removePattern("a12b3\nc", "3.c"); // "a12b"
String s2 = RegExUtils.removeAll("a12b3\nc", "3.c"); // "a12b3\nc"

基础工具——对象工具

ObjectUtils提供了一些对于一般对象进行比较、判空、取最大最小值、中值的方法。SerializationUtils可以用于对象深拷贝。

使用场景:针对可排序对象取最大、最小值、中位数时,如取一个班级学生最高分(需提前定义比较策略);对象深拷贝。

 /* ObjectUtils */
// 比较两个对象,这两个对象必须是Comparable的,如果s1大,返回1;s2大,返回-1;相等返回0。
int compare = ObjectUtils.compare(s1, s2);
// 取最大值
String max = ObjectUtils.max(s1, s2, s3, s4);
// 取中值
String median = ObjectUtils.median(s1, s2, s3, s4);
 /* SerializationUtils */
// 通过序列化和反序列化实现对象的深拷贝
String clone = SerializationUtils.clone(obj);

并发——初始化

ConcurrentInitializer是一个父级接口,用于线程安全地初始化对象。它有多个不同的实现类,包括AtomicInitializer、AtomicSafeInitializer、BackgroundInitializer、ConstantInitializer、LazyInitializer、MultiBackgroundInitializer等。

ConstantInitializer顾名思义,是用常量初始化对象。

LazyInitializer也是见名知义,用于对象懒加载。

AtomicInitializer与LazyInitializer很像,也用于对象懒加载,在内部是基于AtomReference实现的,不需要同步锁,效率更高。

AtomicSafeInitializer一样也用于对象懒加载,它额外加了一层检查,保证initialize方法只被调用一次。

BackgroundInitializer和MultiBackgroundInitializer用额外线程初始化对象,它们可以将一些耗时的初始化操作并行处理,提高运行效率,后者是前者的一个实现类,可以同时处理多个后台任务。

使用场景:在多线程的情况下安全地初始化对象

// 字符串将在首次使用时初始化为abc,此处的LazyInitializer可以替换为AtomicInitializer或AtomicSafeInitializer
ConcurrentInitializer initializer = new LazyInitializer<>() {
            @Override
            protected String initialize() {
                return "abc";
            }
        };
// 在某个Runnable对象中实际使用字符串,调用ConcurrentInitializer对象的get方法
public void run() {
    try {
        String s = initializer.get();
    } catch (ConcurrentException cex) {
        cex.printStackTrace();
    }
}

并发——工具类

ConcurrentUtils用于简化ConcurrentInitializer使用过程中的样板代码,如:

try {
    String s = initializer.get();
} catch (ConcurrentException cex) {
    cex.printStackTrace();
}

可以简化为

String s = ConcurrentUtils.initializeUnchecked(initializer);

使用场景:配合ConcurrentInitializer使用

并发——同步

commons-lang3中扩展了一个信号量的实现类,即TimedSemaphore,用于在给定时间范围内允许完成一些操作。

它和JUC包中的Semaphore类有点类似,区别在于,TimedSemaphore没有提供release方法,所有的限制将在时间结束后自动释放。

这里参考一下官方的示例,可以理解更加准确。

// 一个用于统计的线程类
public class StatisticsThread extends Thread {
    // 限制数据库负载的信号量
    private final TimedSemaphore semaphore;
    // 初始化实例,同时设置信息量
    public StatisticsThread(TimedSemaphore timedSemaphore) {
        semaphore = timedSemaphore;
    }
    // 收集统计数据
    public void run() {
        try {
            while (true) {
                // 调用acquire方法限制数据库访问次数
                semaphore.acquire();   
                // 执行数据库访问
                performQuery();        
            }
        } catch(InterruptedException) {
            // 异常处理
        }
    }
    ...
}
// 信号量声明为1秒10次,即1秒可以访问10次数据库
TimedSemaphore sem = new TimedSemaphore(1, TimeUnit.SECOND, 10);
StatisticsThread thread = new StatisticsThread(sem);
thread.start();

元组

元组就是可以存放不同类型对象的一个小容器,很多编程语言在语言特性层面支持元组。很遗憾,java又一次不在这个很多语言里面。commons-lang3里提供的Pair和Triple类可以满足一定场景下元组的使用。

使用场景:一个方法想要返回两个或三个值

/* Pair */
// 创建一个二元组
Pair pair = Pair.of(123, "abc");
// 获取二元组的第一个值,使用getLeft或getKey
final Integer left = pair.getLeft();
// 获取二元组的第二个值,使用getRight或getValue
final String right = pair.getRight();
/* Triple */
final Triple triple = Triple.of(123, 456.0, "abc");
// 获取三元组第一个值
final Integer first = triple.getLeft();
// 获取三元组第二个值
final Double second = triple.getMiddle();
// 获取三元组第三个值
final String third = triple.getRight();

函数式编程

jdk8在java语言层面引入了更多函数式编程的组件,包括lambda表达式和一些函数式接口,如Consumer、Supplier、Predicate、Function等。这些接口的一个问题是没有考虑抛出异常的情况,这使得我们在异常处理的场景下,编码变得很麻烦。

commons-lang的function包在jdk8提供的函数式接口的基础上提供了一套允许失败场景的版本,如FailableConsumer、FailableSupplier、FailablePredicate、FailableFunction等等。这些类的补充,可以让我们函数式编程的代码更加优雅简洁。

使用场景:函数式编程中处理异常的情况

// 消费者接口在过程中可能出现异常
Consumer consumer = m -> {
    try {
        m.invoke(o, args);
    } catch (Throwable t) {
        throw Failable.rethrow(t);
    }
};
// 使用FailableConsumer的lambda表达式实现,配合Failable.accept方法,可以更优雅地重抛异常
Failable.accept((m) -> m.invoke(o, args), method);

可变基本类型

在java中存大八大基本数据类型,这些数据类型在泛型等场景下,需要使用Integer、Long等包装类。这些包装类有一个特点:它们都是不可变的。如果基础类型值需要频繁变动,使用Integer等包装类将会大量创建对象,降低效率。

commons-lang提供了一套可变的包装类,如MutableInt、MutableLong等,可以解决jdk中包装类不可变的问题。

使用场景:1.需要基本类型作为方法参数并且让方法改变该参数的值;2.集合中频繁改变基本类型的值

// Mutable版本的交换函数
public static void swap(MutableInt a, MutableInt b) {
    int c = a.getValue();
    a.setValue(b);
    b.setValue(c);
}
MutableInt a = new MutableInt(1);
MutableInt b = new MutableInt(2);
swap(a, b); // a = 2, b = 1

反射

FieldUtils、ConstructorUtils、MethodUtils等类补充了java反射的一些方法,一定程度上解决了jdk自带反射api不好用的问题。

出于篇幅考虑,这里只演示一部分api。

使用场景:简化java反射

/* FieldUtils */
// 获取所有成员变量,解决Class类的getFields和getDeclaredFields获取字段不全的问题。
Field[] allFields = FieldUtils.getAllFields(clazz);
// 读取o对象某个属性name的值,允许将其改变为accessible
Object bytes = FieldUtils.readDeclaredField(o, "name", true);
// 获取被@Excel注解标注的字段
List fieldsListWithAnnotation = FieldUtils.getFieldsListWithAnnotation(Student.class, Excel.class);
/* ConstructorUtils */
// 获取可以访问的构造器
Constructor accessibleConstructor = ConstructorUtils.getAccessibleConstructor(clazz, String.class);
// 调用构造器
String s5 = ConstructorUtils.invokeConstructor(String.class, "a");
/* MethodUtils *.
// 调用静态方法
Object result = MethodUtils.invokeStaticMethod(String.class, "valueOf", 123);

数学

Fraction类可以用于模拟分数运算,NumberUtils类可以用于求最大、最小值,判断字符串是否要以转换成数字等。

使用场景:数学运算

/* Fraction */
// 构造分数,4/5
Fraction fraction = Fraction.getFraction(4, 5);
// 分数求和
Fraction sum = fraction.add(Fraction.getFraction(3, 7));
// 将分数转为浮点数
double d = fraction.doubleValue();
/* NumberUtils */
// 获取多个数的最大值,相比之下Math.max方法只能比较两个数字
int max = NumberUtils.max(1, 2, 3, 4, 5);
// 判断一个字符串是否为可转换的数字
boolean parsable = NumberUtils.isParsable("abc"); // false

引入apache的commons-lang3

在项目中可以通过maven引入commons-lang3库,方式如下:


    org.apache.commons
    commons-lang3
    3.12.0

注意事项从3.0版本起,commons-lang更名为commons-lang3,请注意引入的时候名称不要错误本文仅列举了部分常用api,整个工具包的内容非常广泛。如有需要,可到官网进一步深入学习。

整理不易,如果本文对你帮助,欢迎转发点赞支持~

本站内容来自用户投稿,如果侵犯了您的权利,请与我们联系删除。联系邮箱:835971066@qq.com

本文链接:http://news.xiuzhanwang.com/post/3177.html

发表评论

评论列表

还没有评论,快来说点什么吧~

友情链接: