文章目录
null引用
如果需要返回一个可变对象的引用,首先应该对它进行克隆。对象克隆是指存放在另一个新位置上的对象副本。例如
class Employee
{
public Date gethireDay()
{
return (Date)hireDay.clone();
}
权限修饰符
final 实例字段
final 修饰符对于类型为基本类型或者不可变类的字段尤其有用。对于可变的类,使用final修饰符可能会造成混乱。例如,考虑以下字段:
private final StringBuilder evaluations;
evaluations=new StringBuilder();
final关键字表示存储在evaluations变量中的对象引用不会再指示另一个不同的StringBuilder对象。不过这个对象可以更改:
package chapter_4;
public class final_my {
private final String name="1213";
private final StringBuilder evalutions;
final_my(StringBuilder stringBuilder)
{
evalutions=stringBuilder;
}
void giveGoldStar()
{
evalutions.append('1');
evalutions=new StringBuilder("12");
}
public static void main(String[] args) {
final_my finalMy=new final_my(new StringBuilder("123"));
}
}
静态常量
public static final double PI=3.14;
静态方法
静态方法不能访问id实例字段,因为它不能在对象上执行操作。但是,静态方法可以访问静态字段。例如
public static double getPI()
{
return PI;
}
建议使用类名而不是对象来调用静态方法。
工厂方法
类似LocalDate 和NumberFormat的类使用静态工厂方法来构造对象。NumberFormat类如下生成不同风格的格式化对象:
NumberFormat currencyFormatter=NumberFormat.getCurrencyInstance();
NumberFormat percentformatter=NumberFormat.getPercentInstance();
为什么不适用构造器完成这些操作呢?
- 无法命名构造器。构造器的名字必须和类名相同。但是,这里希望有两个不同的名字,分别得到货币实例和百分比实例。
- 使用构造器时,无法改变所构造对象的类型。
方法参数
java 程序设计语言对对象采用的不是按引用调用,实际上,对象引用时按值传递的。
java 对方法参数能做什么和不能做什么
- 方法不能修改基本数据类型的参数
- 方法可以改变对象参数的状态
- 方法不能让一个对象参数引用另一个新的对象
public static void swap(String a,String b)
{
String temp=a;
a=b;
b=temp;
}
public static void main(String[] args) {
String a="12354";
String b="1234565";
swap(a,b);
System.out.println(a);
System.out.println(b);
}
对象构造
默认字段初始化
如果在构造器中没有显示地位字段设置初值,那么就会自动被赋值为默认值,数值为0,布尔值为false,对象引用为null.
显示字段初始化
可以在类定义中直接为任何字段赋值,初始值不一定是常量值。例如
package chapter_4;
public class obvious_var_initial {
private static int nextId;
private int id=assignId();
private static int assignId()
{
int r=nextId;
nextId++;
return r;
}
}
调用另一个构造器
例如
obvious_var_initial(double s)
{
this("Employee #"+nextId,s);
nextId++;
}
obvious_var_initial(String name,double s)
{
}
初始化块
package chapter_4;
import java.util.Random;
public class initial_area {
private static int nextId;
private int id;
private String name;
private double salary;
static
{
var generator=new Random();
nextId= generator.nextInt(1000);
}
{
id=1;
}
{
id=nextId;
nextId++;
}
initial_area()
{
}
int getid()
{
return id;
}
public static void main(String[] args) {
initial_area initialArea=new initial_area();
System.out.println(initialArea.id);
initial_area two=new initial_area();
System.out.println(two.id);
System.out.println(initial_area.nextId);
}
}
static{ }的代码块要比普通的初始化块先调用。
下面是调用构造器的具体处理步骤:
- 如果构造器的第一行调用了另一个构造器,则基于所提供的参数执行第二个构造器。
- 2.否则
- a)所有数据字段初始化为其默认值
- b)按照在类声明中出现的顺序,执行所有字段初始化方法和初始化块。
- 3.执行构造器主体代码
包
为了保证包名的唯一性,要用一个因特网域名以逆序的形式作为包名,然后对于不同的工程使用不同的子包,例如考虑域名horstmann.com
得到包名com.horstmann然后可以追加一个工程名,com.horstmann.corejava再把Employee类放在这个包里,那么全完限定名就是com.horstmann.corejava.Employee。
类的导入
我们可以采用两种方式访问另一个包中的公共类
,第一种方式就是使用完全限定名;就是包名后面跟着类名。例如
java.time.LocalDate today=java.time.LocalDate.now();
第二种是用import例如:
import java.time.*;
还可以导入一个特定的包,例如:
import java.time.LocalDate;
只能使用星号(*)导入一个包,而不能使用import java.或者import java..*导入以java为前缀的所有包
如果两个都有同一个名字的类,就会发生错误。例如:
package chapter_4;
import java.util.*;
import java.sql.*;
public class import_class {
public static void main(String[] args) {
Date today;
}
}
可以增加一个特定的import语句来解决这个问题。
例如
import java.util.Date;
如果两个Date类都要使用,可以在每个类名的前面加上完整的包名。
var deadline=new java.util.Date();
var today=new java.sql.Date(100);
在c++中,必须使用#include来加载外部特性的声明,这是因为,除了正在编译的文件以及在头文件中明确包含的文件,c++编译器无法查看任何其他文件的内部。java编译器则不同,可以查看其他文件的内部,只要告诉它到哪里去查看就可以了。在c++中与包机制类似的是命名空间,可以认为java中的package和import语句类似于c++中的namespace和using指令。
静态导入
有一种import 语句允许导入静态方法和静态字段,而不只是类。例如
import static java.lang.System.*;
public class import_class {
public static void main(String[] args) {
out.print("hello world");
}
}
就可以使用System类的静态方法和静态字段,而不必加类名前缀。另外还可以导入特定的方法或字段;
import static java.lang.System.out;
在包中增加类
将源文件放到与完整包名匹配的子目录中。编译器将类文件也放在相同的目录结构中,需要注意,编译器处理文件(带有文件分隔符和扩展名.java的文件),而java解释器加载类。
编译器在编译源文件的时候不会检查目录结构,例如假定一个源文件开头有以下指令:
package com.mycompany;
即使这个源文件不在目录com/company下,也可以进行编译,吐过他不依赖于其他包,就可以通过编译而不出现编译错误,但是他无法运行,除非先将所有类文件移到正确的位置上。如果包与目录不匹配,虚拟机就找不到类。
为什么无法运行?
类路径
为了使类能被多个程序共享,需要做到以下几点:
- 把类文件放到一个目录中,例如/home/user/classdir。需要注意的是,这个目录是包树状结构的基目录。如果希望增加com.horstmann.corejava.Employee类,那么Employee.class 类文件就必须位于子目录/home/user/classdir/com/horstmann/corejava中。
- 2.将JAR文件(java归档)放在一个目录中,例如:/home/user/archives。
- 3.设置类路径,类路径是所有包含类文件的路径的集合。
在UNIX环境中,类路径的各项之间用冒号:分隔:
\home\user\classdir:.:\home\user\archives\archive.jar
而在windows环境中,都用句点(.)表示当前目录。
类路径包括:
- 基目录/home/user/classdir或c:\classes;
- 当前目录(.);
- JAR文件/home/user/archives/archive.jar或c:\archives\archive.jar
从java6开始,可以在JAR文件目录中指定通配符,如下:
/home/user/classdir:.:/home/user/archives/‘*’
或者
c:\classdir;.;c:\archives*
类路径所列出的目录和归档文件是搜索类的起点。例如:
/home/user/classdir:.:/home/user/archives/archive.jar
假定虚拟机要搜寻com.horstmann.corejava.Employee类的类文件。它首先要查看java API类。显然,在那里找不到相应的类文件。所以转而查看类路径。然后查找以下文件:
- /home/user/classdir/com/horstmann/corejava/Employee.class
- com/horstmann/corejava/Employee.class(从当前目录开始)
- com/horstmann/corejava/Employee.class(/home/user/archives/archive/jar中)
如果引用了一个类,而没有指定这个类的包,编译器将去查找这个类的包。它会查看所有import指令。例如:
import java.util.*;
import com.horstmann.corejava.*;
编译器会尝试查找java.lang.Employee(因为java.lang包总是会默认导入)、java.util.Employee,com.horstmann.corejava.Employee,如果找到了一个以上的类,就会报错,编译器还要查看源文件是否比类文件新,如果是就会再次自动重新编译。
设置类路径
不重要。
JAR文件
过了
类注解
类注解必须放在import语句之后,类定义之前。
方法注解
每一个方法注释必须放在描述的方法之前。除了通用标记之外,还可以使用下面的标记:
public String name;
/*
* @return description
*
* */
void setName(String aname)
{
}
类设计技巧
1.一定要保证数据私有
2.一定要对数据进行初始化
3.不要在类中使用过多的基本类型。
4.不是所有的字段都需要单独的字段访问器和更改器。
5.分解有过多职责的类
6.类名和方法名要能够体现他们的职责。
7.优先使用不可变的类。