Java中克隆与拷贝构造函数

参考A Guide to Object Cloning in Java
Which is better option: Cloning or Copy constructors?

Cloning 含义

当我们使用clone()方法时,JVM做如下两件事:
1. 如果这个类只包含原始数据类型成员,则创建一个对象的拷贝,原始数据成员被依次拷贝,返回指向新对象的Reference
2. 如果类还包括引用数据类型则只拷贝了它的引用,没有开辟新的空间。

Java中的Clone

java中的类要支持clone需要实现如下两条
1. 实现Cloneable接口
2. 重写clone()方法
Java docs about clone() method are given below (formatted and extract).

/*
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object  [More ...] clone() throws CloneNotSupportedException;
  1. 第一条保证克隆对象和原对象在内存的不同地方(内存地址不一样)
  2. 第二条建议克隆对象和原始对象有相同的类类型,但不强制
  3. 第三条建议克隆对象和原始对象使用equals比较是形同,但也不强制

例子: string也会被拷贝是因为String类是不可变的,对String类的任何改变,都是返回一个新的String类对象
Our first class is Employee class with 3 attributes. Id, name and department.

public class Employee implements Cloneable{
    
    private int employeeId;
    private String employeeName;
    private Department department;
    
    public Employee(int employeeId, String employeeName, Department department) {
        this.employeeId = employeeId;
        this.employeeName = employeeName;
        this.department = department;
    }
    @Override
    public Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }
    public int getEmployeeId() {
        return employeeId;
    }
    public void setEmployeeId(int employeeId) {
        this.employeeId = employeeId;
    }
    public String getEmployeeName() {
        return employeeName;
    }
    public void setEmployeeName(String employeeName) {
        this.employeeName = employeeName;
    }
    public Department getDepartment() {
        return department;
    }
    
    public void setDepartment(Department department) {
        this.department = department;
    }
}
class Department
{
    private int id;
    private String name;
    
    public Department(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }   
}

测试类

public class TestCloning {
    
    public static void main(String[] args) throws CloneNotSupportedException {
        Department dept = new Department(1,"Human Resource");
        Employee original = new Employee(1,"Admin",dept);
        
        //clone original 
        Employee cloned = (Employee)original.clone();
        System.out.println(cloned.getEmployeeId());
        
        System.out.println(original != cloned);
        
        System.out.println(original.getClass()== cloned.getClass());
        System.out.println(original.equals(cloned));
    }
}

QQ截图20160424181932

把String改为StringBuilder就不能实现拷贝

现在我们修改Department会发现 original数据也会被修改

public class TestCloning {
    
    public static void main(String[] args) throws CloneNotSupportedException {
        Department dept = new Department(1,"Human Resource");
        Employee original = new Employee(1,"Admin",dept);
        
        //clone original 
        Employee cloned = (Employee)original.clone();
        
        cloned.getDepartment().setName("Software Development");
        System.out.println(original.getDepartment().getName());
    }
}

QQ截图20160424182455

Cloned object changes are visible in original also. This way cloned objects can make havoc in system if allowed to do so. Anybody can come and clone your application objects and do whatever he likes.

浅拷贝

是Java中默认实现方式,上面例子实现的就是浅拷贝,因为我们在Employee类的克隆方法中没有克隆 Department.

深拷贝

使在克隆对象上的改变不影响original

修改Employee的克隆方法

@Override
    public Object clone() throws CloneNotSupportedException
    {
        Employee cloned = (Employee)super.clone();
        cloned.setDepartment((Department)cloned.getDepartment().clone());
        return cloned;
    }

同时Department也要实现clone()方法

 @Override
    public Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }

测试:

public class TestCloning {
    
    public static void main(String[] args) throws CloneNotSupportedException {
        Department dept = new Department(1,"Human Resource");
        Employee original = new Employee(1,"Admin",dept);
        
        //clone original 
        Employee cloned = (Employee)original.clone();
        
        cloned.getDepartment().setName("Software Development");
        System.out.println(original.getDepartment().getName());
        
        System.out.println(cloned.getDepartment().getName());
    }
}

QQ截图20160424183921

这时改变cloned的状态 不改变original的状态

实现深拷贝需要满足如下规则
1. No need to separately copy primitives.
2. All the member classes in original class should support cloning and in clone method of original class in context should call super.clone() on all member classes.
3. If any member class does not support cloning then in clone method, one must create a new instance of that member class and copy all its attributes one by one to new member class object. This new member class object will be set in cloned object.

使用拷贝构造函数

public class PointOne {
    
    private Integer x;
    private Integer y;
    public PointOne (PointOne point)
    {
        this.x = point.x;
        this.y = point.y;
    }
}

通过拷贝构造函数可以得到状态一致的对象

详见参考链接一

通过序列化克隆

只要对应的类是可序列化的可以通过序列化机制实现克隆,做法: 直接将对象序列化到输出流中,然后将其读回。这样产生的新对象是对现有对象的一个深拷贝。可用ByteArrayOutputStream将数据保存到字节数组中。
但是 它通常会比显示地构建新对象并复制或克隆数据域的克隆方法慢的多,并且并不是所有对象是可序列化的,最后使类可序列化是很困难的,并不是所有的类都可以指望得到它的权利。

 class Employee extends SerialCloneable
{
    private String name;
    private double salary;
    private Date hireDay;
    
    public Employee(String n,double s,int year,int month,int day)
    {
        name = n;
        salary  = s;
        GregorianCalendar calendar = new GregorianCalendar(year,month-1,day);
        hireDay = calendar.getTime();
    }
    public String getName()
    {
        return name;
    }
    public double getSalary()
    {
        return salary;
    }
    public Date getHireDay()
    {
        return hireDay;
    }
    public void raiseSalay(double byPercent)
    {
        double raise = salary*byPercent/100.0;
        salary += raise;
    }
    public String toString()
    {
        return getClass().getName()+
                "[name="+name
                +",salary="+salary
                +",hireDay="+hireDay
                +"]";
    }
}
 
 class SerialCloneable implements Cloneable,Serializable
{
    @Override
    public Object clone()
    {
        try{
        //save the object to a byte array
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bout);
        out.writeObject(this);
        out.close();
        
        //read a clone of the object from the byte array
        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
        ObjectInputStream in = new ObjectInputStream(bin);
        Object ret = in.readObject();
        in.close();
        return ret;
        }catch(Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }
}
 
 public class SerialCloneTest {
    
    public static void main(String[] args) {
        Employee harry = new Employee("Harry Hacker",35000,1989,10,1);
        
        Employee harry2 = (Employee)harry.clone();
        harry2.raiseSalay(10);
        
        System.out.println(harry);
        System.out.println(harry2);
    }

}
 

2016-04-24_195601

使用Apache工具包(using Apache commons)

Apache commons has also utility function for deep cloning. If you feel interested the follow their official docs. Below is sample usage of cloning facility using Apache commons:

 SomeObject cloned = org.apache.commons.lang.SerializationUtils.clone(someObject);
 

Best practices

  • When you don’t know whether you can call the clone() method of a particular class as you are not sure if it is implemented in that class, you can check with checking if the class is instance of “Cloneable” interface as below.
if(obj1 instanceof Cloneable){
    obj2 = obj1.clone();
} 
//Dont do this. Cloneabe dont have any methods
obj2 = (Cloneable)obj1.clone();
  • No constructor is called on the object being cloned. As a result, it is your responsibility, to make sure all the members have been properly set. Also, if you are keeping track of number of objects in system by counting the invocation of constructors, you got a new additional place to increment the counter.

Prototype Pattern(原型模式)

参考Prototype Design Pattern
Factory模式与Prototype模式的异同

the prototype is the object containing clone method used to clone similar objects to it for reducing the number of classes

a solution to these problems

How does one reduce the number of classes that share similar behavoir and relationships

Solution

Define a class to replace all the classes that share similar behavior and relationships. Save instances,called prototypes,of this class. To create an instance of any of the classes replaced, simply clone the desired prototype and modify its attributes

Liabilities(缺点)

  1. The subclasses of prototype must implement the clone() method. This requires deep-copying if the instances and original must be independent;otherwise, shallow-copying is sufficient
  2. Implementing clone() may be difficult or impossible if the class is final or there are circular references 循环引用 无法克隆

Example Prototype in Java

  1. Create a “contract” with clone() and getName() entries
  2. Design a “registry” that maintains a cache of prototypical objects
  3. Populate the registry with an initializePrototypes() function
  4. The registry has a findAndClone() “virtual constructor” that can transform a String into its correct object (it calls clone() which then calls “new”)
  5. All classes relate themselves to the clone() contract
  6. Client uses the findAndClone() virtual ctor instead of the “new” operator
package Prototype;
interface Prototype {
    Object clone();
    String getName();
}

//1.The clone() contract

interface Command
{
    void execute();
}
class PrototypesModule
{
    //2. "registry" of prototypical objs
    private static Prototype[] prototypes = new Prototype[9];
    private static int total = 0;

    // Adds a feature to the Prototype attribute of the PrototypesModule class
    //obj The feature to be added to the Prototype attribute
    public static void addPrototype(Prototype obj)
    {
        prototypes[total++] = obj;
    }
    public static Object findAndClone(String name)
    {
        //4. The "virtual ctor"
        for(int i=0;i<total;i++)
        {
            if(prototypes[i].getName().equals(name))
                return prototypes[i].clone();
        }
        System.out.println(name + "not found");
        return null;
    }
}
//5. Sign-up for the clone() contract
class This implements Prototype,Command
{
    public Object clone()
    {
        return new This();
    }
    public String getName()
    {
        return "This";
    }
    public void execute()
    {
        System.out.println("This:execute");
    }
}
class That implements Prototype,Command
{
    public Object clone()
    {
        return new That();
    }
    public String getName()
    {
        return "That";
    }
    public void execute()
    {
        System.out.println("That:execute");
    }
}
class TheOther implements Prototype,Command
{
    public Object clone()
    {
        return new TheOther();
    }
    public String getName()
    {
        return "TheOther";
    }
    public void execute()
    {
        System.out.println("TheOther:execute");
    }
}
public class PrototypeDemo {
    //3. Populate the "registry"
    public static void initializePrototypes()
    {
        PrototypesModule.addPrototype(new This());
        PrototypesModule.addPrototype(new That());
        PrototypesModule.addPrototype(new TheOther());
    }

    public static void main(String[] args) {
        initializePrototypes();
        Object[] objects = new Object[9];
        int total = 0;
        // 6. Client does not use "new"
        for(int i=0;i<args.length;i++)
        {
            objects[total] = PrototypesModule.findAndClone(args[i]);
            if(objects[total]!=null) total++;
        }
        for(int i=0;i<total;i++)
        {
            ((Command)objects[i]).execute();
        }       
    }
}

2016-04-24_154950

Singleton设计模式

参考
Efficetive Java page14
深入浅出单实例Singleton设计模式
如何正确地写出单例模式

Singleton 1.0版本

public class Singleton {
    private static Singleton instance =null;
    private Singleton()
    {
        System.out.println("Singleton 实例化");
    }
    public static Singleton getInstance()
    {
        if(instance==null){
            instance = new Singleton();
        }
        return instance;
    }
}

Singleton的特点

  1. 私有(private)的构造函数,表明这个类是不可能形成实例了。这主要是怕这个类会有多个实例。
  2. 即然这个类是不可能形成实例,那么,我们需要一个静态的方式让其形成实例:getInstance()。注意这个方法是在new自己,因为其可以访问私有的构造函数,所以他是可以保证实例被创建出来的。
  3. 在getInstance()中,先做判断是否已形成实例,如果已形成则直接返回,否则创建实例。
  4. 所形成的实例保存在自己类中的私有成员中。
  5. 我们取实例时,只需要使用Singleton.getInstance()就行了。

单线程下

测试代码:

public class Tests {
    
    public static void main(String[] args) {
        for(int i=1;i<=1000;i++)
        {
            //Object相同则hashCode一样
            System.out.println(Singleton.getInstance().hashCode());
        }
    }
}

测试结果
2016-04-19_132745

多线程下

测试代码:

public class Tests {
    
    public static void main(String[] args) {
        TestSingletonThread T[]=new TestSingletonThread[101];
        for(int i=1;i<=100;i++)
        {
            T[i] = new TestSingletonThread();
            T[i].start();
         }
     }
}
    class TestSingletonThread extends Thread
    {
        @Override
        public void run()
        {
            System.out.println(Singleton.getInstance().hashCode());
        }
}

测试结果:
2016-04-19_133750

可以发现在多线程下会实例化多个实例

Singleton 1.1版本

因为在多线程下会实例化多个实例,所以加入synchronized关键字

public class Singleton {
    private static Singleton instance =null;
    private Singleton()
    {
        System.out.println("Singleton 实例化");
    }
    public static  synchronized Singleton getInstance()
    {
        if(instance==null){
            instance = new Singleton();
        }
        return instance;
    }
}

但是此时在多线程下,当已经实例化一份实例后,之后的线程再去获得实例应该是并行的,而加上synchronized关键字后变成并行 影响程序性能
因为自己电脑为4线程所以开4个线程去获取Singleton的实例,来展示性能差异
代码

public class Tests {
    
    public static void main(String[] args) throws InterruptedException {
        TestSingletonThread T[]=new TestSingletonThread[5];
        long start,end;
        start = System.nanoTime();
        for(int i=1;i<=4;i++)
        {
            T[i] = new TestSingletonThread();
            T[i].start();
        }
        for(int i=1;i<=4;i++)
        {
            T[i].join();
        }
        end = System.nanoTime();
        System.out.println("运行时间"+(end-start)/1000.0+"微秒");
    }
}
class TestSingletonThread extends Thread
{
    @Override
    public void run()
    {
        System.out.println(Singleton.getInstance().hashCode());
    }
}

不加synchronized关键字 但会实例化多个

QQ截图20160419214859

QQ截图20160419215011

加synchronized关键字 不会实例化多个 但会损耗性能

QQ截图20160419214736

QQ截图20160419214826

Singleton1.2版本

为了改善性能同时又不实例化多个Singleton实例,可以使用双重检测(Double-Check)

代码

public class Singleton {
    private static Singleton instance =null;
    private Singleton()
    {
        System.out.println("Singleton 实例化");
    }
    public static Singleton getInstance()
    {
        
        if(instance==null){
            synchronized(Singleton.class){
                if(instance == null)
                    instance = new Singleton();
            }       
        }   
        return instance;
    }
}

说明
1. 第一个条件是说,如果实例创建了,那就不需要同步了,直接返回就好了。
2. 不然,我们就开始同步线程。
3. 第二个条件是说,如果被同步的线程中,有一个线程创建了对象,那么别的线程就不用再创建了。
但是,如果你认为这个版本大攻告成,你就错了。

主要在于singleton = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

  1. 给 singleton 分配内存
  2. 调用 Singleton 的构造函数来初始化成员变量,形成实例
  3. 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

对此,我们只需要把singleton声明成 volatile 就可以了。下面是1.3版:

Singleton 1.3版本

代码

public class Singleton {
    private volatile static Singleton instance =null;
    private Singleton()
    {
        System.out.println("Singleton 实例化");
    }
    public static Singleton getInstance()
    {
        
        if(instance==null){
            synchronized(Singleton.class){
                if(instance == null)
                    instance = new Singleton();
            }       
        }   
        return instance;
    }
}

使用 volatile 有两个功用:

1)这个变量不会在多个线程中存在复本,直接从内存读取。

2)这个关键字会禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。

但是,这个事情仅在Java 1.5版后有用,1.5版之前用这个变量也有问题,因为老版本的Java的内存模型是有缺陷的。

Singleton 1.4版本

这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。

 public class Singleton {
    private  static final Singleton instance = new Singleton();
    private Singleton()
    {
        System.out.println("Singleton 实例化");
    }
    public static Singleton getInstance()
    {   
        return instance;
    }
}

但是,这种玩法的最大问题是——当这个类被加载的时候,new Singleton() 这句话就会被执行,就算是getInstance()没有被调用,类也被初始化了。

于是,这个可能会与我们想要的行为不一样,比如,我的类的构造函数中,有一些事可能需要依赖于别的类干的一些事(比如某个配置文件,或是某个被其它类创建的资源),我们希望他能在我第一次getInstance()时才被真正的创建。这样,我们可以控制真正的类创建的时刻,而不是把类的创建委托给了类装载器。

Singleton 1.5版本

静态内部类

public class Singleton {
    
    private static class SingletonHolder
    {
        private  static final Singleton instance = new Singleton();
    }
    private Singleton()
    {
        System.out.println("Singleton 实例化");
    }
    public static final Singleton getInstance()
    {   
        return SingletonHolder.instance;
    }
}

上面这种方式,仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它只有在getInstance()被调用时才会真正创建;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

Singleton 1.6 枚举 Enum

public enum Singleton {
    INSTANCE;
    public  void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }
}

我们可以通过Singleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。

#其它问题(参考链接1)

Class Loader

序例化 (声明所有实例域都是瞬时的(transient)的 并提供一个readResolve方法

Effective Java 77条

多个Java虚拟机

volatile变量

关于代码重用

GRASP通用职责分配软件模式

GRASP设计模式及OO设计原则

GRASP设计模式的全称是General Responsibility Assignment Software Patterns,即通用职责分配软件模式。它定义了9个基本的OO设计原则或基本的设计构件。这9个设计模式分别是:
* 控制器(Controller)
* 创建者(Creator)
* 高内聚(High cohesion)
* 间接性(Indirection)
* 信息专家(Information expert)
* 低耦合(Low coupling)
* 多态(Polymorphism)
* 预防变化(Protected variations)
* 纯虚构(Pure fabrication)

1.The Controller pattern

  1. the controller collaborates with the bussiness objects to handle the actor request
  2. the controller delivers the result to the presentation,which displays the result to the actor
  3. 为了解决如下问题(a solution to these problems)
    A. Decouple the presentation and bussiness objects
    B. Remove the responsibility to handle an actor request from the presentation and assign it to another object
  4. 缺点(liabilities)
    A controller may be assigned too many responsibilities,resulting in a so-called bloated controller

2.Creator

  1. the creator is the object that is responsible for creating an object of a class
  2. a solution to these problems,supposed that an object of class A is the creator of an object of class B
    A. Class A is an aggregation(聚合) of class B
    B. An object of class A contains objects of class B
    C. An object of class A records object of class B
    D. An object of class A closely uses objects of class B
    E. An object of class A has the information to create objects of class B
  3. 缺点(liabilities)
    One same object may have different creation behaviors

3 6 High cohesion Low coupling

4 Indirection

间接性模式关注这样一个问题:为了避免两个或多个事务之间直接耦合,应该如何分配职责?如何使对象解耦合,以支持低耦合并提高复用性潜力?

间接性模式对此的回答是:将职责分配给中介对象,使其作为其他构件或服务之间的媒介,以避免它们之间的直接耦合。中介则实现了其他构件之间的间接性。

间接性模式的思想比较简单,即通过一个中介就能消除许多的耦合。在GoF的23种设计模式中,有许多模式都利用到了间接性的思想。比如桥接模式中,设计将抽象部分与其实现部分相分离,利用的就是在客户与实现之间增加了一个抽象层次。外观模式则是在整个子系统与客户之间增加了一个便于用户使用的外观类作为中介。而中介者模式中的中介者则更是典型的例子。

5 The expert pattern

  1. the expert is the object that is responsible for handling a request should have the information to fulfill the request
  2. a solution to these problems
    A. who should be assigned the responsibility to handle a request
    B. Remove the excessive responsibility from the controller and assigning them to other objects
  3. 缺点(liabilities)
    The expert may become a big object

7 多态(Polymorphism)

8 预防变化(Protected variations)

防止变异模式关注这样一个问题:如何设计对象、子系统和系统,使其内部的变化或不稳定性不会对其他元素产生不良影响?

防止变异模式的回答是:识别预计变化或不稳定之处,分配职责用以在这些变化之外创建稳定的接口。

防止变异(PV)是非常重要和基本的软件设计原则,几乎所有的软件或架构设计技巧都是防止变异的特例。PV是一个根本原则,它促成了大部分编程和设计的模式和机制,用来提供灵活性和防止变化。在软件设计中,除了数据封装、接口、多态、间接性等机制是PV的核心机制之外,没有一种固定的或者是通用的办法能够防止一切变化的产生。因此PV的实现依赖的是一系列的OO设计方面的经验性原则,用以产生一个设计良好的高内聚、低耦合的系统,从而支持PV。

这里需要参考文章面向对象设计原则

9 纯虚构(Pure fabrication)

纯虚构模式关注这样一个问题:当你并不想违背高内聚和低耦合或其他目标,但是基于专家模式所提供的方案又不合适时,哪些对象应该承担这一职责?

OO设计中的领域模型是对领域内的概念内或现实世界中的对象的模型化表示。创建领域模型的关键思想是减小软件人员的思维与软件模式之间的表示差异。因此,在OO设计时,系统内的大多数类都是来源于现实世界中的真实类。然而,在给这些类分配职责时,有可能会遇到一些很难满足低耦合高内聚的设计原则。纯虚构模式对这一问题给出的方案是:给人为制造的类分配一组高内聚的职责,该类并不代表问题领域的概念,而代表虚构出来的事物,用以支持高内聚、低耦合和复用。

纯虚构模式强调的是职责应该置于何处。一般来说,纯虚构模式会通过表示解析或者行为解析来确定出一些纯虚构类,用于放置某一类职责。理想状况下,分配给这种虚构物的职责是要支持高内聚低耦合的,从而使整个系统处于一种良好的设计之中。

例如,在信息专家模式的最后一段所举的例子中提到,许多后台系统都需要对数据库进行操作,将系统中的一些对象进行持久化。信息专家模式给出的建议是将持久化的职责分配给具体的每一个模型类。但是这种建议已经被证明是不符合高内聚低耦合原则的,因为不会被采纳。于是,设计者往往会在系统中加入类似于DAO或者PersistentStorage这样的类。这些类在领域模型中是并不存在的,它们完全由设计者根据系统的行为而虚构得到。然而,这些类的引入,使得操作数据库进行持久化这种高内聚的职责可以顺理成章地分配给它们。从而在整个系统中实现了比较好的内聚和耦合。

在使用纯虚构模式时,不能毫无限制地对系统中的各种行为进行解析并纯虚构。如此往往会导致系统中大量的行为对象的存在,这样会对耦合产生不良的影响。

JSP使用

Jsp网页的组成

模板数据

静态部分,web容器不作处理,如HTML标签。

元素

  • 脚本元素
  • 指令元素
  • 动作元素

Jsp 脚本元素类型

脚本元素 脚本语法
声明 <%! 声明%>
脚本 <% 脚本%>
脚本表达式 <%= 脚本表达式%>

说明:各个脚本类型的普通格式都有其对应的XML兼容格式,其区别在于:对普通格式页面的访问,会以HTML文件的形式显示,而访问对应的XML兼容格式时,会以XML文件的形式显示。

<%!声明%>

声明语句用于声明方法和变量,对应的XML兼容格式为

 <jsp:declaration></jsp:declaration> 
                `<html>
                    <head><title>HelloWorld</title></head>
                    <body>
                        <%! public long fact(long x){//声明
                                if(x == 0) return 1;
                                else return x * fact(x - 1);
                            }       
                        %>
                        <table border="1">
                            <tr><th>x</th><th>x!</th></tr>
                            <% for(long i = 0; i < 20; i++){  %><!--脚本-->
                            <tr>
                                <td><%=i %></td>
                                <td><%=fact(i) %></td>
                            </tr>
                            <%} %>
                        </table>
                    </body>
                </html>`

// 在声明中定义的变量是翻译成相应Java程序的成员变量, 在脚本中定义的变量是相应Java程序的局部变量。





<%脚本%>


  1. 普通格式
        在<%%>标签里面写入Java代码,访问时以HTML文件的形式呈现。
        '<html>
         <head><title>HelloWorld</title></head>
        <body>
             <%
             String str = "HelloWorld";
             out.println(str);
            %>
        </body>
        </html>'
        说明:脚本翻译成对应java程序的代码放在该java程序的_jspService()方法中,因此,在脚本中声明的变量是局部变量。    
    
  2. XML兼容格式
     在<jsp:scriptlet></jsp:scriptlet>标签间写入Java代码,访问时以XML文件的形式呈现。
             示例:
             '<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns="http://www.w3.org/1999/xhtml" version="2.0">  
                <html>  
                    <head><title>Simple jspx page </title> </head>  
                    <body>  
                    <jsp:scriptlet>  
                        String str = "hello" ;  
                        out.println(str) ;  
                    </jsp:scriptlet>  
                    </body>  
                </html>  
                </jsp:root>'
    



<%=脚本表达式%>


  脚本表达式用来显示动态生成的结果,对应的XML兼容格式为<jsp:expression></jsp:expression>。不能使用分号作为脚本表达式的结束符,脚本表达式可以是常量,也可以是动态计算出来的值。

        示例:
        '<html>
        <head><title>HelloWorld</title></head>
        <body>
            <%
                String str = "Hello World";
            %>
            <%=str %> 
        </body>
    </html>'
    说明:脚本表达式翻译成对应java程序的代码放在该java程序的_jspService()方法中的print()方法中。



JSP页面的注释


<%--注释内容--%>//jsp注释,注释内容不会翻译为java代码,浏览器和源码中无法看到。
//HTML注释,会翻译为对应的java代码,在浏览器中注释的内容不会显示,查看源代码看得到注释的内容。
<%//%>、<%/**/%>//java注释,会翻译为java代码,但是不作为响应的内容,浏览器和源码中无法看到。

例子

    '<html>
        <head><title>HelloWorld</title></head>
        <body>
            <% String str = "aaa"; %>
            <%--<%=str %> --%> //jsp注释
            <!--<%=str %> -->
    //HTML注释,无法在浏览器中看到,但可在网页源文件中看到<!--aaa--> 
            <% //String str2 = "bbb"; %> //java注释
        </body>
    </html>'



JSP指令元素


    JSP指令元素<%@指令类型 属性名=”属性值”%>用于提供整个JSP页面的相关信息,用来设定JSP页面的相关属性。

Page指令

page指令的功能是设定整个JSP页面的属性和相关功能,用于翻译阶段与web容器的通讯。

语法:<%@page属性名=”属性值”%>
对应的xml兼容格式语法为:<jsp:directive.page属性名=”属性值”/>

Page指令的属性

属性名 描述 默认值
language 脚本语言名称 “java”
info 网页信息
contentType MIME类型及字符编码 “text/html;charset=ISO-8859-1”
import 类和包 none
buffer 缓冲区大小 8192k
autoFlush 自动刷新 true
session 能否获取session对象 true
isThreadSafe 是否线程安全 true
errorPage 指定错误页面 none
isErrorPage 允许为错误页面 false
extends 指定父类

例子

    '<%@page contentType="text/html; charset=utf-8" info="aa" %>
    <!--翻译成的java代码为response.setContentType(“text/html;charset=utf-8”)-->
    <%@page import="java.util.Date"%>
    <!--翻译成的java代码为import java.util.Date-->
    <%@page import="java.text.SimpleDateFormat"%>
    <html>
        <head><title>pageTest</title></head>
        <body>
            <% 
                Date date = new Date(); 
                SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
             %>
            <h1>当前系统时间:<%=sd.format(date) %></h1>   
        <br></body>
    </html>'

include指令(静态包含):

include指令用于在JSP编译阶段插入一个包含文本或代码的文件,将文件中的文本静态地包含进当前JSP文件中,如:<%@include file=”hello.jsp”%>。静态包含的文本最好不要有HTML标签和body标签,因为被包含的文件的全部内容将被插入到JSP文件中include指令所在的地方,这些标签会同JSP文件中已有的同样的标签发生冲突,静态包含的文件名不能是变量名且不能传递参数。

taglib指令

taglib指令能让用户在页面中使用自定义的标签。
语法:<%@taglib uri=”uri” prefix=”tagPrefix”%>
对应的xml兼容格式语法为:<jsp:directive.taglib uri=”uri” prefix=”tagPrefix”/>

JSP动作元素

JSP动作利用XML语法格式的标记来控制Servlet引擎的行为。利用JSP动作可以动态地插入文件、重用JavaBean组件、重定向用户到另外的页面、为java插件生成HTML代码、实现页面与组件的通信。

<jsp:include/>动作(动态包含):

语法: <jsp:include page="localUrl" flush="true"/>,flush为true表示缓冲区满时,自动刷新页面。

<jsp:forward/>动作(页面转发):

语法:<jsp:forward page=”url”/>,相当于RequestDispatcher的forward方法, 将请求转发出去。

动态包含和静态包含的区别

☆ 根本区别在于包含的时机,静态包含是在编译期间包含,而动态包含是在运行期间包含。
☆ 静态包含的是代码,动态包含的只是响应的内容。
☆ 静态包含适用于包含静态页面,动态包含适用于包含动态页面。
☆ 静态包含不能传递参数,动态包含可以传递参数。

①    、编写includeTest1.jsp如下:
<body>
    <%
    int i=100;
    String str="includeTest1.jsp";
     %>
    <%=str %>中i值是:<%=i %><br>
    <jsp:include page="includeTest2.jsp"></jsp:include>
</body>
再编写includeTest2.jsp如下:
<body>
    <%
    int i=600;
    String str="includeTest2.jsp";
     %>
    <%=str %>中i值是:<%=i %>
</body>
启动tomcat,在浏览器中输入http://localhost:8080/myapp/includeTest1.jsp查看结果如下:
includeTest1.jsp中i值是:100
includeTest2.jsp中i值是:600
②   、将includeTest1.jsp改为如下:
<body>
    <%
    int i=100;
    String str="includeTest1.jsp";
     %>
    <%=str %>中i值是:<%=i %><br>
 <%@ include  file="includeTest2.jsp"%>
</body>
将includeTest2.jsp改为如下:
<body>
    <%=str%>中i值是:<%=i %>
</body>
在浏览器中输入:http://localhost:8080/myapp/includeTest1.jsp查看结果如下:
includeTest1.jsp中i值是:100
includeTest1.jsp中i值是:100
③   、从上例可知,动态包含时,包含和被包含的文件互不影响,只是将被包含文件编译执行后的结果放入了包含的文件中;静态包含相当于将被包含的文件中的文本直接放入了包含的文件中,然后编译执行。

JSP隐式对象

隐式对象就是不用实例化,可以直接在JSP页面中使用的对象,如下表所示:

对象 描述
request 代表与请求相关的HttpServletRequest对象
response 代表与响应相关的HttpServletResponse对象
PageContext 代表封装请求某个JSP页面时请求环境的PageContext对象
session 代表特定用户请求会话的HttpSession对象
application 代表web应用程序的ServletContext对象
out 代表与响应输出流相关的jspWriter对象
config 代表JSP页面的Servlet相关的ServletConfig对象
page 等于java中的this变量
exception JSP页面抛出的Throwable对象,这个对象只能在JSP错误页面中使用

out对象

out对象是一个输出缓冲流,用来给客户端返回信息,它是javax.servlet.jsp.JspWriter的一个实例。
out对象常用方法:
☆println():向客户端输出各种类型的数据
☆newLine():输出一个换行符
☆close():关闭输出流
☆flush():输出缓冲区里的数据
☆clearBuffer():清除缓冲区里的数据,同时把数据输出到客户端
☆clear():清除缓冲区里的数据,但不把数据输出到客户端
☆getBufferSize():返回缓冲区的大小

pageContext对象:

pageContext对象可以获取其他隐式对象、可以实现页面的转发和包含、可以用它对四个作用域空间进行数据的存取,它是javax.servlet.jsp.PageContext的实例。
① pageContext对象获取其他隐式对象:
☆getRequest :获得request隐式对象
☆getResponse:获得response隐式对象
☆getServletConfig:获得config隐式对象
☆getServletContext:获得application隐式对象
☆getPage:获得page隐式对象
☆getSession:获得session隐式对象
②pageContext对象对四个作用域空间进行数据的存取:
pageContext对象提供了四个常量,用来表示四个作用域:
☆PAGE_SCOPE:pageContext作用域,只在当前页面有效
☆REQUEST_SCOPE: request作用域,在forward转发的页面有效
☆SESSION_SCOPE:session作用域,IE浏览器未关闭就一直有效
☆APPLICATION_SCOPE:application作用域,未重启Tomcat就一直有效。
③pageContext对象实现页面的转发和包含:
pageContext.forward(String relativeURL);//实现页面的转发
pageContext.include(String relativeURL);//实现页面的包含

例子


例1,获取web.xml配置文件中jsp的初始化参数:
先在tomcat安装目录下的webapps目录中新建/myapp/WEB-INF/路径,并在WEB-INF目录中编写web.xml如下:

<web-app version="2.5" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <servlet>
    <servlet-name>param</servlet-name>
    <jsp-file>/get.jsp</jsp-file><!--指定jsp文件,路径相对于当前web项目文件夹-->
    <init-param><!--JSP的初始化参数-->
        <param-name>name</param-name>
        <param-value>zhangsan</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>param</servlet-name>
    <url-pattern>/get.jsp</url-pattern>

  </servlet-mapping>
</web-app>
然后在myapp目录中编写get.jsp如下:
<%@ page language="java" import="java.util.*" pageEncoding="gbk"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<html>
  <head>
    <base href="<%=basePath%>">
    <!--base标签设置当前页面的根路径,即当前页面的其他路径可以相对base标签设置的路径而设置-->
    <title>My JSP 'forward.jsp' starting page</title>
<%!
  public void jspInit() {//访问该jsp时执行该方法
    System.out.println("初始化");
}
public void jspDestroy() {//该jsp对应的Java程序(servlet)销毁时执行
      System.out.println("销毁");
}
%>
  </head>
<body>
<%  
    String param = config.getInitParameter("name");
//获取web.xml中jsp的初始化参数
    out.println("param-value  in web.xml is :"+param);
%><br/>
        basePath is  <%=basePath%>
  </body>
</html>
最后启动tomcat,在浏览器中输入:http://localhost:8080/myapp/get.jsp访问。

例2,在request作用域中传递绑定的对象:
先在tomcat安装目录下的webapps目录中新建myapp目录,然后在myapp目录中编写sendEmp.jsp如下:
<html>
<body>
<%      
request.setAttribute("emp","scott");
%> 
        <jsp:forward page="getEmp.jsp"></jsp:forward><!--请求转发-->
</body>
</html>
继续在myapp目录中编写getEmp.jsp如下:
<%
String emp = (String)pageContext.getAttribute("emp",PageContext.REQUEST_SCOPE);
String emp2 = (String)request.getAttribute("emp");//这句和上句意思一样
out.println(emp+" "+emp2);
%>
启动tomcat,在浏览器中输入:http://localhost:8080/myapp/sendEmp.jsp访问。

##另外
QQ截图20160415131256

QQ截图20160415131316

QQ截图20160415131546

QQ截图20160415131616

QQ截图20160415131622

QQ截图20160415131628

QQ截图20160415131641

QQ截图20160415131648

QQ截图20160415131655

面向对象设计原则

一些软件设计的原则
面向对象设计原则和创建SOLID应用的5个方法

迪米特法则

定义:

一个对象应该对其它对象保持最少的了解 从而降低类之间的耦合

具体来说对于对象 ‘O’ 中一个方法’M’,M应该只能够访问以下对象中的方法:

  1. 对象O
  2. 与O直接相关的Component Object
  3. 由方法M创建或者实例化的对象
  4. 作为方法M的参数的对象
    例子:demeter

SOLID 原则

单一职责 (SRP)

一个类发生变化的原因不应该超过一个。这意味着代码中每个类,或者类似的结构只有一个功能。
QQ截图20160413142903

里氏替换原则 (LSP)

子类必须能够替换成它们的基类。即:子类应该可以替换任何基类能够出现的地方,并且经过替换以后,代码还能正常工作。另外,不应该在代码中出现if/else之类对子类类型进行判断的条件。里氏替换原则LSP是使代码符合开闭原则的一个重要保证。正是由于子类型的可替换性才使得父类型的模块在无需修改的情况下就可以扩展。
A. Derived types must be completely substitutable for their base types
B. This principle is just an extension of the Open Close Principle
C. Making sure that new derived classes are extending the base classes without
changing their behavior

QQ截图20160413145728

正方形不是矩形的例子

接口隔离原则 (ISP)

接口隔离原则(Interface Segregation Principle)指出客户不应该被强迫依赖于他们不使用的接口。当我们使用非内聚的接口时,ISP指导我们创建多个较小的内聚度高的接口。

当你应用ISP时,类和他们的依赖使用紧密集中的接口通信,最大限度地减少了对未使用成员的依赖,并相应地降低耦合度。小接口更容易实现,提升了灵活性和重用的可能性。由于很少的类共享这些接口,为响应接口的变化而需要变化的类数量降低,增加了鲁棒性。

A. Clients should not be forced to depend upon interfaces that they don’t use

QQ截图20160413153256

QQ截图20160413153318

依赖反转原则 (DIP)

依赖反转原则(Dependency Inversion Principle,DIP)指出高层次模块不应该依赖于低层次模块;他们应该依赖于抽象。第二,抽象不应该依赖于细节;细节依赖于抽象。方法是将类孤立在依赖于抽象形成的边界后面。如果在那些抽象后面所有的细节发生变化,那我们的类仍然安全。这有助于保持低耦合,使设计更容易改变。
A. High-level modules should not depend on low-level modules. Both should
depend on abstractions
B. Abstractions should not depend on details. Details should depend on
abstractions

QQ截图20160413154556

QQ截图20160413154612

QQ截图20160413155306

QQ截图20160413155326

开闭原则(OCP)

关于开发封闭原则,其核心的思想是:模块是可扩展的,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。

对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。

A. Software entities like classes, modules and functions should be open for
extensionbut closedformodifications
B. The design and writing of the code should be done in a way that new
nctionality should be added with minimum changes in the existing code
C. The design should be done in a way to allow the adding of new functionality as
new classes, keeping as much as possible existing code unchanged

QQ截图20160413160021

QQ截图20160413160030

其它原则

参考第一个链接一些软件设计的原则
* Common Closure Principle(CCP)– 共同封闭原则
* Common Reuse Principle (CRP) – 共同重用原则
* Hollywood Principle – 好莱坞原则
* High Cohesion & Low/Loose coupling & – 高内聚, 低耦合
* Convention over Configuration(CoC)– 惯例优于配置原则
* Separation of Concerns (SoC) – 关注点分离
* Design by Contract (DbC) – 契约式设计
* Acyclic Dependencies Principle (ADP) – 无环依赖原则
* Don’t Repeat Yourself (DRY)
* Keep It Simple, Stupid (KISS)
* Program to an interface, not an implementation
* Command-Query Separation (CQS) – 命令-查询分离原则
* You Ain’t Gonna Need It (YAGNI)