Chapter 4 对象与类
作者:Denis
版本:1.0
编写时间:2022/10/15
编写地点:中国山西省
4.1 类与对象的概述
可以把类想象为一个模具,利用类能够创造出很多同属于一个物种但参数不同的对象。
对象应该具有封装内部细节的特点,使对象的内部状态不能轻易地被他人所修改,同时也应该暴露出一些公共的接口,方便其他类的对象对其进行操作。
类也应该具备继承性,用户在编写新的类的时候,可以直接参考过往代码,而无需重复造轮子。
类之间的关系
- 依赖(uses-a)
最常见的一种关系,如果一个类的方法需要对另一个类的对象进行操作,就称一个类依赖另一个类,应该尽量减少这种依赖关系,即降低耦合度。
- 聚合(has-a)
一个类的内部实例字段包含另一个类的对象,即为聚合。
- 继承(is-a)
表示一种特殊与一般的关系,如果经理类继承员工类,那么经理就是一种特殊的员工。他与普通员工的大多数参数和行为一样,但也有其自己的参数和行为,如奖金,任免权。
4.2 LocalDate类
可以使用Date类来获取时间和日期,但为了分工明确,现在Date类被用作获取时间
获取日历就用LocalDate类
使用静态工厂方法获取LocalDate类的对象
LocalDate now = LocalDate.now();
或者使用LocalDate类的静态方法获取指定日期的对象
LocalDate nationalDay = LocalDate.of(1999, 10, 1);
一旦拥有这个对象,就可以用getYear
、getMonthValue
、getDayOfMonth
获取该对象的年、月、日
plusDays方法可以为指定的日期添加任意的天数,并返回一个新的对象,而原来的对象不做任何改动。
import java.time.LocalDate;
public class LocalDateTest {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
//可以看到,today并没有受到影响
LocalDate aThousandDaysLater = today.plusDays(1000);
System.out.println(today);
System.out.println(aThousandDaysLater);
}
}
//输出
2022-10-14
2025-07-10
LocalDate的其他方法
方法 | 返回类型 | 描述 |
---|---|---|
getDayOfYear | int | 返回当前对象是一年中的第几天 |
getDayOfWeek | DayOfWeek | 返回一个DayOfWeek对象,使用其getValue方法获取当前对象是星期几 |
minusDays(int n) | LocalDate | 当前对象之前n天的日期 |
4.3 字段修饰符
访问修饰符可以对成员方法或者类字段进行修饰,也可以对类进行修饰。
对类进行修饰时,只能使用public和默认修饰符。
访问符 | 本类 | 包内 | 子类 | 外部类 |
---|---|---|---|---|
public | ✅ | ✅ | ✅ | ✅ |
protected | ✅ | ✅ | ✅ | ❌ |
默认 | ✅ | ✅ | ❌ | ❌ |
private | ✅ | ❌ | ❌ | ❌ |
有一种特殊情况,假如一个对象拥有私有字段,当其作为参数时,所属类的方法可以访问到这些私有字段
public class Employee{
private String name;
public boolean equals(Employee other){
return this.name.equals(other.name);
}
}
如果希望一个字段在设置后就不能被修改,可以设置为final
为字段添加final修饰后,必须在构造的时候就进行初始化操作
public class Employee{
private final String name;
}
final修饰的字段对于基本类型和不可变类的对象非常有效,final修饰的字段只保证指向当前对象,不保证当前对象是否会被修改,例如StringBuilder
静态字段与静态方法
当字段或方法被static修饰,则该类的每个对象共同拥有同一个字段和方法
所以也被称为类变量,类方法
public class Employee{
private String name;
public static int nextId = 1;
}
由于公共变量会被随意修改,最好使用公共常量。典型的公共常量是System.out
静态方法没有隐式参数this,所以它不能访问对象的实例字段(除非该类的另一个对象作为参数),但可以访问该类的静态字段
静态方法的使用场景:
- 不需要访问对象状态,所有参数显式提供
- 只需要访问类的静态字段
- 可以作为工厂方法,返回一个对象,例如
LocalDate.now()
。使用工厂方法可以返回不同状态、不同类型的对象。例如返回一个类的子类对象,而构造器无法实现这个功能。下面是一个使用工厂方法的例子。
import java.text.NumberFormat;
public class ObjectsTest {
public static void main(String[] args) {
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
NumberFormat percentFormatter = NumberFormat.getPercentInstance();
double x = 0.1d;
System.out.println(currencyFormatter.format(x));
System.out.println(percentFormatter.format(x));
}
}
//输出结果
¥0.10
10%
4.4 null引用
为一个引用变量赋值为null意味着当前变量没有指向任何对象,如果使用该变量调用成员方法,会出现空指针异常。
可以采取一种“宽容”的做法,即当判断一个变量为null时,给予一个默认值。
name = n == null ? "unknown" : n;
在Java9中,Objects类提供了一个便利的做法,逻辑与上述一致
public static <T> T requireNonNullElse(T obj, T defaultObj) {
return (obj != null) ? obj : requireNonNull(defaultObj, "defaultObj");
}
如果采取严格的做法,拒绝接受null参数,可以采用另一个方法requireNonNull
,他的第二个参数可以输出错误的信息。
使用这种方法有两种好处:可以告知问题出现的位置,便于排查
public static <T> T requireNonNull(T obj, String message) {
if (obj == null)
throw new NullPointerException(message);
return obj;
}
Objects类的其他方法
方法 | 描述 |
---|---|
T requireNonNullElseGet(T obj, Supplier<? extends T> supplier) | obj不为null返回obj,否则返回默认对象 |
T requireNonNull(T obj, Supplier< String > messageSupplier) | obj不为null返回obj,否则利用供应者以懒方式得到一个值 |
4.5 创建对象流程
- 先在方法区加载类信息
- 在堆中创建对象,分配空间,对基本类型置默认值,对引用类型置null
- 如果调用了另一个构造器(
this(...)
),先执行那个构造器 - 执行类的默认信息
- 执行类的初始化块
public class Person{
//类的默认信息
private String name = "xxx";
public final static String company;
//执行类的初始化块, 可以初始化任何变量,建议放在声明的字段之后
{
name = "李白";
}
//静态初始化块,只能初始化静态变量
static{
company = "sx";
}
}
- 执行构造器,对对象进行初始化,对于构造器里的字符串,放在常量池进行引用。
下面是一个完整的案例
import java.util.*;
public class ConstructorTest
{
public static void main(String[] args)
{
// fill the staff array with three Employee objects
var staff = new Employee[3];
staff[0] = new Employee("Harry", 40000);
staff[1] = new Employee(60000);
staff[2] = new Employee();
// print out information about all Employee objects
for (Employee e : staff)
System.out.println("name=" + e.getName() + ",id=" + e.getId() + ",salary="
+ e.getSalary());
}
}
class Employee
{
private static int nextId;
private int id;
private String name = ""; // instance field initialization
private double salary;
// static initialization block
static
{
var generator = new Random();
// set nextId to a random number between 0 and 9999
// 这是创建随机数的第二种方法,第一种Math类的方法
nextId = generator.nextInt(10000);
}
// object initialization block
{
id = nextId;
nextId++;
}
// three overloaded constructors
public Employee(String n, double s)
{
name = n;
salary = s;
}
public Employee(double s)
{
// calls the Employee(String, double) constructor
this("Employee #" + nextId, s);
}
// the default constructor
public Employee()
{
// name initialized to ""--see above
// salary not explicitly set--initialized to 0
// id initialized in initialization block
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public int getId()
{
return id;
}
}
4.6 静态导入
可以使用import static语句来导入静态方法和静态字段,例如
import static java.lang.System.*
就可以使用System类的所以静态成员和静态方法,而无需添加前缀System
out.println("ok");
exit(0);
4.7 JAR文件
JAR文件把类文件和其所在的目录打包在一起,既可以节省空间又可以改善性能。可以使用ZIP工具查看JAR文件
JAR文件也可以有其他类型文件,如声音和图像
为了让编写的类可以在任意位置运行,需要进行如下设置
- 使用
java -classpath .;d:\archives;d:\jars\xxx.jar MyProg
来启动MyProg程序,将自动从类路径搜索类 - 也可以通过设置CLASSPATH环境变量临时指定一些目录,如
set CLASSPATH=.;d:\archives;d:\jars\xxx.jar
- 上述设置classpath都是临时的,一旦退出控制台,就需要再次输入指令
- 永久设置CLASSPATH的方式是设置一个环境变量
创建JAR文件
命令为:jar cvf jarFileName file1 file2...
参数c为创建归档文件、v为输出详细的生成结果、f指定第一个参数为jar文件名,而非要压缩的文件,另外如果把c改为x是解压操作
清单文件 MANIFEST.MF
生成jar包时,包含一个清单文件,位于META-INF目录中。用户也可以添加自己编写的清单文件
可执行的JAR文件
命令为:jar cvfe jarFileName com.xxx.MainApp add files
,在指定主类后,还需要在后面把主类添加进去
或者在清单文件中指定Main-Class
Manifest-Version: 1.0
Created-By: 11.0.15 (Oracle Corporation)
Main-Class: Welcome
执行jar文件,使用命令java -jar xxx.jar
还可以使用开源或者商业软件将jar变为exe文件
多版本的jar文件
随着Java版本的发行,可能对安装不同版本虚拟机的用户提供不同版本的Java程序
提供多版本jar文件的前提是,所有类的公共API是一样的,否则应该独立提供
在一个jar包内可以包含多个不同版本的Java程序,只需要将其放在META-INF/versions中即可。【since Java 9】
更新一个jar文件,使其支持多版本
命令:jar uf MyProgram.jar --release 9 Application.class
如果需要从头构建,应该使用-C选项,切换不同的目录
命令:jar cf MyProgram.jar -C bin/8 --release 9 -C bin/9 Application.class
对不同版本的文件进行编译时,需要使用-d选项指定输出目录
javac -d bin/8 --release 8 ...
【release标志只能在Java 9或之后的版本使用】
原文地址:http://www.cnblogs.com/run-bit/p/16794692.html