《First Head Java》摘抄

292 阅读16分钟

继承与多态

继承防止子类中出现重复代码

接口与抽象类

接口:纯抽象类(abstract)

抽象方法

抽象方法就是用abstract修饰的方法,它没有实体,如下:

public abstract void eat()

当然抽象方法看起来没有意义,但有时就会碰到没办法放在父类中但却需要可继承的方法体。它的意义就是就算无法实现出方法的内容,但还是可以定义出一组子型共同的协议。当然了你必须实现所有抽象的方法,必须以相同的方法签名和相容的返回类型创建出非抽象的方法。

ArrayList和Object

  • ArrayList:
boolean remove(Object elem);
boolean contains(Object elem);
boolean isEmpty();
int indexOf(Object elem);
Object get(int index);
boolean add(Object elem);
  • Object 我们java的究极父类Object类,万物起源。
boolean equals(Object o);
int hashCode();
Class getClass();
String toString();

书中:

  1. 强烈建议覆盖hashCode()/equals()/toString()
  2. Object实例是通用的轻量级的对象,它常用于线程同步化
  3. Object类的两个主要目的:作为多态让方法应付多种类型的机制,以及提供Java在执行期间对任何对象都有需要的方法(有一部分还是与线程有关)
  4. 确保类型安全检查

使用Object类型的多态引用是会付出代价的

a question:

ArrayList<Object> myDogArrayList = new ArrayList<Object>();
Dog aDog = new Dog();
myDogArrayList.add(aDog);

如上的操作会使下面的语句无法通过编译

Dog d = myDogArrayList.get(0);

因为get(0)方法返回的是Object类型的引用,编译器无法把它确认为Dog。

public void go() {
    Dog aDog = new Dog();
    Dog sameDog = getObject(aDog);
}
public Object getObject(Object o) {
    return o;
}

以上也无法通过编译,因为这个getObject()只能返回给Object对象
需要Object sameDog = getObject(aDog);

编译器是根据引用类型来判断有哪些methon可以调用,而不是根据Object确实的类型。

so给出还原方法

Object o = al.get(index);
Dog d = (Dog)o;
d.roam();
//更安全一点,我们先检查一下它是不是狗
if(o instanceof Dog) {
    Dog d = (Dog) o;
}

以上证明了引用变量类型的重要性!它明确了可调用的方法。
类的公有方法是合约的内容,合约是你对其他程序的承诺协议。

想要 部分已继承Animal类的子类 去实现Pet的多个方法 可以有以下几个方法:

  1. 把Pet方法加入到Animal类中,但河马可能也变成Pet
  2. 把Pet方法设定成抽象方法,强迫每个动物去覆盖,当然这很麻烦
  3. 把方法就加到需要的地方 子类继承了两个父类,这两个父类存在同样的方法签名,这样就会导致子类不知道调用哪一个父类方法。考虑到这种情况出现了接口!!interface(哇我写了三天接口。。。我爱上了它)
//接口的定义
public interface Pet{...只写返回类型和方法签名就好了}
//接口的实现(必须通过类继承实现):
public class Dog extends Canine implements Pet{...}

// public abstract可以忽略哦!!!

不同的继承树可以实现相同的接口:如果你想要对象的状态保存在文件中,只要去实现Serializable这个接口就行;打算让对象的方法以单独的线程来执行,实现Runnable接口就行

设计:

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

抽象类 vs 接口

接口的设计目的,是对类的行为进行约束(更准确的说是一种“有”约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。对“接口为何是约束”的理解,我觉得配合泛型食用效果更佳。

而抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不允许实例化出来(否则当调用到A-B时,无法执行)。

super.已覆盖方法名()调用父类方法。

构造器与垃圾收集器

栈与堆:生存空间

heap(堆)是对象的生存空间,stack(栈)是方法调用及变量的生存空间。以JVM的角度进一步体会一下实例变量(成员变量,方法块外部声明,在堆中)和局部变量(方法块内部声明,包括方法参数,在栈里)!

  • 为什么要理解栈与堆的机制? 如果想要了解变量的有效范围(scope)、对象的建立、内存管理、线程(thread)和异常处理,则必须认识栈与堆,这涉及到线程和异常处理。

对象引用变量与primitive主数据类型变量在栈上。

Duck myDuck = new Duck();

以上声明,创建和赋值。
构造方法没有返回值就也不用写void(这是判断是不是构造函数的判据)。
构造函数无法继承。
构造函数直接是重载(参数不同,方法签名一致是重写)。
编译器不一定会帮你构造无参的构造函数。
若你自己已经写了一个有参的,那你必须手动实现一个无参的。为了子类继承
构造函数可以是公有、私有和不指定类型的。
私有的构造函数说明该类以外不能存取。
父类的每个构造函数会在具体子类创建实例时执行。启动构造函数是一个连锁反应。因为子类的方法可能会用到父类的实例变量
抽象类也有构造函数。

so子父类间类似这样

public class Duck extends Animal {
    int size;
    
    public Duck(int newSize) {
        Animal();
        size = newSize;
    }
}
//Animal(); is illegal
//会想下面这样,但其实JVM都帮我们搞定了
public class Duck extends Animal {
    int size;
    
    public Duck(int newSize) {
        super();
        size = newSize;
    }
}

以上确保了子不会在父没出生前就生出来hhh

this()就是对象本身的引用,靠它来执行真正的构造函数,只能用在构造函数中且与super()冲突!

class Mini extends car {
    Color color;
    
    public Mini() {
        this(Color.Red);
    }
    //真正的构造函数
    public Mini(Color c) {
        super("Mini");
        color = c;
    }
    //illegal,this()和super()不可兼得
    public Mini(int size) {
        this(Color.Red);
        super(size);
    }
 }

life是存活时长,scope是范围,假如被别的方法调用呢,该变量还活着但不在目前范围内了。

三种方法释放对象的引用

//永久性的离开它的范围,z会在方法结束时消失
void go() {
    Life z = new Life();
}
//引用被赋值到其他的对象上,第一个对象在z在别处被赋值后消失
Life z = new Life();
z = new Life();
//直接将引用设定为null,z被赋值为null时击毙
Life z = new Life();
z = null;

数字与静态

Math

Math类不需要实例变量值,因为这些方法都是静态的Math mathObject = new Math()会报错因为Math的构造函数是私有的。

静态方法

静态方法没有成员变量也不能有实例对象。只能靠类名.方法名()来调用。
静态的方法不能调用非静态的变量,静态方法不知道堆上有哪些对象。
静态方法也不能调用非静态方法。

Duck d = new Duck();
String[] s = {};
d.main(s);
//以上合法但是main并不知道是d对象调用的。

静态变量

比如要计算一个类被构造了多少次,来看个代码:

class Duck() {
    int duckCount = 0;
    public Duck() {
        duckCount++;
    }
}
//这样达咩,每次构造都会被重新初始化为O

以上情况就要有请静态变量了

class Duck() {
    private static int duckCount = 0;
    public Duck() {
        duckCount++;
    }
}
//这样就是第一次载入时被初始化。

静态变量可以被同一类的所有实例共享!实例变量每个实例一个,静态变量每个类一个。
静态变量会在该类的任何对象创建之前和任何静态方法执行之前就初始化,默认值为0。
static final就是常量,常量名一般是大写
当然final可以修饰非静态变量,表明不能再修改该变量值、不能覆盖该方法、不能继承它的类。

primitive主数据类型的包装

Boolean
Character
Byte
Short
Integer
Long
Float
Double

//autoboxing
int i = 288;
Integer iWrap = new Integer(i);
int unWrapped = iWrap.intValue();

//以下无法通过编译
ArrayList<int>

数字的格式化

String s = String.format("%,d",1000000000);
sout+enter
//输出就是1,000,000,000
%,.2f
%[argument number][flags][width][.precision]type
String.format("%tc", new Date());
String.format("%tr", new Date());
Date today = new Date();
String.format("%tA%tB %td", today, today, today);
String.format("%tA, %<tB %<td", today);

现在一般用java.util.Calendar

//达咩
Calendar cal = new Calendar();
//一哟
Calendar cal = Calendar.getInstance();
cal.set(2004, 1, 7, 15, 40);
cal.setTimeInMillis(day1);
cal.add(cal.DATE, 35);
cal.roll(cal.DATE, 35);
cal.set(cal.DATE, 1);

我们无法取得Calendar的实例,可以取得它的具体子类的实例。

异常处理

抛出

//必须声明它会抛出BadException
public void takeRisk() throws BadException {
    if(abandonAllHope) {
        //创建对象并抛出
        throw new BadException();
    }
}

public void crossFingers() {
    try{
        anObject.takeRisk();
    } catch (BadException ex) {
        sout("Aaargh!");
        ex.printStackTrace();
    }
}

final语句即使之前有return语句已经会执行的

public class Laundry {
    public void doLaundry () throws PantsException, LingerieException {
    //可能抛出两个异常的程序代码
    }
}

public class Foo {
    public void go() {
        Laundry laundry = new Laundry();
        try{
            laundry.doLaundry();
        } catch(PantsException pex) {
            //恢复程序代码
        } catch(LingerieException lex) {
            //恢复程序代码
        }
    }
}

异常也是多态的,有多个catch块时要从小排到大

tips

  1. catchfinally不能没有try
  2. trycatch之间不能有程序
  3. try一定要有catchfinally
  4. 只带有finallytry必须要声明异常

序列化和文件的输入、输出

对象有状态和行为两种属性,行为存在在类中,状态存在于个别的对象中。
序列化的对象写到文件中,程序可以将对象读出并恢复为对象
用其他程序可以解析的特殊字符写到文件中,列如用tab字符来分隔的档案让电子表格或数据库程序可以应用。
存储状态

//创建出FileOutputStream
FileOutputStream fileStream = new FileOutputStream("MyGame.ser");
//创建ObjectOutputStream
ObjectOutputStream os = new ObjectOutputStream(fileStream);
//写入对象
os.writeObject(characterOne);
os.writeObject(characterTwo);
os.writeObject(characterThree);
//关闭ObjectOutputStream
os.close();

如果要让类能够被序列化,就实现Serializable接口
被称为marker或tag类的标记用接口。如果某类可序列化则其子类也自动地可以序列化

//必须要实现序列化
objectOutputStream.writeObject(myBox);
//Box类
import java.io.*;

public class Box implements Serializable {
    //成员变量会被序列化
    private int width;
    private int height;
    
    public void setWidth(int w) {
        width = w;
    }
    public void setHeight(int h) {
        height = h;
    }
public static void main (String[] args) {
    Box myBox = new Box();
    myBox.setWidth(50);
    myBox.setHeight(20);
    
    try{
        FileOutputStream fs = new FileOutputStream("foo.ser");
        ObjectOutputStream os = new ObjectOutputStream(fs);
        os.writeObject(myBox);
        os.close();
    } catch(Exception ex) {
        ex.printStackTrace();
    }
}
}

序列化也有部分被正确保存的场景

public class Pond implements Serializable{
    private Duck duck = new Duck();
}
public class Duck {
}

Duck不能被序列化。
transient标签标记不能序列化。
不需要序列化的情况:比如密码,不想让它存到文件中
如果父类不可序列化,子类序列化,那还原时父类会创建新的对象。

解序列化

//创建出FileOutputStream
FileOutputStream fileStream = new FileOutputStream("MyGame.ser");
//创建ObjectOutputStream
ObjectOutputStream os = new ObjectOutputStream(fileStream);
//写入对象
os.writeObject(characterOne);
os.writeObject(characterTwo);
os.writeObject(characterThree);
//转换对象类型
GameCharacter elf = (GameCharacter) one;
GameCharacter troll = (GameCharacter) two;
GameCharacter magician = (GameCharacter) three;
//关闭ObjectOutputStream
os.close();
  1. 对象从stream中读出来
  2. Java虚拟机通过存储的信息判断出对象的class类型
  3. Java虚拟机尝试寻找和加载对象的类。如果Java虚拟机找不到或无法加载该类,则Java虚拟机会抛出例外。
  4. 新的对象会被配置在堆上,但构造函数不会执行!对象的状态抹去又变成全新的,而这不是我们想要的结果。我们需要的是对象回到存储时的状态。
  5. 如果对象在继承树上有个不可序列化的祖先类,则该不可序列化类以及在它之上的类构造函数就会执行(就算可序列化的一样)。一旦构造函数连锁启动后将无法停止,也就是说,从第一个不可序列化的父类开始,全部都会重新初始状态。
  6. 序列化时。transient变量会被赋值null的对象引用或primitive主数据类型的默认为0、false等值。

网络传送序列化对象可以通过URL来指定位置,该机制在Java的Remote Method Invocation(RMI,远程程序调用机制),可以把序列化的对象当作参数的一部分来传送,若接收此调用的Java虚拟机没有这个类的话,它可以自动地使用URL来取回并加载该类。

网络与线程

建立Socket连接

Socket chatSocket = new Socket("196.164.1.103", 5000);
端口值:0~65535(其中0~1023已分配给特定服务)

线程

由聊天软件引入了一个场景:

我们需要同时执行的能力,检查服务器信息的同时不会打断用户和GUI的交互。用户在对话框中输入信息或滚动接收画面还需要有东西在持续地读服务器数据!
这就是线程(Thread),一个独立执行空间(stack(这里是不是应该说栈帧?))。

Thread t = new Thread();
t.start();

线程是java.lang.Thread这个类的实例,天然被import的,如StringSystem一样。
Java有多个线程但只有一个Thread类。
线程是独立的线程,代表独立的执行空间,每个Java应用程序会启动一个主线程将main()放在它自己执行空间的最开始处。虚拟机负责主线程的启动,如GC,程序员负责启动自己建立的线程。

//Thread类
void join();
void start();

static void sleep();

虚拟机会在线程间进行切换。

启动新线程

  • Runnable对象
Runnable threadJob = new MyRunnable();
Thread myThread = new Thread(threadJob);
myThread.start();

实现Runnable接口

public class MyRunnable implements Runnable {
    public void run() {
        go();
    }
    public void go() {
        doMore();
    }
    public void doMore() {
        System.out.println("top o' the stack");
    }
}

classThreadTester {
    public static void main (String[] args) {
        Runnable threadJob = new MyRunnable();
        Thread myThread = new Thread(threadJob);
        myThread.start();
    }
}

由此是线程的三个状态:新建、可执行和执行中。
还存在阻塞(闲置、等待)和结束
线程调度器

  • 实现Runnable接口和继承Thread类覆盖run()方法 这其实是设计概念有关,继承Thread并不符合面向对象的观点。Thread与线程任务是两回事,不存在更特殊的线程任务,因此用接口实现更符合设计概念。当然这个不影响性能或语言用法,继承Thread类合法但不是个好主意。
  • Thread对象可以重复使用吗?能否用start()指定新任务 不行,一旦run()完成后线程就die了,Thread对象可能还在堆上,还能接受某些方法的调用,但是那个执行完的线程就是die了。

sleep

//这样会抛出异常
Thread.sleep(2000);

try {
    Thread.sleep(2000);
} catch(InterruptedException ex) {
    ex.printStackTrace();
}

这个异常是API用来支持线程间通信的机制,但几乎没有人用这个。我们通常一不用sleep()来保证其他的线程会被执行。

public class MyRunnable implements Runnable {
    public void run() {
        go();
    }
    
    public void go () {
        try {
            Thread.sleep(2000);
        } catch(InterruptedException ex) {
            ex.printStackTrace();
        }
        doMore();
    }
    
    public void doMore() {
        System.out.println("top o' the stack");
    }
}

class ThreadTestDrive {
    public static void main (String[] args) {
        Runnable theJob = new MyRunnable();
        Thread t = new Thread(theJob);
        t.start();
        System.out.println("back in main");
    }
}
public class RunThreads implements Runnable {
    public static void main (String[] args) {
        RunThreads runner = new RunThreads();
        Thread alpha = new Thread(runner);
        Thread beta = new Thread(runner);
        alpha.setName("Alpha thread");
        beta.setName("Beta thread");
        alpha.start();
        beta.start();
    }
    public void run() {
        for(int i = 0; i < 25; i++) {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "is running");
        }
    }
}

并发问题

并发性会引起竞争状态,由转账问题引出锁。

synchronized

private synchronized void makeWithdrawal (int amount) {
    if(account.getBalance() >= amount) {
        System.out.println(Thread.currentThread().getName() + " is about to withdraw");
        try{
            System.out.println(Thread.currentThread().getName() + " is going to sleep");
            Thread.sleep(500);
        } catch (InterruptedException ex) {ex.printStackTrace();} {
            System.out.println(Thread.currentThread().getName() + " woke up");
            account.out.println(Thread.currentThread().getName() + " completes the withdraw1");
            System.out.println(Thread.currentThread().getName() + " completes the withdraw");
        } else {
            System.out.println("Sorry, not enough for " + Thread.currentThread().getName());
        }
    }
}

synchronized还可以保障数据更新问题:

public synchronized void increment() {
    int i = balance;
    balance = i + 1;
}

实例:聊天室

SimpleChatClient

import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class SimpleChatClient {
    
    JTextArea incoming;
    JTextField outgoing;
    BufferedReader reader;
    PrintWriter writer;
    Socket sock;
    
    public static void main(String[] args) {
        SimpleChatClient client = new SimpleChatClient();
        client.go();
    }
    
    public void go() {
        JFrame frame = new JFrame("Ludicrously Simple Chat Client");
        JPanel mainPanel = new JPanel();
        incoming = new JTextArea(15,50);
        incoming.setLineWrap(true);
        incoming.setWrapStyleWord(true);
        incoming.setEditable(false);
        JScrollPane qScroller = new JScrollPane(incoming);
        qScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        qScroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        outgoing = new JTextField(20);
        JButton sendButton = new JButton("send");
        sendButton.addActionListener(new SendButtonListener());
        mainPanel.add(qScroller);
        mainPanel.add(outgoing);
        mainPanel.add(sendButton);
        setUpNetworking();
        
        Thread readerThread = new Thread(new IncomingReader());
        readerThread.start();
        
        frame.getContentPane().add(BorderLayout.CENTER, mainPanel);
        frame.setSize(400,500);
        frame.setVisible(true);
    }
    
    private void setUpNetworking() {
        try {
            sock = new Socket("127.0.0.1", 5000);
            InputStreamReader streamReader = new InputStreamReader(sock.getInputStream());
            reader = new BufferedReader(streamReader);
            writer = new PrintWriter(sock.getOutputStream());
            System.out.println("networking established");
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    
    public class SendButtonListener implements ActionListener {
        public void actionPerformed (ActionEvent ev) {
            try {
                writer.println(outgoing.getText());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            outgoing.setText("");
            outgoing.requestFocus();
        }
    }
    
    public class IncomingReader implements Runnable {
        public void run () {
            String message;
            try {
                while ((message = reader.readLine()) != null) {
                    System.out.println("read" + message);
                    incoming.append(message + "\n");
                }
            } catch(Exception ex) {ex.printStackTrace();}
        }
    }
}

VerySimpleChatServer


import java.io.*;
import java.net.*;
import java.util.*;

public class VerySimpleChatServer {

    ArrayList clientOutputStreams;

    public class ClientHandler implements Runnable {
        BufferedReader reader;
        Socket sock;

        public ClientHandler(Socket clientSocket) {
            try{
                sock = clientSocket;
                InputStreamReader isReader = new InputStreamReader(sock.getInputStream());
                reader = new BufferedReader(isReader);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        public void run() {
            String message;
            try {
                while((message = reader.readLine()) != null) {
                    System.out.println("read" + message);
                    tellEverone(message);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        
        public void go() {
            clientOutputStreams = new ArrayList();
            try {
                ServerSocket serverSocket = new ServerSocket(5000);

                while(true) {
                    Socket clientSocket = serverSocket.accept();
                    PrintWriter writer = new PrintWriter(clientSocket.getOutputStream());
                    clientOutputStreams.add(writer);

                    Thread t = new Thread(new ClientHandler(clientSocket));
                    t.start();
                    System.out.println("got a connection");
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        public void tellEverone(String message) {
            Iterator it = clientOutputStreams.iterator();
            while(it.hasNext()) {
                try {
                    PrintWriter writer = (PrintWriter) it.next();
                    writer.println(message);
                    writer.flush();
                } catch(Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
        
        public static void main (String[] args) {
            new VerySimpleChatServer().go();
        }
    }
}

tips

静态变量也可以使用同步化
可以依靠线程优先级来维持程序的正确性

集合与泛型

ArrayList没有排序,TreeSetCollections.sort()可以排序。

泛型

几乎所有以泛型写的程序都与处理集合有关,泛型的主要目的就是能写出类型安全性的集合,比如防止把Dog加到一群Cat中。

//创建泛型化类的实例
new ArrayList<Song>();
//声明与指定泛型类型的变量
List<Song> songList = new ArrayList<Song>();
//声明与调用取用泛型类型的方法
void foo(List<Song> list)
x.foo(songList);
  • 想设计出让人们在初始化同时要决定类型的类 创建泛型类
public class ArrayList<E> extends AbstractList<E> implements List<E> ... {
    public boolean add(E o)
}

ArrayList<String> thisList = new ArrayList<String>

使用定义在类声明的类型参数
public class ArrayList<E> extends AbstractList<E>
使用未定义在类声明的类型参数
public <T extends Animal> void takeThing(ArrayList<T> list)
public void takeThing(ArrayList<Animal> list)

  • 参数化类型加上限制的方法 限制只允许有实现某特定接口的类,需要对两种情形都能使用的语法——继承和实现,就是extends不管接口或类都能使用。
    sort()方法
    public static <T extends Comparable<? super T>> void sort(List<T> list)
    <T extends Comparable表示必须是Comparable
    <? super T>表示必须是TT的父型
    List<T>仅能传入Comparable的参数化类型的List

此外还有另一种sort()方法: sort(List<T> list, Comparator<? super T> c)

class ArtistCompare implements Comparator<Song> {
    public int compara(Song one, Song two) {
        return one.getArtist().compareTo(two.getArtist());
    }
}

ArtistCompare artistCompare = new ArtistCompare();
//调用sort()传入list与Comparator对象
Collections.sort(songList, artistCompare);
  • 题外话 调用sort()传入listComparator对象就是垃圾注释,好注释不需要重复程序代码

集合

浅浅分为三大类:

  • LIST 对付顺序的好帮手,知道索引位置的集合
  • SET 注重独一无二
  • MAP 用key来搜索专家,使用成对的键值和数据值。

来聊聊对象相等

  • 引用相等性 堆上同一个对象的两个引用,需要==

  • 对象相等性 堆上的两个不同对象在意义上是不同的,这种需要equals()

  • HashSet要检查hashCode()equals() 如果把对象加入HashSet时,需要用hashcode值判断对象加入的位置,要与其他已加入的对象的hashcode作对比。必须重载overridehashCode()来确保对象相同的值。

hashCode()与equals()

class Song implements Comparable<Song> {
    String title;
    String artist;
    String rating;
    String bpm;

    public boolean equals(Object aSong) {
        Song s = (Song) aSong;
        return getTitle().equals(s.getTitle());
    }

    public int hashCode() {
        return title.hashCode();
    }

    public int compareTo(Song s) {
        return title.compareTo(s.getTitle());
    }

    Song (String t, String a, String r, String b) {
        title = t;
        artist = a;
        rating = r;
        bpm = b;
    }

    public String getTitle() {
        return title;
    }

    public String getArtist() {
        return artist;
    }
    
    public String getBpm() {
        return bpm;
    }
    
    public String toString() {
        return title;
    }
}
  1. 两对象相等hashcode必须相等
  2. 两对象相等a.equals(b)或b.equals(a)会返回true
  3. hashcode值相等,两对象不一定相等
  4. 若equals()被覆盖过,hashCode()也必须被覆盖
  5. hashCode()默认行为是对在堆上的对象产生独特的值。若你没有override hashCode(),则该class的两个对象怎样都不换被认为相同
  6. equals()默认以==执行,即测试两个引用是否对上堆上同一个对象。若equals()没有被覆盖过,两个对象怎样都不会被认为是相同的

TreeSet必须实现Comparable

包、jar存档文件和部署

部署应用程序

  • 本机(Executable Jar):整个程序都在用户的计算上以独立、可携的GUI执行,并以可执行的Jar来部署
  • 两者组合(Web Start|RMI app):分散在用户本地系统运行的客户端,连接到执行应用程序服务的服务器部分
  • 远程(Servlets):整个都在服务器端执行。

将源代码与类文件分离

MyProject:classes(MyApp.class)与source(MyApp.java)

把程序包进JAR

JAR就是JavaARchive,是pkzip格式的文件,把一组类文件包装起来,jar指的是整理文件工具(类似UNIX的tar命令)
在classes目录下建立一个manifest.text描述main()方法:\

Main-Classes:
MyApp

jar工具来创建所有类以及manifest的JAR文件
Java虚拟机能从JAR中载入类,并调用该类的main()方法
package打包指令
META-INF(一个目录)代表META INFormation,jar工具自动创建出这个目录和MANIFEST.MF文件,manifest.text不会放进JAR中,但它的内容会放进去

Java Web Start

应用程序可以通过JWS从浏览器执行首次启动,它并不受浏览器的束缚
其中应用程序连接就是.jnlp文件,它描述应用程序可执行JAR文件的XML文件
JWS的helper app(安装在客户端)读取.jnlp文件,然后向服务器请求MyApp.jar
Web服务器发送.jar文件
JWS取得JAR并调用指定的main()来启动应用程序

  1. 将程序制作成可执行的JAR
  2. 编写.jnlp文件
  3. .jnlp文件与JAR文件放到Web程序
  4. 对Web服务器设定新的mime类型:application/x-java-jnlp-file
  5. 设定网页连接到.jnlp文件
  • JWS与applet的区别 applet无法独立于浏览器之外

远程部署的RMI

  • RMI: Remote Method Invocation
  • EJB: Enterprise Java Bean
  • Servlet

远程过程调用的设计

  • 创建出4种东西:服务器、客户端、服务器辅助设施和客户端辅助设施

辅助设施的任务

辅助设施是个在实际上执行通信的对象,使客户端感觉好像在调用本机对象。客户端调用辅助设施的方法,就像客户端就是服务器一样,客户端是真正服务的代理。
即客户端以为它调用远程的服务,因为辅助设施假装成该服务对象。
服务器的辅助设施会通过Socket连接来自客户端设施的要求,解析打包送来的信息,调用真正的服务,因此对服务对象来说调用来自于本地。

调用方法的过程

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

Java RMI提供客户端和服务器端的辅助设施对象

它使用JRMP或IIOP协议,一个是为了Java与Java间的远程调用而设计的,一个是为了common object request broker architecture而产生的。

创建远程服务

  1. 创建Remote接口
  2. 实现Remote
  3. 用rmic产生stub与skeleton
  4. 启动RMI registry:电话簿一般,获得代理(客户端的stub/helper)
  5. 启动远程服务:服务类创建实例并向RMI registry注册

实例

创建远程接口

import java.rmi.*;
public interface MyRemote extends Remote {
    public String sayHello() throws RemoteException;
}

实现远程接口

public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
    public String sayHello() {
        return "Server says, 'Hey'";
    }
    
    public MyRemoteImp1() throws RemoteException{}
    
    try {
        MyRemote service = new MyRemoteImp1();
        Naming.rebind("Remote Hello", service);
    } catch (Exception ex) {}
}

产生stub和skeleton

执行rmiregistry

启动服务

客户端获取stub对象

MyRemote service = (MyRemote) Naming.lookup("rmi://127.0.0.1/Remote Hello");

用户获取stub类

这样要确保每台机器必须有所需的类文件

当然一般都用servlet了,servlet也可以用RMI

Servlet

创建并执行servlet的步骤

  1. 找出可以存放servlet的地方
  2. 取得servlet.jar并添加到classpath上
  3. 通过extend过HTTPServlet来编写servlet的类
  4. 编写HTML来调用servlet <a href="servlets/MyServletA">This is the most amazing sevlet.</a>\
  5. 给服务器设定HTML网页和servlet

要点

  1. servlet完全在HTTP服务器运行的java程序
  2. servlet用来处理与用户交互的网页程序
  3. 需要servlet.jar文件中的servlet相关包才能编译
  4. 必须有支持servlet的WEB服务器才能运行servlet,如apache.org的tomcat
  5. 一般的servlet是继承HttpServlet并覆盖doGet()和doPost()来创建
  6. 服务器会根据用户请求来启动对于的servlet方法
  7. servlet通过doGet()响应参数取得输出串流来组成响应的网页
  8. servlet要输出带有完整标识的HTML网页

JSP

Java Server Pages,实际上Web服务器最终会把JSP转换成servlet。servlet是带有HTML的输出类,而JSP是带有Java程序的网页。这样需要在HTML网页编写时内嵌程序代码。