1.Head First Java --- 进入Java的世界

本文详细介绍了Java编程的基础知识,包括工作原理、程序结构、类与对象、变量、对象行为、多态、序列化与网络编程,以及分布式计算和Web应用部署。深入浅出地讲解了Java的核心概念和技术实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.进入Java的世界
	1.Java的工作方式
		1.编写源代码
		2.用编译器运行源代码
		3.编译器会产出 字节码,字节码与平台无关
		4.虚拟机可读取与执行字节码

		Java 版本:
			a) Micro Edition (J2ME)
			b) Standard Edition (J2SE)
			c) Enterprise Edition (J2EE)


	2.Java的程序结构

	3.剖析类
		每个Java程序最少都会有一个类以及main()。每个应用程序只有一个main()函数。

	4.main()方法
		在Java中所有的东西都会属于某个类。真正被执行的是类。main()就是程序的起点。

	5.循环
	6.条件分支
	7.设计程序
	8.术语制造机
	9.谈话录

2.拜访对象村

2 拜访对象村	
	1.椅子大战
	2.继承
	3.覆盖
	4.什么是类?
	5.创建你的个对象
	6.使用main()
	7.猜数字

	当你在设计类的时候,要记得对象是靠类的模型塑造出来的,你可以这样看:
		1.对象是已知的事物;(实例变量 / 状态)
			对象本身已知的事务被称为实例变量(instance variable)。它们代表对象的状态(数据),且该类型的每一个对象都会独立的拥有一份该类型的值。
		所以你也可以把对象当成实例。

		2.对象会执行的动作。(方法 / 行为)
			对象可以执行的动作被称为方法。在设计类的时,你也会设计出操作对象数据的方法。


	类不是对象,是用来创建它们的模型。类是对象的蓝图,它会告诉虚拟机如何创建某种类型的对象,根据某种类型创建的对象都会有自己的实例变量。

3.认识变量

3 认识变量
	声明变量
	Primitive主数据类型
		boolean char byte short int long float double

	Java关键字

	引用变量
		虽然 primitive 主数据类型变量是以字节来代表实际的变量值,但对象引用变量却是以字节来表示取得对象的方法。

		引用变量保存的是存取对象的方法。

		Dog myDog = new Dog();
		代表取得Dog对象的方法以字节形式放进变量中,对象本身并没有放进变量中。

		对任意一个Java虚拟机来说,所有的引用对象大小都一样。

		引用对象,相当于遥控器。

		Book b = new Book();
		Book c = new Book();
		Book d = c; // 表示 将 c 的字节组合拷贝给变量 d

	对象的声明与赋值

	可回收堆空间

	数组
		int [] nums;

		nums = new int[7];

		数组也是对象。

4.对象的行为

4.对象的行为
	操作对象状态的方法
		任一类的每个实例都带有相同的方法,但是方法可以根据实例变量来表现不同的行为。

	方法参数与返回类型
		Java 只能返回一个值。

	值传递
		Java 是值传递,如果传的是引用对象,引用对象的变量所携带的是远程控制而不是对象本身,若你对方法传入参数,实际上传入的是远程控制的拷贝。

	Getters与Setters
	
	封装
	
	数组中的引用

	实例变量永远会有默认值。null 代表没有操作对象的远程控制,它是个引用而不是对象。

	局部变量没有默认值。

	使用 == 来比较两个 primitive 主数据类型,或者判断两个引用对象是否引用同一个对象。使用 equals() 来判断两个对象是否在意义上相等。

5.超强力方法
 

5.超强力方法
	创建战舰游戏
	简单版
	编写伪码
	测试
	编写程序
	完成版
	用Math.random()产生随机数
	预先输入好的程序
	循环
	类型转换
	用Integer.parseInt()转换字符串

	极限编程:
		1.多次经常性的小规模发布
		2.避免加入规格没有的功能
		3.先写测试用的程序
		4.正常工作上下班
		5.随时随地的重构,也就是改善代码
		6.保持简单
		7.双双结伴进行工作,并经常交换伴侣以便让大家都清楚全局


	加强版本的for循环:
		Java 5.0 开始,有加强版的for循序。

		for(String name:nameArray) {}

6.使用Java函数库
	ArrayList<String> = new ArrayList<String>();

	在Java 5.0 中 ArrayList 是参数后的,<String>是类型参数。


	短运算符(&&, ||):有时候只计算一边就行了;
	长运算符(&, |):不管如何,两边都得运算。

	在Java的API中,类是被包装在包中的。要使用哪个API中的类,你必须知道它被放在哪个包中。

	1.import java.util.ArrayList;
	2.使用全名,java.util.ArrayList<Dog> list = new java.util.ArrayList<Dog>();


	除了 java.lang 之外,使用到其他包的类都需要指定全名。

	import 不会把程序变大。


7.对象村的优质生活
	继承:is-a,继承概念下 is-a 是个单向关系。

	引用:has-a


	创建对象:
		Dog myDog = new Dog();

		a) 声明一个引用变量
			Dog myDog
		b) 创建对象
			new Dog()
		c) 连接对象和引用
			Dog myDog = new Dog();

	在多态中,引用和对象可以是不同的类型:
		Animal myDog = new Dog();

	运用多态时,引用类型可以是实际对象类型的父类。


	方法的覆盖:
		当你要覆盖父类的方法时,必须遵守合约。方法就是合约的标志。

		编译器会寻找引用类型来决定你是否可以调用该引用的特定方法。但在执行期,Java虚拟机寻找的并不是引用所指的类型,而是在堆上的对象。因此若编译器
	已经同意这个调用,则唯一能够通过的方法就是覆盖的方法也有相同的参数和返回类型。

		1.参数必须一样,且返回类型必须要兼容;
		2.不能降低方法的存取权限


	方法的重载:
		1.返回类型可以不同
		2.不能只改变返回类型
		3.可以更改存取权限


8.深入多态
	接口是一种100%纯抽象的类,是无法初始化的类。

	抽象类 abstract

	抽象方法,代表此方法一定要被覆盖。抽象的方法没有实体。不能在非抽象类中拥有抽象方法。
		将可继承的方法体放在父类中是个好主意,但有时候是没有方法给出任何子类都有意义的共同程序代码,抽象方法的意义在于就算无法实现出方法的内容,
	但还是可以定义出一组子型共同的协议。

	Java中的所有类都是从Object这个类继承出来的。

	使用 Object 类型的多态引用是会付出代价的。编译器无法将此对象识别为 Object 以外的事物。

	编译器只管引用类型,而不是对象的类型。

	多重继承会导致"致命方块"问题。解决方法是用 interface。

	要如何判断应该是设计类、子类、抽象类或者接口呢?
		1.如果新的类无法对其他的类通过 IS-A 测试时,就设计不继承其他的类
		2.只有在需要某类的特殊化版本时,以覆盖或者增加新的方法来继承现有的类
		3.当你需要定义一群子类的模板,又不想让程序员初始化此模板时,设计出抽象类给它们用
		4.如果想要定义出类可以扮演的角色,使用接口


9.对象的前世今生
	在Java中,程序员会在乎内存中的两种区域:对象的生存空间(heap)和方法调用及变量的生存空间(stack)。

	变量存在于哪一个空间要看它是哪一种变量而定,实例变量还是局部遍历。
		1.实例变量
			实例变量是被声明在类而不是方法里面的,它们代表每个独立对象的"字段"。实例变量存在于所属的对象中。

		2.局部变量
			局部变量和方法的参数都是被声明在方法中的,它们是暂时的,且生命周期只限于方法被放在栈上的这段时间。

	构造函数没有返回类型。

	在创建新对象时,所有继承下来的构造函数都会执行。抽象的类也有构造函数。

	super(); //调用父类的构造函数,对super()的调用必须是第一个语句。

	变量的存活期:
		1.局部变量 只会存活在声明该变量的方法中
		2.实例变量的寿命与对象相同,如果对象还活着,则实例变量也是会活着。


	只要有活着的引用,对象也会活着。如果对象的唯一引用死了,对象就会被从堆中踢开。

	有3种方法可以释放对象的引用:
		1.引用永久性的离开了它的范围;
		2.引用被赋值到其他的对象上;
		3.直接将引用设置为null。


10.数字很重要
	静态方法与非静态方法的区别:
		带有静态的方法的类通常(虽然不一定都这样)不打算被初始化。你也可以用私有的构造函数来限制非抽象类被初始化。要记得,被标记private的方法
	代表只能被同一类的程序所调用。任何非静态的方法都代表必须以某种实例来操作,取得 新对象的方法只有通过new或者序列化以及反射。

		静态的方法不能调用非静态的变量。因为静态的方法是通过类的名称来调用的,所以静态的方法无法引用到该类的任何实例变量。

		静态的方法是不知道堆上有哪些实例的。

		静态的方法也不能调用非静态的方法。

		静态变量:它的值对所有的实例来说都是相同的。

		静态变量是在类被加载的时候初始化的,Java虚拟机会加载某个类是因为第一次有人尝试要创建该类的新实例,或者是使用该类的静态变量或者非法。


		静态的final变量是常数。
		public static final double PI = 3.14159...


		静态final变量初始化:
			1.声明的时候
			public static final double PI = 3.14159...

			2.在静态初始化程序中
			public class Bar {
				public static final double PI ;

				static {
					BAR_SIGN = (double)Math.random();
				}
			}

		final 也可以用在非静态变量,表示不能更改。


		primitive 主数据类型的包装:
			1.包装值
				int i = 288;
				Interger iWrap = new Integer(i);

			2.解开包装
				int unWrapped = iWrap.intValue();


11.有风险的行为
	try/catch 块会告诉编译器你确实知道所调用的方法会有风险,并且也已经准备好要处理它。它只会注意到你有没有表示你已经注意到了。

	//方法声明有异常
	public void takeRisk() throws BadException {
		if (xxx) {
			//抛出异常
			throw new BadException();
		}
	}

	编译器会核对每件事情,除了 RuntimeExceptions 之外,编译器保证:
		1.如果你有异常抛出,则你一定要用 throw 来声明这件事;
		2.如果你调用会抛出异常的方法,你必须得确认你知道异常的可能性。将调用包在 try/catch 块中是一种满足编译器的方法(还有其他)。

	不是由编译器检查的 RuntimeException 的子类,被称为异常检查。

	为什么编译器不处理那些运行期间的异常?
	答:大部分的RuntimeException 都是因为程序逻辑的问题,而不是以你所无法预测或者防止的方法出现的执行期间失败的。try/catch 是用来
处理真正的异常,而不是你程序的逻辑错误。
	
	如果不打算处理异常,还是可以正式的将异常给 ducking 来通过编译。

	try {
		...
	} catch(BakingException ex) {
		...
	} finally {
		...
	}

	finally 是无论如何都要执行的部分。如果finally 或者 catch 块有 return 指令,finally还是会执行。流程会跳到finally然后再回到return指令。


	处理多重异常:
		//声明方法可能有多个异常类型
		public void do() throws PantsException, LingerieException {
			...
		}

		try {

		} catch (PantsException pe) {
			...
		} catch (LingerieException le) {
			...
		}

	不能把大篮子放在小篮子上面:
		使用catch时,Java虚拟机只会从头开始往下找第一个符合范围的异常处理块。


	如果不想处理异常,你可以把它 duck 避开。把它duck掉以让调用你的方法的程序来catch该异常。ducking 只是在踢皮球。


12.看图说故事
	在Java上,取得与处理用户操作事件的过程称为 even-handling。

	Swing 的GUI组件是事件的来源。以Java的术语来说,事件来源是个可以将用户操作转换成事件的对象。对Java而言,事件几乎都是以对象来表示的。

	事件源(例如按钮)会在用户做出相关动作时(按下按钮)产生事件对象,你的程序在大多数的情况下是事件的接收方而不是创建方。

	使用内部类实现2个按钮的程序。内部类可以提供在一个类中实现同一个接口的多次机会。


13.使用Swing


14.保存对象
	存储状态的选择:
		1.如果只有自己写的Java持续会用到这些数据
			将被序列化的对象写到文件中。然后就可以让你的持续去文件中读取序列化的对象并把它展开回到活生生的状态。
		2.如果数据需要被其他程序引用
			写一个纯文本文件。用其他程序可以解析的特殊字符写到文件中。

	序列化的文件是很难让人阅读的,但它比纯文本文件更容易让程序恢复,也比较安全。


	将序列化对象写入文件:
		1.创建 FileOutputStream
			FileOutputStream fileStream = new FileOutputStream("MyGame.ser"); //如果文件不存在,会自动创建

		2.创建 ObjectOutputStream
			//它能让你写入对象,但无法直接连接文件,所以需要参数的指引
			ObjectOutputStream os = new ObjectOutputStream(fileStream);

		3.写入对象
			//将变量所指引的对象序列化并写入 MyGame.ser 这个文件。
			os.writeObject(characterOne);
			os.writeObject(characterTwo);
			os.writeObject(characterThree);

		4.关闭 objectOutputStream
			//关闭所关联的输出串流
			os.Close();


		Java的输入/输出API带有连接类型的串流,它代表 来源于目的地之间的连接,连接串联将串流与其他串联连接起来。

		一般来说,串联要两两连接才能做出有意义的事情。其中一个表示连接,另一个则是要被调用的方法。为何要2个?因为连接的串流通常都是
	很底层的。以FileOutputStream味蕾,它有可以写入字节的方法。但我们通常不会直接写字节,而是以对象层次的观点来写入,所以需要高层
	的连接串流。

		那又为何不一单一的串流来执行呢?这就要考虑到良好的面向对象设计了。每个类只要组好一件事情。FileOutputStream把字节写入文件,
	ObjectOutputStream把对象转换成可以写入串流的数据。当我们调用ObjectOutputStream的writeObject时,对象会被打成串流送到
	FileOutputStream来写入文件。

		当对象被序列化时,被该对象引用的实例变量也会被序列化。且所有被引用的对象也会被序列化。


		如果要让类能够被序列化,就实现Serializable。Serializable接口又被称为marker或tag类的标记用接口,因为此接口并没有任何方法
	需要实现的。它唯一的目的就是声明有实现它的类是可以被序列化的。也就是说,此类型的对象可以通过序列化的机制存储。如果某类是可以序列化的,
	则它的子类也自动的可以序列化。

		如果某个实例变量不能或者不应该被序列化,就把它标记为 transient(瞬时)的。如果你把某个对象序列化,transient的引用实例变量会
	返回null,而不管当时存储的是什么。


	解序列化(Deserialization):还原对象
		将对象序列化整件事的重点在于你可以在事后,在不同的Java虚拟机执行期(甚至不是同一个Java虚拟机),把对象恢复到存储时的状态。

		1.创建FileInputStream对象
			//如果文件不存在会报错
			FileInputStream fileStream = new FileInputStream("MyGame.ser");

		2.创建ObjectInputStream
			//它知道如何读取对象,但是还是靠链接的stream提供文件读取
			ObjectInputStream os = new ObjectInputStream(fileStream);

		3.读取对象
			//每次调用readObject() 都会从stream中读出下一个对象,读取顺序与写入顺序相反,次数超过会抛出异常。
			Object one = os.readObject();
			Object two = os.readObject();
			Object three = os.readObject();

		4.转换对象类型
			//返回值是Object类型,因此必须要类型转换
			GameCharacter elf = (GameCharacter) one;
			GameCharacter troll = (GameCharacter) two;
			GameCharacter magician = (GameCharacter) three;

		5.关闭ObjectInputStream
			//FileInputStream会跟着自动关闭
			os.Close();


		解序列化时发生了什么?
			当对象被解序列化时,java虚拟机会通过尝试在堆上创建新的对象,让它维持与被序列化时有相同的状态来恢复对象的原状。但这当然
		不包括transient的变量,它们不是null(对对象引用而言),不然就是使用 primitive 主数据类型的默认值。

			1.对象从stream中读出来
			2.Java虚拟机通过存储的信息判断出对象的class类型
			3.Java虚拟机尝试寻找和加载对象的类。如果java虚拟机找不到或者无法加载该类,则java虚拟机会抛出例外
			4.新的对象会被配置在堆上,但构造函数不会执行。很明显,这样会把对象的状态抹去又会变成全新的,而这不是我们要的结果。我们
			需要的是对象回到存储时的状态。
			5.如果对象在继承树上有个不可序列化的祖先类,则该不可序列化类以及在它之上的类的构造函数(就算是可序列化也一样)就会执行。
			一旦构造函数连锁启动后将无法停止。也就是说,从第一个不可序列化的父类开始,全部都会重新初始化状态。
			6.对象的实例变量会被还原成序列化时点的状态值,transient变量会被赋值null的对象引用或primitive主数据类型的默认为0、
			false等值。


		要点:
			1.你可以通过序列化来存储对象的状态
			2.Stream是 连接串流 或者是 链接用的串流
			3.连接串流 用来表示源或者目的地、文件、网络套接字连接
			4.链接用串流 是用来衔接 连接串流
			5.用FileOutputStream链接ObjectOutputStream来讲对象序列化到文件上
			6.对象必须实现序列化这个接口才能被序列化。如果父类实现序列化,则子类也自动实现,而不管是否有明确的声明
			7.当对象被序列化时,整个对象版图都会被序列化。这代表它的实例变量所引用的对象也会被序列化
			8.如果有不能被序列化的对象,执行期间就会抛出异常
			9.除非该实例变量被标记为transient。否则,该变量在还原的时候会被赋予null或者primitive主数据类型的默认值
			10.在解序列化时,所有的类都必须能让java虚拟机找到
			11.读取对象的顺序必须和写入的顺序相同
			12.readObject()的返回类型是Object,因此解序列化回来的对象还需要转换成原来的对象
			13.静态变量不会被序列化,因为所有对象都是共享同一份静态变量值


		使用serialVersionVID,序列化对象的版本号。


15.网络联机	
	使用BufferedReader从socket上读取数据
		1.建立对服务器的socket连接
			Socket chatSocket = new Socket("127.0.0.1",5000);

		2.建立连接到socket上底层输入串流的InputStreamReader
			//InputStreamReader 是底层和高层串流间的桥梁
			InputStreamReader stream = new InputStreamReader(chatSocket.getInputStream());

		3.建立BufferedReader来读取
			BufferedReader reader = new BufferedReader(stream);
			String message = reader.readLine();

	用PrintWriter写数据到socket上:
		1.对服务器建立socket连接
			Socket chatSocket = new Socket("127.0.0.1",5000);

		2.建立链接到socket的PrintWriter
			//PrintWriter是 字符数据和字节间的转换桥梁,可以衔接String和Socket两端
			PrintWriter writer = new PrintWriter(chatSocket.getOutputStream());

		3.写入数据
			writer.println("message to send");


	Java线程:
		如何启动新的线程:
			1.建立Runnable对象(线程的任务)
				//你会编写实现Runnable的类,而此类就是你对线程要执行的任务的定义,也就是说此方法在线程的执行空间执行。
				Runnable threadJob = new MyRunnable();

			2.建立Thread对象(执行工人)并赋值Runnable(任务)
				//把Runnable对象传给Thread的构造函数,这会告诉Thread对象要把哪个方法放在执行空间上去运行---Runnable的run()方法。
				Thread myThread = new Thread(threadJob);

			3.启动Thread
				//在还没有调研start()方法之前什么也不会发生。当新的线程启动的时候,它会把Runnable对象的方法摆到新的执行空间去。
				myThread.start();

		使用 synchronized 这个关键字来修饰方法使它每次只能被单一的线程存取。防止两个线程同时进入同一个对象的同一个方法。

		每个对象都有单一的锁,单一的钥匙。这只会在对象带有同步化方法时才有实际的用途。

		每个类也有一个锁。


16.数据结构
	ArrayList不是唯一的集合,还有 TreeSet,HashMap,LinkedList,HashSet,LinkedHashMap。

	运用泛型你就可以创建类型安全更好的集合,让问题尽可能在编译期就能抓到,而不用等到执行期才冒出来。

	关于泛型,有几样东西需要知道:
		1.创建被泛型化类(例如ArrayList)的实例
			new ArrayList<Song>();
		2.声明与指定泛型类型的变量
			List<Song> songList = new ArrayList<Song>();
		3.声明(与调用)取用泛型类型的方法
			void foo(List<Song> list)
			x.foo(songList);


	泛型类:
		//E部分会用你所声明与创建的真正类型取代
		public class ArrayList<E> extends AbstractList<E> implements List<E> {
			public boolean add(E o) {
				...
			}

			...
		}

	泛型方法:
		在方法中的类型参数有几种不同的运用方式:

		1.使用定义在类声明的类型参数
		public class ArrarList<E> extends AbstractList<E> ... {
			public boolean add(E o) //只能在此使用E,因为它已经被定义成类的一部分。
		}

		2.使用未定义在类声明的类型参数
		public <T extends Animal> void takeThing(ArrayList<T> list) //因为前面声明T所以这里就可以使用<T>

			如果类本身没有使用类型参数,你还是可以通过在一个不寻常但可行的位置上指定给方法---在返回类型之前。这个方法意味着T可以是
		"任何一种Animal"。


		这行程序:
			public <T extends Animal> void takeThing(ArrayList<T> list)

		跟这个是不一样的:
			public void takeThing(ArrayList<Animal> list)

		这2个都是合法的,但意义不同。

		首先,<T extends Animal>是方法声明的一部分,表示任何被声明为Animal或者Animal的子类的ArrayList是合法的。因此你可以使用
	ArrayList<Dog>、ArrayList<Cat>、ArrayList<Animal>来调用上面的方法。

		但是,下面方法的参数是 ArrayList<Animal> list,代表只有ArrayList<Animal>是合法的。也就是说第一个可以使用任何一种Animal
	的ArrayList,而第二个只能使用Animal的ArrayList。



	从Collection的API说明文件中我们发现有3个主要的接口:List、Set和Map。


	对象要怎样才算相等?
		1.引用相等性
			堆上同一对象的两个引用。引用到堆上同一个对象的两个引用是相等的。调用hashCode(),就会得到相同的结果。

		2.对象相等性
			堆上的两个不同的对象在意义上是相同的。如果你想把两个不同的Song对象视为相等的,就必须覆盖过从Object对象继承下来的
		hashCode()方法与 equals() 方法。


	HashSet 如何检查重复:hashCode()与equals()

		hashCode()与equals() 的相关规定,API文件有对象的状态制定出必须遵守的规则:
			1.如果两个对象相等,则hashcode必须也是相等的;
			2.如果两个对象相等,对其中一个对象调用equals()必须返回true,也就是说,若a.equals(b)则b.equals(a);
			3.如果两个对象有相同的hashCode值,它们也不一定相等。若两个对象相等,则hashcode值一定是相等的;
			4.因此若equals()被覆盖过,则hashCode()也必须被覆盖
			5.hashCode()的默认行为是对在heap上的对象产生独特的值。如果你没有override过hashCode(),则该class的两个对象怎样都不会
			被认为是相同的
			6.equals()的默认行为是执行 == 比较。也就是说会去测试两个引用是否对上heap上同一个对象。如果equals()没有被覆盖过,两个
			对象永远都不会被视为相等的,因为不同的对象有不同的字节组合。
				a.equals(b)必须与 a.hashCode()==b.hashCode() 等值
			但 a.hashCode() == b.hashCode() 不一定要与 a.equals()等值。


	数据的类型检查是在运行期间检查的,但集合的类型检查只会发生在编译期间。


	万用字符:
		//这里的extends同时代表继承和实现。
		public void takeAnimals(ArrayList<? extends Animal> animals) {
			for (Animals a: animals) {
				a.eat();
			}
		}

	相同功能的另一种语法:
		这一行:
		public <T extends Animals> void takeThing(ArrayList<T> list)

		跟这一行:
		public void takeThing(ArrayList<? extends Animal> list)

		如果都一样,为什么要使用万用字符?
			这要看你是否会使用T来决定。举例来说,如果方法有2个参数,都是继承Animal的集合会怎样?此时,只声明一次会比较有效率。

			public <T extends Animal> void takeThing(ArrayList<T> one, ArrayList<T> two)
			而不必这样:
			public void takeThing(ArrayList<? extends Animal>one, ArrayList<? extends Animal> two)


17.发布程序
	部署应用程序:
		1.本机(Executable Jar)
			整个程序都在用户的计算机上以独立、可携的GUI执行,并以可执行的Jar来部署。
		2.两者之间(Web Start / RMI app)
			应用程序被分散成在用户本地系统运行的客户的,连接到执行应用程序服务的服务器部分。
		3.完全在远程(HTTP Servlets)
			整个应用程序都在服务端执行,客户的通过非Java形式,可能是浏览器的装置来存取。


	把程序装进JAR:
		JAR就是 Java ARchive。这种文件是个pkzip格式的文件,它能让你把一组类文件包装起来,所以交付时只需要一个JAR文件。如果你很
	熟悉unix的tar命令的化,你就会知道jar这个工具要怎么使用。

		问题是用户要拿JAR怎么办?
		你会创建出可执行的JAR。可执行的JAR代表用户不需要把文件抽出来就能执行。程序可以在类文件保存在JAR的情况下执行。秘诀在于创建
	出manifest文件,它会带有JAR的信息,告诉Java虚拟机哪个类包含main()这个方法。


	创建可执行的JAR:
		1.确定所有的类文件都在classes目录下

		2.创建manifest.txt来描述哪个类带有main()方法
			该文件带有下面这一行:
			Main-Class:MyApp //这个后面没有.class

			在此行后面要有换行,否则可能出错。将此文件放在classes目录下。

		3.执行jar工具来创建带有所有类以及manifest的JAR文件
			jar -cvmf manifest.txt app1.jar *.class


	执行JAR:
		Java虚拟机能够从JAR中载入类,并调用该类的main()方法。事实上,整个应用程序都可以包在JAR中。一旦main()方法开始执行,
	Java虚拟机就不会在乎类是从哪里来的,只要能够找到就行。其中一个来源就是classpath指定位置的所有JAR文件。如果看到某个JAR,
	则Java虚拟机就会在需要类的时候查询此JAR。

		java -jar app1.jar
		-jar 标识告诉虚拟机所给的是个JAR。
		java虚拟机会检查JAR的manifest寻找入口,如果没有就会发生运行期间异常。
		java虚拟机必须能够找到JAR,所以它必须在classpath下,让JAR曝光的最好方式就是把JAR放在工作目录下。


	用包防止类名称的冲突。
		Sun建议命名规则加上域名称,反向使用domain的包名称。
		com.headfirst.projects.Chart


	把类包进包中:
		1.选择包名
			如 com.heafirstjava

		2.在类中加入包指令
			package com.headfirstjava

		3.设定相应的目录结构
			只是把包指令加入源文件是不够的,类不会真的被加入包中,除非类也在相应的目录结构中。因此,如果完整名是
		com.headfirstjava.PackageExercise,则你必须把PackageExercise源文件放在名为headfirstjava目录下,此目录必须在
		com目录下。


	编译与执行包:
		cd myproject/source
		javac -d ../classes com/headfirstjava/PackageExercise.java

		cd myproject/classes
		java com.headfirstjava.PackageExercise


	-d 选型会要求编译期将编译结果根据包的结构来建立目录并输出,如果目录还没建好,编译期会自动的处理这些工作。


	以包创建可执行的JAR:
		当你把类包进包中,包目录结构必须在JAR中。

		1.确定所有的类文件都放在class目录下正确相对应的包结构中。
		2.创建manifest.txt文件来描述哪个类带有main(),以及确认有使用完整的类名称
			Main-Class:com.headfirstjava.PackageExercise
			然后把manifest文件放到classes目录下。
		3.执行jar工具来创建带有目录结构与manifest的JAR文件
			只要从com开始就行,其下整个包的类都会被保进去JAR
			cd myproject/source
			jar -cvmf manifest.txt packEx.jar com //只要指定目录就行


	那manifest文件跑哪里去了?
		jar -tf //将JAR内容列出来
		jar -xf packEx.jar //解压

		META-INF代表 META INFormation,jar工具会自动创建出整个目录和MANIFEST.MF文件,你的manifest.txt不会被带进JAR中,
	但它的内容会放进真正的manifest中。


	Java Web Start:
		运用Java Web Start(JWS),你的应用程序可以从浏览器上执行首次启动(从web来start),但它运行起来几乎像是个独立的应用程序而不受
	浏览器的束缚。一旦它被下载到使用者的计算机之后(首次从浏览器网址来下载),它就会被保存起来。

		Java Web Start 是个工作上如同浏览器plug-in的小java程序。这个程序被称为Java Web Start的helper app,主要的目的是用来
	管理下载、更新和启动JWS程序。

		当JWS下载你的程序(可执行的JAR)时,它会调用程序的main(),然后用户就可以通过JWS helper app启动应用程序而不需要回到当初的网页。

		这还不是最棒的,JWS还能检测服务器上应用程序局部更新,在不需要用户介入的情况下,下载与整合更新过的程序。

		你可以把JWS应用程序当做html网页或.jpg图文件一样的网络资源。


	Java Web Start 的工作方式:
		1.客户端点击某个网页上JWS应用程序的链接(.jnlp文件)

		2.web服务器收到请求发出.jnlp文件(不是JAR)给客户端的浏览器
			.jnlp文件是个描述应用程序可执行JAR文件的xml文件

		3.浏览器启动Java Web Start,JWS的helper app读取.jnlp文件,然后向服务器请求MyApp.jar

		4.web服务器发送.jar文件

		5.JWS取得JAR并调用指定的main()来启动应用程序
			然后用户就可以在离线的情况下通过JWS来启动应用程序。


	.jnlp文件
		你需要.jnlp(Java Network Lanuch Protocol)来制作Java Web Start的应用程序。JWS会读取这个文件来寻找JAR并启动应用
	程序(调用JAR里面的main())。 .jnlp文件是个简单的xml文件。


	创建于部署Java Web Start的步骤:
		1.将程序制作成可执行的JAR
		2.编写.jnlp
		3.把.jnlp与JAR文件放到web服务器
		4.对web服务器设定新的mime类型 application/x-java-jnlp-file
			这会让web服务器以正确的header送出 .jnlp数据,如此才能让浏览器知道所接收的是什么
		5.设定网页连接到.jnlp文件

	Java Web Start 与 applet 有什么不同?
		applect无法独立于浏览器之外,applet是网页的一部分而不是单独的。浏览器会使用Java的plug-in来执行applet,applect没有
	类似程度的自动更新功能,且一定的从浏览器上门执行。对JWS应用程序而言,一旦从网站上面下载之后,用户不必通过浏览器就可以离线执行程序。


18.分布式计算
	远程调用的设计:
		要创建出4种东西:服务器、客户端、服务器辅助设施和客户端辅助设施。

	调用方法过程:
		1.客户端对象对辅助设施对象调用doBigThing()
		2.客户端辅助设施把调用信息打包通过网络送到服务器的辅助设施
		3.服务端的辅助设施解开来自客户端辅助设施的信息,并以此调用真正的服务

	使用RMI时,你必须要决定协议:JRMP或者是IIOP。JRMP是RMI原生的协议,它是为了java对java间的远程调用设计的。另外一方面,IIOP是
为了CORBA而产生的,它能够让你调用Java对象或者其他类型的远程方法。
	
	在RMI中,客户端的辅助设施叫做stub,而服务端的辅助设施叫做skeleton。


	创建远程服务:
		1.创建Remote接口
			远程的接口定义了客户端可以远程调用的方法。它是个作为服务的多态化类,stub和服务都会实现此接口。

			1.继承 java.rmi.Remote
				Remote 是个标记性的接口,意味着没有方法。然而它对RMI有特殊的意义,所以你必须遵守这项规则。注意这里用的是extend,
			接口是可以继承接口的。

				public interface MyRemote extends Remote

			2.声明所有的方法都会抛出 RemoteException
				远程的接口定义了客户端可以远程调研的方法,它是个作为服务的多态化类。也就是说,客户端会调用有实现此接口的stub,而此
			stub因为会执行网络和输入输出工作,所以可能会发生各种问题。客户端必须处理或声明异常来认知这一类风险。如果方法在接口中声明
			异常,调用该方法的所有程序都必须处理或再声明此异常。

				import java.rmi.*;

				public interface MyRemote extends Remote {
					public String sayHello() throws RemoteException;
				}

			3.确定参数和返回值都是primitive主数据类型或者Serializable
				远程方法的参数和返回值必须都是primitive或Serializable。任何远程方法的参数都会被打包通过网络传送的,而这是通过
			序列化完成的。返回值也是一样的。如果使用的是API中像是String、primitive主数据类型等主要的类型,那就没问题。如果是
			自定义的,那你就得实现Serializable。

		2.实现Remote
			这是真正执行的类。它实现出定义在该接口上的方法。它是客户端会调用的对象。

			1.实现Remote这个接口
				你的服务必须实现Remote --- 就是客户端会调用的方法。

				public class MyRemoteImpl extends UnicastRemoteObject implements MyRemotes {
					public String sayHello() {
						...
					}
				}

			2.继承UnicastRemoteObject
				为了要成为远程服务对象,你的对象必须要有与远程有关的功能,其中最简单的方式就是继承UnicastRemoteObject以让
			这个父类处理这些工作。

				public class MyRemoteImpl extends UnicastRemoteObject implements MyRemotes {}

			3.编写声明RemoteException的无参数构造函数
				UnicastRemoteObject有个小问题:它的构造函数会抛出RemoteException。处理它的唯一方式就是对你的实现声明一个
			构造函数,如此才会有地方可以声明出RemoteException。要记得当类被初始化的时候,父类的构造函数一定会被调用,如果父类的
			构造函数抛出异常,你也得声明你的构造函数会抛出异常。

				public MyRemoteImpl() throws RemoteException {}

			4.向RMI registry注册服务
				现在你有了远程服务,还必须让远程用户存取。这可以通过将它初始化并加进RMI registry。当你注册对象时,RMI系统会把
			stub加到registry中,因为这是客户端所需要的。

				try {
					MyRemote service = new MyRemoteImpl();
					Naming.rebind("remote hello",service);
				} catch(Exception ex) { ... }

		3.用rmic产生stub和skeleton
			客户端和服务器都有helper,你无需创建这些类或者产生这些类的源代码。这都会在你执行jdk附件的rmic工具时自动处理掉。
		4.启动RMI registry(rmiregistry)
			rmiregistry就像是电话簿,用户会从此处取得代理(客户端是stub/helper对象)。
		5.启动远程服务
			你必须让服务对象开始执行。实现服务的类会其实服务的实例并向RMI registry注册。要有注册后才能对用户提供服务。


	关于servlet:
		servlet是http web服务器上面运行的java程序。当用户通过浏览器和网页交互时,请求会发送给网页服务器,如果请求需要java的servlet
	时,服务器会执行或者调用已经执行的servlet程序代码。servlet只是在服务器上运行的程序代码,执行出用户发出请求所要的结果。而servlet
	也可以使用RMI。


	创建并执行servlet的步骤:
		1.找出可以存放servlet的地方(web服务器)
		2.取得servlet.jar并添加到classpath上
			servlet并不是java标准函数库的一部分,你需要包装成servlet.jar的文件。这可以从 java.sun.com 下载,或者从默认好可执行
		java的网页服务器。

		3.通过extend过HttpServlet来编写servlet的类
			servlet是个extend过HttpServlet(javax.servlet.http)的类。还有其他类型的servlet可以创建,但通常你只会使用
		HttpServlet。

			public class MyServletA extends HttpServlet { ... }

		4.编写html来调用servlet
		5.给服务器设定html网页和servlet


	什么是JSP?它跟servlet有什么关系?
		JSP代表Java Server Pages。实际上web服务器最终会把jsp转换成servlet,但差别在于你所写出的是jsp。servlet是让你写出带有
	html输出的类,而jsp刚好相反---你会写出带有java程序的网页。

		RMI是java语言的一部分,所有的RMI相关的类也都在标准库中。而servlet和JSP则不是java语言的一部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值