编译和运行
- javac 编译文件 需要一个文件名(Welcome.java)
- java 运行程序 需要指定类名(Welcome) 不需要带扩展名
- 通配符javac Employee*.java
- EmployeeTest类中包含Employee类,直接编译EmployeeTest类,Employee也会被自动编译。可以认为Java编译器内置了make/nmake功能
Microsoft_Program_Maintenance_Utility,外号NMAKE,顾名思义,是用来管理程序的工具。其实说白了,就是一个解释程序。它处理一种叫做makefile的文件(以mak为后缀),解释里面的语句并执行相应的指令。我们编写makefile文件,按照规定的语法描述文件之间的依赖关系,以及与该依赖关系相关联的一系列操作。然后在调用NMAKE时,它会检查所有相关的文件,如果相关文件的time_stamp(文件最后一次被修改的时间,一个32位数)小于依赖文件(dependent_file)的times_tamp,NMAKE就执行依赖关系相关联的操作。
打包文件
- jar cvfm *.class将所有以.class结尾的字节码文件打包到一个“JAR文件”
applet相关
appletviewer可以快速测试applet
- Java区分大小写
- 访问修饰符
- 根据Java语言规范,main方法必须声明为public
- 每个Java应用程序都必须有一个main方法,并且是静态的。
- 每个句子必须用分号结束。特别需要说明,回车不是语句的结束标志, 如果需要可以将一条语句写在多行上。
- object.method(parameters)
- 强语言类型,每个变量都需要声明类型。
数据类型
8基本类型 4整型,2浮点型,字符类型char,和真值boolean型。
整型
byte 1字节,short 2字节,int 4字节,long 8字节
- 长整型,后缀L或l(400000000000000L)
- 十六进制,前缀0x或0X(0xCAFE)
- 八进制,前缀0([010] = [8])
- 二进制,前缀0b或0B([0b1001] = [9])
- 数字面量,1_000_000(或0b1111_0100_0100_0000)表示一百万。下划线只是为了易读。Java编译器会去除这些下划线。
浮点类型
float 4字节,double 8字节
- float,后缀F或f(3.14F)
- double,数值精度为float类型的两倍。没有后缀F的浮点数值(3.14),默认为double类型,也可以添加后缀D或d
- 十六进制表示浮点数值。0.125 = 2的-3次方→0x1.0p-3。p表示指数,不是e。尾数采用十六进制,指数采用十进制。指数的基数是2,为不是10
- Double.POSITIVE_INFINITY 表示正无穷大。
- Double.NEGATIVE_INFINITY表示负无穷大。
- Double.NaN表示不是一个数字。使用方法:
if(x == Double.NaN) //is never true
if(Double.isNaN(x)) // check whether x is "not a number"
- 浮点数值不适用于无法接受误差的金融计算中。
System.out.print(2.0 - 1.1);
0.8999999999999999
// 浮点数值采用二进制系统表示, 而在二进制系统中无法精确地表示分数 1/10
// 就好像十进制无法精确地表示出分数1/3一样
// 不允许有任何舍入误差,应该使用BigDecimal类
char类型
- char类型可以表示为十六进制值。\u005B\u005D→[],注意注释中的\u可能会产生语法错误。
boolean类型
- false和true。整型值和布尔值不能转换。
变量
- 变量名的长度基本上没有限制。
- 哪些 Unicode 字符属于 Java 中的“ 字母”可以存在变量中,使用Character类的isJavaldentifierStart、isJavaldentifierPart方法检查。
- 不能使用 Java 保留字作为变量名。
- 可以在一行中声明多个变量,但不推荐。
- 不要使用未初始化的变量。
常量
const是Java保留字,但并未使用。必须使用final定义常量。
- 用关键字final指示常量。表示这个变量只能被赋值一次。
- 用关键字static final设置一个类常量。
- 习惯上,常量名为全大写。
运算符
- 整数被0除,产生一个异常。
- 浮点数被0除,得到无穷大或NaN结果。
- 对于使用strictfp 关键字标记的方法必须使用严格的浮点计算来生成可再生的结果。如果将一个类标记为strictfp, 这个类中的所有方法都要使用严格的浮点计算。实际的计算方式取决于Intel处理器的行为
public static strictfp void main(String[] args)
数学函数
import static java.1ang.Math.*;
- sqrt(x) 开方
- pow(x,a) x的a次幂
- floorMod(x,y) 对x求余y
自增与自减
int m = 7;
int n = 7;
int a = 2 * ++m; // now a is 16, m is 8
int b = 2 * n++; // now b is 14, n is 8
不建议在表达式中使用++
位运算符
- & ("and")、 | ("or") 、^("xor") 、~ ("not")
这些运算符按位模式处理。例如,n是一个整数变量,用二进制表示的n从右边的第四位为1,则:
int m= (n & 0b1000)/0b1000;
返回1,否则返回0。利用&并结合使用适当的2的幂,可以把其他位掩掉只保留其中的某一位。
- >>、<<运算符将位模式左移/右移
- >>>运算符用0填充高位,>>会用符号位填充高位。不存在<<<
运算级别
- a && b || c
等价于
(a && b) || c - a += b += c;(+=为右结合运算符)
等价于
a += (b += c);
字符串是否相等
- s.equals(t)
- "Hello".equals(greeting)
- "Hello".equalsIgnoreCase("hello")忽略大小写
- 不要使用 == 检测两个字符串是否相等。只有字符串常量才是共享的,+或subString操作产生的结果是不共享的。
- compareTo方法
空串和null
- 空串“”,是一个对象:
if(str.length == 0)
if(str.equals("")) - null,没有任何对象:
if(str == null)
遍历一个字符串,并且依次査看每一个码点
int[] codePoints = str.codePoints().toArray();
把一个码点数组转换为一个字符串:
String str = new String(codePoints, 0, codePoints.length) ;
输出格式
沿用了C库函数的printf方法,可以进行格式化
double x = 333.333333333334f;
System.out.printf("%8.2f", x);
String name = "Jack";
int age = 12;
System.out.printf("Hello,%s,you'll be %d year-old", name, age);
System.out.printf("%,.2f", 10000.0 / 3.0);
3,333.33
switch语句
有可能触发多个case分支,编译代码时可以考虑加上-Xlint:fallthrough选项,例如:
javac -Xlint:fallthrough Test.java
如果确实是想用这种“直通式(fallthrough)行为”,可以在外围方法添加注解
@SuppressWarnings("fallthrough")
就不会对这个方法生成警告了
带标签的break
- 跳出多重嵌套的循环语句。
read_data:
while(...){
...
for(...){
....
break read_data;
}
}
标签需要紧跟一个":"冒号。
- 跳出if语句/块语句
- 只能跳出语句块,不能跳入语句块。
带标签的continue
跳至与标签匹配的循环首部
大数值
BigInteger和BigDecimal
这两个类可以处理包含任意长度数字序列的数值。BigInteger类实现了任意精度的整数运算,BigDecimal类实现了任意精度的浮点数运算。
常用Biglnteger API:
Biglnteger add(Biglnteger other) // +
Biglnteger subtract(Biglnteger other) // -
Biglnteger multipiy(Biginteger other) // *
Biglnteger divide(Biglnteger other) // /
Biglnteger mod(Biglnteger other) // 取余
int compareTo(Biglnteger other) // 相等为0 小于为-1,大于为1
static Biglnteger valueOf(long x) // 返回一个值为x的Biglnteger 类型数据
常用BigDecimal API:
BigDecimal add(BigDecimal other) // +
BigDecimal subtract(BigDecimal other) // -
BigDecimal multipiy(BigDecimal other) // *
BigDecimal divide(BigDecimal other, RoundingMode mode) // / 给出舍入方式,RoundingMode.HALF_UP 为四舍五入
Biglnteger mod(Biglnteger other) // 取余
int compareTo(Biglnteger other) // 相等为0 小于为-1,大于为1
static BigDecimal valueOf(long x); // 返回值为x
static BigDecimal valueOf(long x ,int scale);// 返回值为x/(10的scale次方)的实数
数组
声明:int[] a=new int[100];
简化: int[] b={1,2,3,4} → b = new int{1,2,3,4}
- 整型数组初始化所有元素为0
- boolean数组初始化所有元素为false
- string数组初始化所有元素为null
- new int[0] 与 null不同
- Arrays.toString()方法返回一个包含数组元素的字符串
一旦创建了数组,就不能改变它的大小。如果需要经常在运行过程中扩展数组的大小,使用数组列表 array list有关数组列表
数组拷贝
int[] b = {1,2,3,4,5};
int[] a = b;
a[2] = 10; // b[2] == 10 为 true
上述代码,两个变量引用同一个数组。
int[] copied = Arrays.copyOf(b, b.length);
上述代码,可以将一个数组的值拷贝到一个新数组中。第二个参数为新数组的长度。如果长度小于原数组的长度,则只拷贝前面的数据元素。
多维数组
即 数组的数组
balances[i]表示引用第i个子数组,即第i行
balances[i][j]表示引用第i个子数组的第j项
double[][] balances = new double[10] [6]; // Java
等价于(分配了一个包含10个指针的数组)
double** balances = new double*[10] ; // C++
for (i = 0; i < 10; i++)
balances[i] = new double[6];
多维数组的拷贝
int[][] a = {{1,2,3,4},{1,2,3,4},{1,2,3,4}};
int[][] b = a.clone();
System.out.println(a == b); // false
b[2][2] = 100;
System.out.println(a[2][2]);// 100
上述为浅拷贝,a,b为两个不同的对象,但指向的是同一地址。因为是包含对象的对象。看下jdk描述:
尤其注意红框部分:
即用clone()时,除了基础数据和String类型的不受影响外,其他复杂类型(如集合、对象等)还是会受到影响的,除非你对每个对象里的复杂类型又进行了clone(),但是如果一个对象的层次非常深,那么clone()起来非常复杂,还有可能出现遗漏。
所以需要深克隆:实现Cloneable接口、重写clone方法
值得一提的是,在Java中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用
Date d = harry.getHireDay();
对d调用更改器方法同样会修改这个雇员对象的私有状态
日期
标准Java类库包含了两个类:
- Date类 GWT格林威治时间,科学标准时间(遵循了世界上大多数地区使用的阳历表示)
- LocalDate类 日历表示法(本地时间)
Date birthday = new Date();
Date deadline = birthday;
System.out.println(deadline); // Wed Jul 11 11:22:22 CST 2018 //
System.out.println(LocalDate.now()); // 2018-07-11
LocalDate date1999_12_31 = LocalDate.of(1999,12,31); // 1999-12-31
LocalDate localDate = date1999_12_31.plusDays(1000); // 返回一个新对象 不改变对象date1999_12_31
System.out.println(localDate);// 2002-09-26
GregorianCalendar someDay = new GregorianCalendar(1999,12,31);
someDay.add(Calendar.DAY_OF_MONTH, 1000);
System.out.println(someDay.get(Calendar.YEAR) + "-" + someDay.get(Calendar.MONTH) + "-" +
someDay.get(Calendar.DAY_OF_MONTH));// someDay对象状态会改变
只访问对象而不修改对象的方法称为访问器方法(access method),更改器方法(mutator method)会改变对象状态。
GregorianCalendar.add方法为更改器方法
LocalDate.getYear 和 GregorianCalendar.get 均为访问器方法
打印当前月份日历:
// 打印当月日历
LocalDate date = LocalDate.now();
System.out.println("\n\nMON TUE WED THU FRI SAT SUN");
// 获取月、日
int month = date.getMonthValue();
int day = date.getDayOfMonth();
// 获取本月第一天
date = date.minusDays(day - 1);
// 本月第一天第一天为周几?
int dayOfWeek = date.getDayOfWeek().getValue();
// 打印日历第一行的缩进
for (int i = 1; i < dayOfWeek; i++) {
System.out.print(" ");
}
// 打印主体
while (date.getMonthValue() == month) {
System.out.printf("%3d", date.getDayOfMonth());
if (date.getDayOfMonth() == day)
System.out.print("*");
else
System.out.print(" ");
date = date.plusDays(1);
if (date.getDayOfWeek().getValue() == 1)
System.out.println();
}
输出如下:
MON TUE WED THU FRI SAT SUN
1
2 3 4 5 6 7 8
9 10 11* 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
JavaSE8引入另外一些类来处理日期和时间
// 时间线 Instant 实现接口 Temporal
// 查看算法的运行时间
Instant start = Instant.now();
printCalendar();
Instant end = Instant.now();
Duration timeElapsed = Duration.between(start, end);
long millis = timeElapsed.toMillis();
System.out.println(millis); // 9
// 比较两个算法运行时间
Instant start1 = Instant.now();
printCalendar1();
Instant end1 = Instant.now();
Duration timeElapsed1 = Duration.between(start1, end1);
boolean faster = timeElapsed1.minus(timeElapsed).isNegative();
Instant为时间线上的某个点
Duration.between获取时间差。
日期调整器
TemporalAdjusters类提供了大量常见调整的静态方法。
// 日期调整器
LocalDate nextTuesday = LocalDate.of(2018, 7, 1).with(TemporalAdjusters.nextOrSame(
DayOfWeek.TUESDAY)); // 从2018.7.1号开始的下一个周二
// 实现自己的日期调整器
TemporalAdjuster NEXT_SUNDAY = w -> {
LocalDate result = (LocalDate) w; //需要强制转换 TemporalAdjuster任何一个实现类都可
do {
result = result.plusDays(1);
}while (result.getDayOfWeek().getValue()!=7);
return result;
};
System.out.println(LocalDate.now().with(NEXT_SUNDAY)); // 2018-7-15
// TemporalAdjusters.ofDateAdjuster(UnaryOperator<LocalDate> dateBasedAdjuster) w默认为LocalDate类型则无须强制转换
TemporalAdjuster NEXT_SATURDAY = TemporalAdjusters.ofDateAdjuster(w -> {
LocalDate date = w;
do {
date = date.plusDays(1);
} while (date.getDayOfWeek().getValue() != 6);
return date;
});
与遗留util.Date类的转换
Date birthday = new Date();
System.out.println(birthday); // Wed Jul 11 15:47:36 CST 2018
birthday.toInstant(); // 2018-07-11T07:47:36.172Z
Date.from(Instant.now()); // Wed Jul 11 15:50:40 CST 2018
隐式参数与显式参数
例如:
public void raiseSalary(double byPercent){
double raise = salary * byPercent / 100;
salary += raise;
}
其中byPercent为显式参数,salary为隐式参数(方法的调用目标的参数),我们也可使用关键字this表示隐式参数,这样便于将实例域与局部变量明显区分开。
final实例域
对象包含final实例域,构建对象时则必须初始化这样的域。即确保在每一个构造器执行之后。这个域的值被设置,并在后面的操作中,不能够再对其修改。
final修饰符大多应用于基本(primitive)类型域,或不可变(immutable)类的域(如果类中的每个方法都不会改变其对象,这种类就是不可变的类)
对于可变对象,使用final容易造成混乱:
private final StringBuiIder evaluations;
构造器构造:
evaluations = new StringBuilder();
final的修饰只是表示存储在evaluations变量中的对象引用不会指向其他StringBuilder对象。不过这个对象内容可以更改:
public void giveGoldStarO
{
evaluations.append(LocalDate.now() + ":Gold star!\n");
}
而String是个不可变的类。String中最核心的就是value[]值了,这个值不会被类中任何方法修改到。
private final char value[];
静态域与静态方法
将域定义为static,每个类中只有一个这样的域。
尔每一个对象对于所有的实例域都有自己的一份拷贝。
静态域可以理解为类域,属于类所有。
class Employee{
private static int nextId = 1;
private int id;
}
每一个雇员都有一个自己的id域,但是Employee所有实例都共享一个nextId域。即,1000个Employee对象,有1000个实例域id,但是只有一个静态域nextId。
即使没有一个雇员对象,静态域nextId也存在。它属于类,不属于任何独立的对象。
public void setld()
{
id = nextld;
nextld++;
}
静态变量使用较少,静态常量使用较多。
public static final double PI = 3.14159265358979323846;
Math.PI就可以取到值。
如果没有static这个字段,每个Math对象都将会有一份PI拷贝。
public final static PrintStream out = null;
System.out也是静态常量。
每个类对象都可以对公有域进行修改,最好不要将域声明为public。然而公有常量(final域)是没问题的。因为被声明为final,out不会被更改为其他打印流。
总结:类中声明static域可以看作是公有量,static+final修饰的为公有常量。
/**
* Reassigns the "standard" output stream.
*
* <p>First, if there is a security manager, its <code>checkPermission</code>
* method is called with a <code>RuntimePermission("setIO")</code> permission
* to see if it's ok to reassign the "standard" output stream.
*
* @param out the new standard output stream
*
* @throws SecurityException
* if a security manager exists and its
* <code>checkPermission</code> method doesn't allow
* reassigning of the standard output stream.
*
* @see SecurityManager#checkPermission
* @see java.lang.RuntimePermission
*
* @since JDK1.1
*/
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
private static native void setOut0(PrintStream out);
setOut方法可以将System.out设置为不同的流,即修改final变量的值。
因为setOut0方法是native,本地方法不是用Java语言实现的,可以绕过Java的存取控制机制。
静态方法
静态方法是一种不能向对象实施操作的方法。可以认为静态方法是没有隐式的参数,没有this参数的方法。
例如Emloyee类中静态方法getNextld,
public static int getNextld()
{
return nextld; // returns static field
}
这里不能访问实例域Id,因为静态方法不可以操作对象。但是可以访问类域。
建议用类名访问静态方法,不建议使用对象的引用调用静态方法,防止混淆。
下面两种情况才使用静态方法:
- 方法只需要访问类的静态域(Emloyee.getNextld)
- 方法不需要访问对象状态,所需参数都是通过显式参数提供(eg: Math.pow)。
静态方法main方法不对任何对象进行操作。启动程序时,还没有任何一个对象,静态的main方法将执行并创建程序所需要的对象。
方法参数
1.对于基本类型参数:
public static void tripieValue(double x){ // 并不能修改x的值
x = 3 * x;
}
double percent = 10;
tripieValue(percent); // percent = 10;
- x被初始化为percent的一个copy,10
- x=x*3; 则x=30,但percent仍为10
- 方法结束,参数变量x不再使用。
方法不可能修改一个基本数据类型的参数
2.对于对象参数:
public static void tripleSalary(Employee x){ // works
x.raiseSalary(200) ;
}
harry = new Employee(. . .) ;
tripleSalary(harry) ; // harry.salary+200
- x被初始化为harry的一个copy,这里是一个对象的引用。
- x.raiseSalary(200),这个被引用的对象salary上涨200
- 方法结束,参数变量x不再使用,对象变量harry继续引用该对象。
3.Java对对象采用的就是引用调用吗?答案是No
看个例子,swap为交换x,y
public void swap(Employee x, Employee y){
Employee tmp = x;
x = y;
y = tmp;
}
...
Employee a = new Employee("Alice", . . .);
Employee b = new Employee("Bob", . . .);
swap(a, b);
- 方法结束时参数变量x和y被丢弃了。原来的变量a,b依然引用之前的对象。
Java 程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的
总结:Java中方法参数的使用情况:
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)
- 一个方法可以改变一个对象参数的状态
- 一个方法不能改变对象参数原有的引用(不能让对象参数引用一个新的对象)
重载(overloading)
多个方法具有相同的名字、不同的参数即重载。签名(方法名+参数类型)。
finalize方法
可以为任何一个类添加 finalize 方法,它将在垃圾回收器清楚对象之前调用。
但在实际应用中,不要依赖于使用finalize 方法回收任何短缺的资源,因为很难知道这个方法什么时候才能调用。
包
import java.util.;
import static java.lang.Math.;//可以直接使用sqrt(pow(x, 2) + pow(y, 2));
文档注释
JDK有个很有用的工具: javadoc,可以由源文件生成一个HTML文档
为以下几部分编写注释:
- 包
- 公有类与接口
- 公有的和受保护的构造器及方法
- 公有的和受保护的域
以/** ... */的格式,自由格式文本第一局应该是一个概要性的句子。javadoc自动将这些句子抽取出来形成概要页。
<em>用于强调
<strong>着重强调
<img>图片
不要使用<hl>或<hr> 会与文档格式产生冲突。
{@code ...}等宽代码
可以参考源码中的标签
类注释
必须放在import语句之后,类定义之前。需要注意的是@see 分隔类名与方法名用的是#,多个see标签要放在一起
/**
* The {@code String} class represents character strings. All
* string literals in Java programs, such as {@code "abc"}, are
* implemented as instances of this class.
* @author Lee Boynton
* @see java.lang.Object#toString()
*/
方法注释
@param 变量描述
@return 描述
@throw 类描述
/**
* Allocates a new {@code String} that contains characters from a subarray
* of the character array argument. The {@code offset} argument is the
* index of the first character of the subarray and the {@code count}
* argument specifies the length of the subarray. The contents of the
* subarray are copied; subsequent modification of the character array does
* not affect the newly created string.
*
* @param value
* Array that is the source of characters
*
* @param offset
* The initial offset
*
* @param count
* The length
*
* @throws IndexOutOfBoundsException
* If the {@code offset} and {@code count} arguments index
* characters outside the bounds of the {@code value} array
*/
public String(char value[], int offset, int count)
域注释
只需要对公有域(通常指的是静态常量)建立文档
/**
* The {@code double} value that is closer than any other to
* <i>pi</i>, the ratio of the circumference of a circle to its
* diameter.
*/
public static final double PI = 3.14159265358979323846;
包与概述注释
想要产生包注释,需要在每一个包目录中添加一个单独的文件。两种方式
- 提供一个package.html命名的HTML文件。<body></body>中的所有文本都会被抽取出来
- 提供一个以package-info.java命名的Java文件。这个文件必须包含一个初始的以 /**
和 */ 界定的 Javadoc 注释, 跟随在一个包语句之后。它不应该包含更多的代码或注释。
为所有的源文件提供一个概述性的注释。这个注释将被放置在一个名为 overview.html 的文件中,这个文件位于包含所有源文件的父目录中。标记 <body>... </body> 之间的所有文本将被抽取出来。当用户从导航栏中选择“Overview” 时,就会显示出这些注释内容。
注释抽取
切换到包含想要生成文档的源文件目录。 如果有嵌套的包要生成文档, 例如 com.horstmann.corejava, 就必须切换到包含子目录 com 的目录(如果存在 overview.html 文件的
话, 这也是它的所在目录 )如果是一个包,应该运行命令:
javadoc -d docDirectory nameOfPackage
或对于多个包生成文档, 运行:
javadoc -d docDirectory nameOfPackage1 nameOfPackage2 . . .
如果文件在默认包中, 就应该运行:
javadoc -d docDirectory *. java
可参考javadoc相关文档
继承
例如Manager和Employee,Manager除了享有Employee待遇和属性之外,还会有其他权利等。根本上讲,Manager is a Employee,is-a 关系是继承的一个明显特征
- Java中所有的继承都是公有继承。
- 域继承、方法继承
- 方法覆盖、重写
- super可调用父类的方法、构造器。调用父类构造器的语句只能作为当前构造器的第一个语句出现。
- 不支持多继承
- 在覆盖一个方法的时候,子类方法不能低于超类方法的可见性
阻止继承:final类和方法
不允许扩展的类被称为final类,被final修饰的方法不能被子类覆盖。
域也可以被声明为 final。对于 final 域来说,构造对象之后就不允许改变它们的值了。 不过, 如果将一个类声明为 final, 只有其中的方法自动地成为 final,而不包括域。
将方法或类声明为 final 主要目的是: 确保它们不会在子类中改变语义。
多态
一个对象变量可以指示多种实际类型的现象被称为多态,在运行时能够自动选择调用哪个方法的现象称为动态绑定。如下e,即可引用Employee对象,又可引用Manager对象:
Manager boss - new Manager("Carl Cracker", 80000,1987, 12 , 15) ;
boss.setBonus(5000) ;
Employee[] staff = new Employee[3];
staff[0] = boss;
staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);
for (Employee e : staff)
System.out.println(e.getName0 + " " + e.getSalary());
对象变量是多态的,一个Employee变量既可以引用Employee对象也可引用Employee类的任何一个子类对象。
- 子类数组的引用可以转换成父类数组的引用,不需要强制转换。
- 将一个超类的引用赋给子类变量,必须进行类型转换。并使用instanceof检查。
- 只能在继承层次内进行类型转换。
养成这样一个良好的程序设计习惯: 在进行类型转换之前, 先查看一下是否能够成功地转换。instanceof
if (staff[1] instanceof Manager){
boss = (Manager) staff[1]:
}
在一般情况下,应该尽量少用类型转换和 instanceof 运算符。并检查一下超类的设计是否合理。
抽象类
- 包含抽象方法的类必须被声明为抽象的。
- 抽象方法充当着占位的角色。可以提供公共的方法,但不需要写具体实现。
- 抽象类中也可以包含具体数据和具体方法。
- 扩展抽象类:
- 子类中只定义部分抽象类方法或不定义抽象类方法,子类必须被标记为抽象类。
- 子类定义了全部的抽象方法,子类就不是抽象的了。
- 抽象类不能被实例化
- 可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象
Object
equals方法
关于重写equals,需要注意遵循自反、对称、传递、一致性,equals(null)=false;
- 如果子类能够拥有自己的相等概念, 则对称性需求将强制采用 getClass 进行检测。
- 如果由超类决定相等的概念,那么就可以使用 instanceof进行检测, 这样可以在不同子类的对象之间进行相等的比较,并且应将超类.equals设为final
完美equals建议:
- 显示参数名定为otherObject
- 检测 this 与 otherObject 是否引用同一个对象
- 检测 otherObject 是否为 null
- 比较 this 与 otherObject 是否属于同一个类,如果 equals 的语义在每个子类中有所改变,就使用 getClass 检测,如果所有的子类都拥有统一的语义,就使用 instanceof 检测。
- 将otherObject转为相应的类型。
- 对所有需要比较的域进行比较了。使用=比较基本类型域,使用 equals 比较对象域。如果所有的域都匹配, 就返回 true; 否则返回 false。
- 如果在子类中重新定义 equals, 就要在其中包含调用 super.equals(other)。
public boolean equals(Object otherObject){
if (this = otherObject) return true;
if (otherObject = null) return false;
if (getClass() != otherObject.getClass()) return false; // equals 的语义在每个子类中有所改变
if (!(otherObject instanceof ClassName)) return false; //所有的子类都拥有统一的语义
ClassName other = (ClassName) otherObject;
return field1 == other.field1 && Objects.equa1s(field2, other.field2) && ... ;
}
String中的equals
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
hashCode方法
如果重新定义 equals方法, 就必须重新定义hashCode 方法,以便用户可以将对象插人到散列表中
String中的hashCode()
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
————for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
- hashCode 方法应该返回一个整型数值(也可以是负数
- 合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀
比较好的做好是组合多个散列值,调用 Objects.hash 并提供多个参数:
public int hashCode(){
return Objects,hash(name, salary, hireDay);
}
如果存在数组类型的域, 那么可以使用静态的 Arrays.hashCode 方法计算一个散列码,这个散列码由数组元素的散列码组成
toString方法
强烈建议为自定义的每一个类增加 toString 方法。
Employee:
public boolean equals(Object otherObject){
// a quick test to see if the objects are identical
if (this == otherObject) return true;
// must return false if the explicit parameter is null
if (otherObject == null) return false;
// if the classes don't match, they can 't be equal
if (getClass() != otherObject.getClass() ) return false;
// now we know otherObject is a non-null Employee
Employee other = (Employee) otherObject;
// test whether the fields have identical values
return Objects.equals(name, other.name) && salary == other.salary&& Objects.equals(hireDay, other.hireDay) ;
public String toString(){
return getClass().getName() + "[name:" + name +",salary:" + salary + ",hireDay=" + hireDay + "]";
}
Manager:
public boolean equals(Object otherObject){
if (!super.equals(otherObject)) return false;
Manager other = (Manager) otherObject;
// super.equals checked that this and other belong to the same class
return bonus == other.bonus;
}
public String toString(){
return super.toString() + "[bonus=" + bonus + "]";
}
泛型数组列表
ArrayList<Employee> staff = new ArrayList<Employee>();
如果调用 add 且内部数组已经满了,数组列表就将自动地创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。
如果已经清楚或能够估计出数组可能存储的元素数量, 就可以在填充数组之前调用
ensureCapacity方法:
staff.ensureCapacity(100);
ArrayList<Employee> staff = new ArrayList<>(100) ;
注意区分 数组列表和数组 分配空间的区别:
- new ArrayList<>(100) // capacity is 100
数组列表只是拥有保存100个元素的潜力(实际上,重新分配空间的话,会超过100),但是最初(初始化构造),数组列表不含有任何元素 - new Employee[100]// size is 100
数组有100个位置可用
确认数组列表的大小不再发生变化,可以调用trimToSize方法。这个方法将存储区域的大小调整为当前元素数量所需要的存储空间数目。垃圾回收器将回收多余的存储空间。
list.add(0,"hello"); //添加新元素
list.set(0,"world"); //替换已有元素内容
list.add(n,e); // 位于n之后所有元素向后移动一个位置
list.remove(n); // 移除n下标元素,且之后的元素都前移一个位置
转数组:
ArrayList<X> list = new ArrayList();
while (. . .){
x = . .
list.add(x);
}
X[] a = new X[list.size()];
list.toArray(a);
- 为了能够看到警告性错误的文字信息,要将编译选项置为 -Xlint:unchecked
- 能确保不会造成严重的后果, 可以用@SuppressWamings("unchecked") 标注来标记这个变量能够接受类型转换
对象包装器和自动装箱
包装器(wrapper):Integer类对应基本类型int
Integer、Long、Float、Double、Short、Byte(前6个类派生于公共的超类Number)、Character 、 Void 和 Boolean
- 对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。
- 对象包装器类还是 final , 因此不能定义它们的子类。
public final class Integer extends Number implements Comparable<Integer>
自动装箱(autoboxing)
ArrayList<Integer> list = new ArrayList<>();
list.add(3);
// 自动变成
list.add(Integer.value0f(3));
list.add(3)的调用即为自动装箱
自动拆箱
int n = list.get(i);
即编译器会翻译为int n = list.get(i).intValue();
Integer n = 3;
n++;
编译器自动插入一条对象拆箱指令,然后自增,然后再将结果装箱
Integer a = 1000;
Integer b = 1000;
System.out.println(a == b); // false
Integer c = 100;
Integer d = 100;
System.out.println(c == d); // true
== 检测的是对象是否指向同一个存储区域。自动装箱将经常出现的值包装到同一个对象中,即:
自动装箱规范要求 boolean、byte、char 127,介于-128 ~ 127之间的 short 和 int 被包装到固定的对象中。
所以经过自动装箱,a,b所指向的100是同一个对象,而c,d所指向的1000是两个不同的对象。
为了避免这种不确定,两个包装器对象比较时,调用equals方法
在一个表达式中混用Integer和Double,Integer会拆箱,提升为double,再装箱为Double
Integer n = 1;
Double x = 2.0;
System.out.println(true ? n : x); // 1.0
敲黑板~ 装箱和拆箱是编译器认可的, 而不是虚拟机。编译器在生成类的字节码时, 会插入必要的方法调用。虚拟机只是执行这些字节码。
有些人认为包装器类可以用来实现修改数值参数的方法, 然而这是错误的。
public static void triple(int x) {// won't work
x = 3 * x; // modifies local variable
}
public static void triple(Integer x) // won't work
因为Java是值传递:int基本类型参数不会变、Integer对象不可变:包含在包装器中的内容不会变。
如果需要一个修改数值参数值的方法,需要使用在 org.omg.CORBA包中定义的持有者(holder)类型,包含IntHolder、BooleanHolder等。每个持有者类型都包含一个共有域值,通过它可以访问存储在其中的值。
public final class IntHolder implements Streamable {
/**
* The <code>int</code> value held by this <code>IntHolder</code>
* object in its <code>value</code> field.
*/
public int value;
如下:
{
...
IntHolder y = new IntHolder();
y.value = 3;
changeInt(y);
System.out.println(y.value); // 9
...
public static void changeInt(IntHolder x){
x.value = x.value * 3;
}}
参数数量可变的方法
eg printf:
public PrintStream printf(String format, Object ... args) {
return format(format, args);
}
这里的 ... 省略号 是Java代码的一部分,表明这个位置可以接收任意数量的对象。
即fmt的第i个格式说明符与arg[i]的值匹配
public static double max(double ... values){
double largest = Double.NEGATIVE_INFINITY;
for (double val : values)
if (val > largest) largest = val;
return largest;
}
...
// 调用
double[] values = {1.2, 123123.12, -0.999, 12312};
System.out.println(max(values)); // 123123.12
System.out.println(max(1.2, 123123.12, -0.999, 12312)); // 123123.12
枚举类
public enum Size {
SMALL("S"), MEDIUM("M"), LARGR("L"), EXTRA_LARGE("XL");
private String abbr; // 缩写
private Size(String abbr) {
this.abbr = abbr;
}
public String getAbbr() {
return abbr;
}
public static void main(String[] args) {
SMALL.getAbbr(); // S
SMALL.toString(); // SMALL
Size s = Enum.valueOf(Size.class, "SMALL");// toString的逆方法 valueOf
Size[] sizes = Size.values();
System.out.println(sizes);
Size.EXTRA_LARGE.ordinal(); // 声明中枚举常量的位置 3
}
}
反射(reflective)5.7
能够分析类能力的程序称为反射。反射机制可以用来:
- 在运行时分析类的能力。
- 在运行时查看对象
- 实现通用的数组操作代码
- 利用Method对象(类似于C++中的函数指针)
继承的设计技巧
- 将公共操作和域放在超类
- 不要使用受保护的域
- 使用继承实现“ is-a” 关系
- 除非所有继承的方法都有意义, 否则不要使用继承
- 在覆盖方法时,不要改变预期的行为
- 使用多态,而非类型信息
- 不要过多地使用反射