• 主页
  • 归档
所有文章 关于我

  • 主页
  • 归档

凯文·卡斯兰娜

2025-02-28

凯文·卡斯兰娜

  • 西西弗斯、普罗米修斯、伊卡洛斯
  • 文本内容几乎全部来自B站up主文七传个火的视频,本人只是出于兴趣而进行的“扒谱式”的码字。

  1. 西西弗斯:徒劳、荒谬

    凯文:这个世界早已无法拯救,但我们还是必须成为英雄

    西西弗斯面对的是这样一种酷刑:热爱生命与自然,因此反抗了神的约定,于是神惩罚西西弗斯把一块巨石推上山顶,但石头总会因自身重力而滚落下来。这是一种的最可怕的惩罚,用尽全部心力却一无所成。

    加缪:登上顶峰的过程足以丰富心灵,西西弗斯应该是幸福的

    为什么?西西弗斯神话基于一种结果的无意义,这与加缪的”荒谬“高度相关”。荒谬基于比较,加缪的荒谬注重于两个词:生命、意义。对加缪影响颇深的帕斯卡是这样说的:

    帕斯卡:我看到整个宇宙的可怖的空间包围了我,我发现自己被附着在那个广漠无垠的领域的一角,而我又不知道,我何以被安置在这一点而不是另一点,也不知道为何使我得以生存的这一点时间,要把我固定在这一点上,而不是先我而往的全部永恒,与继我而来的全部永恒的另一点上,我看见的只是各个方面的无穷,他把我包围的像个原子。我所明了的一切,就是我很快就会死亡,然后我所最无知的,就是这种我所无法逃避的死亡本身。

    这和凯文有什么关系?凯文实际上是“不死的”,但其实凯文的不死与西西弗斯的永恒,就是这种永恒苦难的必要条件。

    • 生命和肉体的荒谬:

      总有那么一天,人发现或者说他已经三十岁了,他就这样确定了他的青春,但同时,她也确定了他对时间的位置。他承认自己处于一条曲线的某一点上,而这条曲线他已经表名是要跑完的,他自身归属于时间,从这种恐惧之中,他认出了自己最凶恶的敌人:明天。他希望着明天,但他本应该是拒绝的。肉体的这种的反抗,就是荒诞。

      人生而必死,但人们依旧会“反抗”。人就是这样。凯文的处境也是类似。

    • 意义的荒谬:“觉察到世界的厚度”,“江畔何人初见月,江月何年初照人”.MEI;“不是我在仰望星空,而是星空在俯视我。”这是一种视角转换,这种“星空的俯视”依旧是人的自我近观,但是人们还是发现了“一个追求意义的人是如此的渺小”,而相比之下不止意义的巨大的宇宙是如此的庞大。认识到宇宙的无限,人的意义是显得如此渺小。MEI:“尽管如此,每个人还是在有意义的活着”。

      加缪:让我们为此最后努力并得出最后的一切结果吧。躯体、温情、创造。行为、人类最后的高贵

    那么人们是要刻意去追寻“做不成”的事吗,人应该如何要找到自己要坚持的东西。
    注重过程而非结果,这似乎是一种鸡汤。让我们用一种更为贴近生活的比喻:游戏。
    游戏实际上是这样两种悖论:胜利的结局实则是失败以及失败的过程实则是胜利:

    • 当我们最终取得游戏胜利的那一刻,其实标志着游戏的结束,这可以说是一种失败。许多游戏的目标其实并没有意义,球落入网中本身没有任意意义,甚至落入网中那一刻标志的即将到来的下坠。
- 当一个人正视这种无意义的结局并仍然全身心投入时,其过程就构成了意义,甚至包括过程中的失败。

​
当听到“游戏人生”时,我们应该心生敬畏,因为这是一种对结果无意义的一种漠视和全身心投入其中的一种坦诚。游戏结束后无意义,并不影响丝毫那一瞬间的重要性。

 > 伊卡洛斯没有失败。他的坠落是飞行的结果,是另一种胜利的终点。
> 纵然这是一种格局极其狭隘的看法,是浪漫主义者的一厢情愿。
 > 这或许是使世界运转的唯一规则。

 > 这些一连串没有联系的行动,变成了他的命运,这命运便是由他创造的

 只要是人总结出的东西;就一定带有自身的某种局限性,就一定有某种范围,只要超过这种范围,就没有意义。因此,许多的哲学家面对就是这样一种困境。但是,又因为“直面真理”一词听来是如此的具有魅力,以至于让无数思考者前赴后继而不论其意义如何。
    
 > 一个人性格就是他的命运

 当我们这重音放在“他的”二字时(性格指什么暂不谈),我们就会意识到,性格塑造的命运,只属于他自己,而非操弄者所摆布的命运。命运因“他”而有意义。这就是整个前文明的精神。
    
 > 加缪的《局外人》:但是在一个突然被剥夺了幻觉和光明的宇宙中,人就感觉到自己是个局外人,这种放逐无可救药。因为人被剥夺了对故土的回忆和对乐土的希望。这种人和生活的分离,演员和布景的分离,正是这种荒诞感。
 
 凯文类似于这样一种局外人。这种局外是相对于上时代的,因为凯文最留恋的事物在上世代,并随上世代消失殆尽。不可否认凯文对现时代的在意很多来自与对MEI,爱莉和其他英杰的承诺。但是凯文却是以实际行动的真心来对待现世界的,无论是在乎现时代人们的理想、愿望,还是那句“最困难的,是放手不管”。

  1. 普罗米修斯:希望的火种

    普罗米修斯被绑在悬崖之上被鹰鹫啄食内脏,但同时内脏又会不断的长出恢复,由此构成了一种了永恒的折磨。凯文也是如此。符华最终了解了凯文窃取终焉之力的真相:非律者的凯文本身并无法承受终焉之力的力量,因此这种力量无时无刻不在摧毁着凯文。但又因为融合战士的因子,凯文肉身被摧毁的同时不断再生。

那么普罗米修斯窃取的火种又是什么呢?是火种计划,是圣痕计划。这就是凯文“窃取”的火种,将他们保存到了现时代,并仍然相信希望就在其中。这实际上给了西西弗斯无意义结局的一种审视态度:凯文把希望从前世代带到了现时代。而我们每个人,也应以人的历史的视角(而非宇宙)来看待我们每个人的结局,将希望作为我们的意义,将希望传递下去,从而投入我们的生命。这也是所谓“卡斯兰娜”精神的核心。凯文:最初的卡斯兰娜。

  1. 伊卡洛斯:鸟为什么会飞

    “人类终将战胜崩坏”是凯文带给这个世界的意志,那么凯文又是怎么得出这个意志的呢。
    鸟为什么会飞?因为他们不得不飞?不,是因为他们想要飞。鸟对天空的向往没有任何理由,或者说对飞翔的向往本身构成了理由,也构成了意义。此后所有的看似荒谬的行动,都是如此。

  • 哲学
  • 崩三

展开全文 >>

单测中使用Mockito屏蔽方法调用

2025-02-28

概述

单元测试的对象往往是类中某一个方法。基于单元测试规范,我们希望测试是独立的,即希望其他方法不会影响我们对这一个方法的测试。但如果在这个方法调用了其他方法,那么独立性就不可避免的受到影响。

这时,我们可以使用Mockito来帮助我们。

在使用Mockito进行单元测试时,如果你的方法调用了同一个类中的另一个方法,你可以使用Mockito.spy()方法来创建一个spy对象,它可以模拟真实对象的行为,并在需要时改变它们的行为。然后,你可以使用Mockito.doReturn()方法来指定被调用方法的返回值。

示例

下面是一个示例代码,其中假设你要测试的类是MyClass,其中有两个方法method1和method2,而method1调用了method2:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyClass {
public int method1() {
// some code
int result = method2();
// some more code
return result;
}

public int method2() {
// some code
return 42;
}
}

现在,我们想要编写一个单元测试,仅测试method1()方法,但是我们需要确保在测试期间不调用method2()。

在这种情况下,我们可以使用Spy对象和doReturn()方法来指定method2()方法的返回值。以下是示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.mockito.Mockito;

public class MyClassTest {

@Test
public void testMethod1() {
MyClass myClass = new MyClass();

MyClass spy = Mockito.spy(myClass);
Mockito.doReturn(42).when(spy).method2();

int result = spy.method1();

assertEquals(42, result);
verify(spy).method1();
verify(spy).method2();
}
}

在这个测试中:

  1. 我们首先创建了一个MyClass对象,并使用Spy()方法创建一个spy对象。
  2. 然后,我们使用doReturn()方法指定了method2()方法的返回值为42。然后,我们调用method1()方法,并验证它的返回值是否等于42。
  3. 最后,我们使用verify()方法来验证mock对象(spy对象)的方法被正确地调用。

Mockito提供的功能还有很多,比如:

  • 如果method2()是一个没有返回值的方法,则可以使用doNothing来代替doReturn
  • 如果method2()需要参数,则可以使用Mockito.any()匹配任意类型的参数(不过我使用的时候,匹配int型的参数碰到些空指针问题,没有解决)
  • 技术

展开全文 >>

博客搭建记录

2025-02-28

[TOC]

博客搭建记录

技术栈

  • Hexo
  • Github
  • PicGo+jsdelivr
  • nodejs

image-20230322161241959

GitHub提供了一项服务 GitHub Pages,这个 GitHub Pages 可以将我们托管在 GitHub 上的一个仓库中的 html、css 和 js 代码渲染成静态页面。当然,这个仓库是特殊的,所以每一个 GitHub 账户只能够育一个这样的仓库。将相应的博客内容上传到 GitHub 上之后,我们就可以通过https://www.username.github.io 来访问自己的博客,这里的 username 要换成自己的用户名。比如,我的用户名是 michael-zhan,那么就可以通过 https://www.michael-zhan.github.io 来访问我的博客。

Hexo 是使用 nodejs 开发的一个快速、简洁、高效的静态博客生成器。Hexo 使用 Markdown 语法解析文章,然后渲染成相应的网页,然后我们将渲染好的网页代码上传到 GitHub 上就可以了。简单来讲,利用 Hexo,我们只需要使用 Markdown 语法写文章,剩下的事情全部交给 Hexo 去做,我们就可以看到想要的博客效果了。

PicGo 是一个图片上传软件。md文档不像word文档可以直接插入图片,而是插入图片的链接,这个链接通常是本地的。因此当文档发布到网上去时,图片链接自然就会失效,图片无法显示。通过 PicGo + 一个图床网站,我们就可以便捷地把图片上传上网,也就可以在md文档中插入一个网络链接,图片就能成功显示。而 Github 就可以作为图床网站,下面会介绍。

jsdelivr配合 PicGo 一起使用可以加快图片加载效率,用起来十分简单,后面会讲到。

nodejs 是目前相当流行的 JavaScript 运行环境,但在这里不需要了解太多。


Github

新建仓库

新建一个名为 {username}.github.io的仓库

配置SSH

  1. 打开 Git Bash,输入命令:

    1
    $ ssh-keygen -t rsa -C user.email # user.email 为GitHub 上注册的邮箱

    然后直接三个回车即可,默认不需要设置密码。

  2. 查看是否已经有 ssh 密钥:进入用户主目录 C:\Users.ssh,找到其中的id_rsa.pub密钥,将内容全部复制。

    • id_rsa 是私钥不能泄露
    • id_rsa.pub 是公钥可以放心告诉他人。
  3. 打开 GitHub SSH and GPG keys 页面(在自己主页的setting里),新建一个 ssh key:

    Title 为标题,任意填即可,将刚刚复制的id_rsa.pub内容粘贴进去,添加。

image-20230322162833091

  1. 在 Git Bash 中检测 GitHub 公钥设置是否成功,输入如下命令:

    1
    $ ssh -T git@github.com

    成功的话会提示 sucessfully 字样,还有提示does not provide shell access,这不用管。

  2. 如果这一步出现报错:connect to host github.com port 22: Connection timed out

    尝试:

    1. 在C盘——用户——你的主机名文件夹中找到 .ssh 文件夹;(此前配置SSH时会生成该文件夹)

    2. 在.ssh文件夹中新建文件 config,不带后缀(可以新建文本文档,去掉.txt后缀)

    3. 打开 config 文件,输入以下内容,保存。

      1
      2
      3
      4
      5
      6
      Host github.com
      User YourEmail(你的邮箱)
      Hostname ssh.github.com
      PreferredAuthentications publickey
      IdentityFile ~/.ssh/id_rsa
      Port 443
    4. 再次尝试 ssh -T git@github.com


nodejs

没有安装nodejs的话请自行搜索教程,安装后才可进行下面的 hexo 环节


hexo

(提示:以下有关 npm 的命令尽量以管理员身份执行,否则可能报错)

安装与初始化

  1. 在 Git Bash 中输入以下命令:
1
$ npm install -g hexo-cli # 此命令完成对 hexo 的安装

安装完成后,在电脑的某个地方新建一个文件夹(名字可以随便取)专门用于存放博客代码,比如我的是 F:\Blogs,由于这个文件夹将来存放博客的所有内容和素材,以及所有的博客操作都会在其中完成,所以最好不要随便放。

  1. 进入新建的博客目录,输入如下命令:
1
$ hexo init # 该命令完成 hexo 在本地博客目录的初始化
  1. 如果执行后提示 dependency 没有安装(没有提示就可以跳过这一步),而且发现初始化的目录中缺少 node_modules 文件夹,导致这个原因为在自己的博客文件夹中 .gitignore 文件中添加了 node_modules/ ,导致更新的时候,这个文件夹被忽略,没有被更新上去。

    解决方案:

    1. 进入博客当前文件夹路径
    2. npm install
    3. hexo server

生成静态文件

  1. 在 Git Bash 中输入以下命令:

    1
    $ hexo g # 生成静态文件

本地预览

  1. 在 Git Bash 输入以下命令:

    1
    $ hexo s # 开启本地预览

    hexo s 是开启本地预览服务,打开浏览器访问 http://localhost:4000 即可看到内容,Ctrl+C 停止本地预览。本地预览可以实时查看博客的编辑情况,待博客写完后一起部署到 GitHub 上。

    第一次初始化的时候 hexo 已经帮我们写了一篇名为 Hello World 的文章,默认的主题比较丑。

部署到 GitHub

  1. 配置站点配置文件

    hexo 有 2 种 _config.yml 文件,一个是根目录下的全局的 _config.yml,一个是各个主体 theme 下的 _config.yml。将前者称为站点配置文件, 后者称为主题配置文件。

    打开根目录下站点配置文件 _config.yml,配置有关 deploy 的部分:

    1
    2
    3
    4
    5
    6
    # Deployment
    ## Docs: https://hexo.io/docs/one-command-deployment
    deploy:
    type: git
    repo: git@github.com:username/reponame.github.io.git #username是你的用户名,repo那么是你的仓库名
    branch: main #2020年github项目主支默认名称已经从master改为main
  2. 安装插件

    在 Git Bash 中输入以下命令:

    1
    $ npm install hexo-deployer-git --save # 安装部署插件

​ 如果不进行上述操作,直接使用 hexo d 部署到 GitHub,可能会报错。

  1. 部署到 GitHub

    在 Git Bash 中输入以下命令:

    1
    $ hexo d

    部署成功后,打开对应的网址 https://www.username.github.io,如果出现了和本地预览一样的效果,那么,表明部署成功。

配置Hexo基本信息

  1. 配置站点信息
    打开根目录下站点配置文件_config.yml,设置如下内容:
1
2
3
4
5
6
7
title:网站标题
subtitle:网站副标题
description:网站描述
keywords:关键字
author:作者
language:网站使用的语言
timezone:网站时区。Hexo 默认使用您电脑的时区

其中 language一定要设置为主题(下面有介绍主题)能够识别的语言(不然中文可能出现乱码),在 \themes\yilia\languages\ 中可以找到。

发布文章

  1. 在 Git bash 中输入以下命令:

    1
    $ hexo new '文章名'

    新的md文件就会在source\_post中自动生成,可自行编辑

  2. 编辑完成,在 Git bash 中输入以下命令即可发布(以后我们对任何文件进行了改动也是同样如此更新)

    1
    $ hexo d -g

可能出现的BUG

hexo的module找不到

很多人的电脑上的可能不止一个 npm 或者 nodejs,比如本人就因机器学习安装了 Anaconda,而 Anaconda 有着自己一套环境系统。我可能在学机器学习时改动了一些系统路径(不清楚),总之导致一段时间后我的 git bash 提示找不到 hexo 的文件。

这里提供一个使用完整的绝对路径来运行 hexo 命令的方法。

正确的情况是应该是,nodejs 是下载在我们的博客目录的,所以我们使用也应该是我们博客目录下的 node_modules 的 hexo,下面以我的博客目录为例,使用绝对路径来运行发布文章的命令:

1
$  F:\\Blogs\\node_modules\\hexo-cli\\bin\\hexo d -g

本人通过这样的方式解决了问题。

SSH 连接超时

即上面提到的connect to host github.com port 22: Connection timed out,在发布文章hexo d -g 也可能遇到这个报错,解决办法是一样的。


hexo主题

Hexo可选主题的很多,须要另行配置,毕竟默认主题一点个性也没有。
我选用的是yilia,轻量化的一款。


PicGo+jsdelivr环节

下载PicGo

Github作为图床网站

  1. 在 Github 新建一个仓库用于存放图片,这次仓库名没有限制,保证仓库是 public 即可
  2. 新建 token:Settings -> Developer settings -> Personal access tokens,最后点击 generate new token。
  3. 过期时间自行选择。
  4. 填写Note(描述,你可以填博客图床),Select scopes勾选 repo
  5. token 生成,复制下来,下面要用

配置PicGo

  1. 依次打开 图床设置 -> Github 图床

  2. 按自己情况填入相关信息

    image-20230322172547192

  3. 可以看到设定自定义域名,依照https://cdn.jsdelivr.net/gh/username/reponame 的格式填写就行,repo 那么就是你图床仓库的名字,这样就算用到 jsdelivr 了

  4. 如果你使用 typora 的话,可以在 typora 的图像设置里面选择配合使用 PicGo,插入图片就很方便了。


写给自己

至于搭这个博客,原因是考研复习累了。先是学了三四天前端入了个门,然后码了几天字,最后又花了两天搭建这个博客。前后有一个多礼拜没复习力。

压力很大啊,身边有不少同学都想考计院,好像还有个狠人都已经N轮复习了。

这些天作息也不太规律,天天到一点睡觉,这不对吧。

是时已是晚上六点,就此搁笔。今晚调整,明天开始重回考研状态~✨

QQ图片20230322181511

  • 博客建设

展开全文 >>

奥托·阿波卡利斯

2025-02-28

奥托·阿波卡利斯

  • 尼采
  • 文本内容几乎全部来自B站up主文七传个火的视频,本人只是出于兴趣而进行的“扒谱式”的码字。

b

首先,我们看这样一组对照,几乎可以完全自信的明确指出奥托的人物塑造与尼采哲学的关系。

悲剧的诞生 - 《悲剧的诞生》
愚者的黄昏 - 《偶像的黄昏》
意志的彼岸 - 《强力意志》《善恶的彼岸》
阿波卡利斯如是说 - 《查拉图斯特拉如是说》

那么,奥托与尼采的联系又体现在何处呢?这可能不得不提到“魔怔”一词,毕竟后世许多对“疯子尼采”的看法大就是这样一种态度。一方面,“查拉图斯特拉”,这位宣传“永恒轮回”学说的教师,在人们看来,是相当魔怔的;而奥托,当我们以五百年的目光审视奥托他的行动时,我们同样也不得不认同他的魔怔——或者说,“强力”。

而尼采自己,我们不得不承认晚年他的疯魔,就连鲁迅也曾说(这是真说过):”尼采就自诩过他是太阳,光热无穷,只是给予,不想取得。然而尼采究竟不是太阳,他发了疯”。后世更有法西斯主义自标榜为尼采思想的信徒,带给了世界以最大的灾难。自此,尼采在人们心中彻底妖魔化。

我希望越来越多的人能够合理的正视尼采的思想,尼采思想实际上批评了那些懒惰者,要求人们以更积极的态度面对生活。但由于本文只是借尼采与奥托的映照关系来解释奥托行为背后的哲学隐喻,这样的隐喻通常是不够深刻而且并非能照顾到尼采思想的全貌,所以不多解释(多的我也不懂呜呜)。

就此开始。


  • 奥托的三种特质

    1. 自私

      这是一个人能做到的,最自私的事。 ——动画短片《阿波卡利斯如是说》

      这里的自私是一个褒义词,表示一种经过真正审视的自我的意志——那是采用“罕见而独有的尺度”、“近乎癫狂的气质”,是“对于被众人冷淡的事物的炽烈情怀”,是因为“我的意志如此”所以“我就要这样做”。

      这种意志是独有的,即为自己为私有。很多人事实上没有办法触及到这样一种自私。
      而这种自私,正是生成之意志所必须的。

      当自私自利开始匮乏时,最好的东西也就匮乏了,本能的选择并不利于自己的东西,倾向于“无利害的”动机,这几乎就是颓废的公式。 ——《偶像的黄昏》

      要生成这样的自私,必须经过“价值”的反思:你认为有价值的东西,到底是什么?那种激情、那种信念、或者那种至高的瞬间,真的是你“愿意为之生为之死”,并且“无数次如此”的吗?

    2. 鹰与蛇

      查拉图斯特拉在正午时刻,看到了那两只动物。鹰身上缠着一条蛇,蛇盘绕在鹰的头颈上。而这两种动物,恰恰是奥托的另外两种品质——“高傲”和“智慧”的象征。

      鹰是最高傲的动物。它完全生活在高空,乞灵于高空。高傲就是确信自己不再与任何他人混淆起来,是由高度和上等存在来规定的高高在上。这和“自大”或者“傲慢”是完全不同的。自大或者傲慢需要与低等联系起来,是想与低等脱离,以突出自己——因此仍旧依赖于低等,而且必然依赖于低等。“自大和傲慢”始终从低等中得到规定,才能够抬高自己,这只是它们自负地幻想出来的东西。而高傲却不是。所谓“高傲”,就是某人“持守在自己的本质地位”,让“自己的使命”充分发挥出来的“坚定性”(Entschiedenheit)。

      而蛇,则是最智慧的动物。“智慧”的意思是控制真正的知识,但面对知识的呈现始终能保持灵活,并且避免钻进自设的圈套之中。这种智慧包括调节和转变的力量,也包括对虚假面具的控制。另外,蛇的那种“盘绕”,其实也正是“永恒轮回”的象征,这是真正知识的一部分。

      这两种特质,正体现在奥托所培养的两位女武神,幽兰黛尔和丽塔身上。幽兰黛尔体现着那种“本质地位的坚定性”,也就是高傲;而丽塔体现为那种“知识的隐秘与灵活”,也就是智慧。这也正是天命女武神对奥托特质的继承。

      因此,我要求我的高傲永远跟我的聪明一起同行 ——《查拉图斯特拉如是说》

      什么高级丽幽糖(喜)

    3. 大发明家

      这是卡莲在最初,赠予奥托的存在之肯定:“你以后一定会成为大发明家。”也是卡莲在最终告别时,称呼奥托的词语:“永别了,我的大发明家。”

      对于这个词,或许很多人都觉得,只是在描述“智慧和科学能力都很高超的天才”。确实,在短促又漫长的生命中,奥托的确发明了很多东西。 但在更深层次,大发明家也象征着“创造者”,不仅仅是创造“物件”,更是创造“价值的标尺”。

      尼采被称为“用锤子做哲学的哲学家”,不惜“重估一切价值”。也就是说,尼采并不相信任何“元价值”,怀疑一切理所当然的道德——“上帝死了”。

      浅层上说,这当然体现为奥托是一个无视道德准则的“坏人”,他并不会真的把平等、同情、谦逊当做什么有价值的品质。他可以怀疑一切,甚至包括“人和崩坏的二元对立”。 在更深的层次上,奥托被告知的元价值,一方面来自于他的亲族,另一方面来自前文明的传承。

      他的亲族可以概括为“旧天命教会”,映照着现实中的道德;而以MEI博士为代表的前文明极端理性,则对应从苏格拉底、柏拉图开始的理性传统。在重估价值的过程中,尼采集中批判的,正是这两者。这两者的共同点,在于忽视,甚至阉割了人类自身“激情”的作用。

      卡莲(以及奥托)反抗天命教会,体现的是对“彼岸价值”的怀疑,对“人在大地上生存”本身的肯定。另一方面,后续剧情,奥托又在面对崩坏的思路上,事实上形成了一种对MEI的批判:他试图实现“律者为人类而战”的构想,依托的正是“激情之爱”,以及因爱而生的“激情之怒”——就像28章末尾奥托提到的,前者可以做到的,后者也可以做到。奥托也正是相信这一点,因此萌生了让琪亚娜体内的西琳人格觉醒以激发琪亚娜体内的律者权能,但寄希望于琪亚娜能够由“爱”的力量从西琳手中夺取身体的控制权,从而变成一个“拥有律者权能的人”。

      而爱和怒的共同点就在于,肯定了人的生存中激情的力量。

      琪亚娜反抗的是自己“作为崩坏使徒的容器而诞生”的原初命运,笨蛋不知道遵从元价值,只会遵循自己的激情,和由内而外的意志。

      在这一点上,奥托其实和琪亚娜也是非常相似的。最后,如果回到短片本身,大发明家的这种“创造”,当然也体现为“创造新的生命、新的大地”,或者说,创造“新的世界”。

      但要想做到这一点,绝非易事。


  • 三个境界

    《查拉》中说,人的精神有三重境界:初级境界就像骆驼,中级境界就像狮子,高级境界就像孩子。如果说的简化、通俗一点,可以这样理解:骆驼在“背负、顾忌着虚假的价值”,狮子“否定虚假的价值,在斗争中争取自我和自由”,最后孩子“由自己的生成之意志,订立并肯定新的价值”。而这三重境界,正体现在奥托人生的不同阶段,甚至在短片最后的闪回中有所展示。

    在骆驼阶段,人只是盲目的背负,可以说是缺乏反思的。很多重负压在人的精神上,久而久之,人默默地忍受着,甚至不觉得那是什么重负。人们为了委曲求全,就如骆驼那样始终驮着它们一直走下去。他们只是盲目顺从被告知的那些约定俗成的“元价值”,不愿拿起重估与战斗的锤子。但终究,这种忍耐到达了极限,对奥托来说,这个极限就是卡莲的死。

    在最寂寥的沙漠中,精神发生了第二种变形:精神在这里变成狮子,精神想要夺取自由而成为自己的沙漠的主人。狮子与最后的上帝为敌,它意愿与巨龙争夺胜利。神不想再称为主人和上帝的巨龙是什么呢?这巨龙就叫作“你应当”。巨龙躺在路上,金光闪闪,那是一头有鳞动物,每一片鳞上都闪烁着金色的“你应当!”但狮子的精神却说:“我意欲!”因为“你应该”被当作神圣的原则而热爱由来已久,为了战胜“你应该”的虚幻和专横,需要的就是狮子这种掠夺成性的斗志,消灭那种忍耐与虔敬的精神,才能获得创造新价值的权利,这个过程近似于狮子的捕食工作。而奥托在这个过程中,所展示出的,也正是狮子一般的残忍和侵略性。那狮子的精神,就是要争取自由,否定所谓神圣的义务,这是创造新价值的必经之途。

    最后,人们终于要创造新的价值了,但这个任务即便是狮子也不能完成。所以精神会过渡到“孩子”,孩子天真、健忘,像一个新的游戏、一个自转之轮,是一个新的开始,是神圣的肯定。就像孩子创造一个新游戏一样,人们需要对自己生命进行神圣的肯定,自己的精神就是意志,这个世界是自己的。正如奥托通过没落创造出的世界,正是事实上属于卡莲的。正如奥托的没落之后,他们又回到了孩子的模样。这个没落的过程本身,昭示着“超人”。


  • 没落的桥

    在短片展示的剧情中,奥托成了一种中介、一座“桥梁”——通往卡莲的复归存在、通往一个新世界的诞生。这种过程精确而“危险”,但他终究成功了。但成功的代价,正是奥托自己“不再存在”,正是一种“没落”。这座“桥梁”的两端,一端是猿猴,一端是超人。人本身不是目的,人是通往超人的过程。

    人是一条不洁的河流。要容纳不洁的河流而不致被污浊,人必须是大海。 ——《查拉图斯特拉如是说》

    用“超人”这个名字,尼采绝不是要表示某个不再是人的东西。作为“超出……之外”的“超”(Über),是与“一种完全确定的”人联系在一起的。“桥梁”意味着,人的价值正在于“克服人类”,从而抵达“超人”。而这种“自我克服”,其实就是“通过将激情转变为价值”,从而“让自己成为高等的人”的过程。

    同时,这种自我克服必须来源于人“内在”的活力和冲动。超人不愿让“生命”停滞于一种可能性和一种形态,而是愿意让“生命”具有它最内在的生成权利。超人将会为“生命”设计、筹划各种新的、更高的可能性,从而在创造中使之超越自身。

    人之所以伟大,乃在于他是桥梁而不是目的:人之所以可爱,乃在于他是过渡和没落。
    (以下为片选)
    我爱那样一种人,他们不向星空的那边寻求没落和牺牲的理由,他们只向大地献身,让大地将来属于超人。
    我爱那样一种人,他干活,动脑筋,是为了给超人建住房,为了给超人准备大地、动物和植物:因此他情愿自己没落。
    我爱那样一种人,他把自己的道德变成自己的偏爱和自己的宿命:因此他甘愿为自己的道德生存或死灭。
    我爱那样一种人,他的灵魂很慷慨大方,他不要人感谢,也不给人报答:因为他总是赠予而不想为自己保留。
    我爱那样一种人,他的灵魂虽受伤而不失其深,他能因小小的体验而死灭:因此他乐愿过桥。
    我爱那样一种人,他们全像沉重的雨点,从高悬在世人上空的乌云里一滴一滴落下来:他们宣告闪电的到来,而作为宣告者灭亡。 ——《查拉图斯特拉如是说》

    a

    当然,超人是一个解释起来非常困难的名词,或许听了后面讲述的“永恒轮回”思想,才能更好的认识何为超人。这也是真正的“知识”,奥托这般意志的源头。


  • 永恒轮回

    在《快乐的科学》一书中,临近结尾,尼采却重点叙述了人“最大的重负”。

    最大的重负。
    ——假如在某个白天或某个黑夜, 有个恶魔潜入你最孤独的寂寞中,并对你说:“这种生活,如你目前正在经历、往日曾经度过的生活,就是你将来不得不无数次重复的生活;其中决不会出现任何新鲜亮色,而每一种痛苦、每一种欢乐、每一个念头和叹息,以及你生命中所有无以言传的大大小小的事体,都必将在你身上重现,而且-切都以相同的顺序排列着一同样是这蜘蛛,同样是这树林间的月光,同样是这个时刻以及我自己。存在的永恒沙漏将不断地反复转动,而你与它相比,只不过是一粒微不足道的灰尘罢了!”
    ——那会怎么样呢?难道你没有受到沉重打击?难道你不会气得咬牙切齿,狠狠地诅咒这个如此胡说八道的恶魔吗?或者,你一度经历过一个非常的瞬间,那时,你也许会回答他:“你真是一个神, 我从未听过如此神圣的道理!”
    假如那个想法控制了你,那它就会把你本身改造掉,也许会把你辗得粉碎。对你所做的每一件事,都有这样一个问题:“你还想要它, 还要无数次吗?”这个问题作为最大的重负压在你的行动上面!或者,你又如何能善待自己和生活,不再要求比这种最后的永恒确认和保证更多的东西了呢?

    粗略的说,对“永恒轮回”的理解,可以划分成两个方向:一是作为促进人更好看待“此刻瞬间”的一种“思想实验”;二是作为一种“实指”,也就是说,真的以事实上的永恒轮回来理解时间性。对于“看作思想实验”的理解方式,一方面,这种思想如此沉重,以至于仅仅作为一种“可能性”都有足够的分量。但另一方面,人也很容易在此预设下,逃离这种构想的严肃性,以“幸亏只是实验”、“世界真实状态大概不是这样的”之类的理由,让自己逃离这种最大的重负,获得虚假的解脱。

    尼采希望我们是以后者的角度去审视这个问题。

    那么,崩三的剧情是怎么融入到这种假设中的呢?

    长光:这个服务器用种种机制生成了不知多少个游戏世界,而你…只是其中某个世界的一名角色。
    长光:你每时每刻在游戏中的行为都会作为存档被记录在这个服务器中,你可以查看它们,但没有修改的权限。
    长光:你对此习以为常,认为游戏服务器中的数据是无人可以更改的。但对于服务器本身来说,事实却并非如此。
    奥托:如果我们再大胆一些,将思考的维度上升至虚数树的领域,那人类的时间也不过是一种记录在磁带 上的数据。
    奥托:所谓[历史」是这条磁带上已经记录下来的部分,所谓「未来」则是将要呈现在这条磁带上的未知….
    奥托:而我们的「现在」,就是这条磁带上那根唯一的探针。 ——《崩坏三》文本

    那么,永恒轮回会导致什么?

    1. 最大的重负

      就如同原文最后提到的问题:“你还想要它,还要无数次吗?”这样一个问题,这个思想随时随地都会压在我们的行动上面。哪怕再微小的行为,也会在无数次重复中获得持存性。没有什么是转瞬即逝的,没有什么是短促而影响甚微的,一切不断生成的筹划,都需要严肃的对待,因为它们无一例外的会被死死钉在永恒上。

    2. 自由意志

      如果说一切都在重复,那么是不是说,永恒轮回就否定了自由意志的存在?海德格尔给出的答案是否定的。简单来说就是:如果你让你的“此在”滑入阴险的境地,滑入不确定性状态及其所有后果之中,那么,这种“阴险之境和不确定状态”就会轮回,而且将成为那个曾经存在过的东西。但如果你从下一个瞬间中并且因此从每一个瞬间中塑造出一个“最高的瞬间”,刻画和确定其中各种后果,那么,这样的瞬间就将轮回。不过,这种永恒是在你的瞬间中得到决断的,而且只有在那里才能得到决断——根据你对你本身,意愿什么以及能够意愿什么。因此,自由意志仍旧存在。

      也就是说,什么被轮回,依旧能被我们决定。时间只有两种:正在生成并由意志刻画的“此刻瞬间”,以及意志无法介入其中的过去和未来。时间的神圣性不存在于任何基于人类历法的“节日”,时间的神圣性只存在于当下瞬间。

      未来终究会作为每一个瞬间意志所刻画出的结果,被确定下来。但面对“过去曾是”,意志是无能为力的。

      在现实维度,尼采通过“爱命运”,把“过去曾是”变成了“我就是意欲如此”。

      但在游戏的维度中,奥托甚至把过去从“意志无法介入的状态”,强行转变成了和此刻一样“由意志刻画的瞬间”。或许,作为领会了永恒轮回的人,奥托可以爱他的命运,但他唯独,怎么也无法爱那个卡莲死去的瞬间。

      奥托:如果我们保持住[现在]这枚探针的特殊性,而把它强行拉回属于[过去的]的磁带上……那么会发生什么呢?


  • 卡莲

    为什么,一定要回到那一个卡莲死去的“瞬间”。

    奥托选择让意志的作用回到卡莲死亡的“特殊瞬间”,追求的是身体、记忆、灵魂三重视角上同时达成的、最苛刻的“同一性”。这就保证了,复活的卡莲就是那个卡莲,而不是一个不同的什么人。其实这还涉及一个“什么是本我”的问题,也是一个经典的哲学问题,但这里就不做说明。

    所以,必须是这样的方式。必须是这个瞬间。

    奥托无疑就是超人了。那么,被奥托复活的卡莲呢?

    奥托能把自己看成一种过渡,能在强力意志“生成”的特征中,根据永恒轮回这个思想,把激情转变为价值,把光亮显现出来,他自然是超人。

    但他还要“教给人超人”,也就是教给卡莲。这也是为什么,剧情要卡莲知道,是奥托“复活”了她。

    超人的伟大性就在于:他把强力意志的本质置入一种人类的意志之中,这种人类,在这样一种意志中意愿自身成为大地的主人。在超人中存在着“一个本己的审判权,没有更高的法庭来审判它了。
    ——《查拉图斯特拉如是说》

    现在,我教你们什么是超人,超人是大地的意义。让你们的意志说:超人必是大地的意义吧! ——《查拉图斯特拉如是说》

    世界将在那一刻只为了一个人而转动。 ——奥托

    卡莲的直接死因,其实正是她在最后时刻,没有困在自己因理性思考而陷入的绝望状态,而是遵从了一种最本源的激情:“从崩坏手中拯救这个世界的一切生命。”而奥托最终从死亡中挽救了这种激情。

    不仅如此,更重要的在于:他的这种作为,创造了一个事实上“因卡莲而得以存在的世界”,这在字面意思上,其实就意味着“卡莲是大地的主人”。

    卡莲从小希望“拯救世界”,把大地的意义当做自己的意义。但反过来,世界并没有回报卡莲,反而“审判”了她。但在奥托复活卡莲的同时,也等同于在虚数之树上创造了新的枝杈、新的世界。于是,卡莲也反过来成为大地的意义了,两者终于达成了一种统一。

    奥托纯爱战神,我哭死。

    所有的神都死了:现在我们意愿超人活着——在某个伟大的正午,就让这成为我们的最后意志吧!
    ——《查拉图斯特拉如是说》


一个人,要犯下多少恶行 才能在地狱的尽头,将她带回黎明 。

一个人,要走多远的距离 才能在时光的尽头,追回最初的自己。

d

看完这篇文章,希望大家能重新审视奥托·阿波卡利斯这个人物。对于奥托,人们有太多的憎恶,但也不乏因其爱而感动的人。在这里,我不是想否认“奥托是一个罪无可赦的人”这一叫骂。平心而论,谁都不想在现实生活碰到这样一个为达目的而不择手段之人(除非你是卡莲本莲)。但是对于“奥托是否是真心漠视自己犯过的所有罪孽”、“奥托就是极恶”这类问题,我希望大家更理智客观。爱一个人,必定是理解并支持所爱之人之理想。卡莲是爱人的,那么奥托也一定是“本愿为了世界的一切美好而战的人”。但是他所要面对的残酷的事实,是自己所爱的、唯一爱自己的、愿为世上所有美好而战的人,被世人送上了行刑台,自己想要救她,放出却最终杀死了卡莲的崩坏兽。这样一种现实对理想的冲击,透过这五百年的故事,我想,是每个人都无法轻视的。在审视奥托罪孽的同时,与我而言,更是在审视我自己。

动画短片《阿波卡利斯如是说》

  • 哲学
  • 崩三

展开全文 >>

芽衣与爱莉希雅

2025-02-28

雷电芽衣

  • 克尔凯廓尔(下称祁克果)
  • 文本内容几乎全部来自B站up主文七传个火的视频,本人只是出于兴趣而进行的“扒谱式”的码字。

前阶段芽衣的剧情在《罪人挽歌》中达到了高潮,面对背负着西琳人格的琪亚娜,芽衣无法忍受她那种“宁可牺牲自我也不想让崩坏破坏世界”的做法,对琪亚娜的爱,使她即使背弃世界也想要保护琪亚娜。

image-20230322140324101

比起这个世界,你更重要 ——雷电芽衣

芽衣体内的律者权能重新觉醒,雷之律者降世,所以现在让我们开始琪亚娜打怪升级最终战胜雷之律者并且芽衣在最后向琪亚娜深情告白:“阿里嘎多” 后永远离去的经典剧情——啊呸!

显然,《罪人挽歌》是前阶段剧情的结束,也是新的成长的开始。崩三中最恢宏的篇章“往世乐土”将为我们讲述芽衣从雷电女王最终到“信仰骑士”的成长经历,同时也揭开了前世代融合战士的面纱,最终带给玩家的,是《因你而存在的故事》。

也许很多人觉得芽衣无非就是对琪亚娜爱的深沉罢了,可如果仅仅是这样,那么芽衣也就只能是雷律,而不是最终的“始源律者”。往世乐土的背后,爱莉希雅的出现,逐火十三英杰的故事,引出我们的另一个重要人物:祁克果。

祁克果,被人们称为“存在主义哲学之父”,他主张人们有三种生活:审美的生活、伦理的生活、信仰的生活。接下来的部分,我们将套上祁克果哲学思想的放大镜,去介绍雷电芽衣的种种历程,以便让我们更好的理解芽衣是如何从雷电女王走向信仰骑士,其中又藏着何种难以想象的绝望与不可思议的坚定。


  • 三种生活

    祁克果说,作为个体的人们有三种方式可以选择:审美的生活、伦理的生活、信仰的生活。首先,这听起来有点像尼采的“骆驼、狮子、孩子”,但有些不同的是,祁克果并不是像尼采那样去要求人们成为“孩子”,而是提出了这三种选择,并强调我们每个人都可以去选择最适合自己的那种生活;其次,听到“信仰”这一词的时候,我想先提醒一下各位不要代入到我们日常所理解的那种宗教信仰当中去,否则会给理解祁克果带来困难。这里的“信仰”放到崩三当中显然是和爱莉高度相关的,这个后面再讲。实际上,芽衣在成长经历中和确实经历了这三种生活,而且是以“罪人挽歌”那个时候为节点,前后各遍历了一次,我们也可以就此将芽衣的经历分成两个大的阶段——我想称为“弱者芽衣”与“强者芽衣”。

    在“弱者芽衣”时期,首先芽衣所在的,当然就是“审美阶段”。什么是“审美阶段“呢?通俗来讲可以理解为“为自己而活”,在较低一点的层次呢,就类似于一种“享乐生活”,以满足自己的低级欲望而活,而在这种生活中人们通常会因为不断地重复而走向厌倦。在较高一点的层次上,审美生活更多的是一种竭尽全力发挥自己的才华,创造属于自己的价值,不过也因为这种行为的直接驱动力是自己的欲望,因此也是一种“审美的生活”。加入天命前的芽衣就是这样一种审美阶段,那时候的芽衣还只是“雷电女王”,对周遭情感的不在意,把自己的意志定在一个更高的优先级上,只为自己活着,也并未在心灵上与永恒的孤寂建立一对一的联系。真正使弱者时期的芽衣从审美到伦理转变的,是“长空市律化事件”。

    芽衣:你在做什么?
    琪亚娜:这不是很明显吗,我在救你啊!(附上一张琪亚娜的笑脸图)

    事件催生了审美与伦理之间不可调和的矛盾,迫使芽衣不得不做出一种“非此即彼”的选择以进入新的“伦理阶段”——什么是”非此即彼”?

    人已被保证拥有最好最美妙的东西是:自由、抉择。非此即彼,这词令我印象深刻。使用这组词意味着有可能把最令人恐惧的对立事物付诸行动。这组词如同一句有魔力的咒语影响着我,而且,我的灵魂由此变得极度严肃认真,有时几乎悲伤痛苦。 ——祁克果

    非此即彼的选择不是仅存在于眼下的选择,而是一种“绝对性”、’有关生活方向”的抉择,这种选择的后果往往是伴随着价值的生成、行动的生成、责任的建立以及——不可回头的决然。当人们真正意识到这般不可逆转的选择时,往往感受到真正的恐惧。祁克果区分了害怕与恐惧——

    “害怕”是对人“自身意识能力之外的、危险的可能性”的一种畏缩和退却。
    “恐惧”则是“内在与自身行动能力的巨大可能性”而产生在人的内心当中的。

    简单来说就是“人居然可以决定要成为什么样的自己”、“人居然可以自己去存在”。这似乎与我们常听到的常推崇的“自由意志”相抵触,难道我们想追求的不就是这样一种自由吗?其实哲学家早就向我们追问过:你想要的真的是一种完全由自己决定的生活吗?你真的确定在那样一种情况下你能够做出一种完全基于自己意志的决定吗?关于自由的问题也是哲学上一个被反复讨论的问题。实际上,大多数人们会做的不过只是逃避自己的选择,堕落在每天日复一日的简单行动并恐惧将自己投以某种长期的委身之中。

    但即使是在这样日复一日的生活中,也总有那样的“关键的”、“危急的”时刻,使得我们不得不直面这一关键性选择。

    令人惊诧的是,强制还是能够一某种方式掌控某人,以至于任何关于选择的问题都不复存在——而且,人选择了合适的事物。就像在死亡时,大多数人都选择了合适的事物。 ——祁克果

    虽然祁克果认为人确实可能在同时以不同的原则行动,但最终,在一个“关键危急”的时刻,还是不得不在“审美-伦理-信仰”这三者中做出选择。对于芽衣而言,这样是时刻就是“长空市律化事件”。

    在长空市造成的伤亡让伦理(即善恶的区分)直接拷问了芽衣:你是否真的能够保持这样一种对周遭事物不在意的态度,尤其是当伦理与自身的审美产生冲突时,你是否真的能把自己的感受放在他人之上。显然,芽衣是承受不了这种拷问的,也无法把自己的意志凌驾在他人的感受之上。至于原因,我们当然不会说什么“芽衣本身的善良”尔尔,而是上面提到的“强制”:

    不具备逻辑上的强迫性,但对于某些个体来说,却具有强迫性 ——祁克果

    我们说不出为什么芽衣在那时选择了伦理,但这完全是在那时“完全出自芽衣内心的强迫”。之后的芽衣就逐渐成为游戏主线开始那样,“温柔地对待周遭的一切事”,也所谓“伦理的生活”。

    为了理解伦理的生活,我们就要理解所谓的“善”。简单来说,就是“不以自己的利益而是以相关更多数人的利益为目的”,即做“对更多数人有利的事”。同时善的生活也要求人们以理性去思考。但是对芽衣来说,这种“为大多数人的利益”而做出选择,实际上还有一部分原因是出自受到创伤后形成的“保护型人格”。我们可以参考康德的“绝对律令”的说法:

    希望自己的做法成为普遍的法规 ——康德

    这句话可以理解为即“希望自己对待他人的方式也是他人能够采取的对待自己的方式”。游戏里芽衣那个“温柔的芽衣”就更多的是这种保护型人格。

    至于那个常常被人以[温柔]评价的我,反倒接近与······在经受巨大打击后出现的保护型人格。 ——雷电芽衣

    在审美阶段,在长空市造成的伤亡成为了芽衣的一种“罪孽”,这种罪孽并不会因为转变到伦理阶段就消亡,正因如此,芽衣要求成为女武神为保护人们而战斗,同时也为自己的心脏装上一个炸弹。

    但是,到了《罪人挽歌》时期,伦理的生活同样被打破,“伦理的生活”与”对琪亚娜的爱”构成了新的非此即彼的选择,只是这次,芽衣无法舍弃的是后者。


  • 对琪亚娜的爱

    其实在解读芽衣对琪亚娜的爱之前,我们可以先介绍一下祁克果自己的爱情,因为这有助于理解后期芽衣对琪亚娜的爱。

    祁克果是一个将哲学真正实践到自己生活中的人,很多哲学家其实做不到这一点。

    那就是······找到一个“对我来说是真的真理”,找到那个“我能为之生为之死的观念”。
    发现所谓的客观真理,完成所有的哲学体系,评论这些哲学体系,这有什么益处呢?提出一套国家理论,把各种细节组合成一个单一的整体,并由此构建出一个我并不居住其中的,而是被他人评头论足的世界,这对我有什么益处呢?
    如果真理站在我面前,冰冷而赤裸,毫不在乎我是否认出了它,这对我有什么益处呢

    我当然没有否认,我依然认可知性的命令,并且这种命令可以对人产生影响。
    但是,这种命令必须付诸我的生活,这是我现在认为最重要的事。

    祁克果年轻时爱上了一个叫做雷吉娜的姑娘,两人甚至已经定下了婚约,但最后却取消了婚约,做一个孤独的人。“我爱她,但是我们的结合遭到了神圣的反对,我就是这么理解的”。在他所给出的三种生活中,祁克果选择的是“信仰的生活”。我们不能说他不爱雷吉娜,他后期很多的作品就是为雷吉娜而写,在死后也将所有的财产留给了雷吉娜。

    对我来说,订婚和婚姻一样,始终是一种束缚。因此我所做的正是以同样的方式,把我的财产归还给她,就好像我曾经和她结过婚一样。

    但是在后来(应该是很多年过后),祁克果在日记中写道:“倘若我当时真的拥有信仰,我本是可以属于雷吉娜的”——理解祁克果的这句话,也是我们理解后来芽衣对琪亚娜的爱的核心,也就是“信仰骑士”与“弃绝骑士”的区别,我们后面会讲到。

    我们回到罪人挽歌的剧情,这其实就很像祁克果的爱情了,芽衣和祁克果都面对着这样一种境地:面对自己要坚守信仰,而不得不与最爱的人分离。只是在芽衣这里,她的信仰就是“对琪亚娜的爱”,而她保护信仰的唯一的方式,就是离开琪亚娜,加入世界蛇。

    信仰是不同于伦理的存在,同时也不关乎自我。在伦理中我们尚区分善恶,但在信仰与伦理的选择中,我们要么接受善恶的同时存在,要么就是同时排斥二者。芽衣能接受伦理吗?芽衣还能以“为大多数人的利益”为原则吗?如果是,那么琪亚娜就终将失去生命——芽衣再次凭借着“强制”,选择了琪亚娜,并同时离开了琪亚娜。由此,芽衣完成了一次信仰之跃。

    祁克果认为,即使是“信仰骑士”,也分为两类:“无限弃绝的骑士”与“真正的信仰者”。在罪人挽歌里,芽衣成为的是前一种。

    借着无限的弃绝,他饮尽了生活的苦涩,他知道了无限者的福佑。他把自己置于一种从未有过的历史境遇之中。这种境遇要求超越一切世俗性的价值,接受人作为存在的本质和恒久的立场。——海德格尔《尼采》

    • 你不再害怕了吗?
    • 真奇怪,心里······已经感觉不到害怕了。还有不甘、自责、悲伤、痛苦······所有的感情都消失了。平静,心中只剩下了平静。 ——《崩坏三》文本

    芽衣所弃绝的,是有关过去生活的牵挂与伦理的重压,伦理的重压是和长空市的罪孽紧密联系的,但在弃绝的过程中,芽衣弃绝了过去的有限的自我,包括“告别同伴家人”、“承受非议”、“再也不能回到她(琪亚娜)身边”、“无法回头”。在罪人挽歌中,芽衣首先弃绝的就是自己的感受,表现在离开琪亚娜;而后芽衣也弃绝了自己的罪孽,表现在重拾雷律的权能;最后,加入世界蛇,也是某个角度上对善恶的弃绝。由此,芽衣成为了所谓的“罪人”,但是这在对琪亚娜的爱面前,不值一提。

    image-20230322140435164

    我将坠入黑暗,换她回到光明。
    再见了,雷电芽衣。
    ——雷电芽衣

    动画短片《罪人挽歌》


  • 强者芽衣的自我矛盾

    也许读者看到这里会感觉到奇怪:芽衣都已经做到弃绝一切,进入到信仰阶段了,怎么还有会矛盾呢?正如前面所提到的,此时芽衣成为的还只是“无限弃绝的骑士”,而非“真正的信仰骑士”。芽衣还需要经历从信仰的再一次跌落,并再次向信仰进行跨越,最后才能成为“真正的信仰骑士”,这就是“往世乐土”篇章芽衣所经历的。

    从信仰的跌落其实在弃绝阶段就已经出现端倪——以对琪亚娜的爱为信仰,代价却竟是永远离开琪亚娜并且背弃善恶,这样的矛盾在弃绝阶段那个危急的时候没有显现出来,但当事态归于缓和,琪亚娜的生命也不再收到严重威胁的时候,信仰的力量开始减弱,矛盾重新在内心质问芽衣。

    矛盾是什么?首先,对一个人的爱,是否也应该意味着认同所爱之人的理想呢?琪亚娜的理想是什么——为世界的一切美好而战,这是否就是一种至善呢?芽衣在上一次的信仰之跃中,依靠对琪亚娜的爱,背弃了世界的善恶,却也在暗中背弃了琪亚娜的理想,这样的爱,还是爱吗?

    在后面紧接的冰岩律剧情中,芽衣认识到了真正的爱一定包含着对理解、尊重、支持所爱之人的理想,但此时的芽衣没有做到这一点。这种“对琪亚娜的爱”,实则还是一种自私的爱,是顾及自身感受的爱,与其说芽衣是因为爱而背弃一切,到倒不如说是芽衣无法忍受“琪亚娜失去生命”这一可能性,这也正是一种对自身感受的未完全弃绝。这种“未完全弃绝”使得芽衣从信仰阶段重新跌落回审美阶段。

    我们将看到“真正的信仰骑士阶段”看到芽衣是如何超越这种矛盾的,但在此之前,芽衣还只能选择次对抗崩坏,也即回到“伦理”,并带着这个矛盾,开始新的旅程——往世乐土。


  • 爱莉希雅-Elysia

    如果说芽衣最终是成为了真正的信仰骑士,那我们就需要知道,信仰骑士,究竟信仰的是什么?

    没错,就是爱门🙏。首先,就剧情而言,芽衣身上就有诸多信仰的因素:荆棘花冠、背负十字、以及(除去爱莉自己)十二英杰与十二门徒的对应。往世乐土是“爱莉希雅”的故事,也就是说,往世乐土就是爱莉的圣经。而面见阿波尼亚就对应着启示的开始,是侵蚀律的段落。而爱莉自身呢,我们知道爱莉就是律者,是人之律者,但她却始终都在以人的姿态而活,亲近于所有人,爱着所有人(噢爱门🙏)。我们当然不能代入我们意义上的那个宗教信仰,崩三中的信仰是不一样的。

    那么,我们所说的,那个“至美”、“至善”、“至爱”、“至高”、“至纯”的信仰究竟是什么呢。当我们穷尽所有的形容词时,才发现,终点就是起点,我们一直所信仰的,就在爱莉希雅之中,就在“人之律者”之中——人。

    拥有人的外表,拥有人的品性。
    像人一样呼吸,像人一样活着。
    时而喜悦,时而悲伤,时而失落,时而振作。不舍昨日,却也愿意······放手未来
    所以我的名字,应该叫做“人之律者”吧?
    “人性”的律者,“人类的”律者

    当以人之律者为名,就代表承载了人的一切价值,包括人的喜悦、悲伤、悔恨,也包括人能够秉持信念、做出选择,也包括人的实践能力,也包括那个能让人自由发挥以上种种的共同理想——一个真正的乐园,Elysia(乐土)。当人们能够以情感彼此相连、人作出真我的选择、人从事真的劳动,那意味着,人人都是“始源”。以我为始,我为始源;以我为终,人皆始源。这就是我们的信仰,芽衣最终的信仰。

    倘若有什么能为“人”加以冠冕。
    那么,也只有“人类”自己。

    不需要太多的形容词,“人”这个词本身就能说明一切。爱莉希雅做的无非就是“真我”,是“无暇之人”,可当我们想要做真正的人时,又需要多少诚实、恐惧、思考、勇气呢。

    往世乐土的故事,是前世代悲剧落幕人们的故事。但是,在游戏中被反复诉说的是:

    悲剧并非终结,而是希望的开始。
    只有人类会这么想,也只有人类会将这样的信念化为现实。
    世界那么大······于命运的角度而言,大多数人都仅仅只是“过程”罢了。
    至于究竟谁是那个为命运写下句号的人······并不重要。

    人都能在对理想的展望中,以”未来的过去”看待此刻,这即是历史的,也是全体的。尼采认为只有少数人能变得伟大,而祁克果认为,人人都有伟大的潜能,而且每个人都在各自的其中,展示着自己一切行为“过程中的价值”。

    烧鸡老师:“一个健康阳光、积极向上的故事”。


  • 如何信仰

    祁克果强调了信仰的几个特征,除了不同于审美和伦理之外,还有其悖论性和内在性。

    先说悖论性。信仰不评判善恶,也不诉于理性,因此信仰也超出善恶,超出理性,信仰所依赖的就是“悖论”。“悖论”指的是“客观上的不确定性”,是“理性看来绝不可能的事情”。信仰性就是这种悖论中彰显的。

    没有风险就没有信仰
    ···
    真理恰恰就是一种冒险,这种冒险凭着无限的激情选择客观上的不确定。
    ···
    请在恐惧和战栗中记住:你持有的这种信仰,把这个宣称当做赌注单单压在你做出如此行为的、你自己的自由上,没有任何保障比你自己的决定更为终极,这是你自己甘冒的风险,这是你自己应该承担的责任。

    我们对上帝不能完全的把握,这种不确定性,才能称之为信靠。这并不是一件坏事。客观把握的东西不需要我们的委身,无关我们的信靠,它就在那里;不确定性越大的东西,越是需要我们的委身。而是否委身,由我来决定。其实试想一下,我们生活就是这样的,当我们看到理想与现实的鸿沟、看到世间的种种罪孽与自己的力所不能及,我们是否还能坚持自己的信仰呢?如果可以,那这本身就是一种悖论,而这恰恰就是信仰。再看罗曼罗兰那句被经常引用的“真正的英雄主义,就是在看清生活的本质后,依旧热爱生活”,我们应该能够理解的更深刻。尽管我们的行动不能被给予正确的保证,但我们必须行动,必须做出选择。生活不是由我们理性思考的得出的,而是全身心的以全部的激情投入某件事当中。而信仰的极致,就是相信逻辑上不可能的事情。“相信奇迹的人,本身就和奇迹一样了不起”。

    我们再看这种悖论性在剧情中的体现。“人之律者”,这一词代表着什么呢?律者,最初的概念,就是人在获取律者权能后,不再成为人,是人的律化;而人之律者,我们的爱莉希雅,却是一个努力想要成为人的律者,是律者的人化。就好像不顾热力学定律,用冰水让热水升温。崩坏是人的问题,是人类文明发展到一定阶段的产物,而崩坏诞生“人之律者”,就表明一种“一种事物向完全背离自己的方向发展”。爱莉本身就是一个奇迹,一个“永恒无限的基督”却想要成为一个确定有限的人,一个“人性总集的真我”却想要成为一个特殊具体的人。在侵蚀律的段落中,乐土打字机说:“爱莉死了”。而在十二个英杰的每个人都对爱莉的“唤回”的笃信中,爱莉“死而复生”,达成了对侵蚀律的胜利。芽衣对这些经历是言传身教的。

    当然,爱莉也不只是总体意义上的人性的总和,她自身也的的确确是一个真正的人。信仰在爱莉身上也有所体现。爱莉相信自己能够将人性带给律者,即使这是从未发生的事情,即使这缺乏任何客观的保证,即使她自己无法亲眼目睹,她也依旧坚定的相信在自己之后一定会有“羽化”的律者,有着人性的光辉,与人们一起对抗崩坏,爱莉,也相信着奇迹。当被问起为什么第一次见到芽衣就这么喜欢时,爱莉调皮的回答说因为芽衣漂亮(谨防乐土钕酮,哎嘿),但实际上更是因为爱莉看到她所期盼的事情真的发生了,芽衣——第一个带有人性的律者出现在了她的面前,她的奇迹,降临了。
    动画短片《因你而存在的故事》

    接下里就是内在性了。祁克果认为信仰不依赖于任何形式的集体宗教活动,也不依赖于任何教义,信仰只有人的内心中才能达成,是人和永恒无限者一对一的关系。而在“乐土面对侵蚀”以及“终章”呼唤爱莉希雅并最终得到回应的时候,都是芽衣处在自己的内心状态的时候,这一点在剧情的刻画并不多而且显而易见,于是就不讲太多理论性的东西了。


  • 心灵的纯净

    祁克果希望人们能够守护“心灵的纯净”,他在《日记》中写到:

    不是拥有几种思想,而是牢牢持守住一种思想。
    ···
    这种骑士有力量把自己生活的全部内容和实在性的全部意义,单单凝聚成一个愿望。要是没有这种凝聚个强度,要是从他的灵魂从一开始就四处分散,他就绝不可能到达进行这种行动的地步,他在生活中就会十分精明,像一个投资各种证券,以便失之东隅而收之桑榆的商人。

    那些没能够保持”心灵的纯净”的人,会在“忙碌”中四处逃遁,绝望于没有找到为之生为之死的信念,也害怕向任何未知的事物委身。这实际上在审美和伦理的生活中也同样适用。

    “一个人的性格,就是他的命运”······你可以试着从正面理解这句话的含义 ——芽衣

    就像任何那些流传甚广的哲学格言,其本身听起来总有一种简洁清晰却又“石破天惊”、“一语中的”的神秘感——的确,大多数人都不会喜欢严谨但费解的长篇大论。苏格拉底的”未经审视的生活是不值得过的”、笛卡尔的“我思故我在”、以及赫拉克利特的“一个人的性格,就是他的命运”等,其实背后都蕴藏着在“每个人心中都不同的名词定义”或者“未表述出的前提”。这当然不是说这些格言的散播是一种草率行为,反而,是我们应意识到,每个人其实都对这些格言有着足够的解释空间与想象余地。不得不承认,纵观哲学史,确实就是一场“所有人反对所有人的战争”。即使我在上面提到的种种观点也是如此——信仰真的一定依附于悖论性吗?悖论性是否也意味引发某些未知的可怕的危险?读者都应该以自己角度去思考、批判,下面的讲述的观点也是如此。扯远了,让我们回到祁克果。

    当以祁克果的观点,解读“一个人的性格就是他的命运”这句话时,其中的“性格”应该指的是人的“真我”、“真信”,也就是那个可以“为之生为之死”的信念,而命运,则是这种信念必定会通达的方向。往世乐土的十二位英杰在面对“上世代末的终焉”以及“往世乐土的侵蚀律者”的结局中,都依据自己的“真信”,通达了类似的结局,而且以言传身教的方式,教给了芽衣。芽衣在获得这些教诲后,实现了再一次的“信仰之跃”,成为了“真正的信仰骑士”,这一点可以在终章的剧情中得到印证。


  • 信仰骑士

    在终章中,为了获得“始源”的权能,芽衣再一次踏上了对爱莉希雅的追寻,而在追寻的起点,布洛妮娅对芽衣进行了一次警告:

    也就是说······如果要继续深入的话,芽衣将进入的,会是一条“单向通道”。

    通道是单向的,而是否有其他出路,不知道——芽衣的选择是“相信”,正如苏告诉她的那样,最重要的东西,是“坚信”。后面的剧情中,芽衣发现了维尔薇留下的“那封信”,也即维尔薇设置的“后门”。但当时的芽衣仅仅只知道这是维尔薇写的信,而不知道它所能发挥的作用。可以说,最终的成功,实际上一半来自芽衣的“笃信”,另一半其实是维尔薇的“操作”。但这恰恰说明,奇迹的发生仍是与“人”高度相关的,它来自于“人的行动”、“人的情感”、“人的信仰”。同时,芽衣也再次印证了对琪亚娜的爱的理解,那就是包含理解、尊重、支持所爱之人的理想。而后,芽衣又经历了一系列的质问:

    对于志同但道不合者,你所信的、所做出的的行动,就一定正确吗?那些未竟的理想,你能否将其背负
    人类就真的是最高的存在吗?机械的生命也是命,理型的命也是命,为什么人类就可以如此傲慢的认为自己占据着最上位?

    芽衣的回答展现出了“信仰骑士”超越审美与伦理的信念,但这仍与之前“无限弃绝的骑士”没有不同。而在接下来,关于“对琪亚娜的爱”的质问中,芽衣终于展现“信仰骑士”与“弃绝骑士”不同的地方:信仰骑士当然以无限弃绝为前提,但“无限弃绝的骑士不能闭上眼睛,不能充满信赖的投入荒谬之中”,而信仰骑士在弃绝有限的事物后,进行了信仰骑士的行动,充满信赖的投入荒谬之中,并且重新得回有限的事物:

    他无限的弃绝了一切,然后又借着荒谬又重新得回一切。

    对琪亚娜的爱是芽衣信仰的根基,如果不存在琪亚娜,对芽衣来说,世界的一切美好都将不复存在。剧情中用“锚点”来互文琪芽的这种感情,琪亚娜,就是芽衣的锚点(卡斯兰娜罪大恶极(bushi)),只有通过琪亚娜,“大爱”才能在芽衣心中显现。

    “弃绝骑士”不同于“信仰骑士”的地方是:前者在弃绝有限性的同时,也弃绝了对此生所有的挂念;而信仰骑士的芽衣,始终爱着琪亚娜。就像亚伯拉罕在上帝试图带走他的儿子以撒那样:

    甚至在刀光闪耀的那一刻,他还在相信···上帝不会要走以撒。

    当芽衣为琪亚娜带上终焉的冠冕时,芽衣也是如此恐惧又坚定地相信:

    即使此刻分离,我们终将重逢。


  • 始源之律者

    “回应我吧,爱莉希雅!”为了让一切再次发生,芽衣在终章中二度呼唤爱莉希雅,第一次,爱莉希雅没有现身;而第二次,当芽衣处于一种近乎悖论的境地中,在最绝望的坚定中,芽衣相信美好的事物不会消失,再次呼唤:“回应我吧,爱莉希雅!”——爱莉希雅,一个已经在上世代中消逝的人,一个已经在乐土中告别的人,“真我之律者”,于奇迹般,再度出现。依靠芽衣,依靠人的情感,依靠人的记忆,依靠人的信仰。

    “感觉如何🎶?”
    一如初见。(笔者发电中)。

    无论何时何地,美丽的爱莉希雅,总会回应你的期待。

    “不是来到这里,才能成为始源;而是成为始源,才能来到这里”。

    image-20230322141011180


那么,以洁净的心灵保持真我吧,不顾理想的荒谬,始终坚信吧,自己做出非此即彼的选择吧,自己去承担存在的不确定性吧,去行动起来吧。不把真理付诸生活,那么真理就什么都不是。以我为始,我为始源;以我为终,人皆始源。

  • 哲学
  • 崩三

展开全文 >>

LeetCode刷题笔记

2025-02-28

[TOC]

LeetCode刷题笔记(现仅供参考。。。)


岛屿问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//    给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
// 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
// 此外,你可以假设该网格的四条边均被水包围。
public int algo8(char[][] grid) {
//DPS
// 遍历数组中的每一个值,如果是1就说明是岛屿,然后把它
// 置为0或者其他的字符都可以,只要不是1就行,然后再遍历他的上下左右4个位置。
// 如果是1,说明这两个岛屿是连着的,只能算是一个岛屿,我们还要把它置为0,然后再以它为
// 中心遍历他的上下左右4个位置……。如果是0,就说明不是岛屿,就不在往他的上下左右4个位置遍历了。
//遍历原则,下-右-上-左
if (grid == null) {
return 0;
}
int res = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
if (grid[i][j] == '1') {
dfs(i, j, grid);
res += 1;
}
}
}
return res;
}

public void dfs(int i, int j, char[][] grid) {
if (i<0||i>=grid.length||j<0||j>=grid[0].length||grid[i][j] == '0' {
return;
}
grid[i][j] = '0';
dfs(i + 1, j, grid);
dfs(i, j + 1, grid);
dfs(i - 1, j, grid);
dfs(i, j - 1, grid);
}
//如果用这种方式些,运行时间会变长
public void dfs(int i, int j, char[][] grid) {
if (i>=0&&i<grid.length&&j>=0&&j<grid[0].length&&grid[i][j] == '1' {
grid[i][j] = '0';
dfs(i + 1, j, grid);
dfs(i, j + 1, grid);
dfs(i - 1, j, grid);
dfs(i, j - 1, grid);
}
}


转盘锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//    你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每个拨轮可以自由旋转:例如把 '9' 变为 '0','0' 变为 '9' 。每次旋转都只能旋转一个拨轮的一位数字。
//
// 锁的初始数字为 '0000' ,一个代表四个拨轮的数字的字符串。
//
// 列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
//
// 字符串 target 代表可以解锁的数字,你需要给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回 -1 。
// 下面是自己写的,太逊了,时间开销太长了,而且还没有考虑重复节点,会陷入死循环
public int algo9(String[] strs,String target){
if(strs==null||target==null){
return -1;
}
int res=0;
String str="0000";
if(str!=target) {
dfs1(strs, target, str, res);
}
return res;
}

public void dfs1(String[] strs,String target,String str,int res){
for(int i=0;i<str.length();i++){
if(i!=0){
res-=1;
}
String add="";
add+=str.substring(0,i);
char ch=str.charAt(i);
add+=(ch=='9')?'0':(ch-'0'+1);
add+=str.substring(i+1);
if(!isLegal1(strs,add)){
continue;
}
res+=1;
if(add==target){
break;
}
else {
dfs1(strs,target,add,res);
}
}
}

public boolean isLegal1(String[] strs,String str){
for(String rec:strs){
if(rec==str)return false;
}
return true;
}

// 如下标答
public int openLock(String[] deadends,String target){
Set<String> set=new HashSet<>(Arrays.asList(deadends));
Set<String> visited=new HashSet<>();//保存已经访问过的节点,防止陷入死循环
Queue<String> queue =new LinkedList<>();//保存当前层的各个节点
String startStr="0000";
int level=0;
queue.offer(startStr);
while(!queue.isEmpty()){
int size= queue.size();
while(size-->0){
String str= queue.poll();
if(str.equals(target)){
return level;
}
for(int i=0;i<target.length();i++){
char ch=str.charAt(i);
String strAdd=str.substring(0,i)+((ch=='9')?'0':ch-'0'+1)+str.substring(i+1);
String strSub=str.substring(0,i)+((ch=='0')?'9':ch-'0'-1)+str.substring(i+1);
if(!visited.contains(strAdd)&&!set.contains(strAdd)){//如果已经访问过或是死亡组合,均不能添加
queue.offer(strAdd);
}
if(!visited.contains(strSub)&&!set.contains(strSub)){
queue.offer(strSub);
}
}
}
level+=1;
}
return level;
}

合法的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 //    给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
//
// 有效字符串需满足:
//
// 左括号必须用相同类型的右括号闭合。
// 左括号必须以正确的顺序闭合。
public static boolean isValid(String s) {
Stack<Character> stack=new Stack<>();
int i=0;
while(i<s.length()){
char ch=s.charAt(i);
i+=1;
if(ch=='('||ch=='['||ch=='{'){
stack.push(ch);
}else{
if(stack.isEmpty()){
return false;
}else{
char head=stack.pop();
if((head=='['&&ch==']')||(head=='('&&ch==')')||(head=='{'&&ch=='}')){
continue;
}else{
return false;
}
}
}
}
if(stack.isEmpty()){
return true;
}else{
return false;
}
}

更高的温度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//    给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指在第 i 天之后,才会
// 有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。
// 输入: temperatures = [73,74,75,71,69,72,76,73]
// 输出: [1,1,4,2,1,1,0,0]
public int[] dailyTemperatures(int[] temperatures) {
//重点是:stack存放的是各个温度对应的下标而不是温度,这样的就根据下标在res中找到位置,
//如果存放的是温度,依然可以比较温度大小,但比较后的res的值以及对应的下标都不知道
Stack<Integer> stack=new Stack<>();
int[] res=new int[temperatures.length];
for(int i=0;i<temperatures.length;i++){
while(!stack.isEmpty()&&temperatures[i]>temperatures[stack.peek()]){
res[stack.peek()]=i-stack.peek();
stack.pop();
}
stack.push(i);
}
return res;
}

逆波兰表达式求值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 //    根据 逆波兰表示法,求表达式的值。
// 输入:tokens = ["2","1","+","3","*"]
// 输出:9
// 解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
*/
public static int evalRPN(String[] tokens) {
Stack<String> stack=new Stack<>();
for(int i=0;i<tokens.length;i++){
if(!tokens[i].equals("*")&&!tokens[i].equals("/")&&!tokens[i].equals("+")&&!tokens[i].equals("-")){
stack.push(tokens[i]);
}else {
int a=Integer.parseInt(stack.pop());
int b=Integer.parseInt(stack.pop());
if(tokens[i].equals("+")){
stack.push(Integer.toString(b+a));
}else if(tokens[i].equals("-")){
stack.push(Integer.toString(b-a));
}else if(tokens[i].equals("*")){
stack.push(Integer.toString(b*a));
}else if(tokens[i].equals("/")){
stack.push(Integer.toString(b/a));
}
}
}
return Integer.parseInt(stack.peek());
}

图的深度克隆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
// 给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。
// 图中的每个节点都包含它的值 val(int) 和其邻居的列表(list[Node])。
// class Node {
// public int val;
public List<Node> neighbors;
// }
*/

public Node cloneGraph(Node node) {
return clone(node,new HashMap<>());
}

public Node clone(Node node,HashMap<Integer,Node> visited){
if(node==null){
return null;
}
if(visited.containsKey(node.val)){
return visited.get(node.val);
}
Node newNode=new Node(node.val,new ArrayList<>());
visited.put(newNode.val,newNode);
for(Node each:node.neighbors){
newNode.neighbors.add(clone(each,visited));
}
return node;
}

class Node {
public int val;
public List<Node> neighbors;
public Node() {
val = 0;
neighbors = new ArrayList<Node>();
}
public Node(int _val) {
val = _val;
neighbors = new ArrayList<Node>();
}
public Node(int _val, ArrayList<Node> _neighbors) {
val = _val;
neighbors = _neighbors;
}
}

合法的数独

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
 /**
请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

写完了下面的才记得可以用一个3*9*9的数组用一次遍历来做,大意了
*/
public boolean isValidSudoku(char[][] board) {
HashSet<Character> hashSet=new HashSet<>();
for(int i=0;i<board.length;i++){
for(int j=0;j<board[0].length;j++){
if(hashSet.contains(board[i][j])&&board[i][j]!='.'){
return false;
}else{
hashSet.add(board[i][j]);
}
}
hashSet.clear();
}
for(int j=0;j<board.length;j++){
for(int i=0;i<board[0].length;i++){
if(hashSet.contains(board[i][j])&&board[i][j]!='.'){
return false;
}else{
hashSet.add(board[i][j]);
}
}
hashSet.clear();
}
for(int k=0;k<9;k++){
for(int i=k/3*3;i<k/3*3+3;i++){
for(int j=k%3*3;j<k%3*3+3;j++){
if(hashSet.contains(board[i][j])&&board[i][j]!='.'){
return false;
}else{
hashSet.add(board[i][j]);
}
}
}
hashSet.clear();
}
return true;
}

合并数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
*
* 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
*
* 注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,
* 其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
*/
public static void merge(int[] nums1, int m, int[] nums2, int n) {
int[] res=new int[m+n];
int up=0;
int down=0;
int i=0;
while(up<m&&down<n){
if(nums1[up]<=nums2[down]){
res[i]=nums1[up];
up++;
}else{
res[i]=nums2[down];
down++;
}
i++;
}
if(up==m){
for(;down<n;down++,i++){
res[i]=nums2[down];
}
}else if(down==n){
for(;up<m;up++,i++){
res[i]=nums1[up];
}
}
nums1=res;
for(i=0;i< nums1.length;i++){
nums1[i]=res[i];
}
}

随机打乱数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 给你一个整数数组 nums ,设计算法来打乱一个没有重复元素的数组。打乱后,数组的所有排列应该是 等可能 的。
*
* 实现 Solution class:
*
* Solution(int[] nums) 使用整数数组 nums 初始化对象
* int[] reset() 重设数组到它的初始状态并返回
* int[] shuffle() 返回数组随机打乱后的结果
*/
class Solution {
private int[] nums=null;
private int[] recoder=null;
private int length;

public Solution(int[] nums) {
this.length=nums.length;
this.nums=new int[length];
this.recoder=new int[length];
for(int i=0;i<length;i++){
this.nums[i]=nums[i];
this.recoder[i]=nums[i];
}
}

public int[] reset() {
if(nums==null)return null;
for(int i=0;i<length;i++){
this.nums[i]= recoder[i];
}
return nums;
}

public int[] shuffle() {
if(nums==null)return null;
for(int i=0;i<length-1;i++){
int random=(int)(Math.random()*(length-i-1))+i+1;
int sign=0;
sign=nums[i];
nums[i]=nums[random];
nums[random]=sign;
}
return nums;
}
}

类atoi函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* 请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。
*
* 函数 myAtoi(string s) 的算法如下:
*
* 读入字符串并丢弃无用的前导空格
* 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
* 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
* 将前面步骤读入的这些数字转换为整数(即,"123" -> 123, "0032" -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
* 如果整数数超过 32 位有符号整数范围 [−231,  231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231 − 1 。
* 返回整数作为最终结果。
* 注意:
*
* 本题中的空白字符只包括空格字符 ' ' 。
* 除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。
*/
public static int myAtoi(String s) {
int off=0;
int length=0;
boolean sign=false;
for(int i=0;i<s.length();i++){
if(s.charAt(i)=='-'||(s.charAt(i)-'0'<10&&s.charAt(i)-'0'>=0)) {
if(!sign)off=i;
sign=true;
length++;
}
}
String s1 = new String(s.toCharArray(), off, length);
return Integer.valueOf(s1).intValue();

}

求算数平方根

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
*
* 由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
*
* 注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
*/
public static int mySqrt(int x) {
if(x==0){
return 0;
}
int left=1;
int right=x;
while(left<=right){
int mid = left + ((right - left) >> 1);
if (mid == x / mid) {
return mid;
}
// 两边*mid后: mid * mid < x, 利用除法代替乘法避免溢出
else if (mid < x / mid) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return right;
}

三数之和(15)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,
* 使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
* <p>
* 注意:答案中不可以包含重复的三元组。
* 思路:先确定一个数,再找另外另外两个数使其和等于0
* 每次都从certain之后的数里找,就可以防止重复
*/
public static List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
if (nums.length < 3) {
return list;
}
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1])
continue;
if (nums[i] > 0)
break;
int certain = nums[i];
int low = i + 1;
int high = nums.length - 1;
while (low < high) {
int sum = nums[low] + nums[high] + certain;
if (sum < 0) {
low++;
if (low == i || low == high) low++;
} else if (sum > 0) {
high--;
if (high == i || high == low) high--;
} else {
List<Integer> l = new ArrayList<>();
l.add(certain);
l.add(nums[low]);
l.add(nums[high]);
list.add(l);

//可能之后还有
low++;
high--;
while (low < high && nums[low] == nums[low - 1]) {
low++;
}
while (low < high && nums[high] == nums[high + 1]) {
high--;
}
}
}
}
return list;
}

矩阵置零

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。
* 你能想出一个仅使用常量空间的解决方案吗?
*
* @param
*/
public void setZeroes(int[][] matrix) {
//标记第一行是否有数字0
boolean row = false;
//标记第一列是否有数字0
boolean col = false;
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
if (matrix[i][j] == 0) {
//如果第一行或者第一列本来就有0,就把他标记一
//下,最后再把第一行或者第一列全部置为0
if (i == 0)
row = true;
if (j == 0)
col = true;
//把最上面一行和最左边一列对应的位置标注为0
matrix[0][j] = matrix[i][0] = 0;
}
}
}
//把那些应该为0的行和列全部置为0
for (int i = 1; i < matrix.length; i++) {
for (int j = 1; j < matrix[0].length; j++) {
if (matrix[i][0] == 0 || matrix[0][j] == 0)
matrix[i][j] = 0;
}
}
//如果第一列本来就有0,把第一列全部变为0
if (col) {
for (int i = 0; i < matrix.length; i++)
matrix[i][0] = 0;
}
//如果第一行本来就有0,把第一行全部变为0
if (row) {
for (int j = 0; j < matrix[0].length; j++)
matrix[0][j] = 0;
}
}

字母异位词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* 给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
* <p>
* 字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。
* <p>
* 输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
* 输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
*
* @param
*/
public List<List<String>> groupAnagrams(String[] strs) {
List<List<String>> list = new ArrayList<>();
String[] newStrs = new String[strs.length];
for (int i = 0; i < newStrs.length; i++) {
char[] chs = newStrs[i].toCharArray();
Arrays.sort(chs);
String newStr = new String(chs);
newStrs[i] = newStr;
}
int[] sign = new int[strs.length];
for (int i = 0; i < newStrs.length; i++) {
if (sign[i] == 0) {
sign[i] = 1;
List<String> list1 = new ArrayList<>();
list1.add(strs[i]);
for (int j = i + 1; j < newStrs.length; j++) {
if (sign[j] == 0 && newStrs[j].equals(newStrs[i])) {
list1.add(strs[j]);
sign[j] = 1;
}
}
list.add(list1);
}
}
return list;
}

//这个好,显得上面的我很呆
public List<List<String>> groupAnagrams1(String[] strs) {
if (strs == null || strs.length == 0)
return new ArrayList<>();
Map<String, List<String>> map = new HashMap<>();
for (String s : strs) {
// 将字符串转化为字符数组后排序
char[] ch = s.toCharArray();
Arrays.sort(ch);
// 将排序后的字符串作为key
String strKey = String.valueOf(ch);
// 如果map中不存在排序后的字符串的key,创建一个list
if (!map.containsKey(strKey))
map.put(strKey, new ArrayList<>());
// 将原字符串添加到key对应的列表中
map.get(strKey).add(s);
}
return new ArrayList<>(map.values());
}

不重复的最长字串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
*
* @param
*/
//来一手刚学的正则表达式
public static int lengthOfLongestSubstring(String s) {
int low = 0;
int high = s.length();
int max = 0;
int res = 0;
String regex = "(.)\\1+";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(s);

while (matcher.find()) {
res = matcher.start() - low;
low = matcher.start() + matcher.group(0).length();
max = (max > res) ? max : res;
}
res = high - low;
max = (max > res) ? max : res;
return max;
}

递增子序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。
* <p>
* 如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 。
* <p>
* 循环求解不难
* <p>
* 实现时间复杂度为 O(n) ,空间复杂度为 O(1) 的解决方案
*
* @param
*/
/**
* 其实可以简化
* 强啊
*
* @param
*/
public boolean increasingTriplet1(int[] nums) {
//3个数字,small记录最小的数字
int small = Integer.MAX_VALUE;
//mid记录中间的数字
int mid = Integer.MAX_VALUE;
for (int num : nums) {
if (num <= small) {
//记录遍历过的最小值
small = num;
} else if (num <= mid) {
//记录比small大的最小值,也就是mid的值
mid = num;
} else {
//mid如果赋值了,那么之前肯定有个比
//mid小的值,这里又有个比mid大的值,
//所以他们三个可以构成递增的三元子序列
return true;
}
}
return false;
}

数字相加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
* <p>
* 请你将两个数相加,并以相同形式返回一个表示和的链表。
* <p>
* 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
*/
class ListNode {
int val;
ListNode next;

ListNode() {
}

ListNode(int val) {
this.val = val;
}

ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
int isJinWei = 0;
ListNode tail = new ListNode(0, null);
ListNode res = new ListNode(0, tail);
while (l1 != null || l2 != null || isJinWei == 1) {
int val1 = (l1 == null) ? 0 : l1.val;
int val2 = (l2 == null) ? 0 : l2.val;
int val = (val1 + val2 + isJinWei) % 10;
isJinWei = (val1 + val2 + isJinWei > 9) ? 1 : 0;
ListNode newNode = new ListNode(val, null);
tail.next = newNode;
tail = newNode;
l1 = (l1 == null) ? null : l1.next;
l2 = (l2 == null) ? null : l2.next;
}
return res.next.next;
}

奇偶链表合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* 给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。
* <p>
* 第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。
* <p>
* 请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。
* <p>
* 你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。
* 输入: head = [1,2,3,4,5]
* 输出: [1,3,5,2,4]
* <p>
* 我们自然想到的是把奇数节点串到一起,我们称之为奇数链表,偶数节点串到一起,我们称之为偶数链表。最后再把偶数链表挂到奇数链表的后面即可。
*/
class ListNode1 {
int val;
ListNode1 next;

ListNode1() {
}

ListNode1(int val) {
this.val = val;
}

ListNode1(int val, ListNode1 next) {
this.val = val;
this.next = next;
}
}

public ListNode1 oddEvenList(ListNode1 head) {
if (head == null) {
return null;
}
ListNode1 oddHead = head;
ListNode1 evenHead = head.next;
ListNode1 node = null;
ListNode1 next = head;
while (next != null) {
node = next;
next = node.next;
node.next = (next == null) ? null : next.next;
}
ListNode1 listNode1 = null;
next = oddHead;
while (next.next != null) {
next = next.next;
}
next.next = evenHead;
return oddHead;
}

链表相交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
* 整个链式结构中不存在环。
* 注意,函数返回结果后,链表必须 保持其原始结构 。
* <p>
* 能否设计一个时间复杂度 O(m + n) 、仅用 O(1) 内存的解决方案
*/
class ListNode2 {
int val;
ListNode2 next;

ListNode2(int x) {
val = x;
next = null;
}
}
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}

//the way i to do
public ListNode2 getIntersectionNode(ListNode2 headA, ListNode2 headB) {
if (headA == null || headB == null) {
return null;
}
HashSet<ListNode2> hashSet = new HashSet<>();
ListNode2 nextA = headA;
while (nextA != null) {
hashSet.add(nextA);
nextA = nextA.next;
}
ListNode2 nextB = headB;
while (nextB != null) {
if (hashSet.contains(nextB)) {
return nextB;
}
nextB = nextB.next;
}
return null;
}

可生成的所有字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
*
* 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
* 2:abc
* 3:def
* 4:ghi
* 5:jkl
* 6:mno
* 7:pqrs
* 8:tuv
* 9:wxyz
* @param
*/
public List<String> letterCombinations(String digits) {
List<String> list=new ArrayList<>();
if(digits.equals(""))return list;
String[] numberToChar=new String[]{"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
String add="";
dfs1(digits,0,list,add,numberToChar);
return list;
}
public void dfs1(String digits,int index,List<String> list,String add,String[] numberToChar){
if(index+1<=digits.length()-1) {
String str = numberToChar[digits.charAt(index) - '2'];
for (int i = 0; i < str.length(); i++) {
dfs1(digits, index + 1, list, add + str.charAt(i), numberToChar);
}
}else{
String str = numberToChar[digits.charAt(index) - '2'];
for (int i = 0; i < str.length(); i++) {
list.add(add+str.charAt(i));
}
}
}

所有的括号组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
*
*  
*
* 示例 1:
*
* 输入:n = 3
* 输出:["((()))","(()())","(())()","()(())","()()()"]
* 示例 2:
*
*
* 输入:n = 1
* 输出:["()"]
*
* 作者:力扣 (LeetCode)
* 链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xv33m7/
* 来源:力扣(LeetCode)
* 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
* @param
*/
public List<String> generateParenthesis(int n) {
List<String> list=new ArrayList<>();
int consume=0;
int legalRemain=0;
String add="";
dfs2(0,0,n,add,list);
return list;
}
public void dfs2(int consume,int legalRemain,int n,String add,List<String> list){
if(n-consume>0){
dfs2(consume+1,legalRemain+1,n,add+"(",list);
if(legalRemain>0){
dfs2(consume,legalRemain-1,n,add+")",list);
}
}else{
for(int i=0;i<legalRemain;i++){
add+=")";
}
list.add(add);
}
}

全部幂集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
*
* 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
* @param
*/
public static List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> list=new ArrayList<>();
List<Integer> add=new ArrayList<>();
if(nums.length==0)return list;
List<Integer> check=new ArrayList<>();
list.add(add);

for(int i=0;i<nums.length;i++){
check.add(nums[i]);
}

for(int i=1;i<=nums.length;i++){
dfs4(i,add,check,list);
}
return list;

}
public static void dfs4(int size,List<Integer> add,List<Integer> check,List<List<Integer>> list){
if(size-add.size()>0){
if(check.size()==0)return;
int length=check.size();
for(int i=0;i<length;i++) {
List<Integer> add1=new ArrayList<>();
for(Integer integer:add){
add1.add(integer);
}
Integer integer1=check.get(0);
check.remove(integer1);
if((add.size()>0&&add.get(add.size()-1)<integer1)|| add.size()==0) {
add1.add(integer1);
dfs4(size, add1, check, list);
}
check.add(integer1);
}
}else{
list.add(add);
}
}

单词搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
*
* 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
*
*  
*
* 作者:力扣 (LeetCode)
* 链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvkwe2/
* 来源:力扣(LeetCode)
* 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
* @param
*/
public boolean exist(char[][] board, String word) {
int row=board.length;
int col=board[0].length;

if(row*col<word.length()){
return false;
}

int index=0;
for(int i=0;i<row;i++){
for(int j=0;j<col;j++){
if(board[i][j]==word.charAt(0)){
boolean[][] visited=new boolean[row][col];
boolean ans=dfs1(board,word,visited,0,i,j);
if(ans==true){
return true;
}
}
}
}
return false;
}
public boolean dfs1(char[][] board,String word,boolean[][] visited,int index,int m,int n){
if(index==word.length()){
return true;
}
if(m<0||m>=board.length||n<0||n>=board[0].length||visited[m][n]==true){
return false;
}
if(board[m][n]==word.charAt(index)){
visited[m][n]=true;
boolean ans=(dfs1(board,word,visited,index+1,m+1,n)
|| dfs1(board,word,visited,index+1,m-1,n)
|| dfs1(board,word,visited,index+1,m,n+1)
|| dfs1(board,word,visited,index+1,m,n-1));
if(ans==true){
return true;
}else{
visited[m][n]=false;
}
}
return false;
}

红白蓝排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
*
* 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
*
* 必须在不使用库的sort函数的情况下解决这个问题。
*
* 作者:力扣 (LeetCode)
* 链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvg25c/
* 来源:力扣(LeetCode)
* 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
* @param
*/
public void sortColors(int[] nums) {
int left=0;
int right=nums.length-1;
int index=0;
while(index<=right){
if(nums[index]==0){
swap(nums,index,left);
index++;
left++;
}else if(nums[index]==2){
swap(nums,index,right);
right--;
}else{
index++;
}
}
}
public void swap(int[] nums,int index1,int index2){
if(index1<nums.length&&index2<nums.length) {
int copy = nums[index1];
nums[index1]=nums[index2];
nums[index2]=copy;
}
}

合并区间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 /**
* 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
*
* 作者:力扣 (LeetCode)
* 链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xv11yj/
* 来源:力扣(LeetCode)
* 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
* 输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
* 输出:[[1,6],[8,10],[15,18]]
* 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
*
* 作者:力扣 (LeetCode)
* 链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xv11yj/
* 来源:力扣(LeetCode)
* 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
* @param
*/
public int[][] merge(int[][] intervals) {
if (intervals.length == 0) {
return new int[0][2];
}
//自定义排序规则
Arrays.sort(intervals, new Comparator<int[]>() {
public int compare(int[] interval1, int[] interval2) {
return interval1[0] - interval2[0];
}
});
List<int[]> merged = new ArrayList<int[]>();
for (int i = 0; i < intervals.length; ++i) {
int L = intervals[i][0], R = intervals[i][1];
if (merged.size() == 0 || merged.get(merged.size() - 1)[1] < L) {
merged.add(new int[]{L, R});
} else {
merged.get(merged.size() - 1)[1] = Math.max(merged.get(merged.size() - 1)[1], R);
}
}
return merged.toArray(new int[merged.size()][]);
}


搜索二维矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

每行的元素从左到右升序排列。
每列的元素从上到下升序排列。

作者:力扣 (LeetCode)
链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvc64r/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
//自己写的。。。笨死了,没写完就放弃了,因为太复杂了
public boolean searchMatrix(int[][] matrix, int target) {
int rowCount=matrix.length;
int colCount=matrix[0].length;
int maxBorder=Math.min(rowCount,colCount);
int border=0;

for(int i=0;i<maxBorder;i++,border++){
if(target<=matrix[i][i]){
break;
}
}
if(border==maxBorder){
if(colCount>=rowCount){
for(int i=border;i<colCount;i++){
if(matrix[border-1][i]==target){
return true;
}else if(matrix[border-1][i]>target){
return false;
}
}
}else{

}
}
for(int i=border-1;i>=0;i--){
for(int j=i;j<colCount||j<rowCount;j++){
if(j<colCount&&matrix[i][j]==target){
return true;
}
if(j<rowCount&&matrix[j][i]==target){
return true;
}
}
}
return false;
}

//标答
public boolean searchMatrix(int[][] matrix, int target) {
int p = 0;
int q = matrix[0].length - 1;
while (p < matrix.length && q >= 0) {
if(matrix[p][q]>target) q--;
else if(matrix[p][q]<target) p++;
else return true;
}
return false;
}

搜索旋转排序数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

作者:力扣 (LeetCode)
链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvyz1t/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
其实如果明白了二分查找的本质——根据有序元素段判断元素位置以不断收缩边界选择搜索空间,这道题也可以很轻松的想出来解法。比如,对于一个有序数组[1,2,3,4,5,6,7]。mid = 4,如果我们要查找元素6会很自然的去他的右端寻找[mid+1, left]。因为我们知道mid左侧的元素有序,故这些元素都比mid 4小,自然也就比6小了。
那么对于[4,5,6,7,0,1,2],可以想象,无论mid落到哪,在其两端都至少有一个局部有序段。假如mid为元素6,那么左侧456有序。若果mid为元素0,那么元素012有序。我们就可以利用有序段和当前target元素的比较,去排除一部分元素从而收缩边界继续寻找

public int search(int[] nums, int target) {
int low=0;
int high=nums.length-1;
int mid=-1;
while(low<=high){
mid=(high-low)/2+low;
if(nums[mid]==target){
return mid;
}
if(nums[mid]>=nums[low]){
//说明此时落在左端有序段中
if(target<nums[mid]&&target>=nums[low]) {
//说明target在左端有序段中
high = mid - 1;
}else{
low=mid+1;
}
}else{
//说明此时落在右端有序段中
if(target>nums[mid]&&target<=nums[high]){
low=mid+1;
}else{
high=mid-1;
}
}
}
return -1;
}

跳跃游戏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标。
public boolean canJump(int[] nums) {
int canReach=nums[0];
int index=0;
int maxReach=nums.length-1;
while(index<maxReach&&index<=canReach){
if(index+nums[index]>canReach){
canReach=index+nums[index];
}
index+=1;
}
if(canReach>=maxReach){
return true;
}else{
return false;
}
}

不同路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

作者:力扣 (LeetCode)
链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvjigd/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
int uniquePaths(int m, int n){
if (m == 1 || n == 1) {
return 1;
}
int i;
int j;

int roads[n];
for (j = 0; j < n; j++) {
roads[j] = 1;
}

for (i = 1; i < m; i++) {
for (j = 1; j < n; j++) {
roads[j] = roads[j - 1] + roads[j]; // 当前路径数 = 左边的路径数 + 上一行的路径数
}
}
return roads[n - 1];
}


最长递增子序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

将算法的时间复杂度降低到 O(n log(n))

//T(n)=n^2
public int lengthOfLIS(int[] nums) {
int numLen=nums.length;
int[] g=new int[nums.length];
Arrays.fill(g,1);

for(int i=1;i<numLen;i++){
for(int j=0;j<i;j++){
if(nums[j]<nums[i]){
g[i]=Math.max(g[i],g[j]+1);
}
}
}
int res=0;
for(int i=0;i<g.length;i++){
res=Math.max(res,g[i]);
}
return res;
}

//T(n)=n*log(n)
public int lengthOfLIS(int[] nums) {
int tail[]=new int[nums.length];
tail[0]=nums[0];
int end=0;

for(int i=1;i<nums.length;i++){
if(nums[i]>tail[end]){
end+=1;
tail[end]=nums[i];
}else{
//找到tail中第一个大于nums[i]的元素并替换它
int low=0;
int high=end;
while(low<high){
int mid=(high-low)/2+low;
if(tail[mid]<nums[i]){
low=mid+1;
}else{
high=mid;
}
}
tail[low]=nums[i];
}
}
return end++;
}

位运算实现加减乘除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//加法
int add(int a,int b) {
int carry,add;
do{
add = a^b;
carry = (a&b)<<1;
a = add;
b=carry;
}while(carry!=0);
return add;
}

//减法,假设a,b均大于0
//add(~b,1)得到-b的补码
int substraction(int a,int b){
return add(a,add(~b,1));
}

//a 被乘数,b 乘数
int multiplication(int a,int b){
int i = 0;
int res = 0;
//乘数不为0
while (b != 0){
//处理当前位
//当前位是1
if ((b & 1) == 1){
res += (a << i);
b = b >> 1;
//记录当前是第几位
i++;
}else {
//当前位是0
b = b >> 1;
i++;
}
}
return res;
}

//除法
//除法的意义就在于:求a可以由多少个b组成。那么由此我们可得除法的实现:求a能减去多少个b,做减法的次数就是除法的商
int division(int a,int b){
int res;
if(a<b){
return 0;
}else{
res=division(subtraction(a, b), b)+1;
}
return res;
}

任务调度器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。
//
//然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。
//
//你需要计算完成所有任务所需要的 最短时间 。
//
//作者:力扣 (LeetCode)
//链接:https://leetcode.cn/leetbook/read/top-interview-questions-medium/xwvaot/
//来源:力扣(LeetCode)
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
public int leastInterval(char[] tasks, int n) {
HashMap<Character,Integer> map=new HashMap<>();
for(char c:tasks){
map.put(c,map.getOrDefault(c,0)+1);
}

//下次合法执行的时间
List<Integer> nextValid=new ArrayList<>();
//剩余执行的次数
List<Integer> rest=new ArrayList<>();
Set<Map.Entry<Character,Integer>> set=map.entrySet();
for(Map.Entry<Character,Integer> entry:set){
nextValid.add(1);
rest.add(entry.getValue());
}
int time=0;
for(int i=0;i<tasks.length;i++){
time+=1;
int minNextValid=Integer.MAX_VALUE;
for(int j=0;j<map.size();j++){
if(rest.get(j)>0) {
minNextValid = Math.min(nextValid.get(j), minNextValid);
}
}
time=Math.max(time,minNextValid);
int best=-1;
for(int j=0;j<map.size();j++){
if(nextValid.get(j)<=time&&rest.get(j)!=0){
if(best==-1||rest.get(j)>rest.get(best)){
best=j;
}
}
}
nextValid.set(best,time+n+1);
rest.set(best,rest.get(best)-1);
}
return time;
}

多数元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//返回数组中多数元素:在数组中出现次数大于[n/2]的元素
//设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题
//你可以假设数组是非空的,并且给定的数组总是存在多数元素
//下面的方法称为摩尔投票法,核心是“对拼消耗”
public int majorityElement(int[] nums) {
int res=nums[0];
int count=1;
for(int i=1;i<nums.length;i++){
if(nums[i]==res){
count+=1;
}else{
count+=-1;
if(count<=0){
res=nums[i+1];
}
}
}
return res;
}

两数相除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public static int dividee(int dividend, int divisor) {
// 考虑被除数为最小值的情况
if (dividend == Integer.MIN_VALUE) {
if (divisor == 1) {
return Integer.MIN_VALUE;
}
if (divisor == -1) {
return Integer.MAX_VALUE;
}
}
// 考虑除数为最小值的情况
if (divisor == Integer.MIN_VALUE) {
return dividend == Integer.MIN_VALUE ? 1 : 0;
}
// 考虑被除数为 0 的情况
if (dividend == 0) {
return 0;
}

// 一般情况,使用二分查找
// 将所有的正数取相反数,这样就只需要考虑一种情况
boolean rev = false;
if (dividend > 0) {
dividend = -dividend;
rev = !rev;
}
if (divisor > 0) {
divisor = -divisor;
rev = !rev;
}

int left = 1, right = Integer.MAX_VALUE, ans = 0;
while (left <= right) {
// 注意溢出,并且不能使用除法
int mid = left + ((right - left) >> 1);
boolean check = quickAdd(divisor, mid, dividend);
if (check) {
ans = mid;
// 注意溢出
if (mid == Integer.MAX_VALUE) {
break;
}
left = mid + 1;
} else {
right = mid - 1;
}
}

return rev ? -ans : ans;
}

// 快速乘
public static boolean quickAdd(int y, int z, int x) {
// x 和 y 是负数,z 是正数
// 需要判断 z * y >= x 是否成立
int result = 0, add = y;
while (z != 0) {
if ((z & 1) != 0) {
//z的最后一位是1则可以进来
// 需要保证 result + add >= x
if (result < x - add) {
return false;
}
result += add;
}
if (z != 1) {
// 需要保证 add + add >= x
if (add < x - add) {
return false;
}
add += add;
}
// 不能使用除法
z >>= 1;
}
return true;
}
  • 算法

展开全文 >>

Java基础

2025-02-28

[TOC]

DAY1

数值传递机制

  • 值传递

  • 引用传递机制

    数组默认是引用传递机制

    1
    2
    3
    4
    int[] arr1={0,1,2};
    int[] arr2=arr1;
    arr2[0]=4;
    //此时arr1[0]=4;

例:数组拷贝

1
2
3
4
5
6
7
//实现数组拷贝(内容复制),即对arr2的改动不会影响arr1
int[] arr1={0,1,2};
int[] arr2=new int[arr1.length];

for(int i=0;i<arr1.length;i++){
arr2[i]=arr1[i];
}

例:数组扩容

1
2
3
4
5
6
7
8
9
10
//实际上是数组拷贝
int arr={0,1,2};
int arrNew=new int[arr.length+1];

for(int i=0;i<arr.length;i++){
arrNew[i]=arr[i];
}
arrNew[arrNew.length-1]=3;
arr=arrNew;
//实现从{0,1,2}到{0,1,2,3}的扩容

DAY2

JVM中的对象内存形式

image-20241109112255536

  • 解释上图:JVM可以划分为栈、堆、方法区三个区域。main方法会在栈中开辟一块区域占领,如果在main方法中调用一个对象的方法,那么这个方法同样也会在栈中开辟一块新的区域占领。栈的区域用来存放引用类型对象的地址,这里cat是一个引用类型对象,所以其地址存放在栈中;而这个地址指向堆中的一片区域,存放的是引用对象的成员变量,这里要分为两种情况:1.成员变量是基本类型,如int,那么堆中直接存放这个成员值。2.成员变量也是一个引用类型对象,如数组和字符串,那么堆中存放的将是他们的地址,而非值,真正的值由地址指向方法区中的常量池。

方法传参机制

  • 值传递

  • 引用传递:参数类型是引用类型。

    举例:public void method1(in[] arr)//数组是引用类型,所以这里是引用传递

    假设在main方法中,一个对象b拥有并调用了这个方法:

    1
    2
    int[] arr1={1,2,3};
    b.method1(arr1);

    根据上一小节的JVM内存布局,main在栈中占领一块区域,这块区域中有一个名为arr1的引用类型对象;而当调用method1时,会在栈中开辟一块新的区域,由method1占领,而在这块区域中,将会有一个名为arr的引用类型对象,而arr与arr1将指向堆中同一片区域,这就是引用传递。

可变参数方法

Java支持将多个同名同功能但参数个数不同的方法,封装成一个方法。

  • 基本语法:访问修饰符 返回类型 方法名(数据类型… 参数名)
  • 可变参数的实质是数组,当数组类型是int时,如public void method1(int… nums),在method1体内,nums可以当做数组使用

继承

继承使用细节:

  • 子类继承了父类==所有==的==属性与方法==,但父类的私有属性和私有方法不能直接在子类中访问,必须通过公共的方法进行访问
  • 子类创建时一定会先调用父类的构造器,完成父类的初始化
  • 一般情况下,无论子类使用哪个构造器,都会默认调用父类的无参构造器。如果父类没有提供无参构造器,则必须用super()指定调用父类的某一构造器,否则就会报错。
  • super()和this()在使用时都只能==放在第一行==,因此这两个不能在同一个构造器中出现
  • 父类构造器的调用不限于直接父类。将直接往上追溯到Object类(顶级父类)

继承时属性的关系:

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Grandpa{
String name="Grandpa";
int age=66;
}

public class Father extends Grandpa{
String name="father";
String hoppy="ball";
}

public class Son extends Father{
String name="son";
}

public static void main(String[] args){
Son son=new Son();
System.out.println(son.name);
System.out.println(son.age);
System.out.println(son.hoppy);

//要按照查找关系来返回信息
//(1)首先在子类中查找属性,若有且可以访问,则返回该信息
//(2)若子类中没有,则在父类中查找该属性,若有,则返回该信息
//(3)若父类中没有,则继续向上寻找上级父类,直到Object
}

//输出:son 66 ball

==当继承发生时的内存布局==

QQ图片20220206160750

super关键字:

  • 访问父类的属性,但不能访问父类的私有属性:super.属性
  • 访问父类的方法,但不能访问父类的私有方法:super.方法
  • ==super不仅可以用来访问直接父类==,也可以用来访问爷爷类的属性和方法。若多个上级父类中都有相同的成员,则依据==就近原则==

super与this的比较

QQ图片20220206195839

多态

==动态绑定机制==:

  • 当调用对象的==非静态方法==时,该方法会和对象的==内存地址/运行类型==绑定
  • 当调用对象==属性==时,不会有动态绑定机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
例:判断对错。在java的多态调用中,new的是哪一个类就是调用的哪个类的方法。(错)
//链接:https://www.nowcoder.com/questionTerminal/e5556245c9cc467885c9cf2dbc305f5c

public class Father {
public void say(){
System.out.println("father");
}
public static void action(){
System.out.println("爸爸打儿子!");
}
}
public class Son extends Father{
public void say() {
System.out.println("son");
}
public static void action(){
System.out.println("打打!");
}
public static void main(String
[] args) {
Father f=new Son();
f.say();
f.action();
}
}
输出:son
爸爸打儿子!
//当调用say方法执行的是Son的方法,也就是重写的say方法
//而当调用action方法时,执行的是father的方法。
  • “=”左边称为==编译类型==,右边称为==运行类型==
  • 普通方法,运用的是==动态单分配==,是根据new的类型确定对象,从而确定调用的方法;
  • 静态方法,运用的是==静态多分配==,即根据静态类型确定对象,因此不是根据new的类型确定调用的方法
  • 对于成员变量,看左边;对于非静态成员函数,看右边;对于静态成员函数,看左边。

多态的属性的细节:

  • 属性没有重写一说,属性的值看==编译==类型
1
2
3
4
5
6
7
8
9
10
11
12
13
class AA{
int value=10;
}
class BB extends AA{
int value=20;
}

public static void main(String[] args){
AA aa=new BB();
System.out.println(aa.value);
}

//输出结果:10
  • instanceof,用于判断对象的==运行类型==是否XX类型或XX类型的子类型

向上转型

  • 向上转型:父类的引用指向了子类的对象

  • 语法:父类类型 变量名=new 子类类型();

    1
    Animal animal=new Cat();

    特点:编译类型看左边,运行类型看右边

    • 可以调用父类的所有成员(需遵循访问权限)
    • 不能调用子类的特有成员

向下转型

语法:子类类型 引用名=(子类类型)父类引用

细节:

  • 只能强转父类的引用,不能强转父类的对象
  • 要求父类的引用必须指向的是当前目标类型的对象
  • 当向下转型后就可以调用子类的所有成员

自动转换类型与自动封装

1
2
3
4
int a='a';//char类型自动转化为int
double b=3;//int类型自动转化为double
Double c=3.0;//会自动封装
Double d=3;//会编译错误

强制转化

  • 高精度转化为低精度数据,会造成精度丧失
1
2
int a=(int)1.6;
char b=(char);

DAY3

迷宫问题:老鼠走迷宫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
public class Maze {

//0是可以走,1是障碍物,2是可以走到,3是走过,但走不通,是死路
//下——右——上——左
private int[][] map;
boolean sign;

public void initMap(){

sign=false;
map=new int[8][7];
for(int i=0;i<8;i++){
for(int j=0;j<7;j++){
if(i==0||i==7){
map[i][j]=1;
}else{
if(j==0||j==6){
map[i][j]=1;
}
}
}
}
map[5][1]=1;
map[5][2]=1;
}

public void print(){
System.out.println("====打印迷宫====");
for(int i=0;i<8;i++){
for(int j=0;j<7;j++){
System.out.print(map[i][j]);
}
System.out.println();
}
}


//自己写的findWay()
//还写得有问题
//什么垃圾玩意
public boolean findWay(int x,int y){
if(map[6][5]==2){
sign=true;
return true;
}
else if(map[y][x]==1){
System.out.println("Out of border");
return false;
}
else {
if (x < 1 || x > 5 || y < 1 || y > 6) {
System.out.println("Out of border");
return false;
} else {
map[y][x] = 2;
if (y > 0 && y < 5 && map[y + 1][x] == 0&&!sign) {
findWay(x, y + 1);
}
if (x > 0 && x < 5 && map[y][x + 1] == 0&&!sign) {
findWay(x + 1, y);
}
if (y > 1 && y < 7 && map[y - 1][x] == 0&&!sign) {
findWay(x, y - 1);
}
if (x > 1 && x < 6 && map[y][x - 1] == 0&&!sign) {
findWay(x-1,y);
}
}
}
if(!sign){
map[y][x]=3;
return false;
}
return true;
}


//真正的findWay()
public boolean findWay(int row,int col){
if(map[6][5]==2){
return true;
}else{
if(map[row][col]==0){
map[row][col]=2;
if(findWay(row+1,col)){
return true;
}else if(findWay(row,col+1)){
return true;
}else if(findWay(row-1,col)){
return true;
}else if(findWay(row,col-1)){
return true;
}else{
map[row][col]=3;
return false;
}
}else{
return false;
}
}
}



public static void main(String[] args){
Maze maze=new Maze();
maze.initMap();
maze.print();
if(maze.findWay(1,1)){
System.out.println("Finded the way");
Maze.print();
}else{
System.out.println("Not finded the way");
}
}
}

DAY4

八皇后问题

在一个8*8的棋盘上放八个棋子,要求任意两个棋子不能在同一行或同一水平线或同一斜线上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package PAPER;

public class EightQueen {

int[] chessBoard;
int soluations;//记录摆放方案

public void initBoard(){
chessBoard=new int[8];
soluations=0;
}

public boolean solve(int row){
if(row==8){
soluations+=1;
return true;
}else{
for(int col=0;col<8;col++){
if(isLegal(chessBoard,row,col)){
solve(row+1);
}
}
}
return false;
}

//检测前row+1行是否合法
public boolean isLegal(int[] board,int row,int col){
board[row]=col;
int[] check=new int[8];
check[0]=board[0];
for(int i=1;i<=row;i++){
if(isRight(board[i],check,i)){
check[i]=board[i];
}else{
board[row]=0;
return false;
}
}
return true;
}

public boolean isRight(int num,int[] arr,int row){
for(int i=0;i<row;i++){
if(num==arr[i]){
return false;
}
if((row-i)==(num-arr[i])||(row-i+num-arr[i]==0)){
return false;
}
}
return true;
}

public static void main(String[] args){
EightQueen Queen=new EightQueen();
Queen.initBoard();
Queen.solve(0);
System.out.println("有"+Queen.soluations+"种解法");
}
}

可变参数使用

基本语法

1
//访问修饰符 返回类型 方法名(数据类型... 形参名){...},表示可以接收0-N个某种数据类型的参数

用例

1
2
3
4
5
6
7
8
9
//计算N个数的和
//此时nums可以当做数组处理,即nums就是int型数组
public int sum(int... nums){
int sum=0;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
}
return sum;
}
  • 使用细节

    • 可变参数的本质是数组

    • 可变参数的实参可以是数组

      1
      2
      3
      4
      5
      6
      7
      8
      public class T{
      public static void main(String[] args){
      T t=new T();
      int[] arr=new int[9];
      t.f1(arr);//直接传数组进去
      }
      public int f1(int...nums){...}
      }
    • 当可变类型参数和普通参数一起使用时,可变类型参数必须放在参数列表末尾

DAY5

IDEA快捷方式

  • 格式化代码:ctrl+alt+L

  • 生成构造器:alt+insert->constructor

  • 得到IDEA提供的重写后的toString用于输出属性:alt+insert->toString

  • 将光标放到一个方法上,ctrl+b,可以快速定位到该方法所在的类

  • 快速创建变量名:new 类名().var,IDEA就会自动分配一个变量名

  • 查看源码:将光标放在方法上,ctrl+b

  • 快速生成循环体:

    1
    2
    3
    4
    5
    //循环次数.for->回车
    //即会生成循环体:
    for(int i=0;i<循环次数;i++){
    ...
    }

DAY6

访问修饰符:

  • 公开级别:==public==,对外公开
  • 受保护级别:==protected==,对子类和同一个包中的类公开
  • 默认级别:没有修饰符,向同一个包中的类公开
  • 私有级别:==private==,只有类本身可以访问,不对外公开

使用细节:

  • 只有==默认==和==public==可以用来修饰类

DAY7

Object类详解

equals

  • equals只能用来比较引用类型

  • 是Object类中的方法,默认只能比较地址是否相同。在子类中往往==重写==,用于比较内容是否相同,比如Integer和String

  • equals与“==”的比较:

    1. “==”既可以比较基础类型,也可以比较引用类型

    2. “==”用于比较基础类型时,比较的是值是否相同

    3. “==”用于比较引用类型时,比较的是地址是否相同,即是否是同一个对象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public static void main(String[] args){
      A a=new A();
      A b=a;
      A c=b;
      System.out.println(a==b);//true
      System.out.println(a==c);//true
      System.out.println(b==c);//true
      B obj=new A();
      System.out.println(obj==a);//true
      }

      class B{}
      class A extends B{}

hashCode方法

  • 如果指向的是同一个对象,则哈希值一定相同
  • 如果指向的不是同一个对象,则一般哈希值不同
  • 哈希值主要是根据地址号来的,但不能完全等同于地址

toString方法

  • 默认返回:==全类名(包名+类名)==+@+哈希值的十六进制,子类往往重写toString方法

  • 重写toString方法,打印对象或拼接对象时,都会自动调用对象的toString方法

  • 当直接输出一个对象时,会默认调用toString方法

  • //源码
    public String toString(){
        return getClass().getName()+"@"+Integer.toHextString(hashCode());
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47

    - IDEA提供了一个重写后的toString用于输出属性:快捷键alt+insert->toString

    ### finalize方法

    - 当对象被回收时,会自动调用该对象的finalize方法。可以自己重写该方法
    - 什么时候对象会被回收:当没有引用指向该对象时,该对象就会被回收
    - 垃圾回收机制是由系统的GC算法调用的,即回收对象并不是即时的。我们也可以通过==System.gc()==来主动调用GC算法实现即时回收



    # DAY8

    ## 断点调试

    ### 断点调试快捷键

    - F7:跳入方法内
    - F8:逐行执行代码
    - shift+F8:跳出方法
    - F9:执行到下一个断点
    - ![](F:\工具资料\工具图片\QQ图片20220208140225.png)

    # DAY9

    ## main方法

    - **理解main方法的形式:**

    public static void main(String[] args){}

    1. main方法由jvm调用,因此访问权限必须是public

    2. jvm在调用main方法时不创建对象,因此必须是static

    3. 该方法接受String类型的数组参数,该数组中保存执行java命令时传递给所运行类的参数

    ```java
    public static void main(String[] args){
    EightQueen Queen=new EightQueen();
    Queen.initBoard();
    Queen.solve(0);
    System.out.println("有"+Queen.soluations+"种解法");
    for(int i=0;i< args.length;i++){
    System.out.println(args[i]);
    }
    }
    ![image-20220210094909732](C:\Users\38156\AppData\Roaming\Typora\typora-user-images\image-20220210094909732.png)
  • 注意:

    1. main方法是静态方法,所以如果想访问该类中非静态成员,只有通过创建该类的对象,通过该对象去访问非静态成员
  • IDEA中向main方法传参数

  • 控制台传入参数:

    java eightqueen.java 参数1 参数2 参数3

代码块

  • 基本语法:

    1
    2
    3
    [修饰符] {
    ...
    }
    • 修饰符可选,若选也只能是==static==
  • 对代码块的理解:

    1. 作为初始化的另一种方法
    2. 代码块在==创建对象==或==加载类==时被隐式调用
    3. 运行时代码块先于构造器运行
    4. 若多种构造器都有相同的步骤,则可以将其写入代码块中,使代码简洁
  • 代码块使用细节

    1. static代码块又称静态代码块,随==类的加载==而运行,而且==只会运行一次==。若是普通代码块,则每创建一个对象就会运行一次
    2. static属性也会随==类的加载==而运行
    3. 什么时候类会被加载:
      1. 对象被创建时
      2. 子类被创建时,父类也会被加载,而且父类先加载,子类后加载
      3. 使用类的静态成员时
    4. 若只是调用类的静态成员,则==普通代码块不会被调用==
  • 创建对象时,在一个类中的调用顺序是==(重要)==:

    1. 静态代码块和静态成员属性(两者按定义顺序,下同)
    2. 普通代码块和普通成员属性
    3. 调用构造器
    4. 构造器的最前面其实隐含了super()和==调用普通代码块==
  • 创建一个子类时,静态代码块、静态属性、普通代码块、普通属性、构造方法的顺序==(难点)==

    1. 父类的静态代码块和静态属性

    2. 子类的静态代码块和静态属性

    3. 父类的普通代码块和普通成员属性

    4. 父类的构造函数

    5. 子类的普通代码块和普通成员属性

    6. 子类的构造函数

DAY10

设计模式

  • 单例设计模式:

    • 介绍:单例设计模式就是,在整个软件中保证只存在一个对象实例,且该类只提供一个取得该对象实例的方法

    • 设计思路:

      1. 饿汉式:

        • 设计步骤:

          1. 该类构造器私有化

          2. 该类内部创建一个该类的实例对象(权限为私有且须static)

          3. 向外暴露一个公共的取得该实例对象的静态方法

          4. 代码实现:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            //只能有一个GirlFriend
            public Person{
            public static void main(String[] args){
            GirlFriend girlFriend=GirlFriend.getInstance();
            }
            }

            class GirlFriend{
            private String name;
            private static GirlFriend girlFriend=new GirlFriend("zjm");

            private GirlFriend(String name){
            this.name=name;
            }

            public static GirlFriend getInstance(){
            return girlFriend;
            }
            }
      2. 懒汉式:

        • 设计步骤:

          1. 构造器私有化

          2. ==定义==一个private的static对象

          3. 提供一个public的静态方法返回该类对象

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            public Person{
            public static void main(String[] args){
            GirlFriend girlFriend=GirlFriend.getInstance();
            }
            }

            class GirlFriend{
            private String name;
            private static GirlFriend girlFriend;

            private GirlFriend(String name){
            this.name=name;
            }

            public static getInstance(String name){
            if(girlFriend==null){
            girlFriend=new GirlFriend(name);
            }
            return girlFriend;
            }
            }
      3. 饿汉式和懒汉式的主要区别:

        • 饿汉式在类加载时就被创建,懒汉式则是在使用时才被创建
        • 饿汉式会浪费资源,因为有时没有用到对象但对象却被创建了
        • 懒汉式会有线程安全问题

DAY11

接口

  • 使用语法:

1
2
3
4
5
6
7
8
9
interface jiekouming{
//属性
//方法(抽象方法、默认方法、静态方法)
}
class leiming implements jiekouming{
//自己属性
//自己方法
//必须实现的接口的抽象方法
}

****JDK8.0之后接口中可以有静态方法、默认方法(==用default修饰==)

  • 接口使用细节
  1. 接口不可以被实例化对象
  2. 接口中所有的方法都是public,且abstract修饰符可以被省略
  3. 抽象类实现接口可以不实现接口的方法
  4. 接口中的属性必须是==final==且必须是==public static final==(一定是==静态且必须初始化==)
  5. 接口中属性的访问形式:接口名.属性名
  • 接口的多态特性

    • 接口的多态

      1. 虽然接口不能被创建实例对象,但接口引用可以指向一个实现了该接口的类

        1
        2
        3
        4
        5
        class AA implements IA{}
        interface IA{}
        public static void main(String[] args){
        IA ia=new AA();
        }
      2. 当方法形参是一个接口类型参数时,可以传入一个实现了该接口的类实参

      3. 接口类型数组可以存放实现了该接口的对象

    • 接口的多态传递

      若A接口继承了B接口,而类实现了A接口,则认为类也实现了B接口

DAY12

内部类

  • 内部类可分为四类:

    • 定义在外部类的局部位置(如方法内)
      1. 局部内部类(有类名)
      2. 匿名内部类
    • 定义在外部类的成员位置上
      1. 成员内部类(没有static修饰)
      2. 静态内部类(有static修饰)
  • 局部内部类

  1. 可以直接访问外部类的所有成员,包括私有成员
  2. 不可以添加修饰符。因为其本质是局部变量,局部变量是不能有修饰符的,但可以用final修饰,因为final本身就可以修饰局部变量
  3. 作用域:仅在定义他的方法体或代码块中
  4. 当外部类的成员和局部内部类的成员重名时,遵循就近原则。如果想访问外部类成员,使用:外部类名.this.成员名去访问(外部类名.this的本质就是外部类对象)
  • 匿名内部类==(重要!!!)==

    • 基础说明:

      1. 本质是一个类
      2. 同时还是一个对象
      3. 匿名内部类实际是有名字的,由系统分配,命名方法:外部类名$1;
    • 语法:new 类名/接口名(参数列表){…};

    • 实例解读(基于接口)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      //需求:使用IA接口并创建对象
      //传统方案:写一个类,实现该接口,并创建对象
      //理想方案:该类只使用一次,以后就不再使用了
      //此时使用匿名内部类来简化开发
      interface IA{
      public void cry();
      }
      public class Outer{
      private int num;

      public void method(){
      IA iA=new IA(){
      public void cry(){
      System.out.println("www");
      }
      };//JDK在底层立即创建Outer$1类(实现了IA接口),并创建实例返回给iA
      }
      }
      • 实际是JDK在底层立即创建Outer$1类(实现了IA接口),并创建实例返回给iA
      • 此时iA的==编译类型==:IA
      • 此时iA的==运行类型==:就是匿名内部类,即外部类名$1
    • 实例解读(基于类)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      public Outer{

      public void method(){
      Person person=new Person("michael"){//参数会传递给Person的构造器
      ...
      };
      }
      }
      class Person{
      String name;
      public Person(String name){
      this.name=name;
      }
      }
      //person编译类型:Person
      //person运行类型:
      • 实际是JDK在底层立即创建Outer$2类(==继承了Person==),并创建实例返回给person
      • 编译类型:Person
      • 运行类型:Outer$2
  • 成员内部类

  • 静态内部类

    • 使用:

      1. 可以访问外部类的所有的静态成员,但不能访问非静态的成员
    • 外部类访问静态类:先创建对象再访问

    • 外部其他类访问静态类:

      1
      Outer.Inner inner=new Outer.Inner();

DAY13

枚举

  • 自定义枚举类

    1. 构造器私有化,防止直接创建
    2. 成员属性用final static修饰防止被更改
    3. 枚举对象名通常全部大写
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Season{
    private String season;
    private String desc;

    private static final Season SPRING=new Season("春天","温暖");
    ...

    private Season(String season,String desc){
    this.season=season;
    this.desc=desc;
    }
    }
  • 使用enum关键字来实现枚举类

    1. 用enum替换class

    2. 用SPRING(“春天”,”温暖”)带替换 private static final Season SPRING=new Season(“春天”,”温暖”);

    3. 常量间用逗号分隔

    4. 常量必须必须写在最前面

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      enum Season{
      private String season;
      private String desc;

      SPRING("春天","温暖"),SUMMER("夏天","炎热");

      private Season(String season,String desc){
      this.season=season;
      this.desc=desc;
      }
      }
    5. ```java
      enum Gender{
      BOY,GIRL;
      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32

      上面的代码是没有错误的,**相当于调用了默认的无参构造器**

      ## 注解(Annotation)

      ### 基本的注解介绍:

      - **@Override**:限定某个方法,是重写父类方法。只能用于修饰方法
      - **@Deprecated**:用于表示某个程序元素(类、方法、字段、包、参数)已经过时了(但仍可以用)
      - **@SuppressWarnings**:抑制编译器警告





      # DAY14

      ## 包装类

      -

      ![](F:\工具资料\工具图片\QQ图片20220214221134.png)

      - **自动装箱与自动拆箱**

      ==**实质是JDK在底层调用valueOf()和inValue()方法**==

      ```java
      int n1=100;
      Integer integer=n1;//JDK在底层调用valueOf()

      int n2=integer;//JDK在底层调用intValue()
    • valueOf()的底层源码

    1
    2
    3
    4
    5
    public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)//即-128-127之间
    return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
    }

    ==可见,当i处于-127-128之间时,valueOf并不会返回Integer对象,而是直接返回值==

    ->

    ==面试题==

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public void method(){
    Integer n=new Integer(1);
    Integer m=new Integer(1);
    System.out.pirntln(n==m);

    Integer a=1;
    Integer b=1;
    System.out.println(a==b);

    Integer x=128;
    Integer y=128;
    System.out.println(x==y);

    int i=128;
    Integer j=128;
    System.out.println(i==j);//只要有基本数据类型,则仅判断值是否相等
    }

    //输出结果:false true false true

String类

  • String是final类,不可以被继承

  • String有属性==private final char[] value==,用于存放字符串内容

  • final修饰的value字符数组,表示value的地址不可以被修改,即下面的代码是会报错的

    1
    2
    3
    private final char[] value={"s","e"};
    char[] ch={"t","w"};
    value=ch;//wrong
  • String的常用方法

String的两种创建方式的区别

  • 方式一:String s=”abc”;

    方式二:String s2=new String(“abc”);

    • 方式一:在常量池中寻找是否有“abc”数据空间,有则直接指向,没有则在常量池中创建后再指向,最终指向的是常量池中的地址

    • 方式二:先在堆中创建了空间,里面维护了value属性。value指向常量池中的“abc”空间,如果常量池中没有“abc”空间,则创建后再指向。最终s2指向的是堆中的空间地址

      image-20220222191150515

  • ==练习题==

    1
    2
    3
    String str="abc"+"hjn";
    //上述创建了几个对象?
    //一个:abchjn

    解读:对于两个==字符串常量==的相加,编译器会做出优化,直接在常量池中创建一个“abchjn”对象

    1
    2
    3
    4
    5
    String s1="abc";
    String s2="hjn";
    String s3=s1+s3;
    //上述创建了一个对象?
    //三个,"abc"、"hjn"在常量池中,s3指向堆中创建的value[],value[]指向常量池中的"abchjn"

    解读:String s3=s1+s2在底层实际等于String s3=new String(“abchjn”),可以通过debug验证

浮点数类型

image-20220222182541375

  • 一般更常使用double型因为精度更高
  • 如果使用float型变量储存小数点后有多位数的小数,则输出时会丢失一部分尾数的精度
  • 定义是float类型的值可以赋给double,反之不可
  • 定义float类型的常量要在数字末尾加f

使用浮点数细节

  • 浮点数经计算后不能用于比较,下例:

    1
    2
    3
    4
    5
    6
    7
    double num1=2.7;
    double num2=8.1/3;
    if(num1==num2){
    System.out.println("相等");
    }

    //结果是没有输出任何东西

    因为计算机的计算是以二进制进行,而某些数值并不能用二进制精确的表示,比如0.1,这时计算就会产生精度误差,导致8.1/3在计算机中并不等于2.7

大数处理方案

当需要使用的数字过大超过long或需要保存的精度过高时,可以用BigInteger或BIgDecimal来处理

  • BigInteger

1
BigInteger bigInteger=new BigInteger("99999999999999999999999999999999");

当需要对BigInteger进行加减乘除时,不能直接用+、-、*、/,而是要使用对应的方法

  • BigDecimal

    1
    BigDecimal bigDeciaml=new BigDecimal("34.111111111111112343333333333333");

    同样也不能直接进行加减乘除,而是使用对应的方法

    ==注意==:当使用除法divide()时,若计算结果是一个无限循环数,则会报错。此时需要指定结果的精度

    1
    2
    3
    BigDecimal bigDeciaml=new BigDecimal("34.1111111111111123433333333333338");
    BigDecimal bigDecimal2=new BigDecimal("3");
    BigDecimal bigDecimal3=bigDecimal.divide(bigDecimal,BigDecimal.ROUND_CEILING);

    这样可以使结果的精度与分子一致

DAY15

异常

  • 异常的分类:

    • Error:JVM虚拟机无法解决的严重问题,如内存资源耗尽

    • Exception:其他因编程错误或外在因素导致的一般性问题,可以使用针对性代码进行处理,如空指针访问,试图读取不存在的文件

    • Exception又可以分为两大类:运行时异常和编译时异常

  • 异常体系图

  • 异常小结:

    • 运行时异常(RuntimeException),是编译器不要求强制处理的异常(其实是编译器检测不出来),一般指编程时的逻辑错误,程序员应尽量避免。
    • 对于运行时异常可以不做处理,因为这类异常很常见,若都做处理则会对程序的可读性和运行效率产生影响
    • 编译时异常是编译器要求必须处理的异常
  • try/catch/finally

    ==练习题==

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class Son extends father {

    public static int method() {
    int i = 1;
    try {
    i++;
    String[] strs = new String[3];
    if (strs[0].equals("tom")) {
    System.out.println(strs[0]);
    }
    } catch (NullPointerException e) {
    return ++i;
    } finally {
    ++i;
    System.out.println("i="+i);
    }
    return 0;
    }

    public static void main(String[] args) {
    System.out.println(method());
    }
    }

    //输出结果:i=4 3

    程序执行到catch中的return时,因为必须执行finally中的语句,所以此时底层会用一个==temp临时变量==来保存当前i的值,即3。随后i进入到finally中变成4,但最后return的==是temp保存的值==,即3

throws处理机制

  • 异常处理机制有两种:
  1. try/catch/finally:程序员在代码中捕获发生的异常,自己设计代码处理
  2. throws:将异常抛出,交给调用者(方法)来处理,最顶级的处理者是JVM
  • JVM的处理方式:打印异常信息,并终止程序
  • 如果程序员没有选择任意一种处理方式,则程序会默认选择throws Exception
  • throws使用细节:
    • 子类重写父类方法,对抛出异常的要求:子类中方法抛出的异常要么与父类一致,要么是父类方法抛出异常的子类

自定义异常类

  • 步骤

    • 自定义一个异常类继承Exception或RuntimeException
    • 继承Exception则是编译时异常
    • 继承RuntimeException则是运行时异常(一般继承RuntimeException)
    • 因为main默认抛出的是RuntimeException,如果继承Exception,则需要在main声明抛出Exception,即写上throws Exception
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//要求,若接受的年龄不在18-100之间,则抛出一个自定义的异常
public class Test
public static void main(String[] args){
int age=80;
if(age<18||age>120){
throw new AgeException("age out of the limit");
}
}
}

class AgeException extends RuntimeExcepption{
public AgeException(String message){
super(message);
}
}

关于自定义异常类的继承问题

在接口开发的过程中,为了程序的健壮性,经常要考虑到代码执行的异常,并给前端一个友好的展示,这里就得用到自定义异常,继承RuntimeException类。那么这个RuntimeException和普通的Exception有什么区别呢。

  1、Exception: 非运行时异常,在项目运行之前必须处理掉。一般由程序员try catch 掉。

  2、RuntimeException,运行时异常,在项目运行之后出错则直接中止运行,异常由JVM虚拟机处理。

  在接口的逻辑判断出现异常时,可能会影响后面代码。或者说绝对不容忍(允许)该代码块出错,那么我们就用RuntimeException,但是我们又不能因为系统挂掉,只在后台抛出异常而不给前端返回友好的提示吧,至少给前端返回出现异常的原因。因此接口的自定义异常作用就体现出来了。

  由于项目中自定义异常一般继承 RuntimeException 所以想了解一下为什么。

一、异常基础知识
  关于异常基础知识详见之前这篇博客:Java异常处理基础知识笔记:异常处理机制、异常继承关系、捕获异常、抛出异常、异常的传播、异常调用栈、自定义异常、第三方日志库

二、runtimeException和exception的两种异常的用途
  网上摘得一段话,比喻的很恰当:

  继承Exception还是继承RuntimeException是由异常本身的特点决定的,而不是由是否是自定义的异常决定的。

  例如我要写一个java api,这个api中会调用一个极其操蛋的远端服务,这个远端服务经常超时和不可用。所以我决定以抛出自定义异常的形式向所有调用这个api的开发人员周知这一操蛋的现实,让他们在调用这个api时务必考虑到远端服务不可用时应该执行的补偿逻辑(比如尝试调用另一个api)。此时自定义的异常类就应继承Exception,这样其他开发人员在调用这个api时就会收到编译器大大的红色报错:【你没处理这个异常!】,强迫他们处理。

  又如,我要写另一个api,这个api会访问一个非常非常稳定的远端服务,除非有人把远端服务的机房炸了,否则这个服务不会出现不可用的情况。而且即便万一这种情况发生了,api的调用者除了记录和提示错误之外也没有别的事情好做。但出于某种不可描述的蛋疼原因,我还是决定要定义一个异常对象描述“机房被炸”这一情况,那么此时定义的异常类就应继承RuntimeException,因为我的api的调用者们没必要了解这一细微的细节,把这一异常交给统一的异常处理层去处理就好了。

  总结一下:

  抛出 RuntimeException(运行期才可以发现的异常),调用方法的程序员不需要知道会出这个异常。

  抛出Exception的方法,调用者需要明确知道这个方法里会出现什么异常,并提示调用者要去处理这个可能得异常。

  简单的说,非RuntimeException必要自己写catch块处理掉。RuntimeException不用try catch捕捉将会导致程序运行中断,若用则不会中断。

DAY16

集合

  • 集合类的体系图图(熟记下图内容)

    1. 单列集合体系图

    2. 双列集合体系图

  • 集合体系分为两类(单列集合,双列集合)

    1. 单列集合中存放的都是单个对象
    2. 双列集合中存放的都是==K-V式==的对象
  • Iterator迭代器(用于遍历元素)

    所有实现了Collection的集合类都有一个iterator方法,可以返回一个Iterator容器

    Iterator只是遍历元素所用的,本身并不存放对象

  • Iterator执行原理

    1
    2
    3
    4
    5
    6
    7
    Collection col=new ArrayList();

    Iterator ite=col.iterator();
    while(ite.hasNext()){//判断是否有下一个元素
    Object obj=ite.next();//返回下一个元素
    System.out.println("obj="+obj);
    }
    1. 在使用next()方法之前必须先用hasNext()判断,否则就会抛出一个NoSuchElementExcepiton
    2. 如果需要再次遍历,则需要重置迭代器,重置语句:Iterator ite=col.iterator();
  • List常用方法

  • ArrayList底层源码分析

    1. ArrayList中维护了一个Object类型的数组elementData:transient Object elementData
    2. 当创建ArrayList对象时,如果是无参构造器,则初始容量为0,第一次添加则容量扩充为10,以后每次扩充为之前的1.5倍
  • Vector底层源码分析

    1. Vector底层也是一个对象数组:protected Object[] elementData;
    2. Vectors是线程同步的,即线程安全,Vector类的操作方法带有synchronized关键字
  • LinkedList底层源码分析

    • LinkedList的底层是一个双向链表

    • 有成员变量first、last分别指向链表的头结点和尾节点

    • 添加元素的源码分析

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      void linkLast(E e){
      final Node<E> l=last;
      final Node<E> newNode=new Node<E>(l,e,null);
      last=newNode;
      if(l==null){
      first=newNode;
      }else{
      l.next=newNode;
      }
      size++;
      modCount++;
      }
    • 删除元素的源码分析

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      public E remove(){
      return removeFirst();//可见remove()默认删除第一个元素
      }
      public E removeFirst(){
      final Node<E> f=first;
      if(f==null)
      throw new NoSuchElementException();
      return unLinkFirst(f);
      }
      private E unLinkFirst(Node<E> f){
      final E element=f.item;
      final Node<E> next=f.next;
      f.item=null;
      f.next=null;
      first=next;
      if(next==null){
      last=null;
      }else{
      next.prev=null;
      }
      size--;
      modCount++;
      return element;
      }
  • Set接口

    1. Set接口的实现类的对象(下称Set接口对象)添加元素是无序的,即==添加元素和取出元素的方法不一致==,但取出元素的顺序是固定的,这在底层由算法控制

    2. Set接口对象中的元素是没有索引的

    3. **Set接口对象中不允许添加重复的元素(何为重复,可以由程序员自己定义 **

      • HashSet

        1. HashSet的底层实质上是HashMap,是数组+链表+红黑树

          1
          2
          3
          4
          5
          //源码中的构造器
          public HashSet(){
          map=new HashMap<>();
          }

        2. ==注意==:关于Set接口对象中不允许添加重复的元素

          1
          2
          3
          4
          Set set=new HashSet();
          set.add=new String("zmx");//true
          set.add=new String("zmx");//false
          //思考:上下两个String并不是同一个对象,那么为什么不能添加呢?

          解读:HashSet的底层源码分析:自己Debug去~~

          结论:

          1. HashSet底层是HashMap,是数组+链表+红黑树

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            //简单模拟HashMap结构
            public class HashMapStructure{
            Node[] table=new Node[16];//数组
            }

            class Node{
            Object obj;
            Node next;//链表

            public Node(Object obj,Node next){
            this.obj=obj;
            this.next=next;
            }
            }
          2. 添加一个元素时,先得到该元素的hash值==(通过对该对象的hashCode值处理得到)==,将hash值用算法处理得到该元素的索引值

          3. 通过索引值找到索引位置, 看是否已经存在元素

          4. 如果不存在,则直接加入

          5. 如果存在,再==调用equals==比较,如果相同,则放弃添加;如果不同,则放到该索引位置对应的链表的最后

          6. 在JAVE8中,如果某条链表的节点个数到达8,并且table的大小到达64,就会进行树化

          7. 如果链表个数不小于8但table表的大小小于64,则会对table进行两倍扩容

          综上所述,当将程序员自己编写的类加入HashSet之前,往往要==重写该类的hashCode()和equals()==

          练习题:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          //创建一个一个Employee类,有name和age属性
          //要求当name和age相同时,被认为是相同员工,不能重复加入

          public class Employee{

          String name;
          int age;

          public Employee(String name,int age){
          this.name=name;
          this.age=age;
          }
          //下面重写hashCode()和equals()
          //IDEA提供快捷键帮助编写:alt+insert 或右键->generate

          public int hashCode(){
          return OBjects.hash(name,age);
          }

          public boolean equals(Object o){
          if(o==this)return true;
          if(o==null||getClass()!=o.getClass())return false;
          Employee p=(Employee)o;
          return p.age=age&&Objects.equals(name,p.name);
          }

          }
        3. Set集合的遍历方式

          • 因为Set接口继承自Collection,因此可以使用Iterator进行遍历
          • forEach增强for循环
      • LinkedHashSet

        • extends HashSet
        • 底层是一个LinkedHashMap,维护了一个数组加双向链表
        • 添加元素的原则与HashSet类似,但同时使用双向链表维护了元素的次序,使得元素看上去是以加入顺序保存的,因此,==LinkedHashSet中加入与取出的顺序是一致的==
      • TreeSet

        • 底层是TreeMap,跟HashSet与HashMap类似

        • TreeSet可以使用无参构造器,这样没有排序功能

        • 当使用有参构造器时,传入一个Comparator来确定排序的规则

        • Comparator由程序员自己实现

          1
          2
          3
          4
          5
          6
          TreeSet treeSet=new TreeSet(new Comparator(){
          @Override
          public void Compare(Object o1,Object o2){
          return (String)o1.compareTo((String)o2);
          }//返回0即是相等
          })
        • 传入相同的key(相同与否也是由Comparator决定)时,底层源码会用新value覆盖之前的value,key不做修改,但TreeSet的value均为null

          1
          2
          3
          4
          //截取部分源码
          else{//如果添加元素时发现key相同
          return t.setValue(value);
          }
      • Map接口

        双列集合,用于保存具有映射关系的数据:Key-Value

        HashMap概述:

        1. 基本结构在HashSet中已经介绍过,是数组+链表+红黑树
        2. Key-Value可以使任何类型的数据(未使用泛型),在底层会被封装到HashMap$Node中
        3. 由debug追源码可知,Map中的key不允许重复,而value是允许重复的
        4. 添加元素时,当key重复而value不同时,新添加的value会替换掉原来的value
        5. key可以为null,同样不能重复
        6. key对value存在单一映射关系,即总能通过指定的key找到对应的value
        7. 添加元素的方法:put()
        • 源码分析(关于K-V在底层的存储)

          • K-V最后在底层的存储是:HashMap$Node node=newNode(hash,key,null);(newNode之间没有漏空格,newNode是方法(确信)
            • Node实现了Entry接口
          • 同时,==为了方便遍历==,HashMap还维护了一个EntrySet集合,集合名为entrySet,该集合存放Entry<K,V>类型的对象,即EntrySet<Entry<k,v>>;
            • 其实,Entry是一个接口,而接口不能实例化,因此,实际上ertrySet中存放的是==上面所述的==实现了Entry接口的Node对象(接口的多态)
            • 方法entrySet():返回Map的entrySet对象
          • 同样的,为了方便遍历,底层还维护了KeySet、Values分别维护key、value数据,实际上与EntrySet一致,都是不直接存储,但是有对Node的指向
        • Map接口的遍历方式:

          • keySet结合get()

            1
            2
            3
            4
            Set keys=map.keySet();//keySet方法返回keySet对象
            for(Object key:keys){
            System.out.println(key+"::"map.get(key));
            }
          • Iterator迭代器

            • keySet实现了Collection接口,因此可以使用迭代器
          • entrySet结合getKey()、getValue()

            1
            2
            3
            4
            5
            Set entrySet=map.entrySet();
            for(Object entry:entrySet){
            Map.Entry m=(Map.Entry)entry;
            System,.out,println(m.getKey()+":"m.getValue());
            }
            • 同样,entrySet也实现了Collection接口,也可以使用Iterator迭代器

      HashTable

      • 概述:
        1. HashTable存放的是K-V类型数据
        2. key不能重复,value可以重复
        3. 当key相同而value不同时,新的value会覆盖之前的value
        4. key和value都不能为null,==这点与HashMap不同==
        5. HashTable是线程安全的,HashMap不是

      TreeMap

      • 同样可以选择无参构造器或传入一个Comparator来进行排序

      • 传入相同元素(相同与否也是由Comparator决定)时,底层源码会用新value覆盖之前的value值。key不做修改

        1
        2
        3
        4
        //截取部分源码
        else{//如果添加元素时发现key相同
        return t.setValue(value);
        }

集合选型规则

由开发需求确定

  1. 先判断存储的类型
  2. 单列数据类型:Collection接口
    1. 允许重复:List
      • 改查较多:ArrayList
      • 增删较多:LinkedList
    2. 不允许重复:Set
      • 无序:HashSet
      • 存入与取出顺序一致:LinkedHashSet
      • 排序:TreeSet
  3. 双列数据类型:Map接口
    1. key无序:HashMap
    2. key有序:TreeMap
    3. 存入与取出顺序一致:LinkedHashMap

DAY17

泛型

问题引出:==有了多态,为什么还要泛型?==

  1. 多态不能对对象的类型进行约束
  2. 多态需要使用向下转型,效率低下

自定义泛型类

  1. 基本语法

    1
    2
    3
    4
    5
    6
    public class Car<T,E,R...>{
    T t;
    E e;
    R r;
    ...
    }
  2. 注意

    1. 使用泛型的数组不能初始化,因为==泛型擦除==
    2. 静态成员不可以使用泛型
      • 因为==泛型类的泛型是在创建对象时确定的==,而静态成员与类的加载有关,当类被加载时,对象还未被创建
    3. 当泛型类未使用泛型时(即没有传入T、E、R),泛型默认为Object

自定义泛型接口

  1. 基本语法

    1
    2
    interface I<T,R...>{
    }
  2. 注意

    1. 静态成员同样不能初始化
    2. 没有指定类型时,同样默认为Object
    3. 泛型接口类型在==继承接口==或==实现接口==时确定

自定义泛型方法

  1. 基本语法

    1
    2
    //修饰符 <T,R.> 返回类型 方法名(参数列表){
    }
  2. 注意

    1. 泛型方法既可以定义在泛型类中,也可以定义在普通类中

    2. 泛型方法的类型在调用时被确定

      1
      2
      3
      4
      5
      zmx.eat("chips",3);

      public <E,T> void eat(E e,T,t){
      ...
      }
    3. public void eat(E e){},声明时没有<T,E..>,==不是泛型方法,而是使用了泛型==

      • 使用了泛型的方法一定是定义在泛型类中,并使用了泛型类的泛型类型

        1
        2
        3
        4
        5
        6
        7
        8
        public class Animal<E e>{
        public void eat(E e){//true
        ...
        }
        public void play(R r){//false
        ...
        }
        }
  • 泛型的继承和通配符

    • 泛型==不具有继承性==

      1
      2
      Object str=new String("abc");//true
      List<Object> list=new ArraysList<String>();//false
    • <?>表示任意泛型类型

    • <? extends A>表示支持A和A的子类,规定了泛型的上限

    • <? super B>表示支持B和B的父类,规定了泛型的下限

  • ==关于泛型擦除==

    泛型擦除概念#

    Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
    例如:List<String> 和 List<Integer> 在编译后都变成 List。

    证明泛型擦除#

    1
    2
    3
    4
    5
           ArrayList<String> arrayList1=new ArrayList<String>();
    arrayList1.add("abc");
    ArrayList<Integer> arrayList2=new ArrayList<Integer>();
    arrayList2.add(123);
    System.out.println(arrayList1.getClass()==arrayList2.getClass());

    我们定义了两个ArrayList数组,不过一个是ArrayList泛型类型,只能存储字符串。一个是ArrayList泛型类型,只能存储整形。最后,我们通过arrayList1对象和arrayList2对象的getClass方法获取它们的类的信息,最后发现结果为true。说明泛型类型String和Integer都被擦除掉了,只剩下了 原始类型。

    1
    2
    3
    4
    5
    6
    ArrayList<Integer> arrayList3=new ArrayList<Integer>();
    arrayList3.add(1);//这样调用add方法只能存储整形,因为泛型类型的实例为Integer
    arrayList3.getClass().getMethod("add", Object.class).invoke(arrayList3, "asd");
    for (int i=0;i<arrayList3.size();i++) {
    System.out.println(arrayList3.get(i));
    }

    在程序中定义了一个ArrayList泛型类型实例化为Integer的对象,如果直接调用add方法,那么只能存储整形的数据。不过当我们利用反射调用add方法的时候,却可以存储字符串。这说明了Integer泛型实例在编译之后被擦除了,只保留了 原始类型。

    引用检查与编译#

    说类型变量会在编译的时候擦除掉,那为什么我们往ArrayList arrayList=new ArrayList();所创建的数组列表arrayList中,不能使用add方法添加整形呢?

    java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,在进行编译的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
           ArrayList<String> arrayList1=new ArrayList();
    arrayList1.add("1");//编译通过
    arrayList1.add(1);//编译错误
    String str1=arrayList1.get(0);//返回类型就是String

    ArrayList arrayList2=new ArrayList<String>();
    arrayList2.add("1");//编译通过
    arrayList2.add(1);//编译通过
    Object object=arrayList2.get(0);//返回类型就是Object

    new ArrayList<String>().add("11");//编译通过
    new ArrayList<String>().add(22);//编译错误
    String string=new ArrayList<String>().get(0);//返回类型就是String

    类型检查就是针对引用的,会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。

    泛型擦除与多态的冲突和解决方法#

    泛型重载变重写?#

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class Pair<T> {
    private T value;
    public T getValue() {
    return value;
    }
    public void setValue(T value) {
    this.value = value;
    }
    }

    public class DateInter extends Pair<Date> {
    @Override
    public void setValue(Date value) {
    super.setValue(value);
    }
    @Override
    public Date getValue() {
    return super.getValue();
    }
    }

    在这个子类中,我们设定父类的泛型类型为Pair,在子类中,我们覆盖了父类的两个方法,我们的原意是这样的:

    将父类的泛型类型限定为Date,那么父类里面的两个方法的参数都为Date类型

    1
    2
    3
    4
    5
    6
       public Date getValue() {
    return value;
    }
    public void setValue(Date value) {
    this.value = value;
    }

    实际上,类型擦除后,父类的的泛型类型全部变为了原始类型Object,而子类的类型是Date,参数类型不一样,这如果实在普通的继承关系中,根本就不会是重写,而是重载。

    本意是将父类中的泛型转换为Date类型,子类重写参数类型为Date的两个方法

    实际上,虚拟机并不能将泛型类型变为Date,只能将类型擦除掉,变为原始类型Object。这样,我们的本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突。JVM知道你的本意吗?知道!!!可是它能直接实现吗,不能!!!

  • 擦除带来的问题

    Java 是通过擦除来实现把泛型类型实例关联到同一份字节码上的。

    编译器只为泛型类型生成一份字节码,从而节约了空间,但是这种实现方法也带来了许多隐含的问题。下面介绍几种常见的问题。

    1) 泛型类型变量不能是基本数据类型

    泛型类型变量只能是引用类型,不能是 Java 中的 8 种基本类型(char、byte、short、int、long、boolean、float、double)。

    以 List 为例,只能使用 List,但不能使用 List,因为在进行类型擦除后,List 的原始类型会变为 Object,而 Object 类型不能存储 int 类型的值,只能存储引用类型 Integer 的值。

    2) 类型的丢失

    通过下面一个例子来说明类型丢失的问题。

    1
    class Test{    public void List<Integer> list){}    public void List<String> list){}}

    上述代码中,编译器认为这个类中有两个相同的方法(方法参数也相同)被定义,因此会报错,主要原因是在声明 List 和 List 时,它们对应的运行时类型实际上是相同的,都是 List,具体的类型参数信息 String 和 Integer 在编译时被擦除了。

    正因为如此,对于泛型对象使用 instanceof 进行类型判断的时候就不能使用具体的类型,而只能使用通配符?,示例如下所示:

    1
    List<String> list=new ArrayList<String>();if( list instanceof ArrayList<String>) {}  //编译错误if( list instanceof ArrayList<?>) {}  //正确的使用方法

    3) catch中不能使用泛型异常类

    假设有一个泛型异常类的定义 MyException,那么下面的代码是错误的:

    1
    try{}catch (MyException<String> e1)

    catch ( MyExceptione2){…}

    因为擦除的存在,MyException 和 MyException 都会被擦除为 MyException,因此,两个 catch 的条件就相同了,所以这种写法是不允许的。

    此外,也不允许在 catch 子句中使用泛型变量,示例代码如下所示:

    1
    public <T extends Throwable> void test(T t) {    try{        ...    }catch(T e) {  //编译错误        ...    } catch(IOException e){    }}

    假设上述代码能通过编译,由于擦除的存在,T 会被擦除为 Throwable。由于异常捕获的原则为:先捕获子类类型的异常,再捕获父类类型的异常。

    上述代码在擦除后会先捕获 Throwable,再捕获 IOException,显然这违背了异常捕获的原则,因此这种写法是不允许的。

    4) 泛型类的静态方法与属性不能使用泛型

    由于泛型类中的泛型参数的实例化是在实例化对象的时候指定的,而静态变量和静态方法的使用是不需要实例化对象的,显然这二者是矛盾的。

    如果没有实例化对象,而直接使用泛型类型的静态变量,那么此时是无法确定其类型的。

    DAY17

    JUnit简单使用

    • 介绍:JUnit是一个java语言的单元测试框架

    • 当我们需要测试类的方法时,往往需要将其写在main方法中调用,但使用JUnit则不必

    • 使用

      在Test处alt+enter,选择合适的JUnit版本添加,比如JUnit5.7.0

    DAY18

    Java坐标体系

    基本介绍

    • java坐标体系以左上角为原点,以像素作为单位
    • 像素:像素是密度单位,不是长度单位。同一块屏幕,可以分为1080/720,也可以分为720/480

    基本结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class MyFrame extends JFrame{
    private myPanel;

    public MyFrame(){
    myPanel=new MyPanel();
    this.add(myPanel);
    this.setSize(500,500);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setVisuable(true);
    }
    }

    class MyPanel extends JPanel{

    @Override
    public void paint(Graphi g){
    super.paint(g);//必须要有这一行,调用父类的paint()
    g.drawLine(x,y,xx,yy);
    }
    }

    关于paint()方法

    • paint()绘制组件的外观
    • paint()会在以下情况中被自动调用
      1. 组件第一次在屏幕中显示的时候
      2. 先最小化,再最大化时
      3. 窗口大小变化时
      4. 调用repaint()时

    DAY19

    线程

    • 线程的两种使用方式:本质一致,均是将要执行的任务写在==run==方法中

      1. 继承Thread类

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        public static void main(String[] args){
        Mythread t=new MyThread();
        t.start();//启动新线程并在底层调用run方法
        }
        class MyThread extends Thread{
        @Override
        public void run(){
        ...
        }
        }
      2. 实现Runable接口

        1
        2
        3
        4
        5
        6
        7
        8
        9
        public static void main(String[] args){
        Thread t=new Thread(new MyRunable());
        t.start();//启动新线程并在底层调用run方法
        }
        class MyRunable implements Runable{
        public void run(){
        ...
        }
        }
    • 细节

      1. 为什么不直接调用run方法?
        • 源码中start方法中调用了start0方法,这个方法是实现多线程的关键,而run只是一个普通方法
        • 因此,直接调用run方法,只会在main线程中执行run中的任务,并没有达到多线程的目的
      2. main线程的结束不会影响新开的线程,二者是并发关系
    • Thread常用方法

      1. yield():礼让
        • 让出cpu,让其他线程先执行,但礼让的时间不确定,也不一定礼让成功,如果cpu资源充足,则就不会礼让成功
      2. join():插队
        • 插队的线程一旦插队成功,则一定会执行完所有的任务
        • 如:在main线程中执行:t1.join(),则一定会t1执行完成后再执行main线程
      3. interrupt():中断
        • 一般用于中断线程的休眠
        • 休眠中的线程被中断是会抛出一个InterruptedException,可以catch并添加自己的业务代码
    • 工作线程与守护线程

      • 工作线程:当线程的工作完成或以被通知的方式完成

      • 守护线程:一般是为工作线程服务的,当工作线程结束时,守护线程自动结束

      • 典型守护线程:垃圾回收机制

      • 示例

        1
        2
        3
        4
        5
        //在main线程中
        MyThread t=new MyThread();
        t.setDaemon(true);//这句写在start之前
        t.start();
        //此时t是main的守护线程,当main结束,t则自动结束
    • 线程的生命周期

      • new

      • Runable

      • Time-waiting

      • Waiting

      • Blocked

      • Ternimated

      • ==状态转换图==

        ![](C:\Users\38156\Desktop\工具资料\工具图片\【零基础 快速学Java】韩顺平 零基础30天学会Java_哔哩哔哩_bilibili - Google Chrome 2022_3_15 16_41_49.png)

    DAY20

    IO流

    导图

    • 概念

      • 流:数据在数据源(文件)和程序(内存)之间经历的路径
        • 输入流:数据从数据源到程序
        • 输出流:数据从程序到数据源
    • 创建文件的相关构造器及方法

      • new File(String pathName)//根据路径名创建

      • new File(File parent,String child)//根据父目录以及子路径创建

      • new File(String parent,String child)//根据父目录加子路径创建

      • creatNewFile();

      • //方式一:
        String path=new File("d:\\zmx.txt");
        File file=new File(path);
        
        //方式二:
        String parentPath="e:\\";
        File parent=new File(parent);
        String childPath="zmx.txt";
        File file=new File(parent,child);
        
        //方式三
        String parentPath="e:\\";
        String childPath="zmx.txt";
        File file=new File(parentPath,childPath);
        
        //最后
        file.createNewFile()
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53

        **==注意==:创建完file之后,file还仅仅停留在内存中,要通过==creatNewFile()==才能联系到硬盘**

        - **文件操作**

        1. **首先记住,目录也被当做文件**
        2. **创建目录:(由file调用)**
        1. **mkdirs():创建多级目录**
        2. **mkdir():创建一级目录**



        ## IO流原理和分类

        - **字节流:FileInputStream、FileOutputStream,按字节操作**
        - **字符流:FileReader,FileWriter,按字符操作**





        **InputStream结构图**

        ![](F:\工具资料\工具图片\QQ图片20220317151100.png)





        - **FileInputSteam**

        **直接上代码学习**

        ```java
        public void readLine(){
        String filePath="d:\\zmx.txt";
        FileInputStream fileInputStream=null;
        int readConTent;
        try {
        fileInputStream=new FileInputStream(filePath);
        while((readConTent=fileInputStream.read())!=-1){
        System.out.println((char)readConTent);
        }
        } catch (IOException e) {
        e.printStackTrace();
        }finally {
        try {
        fileInputStream.close();
        } catch (IOException e) {
        e.printStackTrace();
        }
        }
        }
      • 构造方法:FileInputStream fileInputStream=new FileInputStream(filePath);

      • read():每次读取下一个==字节==的数据,若不存在则返回-1

        • 因为读取的是字节信息,而一个汉字在UTF-8中有三个字节,因此==这种方式不能读取汉字==。否则乱码
      • close():流是路径,如果用完不及时不关闭流,则会导致可能有多个流指向同一个文件,造成资源的浪费

      ==read(Byte[])==可以一次读取多个字节

      • read(byte[]):最多可以一次读取byte[].length个字节,最后返回实际读取的字节数,如果读取完毕则返回-1
      • new String(byte[],int beginIndex,int endIndex):转成字符串

      使用read(Byte[])提升效率

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public void readLine(){
      String filePath="d:\\zmx.txt";
      FileInputStream fileInputStream=null;
      byte[] by=new byte[8];
      int readLength=0;
      try {
      fileInputStream=new FileInputStream(filePath);
      while((readLength=fileInputStream.read(by))!=-1){
      System.out.println((new String(by,0,readLength));
      }
      } catch (IOException e) {
      e.printStackTrace();
      }finally {
      try {
      fileInputStream.close();
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
      }

    OutputSteam结构图

    • FileOutputStream

      上代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public void writeFile(){
      String filePath="d:\\zjm.txt";
      FileOutputStream fileOutputStream=null;
      try {
      fileOutputStream=new FileOutputStream(filePath,true);
      // fileOutputStream.write('z');//输入单个字符
      String str="hello,world";
      fileOutputStream.write(str.getBytes());//输入字符串
      } catch (IOException e) {
      e.printStackTrace();
      }finally {
      try {
      fileOutputStream.close();
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
      }
      • 构造方法: FileOutputStream(String filePath,boolean append)//==append为true,则在文件末端写入,为false,则在文件开头写入==
      • 指定路径不存在时会自动创建
      • write(int a):输入单个字符
      • write(byte[] bytes):输入字符串
      • write(byte[] bytes,int off,int len):在bytes的off偏移位置开始取出len长度的字节并输出
      • getBytes():字符串转字节数组
    • FileReader

      • 构造方法:new FileReader(File/String)

      • read():每次读取一个字符并返回,若文件尾则返回-1

      • read(char[]):批量读取多个字符到字符数组中,并返回读取字符的个数,若文件尾则返回-1

      • 相关api:new String(char[] chs,int off,int length)

      • ```java
        public void readFile(){
        String filePath=”d:\zmx.txt”;
        FileReader fileReader=null;
        try {
        fileReader=new FileReader(filePath);
        //单个字符读取
        // int ch=0;
        // while((ch=fileReader.read())!=-1){
        // System.out.print((char)ch);
        // }

                //多个字符读取
                int readLength=0;
                char[] chs=new char[8];
                while((readLength=fileReader.read(chs))!=-1){
                    System.out.print(new String(chs,0,readLength));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24






        - **FileWriter**

        - **构造方法:new FileWriter(File/String,boolean append)**

        - **write(int):写入单个字符**

        - **write(char[]):写入字符数组**

        - **write(char[] ,int off,int len):写入字符是数组的指定部分**

        - **write(String):写入字符串**

        - **write(String str,int off,int len):写入字符串的指定部分**

        - **一定要flush()或close(),==只有flush()或close()之后,字符才会被真正写入到流==**

        - ```
        //懒得写了,都差不多

    文件拷贝

    文件拷贝一定是边读遍写的,不是先完全读完再去写入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public void copyFile(){
    String srcPath="d:\\zjm.txt";
    String destPath="d:\\zmx.txt";
    FileInputStream fileInputStream=null;
    FileOutputStream fileOutputStream=null;

    try {
    fileInputStream=new FileInputStream(srcPath);
    fileOutputStream=new FileOutputStream(destPath,true);
    byte[] bytes=new byte[10];
    int readLength=0;
    while((readLength=fileInputStream.read(bytes))!=-1){
    fileOutputStream.write(bytes,0,readLength);
    }
    } catch (IOException e) {
    e.printStackTrace();
    }finally {
    try {
    fileOutputStream.close();
    fileInputStream.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }

    节点流和处理流

    • 节点流可以从一个特定的数据源读写数据,如:FileReader,FileWriter

    • 处理流(也叫包装流):是”连接”在已经存在的流(节点流或处理流)之上,为程序提供更强大的读写功能,如BufferedReader,BufferedWriter

      • 以BufferedReader为例,其有一个Reader类型的成员变量,因此BufferedReader可以接收任意一个Reader的子类对象
    • BufferedReader

      • ==构造方法==:new BufferedReader(Reader in)
        • ==意味着可以传入任意一个Reader类的子类==
      • readLine():读取一行并返回读到的String,否则返回null
      • readLine()方法在进行读取一行时,只有遇到回车(\r)或者换行符(\n)才会返回读取结果,并且==对返回的读取结果进行擦除(\r)或(\n)的操作==,因此在循环输出时使用println
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public void readFile(){
      String filePath="d:\\zmx.txt";
      BufferedReader bufferedReader=null;
      try {
      String str=null;
      bufferedReader=new BufferedReader(new FileReader(filePath));
      while((str=bufferedReader.readLine())!=null){
      System.out.println(str);//因为擦除了换行符,所以这里使用println而不是print
      }
      } catch (IOException e) {
      e.printStackTrace();
      }finally {
      try {
      bufferedReader.close();
      } catch (IOException e) {
      e.printStackTrace();
      }

      }
      }
    • BufferedWriter

      1
      //懒得写(,都差不多
      • 记得writer.newLine();
      • 记得writer.flush()

    对象处理流

    需求:将int num=100保存到文件中,注意并不止保存100这个数值,还要保存int数据类型;将Dog dog=new Dog(“大黄”,10)保存到文件中;同时还要保证能从文件中恢复

    上面的要求即是:能够将基本数据类型进行序列化或反序列化

    • 序列化和反序列化

      1. 在保存数据时,保存数据的数值和数据类型

      2. 在恢复数据时,恢复数据的数值和数据类型

      3. 要让某个对象或数据类型可以被序列化或反序列化,==就要这个对象及其成员属性实现如下两接口之一==

        1. ==Serializable==:标记接口,没有方法需要实现
        2. Externalizable:该方法有方法需要是实现,因此一般使用Serializable
      4. 序列化的对象建议添加SerialVersionUID属性,为了提高版本的兼容性

      5. static或transient修饰的属性不可以被序列化

      6. 序列化具备可继承性,即,若一个类实现了序列化在,则其子类默认实现了可序列化

    • ObjectOutputStream

      上代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      public void writer(){
      String filePath="d:\\data.dat";
      ObjectOutputStream objectOutputStream=null;

      try {
      objectOutputStream=new ObjectOutputStream(new FileOutputStream(filePath));

      objectOutputStream.writeInt(100);
      objectOutputStream.writeBoolean(true);
      objectOutputStream.writeChar('a');
      objectOutputStream.writeUTF("詹美祥zmx");
      objectOutputStream.writeObject(new Dog("黄",10));

      } catch (IOException e) {
      e.printStackTrace();
      }finally {
      try {
      objectOutputStream.close();
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
      }
      }

      class Dog implements Serializable{
      String name;
      int age;

      public Dog(String name, int age) {
      this.name = name;
      this.age = age;
      }
      }
    • ObjectInputStream

      反序列化的顺序要和序列化的顺序保持一致

      方法:例:将writeInt换成readInt

      注意:readObject()返回的是Object类型

    标准输入输出流

    • System.in 标准输入 类型:InputStream 默认设备:键盘
    • System.out 标准输出 类型:PrintStream 默认设备:显示器

    转换流

    • InputStreamReader:可以将InputStream包装成字符流,并选择指定的编码格式(可以不选,下同)

      • 构造方法:new InputStreamReader(InputStream,CharSet)
    • OutputStreamWriter:可以将OutputStream包装成字符流,并根据指定的编码格式

      • 构造方法:new OutputStreamWriter(OutputStream,CharSet)

    打印流

    • PrintStream
    • PrintWriter

    Properties

    • Properties不是一种流,他是一种集合,继承自HashTable

    • 在开发中,我们常常用properties类型的文件储存信息,这也是一种==K-V==的存储模式,如下

    • Properties对象的作用就是:方便的读取properties文件,并对其进行的修改,或者是创建Properties文件

      • 信息的默认类型是String

      • 中文在properties对象中会以unicode码的形式保存

    • 常用方法:

      • load(Read in/InputStream in):加载properties文件的K-V值到properties中
      • list(PrintWriter out/PrintStream out):将数据显示到指定设备
      • getProperty(key):根据键获取值
      • setProperty(key,value):设置键值对到Properties对象
      • store(Writer writer/OutputStream out,String comment):将Properties对象中的键对保存到对应的properties文件中,comment是对文件的描述,将写在文件的第一行
    • 实例

      1
      2
      3
      4
      5
      6
      7
      8
      public class Properties_ {
      public static void main(String[] args) throws IOException {
      Properties properties=new Properties();
      properties.load(new FileReader("src\\PAPER\\mysql.properties"));
      properties.list(System.out);
      properties.store(new FileWriter("src\\PAPER\\mysql.properties"),"mysql用户信息");
      }
      }

    DAY20

    ip地址

    • A类:0.0.0.0-127.255.255.255
    • B类:128.0.0.0-191.255.255.255
    • C类:192.0.0.0-223.255.255.255
    • D类:224.0.0.0-239.255.255.255
    • E类:240.0.0.0-247.255.255.255

    端口

    网络通信协议

    TCP字节流编程

    • InetAddress类

      • 常用方法:
        1. 获取本机InetAddress对象:getLocalHost();
        2. 根据指定主机名,获取InetAddress对象:getByName(String)
        3. 根据域名返回InetAddress对象:getByName(String)
        4. 通过InetAddress对象,获取对应的地址:getHostAddress()
        5. 通过InetAddress对象,获取对应的主机名或者域名:getHostName()
    • TCP编程思路

      • 客户端和服务器端各有一个socket对象

      • 客户端

        1. 连接服务端(服务端ip,服务端端口)
        2. 连接上后,生成Socket,通过socket.getOutputStream()获取输出流
        3. 通过获取的输出流输出数据
        4. 关闭流、socket
      • 服务端

        1. 在本机的一个空闲端口监听,等待连接
        2. 没有客户端连接端口时,服务器端会处于阻塞状态
        3. 有客户端连接时,返回一个socket对象,通过socket.getInputStream()获取输入流
        4. 关闭流、socket、serverSocket
      • ==注意==

        • 每次输出完内容之后,必须调用==socket.shutdownOutput()==来作为结束标记,否则内容将无法传输成功
        • 或者也可以在write之后,添加一行==writer.newLine()==(这个方法实际就是添加一个换行符)作为结束标记,不过这要求接收方==必须使用readLine==来接收
        • 在TCP传输的过程中,在客户端,其实也是靠端口来进行连接的,只是该端口号是由TCP协议随机生成
    • StreamUtils_(自己的一个手写工具类)

      • 功能::用于将输入流转化为字节数组,即可以把文件的内容读取到一个字节数组

      • ```java
        public class StreamUtils_ {
        /*
        功能:用于将输入流转化为字节数组
        即可以把文件的内容读取到一个字节数组
        */

        public static byte[] streamToByteArray(InputStream in) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buf=new byte[1024];
        int readLength=0;
        while((readLength=in.read(buf))!=-1){
        byteArrayOutputStream.write(buf,0,readLength);
        }
        byte[] res=byteArrayOutputStream.toByteArray();
        byteArrayOutputStream.close();
        return res;
        }
        }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60





        - **netstat指令**
        - **netstat -an:可以查看当前主机网络情况,包括端口监听情况和网络连接情况**
        - **netstat -an | more:可以分页显示**
        - **netstat -anb:在上述基础上显示端口连接的具体程序(需要管理员权限)**





        ## UDP编程

        - **类DatagramSocket和类DatagramPacket实现了基于UDP协议网络程序**

        - **UDP数据报由DatagramSocket接收和发送,系统不保证数据报能不能送到以及什么时候送到**

        - **数据报封装在DatagramPacket对象中,在数据报中包含了发送端的ip地址和端口号以及接收端的ip地址和端口号**

        - **UDP协议的特点:由于每一份数据报中都包含完整信息,因此不需要建立发送端和接收端的连接**

        - **每一份数据报最大不超过64kb**

        - **基本流程**

        1. **核心类:DatagramSocket,DatagramPacket**

        2. **建立发送端,接收端**

        3. **建立数据报**

        4. **调用Socket的发送,接收方法**

        5. **关闭socket**

        6. **==测试==**

        ```java
        //发送测试端
        public class ServerA {
        public static void main(String[] args) throws IOException {

        //选择6666端口作为packet的接收端口,此时作为发送端,所以没有用到
        DatagramSocket datagramSocket = new DatagramSocket(6666);
        byte[] buf=new byte[1024];
        buf="原来你也玩原神".getBytes();//转成字节数组

        //当packet用于发送时,要指定接收端的IP地址以及端口号
        DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 6665);

        //发送
        datagramSocket.send(datagramPacket);

        //关闭socket
        datagramSocket.close();
        }
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        //接收测试端
        public class ServerB {
        public static void main(String[] args) throws IOException {

        //选择6665端口作为packet的接收端口
        DatagramSocket datagramSocket = new DatagramSocket(6665);
        byte[] buf=new byte[1024*64];

        //创建要用于接收数据报的packet
        DatagramPacket datagramPacket = new DatagramPacket(buf, 0, buf.length);

        //等待接收数据报,如果没有数据报传来,将会一直阻塞在这里
        datagramSocket.receive(datagramPacket);

        //获取接收到的数据包的实际长度
        int length=datagramPacket.getLength();

        //getData():获取真正需要的数据
        String str=new String(datagramPacket.getData(),0,length);
        System.out.println(str);


        //关闭socket
        datagramSocket.close();
        }
        }

    • 技术

    展开全文 >>

    西方哲学史

    2025-02-28

    [TOC]

    什么是哲学

    哲学这个概念起源于希腊语philosophia,由philos和sophia组合而成,意思是”爱智慧“。因为智慧不是”小聪明“,它是指宇宙自然之最深邃最根本的奥秘,标志的是一个至高无上、永恒无限的境界。对此,柏拉图说:智慧太大了,以至于人类只能爱智慧。

    但是哲学家终究不愿意值纠结于“智慧之爱”,如果自然科学可以称为真理性的知识,那么哲学也一定可以,如此,智慧之爱就变成了智慧之学。

    我们还可以从哲学和宗教的起源来对比哲学的本性。科学,以理性为基础,其成果表现为具有一定普遍必然性的知识和实用性技术。宗教依靠与信仰,他们产生于人类精神的“终极关怀”,即对宇宙的真实存在和终究奥秘以及包括人在内的所有存在物的来源、归宿的实在性的关怀或牵挂,因而宗教的对象是超验的理想性的存在,对于这样的对象人类不可能把握,只能信仰。

    哲学居于二者之间,优越与局限并存。一方面,科学解决不了关于人类终极关怀的问题,宗教又因为诉诸信仰,所以缺少理论上的合理性;而另一方面,哲学既起源于人类精神的终极关怀,那他的对象一定是永恒无限的东西,是超验的东西,那实际上是我们的认知即理性难以企及的,这就导致了哲学既没有宗教那样诉诸信仰的方便法门,又不能达到科学知识所具有的的确定性。这就使哲学陷入了十分尴尬的境地:他的问题几乎都无法解决。

    历史上曾有无数学者费劲心思想让哲学成为一门科学,其结果终究是落空,所有哲学家都在一切哲学问题上争论不休,从来没有达到过是一种科学知识应该具备的普遍必然性。

    我们以为,哲学不是科学。我们并不以此为耻,而应该一次为荣。科学是人类认识和改变自然地工具,如果哲学也是科学,那就意味这哲学也成为了认识世界改造世界的工具。倘若如此,文明的发展应该由什么来树立或确定呢?就哲学的地位而言,他应该担负起为人类文明树立和确定目标和发展方向的重任。所以仅仅就此而论,哲学也不应该为科学。

    哲学的发展方式也和科学不一样。

    科学发展到今天,其成果一步步积累从而凝成结晶。对于现代人,我们只要将这个结晶“拿过来用”即可。一个中学生掌握的数学知识也足以超过几百年前的大数学家。但哲学不同,哲学史上每一部著作几乎都是晦涩难懂,即便是当代的哲学大师,也不敢说其思维水平可以比肩柏拉图或亚里士多德。

    为什么?

    因为哲学讨论的都是永远不可能有唯一答案的东西。在这样永恒的问题面前,我们只有各式各样的解答方式,各种解答方式之间没有高下之分,而所谓大哲学家,正是将某一种方式推到了极致,以至于任何后人想重复这条路,走到底,发现路已然断绝。所以,哲学的图画是所有运思之路的集合。

    那么,我们为什么要讨论一个永恒没有答案的问题?我们为什么要追求这种“智慧的痛苦”?

    智慧的痛苦

    伊甸园传说:上帝让亚当和夏娃在伊甸园中生活,伊甸园中有两种最奇特的树,一种生命之树,一种是智慧之树。上帝告诫他们不能吃智慧之树的果子,但在蛇(撒旦)的诱惑下,终于偷吃了智慧之果,最终被赶出了伊甸园。

    寓意:智慧与原罪密切相关。

    伊甸园神话具有非常深刻的象征意义:人不是因为追求智慧才死的,而是说人因为追求智慧才知道自己是有死的。智慧的痛苦来源于此。

    正因为如此,哲学即时最深刻的痛苦,也是至高无上的快乐。因为哲学乃是人生所能通达的最高境界,正是在智慧的痛苦中,人赋与人生以意义,实现自身价值。

    人活着,并希望知道他自己为什么活着,明白人生的意义和价值。然而,人作为一个自然存在物,它生存与其中的自然界并没有什么意义和价值,应该说,所谓意义和价值是人赋与这个世界的。当人们说某个事物有价值的时候,总是在对人类有好处、有意义的基础上使用的。可见,“价值”是与主体的目的、意愿或需要相关的概念。

    从广义说,我们甚至可以把一切与主体相关的东西都看做是“有价值的”,因此,人的存在其实就是一种价值性的存在。由此而论,所有与主体有关的事物就组成了一个价值系统,其中的价值各不相同,却应该有一个“最高价值”,这个最高价值的或者内在的目的就是人类理性的“终极关怀”的对象。

    如果人类不追寻或者创造价值,那么人类自身就仅仅是一个自然存在物,那么“人”也就不再为“人”,这是“人”所无法忍受的。

    希腊哲学的精神

    希腊哲学的主题是获得关于宇宙万物的必然性或规律的知识

    希腊哲学体现了希腊人探索命运的必然性的那种坚韧不拔百折不挠的精神。他们承认命运和必然性,但他们并不就此对命运放任不管,而是义无反顾的逃避或抗争。

    以希腊悲剧《俄狄浦斯》为例。俄狄浦斯尚未出生就被人预言他将杀父娶母。他的父母为了逃避命运而抛弃了他,将他交给了养父母。但是俄狄浦斯并不知道他的养父母并不是他的亲生父母,为了逃避杀父娶母的命运,他离家出走。最后的结果……

    正是在俄狄浦斯逃避命运的同时,他一步一步实现了自己的命运。

    《俄狄浦斯》告诉我们,在希腊人看来,命运确实是一切的主宰,但是人们并不像命运低头,而是努力否认这是自己的命运。所以可以说,希腊精神是一种积极的悲剧精神。

    希腊哲学的特征:

    1.朴素直观性。希腊哲学是西方哲学的起源,而西方哲学的特征之一就是理论思维或者说思辨行为,这与朴素直观是对立的,有什么会有这样的矛盾呢?——在希腊人开始思考的时候,他们并不是向我们这样随时就有抽象概念可以用,那时候还没有形成抽象的概念,在他们与自然之间还没有任何观念的“中介”,因而他们只能借助于感性的表象来表达抽象的概念,所以希腊哲学始终披着感性的外壳。

    2,。经验主义。希腊哲学是建立在经验观察的基础上的。这里的经验主义并不是与理性主义相对的概念。在希腊人的眼中,哲学的目的是为了“拯救现象”,当时的人们还没有分清“现象”和‘本质“的区别,对他们来说,世界只有一个,那就是”自然“,而现象就是对人所显现出来的一切事物,现象之外无他。因此希腊人的一切理论都是为了解释现象而说明现象的,即用“经验“去说明”经验“。

    希腊哲学的第一个概念:本原

    当希腊人开始用理性眼光看待世界时,他们看到的是一个四季交替,草木枯荣的自然,他们产生疑问:为什么存在着的东西永远存在,而不是归于虚无呢?那时人们的思想还很朴素,因为万物都是有生必有死的,因此存在着的东西必然会归于虚无,万物构成了自然,因此自然也会归于虚无。但是自然却始终在周而复始的循环着,因此,这其中一定有什么时永恒不变的东西——此时,希腊哲学的第一个概念:本原(arche),就诞生了。

    最初的哲学家从朴素观察的角度出发,认为本原是无定形的,是永恒变化着的。因此他们中有人提出了本原是水,本原是气这样的概念。

    这里要注意,哲学家所说的水,气并不指我们真的喝的水,呼吸的气。前面说过,那时的人们还没有合适的抽象概念去使用,因此他们用现实生活中感性的事物去描述理性的概念。因此他们所说的水实际是一种隐含着流动,可变,可塑,赋予生命活力的一个概念。

    自然哲学

    早期的希腊哲学家们追求的本原是一种在时间上为先的东西。万物从一个东西即本原生出来,生死变化后又回归本原,强调万物始终在运动,而本原无定形。

    赫拉克利特是这一派的代表。他以火为宇宙的本原,或许说他以火来体现本原的流动性更为恰当,因为在希腊人的水、火、土、气四大元素中,只有火是最特殊的,因为它本身不是什么,而是其他元素的燃烧,所以火这个概念能体现出运动变化的永恒性。“一切皆流,无物常住”。

    本体论的转向

    巴门尼德的思想:存在(to on/to being)是一(静止的)。我们认识的有两条路,一条是真理(具有普遍自然性)之路,一条是意见(因人而异)之路。真理之路以“存在”为对象,而意见之路以“非存在”为对象。非存在不是不存在,而是既存在有不存在的自然之物。只有存在是可以思想和诉说的,非存在既不能被思想也不能被诉说。因此,知识只有一条路,那就是存在之路,作为思想和作为存在是一回事。我们一般把这个命题叫做思想和存在的同一性。

    实际上,巴门尼德的思想,就把哲学从追求时间上的第一性,扭转为追求逻辑上的第一性。巴门尼德关于两条路的划分,相当于我们所说的本质与现象,只不过当时还没有那么明确的区分这两个概念。

    出现这种扭转的原因主要是当时哲学家对时间上的第一性为何物争论不休所导致的,这样一来知识就没有一个立足点。显然知识需要一个立足点以防止探究出现无穷后退的情况——实际上这也成为了古典哲学和形而上学的一个基本原则。现代哲学则不再认同,因为我们不再像一往认为哲学和科学尤其是科学知识看做是绝对的普遍必然的了。

    • 哲学

    展开全文 >>

    &laquo; Prev12
    © 2025 Michael
    Hexo Theme Yilia by Litten
    • 所有文章
    • 关于我

    tag:

    • JavaScript
    • 随笔
    • 哲学
    • 项目文档
    • 崩三
    • 博客建设
    • 技术
    • 算法
    • 分布式锁
    • coze
    • laf
    • 锁
    • 并发
    • HashMap
    • 线程池

      缺失模块。
      1、请确保node版本大于6.2
      2、在博客根目录(注意不是yilia根目录)执行以下命令:
      npm i hexo-generator-json-content --save

      3、在根目录_config.yml里添加配置:

        jsonContent:
          meta: false
          pages: false
          posts:
            title: true
            date: true
            path: true
            text: false
            raw: false
            content: false
            slug: false
            updated: false
            comments: false
            link: false
            permalink: false
            excerpt: false
            categories: false
            tags: true
      

    去把这个不完美的故事,变成你所期望的样子。