Think in Java第四版 读书笔记1

第一章对象导论(Java的几个重要部分)

访问控制的目的:

1.权限控制 2.类创建者修改某些实现而不会影响类使用者

代码复用的方式:

1.继承 2.组合(composition UML中实心菱形+实线表示)

继承

(UML中空心三角+实心线表示)

基类与子类行为不同的产生方式

  1. 为子类新增方法
  2. 在子类覆盖(overriding)基类的原来的方法

两种关系

  1. 是一个 is-a 纯粹替代(子类和父类接口方法完全相同,子类的接口 方法可能在父类存在,反之亦然)
  2. 像是一个(子类和父类不完全相同,子类的接口 方法可能在父类不存在,下图为is-like-a的例子)

多态:

父类引用指向子类对象 ClassParent classA = new ClassChild(); 也就是将子类泛化成父类类型 在新增一个继承父类的子类类型时,可以轻松替换掉原来的子类类型,程序更新维护便利,在实际classA调用方法的时候编译器并不知道要调用的代码在什么地方,是在运行时才知道调用子类的对应方法的,这也称为后期绑定。

向上转型:

将子类看作是基类的过程称为向上转型(upcasting)

方向相反则是向下转型,向下转型可能导致ClassCastException,因为就像是一个形状不一定是圆,它也可能是矩形或者三角形.而参数化类型:泛型正是为此而生

Java 单根继承的好处:

多重继承很灵活但是复杂,而单根继承相对简单,效率更高。单根继承类型是确定的,这样优化了垃圾回收;垃圾回收往往因为不确定的类型而陷入僵局。

Java的容器列举

List(存储序列)Map(关联数组,建立Key value关系)Set(内容不可重复,实现基于map)队列 树 堆栈等

Java的内存分配与垃圾回收

C++对象的生命周期和存储位置可以在编写程序时决定,但是这样必须知道对象的确切数量生命周期和类型,在解决一般化或者特别复杂的问题时不够灵活。
Java则在堆(heap)内存池中动态创建对象。知道运行时才知道需要多少对象,生命周期如何,具体是什么类型。只有程序运行到相关代码行才能确定这一切。如果需要一个对象,可以随时在堆中创建,这是因为存储空间在运行时时动态管理的,也因此一般比在堆栈中创建和销毁对象需要更多的时间。
动态方式在创建对象时具有灵活性,所以适用于复杂系统。但是效率可能就没有C++高了。
因为Java采用了动态堆上分配内存的机制,因此Java垃圾回收可以自动回收,而像C++手动堆栈分配内存就没法做到自动回收,程序员手动回收时则容易出现遗漏后者错误

异常处理

在从错误状态进行可靠恢复的途径

并发编程

优点:多处理器同时执行任务,效率高 缺点:共享资源时存在隐患
网络编程
客户端编程与服务器编程

本章主要讲述Java的一些重要特点

第二章 一切都是对象

Java程序的存储位置

(书中总结的很好)

寄存器:

运算速度最快,但是数量有限,在Java中是不可直接控制的。从程序中无法感觉寄存器的存在

堆栈:

位于RAM(随机访问存储)中,多用于存储对象的引用,依靠指针的上下移动来工作。指针上移,便是释放一个对象,指针下移,表示分配一个新内存。因为需要上下移动指针,所以Java系统需要知道存储在堆栈中所有对象的确切生命周期。对战的速度仅次于寄存器。注意堆栈中存储了对象的引用但没有将实际对象存储在这里

堆:

也位于RAM中,用于存放Java对象。堆与堆栈的区别主要在于1.他们存储的内容不同2.堆不需要也不知道堆中存储的数据需要存活多久。当使用new新建一个对象时,堆中自会给其分配内存。因此相比于堆栈,堆中的内存分配更加灵活,但是因为这种灵活,也需要付出代价:堆中的内存分配和释放需要更多的时间

常量存储:

根据不同系统而不同,有的系统将常量存放在程序代码内部;而有的系统则会将常量与其他部分分离,在此情况下,可以将常量存放于ROM(只读存储器)–典型的例子是字符串常量池

非RAM存储:

数据可以脱离程序而存在,此类的两个典型是流对象和持久化对象。流对象通常发生在一个机器与另一个机器的数据传输,对象被转化成字节流或者字符流传输给另外一个对象。持久化对象则被存储与磁盘,需要时则可以将他们解析和转换成基于RAM,可恢复的常规的程序对象。
特例,基本类型的存储
因为使用new创建对象需要在堆栈生成一个引用,在堆中动态分配内存,对象如果比较小,往往效率不高。8大基本类型在堆栈中创建的变量存放的不是引用而直接存放值,这样就不必在堆中分配内存,因此基本类型的创建更为灵活。

当然基本数据类型都有与之对应的包装类,而包装类的创建对象的形式则和普通对象的创建方式一样。比如
Integer I = new Integer(10);
User user = new User();
在堆栈和堆中创建变量,分配内存的形式是一样的

数组的存储

数组在C与C++中是一个连续的内存块,而在Java中,创建数组则意味着创建了一个引用数组,并且每一个引用初始化为null
变量的作用域由花括号决定。

对象的作用域:

举个例子
{
String s = new String(“aaa”);
}
其中变量s的作用域在后花括号之后就消失了,但是s所指向的String对象仍然存在于堆栈中。在C++中,这样的对象积累起来,会导致内存占用,这会引起内存泄漏但是Java由自己的垃圾回收机制,不需要程序员手动处理类似问题,这样就避免嘲笑我忘记释放内存而产生问题。

变量的初始化

类的字段可以不进行初始化,Java会有一些默认初始化

类的字段如果是一个引用,则没有初始化,其值默认初始化为null.
而方法中的变量必须初始化,若非如此,Java会提示错误 变量没有初始化。

变量的区分

使用包名区分

使用其他构建

运用import导包

static关键字

类的static变量对于该类的所有对象来说只存在一个实例(static字段堆每个类来说只有一份存储空间),该变量可以直接使用类名获取其值,也就是说即使没有创建对象,也可以调用该变量。被static修饰的方法和变量在一些文献中也叫类方法和类变量。

编译运行的条件

1.安装JDK并配置好环境变量(目的是能找到java和javac两个命令)
2.javac Xxx.java
3.java Xxx

注释和嵌入式文档(关于如何写注释)

常见的单行 多行注释不需多说,本小节主要说的是Javadoc这个注释提取工具,在java文件中注释以一定格式编写,则使用javadoc可以提取注释并生成另外的文档,一般可以使用浏览器打开类似的文档,撰写注释是务必遵守一些语法,还会使用到一些html标签和@开头的javadoc标签(@See @link之类),在实际中其实用的比较少,但是我们可以看到我们使用的JDK的注释中确实出现类似描述的东西。由于用的比较少,就不详述了。

编码风格

驼峰式为主

本章主要讲述要创建第一个Java程序需要的要素,比如项目构建需要创建包名,需要导包,需要创建方法,变量,使用静态方法,如何注释并通过javadoc生成html文档等等

第三章 操作符(有些简单的操作符就略过了)

别名机制问题引入

其实就是引用传递的问题,问题出现在赋值和方法调用上
比如

public class Book {
	
	public Book(String name) {
		super();
		this.name = name;
	}

	String name;
	
	private void changeString(String s) {
		s = "zzz";
	}
	
	private void changeString(Book book) {
		book.name = "zzz";
	}
	
	public static void main(String [] args){
		Book book1 = new Book("Java");
		Book book2 = new Book("Android");
		System.out.println(book1.name);
		System.out.println(book2.name);
		
		book1 = book2;//赋值产生的引用传递  这句是问题关键
		book1.name = "Java Script";
		System.out.println(book1.name);
		System.out.println(book2.name);
		
		book2 = book1;
		book1.name = "Python";
		System.out.println(book1.name);
		System.out.println(book2.name);
		System.out.println();
		
		//参数传递时的引用传递
		String s = "abc";
		book1.changeString(s);//调用changeString方法
		System.out.println(s);
		
		String bookName = "C++";
		Book book = new Book(bookName);
		book.changeString(book);//调用changeString(Book)方法
		System.out.println(bookName);
		System.out.println(book.name);
		//核心: 基本类型赋值和方法调用都不会发生引用传递,都是拷贝值,只有引用类型的变量才有这两个问题。
		//这里应该时问题的引入,不理解也没关系,第六章应该会有详述
	}
}

输出

Java
Android
Java Script
Java Script
Python
Python

abc
C++
zzz

关于运算符

其优先级不需要死记硬背,有时候不记得可以使用括弧,而且这样程序的可读性更高。
对于++i --i i++ i–之类的操作符也很简单,不赘述

等价性

基本类型的等价性

直接用== !=判断即可,比如

	public static void main(String[] args) {
		String string1 = "sd";
		String string2 = "sd";
		System.out.println(string1 == string2);
	}

输出true

引用类型的等价性

对于Java已经定义好的引用类型(如Integer String Boolean)大多数可以使用equals来判断内容是否相等

	public static void main(String[] args) {
		String string1 = new String("sd");
		String string2 = new String("sd");
		System.out.println(string1 == string2);
		System.out.println(string1.equals(string2));
		
		Boolean boolean1 = new Boolean(false);
		Boolean boolean2 = new Boolean(false);
		System.out.println(boolean1 == boolean2);
		System.out.println(boolean1.equals(boolean2));
		
		Integer integer1 = new Integer(15);
		Integer integer2 = new Integer(15);
		System.out.println(integer1 == integer2);
		System.out.println(integer1.equals(integer2));
	}

输出

false
true
false
true
false
true

可以看到 “== ”用在引用类型上,输出结果可能与想象的不同,这是因为对于引用类型,“”判读的是 对象的引用是否相等,当我们使用new创建对象时,java往往分配不同的堆栈和堆空间给不同的对象,因此对于引用类型,使用“”判断往往返回false。
但并不是只要是引用类型,只要使用equals就可以判断其内容是否等价。当比较对象类型是自定义类型时,需要我们覆盖Object的equals方法才可以正确比较,否则调用equals方法会默认调用Object的equals方法,而它的实现就是使用“==”来比较,这一点在第17章将会详述。

逻辑操作符

与或非(&& || !)比较简单,就略过了,但是书中也讲述了&&和& 以及|| 和|的关系,实际应用时,似乎|和&很少使用,关键词是短路,简单,略过。

直接常量

此处没搞明白,说是直接变量或许更好?这里说的最多的一个是定义数字类型时加前缀后缀的问题,这个用的其实很少。另外一个是“窄化转型”,在本章后面有讲述。

指数计数法

这用的也特别少,这里Think in Java还是很严谨的,详述了Java中e与科学自然e的区别。

按位操作符

这个用的也比较少,一共四种 按位与& 按位或| 按位非~ 按位异或^,他们和逻辑操作符极为类似。
一个例子

	public static void main(String[] args) {
		int i = 1 + 4 + 16 + 64;
		int j = 2 + 8 + 32 + 128;
		System.out.println("i = " + Integer.toBinaryString(i));
		System.out.println("j = " + Integer.toBinaryString(j));
		System.out.println("i & j = " + Integer.toBinaryString(i & j));
		System.out.println("i | j = " + Integer.toBinaryString(i | j));
		System.out.println("i ^ j = " + Integer.toBinaryString(i ^ j));
		System.out.println("~i = " + Integer.toBinaryString(~i));
		System.out.println("~j = " + Integer.toBinaryString(~j));
	}

结果

i =   1010101
j = 10101010
i & j = 0
i | j = 11111111
i ^ j = 11111111
~i = 11111111111111111111111110101010
~j = 11111111111111111111111101010101

移位操作符

分左移位操作符<< 右移位操作符>> 无符号右移>>> 这个我也不经常使用,其他书籍资料中讲述位运算对于机器来说,其速度比普通的加减乘除快,所以很多地方为了提高运算速度使用移位,比如左移一位一般是2 两位4,三位*8……
这里的移位和按位操作符结合起来,还是很复杂的,但其实用的不多,至于掌握到什么程度,就看个人需要了。

三元操作符 ? :

就是if else的简化版,只能由一个else,看起来代码简洁些。在Android studio中,如果可以使用但没有使用三元操作符的地方会有警告,但书中以及个人也觉得if else的逻辑可读性更高,何时使用三元操作符就看个人了

字符串拼接 +

这个用的就多了,不过也很简单,就是如果一个表达式以字符串起头,后面所有操作数都会自动转成字符串类型
例子

	public static void main(String[] args) {
		int x = 1;
		int y = 3;
		String string = "string";
		System.out.println(x + y + string);
		System.out.println(x + string + y);
		System.out.println(string + x + y);
	}

输出

4string
1string3
string13

关于Java数字类型的转换

byte8位 short16位 int32位 long64位 float32位 double64位
char16位
日常赋值和计算时会有些类型的转换,这种情况发生在
1.不同类型的变量之间的赋值
2.不同类型的变量之间的运算
第一点例如:

		int x =1;
		float y = 2.02f;
		System.out.println(x +" "+ y );
		
		y = x;
		System.out.println(x +" "+ y );
		y = (float)x;//强制转换其实是不必要的,会自动转换
		System.out.println(x +" "+ y );

第二点比如int类型与float类型运算,结果会是float类型
float类型与double类型运算,结果会是double类型

类型提升和强制转换

将int值赋值给float变量,值会自动转型成float类型,就像执行了强制转换,这种自动的转换在本书中称为
类型提升,即将一个小容器里的东西放到大容器中。但反过来则不行
将float值赋值给int变量,会报错,除非加上强制转换,可以理解为Java认为这样做会丢失精度,是不安全的,需要使用者自己知道。这就相当于将大容器的东西放到小容器,小容器有可能放不下,那么只能把物体裁小(精度丢失),才能放入小容器,就出现了截尾。
比如将29.7赋值给int值,则最后的值是29
想要使用四舍五入要使用Math类的round方法

关于操作符的建议

1.如果不确定操作符的运算顺序,就使用括弧,这样可以提高可读性和确保代码正确性
2.(个人想法,不是书中建议)如果没有必要,尽量避免不同类型数值之间的运算,虽然类型转换有一定的规律可循,但是我们应当避免无意的错误或者精度丢失。这样的错误往往是难以发现的。

操作符小结

char byte short运算时有时会被操作符进行类型提升(转化成int值)之后需要自行窄化(强制转换成原来的类型,可能信息丢失)
相同类型的数据进行运算时要注意可能的超出范围
bool类型的数据在java中的功能很有限,很多在c c++中可以进行的操作在Java中是行不通的

你可能感兴趣的:(java)