Java核心技术卷1 第4章
-
面向对象程序设计(object-oriented programming,OOP)是当今主流的程序设计范型。
-
类(class)是构造对象的模板或蓝图。由类构造(construct)对象的过程称为创建类的实例(instance)。
-
封装(encapsulation)是处理对象的一个重要概念。从形式上说,封装就是将数据和行为组合在一个包中,并对对象的使用者ui内藏具体的实现方式。对象中的数据称为实例字段(instance field)(字段是属于类的,它是会用public、private等关键字修饰的)。操作数据的过程称为方法(method)。
-
在Java中,所有的类都源自一个超类-Object。所有的类都扩展自这个Object类。
-
通过扩展一个类来建立另外一个类的过程称为继承。
-
类之间最常见的关系为依赖、聚合、继承。
-
一个类的方法使用或操纵另一个类的对象,我们就说一个类依赖于另一个类。A发生了变化引起B的变化,则B依赖于A。
-
聚合关系为一个对象包含一些Item对象。包含关系意味着类A的对象包含类B的对象(表示的是整体和部分的关系,整体与部分 可以分开)。例如车和轮胎。
-
继承关系:如果类A扩展类B,类A不但包含从类B继承的方法,还会有一些额外的功能。
-
使用构造器(constructor,或称构造函数)构造新实例。如new Date();
-
Date deadline;这定义了一个对象变量deadline,可以引用Date类型的对象。但是变量deadline不是一个对象,而且实际上它也能没有引用任何对象。此时不能使用任何Date方法。
-
所有的Java对象都存储在堆中。
-
Data类时间是用距离一个固定时间点的毫秒数表示的,这个时间点就是所谓的纪元(epooh)。它是UTC时间1970年1月1日00.00.0。UTC就是国际协调时间。
-
LocalDate类采用日历表示法表示日期。Date类用来表示时间点。
-
不要使用构造器来构造LocalDate类的对象,应使用静态工厂方法(factory method)。静态工厂方法简单地说,就是用一个方法来表示所有的构造函数。具体见https://zhuanlan.zhihu.com/p/357467564。
-
LocalDate.now()会构造一个新对象,表示构造这个对象的日期。
-
可以提供年、月、日来构造对应于一个特定日期的对象。
LocalDate newYearEve=LocalDate.of(1999,12,31);
可以用方法getYear,getMonthValue,getDayOfMonth得到年月日。getDayOfWeek得到周几。
-
plusDays方法会得到一个新的LocalDate,这个新日期对象则是距当前对象指定天数的一个新日期。
LocalDate aThousandDaysLater=newYearEve.plusDays(1000);
-
plusDays方法没有更改调用这个方法的对象,而是新生成一个对象。只访问对象而不修改对象的方法有时称为访问器方法。相反则是更改器方法。
-
关键字private确保只有类自身的方法能够访问实例字段。可以用public标记实例字段,但这是一种很不好的做法,破坏了封装。
-
实例字段可以本身就是对象。如private String s,s时String类对象。
-
构造器总是结合new运算符来调用。不能对一个已经存在的对象调用构造器来达到重新设置实例字段的目的。
-
Java10 可以用var关键字声明局部变量而无需指定类型,不过我们不会队数值类型使用var,如int,long,double。var关键字只能用于方法中的局部变量。
-
方法中有两种参数:隐式(implicit)参数、显式(explicit)参数。隐式参数出现在方法名前的对象,显式参数位于方法名后面括号中的数值。显式参数显式地列在方法声明中,隐式参数没有出现在方法声明中。在每一个方法中,关键字this指示隐式参数。
-
访问器方法如类中的get方法,只返回实例字段值,由此又称为字段访问器。
-
想要获得或设置实例字段的值:
-
一个私有的数据字段
-
一个公共的字段访问器方法
-
一个公共的字段更改器方法
-
-
不要编写返回可变对象引用地访问器方法。
class empleyee { private Date birthday; ... public Date getHireDay() { return hireDay;//bad } }
Date类中有一个更改器方法setTime,可以设置毫秒数。Date对象是可变的,这一点就破坏了封装性!
Employee harry=...; Date d=harry.getHireDay(); double tenYearsInMilliSeconds=10* 365.25* 24* 60* 60* 1000; d.setTime(d.getTime()-(long) tenYearsInMilliSeconds); //出错
出错的原因为d和harry.hireDay引用同一个对象。对d调用更改器方法就可以自动地改变这个Emplyee对象的私有状态。
如果炫耀返回一个可变对象的引用,首先应该对他进行克隆。对象克隆是指存放在另一个新位置上的对象副本。
BAD
import java.awt.geom.Arc2D; import java.util.Date; class employee { private Date hireDay; public void setHireDay(Date hireDay) { this.hireDay = hireDay; } public Date getHireDay() { return hireDay;//bad } } public class test { public static void main(String[] args){ employee harry=new employee(); Date now=new Date(); System.out.println(now.toString()); harry.setHireDay(now); Date d=harry.getHireDay(); double tenYearsInMilliSeconds=10* 365.25* 24* 60* 60* 1000; d.setTime(d.getTime()-(long) tenYearsInMilliSeconds); System.out.println(now.toString()); System.out.println(d.toString()); } } /**输出为 Wed Sep 07 10:13:02 CST 2022 Thu Sep 06 22:13:02 CST 2012 Thu Sep 06 22:13:02 CST 2012 */ //可以看出now对象和d一样时间发生了改变
OK
public Date getHireDay() { return (Date) hireDay.clone();//OK } /**输出为 Wed Sep 07 10:18:38 CST 2022 Wed Sep 07 10:18:38 CST 2022 Thu Sep 06 22:18:38 CST 2012 */ //可以看出now对象没有发生改变
-
一个方法可以访问所属类的所有对象的私有数据。
-
可以将实例字段定义为final,这样的字段必须在构造对象时初始化。必须确保在每一个构造器执行之后,这个字段的值已经设置,并且以后不再修改这个字段。
class Employee { private final string name; }
final修饰符对于类型为基本类型或者不可变类的字段尤其有用。(如果类中所有的对象都不会改变其对象,这样的类就是不可变类。例如String类就是不可变的)。
对于可变的类,使用final修饰符可能会造成混乱。(可以理解为c中的const 在修饰指针变量时位置的不同会导致其意义不同有混乱)
-
静态字段 属于类,而不属于任何单个的对象,每个类只有一个这样的字段,也被称为类字段。
private static int nextId=1;
-
静态常量比较常用。如在Math类中定义一个静态常量:
public class Math { ... public static final double PI=3.14159...; ... }
另一个常用的静态常量System.out.
public class System { ... public static final printStream out=...; ... }
由于每个类对象都可以修改公共字段,所以最好不要有公共字段。然而,公共常量(即final字段)却没问题。因为out被声明为final,所以,不允许再将它重新赋值为另一个打印流。
-
静态方法是不在对象上执行的方法。例如Math类的pow方法。可以认为静态方法是没有this参数的方法。静态方法不能访问实例字段,但是可以访问静态字段。
-
可以使用对象调用静态方法,但是很容易造成混乱。所以建议使用类名而不是对象来调用静态方法。
-
在以下两种情况下可以使用静态方法:
- 方法不需要访问对象状态,因为它需要的所有参数都通过显式参数提供(例如:Math.pow)
- 方法只需要访问类的静态字段。
-
使用工厂方法的两个原因:
- 无法命名构造器,构造器的名字必须与类名相同。
- 使用构造器时,无法改变所构造对象的类型。
-
main方法也是一个静态方法。main方法不对任何对象进行操作,静态的main方法将执行并构造程序所需要的对象。
-
每一个类可以有一个main方法,这是常用于对类进行单元测试的一个小技巧。
-
按值调用(call by value)表示方法接受的是调用者提供的值。而按引用调用(call by reference)表示方法接受的是调用者提供的变量地址。方法可以修改按引用传递的变量的值,而不能修改按值传递的变量的值。
-
java程序设计语言总是采用按值调用。方法得到的是所有参数值的一个副本。
-
一个方法不可能修改基本数据类型的参数,而对象引用作为参数就可以。因为方法得到的是对象引用的副本,原来的对象引用和这个副本都引用同一个对象。
-
Java程序设计语言对对象采用的不是按引用调用,实际上,对象引用是按值传递的。
-
java对方法参数的作为:
- 方法不能修改基本数据类型的参数(即数值型或布尔型)
- 方法可以改变对象参数的状态
- 方法不能让一个对象参数引用另一个对象。
-
如果多个方法有相同的名字、不同的参数,便出现了重载(overloading)。编译器必须挑选出具体调用哪个方法。它用各个方法首部中的参数类型与特定方法调用中所使用的值类型进行匹配,来选出正确的方法。如果编译器找不到匹配的参数,就会产生编译时错误,因为根本不存在匹配,或者没有一个比其他的更好(这个查找匹配的过程称为重载解析(overloading resolution)).
-
要完整地描述一个方法,需要指定方法名以及参数类型,这叫做方法的签名(signature)。
indexOf(int) indexOf(int,int) indexOf(String)
返回类型不是方法签名的一部分。也就是说,不能有两个名字相同,参数类型也相同却有不同返回类型的方法。
-
默认字段初始化 :如果构造器没有显示地位字段设置初值,那么会被自动地赋为默认值:数值为0、布尔值为false、对象引用null。
-
方法中地局部变量必须明确地初始化。但是在类中,如果没有初始化类中地字段,将会自动初始化为默认值(0,false或null)。
-
如果类中提供了至少一个构造器,但是没有提供无参数的构造器,那么构造对象时如果不提供参数就是不合法的。
-
仅当类没有任何其他构造器的时候,你才会得到一个默认的无参数构造器。如果希望所有字段被赋予默认值,只需要提供一下代码:
public ClassName { }
-
参数变量遮蔽同名的实例字段。例如,如果将参数命名为salary,salary将指示这个参数。而不是实例字段。但是,还是可以用this.salary访问实例字段。
-
初始化块(initialization block).在一个类的声明中,可以任何多个代码块。只要构造这个类的对象,这些块就会被执行。
-
调用构造器的具体处理步骤:
-
如果构造的第一行调用了另一个构造器,则基于所提供的参数执行第二个构造器。
-
否则,
a)所有数据字段初始化为其默认值(0,false,nuil)
b)按照在类声明中出现的顺序,执行所有字段初始化方法和初始化块
-
执行后早期主体代码。
-
-
在类第一次加载的时候,将会进行静态字段的初始化。
-
由于java会完成自动的垃圾回收,不需要人工回收内存,所以Java不支持析构器。
-
Java允许使用包(package)将类组织在一个集合中。
-
为了保证包名的绝对唯一性,需用一个因特网域名(这显然是唯一的)以逆序的形式作为包名,然后作为不同的工程使用后可以追加一个工程名。
-
一个类可以使用所属包中的所有类,以及其他包中的公共类(public class)。
-
可以采用两种方式访问另一包中的公共类。第一种方式就是使用完全限定名(fully qualified name);就是包名后面跟着类名。例如
java.time.LocalDate today=java.time.LocalDate.now();
另一种更简单更常用的方式是使用import语句。import语句是一种引用包中各个类的简捷方式。
-
Import语句应该位于源文件的顶部(但位于package语句的后面)。
-
只能使用星号* 导入一个包,而不能使用Import java.* * 或import java.* .* 导入以java为前缀的所有包。
-
在发生命名冲突的时候要注意包。例如,java.util和java.sql包都有Date类。如果在程序中导入了这两个包: import java.util.* ; import java.sql.* 在程序中使用Date类的时候,就会出现一个编译错误。此时可以增加一个特定的import语句来解决这个问题:
import java.util.*; import java.sql.*; import java.util.Date;
如果这两个Date类都需要使用,可以在每个类名的前面加上完整的包名。
var deadline=new java.util.Date(); var today=new java.sql.Date(...);
在包中定位类是编译器(complier)的工作 。类文件中的字节码总是使用完整的包名引用其他类。
-
有一种import 语句允许导入静态方法和静态字段,而不只是类。例如,如果在源文件顶部,添加一条指令:
import static java.lang.System.*;
就可以使用System类的静态方法和静态字段,而不必加类名前缀:
out.println("");
还可以导入特定的方法或字段:
import static java.lang.System.out;
-
将类放入包中,就必须将包的名字放在源文件的开头,即定义在这个包中各个类的代码之前。
package com.horsemann.corejava public class Employee { }
-
如果没有在源文件中放置package语句,这个源文件中的类就属于无名包(unnamed package)。无名包没有包名。
-
标记为public的部分可以由任意类使用;标记为private的部分只能由定义他们的类使用。如果没有指定public或private,这个部分(类、方法或变量)可以被同一个包中的所有方法访问。
-
类文件也可以存储在JAR(java归档)文件中。在一个JAR文件中,可以包含多个压缩形式的类文件和子目录,这样既可以节省空间又可以改善性能。
-
JAR文件使用ZIP格式组织文件和子目录。可以使用任何ZIP工具查看JAR文件。
-
为了使类能够被多个程序共享:
- 把类文件放到一个目录中。这个目录是包树状结构的基目录。
- 将JAR文件放在一个目录中。
- 设置类路径(class path)。类路径是所有包含类文件的路径的集合。
-
最好使用-classpath(或-cp,或者Java9中的--class-path)选项指定类路径。
如java -classpath c:\classdir; . ;c:\archives\archive.jar MyProg
利用-classpath选项设置类路径是首选的方法,也可以通过设置CLASSPATH环境变量来指定。
-
一个JAR文件及可以包含类文件,也可以包含诸如图像和声音等其他类型的文件。此外,JAR文件是压缩的,它使用了ZIP压缩格式。可以使用jar工具制作JAR文件。创建一个新JAR文件可以使用语法:
可以使用jar工具制作JAR文件。创建一个新JAR文件可以使用语法:
jar cvf jarFileName file1 file2...
通常,jar命令的格式如下:
jar options file1 file2....
可以将应用程序和代码库导报在JAR文件中。
更多见P144,145,146,147
-
javadoc可以由源文件生成一个HTML文档。
- 每个/**...* /文档注释包含标记以及之后紧跟着的自由格式文本(free-form text)。标记以@开始,如@since或@param
- 自由格式文本的第一句应该是一个概要性的句子。javadoc工具自动地将这些句子抽取出来生成概要页。
- 在自由格式文本中,可以使用HTML修饰符。
-
类注释必须放在import语句之后,类定义之前。
-
@param variable description 这个标记将给当前方法的“parameters(参数)"部分添加一个条目。这个描述可以占据多行,并且可以使用HTML标记。一个方法的所有@param标记必须放在一起。
-
@return description 这个标记将给当前方法添加”return(返回)“部分。这个描述可以跨多行,并且可以使用HTML标记。
-
@throws class description这个标记将添加一个注释,表示这个方法可能抛出异常。
-
/** Raises the salary of an employee @param byPercent the percentage by which to raise the salary(e.g.,10 means 10%) @return the amount of the raise */ public double raiseSalary(double byPercent) { double raise=salary*byPercent/100; salary+=raise; return raise; }
-
对于字段只需要对公共字段(通常指的是静态常量)建立文档。
-
通用注释 @since text会建立一个”since“条目。text可以是引入这个特性的版本的任何描述,例如@since 1.7.1。
-
@author name 这个标记将会产生一个”author“条目。可以使用多个@author标记,每个@author标记对应一个作者。
-
@version text这个标记将会产生一个”version“条目。这里的文本可以是对当前版本的任何描述。
-
通过@see和@link标记可以使用超链接,连接到javadoc文档的相关部分或外部文档。
@see reference将在”see also“部分增加一个超链接。它可以用于类中,也可以用于方法中。reference格式可以有:
package.class#feature label ; <a href="">label</a> "text"
第一种情况最常用。例如:@see com.horstmann.corejava.Employee#raiseSalary(double)
可以省略包名,甚至包名和类名,此时会位于当前包或当前类中。
一定要使用#而不要使用.分割类名与方法名。
其它格式例子,
@see <a href="www.horstmann.com/corejava.html">The Core Java home page</a> @see "Core Java 2 volume 2"
可以在文档注释中的任何位置放置指向其它类或方法的超链接。
-
在Java 9,还可以使用{@index entry}标记为搜索框增加一个条目。
-
要想产生包注释,就需要在每一个包目录中添加一个单独的文件:1.提供一个名为package-info.java的Java文件。这个文件必须包含一个初始的以/* * 和 * /界定的javadoc注释,后面是一个package语句。它不能包含更多的代码或注释。2.提供一个名为package.html的html文件。会抽取标记...之间的所有语句。
-
注释抽取要切换到包含想要生成文档的源文件的目录。如果有嵌套的包要生成文档,就必须切换到包含子目录com的目录。
- 如果是一个包,应该运行命令 javadoc -d docDirectory nameOfPackage
- 多个包 javadoc -d docDirectory nameOfPackage1 nameOfPackage12...
- 文件在无名的包内,javadoc -d docDirectory *.java
-
类设计技巧:
- 一定要保证数据私有
- 一定要对数据进行初始化
- 不要在类中使用过多的基本类型
- 不要所有的字段都需要单独的字段访问器和字段更改器
- 分解过多职责的类
- 类名和方法名要能够体现他们的职责。类名应当是一个名词(Order),或者是前面有形容词修饰的名词(RushOrder),或者是有动名词修饰的名词(BillingAddress).对于方法,访问器方法用小写get开头,更改器方法用小写set开头。
- 优先使用不可变的类。更改对象的问题在于,如果多个线程试图同时更新一个对象,就会发生并发更改。其结果是不可预料的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异