2024年1月

在Langchain-Chatchat的上传文档接口(
upload_docs
)中有个自定义的docs字段,用到了Document类。根据发现指的是
from langchain.docstore.document import Document
。本文简要对Document类进行介绍。

1.上传文档接口
upload_docs

def upload_docs(
        file: List[UploadFile] = File(..., description="上传文件,支持多文件"),
        knowledge_base_name: str = Form(..., description="知识库名称", examples=["samples"]),
        override: bool = Form(False, description="覆盖已有文件"),
        to_vector_store: bool = Form(True, description="上传文件后是否进行向量化"),
        chunk_size: int = Form(CHUNK_SIZE, description="知识库中单段文本最大长度"),
        chunk_overlap: int = Form(OVERLAP_SIZE, description="知识库中相邻文本重合长度"),
        zh_title_enhance: bool = Form(ZH_TITLE_ENHANCE, description="是否开启中文标题加强"),
        docs: Json = Form({}, description="自定义的docs,需要转为json字符串",
                          examples=[{"test.txt": [Document(page_content="custom doc")]}]),
        not_refresh_vs_cache: bool = Form(False, description="暂不保存向量库(用于FAISS)"),
) -> BaseResponse:

这里的docs是Json数据类型,本质上可以理解为dict数据类型。
pydantic
中的
Json
类用于表示包含 JSON 数据的字段。它可以接受任何合法的 JSON 数据,然后在验证时将其解析为 Python 字典。以下是一个使用
Json
类的简单示例:

from typing import List
from pydantic import BaseModel, Json

class MyModel(BaseModel):
    json_data: Json

# 实例化 MyModel 类
data = {'key1''value1''key2': [123]}
my_model_instance = MyModel(json_data=data)

# 输出实例
print(my_model_instance)

在这个例子中,定义了一个
MyModel
类,其中有一个字段
json_data
,它的类型是
Json
。然后创建一个包含 JSON 数据的字典
data
,并用它实例化
MyModel
类。在输出实例时,
Json
类会将传入的 JSON 数据解析为 Python 字典。请注意,
Json
类并不关心具体的 JSON 数据结构,它接受任何合法的 JSON 数据。

2.
Document
类源码

该类的引用包为
from langchain.docstore.document import Document
。简单理解就是包括文本内容(page_content)、元数据(metadata)和类型(type)的类。源码如下所示:

class Document(Serializable):
    """Class for storing a piece of text and associated metadata."""

    page_content: str
    """String text."""
    metadata: dict = Field(default_factory=dict)
    """Arbitrary metadata about the page content (e.g., source, relationships to other
        documents, etc.).
    "
""
    type: Literal["Document"] = "Document"

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """Return whether this class is serializable."""
        return True

    @classmethod
    def get_lc_namespace(cls) -> List[str]:
        """Get the namespace of the langchain object."""
        return ["langchain""schema""document"]

3.
Document
类例子

代码定义了一个
Document
类,该类继承自
Serializable
,使用了 Python 的类型提示和注解。在
Document
类中,有
page_content

metadata

type
三个属性,并定义了一些方法。

下面实例化
Document
类,并输出实例的内容:

from typing import List, Literal
from langchain_core.load.serializable import Serializable
from pydantic import Field

class Document(Serializable):
    page_content: str
    metadata: dict = Field(default_factory=dict)
    type: Literal["Document"] = "Document"

    @classmethod
    def is_lc_serializable(cls) -> bool:
        return True

    @classmethod
    def get_lc_namespace(cls) -> List[str]:
        return ["langchain""schema""document"]

# 实例化 Document 类
custom_doc = Document(page_content="custom doc")

# 输出实例
print(custom_doc)

输出结果,如下所示:

page_content='custom doc' metadata=FieldInfo(annotation=NoneType, required=False, default_factory=dict)

在这个例子中,创建了一个名为
custom_doc

Document
类的实例,并通过
print(custom_doc)
将其输出。确保环境中已经安装了
pydantic

langchain_core
模块,可以使用
pip install pydantic langchain_core -i https://pypi.tuna.tsinghua.edu.cn/simple
进行安装。

参考文献:
[1] 文档加载器:https://python.langchain.com/docs/integrations/document_loaders/copypaste
[2] https://docs.pydantic.dev/latest/concepts/fields/
[3] https://github.com/chatchat-space/Langchain-Chatchat/blob/master/server/api.py





NLP工程化

1.本公众号以对话系统为中心,专注于Python/C++/CUDA、ML/DL/RL和NLP/KG/DS/LLM领域的技术分享。
2.本公众号Roadmap可查看飞书文档:https://z0yrmerhgi8.feishu.cn/wiki/Zpewwe2T2iCQfwkSyMOcgwdInhf

NLP工程化(公众号)

NLP工程化(星球号)

肉夹馍(
https://github.com/inversionhourglass/Rougamo
)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应用启动的初始化时间让服务更快可用,同时还能对静态方法进行AOP操作。

上一篇文章至此共发布两个版本,本篇文章中将依次介绍2.1和2.2版本中新增的功能。

2.1

Pattern增强-支持Attribute匹配

在2.0版本推出了
表达式匹配
功能,支持通过字符串表达式匹配方法。2.0版本共支持
method

getter

setter

property

execution

regex
六种匹配规则,之后在github上收到社区朋友的issue反馈,希望能够直接筛选出应用了某个Attribute的方法,因此在2.1版本中新增匹配规则
attr

attr
的基本格式为
attr(POS TYPE)

  • TYPE
    ,表示我们匹配的Attribute类型,其格式与其他匹配规则中类型的格式相同
  • POS
    ,表示应用Attribute的位置,我们知道Attribute可以应用于程序集、类、方法、属性、字段、参数、返回值等,
    POS
    支持以下几种位置
    • type
      ,表示Attribute应用于类型上
    • exec
      ,表示Attribute应用于方法或属性或属性getter或属性setter上(后续统一简称方法)
    • para x
      ,表示Attribute应用于方法参数上,其中
      x
      表示参数的位置,
      0
      表示第一个参数,
      x

      *
      时表示任意参数
    • ret
      ,表示Attribute应用于方法返回值上

下面展示几个简单的表达式示例:

// 1. 匹配类型上应用了类名为ObsoleteAttribute(任意命名空间)的类型,选取该类型下的所有方法
"attr(type ObsoleteAttribute)"

// 2. 匹配应用了Test.XAttribute(限定Test命名空间)的方法
"attr(exec Test.XAttribute)"

// 3. 匹配方法第3个参数(参数索引从0开始)上应用了Restrict命名空间下类名以Attribute结尾的任意Attribute的方法
"attr(para 2 Restrict.*Attribute)"

// 4. 匹配方法返回值上应用了任意Attribute的方法
"attr(ret *)"

// 5. 联合其他匹配规则完成更复杂的匹配
"attr(type ObsoleteAttribute) && method(public * *(..))"

我们知道,在应用Attribute时对于同一个Attribute类型,我们还可以设置其构造参数和属性,不同的构造参数和属性所表达的含义可能完全不同,我们可能会有希望能够更详细的匹配其参数值和属性值的需求。但遗憾的是,肉夹馍目前没有计划在这个目标上继续细化,表达式匹配相对于最早的
AccessFlags
提供了更灵活细化的匹配方式,但同样增加了学习成本和复杂度,目前是希望在灵活度和复杂度上取一个平衡,不希望表达式演变到晦涩难懂,也同样希望能满足基本的需求。当然,另一方面来说,相对简单的表达式也为后续的维护省下不少精力。对于确实有需要对参数值和属性值进行细分处理的,目前建议在
OnEntry
等方法中通过
MethodContext.Method
获取更多方法相关信息进行处理。

参数值实时更新

这同样是github上社区的朋友通过issue反馈的,方法包含
out
参数值,该参数在
OnSuccess
中无法从
MethodContext.Arguments
中获取到。

在此前的版本中,
MethodContext.Arguments
实际只有在执行
OnEntry
前会进行一次初始化,之后并不会对其进行更新,也就是说实际上不仅
out
的参数值没有更新,其他参数同样没有更新。可能有的朋友之前也从
MethodContext.Arguments
中获取过参数值并且发现是最新的,那大概是因为你这个参数是引用类型,你只是修改了应用对象的内部值,而不是直接为这个参数重新赋值。

2.1版本除了在执行
OnEntry
之前对
MethodConetext.Arguments
进行初始化之外,还会在执行
OnSuccess

OnException
之前也进行一次更新,保证各阶段获取到的
Arguments
都是最新的。该功能在升级到2.1+版本后自动生效。

这样的更新操作势必要额外产生一些代码的,所以
Feature
同时新增枚举项
FreshArgs
,如果你并不需要这个刷新参数值的功能,可以通过排除该枚举值来减少代码的织入,对
Feature
不理解的朋友可以回顾往期的
部分织入
介绍。

2.2

2.2版本的主要内容是性能优化,同样是来自github社区朋友的issue反馈,使用肉夹馍后GC相应的也增加了。在2.2版本从引入结构体、延迟初始化(没用到则不初始化)、减少装箱等各方面进行优化,下面介绍的是我们使用上的一些变化,内部的细节优化这里不做阐述。

结构体

结构体大家都知道,但是使用可能并不是很频繁,类对象是分配在堆上的,最后由GC回收,而结构体是保存在栈上的,在调用栈结束后直接释放不走GC,所以一般结构体的效率是优于类的。关于类和结构体有一个很有趣的事情分享一下,我们知道C#项目在编译时可以选择Debug模式和Release模式,Debug模式下包含了很多调试信息,并且不会对我们的代码进行优化,而Release模式则会精简很多,无意义的分支跳转甚至都会被优化掉,不仅如此,Release模式下还使用结构体代替类对异步方法进行了优化。每个异步方法都会生成一个实现了
IAsyncStateMachine
的类型,在Debug模式下生成的类型时一个class,而Release模式则是struct,感兴趣的同学可以反编译看看,这里不再展开了。

那么回归主题,既然结构体这么好,那是不是无脑将class改为struct就好了呢,答案显而易见,不然就不会有class了。结构体是值传递,一个结构体在方法间进行传递时实际是复制了一个副本进行传递,那么最直观的表现就是你将一个结构体传入方法M,方法M内部修改了结构体的字段,但是在方法M执行完毕后,你外部的结构体字段并没有变化。再回到肉夹馍中,肉夹馍定义的类型主要包含两个,一个是继承了
MoAttribute
定义AOP内容的类型,另一个是
MethodContext
,其中
MethodContext
用于在多个
MoAttribute
子类之间传递,所以无法改造为结构体,那么结构体的优化就落在了
MoAttribute
上了。

MoAttribute
继承自
Attribute
,而
Attribute
是一个class,
MoAttribute
是无法直接定义为struct的。如果对肉夹馍已经比较熟悉的朋友可能知道,
MoAttribute
除了继承自
Attribute
还实现了
IMo
接口,同时肉夹馍除了可以
MoAttribute
直接应用于类或方法上的方式之外,还有一种方式是让被织入类型实现空接口
IRougamo<T>
,而
IRougamo<T>
的泛型约束是
where T : IMo, new()
,所以最本质上还是
IMo
这个接口。那么下面的例子展示了如何使用结构体进行优化:

// 1. 定义结构体实现IMo接口
struct ValueMo : IMo
{
    // 实现接口,定义AOP操作
}

// 2.1. 通过RougamoAttribute指定结构体类型
[Rougamo(typeof(ValueMo))]
class Cls
{
    // 如果项目使用C#11及以上语法,可以直接使用下面这种泛型Attribute
    [Rougamo<ValueMo>]
    public void M() { }
}

// 2.2. 同样可以通过IRougamo<T>配合使用
class Clss : IRougamo<ValueMo>
{
}

遗弃部分数据

2.0版本中介绍了部分织入的功能,我们可以通过选择自己需要的功能来减少织入的IL代码量。现在,我们还可以选择丢掉部分我们不需要的数据。
MethodContext
中保存了方法上下文信息,同样的,也不是所有信息大家都会需要。在2.2版本中,将部分相对有性能开销的部分数据设置为可选,在实现
IMo
接口或继承
MoAttribute
时可以通过
MethodContextOmits
属性进行设置,该属性类型为枚举,包含以下枚举项:

  • None
    ,不会遗弃任何数据,默认值
  • Mos
    ,会遗弃
    MethodContext.Mos
    属性值,该属性包含了当前方法织入的所有
    IMo
    对象,如果你使用了结构体,那么在存储到该属性中时将包含一个装箱操作,同时也会增加一个
    IReadOnlyList<IMo>
    对象。
    需要注意的是,使用
    ExMoAttribute
    时请务必不要指定该值
  • Arguments
    ,会遗弃
    MethodContext.Arguments
    属性值,该属性存储了调用放方法的所有入参值,如果入参中包含值类型,那么将产生一个装箱操作,同时也会增加一个
    object[]
    对象。
    需要注意的是,如果Features属性包含了
    Args

    RewriteArgs

    FreshArgs
    中的任意一个,那么就表示需要使用到参数,此时指定该值将无效,参数依旧会被存储下来
  • All
    ,会遗弃所有数据,当前版本就是Mos和Arguments,后续如果有增加枚举项,也将包含在内

使用优化-静默内置属性

依旧是github社区朋友反馈的issue,在使用肉夹馍封装中间件后,其他人在使用中间件定义的Attribute时IDE会提示出
MoAttribute
内置的属性,情况大致如下图:

MoAttribute
内置的几个属性(
Features

Flags

Order

Pattern
)都提示出来了,虽然说这几个属性在应用Attribute时进行设置是肉夹馍提供的功能之一,但是如果咱们的中间件已经为某些属性设置了默认值,同时不希望开发者在应用Attribute时再去修改。比如上图中,如果
RetryAttribute
在定义时已经重写了
Feature
属性,限定了要启用的功能,就不希望使用
RetryAttribute
的人再修改该值,最好的方法就是让IDE都不提示出这个属性,但此时又会发现这些属性根本无法屏蔽。虽然可以人为提醒开发者不要设置该属性,但这在使用上非常不友好。

这个现象主要原因是
MoAttribute
将属性的getter和setter都设置为了public,所有继承自
MoAttribute
的类都无法屏蔽public的setter,所以在2.2版本中,
MoAttribute
的所有属性的setter都修改为private,是否将属性公开由继承
MoAttribute
的类型决定,可以通过
new
关键字覆盖原属性然后将setter改为public:

public class OrderableAttribute : MoAttribute
{
    // 通过new关键字覆盖了MoAttribute的Order属性,同时将setter设置为public
    // virtual关键字是可选的,如果你确定没有类型会继承自TestAttribute并重写Order属性,那么可以去掉virtual
    // 这里为Order设置了一个默认值,当然也可以不设置
    public new virtual double Order { get; set; } = 10;
}

注意:
这个功能会影响到现在在应用Attribute时设置MoAttribute内置属性的开发者,更新到该版本之后将会产生报错,请及时按需按上面的方式重新将需要的属性进行公开。由于不必要的属性提示确实会给中间件开发者带来困扰,且该功能也无法平滑过度,所以只能一刀切,对受到影响的开发者深表抱歉。

大家好,我是Edison。

上一篇:
初识ElasticSearch

ElasticSearch的安装方式

ElasticSearch可以有多种安装方式,比如直接下载安装到宿主机进行运行,也可以通过docker的方式运行,完全取决我们的用途。这里,我们只是为了学习和练习,通过docker方式运行即可。

Docker安装的前置条件

这里为了成功通过docker安装ElasticSearch+Kibana,我们需要准备一下docker和docker-compose(如果你的实验机器没有安装的话):

安装docker:

wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O
/etc/yum.repos.d/docker-ce.repoyum -y installdocker
systemctl enable docker
&&systemctl start docker
docker
--version

安装docker-compose:

wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O
/etc/yum.repos.d/docker-ce.repoyum -y installdocker
systemctl enable docker
&&systemctl start docker
docker
--version

这里我们通过直接运行的方式(非Docker)运行Logstash,因此这里我们安装一下JDK:

yum install java-1.8.0-openjdk
java
-version

修改系统参数(如果你的机器配置较低的话,比如只有2个G内存):

# 修改配置sudo vim /etc/sysctl.conf
vm.max_map_count
= 655360# 让配置生效sudo sysctl -p

Docker安装ElasticSearch+Kibana

这里我们以ES 7.1.0版本为例,虽然它是几年前的版本了,但这里我们只是学习完全够用了。

下面是我们准备好的docker-compose.yml文件:

version: '2.2'services:
kibana:
image: docker.elastic.co
/kibana/kibana:7.1.0container_name: kibana7
environment:
- I18N_LOCALE=en-US- XPACK_GRAPH_ENABLED=true - TIMELION_ENABLED=true - XPACK_MONITORING_COLLECTION_ENABLED="true"ports:- "5601:5601"networks:-es7net

elasticsearch:
image: docker.elastic.co
/elasticsearch/elasticsearch:7.1.0container_name: es7_01
environment:
- cluster.name=edisontalk- node.name=es7_01- bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - discovery.seed_hosts=es7_01,es7_02- cluster.initial_master_nodes=es7_01,es7_02
ulimits:
memlock:
soft:
-1hard:-1volumes:- es7data1:/usr/share/elasticsearch/data
ports:
- 9200:9200networks:-es7net

elasticsearch2:
image: docker.elastic.co
/elasticsearch/elasticsearch:7.1.0container_name: es7_02
environment:
- cluster.name=edisontalk- node.name=es7_02- bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - discovery.seed_hosts=es7_01,es7_02- cluster.initial_master_nodes=es7_01,es7_02
ulimits:
memlock:
soft:
-1hard:-1volumes:- es7data2:/usr/share/elasticsearch/data
networks:
-es7net

volumes:
es7data1:
driver: local
es7data2:
driver: local

networks:
es7net:
driver: bridge

在这个文件中,定义了两个ES实例 和 一个Kibana实例,两个ES实例组成了一个小集群,Kibana则是可视化查询工具。

这里需要注意的是参数是“
ES_JAVA_OPTS
”,建议将Xmx 和 Xms 设置成一样的,如这里的512M。当然,如果你的机器配置较低,建议将这两个值调的低一些,比如256M。但是,
Xmx的值不要超过机器内存的50%

运行docker-compose文件执行运行安装:

docker-compose up -d

运行后等待1分钟,通过浏览器URL访问ES实例:

然后通过浏览器URL访问Kibana实例:

至此,你的ES+Kibana初步安装好了。

安装Logstash并导入测试数据集

这里我们再安装一个logstash,选择下载一个logstash-7.1.0安装到宿主机上的/usr/local/elastic/elk7目录下。

从这里下载logstash 7.1.0,与我们刚刚安装的ES实例保持一致:https://www.elastic.co/cn/downloads/past-releases/logstash-7-1-0

然后将其copy到你的服务器上,并进行解压:

然后准备一个logstash.conf配置文件,并copy到logstash-7.1.0/bin目录下:

input {file{
path
=> "/usr/local/elastic/elk7/logstash-7.1.0/bin/movies.csv"start_position=> "beginning"sincedb_path=> "/dev/null"}
}
filter {
csv {
separator
=> ","columns=> ["id","content","genre"]
}

mutate {
split => { "genre" => "|"}
remove_field
=> ["path", "host","@timestamp","message"]
}

mutate {
split => ["content", "("]
add_field
=> { "title" => "%{[content][0]}"}
add_field
=> { "year" => "%{[content][1]}"}
}

mutate {
convert
=>{"year" => "integer"}
strip
=> ["title"]
remove_field
=> ["path", "host","@timestamp","message","content"]
}

}
output {
elasticsearch {
hosts
=> "http://localhost:9200"index=> "movies"document_id=> "%{id}"}
stdout {}
}

这个配置文件定义了我们需要采集的数据的路径,为了实现测试数据集的导入,我们也需要下载一个测试数据集,这里选择的是MovieLens的开放数据集,选择其small类型的movies测试数据,将这个movices.csv数据copy到logstash-7.1.0/bin目录下即可。

数据集地址:http://files.grouplens.org/datasets/movielens/ml-latest-small.zip

这个movie.csv中包含了一些电影的id和标题,以及该电影的类别,数据格式如下:

movieId, title, genres

最后,开始运行logstash:

sudo ./logstash -f logstash.conf

稍后,我们就可以看到一条条数据被传到了ElasticSearch中:

NOTE:
logstash的执行比较慢,需要耐心等待一下,取决于你的测试服务器的配置了。

数据插入完成后,我们可以到Kibana的Dev Tools中验证一下:

可以看到,共计9743个movie数据被传到了ElasticSearch中。

安装Cerebro可视化管理界面

Cerebro是一个常用的开源可视化管理工具,它可以对ElasticSearch进行集群监控和管理、集群配置修改、索引分片管理。

要安装Cerebro,只需要修改一下我们的docker-compose.yml,添加一个service即可:

version: '2.2'services:
cerebro:
image: lmenezes
/cerebro:0.8.3container_name: cerebro
ports:
- "9000:9000"command:- -Dhosts.0.host=http://elasticsearch:9200 networks:-es7net

......

然后重新执行以下命令即可安装:

docker-compose up -d

安装好后访问9000端口即可看到:

小结

本篇,我们了解了ElasticSearch的安装方式,并通过docker-compose的方式快速搭建了一个两个ES节点的ElasitcSearch + Kibana服务。然后,通过手动安装Logstash并导入测试数据集,为后续学习ElasticSearch基本概念和查询练习奠定了基础。

下一篇,我们就正式开始ElasticSearch的入门,先从一些常见的基本概念走起!

参考资料

极客时间,阮一鸣,《
ElasticSearch核心技术与实战

写在开头

在上一篇文章:
一文搞清楚Java中的包、类、接口
中我们讲了Java中的包、类和接口,今天继续将剩下的方法、常量、变量以及参数梳理完。

Java中的变量与常量

在JVM的运转中,承载的是数据,而数据的一种变现形式就是“量”,量分为:
常量与变量
,我们在数学和物理学中已经接触过变量的概念了,在Java中的变量就是在程序运行过程中可以改变其值的量。

常量

与变量相对应的是常量,所谓常量,即在作用域内保持不变的值,一般用final关键字进行修饰,通常分为全局常量、类内常量、局部常量。
【程序用例】

public class Person {
    //全局常量
    public static final String SEX = "男";
    //类内常量
    private static final String BIRTHPLACE ="安徽";

    public void method1(String str){
        //局部常量-方法常量
        final String LANGUAGE = "汉语";
    }
    
    public void method2(final int age){
        //局部常量-参数常量,不可改,这段代码会编译报错
        age = 20;
    }
}

变量

变量同样分为局部变量、成员变量和静态变量
【程序用例】

public class Person {
    //成员变量
    //在类内部但在方法体外声明的变量称为成员变量,或者实例变量,或者字段。
    //之所以称为实例变量,是因为该变量只能通过类的实例(对象)来访问。
    int data1 = 3;
    //静态变量
    //通过 static 关键字声明的变量被称为静态变量(类变量),
    //它可以直接被类访问
    static int data2 = 4;

    public int sum(){
        //局部变量
        //在方法体内声明的变量被称为局部变量,该变量只能在该方法内使用,
        //类中的其他方法并不知道该变量。
        int a = 1;
        int b = 2;
        return a+b;
    }

    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person.data1 == person.sum());
        System.out.println(Person.data2 == person.sum());
    }

}

【变量使用时注意事项】

局部变量
局部变量声明在方法、构造方法或者语句块中。
局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,将会被销毁。
访问修饰符不能用于局部变量。
局部变量只在声明它的方法、构造方法或者语句块中可见。
局部变量是在栈上分配的。
局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。

成员变量
成员变量声明在一个类中,但在方法、构造方法和语句块之外。 当一个对象被实例化之后,每个成员变量的值就跟着确定。
成员变量在对象创建的时候创建,在对象被销毁的时候销毁。
成员变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息。 成员变量可以声明在使用前或者使用后。
访问修饰符可以修饰成员变量。
成员变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把成员变量设为私有。通过使用访问修饰符可以使成员变量对子类可见;成员变量具有默认值。数值型变量的默认值是
0,布尔型变量的默认值是 false,引用类型变量的默认值是 null。变量的值可以在声明时指定,也可以在构造方法中指定。

静态变量
静态变量在类中以 static 关键字声明,须在方法、构造方法和语句块之外。
无论一个类创建了多少个对象,类只拥有静态变量的一份拷贝。 静态变量除了被声明为常量外很少使用。 静态变量储存在静态存储区。
静态变量在程序开始时创建,在程序结束时销毁。 与成员变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为 public 类型。
静态变量的默认值和实例变量相似。 静态变量还可以在静态语句块中初始化

Java中的方法

何为方法?我的理解是:方法是Java中对逻辑行为的一种程序实现,通过方法的调用去实现我们想让对象所拥有的行为,比如一个对象的增删改查,只需要创建相应的方法即可!

方法的声明

在方法声明中,最重要的就是方法名(采用小驼峰命名法)与参数,这两者构成的方法的签名,是方法重载的判断依旧,除此之外,在声明方法的时候,还会设置访问权限、是否静态、返回值类型等。结构如下图:

实例方法

在类中声明,无需static、abstract等关键字修饰,创建类对象,通过对象的引用所调用的方法为实例方法,类中最为常见的一种方法类型。上代码!

public class Test {

    public static void main(String[] args) {
        Test sum = new Test();
        System.out.println(sum.add(1,2));
    }
    //实例方法,求两数之和
    public int add(int a, int b) {
        return a + b;
    }
}

静态方法

若方法的声明中加上了static关键字,静态方法是属于类的,而不是属于类创建的对象或实例的,故在调用时无需通过对象实例。

public class Test {

    public static void main(String[] args) {
        System.out.println(sub(2,1));
    }
    public static int sub(int a,int b){
        return a - b;
    }
}

【注意事项】

1、静态方法只能访问静态成员
2、调用静态方法时可能对象并没有被初始化,此时非静态变量还未初始化
3、非静态方法的调用和非静态成员变量的访问要先创建对象

抽象方法

当一个方法被关键字abstract修饰后,且没有方法体,那它就成为了一个抽象方法,抽象方法往往出现在抽象类和接口中。

/**
 * @author JavaBUild
 */
abstract class AbstractDemo {
    abstract void display();
}

【注意事项】

1、抽象类中不一定必须要有抽象方法,但是有抽象方法的类必须是抽象类
2、若一个类继承了抽象类,则必须实现抽象类中的抽象方法!
3、抽象类不能被实例化
4、抽象方法不能被声明为静态
5、抽象方法不能用 private 修饰
6、抽象方法不能用 final 修饰

native方法

我们在讲Java的Object类时,已经接触过Java中的native方法了,它们用native关键字修饰,用C/C++语言实现,声明时native关键字必须在返回值前,不能用abstract修饰、没有方法体,甚至没有大括号!

public final native Class<?> getClass();
public native int hashCode();
protected native Object clone() throws CloneNotSupportedException;

这类方法存在的意义是作为Java标准类库的一种扩展,或者兼容其他语言写好的且更好用的类库,或者提升程序执行效率。

构造方法

除了以上那些方法外,Java中还有一种特殊的方法,名为构造方法,用以在对象初始化工作,Java 有两种类型的构造方法:无参构造方法和有参构造方法。
当我们在一个类中不写任何构造方法时,编译器会默认一个无参构造方法,只不过这个方法是缺省的,显示不可见。
构造方法的声明规范

构造方法的名字必须和类名一样;
构造方法没有返回类型,包括 void;
构造方法不能是抽象的(abstract)、静态的(static)、最终的(final)、同步的(synchronized)。

代码示例

public class Test {
   private String name;
   private int age;
    //默认构造方法,无参构造
    public Test() {
    }
    //有参构造方法
    public Test(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public static void main(String[] args) {
        //通过无参构造创建的对象,会有默认值
        Test test1 = new Test();
        //通过有参构造方法构造的对象,在初始化时便进行了赋值
        Test test2 = new Test("JavaBuild", 18);
        System.out.println(test1.name+":"+test1.age);
        System.out.println(test2.name+":"+test2.age);
    }
}

输出:

null:0
JavaBuild:18

Java中参数

上面构造方法中提及了有参构造和无参构造,那么现在就好好聊一聊这个参数,参数应用于方法之中,作为方法运转的条件和参考,当然这仅限于有参数的方法,方法中的参数分形参和实参,形参是在方法的定义阶段,而实参则是在方法的调用阶段,参数个数不限,类型不限,可以是基本数据类型,也可以是引用类型。

可变参数
在jdk5中为了解决反射机制和printf中的问题引入了可变参数概念,顾名思义就是参数个数不确定的场景。定义方式:"参数类型...";

代码示例

public class Test {

    public static void main(String[] args) {
      countLength("a");
      countLength("a","b");
      countLength("a","b","c");
    }
    public static void countLength(String... args){
        System.out.println(args.length);
    }
}

【注意事项】

1、每个方法最多只有一个可变参数
2、可变参数必须是方法的最后一个参数
3、无法仅通过改变可变参数的类型,来重载方法
4、通过对 class 文件反编译可以发现,可变参数被编译器处理成了数组

热点随笔:

·
35岁程序员被裁员,这半年他的故事
(
路泽宇
)
·
博客园淘宝店开张:园子的第一款简陋鼠标垫,是否是您值得拥有的周边
(
博客园团队
)
·
如何使用.NET在2.2秒内处理10亿行数据(1brc挑战)
(
InCerry
)
·
前任开发在代码里下毒了,支付下单居然没加幂等
(
程序员老猫
)
·
Spring解决泛型擦除的思路不错,现在它是我的了。
(
why技术
)
·
大龄码农的转型:总结免费升讯威在线客服系统的推广经验与成绩
(
升讯威在线客服系统
)
·
这应该是java最好用的orm之一了
(
薛家明
)
·
记一次 .NET某MES自动化桌面程序 卡死分析
(
一线码农
)
·
.NET使用QuestPDF高效地生成PDF文档
(
追逐时光者
)
·
flex布局之美,以后就靠它来布局了
(
xiezhr
)
·
日常Bug排查-集群逐步失去响应
(
无毁的湖光-Al
)
·
5分钟教会你如何在生产环境debug代码
(
欧阳码农
)

热点新闻:

·
可怜的压铸机:因太快被中国掌握,痛失“工业明珠”资格
·
汉字光标准化和数字化就花了一百多年,它能在 AI 时代存续吗
·
“摇一摇”广告被判违法?虽然只赔1元,但意义很大
·
摆脱「Android套壳」, 华为更想做苹果
·
人气爆火美女网红竟是AI?视频照片几乎无破绽,亿万富豪都想和她约会
·
贝塔伏特准备量产有 50 年寿命的核电池
·
三个旋翼失效也不坠毁,我国科研团队成功破解无人机失控难题
·
“联合研发”是不是自研?
·
马斯克机器人炫技引千万网友围观!
·
网约车订单被层层转卖,多平台抽成近五成!谁在触碰30%隐形红线?
·
开网约车的中年人,职业“堡垒”开始晃动
·
为什么洗衣机会每天自动上传3.6GB数据?