Java笔记_6
面向对象编程(OOP
)
Java
中对象变量只是包含了一个引用, 没有实际包含一个变量
1 | Date startTime = new Date(); |
- 所有的
Java
对象都存储在堆中, 当一个对象包含了另一个对象的时候, 实际上只是包含了另一个对象在堆中的指针 - 所以如果需要得到一个对象的副本, 不能简单的用
=
, 而是应该用clone()
方法
更改器方法和访问器方法
更改器方法: 调用方法以后, 对应实例的状态会改变
访问器方法: 调用方法以后, 只访问对象, 不会修改它. 比如
get()
- 访问器方法不要返回可变对象的引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14class E {
private Date hireDay;
public Date getHireDay() {
return hireDay;
}
}
// 这里的hireDay就是一个Date类的对象引用, 而Date类本身存在更改器方法setTime
// 所以此时hireDay是可变的, 破坏了封装性
// 如果需要返回一个可变对象引用, 需要先clone
class R {
public Date getHireDay() {
return (Date) hireDay.clone();
}
}
CPP
中带有const
后缀的方法是访问器方法, 没有const
后缀的方法是更改器方法Java
中没有这种明显的标识
- 构造器没有返回值, 总是和
new
一起使用 - 所有的方法中都不要使用和实例字段同名的方法, 可以同名, 但是最好不要出现, 除了后面讲到的
record
- 实例字段不要设置成
public
, 这样会破坏封装, 要保证数据私有 - 在构造类的实例时, 推荐使用
var
来声明这个对象的类型, 这样就不用重复写类了 - 不要对数值类型写
var
,var
只能用于局部字段, 对于参数和实例字段不能使用var
null
- 对象变量包含一个引用, 或者是
null
- 如果对
null
值变量调用方法会产生NullPointException
- 基本数据类型不会是
null
, 需要注意String
类型的null
, 可以检测到以后将其转换为另一个值1
2
3
4
5
6
7
8
9
10if (n == null) {
name = "unknown";
} else {
name = n;
}
// 也有更简单的方法
name = Objects.requireNonNullElse(n, "unknown");
// 或者可以直接拒绝null
name = Objects.requireNonNull(n, "The name can not be null");
// 这样可以直接定位到哪里有空值, 如果等程序自动触发NPE的话, 可能不是空值存在的地方
静态字段
静态变量
- 一个对象定义为
static
, 那么这个字段并不会出现在每个类的对象中. 每个静态字段只有一个副本 - 所以静态字段属于类, 但是不属于单个类的实例
比如对员工分配唯一的标识码
1 | class E { |
静态常量
- 静态常量相比于静态变量更加常用
1 | public class Math { |
- 类中最好不要有公共字段, 因为公共字段谁都可以访问, 但是如果是final的公共常量就不要紧
- 因为
out
是由final
修饰的, 所以out
本身是不允许重新赋值的 - 但是
System
中有一个setOut
方法, 这是因为setOut
方法不是Java
实现的, 是一个原生方法, 可以跳过访问控制机制
静态方法
- 静态方法是不操作对象的方法, 比如
Math.pow(x, a)
, 不需要使用Math
对象, 没有隐式参数this
- 所以上面的
E
类, 静态方法不能访问id
字段, 因为不操作对象, 但是静态方法可以访问静态字段 - 同样可以使用对象实例调用静态方法, 但是没有意义, 因为静态方法与对象无关, 所以最好直接用类名调用静态方法
以下两种情况可以使用静态方法:
- 方法不需要访问对象状态, 所有的参数可以直接通过显示参数提供, 比如
Math.pow(x, a)
- 方法只需要访问静态字段
main
方法就是一个静态方法, 可以在每个类都创建一个静态方法, 用于演示- 不演示的时候直接调用
Application.main
则不会执行内部其他类的main
函数
静态工厂方法
为什么不用构造器要用静态工厂方法:
- 无法为构造器命名, 因为构造器的命名总是要与类名相同, 但是如果需要得到两个不同的名字, 就无法实现了
- 构造器无法改变构造对象的类型, 静态工厂方法可以返回指定的类型, 比如某个类的子类
构造器
- 可以在一个构造器中调用另一个构造器
1
2
3
4
5
6
7public class E {
public E() {
// 调用了E(String, double)的构造方法, 这样只需要写一次公共的构造函数
this("123" + nextId, s);
nextId ++;
}
}
- 自动定义的, 设置所有实例字段的构造器是标准构造器
- 自定义构造器的第一个语句必须调用另一个构造器, 最终调用标准构造器
记录
JDK 14
引入, 状态不可变, 公共可读, 一个记录的实例字段称为组件
1 | record Point(double x, double y) {}; |
- 每个记录都有自动定义的三个方法:
toString(), equals(), hashCode()
- 记录可以自己定义静态字段和方法, 但是不能新增实例字段, 实例字段应该全部都作为参数
包
包名为了确保类名的唯一性, 一般是
域名的逆序.项目名.类名
- 一个类可以使用所属包中的所有类, 以及其他包中的公共类
- 如果包名写错了, 但是他不依赖其他包, 那么可以顺利编译通过, 但是执行的时候会失败, 因为虚拟机无法根据包名找到类
jar
- 使用
jar cvf jarFileName file1 file2 ...
创建新的jar
文件,u
选项可以更新jar
包 - 每个
jar
都包含一个清单文件manifest
用于描述归档文件的特殊性
- 清单文件
MANIFEST.MF
位于jar
文件的META-INF
子目录中 - 清单文件中包含多个条目, 分组成多个节, 第一节称为主节, 作用于整个
JAR
文件 - 节与节之间使用空行分割, 除主节外,随后的每一节中的条目可以指定命名实体的属性, 比如单个文件, 包或者
url
, 都需要以Name
条目开始 - 如果需要编辑清单文件, 可以将需要添加到清单文件的行放到文本文件中, 使用
jar cfm jarFileName manifestFileName
- 如果需要更新清单文件, 可以将增加的部分放到文本文件中, 使用
jar ufm xxx.jar manifest-additions.mf
- 清单文件的最后一行需要以换行符作为结束, 否则无法正确读取
- 可以使用
jar cvfe xxx.jar xxxxxClass
来指定程序的入口点- 或者可以在清单文件中添加主类
Main-Class: xxxxxClass
- 这样就可以使用
java -jar xxx.jar
来启动程序1
2
3
4
5
6// Hello.java
public class Hello {
public static void main(String[] args) {
System.out.Println("hello");
}
}
- 或者可以在清单文件中添加主类
- 比如上述
Hello.java
, 使用javac Hello.java
可以编译为Hello.class
, 运行java Hello
可以直接输出"Hello"
- 此时如果使用
jar cvf Hello.jar Hello.java Hello.class
, 将会生成Hello.jar
- 运行
java -jar Hello.jar
, 会报错, 提示没有主清单属性- 第一种解决办法: 重新生成一个
jar
包, 生成的时候指定入口程序jar -cvfe Hello.jar Hello Hello.java Hello.class
- 第一个
Hello.jar
是生成的jar
包, 第二个Hello
是主入口程序为Hello
这个类, 第三个和第四个是jar
包中需要包含的文件
- 第二种解决办法: 修改
MANIFEST.mf
- 创建一个
MANIFEST-ADD.mf
文件, 添加Main-Class: Hello
- 这里一定需要换行, 保存文件后, 运行
jar -ufm Hello.jar MANIFEST-ADD.mf
即可
- 创建一个
- 第一种解决办法: 重新生成一个
多版本jar
JDK 9
引入了多版本jar
, 将特定于版本的类文件放在了META-INF/versions
中
- 如果要增加不同版本(比如
JDK 9
)的类文件, 可以使用jar -uf xxx.jar --release 9 xxx.class
- 如果要从头构建一个多版本
jar
, 可以使用-C
, 每个对应的版本切换到一个不同的类文件目录jar cf xxx.jar -C bin/8 . --release 9 -C bin/9 xxx.class
- 不同版本的编译, 需要使用
--release
和-d
指定输出目录 - 多版本
jar
唯一的作用是让你的程序可以使用不同版本的jdk
注释
类注释
- 放在
import
之后,class
之前 - 使用
\** *\
方法注释
- 可以对方法的作用, 方法的参数, 返回值, 异常添加注释, 使用
@param, @return, @throws
字段注释
- 只需要对公共字段, 静态常量进行注释
包注释
- 需要单独写一个文件, 比如
package-info.java
, 里面是文档注释 - 或者可以写一个
package.html
, 里面抽取标记<body>...</body>
的所有文本
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Sangs Blog!