import:import述句在程序源文件最前端。
import java.util.ArrayList
type:程序代码中使用全名。
java.util.ArrayList
List=new java.util.ArrayList ();
“子类继承父类”,继承即子类继承了父类的方法。(子类是extend 父类出来的)
“类的成员”,成员指实例变量和方法。当调用对象引用的方法时,会调用到与该对象类型最接近的方法,即Java虚拟机会从层次树的最下方开始找起。
IS-A测验:“是一个”VS“有一个”。测验是否是继承关系,A是B,则A继承B。
eg:“三角形是多边形”,即三角形可继承多边形。
“浴室有一个澡盆”,即浴室带有澡盆的实例变量。
“super”关键字——可用于取用父类。(子类不用完全覆盖掉父类功能,只加上额外行为)
public void roam(){
super.roam(); //调用父类的roam方法
//my own roam stuff
}
继承下来的方法可被覆盖掉,但实例变量不能被覆盖掉。
多态:同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同操作
Dog myDog = new Dog();
,声明一个引用变量并创建对象,引用类型与对象类型必须相符。
Animal myDog = new Dog();
,但在多态下,引用与多态可以是不同类。
编译器会根据引用的类型而不是实际对象来判定你能够调用哪些方法
运用多态时,引用类型可以是实际对象类型的父类。即任何extend过声明引用变量类型的对象都可被赋值给这个引用对象。
Animal [] animals=new Animal[2];
animals [0]=new Dog();
animals [1]=new Cat(); //可放任何Animal子类对象进去
for (int i=0;i<animals.length;i++){ //多态,可将数组的元素逐个调出,当做Animal来操作
animal[i].eat();
animal[i].roam();
};
参数和返回类型也可以多态,如将参数声明为父类类型,则即可在运行时传入任何的子类对象。
覆盖父类的方法,需保证:
public class Overloads{
String uniqueID;
public int addNums(int a,int b){
return a+b; }
public double addNums(double a,double b){
return a+b; }
public void setUniqueID(String theID){
uniqueID=theID; }
public void setUniqueID(int ssNumber){
String numString=""+ssNumber;
setUniqueID(numString); }
}
关键字: abstract
具体类可以被初始化为对象。
抽象方法也可以标记为abstract。
public abstract void eat(); //抽象的方法没有实体,不含方法体
抽象类代表此类必须要被extend过,抽象的方法代表此方法一定要被覆盖过。
若声明一个抽象方法,就必须要将类标记为抽象类,即不能在非抽象类中拥有抽象方法
抽象方法意义:就算无法实现方法内容,但可定义出一组子型共同的协议,有利于多态的使用,所有子型都会拥有抽象的方法。
继承树结构下的第一个具体类必须要实现出所有抽象方法。
必须以相同的方法鉴名(名称与参数)和相同的返回类型创建出非抽象的方法。
Java中所有类都是从Object这个类继承出来的,是所有类的父类。
不管实际上所引用的对象是什么类型,只有在引用变量的类型就是带有某方法的类型时才能调用该方法。
任何从ArrayList取出的Object都会被当做Object这个类的实例,而不管它原来是什么。
对象类型转换
若确定为Dog对象,则从Object中拷贝出一个Dog引用,并赋值给Dog引用变量。
若不确定为Dog对象,则使用instanceof运算符检查。若类型转换错误,在执行时会遇到ClassCastException异常并终止。
object o = al.get(index);
if (o instanceof Dog){
Dog d = (Dog) o; //将类型转换成Dog
}
d.roam();
Java不支持多重继承。
接口是抽象方法的集合,所有接口的方法都是抽象的,通常以interface声明。
一个类通过继承接口的方式,从而来继承接口的抽象方法。
public interface 接口名称 {...} //接口的定义
接口的实现
当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。
类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。
public class Dog extends Canine implements Pet {...} //接口的实现
实现某接口的类必须实现它所有的方法,因为这些方法都是public和abstract的。
类可以实现多个接口。
从子类调用父类的方法(即不完全覆盖父类方法,只加入额外动作)可以用super关键字进行引用。
abstract class Report{
void runReport(){ //父类方法,带有子类可运用的部分
//设置报告
}
}
class BuzzwordsReport extends Report{
void runReport(){
super.runReport(); //调用父类的方法
buzzwordCompliance();
printReport();
}
}
堆中存放所有的对象,又称为可垃圾回收的堆。
栈中存放方法调用和局部变量。
变量分为实例变量和局部变量。
public class Duck{
int size; //每个Duck对象都会有独立的size
}
public void foo (int x)
int i=x+3;
boolean b=ture; //参数x和变量i,b均为局部变量
非primitive的变量只是保存对象的引用而已,而不是对象本身。
对象引用变量与primitive主数据类型变量都放在栈上。
无论是实例变量或局部变量,对象本身只会存在于堆上。
若Cellphone对象带有一个Antenna对象(即Cellphone带有Antenna类型的引用变量),Java只会留下Antenna引用量而不是对象本身所用到的空间。
pritive Antenna ant;
pritive Antenna ant = new Antenna();
构造函数
Duck myDuck = new Duck(); //在调用Duck的构造函数。
构造函数带有在初始化对象时会执行的程序代码,即新建一个对象时就会被执行。
public Duck {...} //构造函数代码
构造函数无返回类型,且一定与类名相同。
构造函数会在对象能够被赋值给引用之前就执行。常被使用来初始化对象的状态,即设置和给对象的实例变量赋值。
最好的方法是将初始化的程序代码放在构造函数中,把构造函数设置为需要参数的。
public class Duck{
int size;
public Duck(int ducksize){ //给构造函数加参数
size = ducksize; //使用参数值来设定size这个实例变量
System.out.println("size is"+size);
}
}
public class useADuck{
public static void main(String[] args){
Duck d = new Duck(42); //传值给构造函数
}
}
若你没有自己写构造函数,编译器会帮你写一个,默认的构造函数没有参数。
若你已经手动写了一个带参数的构造函数,则编译器不会调用,若你需要一个没有参数的构造函数,则必须自己手动写。
若类有一个以上的构造函数,即构造函数重载,则参数必须不同,包括参数的顺序与类型。
public Duck () { }
public Duck (int size) { }
public Duck (String name) { }
public Duck (String name,int size) { }
public Duck (int size,String name) { } //类型相同但顺序不同
在创建新对象时,所有继承下来的构造函数都会执行。
构造函数在执行时,首先是去执行它父类的构造函数,连锁反应到Object这个类为止。
抽象类也有构造函数。虽然对抽象类不能执行new操作,但抽象类还是父类,它的构造函数会在具体子类创建出实例时执行。
调用父类构造函数的唯一方法是调用super()。对super()的调用必须是构造函数的第一个语句。
public class Duck extends Animal{
int size;
public Duck(int newSize){
super(); //调用父类构造函数
size=newSize;
}
}
若我们没有调用super(),编译器会帮加上super()的调用。编译器帮加的一定是没有参数的版本,若父类有多个重载版本,只有无参数版本会被调用。
使用this()来从某个构造函数调用同一个类的另外一个构造函数。
this()只能用在构造函数中,且必须是第一行语句。super()和this()不能同时调用。
cless Mini extends Car{
Color color;
public Mini(){
this(Color.Red); //无参构造函数以默认的颜色调用真正的构造函数
}
public Mini(Color c){
super("Mini"); //真正的构造函数
color=c;
//初始化动作
}
}
引用变量的生命周期:
public class TestLifeOne{
public void read(){
int s=42; //s的范围只限于read()里,read()方法结束时s完全消失
sleep();
}
public void sleep(){
s=7; //非法使用!
}
}
public class Life{
int size;
public void setSize(int s){
size=s; //s在方法结束时消失,但size在类中,到处都可用
}
}
对象的生命周期由引用变量的生命周期而定。当最后一个引用消失时,对象就会变成可回收的。有三种方式可以释放对象的引用:
void go() { Life z=new Life(); } //z会在方法结束时消失
Life z=new Life();
z=new Life(); //第一个对象会在z被赋值到别处时挂掉
Life z=new Life();
z=null; //第一个对象会在z被赋值为null时击毙
Math类中所有方法都不需要实例变量值。这些方法都是静态的,所以无需Math的实例,用到的只有它的类本身。
int x=Math.round(42.2);
int y=Math.min(56,12); //这些方法无需实例变量,因此无需特定对象来判别行为
非静态方法VS静态方法
static关键词可以标记出不需类实例的方法,即静态方法。
以类的名称调用静态方法
Math.min(88,86);
以引用变量的名称调用非静态方法
Song t2=new Song();
t2.play();
静态方法不能调用非静态的变量和方法。
静态变量的值对所有的实例来说都相同。即,静态变量是共享的,同一类所有的实例共享一份静态变量。
实例变量:每个实例一个
静态变量:每个类一个
public class Duck{ //每个Duck对象都有自己的size变量,但所有Duck实例只有一份duckCount变量
private int size;
private static int duckCount=0; //此静态duckCount变量只会在类第一次载入时被初始化
public Duck(){
duckCount++; //每当构造函数执行时,此变量的值就会递增
}
public void setSize(int s){
size=s;
}
public int getSize(){
return size;
}
}
如果类只有静态的方法,你可以将构造函数标记为private以避免被初始化。
构造函数不可以标记为静态。
静态变量是在类被加载时,在该类的任何静态方法执行之前就初始化的。
静态final变量是常数,加载后一直维持原值。常数变量的名称应该都是大写字母。
public static final double PI=3.1415926
public:可供各方读取
static:因此不需要Math的实例
final:圆周率是不变的
静态final变量的初始化:
public class Foo{
public static final int FOO_X=25; //命名惯例:都是大写,以下划线字符分隔
}
public class Bar{
public static final double BAR_SIGN;
static{ //这段程序会在类被加载时执行
BAR_SIGN=(double)Math.random();
}
}
final也可用来修饰非静态的变量(包括实例变量、局部变量或方法的参数),方法及类。
final的变量代表你不能改变它的值。
final的方法表示你不能覆盖掉该方法。
final的类代表你不能继承该类(即创建它的子类)。
静态的方法不能存取非静态的变量。
非静态的方法可以读取静态的变量,但不能调用该类静态的方法或变量。
autoboxing功能能够自动的将primitive主数据类型转换成包装过的对象。
如创建int的ArrayList:
public void doNumNewWay(){
ArrayList<Integer>listOfNumbers=new ArrayList<Integer>(); //创建Integer类型的ArrayList
listOfNumbers.add(3); //虽然ArrayList没有add(int)方法,但编译器会自动包装
int num=listOfNumbers.get(0); //编译器也会自动解开Integer对象的包装,因此可直接赋值给int
}
generic类型规则是只能指定类或接口类型,因此ArrayList无法通过编译。
数字的格式化
数字的格式化只要调用静态的String.format()并传入值与格式设定。
String s=String.format("%,d",1000000000) //输出1,000,000,000
格式化指令中的%代表一项变量,此变量就是跟在格式化指令后面的参数。
format("%,6.1f",42.000);
int one=20456654;
int two=100567890.248907
String s=String.format("The rank is %,d out of %,.2f", one, two); //输出:The rank is 20,456,654 out of 100,567,890.25
Calendar cal=Calendar.getInstance(); //对静态方法的调用,返回具体子类的实例
cal.set(2004,1,7,15,40); //将时间设定为2004年1月7日15:40
1. 异常是一种Exception类型的对象,Exception类型的对象可以是任何它的子类的实例。
try{
//把有风险的程序放在try块
} catch(Exception ex){
//用catch块摆放异常状况的处理程序
}
throw new FileIsTooSmallException();
public void takeRisk() throws BadException{ //必须声明会抛出BadException
if (abandonAllHope){
throw new BadException(); //创建Exception对象并抛出
}
}
(2)调用该方法的程序代码 public void crossFingers(){
try{
anObject,takeRisk();
} catch (BadException ex){
System.out.println("Aaargh!");
ex.printStackTrace(); //若无法从异常中恢复,至少使用printStackTrace()列出有用信息
}
}
try{...} catch{...} finally{...}
public void doLaundry() throw [父类名称] { //生命成父类可以抛出任何父类的子类
try{
laundry.doLaundry();
} catch ([父类名称] cex) {
//解决方案,可以catch父类的任何子类
}
若不同子类的处理方法不同,则分别写出catch块,若子类的处理方式相同,则可使用父类的catch来处理。try{
x.doStuff();
}
int y=43; //错误,不能在这里放程序
} catch(Expection ex){ }
void go() throws FooException{
try{
x.doStuff();
} finally{}
}
JFrame frame=new JFrame();
frame.getContentPane().add(button);
JFrame frame=new JFrame();
JButton button=new JButton("click me");
frame.getContentPane().add(BorderLayout.EAST,button)
frame.setSize(300,300);
frame.setVisible(true);
addListener
形式。如按钮的ActionEvent注册:button.addActionListener(this);
public void actionPerformed(ActionEvent event) {
button.setText(""clicked) }
import java.awt.*;
import javax.swing.*;
class MyDrawPanel extends JPanel{ //创建JPanel的子类
public void paintComponent(Graphics g){ //方法由系统调用
g.setColor(Color.orange);
g.fillRect(20,50,100,100); //把g想象成绘制装置,设置颜色形状
Image image=new ImageIcon("c.jpg").getImage(); //图文件名
g.drawImage(image,3,4,this);//图案左上角离panel左边缘像素位置
}
}
Graphics2D g2d = (Graphics2D) g;
class MyOuter{
private int x; //外部私用x实例变量
MyInner inner=new MyInner(); //创建内部实例
public void doStuff(){
inner.go(); //调用内部方法
}
class MyInner{
void go(){
x=42; } //内部可以使用外部x变量
} //关闭内部类
} //关闭外部类
x.takeButton(new DogInnerButton())
frame.getContentPane().add(BorderLayout.EAST,button);//指定区域
panel.add(button)
加入)由面板的FlowLayout布局管理。//改变布局管理器
//它的构造函数需要知道管理哪个组件以及使用哪个轴
panel.setLayout(new BoxLayout(panel,BoxLayout.Y_AXIS));
存储对象:
对象的序列化
FileOutputStream把字节写入文件。ObjectOutputStream把对象转换成可以写入串流的数据。当调用ObjectOutputStream的writeObject时,对象会被打成串流送到FileOutputStream写入文件。
①. 创建FileOutputStream
//创建存取文件的FileOutputStream对象
//若文件不存在,会自动被创建出来
FileOutputStream filestream=new FileOutputStream("My.ser");
②. 创建ObjectOutputStream
//能写入对象,但不能直接连接文件,所以需要参数指引
ObjectOutputStream os=new ObjectOutputStream(fileStream);
③. 写入对象
//将变量引用的对象序列化并写入My.ser文件
os.writeObject(characterOne);
os.writeObject(characterTwo);
os.writeObject(characterThree);
④. 关闭ObjectOutputStream
//关闭所关联的输出串流
os.close();
对象必须实现序列化接口才能被序列化。
Serializable接口能让类序列化,即此类的对象可以通过序列化的机制来存储。如果父类实现序列化,则子类也自动实现,无论是否有明确声明。
当对象被序列化时,整个对象版图都会被序列化。被该对象引用的实例变量,和所有被引用的对象也都会被序列化。
序列化是全有或全无的,若有不能序列化的对象,执行期间会碰到异常。
若有某个实例变量不能活不应该被序列化,就把它标记为transient,序列化程序会将它跳过。
import java.io.*;
public class Pond implements Serializable{ //Pond对象可被序列化
transient String currentID; //变量标记为不需要序列化
String username; //变量会被序列化被保存
private Duck duck=new Duck(); //Duck实例变量
public static void main(String[] args){
Pond myPond=new Pond();
try{ //可能会抛出异常
FileOutputStream fs=new FileOutputStream("Pond.ser");
ObjectOutputStream os=new ObjectOutputStream(fs);
os.writeObject(myPonds); //将myPond序列化的同时Duck也会被序列化
os.close();
} catch(Expection ex){
ex.printStackTrace();
}
}
}
public class Duck{ //Duck不能被序列化!因此没有实现序列化!
//duck程序代码
}
对象从stream中读出,Java虚拟机通过存储的信息判断对象的class类型,尝试寻找加载对象的类。若找不到或无法加载该类,虚拟机会抛出异常。新的对象会被配置在堆上,但构造函数不会执行。
//若文件不存在,会抛出异常
FileInputStream filestream=new FileInputStream("My.ser");
//它知道如何读取对象,但需要链接的stream提供文件存取
ObjectInputStream os=new ObjectInputStream(fileStream);
//每次调用readObject()都会从stream中读出下一个对象
Object one=os.readObject();
Object Two=os.readObject();
Object Three=os.readObject();
GameCharacter elf=(GameCharacter) one;
GameCharacter troll=(GameCharacter) two;
GameCharacter magician=(GameCharacter) three;
//FileInputStream会自动跟着关掉
os.close();
写入文本文件
fileWriter.write("My first String to save");
import java.io.*; //加载包
class WriteAFile{
public static void main(String[] args){
try{
FileWriter writer=new FileWriter("Foo.txt");
writer.write("hello,foo!");
writer.close(); //记得关掉
} catch(IOException ex){
ex.printStackTrace();
}
}
}
File类代表磁盘上的文件,但并不是文件中的内容。
File对象代表磁盘上的文件或目录的路径名称,(如:/User/Kathy/Data/GameFile.txt),但它并不能读取或代表文件中的数据。
File对象可创建。浏览和删除目录。
File f=new File("MyCode.txt");
File dir=new File("Chapter7");
dir.mkdir();
if (dir.isDirectory()){
String[] dirContents=dir.list();
for(int i=0;i<dirContents.length;i++){
System.out.println(dirContents[i]);
}
}
System.out.println(dir.getAbsolutePath());
boolean isDelected=f.delect();
BufferedWriter writer=new BufferedWriter(new FileWriter(aFile));
String的split()可以把字符串拆开成String的数组,其中分割字符不会被当做数据来看待。
String toTest="What is blue+yellow/green"; //从文件读出时的样式
String[] result=toTest.split("/"); //split()会用参数所指定的字符把String拆开成两个部分
.for(String token:result){
System.out.pringln(token); //把数组中每个元素逐一列出
}
客户端与服务器应用程序通过Socket连接来沟通。Socket是代表两台机器之间网络连接的对象(java.net.Socket)
Socket连接的建立代表两台机器之间存有对方的信息,包括IP地址与端口号。
TCP端口是一个16位宽,用来指定服务器上特定的应用程序。能够让用户连接到服务器上各种不同的应用程序。
一个地址可以有65536个不同的端口号,从0~1023端口号是保留给HTTP/FTP/SMTP等已知的特定服务使用。
客户端通过Socket连接服务器
Socket s=new Socket("127.0.0.1",5000)
一旦建立连接,客户端可以从Socket取得底层串流
sock.getInputStream();
Socket chatSocket=new Socket("127.0.0.1",5000);
//从Socket取得输入串流
InputStreamReader stream=new InputStreamReader(chatSocket.getInputStream());
BufferedReader reader=new BufferedReader(stream);
String message=reader.readLine();
Socket chatSocket=new Socket("127.0.0.1",5000);
PrintWriter writer=new PrintWriter(chatSocket.getOutputStream());
writer.println("message to send");
writer.print("another message");
服务器可以使用ServerSocket来等待客户端对特定端口的请求
当ServerSocket接到请求后,它会做一个Socket连接来接受客户端的请求
①. 服务器应用程序对特定端口创建出ServerSocket
//服务器应用程序开始监听来自4242端口的客户端请求
ServerSocket serverSock=new ServerSocket(4242);
②. 客户端对服务器应用程序建立Socket连接
Socket sock=new Socket("190.165.1.103",4242)
③. 服务器创建出与客户端通信的新Socket
Socket sock=serverSock.accept();
//accept()方法在等待用户的Socket连接时闲置,当用户连接时,方法会返回一个Socket在不同的端口上以便与客户端通信,ServerSocket与Socket端口不同,因此ServerSocket可空闲等待其他用户
线程是独立的线程,每个线程都有独立的执行空间。
Thread是Java中用来表示线程的类,有启动线程、连接线程、让线程闲置等方法。
每个Thread需要一个任务来执行,即线程在启动时去执行的工作,Runnable即为这个工作
Runnable接口
public void run()
,run()是新线程执行的第一项方法启动新的线程
①. 建立Runnable对象(线程的任务)
Runnable threadJob=new Runnable();
②. 建立Thread对象(执行工人)并赋值Runnable(任务)
Thread myThread=new Thread(threadJob);
//把Runnable对象传给Thread的构造函数,告诉Thread对象要把哪个方法放在执行空间去执行——Runnable的run()方法
③. 启动Thread
myThread.start();
//线程在初始化以后还没有调用start()之前处于新建立的状态。调用Thread对象的start()之后,会建立出新的执行空间,处于可执行状态等待被挑出执行。
通常线程会在可执行与执行中两种状态中来回交替。
当Java虚拟机的调度器选择某个线程之后就处于执行中状态,单处理器的机器只能有一个执行中的线程。
调度不能保证任何执行时间和顺序,不会完全平均分配执行。
Thread.sleep()
Thread.sleep(200); //线程休息200毫秒
setName()方法可帮线程命名
线程并发性问题:两个或两个以上线程存取单一对象的数据,即两个不同执行空间上的方法在堆上对同一个对象执行getter或setter
使用synchornized关键词修饰方法,使它每次只能被单一的线程存取,防止两个线程同时进入同一对象的同一方法
public synchronized void male(int amount) //加到方法的声明上即可
每个对象都有单一的锁,单一的钥匙。
若对象有同步化的方法,则线程只能在取得钥匙的情况下进入线程。
一旦某个线程进入对象的同步化方法,则其他线程无法进入该对象上的任何同步化线程。
同步化会降低程序运行效率,并可能导致锁死现象。所以原则上最好只做最少量的同步化。
当只有个别调用需要同步化时,使用参数所指定的对象的锁来做同步化。
public viod go(){ //不需要整个同步化
doStuff();
synchronized(this){ //以当前对象(this)来同步化
criticalStuff();
moreCriticalStuff();
}
}
没有泛型时,无法声明ArrayList的类型,,只能用Object操作,出来时回事Object类型的引用。
使用泛型后,仅有Fish对象可以放入ArrayList中,取出的还是Fish对象的引用。
new ArrayList()
List songList=new ArrayList()
void foo(List list)
x.foo(songList)
public class ArrayList extends AbstractList implements List
public boolean add(E o)
//E为类型参数,代表用来创建与初始ArrayList的类型,如创建ArrayList,则add会变为add(Dog o)
public class ArrayList extends AbstractList implements List
public boolean add(E o)
//当声明类的类型参数时,可以直接把该类或接口类型用在任何地方
public void takeThing(ArrayList list)
表示任何被声明为Animal及Animal的子类的ArrayList是合法的;
而方法参数ArrayList
表示只有ArrayList是合法的list
public interface Comparable {int compareTo(T o);}
Map事实上并没有继承Collection这个接口,但也是Java集合的一种
if(foo==bar){
//两个引用都指向同一个对象
}
if(foo.equals(bar)&&foo.hashCode()==bar.hashCode()){
//两个引用指向同一个对象,或者两个对象是相等的
}
class Book implements Comparable{
String title;
public Book(String t){
title=t;
}
public int compareTo(Object b){
Book book=(Book) b;
return (title.compareTo(Book.title));
}
}
public class BookCompare implements Comparator<Book>{
public int compare(Book one,Book two){
return(one.title.compareTo(two.title));
}
}
class Test{
public void go(){
Book b1=new Book("Cats");
Book b2=new Book("Remix");
Book b1=new Book("Finding");
BookCompare bCompare=new BookCompare();
TreeSet<Book> tree=new TreeSet<Book>(bCompare);
tree.add(new Book("Cats"));
tree.add(new Book("Remix"));
tree.add(new Book("Finding"));
System.out.println(tree);
}
}
import java.util.*;
public class TestMap{
public static void main(String[] args){
HashMap<String,Integer> scores=new HashMap<String,Integer>(); //HashMap需要两个类型参数:关键字和值
scores.put("Kathy",42);
scores.put("Bert",343); //使用put()取代add(),需要两个参数
scores.put("Skyler",420);
System.out.println(scores);
System.out.println(scores.get("Bert")); //get()取用关键字参数,返回它的值
}
}
Map列出时,以{key=value,key=value,…}的形式打印出
void foo(Animal[] a){}
,若Dog有extend过Animal,则可以用两种方法调用foo(anAnimalArray);
, foo(aDogArray);
public void takeAnimals(ArrayList extends Animal>animal){
for(Animal a:animals){
a.eat();
}
}
使用带>的声明时,编译器会阻止任何东西加到集合中,可以操作集合元素,但不能新增元素标准的组织化结构:创建项目目录,在其下建立source和classes目录。把源代码(.java)存储在source目录下。在编译时让输出(.class)产生在classes目录下。
%cd MyProject/source
%javac -d../classes MyApp.java
-d可以指定编译过的程序放在指定的目录结构中
执行程序:
%cd MyProject/classes
java MyApp //从classes目录来运行
把程序包进JAR
大部分完全在本机的Java应用程序都是以可执行的JAR来部署的
可执行的JAR代表用户不需把文件抽出来就能运行
①确定所有的类文件都在classes目录下
②创建manifest.txt来描述哪个类带有main()方法
Main-Class:MyApp //后面没有class
将此文件放在classes目录下
③执行jar工具来创建带有所有类以及manifest的JAR文件
%cd MyProject/classes
%jar -cvmf manifest.txt app1.jar *.class
执行JAR
Java虚拟机能从JAR中载入类,并调用该类的main()方法
Java虚拟机会检查JAR的manifest寻找入口,-jar标识告诉Java虚拟机所给的是个JAR
%cd MyProject/classes
%java -jar app1.jar
将类放在包中可防止名称冲突。在类名称前加上域名。
com.headfirstjava.Chart
在程序源文件最前面加上包指令可以把类包进包中。
package com.headfiresjava
类必须待在完全相对应于包结构的目录中才能包进包中。以上为例,Chart类必须放在com目录下的headfirstjava目录中
编译与执行包
加上-d(directory)选项来编译:
%cd MyProject/source
%javac -d../classes/com/headfirstjava/Chart.java
执行程序:
%cd MyProject/classes
%java com.headfirstjava.Chart
-d会要求编译器将编译结果根据包的结构建立目录并输出,若classes目录下包的目录结构还没有建好,编译器会自动处理。
JAR与包
①确定所有的类文件都在classes目录下正确对应的包结构中
②创建manifest.txt来描述哪个类带有main()方法,以及确认有使用完整的类名称
Main-Class:com.headfirstjava.Chart //后面没有class
将此文件放在classes目录下
③执行jar工具来创建带有所有类以及manifest的JAR文件
%cd MyProject/classes
%jar -cvmf manifest.txt Chart.jar com
Java Web Start
JWS工作方式:
①客户端点击某个网页上JWS应用程序的链接(.jnlp文件)
②Web服务器接受请求发出.jnlp文件(不是JAR)给客户端浏览器
③浏览器启动JWS,JWS的helper appa读取.jnlp文件,然后向服务器请求.jar
④Web服务器发送.jar文件
⑤JWS取得JAR并调用指定的main()来启动应用程序
创建于部署JWS的步骤
①将程序制作成可执行的JAR
②编写.jnlp文件
③把.jnlp与JAR文件放到Web服务器
④对Web服务器设定新的mime类型
application/x-java-jnlp-file
⑤设定网页连接到.jnlp文件(.html)
Launch My Application
远程调用方法过程
①客户端对象对辅助设施对象调用doBigThing()
②客户端辅助设施把调用信息打包通过网络送到服务端的辅助设施
③服务端辅助设施解开来自客户端辅助设施的信息,并以此调用真正的服务
- 客户端对象以为它调用的是远程的服务,因为辅助设施假装成该服务对象
- 客户端辅助设施假装成服务,但只是“代理”
- 服务器辅助设施会通过Socket连接来自客户端设施的要求,解析打包送来的信息,调用真正的服务
在RMI中,客户端辅助设施称为stub,服务端辅助设施称为skeleton
stub是个处理底层网络细节的辅助性对象,会把方法的调用包装起来送到服务器上
创建远程服务
步骤1:创建Remote接口
远程接口定义了客户端可以远程调用的方法,是个作为服务的多态化类,stub和服务都会实现此接口。
import java.rmi.*;
//Remote是标记性接口,没有方法,你的接口必须继承java.rmi.Remote
public interface MyRemote extends Remote{
//所有接口中的方法都必须声明RemoteException
//确定参数和返回值都是primitive主数据类型或Serializable
public String sayHello() throws RemoteException;
}
步骤2:实现Remote
这是真正执行的类,它实现出定义在该接口上的方法,是客户端会调用的对象。
import java.rmi.*;
import java.rmi.server.*;
//服务必须要实现Remote——就是客户端会调用的方法
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote{
//编译器会确保你实现出所有接口定义的方法,此例中只有一个
//无需声明RemoteException
public String sayHello(){
return "Server says,'Hey'";
}
//父类UnicastRemoteObject的构造函数声明了异常,因此必须写出构造函数,代表你的构造函数会调用带有风险的程序代码
public MyRemoteImpl() throws RemoteException{}
public static void main(String[] args){
try{
//帮服务器命名,客户端会靠名字查询registry
//向registry注册服务,RMI系统会把stub加到registry中
//使用java.rmi.Naming的rebind()来注册远程服务
MyRemote service=new MyRemoteImpl(); //创建远程对象
Naming.rebind("Remote Hello",service);
} catch(Exception ex){
ex.printStackTrace();
}
}
}
步骤3:用rmic产生stub与skeleton
对实现出的类(不是remote接口)执行rmic,客户端和服务器都有helper,无需创建类的源代码,在执行JDK所附的rmic工具时会自动处理掉。
步骤4:启动RMI registry
调用命令行,确定从可以存取到该类的目录来启动。
%rmiregistry
步骤5:启动远程服务
调用另一个命令行,可能是从你所实现的类上的main()或独立的启动用类来进行。
%java MyRemoteImpl
import java.rmi.*; //Naming类在java.rmi中
public class MyRemoteClient{
public static void main(String[] args){
new MyRemoteClient().go();
}
public void go(){
try{
//类型转换,客户端必须要使用与服务相同的类型,必须要转换成接口类型,因为查询结果是Object类型
//rmi://主机名称或IP地址/注册名称
MyRemote service=(MyRemote) Naming.lookup("rmi://127.0.0.1/Remote Hello");
String s=service.sayHello();
System.out.println(s);
} catch(Exception ex){
ex.printStackTrace();
}
}
}
使用RMI时注意:
- RMI registry必须在同一台机器上与远程服务一起执行
- 使用Naming.rebind()注册服务前rmiregistry必须启动
- 参数和返回类型必须做成可序列化
servlet
servlet不是Java标准函数库的一部分,需要servlet.jar文件中的servlet相关包才能编译出servlet。可从java.sun.com或Web服务器供应商处获得(Java2 Enterprise Edition,即J2EE带有Servlet函数库)
public class MyServletA extends HttpServlet {...}
This is a servlet.
import java.io.*;
import java.servlet.*;
import java.servlet.http.*; //两个包需另行下载,不是Java标准库的一部分
public class MyServletA extends HttpServlet{
//servlet可以通过doGet()的相应参数取得输出串流来组成响应的网页
public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
//告诉服务器和浏览器相应结果的形式
response.setContentType("text/html");
//此对象会给我们可写回结果的输出串流
PrintWriter out=response.getWriter();
//print对象是个网页,内容由程序的输出组成
String message="If you're reading this,it worked";
out.println("");
out.println(""
+message+"");
out.println("");
out.closse();
}
}
- EJB对象会拦截对带有商业逻辑的bean的调用,并将所有EJB服务器提供的服务加上一个中介层。
- bean对象被保护不受客户端直接存取,只有服务器能够与bean沟通,使服务器能介入执行安全性和交易控管等工作
Jini也使用RMI,多了几个关键功能:
(1)自适应探索(adaptive discovery)
(2)自恢复网络(self-healing networks)
当服务上线,它会动态探索网络上的Jini查询服务并申请注册,服务会送出一个序列化的对象给查询服务,且注册的是所实现的接口。取得查询服务的引用后,客户端可以查询“有没有东西实现ScientificCalculator”,查询服务若找到,会返回该服务所放上来的序列化对象。