Java编程思想


文章目录

  • 简介
  • 第一章 对象导论
  • 伴随多态的可装换对象
  • 单根继承
  • 参数化类型
  • 对象的创建和生命期
  • 第二章 一切都是对象
  • 必须由你创建所有的对象
  • 方法、参数和返回值
  • 第三章 操作符
  • 第四章 控制执行流程
  • break 和 continue
  • 第五章 初始化与治理
  • 使用构造器确保初始化
  • 成员初始化
  • 第六章 访问控制权限
  • 第七章 复用类
  • 初始化基类
  • 在组合与继承之间选择
  • 向上转型
  • 第八章 多态
  • 转机
  • 向下转型与运行时类型识别
  • 总结
  • 第九章 接口
  • 抽象方法和抽象类
  • 接口
  • 通过继承来拓展接口
  • 接口与工厂
  • 第十章 内部类
  • 创建内部类
  • 链接到外部类
  • 第十一章 持有对象
  • 基本概念
  • 容器的打印
  • 迭代器
  • LinkedList
  • Stack
  • Set
  • 第十二章 通过异常处理错误
  • 第十三章 字符串
  • 不可变的 String
  • 重载 "+" 与 StringBuilder


简介

Java 编程思想为 Java 开发的圭臬, 是 Java 开发的经典手册. 作为一个开发人员还是建议多看一看. 从大学时起到现在已经开发多年, 也看过多遍, 随着年龄的增长和开发经验的增加, 每次重新阅读侯都会有新的理解, 所谓温故而知新. 但也存在问题, 一个是阅读时获得新的理解隔一段事件后容易遗忘. 二是每次阅读后做的纸质笔记容易丢失和难以拓展. 遂决定以电子版记之~~

Java编程思想基于 jdk 1.5版本,

第一章 对象导论

伴随多态的可装换对象

  • 在处理类型的层次结构的时候, 经常把以对象不当作它所属的特定类型来对待, 而是将其当作基类的对象来对待.
  • 多态实现原理(面向对象的最重要妙诀): 编译器不可能产生传统意义上的函数调用. 非面向对象产生的函数调用使用前期绑定. 面向对象采用后期绑定. 即当向对象发送消息时, 被掉用的代码运行时才能确定, 所以编译器要确定被调用方法的存在
  • 向上转型: 把导出类看作它的基类的过程.

单根继承

  • 面向对象所有的类都继承自一个单一的基类 – Object
  • 单根继承保证所有的基类都具备某些功能

参数化类型

  • 容器存储对象的时候, 需要向上转型为 Object. 会丢失类型信息, 取出的时候向下转型时不安全的. 因此添加了参数化类型, 在 Java 中称为范型
  • 参数化类型(范型): 编译器可以自动定制作用语特定类型上的类

对象的创建和生命期

  • 垃圾回收器原理: 所有的类都继承自单根基类 Object 以及只能以一种方式创建(在堆上创建)

第二章 一切都是对象

描述: 尽管 Java 是基于 C++ 的, 但相比之下, Java 是一种更“纯粹”的面向对象程序设计语言

必须由你创建所有的对象

  • 对象的存储
  • 寄存器. 速度最快, 位于 cpu 中, 根据需求分配
  • 堆栈. 速度仅次于寄存器. 位于 RAM 中(内存). 一半对象的引用存放于此
  • 堆. 一种通用的内存池. 位于 RAM 中. 使用 new 创建的对象存放于此
  • 常量存储. 常量值通常直接存放在程序代码内部
  • 非 RAM 存储. 存在计算机外设中, 即磁盘, U盘等
  • 特例: 基本类型存放在堆栈中
  • 基本类型: boolean, char, (byte, short, int ,long), (float, double)
  • 基本类型都有对应的包装器类
char c = 'x';
Character ch = new Character('x');

自动包装:编译器可以自动将基本类型转化为包装器类型

Character ch = 'x';

自动拆包: 编译器自动将包装器类型转换为基本类型

char c = ch;
  • 高精度数字: 没有对应的基本类型
    BigInteger: 支持任意精度的整数
    BigDecimal: 支持任意精度的浮点数
  • 基本数据类型初始化的时候, 会有默认值, 但最好对变量初始化

基本数据类型

默认值

boolean

fasle

char

‘\u0000’(null)

byte

(byte)0

short

(short)0

int

0

long

0L

float

0.0f

double

0.0d

方法、参数和返回值

  • 参数列表: Java 中任何对象的传递, 传递的实际是引用,例如调用方法、一个引用给另一个引用赋值等(注意是任意对象, 而基本类型变量没有引用, 传递的是值)

第三章 操作符

在最底层, Java 中的数据是通过使用操作符来操作的

第四章 控制执行流程

就像有知觉的生物一样, 程序必须在执行过程中控制它的世界, 并作出选择. 在 Java 中, 你要使用执行控制语句来做出选择

break 和 continue

  • 无穷循环的两种基本方式: for(;😉 和 while(true)
  • goto 是 Java 中的一个保留字, 目前的版本中没有使用它. goto 的替代机制 – 标签, 用于完成循环中需要跳转的功能. 用于循环语句之前, 配合 break 和 continue 语句一起使用, 中断循环到直接到标签所在的地方
    标签是后面跟有冒号的标识符
// eg
label1:
for(int i=0; i<10; i++){
    label2:
    for(int j=0; j<10; j++){
        if(j=2){
            continue label1; // 中断内层循环, 外层开始下一次循环
        } else {
            break label1;	 // 跳转并退出外层循环
        }
	}
}

第五章 初始化与治理

随着计算机革命的发展, “不安全” 的编程方式已逐渐成为编程代价高昂的主因之一

使用构造器确保初始化

  • 初始化期间, 编译器自动调用构造器
  • 构造器实际上是 static 方法, 只不过该 static 声明式隐式的
  • 设计基本数据类型的重载
  • 基本数据类型能从一个 “较小” 的类型自动提升至一个"较大"的类型, 因此重载方法能接受一个较低类型的参数
void f(long a){System.print.out("long a")};
f(5); // 5为 int 类型, 编译不会报错
  • 如果参数高于方法能接受的基本数据类型, 则需要强制类型转换
void f(char a){System.print.out("char a")};
f((char)8); // 5为 int 类型, 编译不会报错

成员初始化

  • Java 尽力保证, 所有的成员变量在使用前初始化. 对于局部变脸, 编译器以报错的方式保证被初始化后才能使用

第六章 访问控制权限

访问控制 (或隐藏具体实现) 与 “最初的实现并不恰当” 有关

四种访问权限控制: public、protected、包访问权限(没有关键字) 和 private

  • 接口和实现
    访问权限控制常常被称为具体实现的隐藏. 把数据和方法包装进类中, 以及具体实现的隐藏, 长共同被称作封装.

第七章 复用类

复用代码时 Java 众多引人注目的功能址以. 但要想成为极具革命性的语言, 仅仅能够复制代码并对之加以改变时不够的, 它还必须能够做更多的事情

初始化基类

  • 当创建一个导出类的对象时, 该类包含了一个基类的子对象. 这个子对象和你用基类直接创建的对象时一样的. 二者的却别在于, 后者来源于外部, 而基类的子对象包含在导出类的内部(导出类初始化过程: 编译器先调用基类的构造器初始化基类, 然后再初始化子类, 所以子类可以调用父类的方法)
  • 带参宿的构造器: 如果想调用带参数的构造器, 可以使用super来调用, 所以 super 要放在构造器的第一行调用
class Game {
    Game(int i){
        System.out.print("Game constructor")
    }
}

class BoardGame extends Game{
    BoardGame(int i){
        super(i);
        System.out.print("BoardGame constructor");
    }
}

在组合与继承之间选择

  • 组合和继承都允许在新的子类中放置子对象, 组合是显式的放, 继承是隐式的放

向上转型

  • 由导出类转型成基类, 一般称为向上转型
  • 向上转型总是安全的

第八章 多态

“我曾经被问到’求教, Babbage 先生, 如果你向机器中输入错误的数字, 可以得到正确的答案吗?’ 我无法恰当的理解产生这种问题的概念上的混淆”

在面向对象设计语言中, 多态是继数据抽象和继承之后的第三种基本特征

转机

  • 后期绑定: 也叫动态绑定或运行时绑定
Shape s = new Circle();
s.draw();					// 实际调用的是导出类 Circle 的 draw() 方法
  • Java 中除了 static 和 final 方法(private 方法属于 final 方法) 之外, 其它所有的方法都是后期绑定

向下转型与运行时类型识别

  • 运行时类型识别: 在 Java 语言中, 所有的转型在运行期间都会得到检查(如果类型不争取, 会返回一个 ClassCastException 异常(类转型异常))

总结

  • 多态依赖于数据抽象和继承

第九章 接口

接口和内部类为我们提供了一种将接口和实现分离的更加结构化的方法

抽象方法和抽象类

  • 抽象方法: 仅有声明而没有方法体;
  • 抽象类: 包含抽象方法的抽象类. 抽象类并不完整, 所以不能被实例化

接口

  • 只有方法名, 没有方法体, 且方法的作用域只能式public
  • 接口被用来建立类与类之间的协议(某些面向对象的语言使用 protocol 完成这一功能)

通过继承来拓展接口

  • 可以在接口中添加新的方法声明
  • 可以在新接口中组合数个接口

接口与工厂

  • 工厂方法: 与直接调用构造器不同, 在工厂对像上调用的是创建方法, 而该工厂对象将生成接口的某个实现的对象. 目的是将接口与实现分离

第十章 内部类

可以将一个类的定义放在另一个类的定义的内部, 这就是内部类

创建内部类

  • 在类的内部使用内部类: 和普通类完全一样
  • 在类的外部创建内部类方式: OuterClassName.InnerClassName, 先创建外部类, 在创建内部类
public class Outer{
    
    public class Inner{
        
    }
}

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();

链接到外部类

  • 内部类可以访问外部类所有的成员(外部类对于内部类来说, 像是一个公共资源/内部类依赖于外部类)

第十一章 持有对象

如果一个程序只包含固定数量的且其生命周期都是已知的对象, 那么这是一个非常简单的程序.

程序需要在任意时刻和任意位置创建任意数量的对象, 因此就不能依靠命名的引用来持有每一个对象.

因为你不知道实际上需要需要多少这样的引用.

Java 实用类库还提供了一套相当完整的容器类来解决这个问题, 并且容器类都可以自动调整自己的尺寸.

基本概念

你应该创建一个具体的对象, 将其转型为对应的接口, 然后再其余的代码中都使用这个接口.

这样做的目的是, 需要修改实现的时候, 只需要再创建处修改就可以了, eg:

List<Apple> apples = new ArrayList<>();
apples = new LinkedList<>();

容器的打印

  • ArrayList 和 LinkedList 都是 List 类型, 按照插入的顺序保存元素, 不同之处仅在于执行某些类型的操做是的性能, 并且 ListedList 包含的操错多余 ArrayList
  • HashSet、TreeSet 和 LinkedHashSet 都是 Set 类型, 每个相同的项只保留一次, HashSet 不保存插入的顺序, TreeSet 按照比较结果升序保存对象, LinkedHashSet 按照插入顺序保存对象
  • HashMap、TreeMap 和 LinkedHashMap 都是 Map 类型, 存储键值对. HashMap 使用一种非常快的算法来控制顺序, 提供了最快的查找速度. TreeMap 按照比较的结果升序保存键, LinkedHashMap 则按照插入的顺序保存键, 同时还保留了 HashMap 的查询速度

迭代器

编码中只是使用容器, 不着调或者不关心容器的类型, 那么如何才能不重写代码就可以应用于不同类型的容器?

迭代器(也是一种设计模式)的概念可以用于达成此目的. 迭代器是一个对象, 它的工作是遍历并选择序列中的对象, 而不必知道底层的数据结构.

  • 迭代器是轻量级对象: 创建代价小
  • iterator() 方法返回一个 Iterator 对象
  • 使用 next() 获得序列中的下一个元素
  • 使用 hasNext() 检查序列中是否还有元素
  • 使用 remove() 将迭代器新近返回的元素删除(因此调用该方法前必须先调用 next()方法)
  • 接受容器并传递它, 从而在每个对象上都执行操作, 这种思想十分强大, 并且贯穿于本书

LinkedList

  • LinkedList 也像 ArrayList 一样实现了基本的 List 接口. 插入删除比 ArrayList 更加高效, 随机访问比ArrayList 慢
  • LinkedList 还添加了可以使其用作栈、队列或双端队列的方法
  • getFirst() 和 element() 方法完全一样, 都返回列表的第一个元素, 如果列表为空, 则抛出异常 NoSuchElementException; peek() 也是返回首个元素, 列表为空时返回null
  • removeFirst() 与 remove() 也完全一样, 移除并返回列表的头, 列表为空时抛出异常 NoSushElementException; poll() 稍有差异, 列表为空时返回 null
  • removeLast() 移除并返回列表的最后一个元素, 如果列表为空, 抛出异常 NoSuchException
  • addFirst() 与 add() 和 addLast() 相同, 都将元素插入到列表的尾(首)部

Stack

  • “栈” 通常值 “后进先出” 容器.
  • 只需要栈的行为(入栈 push, 出栈 pop,返回栈顶元素 peek, 判空 empty)的话, jdk 原始的 Stack 通过继承 LinkedList 实现, 并不是太合适, 可以单独定义一个 Stack,或者引入 import net.mindview.util.Stack 类
import java.util.LinkedList;

public class Stack<T> {
    private LinkedList<T> storage = new LinkedList<>();
    // 入栈
    public void push(T v){storage.addFirst(v);}
    // 返回栈顶元素
    public T peek(){return storage.getFirst();}
    // 出栈
    public T pop(){return storage.removeFirst();}
    public boolean empty(){return storage.isEmpty();}
    public String toString(){return storage.toString();}
}

Set

  • Set 不保存重复的元素
  • HashSet 出于速度原因的考虑, 使用了散列函数; TreeSet 将元素存储在红黑树数据结构中; LinkedHashList 因为查询速度的原因, 也使用了散列函数, 使用链表维护插入顺序

第十二章 通过异常处理错误

Java 的基本理念是 “结构不佳的代码不能运行”

发现异常最理想的时机是在编译阶段, 也就是在你试图运行之前. 然而, 编译期间并不能找出所有的错误, 余下的问题必须在运行期间解决. 这就需要错误源能通过某种方式, 把适当的信息传递给某个接受者 – 该接收者将知道如何正确处理这个问题

第十三章 字符串

可以证明, 字符串操作是计算机程序设计中最常见的行为

不可变的 String

  • String 对象是不可变的. 对 String 对象值的修改操作, 实际上会创建一个全新的 String 对象. 已包含修改后的字符串的内容, 及 String 对象具有 “只读” 属性
  • String 对象作为参数传给方法的时候, 实际传递的是原对象引用的拷贝
// s 是局部变量, 该方法执行的时候才存在, 方法执行完后消失
public String upcase(String s){
    // 返回一个新的引用给调用者
	return s.toUpperCase(); 
}

public static void main(String[] args){
	String q = "aa";
	print(q);
	String qq = upcase(q);
	print(qq);
	// 原对象值未被改变
	print(q);
}

// output
// aa
// AA
// aa

重载 “+” 与 StringBuilder

  • 一个操作符在应用于特定的类时, 被赋予了特殊的意义(用于 String 的 “+” 与 “+=” 是 Java 中仅有的两个重载过得操作符)
// "+" 被重载过, 作用与于 String 类时, 自动引入 StringBuilder 对象, 调用三次 append() 方法, 最终调用 toString() 方法生成结果, 并存为 s. 因为 StringBuilder 更加高效. 所以只产生一个对象
String s = "abc" + "def" + "ghi";