Java编程思想
这是一个通过对《Java编程思想》(Think in java)第四版进行阅读同时对java内容查漏补缺的系列。一些基础的知识不会被罗列出来,这里只会列出一些程序员经常会忽略或者混淆的知识点。
所列知识点全部都是针对自己个人而言,同时也欢迎大家进行补充。
第一章(对象导论)
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello every one,I'm cpacm");
}
}
这一章是java的整体介绍,让我们先熟悉了java是什么。其具体的内容都会在后面介绍。
面向基本语言的五大特性
- 万物皆为对象。
- 程序是对象的集合,它们通过发送消息来告知彼此所要做的。
- 每个对象都拥有其类型。
- 某一特定类型的所有对象都可以接收同样的消息。
第二章(一切都是对象)
一、存储
p22
栈(堆栈):存放基本类型变量和对象引用变量。位于RAM区
堆:存放new得到的对象和数组。也位于RAM区
常量存储:存放常量,包括静态变量。
二、基本类型
p26
基本数据类型在没有初始化的时候会获得一个默认值。
基本数据类型 | 默认值 | 大小 |
---|---|---|
boolean | false | 未确定 |
char | null | 16bits |
byte | 0 | 8bits |
short | 0 | 16bits |
int | 0 | 32bits |
float | 0f | 32bits |
long | 0L | 64bits |
double | 0d | 64bits |
tip1:String不是基本数据类型
tip2:上面的初始默认值并不适用于方法内部变量。其默认初始化值未知。当使用未初始化的变量编译器会返回错误
public class BaseType {
static boolean b;
static char c;
static byte bt;
static short s;
static int i;
static float f;
static long l;
static double d;
public static void main(String[] args) {
System.out.println("类变量——"+"boolean:"+b+" char:"+c+" byte:"+bt+" short:"+s+" int:"+i+" float:"+f+" long:"+l+" double:"+d);
}
}
//类变量——boolean:false char: byte:0 short:0 int:0 float:0.0 long:0 double:0.0
第三章(操作符)
一、别名现象
p40
当两个变量包含的是同一个引用时,修改其中一个变量的值另一个变量的值也同时改变。记住new出来的对象的=赋值都是只传递引用。
Tip:减少为对象赋值。
class T{
int i;
}
public class Assigment {
public static void main(String[] args) {
T t1 = new T();
T t2 = new T();
t1.i = 5;
t2.i = 8;
t1 = t2;
t1.i = 10000;
System.out.println(t2.i);
}
}
//10000
二、equals方法
p45
在自定义的对象中使用equals方法时需要覆盖此方法,否则默认是比较引用。
三、短路
p47
其现象本质为:当已经确定一个逻辑表达式的结果时不会再计算剩余的部分。
public class ShortCircuit {
public static void main(String[] args) {
// TODO Auto-generated method stub
boolean flag1 = test1()&&test2()||test3()&&test4();
System.out.println("\n");
boolean flag2 = test1()&&test3()||test2()&&test4();
}
static boolean test1(){
System.out.println("test1");
return true;
}
static boolean test2(){
System.out.println("test2");
return false;
}
static boolean test3(){
System.out.println("test3");
return true;
}
static boolean test4(){
System.out.println("test4");
return false;
}
}
/*
test1
test2
test3
test4
test1
test3
*/
boolean flag2 = test1()&&test3()||test2()&&test4();
若test1为true,test3为true时,因为前面这部分已经确定为true,所以后面部分不会被调用。
四、e
p49
科学与工程领域中,"e"代表自然对数的基数,为2.718。
而在C,C++和java(或者更多的语言)中,"e"代表“10的幂次”
$1.39e-43f = 1.39*10^{-43}$
五、位操作
p49
与,或,异或,非 &,|,^,~
与:所有的位都为1则输出1,否则输出0;
或:只要有一个位是1就输出1;
异或:两个位值相等时输出1;
非:1输出0,0输出1.
位运算不会出现短路现象。p50
移位操作符:
$<<$:操作数向左移动,低位补0;
$>>$:操作数向右移动,(1)符号为正时,高位补0,(2)符号为负时,高位补1;
$>>>$:java独有操作符,操作数向右移动,高位统一补0。
char,byte,short进行移位操作时先会转成int类型,即32位
public class URShift {
public static void main(String[] args) {
int i = 1024;
System.out.println(Integer.toBinaryString(i));
i >>= 10;
System.out.println(Integer.toBinaryString(i));
i = -1;
System.out.println(Integer.toBinaryString(i));
i >>>= 10;
System.out.println(Integer.toBinaryString(i));
i <<= 1;
System.out.println(Integer.toBinaryString(i));
short s = -1;
s >>>= 10;//s移位后得到的结果在赋值时会强行转为int,所以移位后的s已经是int型
System.out.println(Integer.toBinaryString(s));
s = -1;
System.out.println(Integer.toBinaryString(s>>>10));
}
}
/*
10000000000
1
11111111111111111111111111111111
1111111111111111111111
11111111111111111111110
11111111111111111111111111111111
1111111111111111111111
*/
六、截尾
p55
将float或double转型为整数值时,总是对数字进行截尾,不会进行四舍五入。如果想要得到舍入的结果可以使用Math.round()
public class CastingNumbers {
public static void main(String[] args) {
double d = 1.7d;
int i = (int)d;
System.out.println(i);
i = (int) Math.round(d);
System.out.println(i);
}
}
//1
//2
第四章(控制执行流程)
一、goto 标签
goto关键词,在java中则是使用标签代替臭名昭著的goto。其实在java中也是最好不要用标签来跳转语句,太伤智商。。
写法
outer:
并放在迭代语句前。
(1)continue 标签
跳出所有循环,并到标签位置的语句,但会重新进入紧接后面的循环里。
(2)break 标签
跳出所有循环,且不再进入紧接后面的循环里。
public class LabeledFor {
public static void main(String[] args) {
System.out.println("测试continue————————————");
label:
for(int i=0;i<3;i++){
System.out.println("外部for循环"+i);
for(int j=0;j<3;j++){
if(j==1){
continue label;
}
System.out.println("内部循环"+j);
}
}
System.out.println("测试break————————————————");
label2:
for(int m=0;m<3;m++){
System.out.println("外部for循环"+m);
for(int n=0;n<3;n++){
if(n==1){
break label2;
}
System.out.println("内部循环"+n);
}
}
}
}
/*
测试continue————————————
外部for循环0
内部循环0
外部for循环1
内部循环0
外部for循环2
内部循环0
测试break————————————————
外部for循环0
内部循环0*/
第五章(初始化与清理)
一、重载
p81
重载主要以传入参数及顺序来区别。不能通过返回值来区别
以基本数据类型传入时:自动包装机制
(1)若传入的数据类型小于方法中声明的形式参数类型,实际数据类型就会提升。
byte->short->int->long->float->double
(2)如果传入的实际参数较大,就得通过类型转换执行窄化转换。
二、垃圾回收机制
p91
标记-清扫(Android中使用这个技术)
从堆栈和存储区出发,遍历所有的引用,进而找出所有存活的对象,并给与标记。遍历完成后,清理所有未被标记的对象。
停止-复制
先暂停程序的运行,然后将所有存活的对象复制到另一个堆,而没有复制的是可回收的内存。
三、初始化顺序
p94
所有的变量都会在任何方法(包括构造器)被调用之前得到初始化。p95
无论创建多少对象,静态数据都只占用一份存储区域,static不能作用于局部变量。且静态初始化动作只执行一次。
四、可变参数列表。
p102
void method(Object... args){}
调用 method();或method(new Object[]{1,2,3,4});
第七章(复用类)
一、toString()的自动调用
p126
有时候编译器会自动帮你调用toString()方法。
"source"+ source;这时候会自动调用source对象的toString方法。
二、构造函数调用顺序
p130
构造函数总是从基类开始。
class Insert{
Insert(){
System.out.println("Insert");
}
}
public class Beetle extends Insert{
Beetle(){
System.out.println("Beetle");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Beetle b = new Beetle();
}
}
//Insert
//Beetle
三、final
p140
对于基本类型,final使数值恒定不变;而用于对象引用,final使引用恒定不变,但对象本身是可以改变的。
private 属于 final 方法
static final是属于类属性,即能被类调用,不用实例化
final则需要实例化。
四、组合模式
组合模式可以看做是一颗树,每个枝干都可以长出新的枝干,它们的结构都是相同的。
将枝干抽象为一个类,里面包含链接下一个节点的方法,若需要不断的链接下一个节点只需要继承这个方法并实现。
示例:导航菜单
组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。
public abstract class Tab {
private String title;
public Tab(String title) {
this.title = title;
}
protected abstract void add(Tab tab);
protected abstract void romove(Tab tab);
}
public class CardTab extends Tab{
public CardTab(String title) {
super(title);
// TODO Auto-generated constructor stub
}
private List<Tab> tabs;
@Override
protected void add(Tab tab) {
// TODO Auto-generated method stub
tabs.add(tab);
}
@Override
protected void romove(Tab tab) {
// TODO Auto-generated method stub
tabs.remove(tab);
}
}
public class TabView {
public static void main(String[] args) {
// TODO Auto-generated method stub
CardTab rootTab = new CardTab("roottab");
CardTab tab1 = new CardTab("tab1");
CardTab tab2 = new CardTab("tab2");
CardTab tab3 = new CardTab("tab3");
rootTab.add(tab1);
rootTab.add(tab2);
rootTab.add(tab3);
CardTab tab4 = new CardTab("tab1-1");
CardTab tab5 = new CardTab("tab1-2");
tab1.add(tab4);
tab1.add(tab5);
}
}
/**
* 这样子Tab组成了一个导航列表,这就是一个简单的组合模式.
*/
第八章(多态)
多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术。
一、多态缺陷
p156
缺陷1:只有非private方法才可以被覆盖
class Super{
public int field = 0;
public int getField(){return field;};
}
class Sub extends Super {
public int field = 1;
public int getField() { return field; }
public int getSuperField() { return super.field; }
}
public class FiledAccess {
public static void main(String[] args) {
Super sup = new Sub(); // Upcast
System.out.println("sup.field = " + sup.field +
", sup.getField() = " + sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = " +
sub.field + ", sub.getField() = " +
sub.getField() +
", sub.getSuperField() = " +
sub.getSuperField());
}
}
输出:
sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
缺陷2:域和静态方法直接在编译时候进行解析,所以多态不会对其产生作用。
class StaticSuper {
public static String staticGet() {
return "Base staticGet()";
}
public String dynamicGet() {
return "Base dynamicGet()";
}
}
class StaticSub extends StaticSuper {
public static String staticGet() {
return "Derived staticGet()";
}
public String dynamicGet() {
return "Derived dynamicGet()";
}
}
public class StaticPolymorphism {
public static void main(String[] args) {
StaticSuper sup = new StaticSub(); // Upcast
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
} /* Output:
Base staticGet()
Derived dynamicGet() */
二、断言
p153
@Override作用:
断言,如果我们使用了这种annotation在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示
三、构造器构造顺序
p158
构造器在多态时的构造顺序:
class Meal {
Meal() {
P.print("Meal()");
}
}
class Bread {
Bread() {
P.print("Bread()");
}
}
class Cheese {
Cheese() {
P.print("Cheese()");
}
}
class Lettuce {
Lettuce() {
P.print("Lettuce()");
}
}
class Lunch extends Meal {
Lunch() {
P.print("Lunch()");
}
}
class PortableLunch extends Lunch {
PortableLunch() {
P.print("PortableLunch()");
}
}
public class Sandwich extends PortableLunch {
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
public Sandwich() {
P.print("Sandwich()");
}
public static void main(String[] args) {
new Sandwich();
}
}
class P {
public static void print(String s){
System.out.println(s);
}
}
输出:
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()
解释说明:
一个继承类实例化的时候必须要确保所使用的成员已经构建完毕,所以必须先调用基类的构造器,所以当实例化Sandwich对象时先调用其基类的构造方法:
Meal()
Lunch()
PortableLunch()
其次对成员变量进行初始化
Bread()
Cheese()
Lettuce()
最后调用构造器
Sandwich()
三、构造器初始化
p163
初始化的过程:
(1)在所有事物发生之前,将分配给对象的存储空间初始化为二进制的零。
(2)调用基类构造器。
(3)按照声明顺序调用成员的初始化方法。
(4)调用导出类(本体)的构造器主体。