美文网首页
Thinking in Java 笔记

Thinking in Java 笔记

作者: Perebor | 来源:发表于2016-09-30 00:59 被阅读120次

Java 与其他语言的不同之处,是它在设计的时候就把目标专注于克服开发与维护程序的复杂性。然而很多EE特性和乱七八糟的可配置性XML又与其背道而驰。Python尝试在语言级别克服复杂性,然而简单引入了大量的门外汉,他们产生的低质量代码有搞坏了python的部分名声。
Perl向后兼容awk、sed、grep,并且期望替代所有Unix工具,于是成为了“write-only”的代码。


对象(OOP)入门

Java的世界,就是OOP的世界。思考的时候,不能用过程式的思路。
事实上,很难很好的设计出对象— 从而很难设计好任何东西。因此,只有数量相当少的“专家”能设计出最好的对象,然后让其他人享用
程序设计语言,就是用想法描述、处理、控制世界的过程,先要把现实的世界描述出来。世界很复杂,如果描述细节,对于不同事物就会有不同的描述。各个描述之间无法共享、处理方法更无法通用。一个苹果加一个苹果是两个苹果,一个橘子加一个橘子是两个橘子。不能把苹果、橘子抽象成水果,也就没有一个加一个等于两个的通用计算过程。

OOP的老祖宗,smalltalk的五个特性:

  1. 万物皆对象
  2. 程序是对象的组合,他们通过发送消息来告知彼此需要做的。
  3. 每个对象都有自己的,由其他对象所组成的存储。
  4. 每个对象都有其类型,每个object都有它的原型class
  5. 某一特定类型的所有对象都可以接受同养的消息。

对象具有状态、行为和标识
状态是用内部存储来支持
行为是他可以接收的消息
标识是它的原型以及对象的句柄

OOP设计的最大挑战,就是在问题空间的元素和解空间的对象之间创建一对一的映射

访问控制
为什么要有访问控制?因为角色不同,类创建者不希望客户端程序员触碰那些类内部“柔软的地方”,那些于类实现接口无关的内部细节。

  • Public 随便搞
  • Private 谁也不许碰
  • Protected 继承它的子类可以使用
  • 默认,包内public,包外private

组合composition vs 继承 inherit

  1. has-a vs is-a
  2. 所有情况下,优先选择组合

前期绑定(编译期绑定) vs 动态绑定(运行时绑定)
Java为这种函数产生特定代码,C++使用virtual关键字告诉编译器,产生特别指针。
动态构型,upcasting 向上转型,把派生类当成基类来用。只是用基类、接口中的函数。

container 容器
这些其实就是数据结构中我们一直学习的东西,List、Map、Set等等。这里List分LinkedList和ArrayList;
容器支持泛型,可以在声明容器时使用类似C++的方式指明内部元素的类型。进一步的放入约束,以及提高效率。

object生命周期
一般情况下,所有的新对象都放在heap里,使用new 创建。动态方式的一个一般性假设是,对象趋于变得负责,所以查找和释放存储空间的开销不会对对象的创建产生重大冲击。

Thinking in Enterprise Java
专门讲JavaEE么?好像已经废弃了

除了Java还有什么选择
如果是OOP可以选 .net, 是的,因为Mono Project,*nix上也可以用。但是不知道好不好用。
过程式编程,数据定义和函数调用。


一切都是对象

虽然Java和C++一样都是“杂合”语言,可以使用过程式和面向对象式或混合式方法进行编程,但是对于Java,最好还是纯粹使用OOP思想与方法。
对象存储位置
Java不允许用户指定寄存器;对象引用放在堆栈中,但是对象不在里面;new出来的对象都在heap里;持久化可以选择轻量级或是JDBC、Hibernate等方式储存在数据库中。
基本类型
Java还是有基本类型,不是通过变量引用放在heap里,而是不用new,直接把值放在stack中。

  1. boolean; void nosize
  2. byte 8bits; short 16bits; int 32bits; long 64bits;
  3. float 32bits; double 64bits
  4. BigInteger; BigDecimal
    类成员的基本类型自动变量有默认值。都是0, boolean是false。
    但是其他自动变量没有,Java会在编译时返回错误。

大括号代表作用域
在作用域里面定义的自动变量出了作用域会失效,作用域内不能shadow作用域外已经定义的自动变量。对象不受此限制,因为是放在heap上的,不是堆栈里。

函数与方法
名称、参数、返回值和方法体。

static* Java有static的class成员变量、方法、内部类。但是没有顶级的static class。为什么要用static内部变量?共享内部成员数据(但是多线程就SB,)为什么要static函数?只要不操作内部变量的,就可以static,而且java IDE好像还鼓励这样,因为可以更大幅度的共用代码。
关于static和multi-threading问题,一个class loader (也就是一个JVM)可以在内存中产生一个class,这种一个class内static是共享的。如果想区别不同线程,可以使用ThreadLocal<T>

取得系统属性

  • System.getProperities()
  • System.getProperity(String )

JavaDoc

  • JavaDoc 只能给publicprotected 成员进行文档注释。private和包内可访问成员的注释会被忽略掉。

操作符

  • 对于不同类型的操作数,会进行隐式转换。
  • 赋值操作,对象赋值是引用,除非使用copy函数。
  • 比较相等或不等,基本类型可以用== 和 !=,对象要使用.equals(),而且要自己实现你对象的equals,因为默认情况下是比较引用。
  • 0xff; 07777; 0b1, 在IntelliJ 里面可以相互转换。
  • 按位操作符 & and, | or , ^ nor , ~ not; &=, |=, ^= 都合法,只有~不可以,因为是一元操作符。
  • 左移<< 低位补0,右移 >> 正数高位补零,附属高位插1.>>>无符号右移,只补零。
  • 三元操作符,又称为条件赋值 <boolean> ? value1 : value2。它的缺点就是可读性比较差,对于技术一般、不那么熟练的新手会有一定困难。当然,自己迷糊的时候也有可能。
  • java.lang.Math.round() 在java.lang 包里的东西不用import,直接使用。
  • 大数还是会overflow,没有编译器警告,没有运行时错误,只会默默地溢出。

控制流程

  • true/ false;
  • if-else
  • while, do-while, for
  • foreach :
    • for (int x: array) { ... } 遍历 array;
    • for (int x: range(10)) { ... }
  • switch, 接受 char 和int,但是可以和enum一起工作。

初始化与清理

构造器constructor & 垃圾回收 garbage collection

  • 没有任何参数的构造器(构造函数)被称为无参构造器(默认构造函数)。构造函数没有返回值,不是void, 因为只能用new,只能返回一个新的object
  • 函数重载使用参数区分,不能使用返回值。
  • 如果class中没有定义任何构造函数,编译器会自动生成一个默认构造器。如果有任何一个已经存在,编译器将不做动作。
  • return this; 传的是自己这个object的引用,可以实现链式表达式。
  • static 方法没有this指针
  • 在类中,变量定义的先后顺序决定了初始化的顺序,向前引用是非法的。但是成员变量一定会比构造函数运行更为优先,不管成员变量和构造函数在class中的声明顺序如何。
  • 类中的其他类作为成员变量,一定要在定义时使用new 来做初始化,不然使用时会出现NullPointer Exception。
  • 静态示例初始化,非静态实例初始化,都在构造函数执行之前被调用。如果是static的成员变量,要用静态匿名初始化;非静态的就用非静态看起来像“闭包”一样的匿名函数初始化。
  • 数组Array,
    • 声明初始化一个数组引用 int[] a1;
    • 声明同时初始化 int[] a2 = {1, 2, 3, 4, 5};
    • 如果 赋值, a1 = a2,那么它们指向同一个数组实例。
    • 可变参数参数表, f(String... trailing),等价于 f(String[] trailing),它也可以识别出传递进来的数组。f(Object... args) f({1, 2, 3})。但是可变参数列表会带来自动包装机制,可能带来重载不匹配的问题。
    • enum 可以用 .values() 来获得所有的可能值,对于每一个,也可以使用单个的.ordinal()取得次序。

访问权限控制

访问控制(或隐藏具体实现)与“最初的实现并不恰当”有关。
所有优秀的作者,包括哪些编写软件的程序员,都清楚其著作的某些部分直至重新创作的时候才会变得完美,有时甚至要反复重写多次。如果你把一个代码段放到了某个位置,等过一会儿回头再看时,有可能会发现有更好的方式去实现相同的功能。这正是冲过的原动力之一,重构即重写代码,以使得它赓可读、更易理解,并因此而更具可维护性。
但是总是有一些需要你的代码在某些方面保持不变。由此而产生了在面向对象设计中需要考虑的一个基本问题:“如何把变动的食物与保持不变的食物区分开来”。
访问权限控制的等级,从最大权限到最小权限依次为:public、protected(对子类开放)、包访问权限(没有关键词)和private。
当编写一个Java源代码文件时,此文件通常被称为编译单元(有时也被称为转义单元)。每个编译单元都必须有个后缀名.java,在单元内有一个public类,需要与文件名同名。除了这个同名的public类外,可以有其他的类,但是这些类在package外是不可见的。编译文件时,每个class会产生一个.class文件。为了管理命名空间以及组织代码结构,使用package。 package语句要放在java文件的第一行。
使用package中的类时,用import语句导入。Java会在CLASSPATH的目录下去寻找相应的包。


复用类

复用代码是java众多引人注目的功能之一。但是想成为极具革命性的语言,仅仅能够复制代码并加以改变是不够的,它还必须能够做更多的事情。
使用现有类而不破坏其代码,完成工作通过创建新类来复用代码,而不必再重头开始编写。

组合

优点是比较简单,没有副作用。但是缺点也有很多,比如无法使用protect方法;不可以替换原来的类使用;要实际指明如何调用里面包含对象的方法……
而且要注意,在类域里的成员对象都是未初始化的,需要显示初始化,不然都是空指针NullPointer。
另外除了基本类型外,java中的成员变量存储的都是引用的值。具体情况参见《冒号课堂》第11课,值与引用,比较详细的介绍了C++, C#,Java中引用于值得区别。

继承

问题:这一节里提到了每个类都写一个Main funciton,为了单元测试用。但是单元测试不是应该利用测试框架么?把main固化在class里只为测试有点本末倒置。
构造函数:
如果没有一个默认构造函数,或是想要使用基类中带参数的构造函数,就要使用super显式调用那个基类的带参数构造函数。
** 在覆盖基类方法时(覆盖意味着函数签名完全一致,函数名、参数表),需要使用@Override 标注,不然编译器会返回error. **

代理

不使用继承,但是把一个class的对象放入代理类中。然后暴露几乎所有的接口。
Java本身不直接支持代理,但是IDE比如IDEA IntelliJ 可以支持。

结合使用组合和继承

super()构造函数只能用一次,在构造函数的第一行。但是super.f() 这种成员函数不受限制,主要用在被子类覆盖的同名函数中,不然不需要使用super这个限制词。

关于main 函数

在任何Java语法单元里,都可以有一个main函数,按照函数签名,准确的说是一个 public static void main(String[] args)。在这个main函数里,可以new这个类自身。没关系,因为他是静态的,在哪里都没问题。与此相反,如果在类的构造函数里有创建自身实例的举动,会出现堆栈溢出的异常。参见文章[Java构造函数里做个死 -- 创建自己的实例]

向上转型(构型) 由派生类转换成基类,一般用于把特定类应用于通用方法。这里不存在危险,因为派生类总是基类的子类,不管是成员变量还是函数,符合所有基本要求。
向下转型(构型) 由基类转换成派生类,从泛型容器中取出对象并使用,但是把一个基类对象错误的转换成另外一个,就麻烦了。

final关键字

对于基本类型,final表示这是一个常量,值不会变化,相当于c++中的const。
对于对象,一旦指向一个,就不能改变其引用指向其他的,但是该对象内部的改变不受限制。
未完待续

相关文章

网友评论

      本文标题:Thinking in Java 笔记

      本文链接:https://www.haomeiwen.com/subject/vxjsettx.html