java-基础

数据类型

byte(1字节), short(2字节), int(4字节), long(8字节), float(4字节), double(8字节), char(2字节), boolean(true, false)

Java 没有任何无符号(unsigned) 形式的 int、long、short 或 byte 类型。

char 类型描述了 UTF-16 编码中的一个代码单元,占2字节,(有的字符编码占用两个代码单元)建议不要在程序中使用 char 类型。

整数被 0 除将会产生一个异常, 而浮点数被 0 除将会得到无穷大或 NaN 结果。

&&和||运算符是按照“ 短路”方式来求值的: 如果第一个操作数已经能够确定表达式的值,第二个操作数就不必计算了。

>>运算符会用符号位填充高位,>>>运算符会用 0 填充高位。

移位运算符的右操作数要完成模32的运算(除非左操作数是long类型,在这种情况下需要对右操作数模 64 )。 例如,1<<35 的值等同于 1<<3 。

1
2
3
4
5
6
7
8
9
10
System.out.println ( 1.0 / 0 );
//output Infinity

int valInt = 1<<35;
System.out.println ( valInt);
//output 8

long valLong = (long)1<<35;
System.out.println ( valLong );
//output 34359738368

字符串“xxoo”是unicode字符x、x、o、o组成的序列。字符串、String类对象是不可变对象。

判断字符串内容相等用equals,==运算符是判断字符串的地址是否相等。

String取length是取的字符串的代码单元的个数。

1
2
String hehe = "😄";
System.out.println ( hehe.length () ); //2

Console类用于输入密码,为了安全起见,返回的密码存放在一维字符数组中, 而不是字符串中。

1
因为字符串有字符串常量池,导致密码长期保存在内存中,容易通过jmap+jhat分析出密码来,而使用字符数组,用完即可置为null,相对安全

在 C++ 中, 可以在嵌套的块中重定义一个变量。 在内层定义的变量会覆盖在外层定义的变量。 但是,在java中不允许嵌套块中重定义外层的变量。

在 Java 中, 允许数组长度为 0。 在 Java 中, 允许将一个数组变量拷贝给 另一个数组变量。这时, 两个变量将引用同 一个数组。如果希望将 一个数组的所有值拷贝到一个新的数组中去, 就要使用 Arrays 类的 copyOf 方法。可以通过二维数组创建不规则数组(先创建行,再单独创建每行的数组)

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//浮点数默认为双精度的(double)
//float i = 1.1;
float i = (float) 1.1;

short j = 1;
//整型默认为int
//j = j + 1;
// += 运算自动向下转型 int -> short
j += 1;

//不使用临时变量交换两个整数的值
//方法1. 使用异或操作swap a 和 b 的值
int a = 456;
int b = 123;
a = a ^ b;
b = a ^ b;
a = a ^ b;
System.out.println ( a ); //123
System.out.println ( b ); //456

//方法2. 使用加减法运算
int a = 123;
int b = 456;
a = a + b;
b = a - b;
a = a - b;
System.out.println ( a );
System.out.println ( b );

//编译时能确定的String对象都放入常量池
//运行时才能确定或者new出的String对象都放入堆
String str1 = "I am String1"; //变量str1引用指向字符串常量池
final String str2 = "I am String"; //final修饰,str2是编译期常量
String str3 = "I am String"; //变量str3引用指向字符串常量池
String str4 = str2 + 1; // str2 + 1 得到的也是个常量,放在常量池
String str5 = str3 + 1; // str3 + 1 得到的是个变量,放在堆上 (因为str3变量在运行时才可以取到它所引用的值)
System.out.println ( str1 == str4 );
System.out.println ( str1 == str5 );

//try语句带return,finally执行时机
try {
if (a == 123) {
System.out.println ( "in try return a = " + a );
return; // 执行到此处,记录return的值,然后执行finally代码块,再然后return
}
} finally {
a = 1234;
System.out.println ( "in finally a = " + a );
}

类和对象

识别类的简单规则是在分析问题的过程中寻找名词, 而方法对应着动词。

关系 含义
继承(is a) is a,类A扩展自类B
接口实现 类实现接口中的方法
依赖(use a) use a,一个类的方法操纵另一个类的对象
聚合(has a) has a,类A的对象包含类B的对象(属性关联)
关联 通过属性、方法关联

构造器总是伴随着 new 操作符的执行被调用。

不要编写返回引用可变数据域对象的访问器方法。 如果需要返回一个可变数据域的拷贝, 就应该使用 clone。

  1. Get不使用clone,直接返回引用

  1. Get返回引用对象的clone

对于可变的类, 使用 final 修饰符可能会对读者造成混乱。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Test {
private final StringBuilder evaluations;

public Test(){
evaluations = new StringBuilder ( );
}

//final 关键字只是表示存储在 evaluations 变量中的对象引用不会再指示其他 StringBuilder 对象。 不过这个对象可以更改
public void giveGoldStar() {
evaluations.append ( new Date () + ": Gold Star!\n" );
}

public void printTest() {
System.out.println ( evaluations );
}
}

静态变量使用得比较少, 但静态常量(static final)却使用得比较多。 静态方法是没有this参数的方法,在一个非静态方法中this参数是该方法的隐式参数,代表操作此方法的对象。

java方法调用参数是按值传递,下面总结一下 Java 中方法参数的使用情况:

• 一个方法不能修改一个基本数据类型的参数 (即数值型或布尔型)。

• 一个方法可以改变一个对象参数的状态。

• 一个方法不能让对象参数引用一个新的对象。

类属性与局部变量的主要不同点: 必须明确地初始化方法中的局部变量。 但是,如果没有初始化类中的属性, 它将会被自动初始化为默认值(0、false 或 null ),但是,这并不是一种良好的编程习惯。

仅当类没有提供任何构造器的时候,系统才会提供一个默认的构造器 。

可以在类定义中, 直接将一个值赋给任何属性。在执行构造器之前,将会先执行赋值操作。

类构造初始化次序:

  1. static 属性赋值
  2. static 初始化块
  3. 实例属性赋值
  4. 实例属性初始化块
  5. 构造函数体

可以为任何一个类添加 finalize 方法。finalize 方法将在垃圾回收器清除对象之前调用。 在实际应用中, 不要依赖于使用 finalize 方法回收任何短缺的资源,这是因为很难知道这个方法什么时候才能够调用。

从编译器的角度来看,嵌套的包之间没有任何关系。 例如, java.util 包与 java.util.jar 包毫无关系。每一个都拥有独立的类集合。

将包中的文件放到与完整的包名匹配的子目录中。

利用 -classpath 选项设置类路径是首选的方法, 也可以通过设置 CLASSPATH 环境变量 完成这个操作。

1
2
3
java -classpath c:\classdir;.;c:\archives\archive.jar MyProg
//或者
export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar

继承

如果子类的构造器没有显式地调用超类的构造器,则将自动地调用超类默认(没有参数 ) 的构造器。 如果超类没有不带参数的构造器,并且在子类的构造器中又没有显式地调用超类的其他构造器, 则 Java 编译器将报告错误。

是否应该设计为继承关系的一个简单规则: “is-a” 规则。它的另一种表述法是置换法则。它表明程序中出现超类对象的任何地方都可以用子类对象置换。

1
2
3
Employee e;
e = new Employee();
e = new Manager(); //ok, Manager extends from Employee

在 Java 中,子类数组的引用可以转换成超类数组的引用,而不需要采用强制类型转换。但是一定要注意这种用法会引起以下类型紊乱的错误。

1
2
3
4
Manager[] managers = new Manager[10];
Employee[] staff = managers; //ok, but now staff and managers reference to the same Objects
staff[0] = new Employee(); //ok
managers[0].setBonus(1000); //error, this will arise ArrayStoreException

返回类型不是签名的一部分, 因此,在覆盖方法时,一定要保证返回类型的兼容性。允许子类将覆盖方法的返回类型定义为原返回类型的子类型。

方法调用的两种方式:静态绑定(private方法、static方法、final方法),动态绑定(其他)

如果将一个类声明为 final, 只有其中的方法自动地成为 final, 而不包括属性。

只能在继承层次内进行类型转换。 在将超类转换成子类之前,应该使用 instanceof 进行检查。 但是尽量不要使用类型转换,如果必须使用类型转换,则应该检查超类的设计是否合理。

包含一个或多个抽象方法的类本身必须被声明为抽象的。 但是,类即使不含抽象方法, 也可以将类声明为抽象类。

在 Java 中,只有基本类型 ( primitive types ) 不是对象, 所有的数组类型,不管是对象数组还是基本类型的数组都是扩展自Object的类对象。

为一个类编写equals方法的完美建议:

  1. 显示参数命名为otherObject
  2. 检测this与otheObject是否引用同一对象:
1
2
> if (this == otherObject) return true;
>
  1. 检测otherObject是否为null
1
2
> if (otherObject == null) return false;
>
  1. 检测this与otherObject是否属于同一个类:

如果equals的语义在每个子类有所改变,则使用getClass检测

1
2
> if (getClass() != otherObject.getClass()) return false;
>

如果所有子类使用相同的语义,即由超类决定相等的概念,则使用instanceof检测

1
2
> if (!(otherObject instanceof ClassName)) return false;
>
  1. 将otherObject转换为相应的类类型变量
1
2
> ClassName other = (ClassName) otherObject;
>
  1. 开始属性域的比较,使用==比较基本类型,使用Objects.equals比较对象

如果重新定义 equals 方法, 就必须重新定义 hashCode 方法。

强烈建议为自定义的每一个类增加toString方法。

对象包装器类(Integer,Long,Float,Double,Short,Byte,Character,Void,Boolean)是final类, 因此不能定义它们的子类。

自动装箱规范要求 介于 -128 ~ 127 之间的byte, short , int, char(0~127) 被包装到固定的对象中(ByteCache,ShortCache,IntegerCache,CharacterCache)。 因此

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
32
33
34
Integer a = 1000;
Integer b = 1000;
if (a == b) // false

Integer a = 100;
Integer b = 100;
if (a == b) // true

System.out.println ( "--------Short--------" );

Short s1 = 12;
Short s2 = 12;
Short s3 = 129;
Short s4 = 129;
System.out.println ( s1 == s2 ); //true
System.out.println ( s3 == s4 ); //false

System.out.println ( "--------Byte--------" );

Byte b1 = 13;
Byte b2 = 13;
Byte b3 = 127;
Byte b4 = 127;
System.out.println ( b1 == b2 ); //true
System.out.println ( b3 == b4 ); //true

System.out.println ( "--------Character--------" );

Character c1 = 56;
Character c2 = 56;
Character c3 = 156;
Character c4 = 156;
System.out.println ( c1 == c2 ); //true
System.out.println ( c3 == c4 ); //false

自动装箱拆箱陷阱

1
2
3
4
5
6
7
8
9
10
11
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer e = 321;
Integer e1 = 300;
Integer e2 = 21;
Long g = 3L;
System.out.println ( e == (e1+e2) ); //true == 在遇到算术运算后,自动拆箱,比较值相等
System.out.println ( c.equals ( a+b ) ); //true a+b 算术运算后,自动装箱为Integer
System.out.println ( g == (a+b) ); // true a+b 算术运算后,自动拆箱为 3
System.out.println ( g.equals ( a+b ) ); //false equals方法不会处理数据类型转换的关系
-------------本文结束感谢您的阅读-------------
Good for you!