head first java读书笔记
1. 基本信息
- 页数:689
- 阅读起止日期:20170104-20170215
2. 标签
- Java入门
3. 价值
- 8分
4. 主题
使用面向对象的思路介绍Java的基础知识,从对象的基本概念、变量、方法,到函数库,集成与多态,静态,再到GUI,序列化,网络,数据结构,最后介绍发布和远程调用。
5. 纲要
- Java的基本介绍-第1章
- 对象的基本介绍(变量与方法)-第2-5章
- 函数库-第6章
- 对象的深入-继承、多态、构造函数-第7-9章
- 静态的变量或方法-第10章
- 异常处理-第11章
- GUI-第12-13章
- 序列化与IO-第14章
- 网络与线程-第15章
- 常用数据结构-第16章
- 包与发布程序-第17章
- 远程过程调用、servlets等-第18章
- 附录
6. 点评
本书最大的启发是建立面向对象的基本思想,万物皆在对象中,到底是如何组成和实现的。
7. 摘录
7.1 Java的基本介绍
- Java程序包括:源代码、编译器、输出(class文件)、JVM;
最常用的java
public class ClassName { public static void main(String[] args){ System.out.println("Hello World!"); } }
- Java不能像C一样使用整型做测试条件
(int)(Math.random()*length)
来出现随机数;
7.2 对象的基本介绍
- 对象本身已知的事物被称为实例变量,对象可以执行的动作被称为方法;
- 类是对象的蓝图;
- main的两种用途:测试真正的类,启动你的java应用程序;
- Java的程序在执行期是一组会互相交谈的对象;
- 变量有两种类型primitive主数据类型和引用;
- primitive主数据类型:boolean, char(0~65535), byte(-128~127), short(-32768~32767), int, long, float, double。
- 没有对象变量,只有引用到对象的变量;引用变量更像是对象的遥控器;
- 没有引用到任何对象的引用变量的值为null;
- 如果堆上的对象没有任何引用变量,便会被回收;
- 数组就像杯架,数组变量就是数组对象的遥控器;
- 将实例变量标记为private,将getter和setter标记为public;
- 实例变量永远都会有默认值,引用变量的默认值是null;局部变量没有默认值。
- 程序设计的方法:找出类应该做的事情,列出实例变量和方法,编写伪码,编写测试用的程序,实现类,测试方法,排错或重新设计。
- 伪码描述要做什么事情,而不是如何做。
- Interger.parseInt()只会在所给的String为数字时有用。
7.3 API
- ArrayList listName = new ArrayList();
- Java的API中,类是包含在包中的。使用时,必须import 类全名或直接打出去全名。除非是来自于java.lang这个包中,比如System, String, Math等。
7.4 对象深入
- extends表示继承,继承搜索方法时,会从底层向祖先搜索;
- 继承是是一个,实例变量是有一个;
- public会被继承,private不会被继承。
- IS-A是单方向的;
- 继承的意义有二:避免了重复的代码;定义出共同的协议;
- 多态为声明父类的对象引用指向子类的对象;
- 通过多态,可以写出即使引进新型子类时,也不必修改的程序;
- 继承虽然没有层次的限制,但一般不会超过2层;
- 标识出final的类可以确保方法都是自己需要的版本,final也可以防止特定的方法被覆盖,只要加在方法前;
- 覆盖父类方法需要满足两个条件:参数必须要一样,且返回类型要兼容;不能降低方法的存取权限;
- abstract表示抽象,抽象类不会被初始化;
- abstract可以放在方法前,直接以分号结尾。抽象方法只能放在抽象类中,所有的抽象方法在子类中必须被实现;
- Java中,所有的类都继承自object。object有equals,getClass,hashCode,toString等方法。
- 接口是为了解决多重继承的问题出现的,它没有实例变量。像是一个100%的纯抽象类,使用interface定义,所有的方法都是抽象的。其他类用implements来实现接口的方法。类是可以实现多个借口的。
- 如果新的类无法对其他类通过IS-A测试,就设计不继承的类;
- 只有在需要某类的特殊化版本时,已覆盖或增加新的方法来继承现有的类;
- 当你需要定义一群子类的模板,又不想初始化此模板时,使用抽象类;
- 如果定义出类可以扮演的角色,使用接口;
- 使用super关键字执行父类的方法;
- 抽象类可以带有抽象和非抽象的方法;
- 方法调用和局部变量在栈空间,所有的对象在堆空间;
- 构造函数没有类型,也不会被继承。通常一定要有没有参数的构造函数。如果有了一个有参数的构造函数,则没参数的构造函数必须自己写才会有,否则创建会出错。
- 通常状况下,类的构造函数是public。
- 构造函数的调用链是自顶向下的,即先运行父类,再运行子类。
- 子类的构造函数如果想调用父类,使用super();通常状况下,super会被编译器默认调用没有参数的版本,显示调用的话,必须要把super();放到第一行。
- 可以使用this(args)调用重载版的其他的构造函数,例如无参数的构造函数,加个默认值调用有参数的构造函数。this(Color.Red)
7.5 静态的变量或方法
- 静态方法不需要创建实例变量就可以调用,像Math.abs(-1);
- 如果类只有静态方法,即要限制非抽象类初始化,可以将构造函数设置为private;
- 静态方法不能调用非静态的变量或方法,例如在main中调用;但可以调用静态变量或方法。
- 类中静态变量的值,对所有实例都相同,均是共享的;即实例变量每个实例一个,静态变量每个类一个。
public static final double PI=3.1415
public表示可供各方读取,static表示不用创建实例即可使用,final表示不变;静态final变量默认取名大写,且必须初始化。- final的变量代表不能改变,final的方法不能被覆盖,final的类不能被继承;
- 使用类似ArrayList时,要用到对主类型数据的封装。因为ArrayList设计成只接受类和对象。Java5.0之前,int不能直接加入ArrayList,5.0之后可以了,因为加入了autoboxing。Integer iWarp = new Integer(i); int unWrapped = iWrap.intValue();利用上述解包装;
- 主数据类型的静态方法:Integer.parseInt("2"), new Boolean("true").booleanValue();Double.toString(d);
- 格式化输出String.format("%,d",1000);
- 日期的格式化输出:%tc 完整的日期和时间,%tr是时间,%tA %tB %td 分别是星期,月份和日期。如果不想重复输入参数,可以使用String.format("%tA, %
java.util.Calendar对象来操作日期
Calendar cal = Calendar.getInstance(); cal.set(2017,1,6,15,40); cal.getTimeInMillis(); cal.HOUR_OF_DAY cal.add(cal.DATE, 30);//月份滚动 cal.roll(cal.DATE, 30);//月份不动 cal.set(cal.DATE, 1); //重要的方法 add(int field, int amount) get(int field) getInstance() getTimeInMillis() roll(int field, int amount) set(int field, int amount) set(year,month,day,hour,minute) setTimeInMillis(long millis) //关键字段 DATE / DAY_OF_MONTH HOUR / HOUR_OF_DAY MILLISECOND MINUTE MONTH YEAR ZONE_OFFSET
import static java.lang.System.out;静态的import可以到方法,然后直接调用out即可,不建议使用;
7.6 异常处理
- Java通过throws语句来告诉你所有的异常行为;把有风险的程序放在try块中,用catch块摆放异常的处理程序;异常时Exception类型的对象;
会抛出异常的方法必须要声明它有可能会这么做。方法可以抓住其他异常,异常总会丢回给调用方;
public void takeRisk() throws BadException{ if (abandonAllHope){ throw new BadException(); } } public void crossFingers(){ try{ anObject.takeRisk(); }catch (BadException ex){ System.out.println("Aaargh"); ex.printStackTrace(); } }
- try/catch块用来处理真正的异常,而不是程序的逻辑错误。
- 开发与测试期间发生RuntimeException是不会受编译器关于是否声明它会抛出RuntimeException的检查的,也不会管调用方是否认识到该异常;
- 无论成功或者失败都要运行的放在finally中;即使try和catch都有return也一样。
- throws可以抛出多个异常。
throws Exception1, Exception2
;catch也可以用类似switch语句一样来分别处理,但是要从小到大; - 异常也可以是多态的,可以用所抛出的异常父型来catch异常。即可以用Exception ex来catch异常,但是不推荐这么做;
duck异常表示,当调用有异常的方法,也声明会抛出异常时,可以不把此方法的调用放在try/catch块中,但是不推荐这么做。
7.7 GUI相关
- GUI的流程是创建frame,创建widget,加在widget,显示出来。
监听和事件源之间的沟通通过程序代码调用
button.addActionListener(this)
来向按钮注册。按钮会在事件发生时,调用注册该接口的方法actionPerformed(theEvent)
;import javax.swing.*; import java.awt.event.*; public class SimpleGui1B implements ActionListener{ JButton button; public static void main(String[] args){ SimpleGui1B gui = new SimpleGui1B(); gui.go(); } public void go(){ JFrame frame = new JFrame(); button = new JButton("click me"); button.addAcitonListener(this); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(button); frame.setSize(300,300); frame.setVisible(true); } public void actionPerformed(ActionEvent event){ button.setText("I've been clicked!"); } }
- frame默认有东西南北中五个位置来放置widget;
当有多个widget需要监听事件时,用内部类来解决;
public void go(){ //... labelButton.addActionListener(new LaberlListener()); colorButton.addActionListener(new ColorListener()); label = new JLabel("I'm a label") //... } class LaberListener implements ActionListener { public void actionPerformed(ActionEvent event){ labnel.setText("Ouch!") } }
- 不同的事件有不同的对应回调函数,ActionListener对应的回调函数是actionPerformed,通过addActionListener添加。
每个背景组件都可以有自定义规则的布局管理器。BorderLayout表示五个区域,是frame默认的,FlowLayout表示从左至右,有必要时换行,是panel默认的;BoxLayout以垂直排列;
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); panel.add(button); frame.getContentPane().add(BorderLayout.North, panel);
JTextField文本框组件,JTextArea可滚动的文本框组件,JCheckBox组件,JList组件均为常用组件。
7.8 序列化与IO
- 储存对象的状态可以有两种办法,序列化(自己的Java程序读),纯文本文件(公共格式,其他程序可读)。
序列化写入的步骤:创建FileOutputStream对应一个文件xxx.ser,有FileStream创建ObjectOutputStream,由ObjectOutputStream写入object,关闭ObjectOutputStream。其中需要注意,能够写入ObjectOutputStream的object必须implements Serializable,但是不需要实现任何方法。
FileOutputStream fileStream = new FileOutputStream("MyGame.ser"); ObjectOutputStream os = new ObjectOutputStream(fileStream); os.writeObject(characterOne); os.close();
- 当一个类可以序列化时,必须所有的实例变量都能被序列化,如果某个变量不需要序列化,需要标记transient,例如
transient String currentID;
解序列化步骤,创建FileInputStream读取一个文件,创建ObjectInputStream,读取对象,转换对象类型,关闭。
FileInputStream fileStream = new FileInputStream("MyGame.ser"); ObjectInputStream os = new ObjectInputStream(fileStream); Object one = os.readObject(); GameCharacter elf = (GameCharacter) one; os.close()
读写文本文件。注意可以使用缓冲区的方法来减少磁盘IO。使用writer.flush()强制缓冲区内容写入磁盘。
import java.io.*; class WriteAFile{ public static void main(String[] args){ try { FileWriter writer = new FileWriter("Foo.txt"); writer.write("hello, foo!"); //BufferedWriter bWriter = new BufferedWriter(writer); //bWriter.flush(); writer.close(); File myFile = new File("Foo.txt"); FileReader fileReader = new FileReader(myFile); BufferedReader reader = new BufferedReader(fileReader); String line = null; while((line = reader.readLine()) != null){ System.out.println(line); } reader.close(); } catch(IOException ex){ ex.printStackTrace(); } } }
java.io.File类的操作
//创建出File对象 File f = new File("MyCode.txt"); //建目录 File dir = new File("Chapter_8"); dir.mkdir() //列出目录内容 if (dir.isDirectory()){ String[] dirContents = dit.list(); } //取得绝对路径 dit.getAbsolutePath(); //删除文件 boolean isDeleted = f.delete()
每个对象被序列化的同时,都会带上一个类的版本的识别ID,即serialVersionUID。因此要注意版本。
7.9 网络与线程
网络接收消息的步骤:建立socket连接,输入到底层的InputStreamReader上,转换到缓冲区字符BufferedReader,读取数据;
Socket chatSocket = new Socket("127.0.0.1", 5000); InputStreamReader stream = new InputStreamReader(chatSocket.getInputStream()); BufferedReader reader = new BufferedReader(stream); String message = reader.readline(); reader.close();
写消息的流程是:建立socket,getOutputSream并建立PrintWriter,写入数据;
Socket chatSocket = new Socket("127.0.0.1", 5000); PrintWriter writer = new PringWriter(chatSocket.getOutputStream()); writer.println("haha"); writer.close();
服务器端建立服务的流程为:建立ServerSocket,等待客户端连接,客户连接后,调用accept方法建立新的socket。
ServerSocket serverSock = new ServerSocket(5000); while(true){ Socket sock = serverSock.accept(); PrintWriter writer = new PrintWriter(sock.getOutputStream()); writer.println("haha"); writer.close(); }
- Thread类用于创建线程,它有void join();void start();static void sleep();等方法。
- 启动新线程的流程是:建立Runnable对象,建立Thread对象,并赋值Runnable任务;启动Thread。
Runnable是一个接口,它只有一个方法public void run()。它就是线程要执行的工作。
public class MyRunnable implements Runnable { public void run(){ go(); } public void go(){ System.out.println("top o' the stack"); } } class ThreadTestDrive{ public static void main(String[] args){ Runnable threadJob = new MyRunnable(); Thread myThread = new Thread(threadJob); myThread.start(); System.out.println("back in main"); } }
- 新建线程线程有新建(new),可执行(start),执行中,三个状态,可执行和执行中两个状态是来回切换的,由JVM调度决定,是随机不可控的。最多只能靠sleep来影响最小保证时间。
- 线程不可重复启动。
- 线程的名字可以通过myThread.setName("Solon's Thread")来设定,并可通过调用Thread.currentThread().getName()来获取。
线程会产生并发性问题(concurrency),并发性问题会引发竞争状态(race condition),竞争状态会引发数据损毁。举个例子,两个线程共用一个余额,每次用钱时,先检查余额,再扣钱。这样会由于竞争原因出现负数。所以需要一把锁,来保证一个方法一次只能被一个线程调用,即用synchronized关键字。
private synchronized void makeWithDrawa(int amount){ //... }
- 要注意锁是锁在对象上的,只有对象包含同步化方法时才起作用。对象就算有多个同步化方法,也还是只有一个锁。
同步化可以只修饰几行,这样可以减小原子操作的范围,提高效率。
private void makeWithDrawa(int amount){ //... synchronized(this){ //... } }
- 两个线程和两个对象就可以引起死锁,各自占有一个资源,又需要调用彼此的资源。
静态的方法是运行在类上的,当要对静态的方法做同步化是,会使用类本身的锁。
7.10 常用数据结构与泛型
- TreeSet一有序状态保持并可防止重复;
- HashMap-KV来存储;
- LinkedList-针对经常插入和删除的集合;没有ArrayList常用;
- HashSet-防重复的集合,快速查找;
- LinkedHashMap-类似HashMap,但可记住元素的插入顺序,可按照存取顺序来排序;
- 可用java.util下的Collections.sort(List list)来给ArrayList排序,此函数会直接改变list的顺序。
- 不用泛型时,任何object对象都可以加入ArrayList,造成混乱。使用泛型,只有任一单一类型可加入;
泛型有三件事是重要的:
new ArrayList
(); //创建实例 ArrayList songList = new ArrayList (); //声明指定泛型类型的变量 void foo(ArrayList list); //声明或调用指定泛型的方法 在说明文件中,一般用E表示指定类型;
public class ArrayList
extends AbstractList implements List ...{ public boolean add(E o) } 运用泛型的方法可以使用未定义在类声明的类型参数。
public
void takeThing(ArrayList list) //与后续的万用字符相同 public void takeThing(ArrayList extends Animal>) //与下面不一样,下面的只能使用Animal,不能使用其子类 public void takeThing(ArrayList list) - 以泛型的观点来说extends可以代表extend或implement,表示是一个。
- 如果要对自定义类实现排序,有两种方法,一是实现该类的Comparable接口,这个接口有compareTo方法。可以调用已有的类型来辅助实现该方法;二是增加Comparator类的参数来比较。
- 可以调用HashSet的addAll方法,来生成ArrayList对应的队列;
同样如果要使用HashSet去除重复,针对自定义类,要覆盖equals方法和hashCode方法。它的比较过程是先比较hashCode(),如果相同,再比较equals。
class Song implements Comparable
{ ... public boolean equals(Object aSong){ Song s = (Song)aSong; return getTitle().equals(s.getTitle()); } public int hashCode(){ return title.hashCode(); } } HashMap适合用KV场景存储数据
HashMap
scores = new HashMap (); scores.put("Bert", 43); System.out.println(scores.get("Bert")); 泛型参数和数组参数的区别
public void takeAnimals1(ArrayList
animals) public void takeAnimals2(Animal[] animals) //takeAnimals1(new ArrayList dogs)会出错,编译错误 //takeAnimals2(new Dog[] dogs)不会出错 //前者可以防止在函数中进行Dog的特有相关操作,后者在执行期如果运行相关操作,会抛出异常 为了解决上述前者的问题,可以使用万用字符
public void takeAnimals3(ArrayList extends Animal> animals) //为了防止此时,将ArrayList
中加入Cat,当用万用字符时,不能对队列做加入操作
7.11 包与发布程序
可以使用
-d class_path
来将源代码与类文件相分离,实现对源码的保护;javac -d ../classes *.java
jar类似tar命令,它有自己的规则。首先要确定所有的类文件都在classes目录下,其次要有manifest.txt文件来描述哪个类带有main()方法,最后执行命令打jar包
cat manifest.txt Main-Class:MyApp cd MiniProject/classes jar -cvmf manifest.txt app1.jar *.class
- 如果jar包指定了manifest,则可以执行
java -jar app1.jar
。 - 除非类是包的一部分,否则Java虚拟机不会深入其他目录去找。
- 用包防止类名冲突,可以把类放到各个包里。就像java.util.ArrayList。
而为了防止包命名冲突,一般反向使用domain作为包名称。
com.headfirstbooks.Book com.headfirstjava.projects.Chart
- 选定包名称后,需要再类的源代码的第一行将类加入包中
package com.headfirstjava;
。然后要设定对应的目录结构。 - 一般Java程序的路径为项目根目录下有source和classes两个文件,source目录下为包路径和源代码,这样用-d ../classes编译后,-d会在classes目录下建立对应的目录和class文件。
- 按照上述约定时,manifest文件也放在class目录下,一般为
Main-Class:com.headfirstjava.PackageExcise
。 在classes目录下执行jar语句打包,这里只要指定com路径就行。
jar -cvmf manifest.txt packEx.jar com
- 使用
jar -xf packEx.jar
解压后会发现有META-INF
目录,其下有MANIFEST.MF
文件,即为写入的对应文件。 - JWS即Java Web Start,用户能通过网页上的某个连接来启动Java程序,一旦程序下载后,下次就能独立于浏览器来运行,它是通过网络来发布程序的一种手段。jnlp文件是个描述应用程序可执行JAR文件的XML文件。
制作可执行jar程序;编写jnlp文件;两者均放入Web服务器;Web服务器设定新类型
application/x-java-jnlp-file
;设定网页链接到jnlp文件Launch My App
7.12 远程过程调用、servlets等
- 远程过程调用最早是为了利用Server端的强大处理能力。RMI全称Remote Method Invocation;
- RMI需要服务器与服务器helper和客户端与客户端helper。helper假装成服务,但其实只是真实服务的代理;
- 使用RMI时要确定协议,两端都是Java可使用JRMP协议,IIOP可以调用Java对象或其他类型的远程方法。
- 在RMI中客户端的helper称为stub,客户端的helper成为skeleton
- 创建远程服务的流程是创建接口(extends Remote),实现类(extends UnicastRemoteObject impements MyRemote),rmic产生stub与skeleton(
rmic MyRemoteImpl
),启动RMI registry(rmiregistry
),启动远程服务(Naming.rebind("Remote Hello", serviceMyRemote)
)。 - 客户端查询RMIregistry(
(MyRemote)Naming.lookup("rmi://127.0.0.1/Remote Hello")
),RMIregistry返回stub对象,客户端调用stub上的方法; - 注意点:启动远程服务前启动registry;参数和返回类型可序列化;
- servlet相当于Java的CGI,可以处理用户提交的请求,并将结果返回给浏览器的网页;
- 创建并执行serverlet的步骤:找到网页服务器可以存放servlet的地方;取得servlet.jar并添加到classpath上(其不是标准函数库的一部分);extends HttpServlet来编写servlet的类;编写html来调用servlet;给服务器配置html和servlet;
- servlet一般通过覆盖HttpServlet的doGet和doPost来创建;它输出带有完整标识的HTML网页;
- EJB是Enterprise JavaBeans,它具有一组光靠RMI不会有的服务,比如交易管理、安全性、并发性、数据库和网络功能等;EJB服务器作用于RMI调用和服务层之间。service helper调用EJB object,object调用Enterprise bean,后者再调用DB等操作;
Jini也使用RMI,但具有自适应探索(接收服务注册和客户端调用查询)和自恢复网络(用心跳检查服务)的功能;
7.13 附录
- 按位非~,按位与&,按位或|,按位异或^;
- 右移>>,无符号右移>>>;
- String具有不变性,放在String pool中,不受Garbage Collector管理。
- 包装类没有setter,其具有不变性。例如new Integer(42),永远都是42。
断言可以用来测试,它比println的好处是没有特别设定的话,它会自动被JVM忽略,也可专门打开断言调试程序。
assert (height > 0) : "height = " + height + " weight = " + weight; //冒号后面的语句可以是任何返回非null值得语句,但一定要注意不要在这里改变对象的状态 //断言的编译和普通编译没差别,执行时有差异。 javac TestDriveGame.java java -ea TestDriveGame
new Foo().go()
是一个调用go方法,但又不需要维持对Foo引用的方式;- 静态嵌套类,和普通类很像,可通过new Outer.Inner()来创建。可存取外层静态私有变量;
- 非静态的嵌套类,通常被称为内部类。
匿名内部类。
//button.addActionListener(quitListener); //通常是传递一个内部类的实例 //虽然ActionListener是个接口,而且我们不能声明一个接口的实例,但匿名内部类是个例外 button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev){ System.exit(0); } });
- public任何程序代码都可公开存取;default只在统一包中的默认事物能够存取;protected可允许不在同一包的子类,继承该部分;
- 对于常用的可变String,一般使用StringBuilder,Thread安全环境中,采用StringBuffer;
- 二维数组是指数组的数组,
new int [4][2]
其实是创建一个包含四个元素的数组,每个元素是一个长度为2的数组。 - 枚举相对于普通常量的优势是限定了范围,其实际是继承了java.lang.Enum。声明时使用
public enum Members {JERRY, BOBBY, PHIL};
- 每个枚举都内置values()可以用于遍历操作;
可以在enum中加入构造函数,方法、变量和特定常量的内容;
8. 相关
暂无