Posted in Java, 技术

有趣的译文 《Java Pattern 那些小事》

摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!

一、现代的玩具 – Java 可以做很多事

一个 Q 与 A 的对话,展示了 Java 那些小事。

Q : 5 是整数吗?

A : 是。


Q : -23 是数吗?

A : 是,但负数使用场景很少。


Q : 5.32 是整数吗?

A : 不是,我们不用这种数值类型表示 5.32 。


Q : 5 属于什么数值类型?

A : int (整型)。
批注: int 是基本类型,int 代表整数。


Q : 快,想出另一个整数!

A : 19?


Q : 那 true 属于什么数值类型?

A : boolean (布尔型)。


Q : false 属于什么数值类型?

A : boolean。


Q : 想得出其他布尔值吗?

A : 没有,布尔值只有 true 和 false。


Q : int 是什么?

A : 一种类型。


Q : boolean 是什么?

A : 另一种类型。


Q : 什么是类型?

A : 类型是一个相关值集合的名称。


Q : 能举例解释下类型吗?

A : 使用类型就像使用一种集合一样。比如使用 boolean 可以表示逻辑值:是与非


Q : 能创建新的类型吗?

A : 我们还不知道怎么创建。


Q : 画出下面类的基本关系。

abstract class Seasoning {}

class Salt extends Seasoning {}

class Pepper extends Seasoning {}

A : 这样画?
图 1.1 Seasoning 类关系图

wechatimg4


Q : 是的。Seasoning 是数据类型,Salt 和 Pepper 是 Seasoning 的具体类型。

A : 好的。三个都是新类型吗?


Q : 是的。那么 new Salt() 是一个 Seasoning 吗?

A : 是的。 new Salt() new 关键字表示创建了 Salt 的一个实例,每个 Salt 的实例也是 Seasoning。


Q : new Pepper() 也是吗?

A : 是的。它也是一个 Seasoning。 new Pepper() new 关键字表示创建了 Pepper 的一个实例,每个 Pepper 的实例也是 Seasoning。


Q : 什么是 abstract , class 和 extends 关键字?

A : 简单地说:
abstract class 表示抽象数据类型
class 表示具体类型
extends 连接了具体类型到数据类型


Q : 还有其他的 Seasoning 具体类型吗?

A : 没有,因为只有 Salt 和 Pepper 继承 Seasoning。


Q : 正确。只有 Salt 和 Pepper 是 Seasoning 的具体类型。我们见过类似 Seasoning 数据类型吗?

A : 没有。但是 boolean 类型有两个值:true 和 false。
批注:值与具体类型是不同的。


Q : 再定义一个 Seasoning 具体类型:

class Thyme extends Seasoning {}

A : 再来一个:

class Sage extends Seasoning {}

Q : 4 个 Seasoning 具体类型了。

A : 是的。


Q : 什么是笛卡尔点?

A : 是一对数字。


Q : 曼哈顿地点位于哪里?

A : 两个城市街道的十字路口。


Q : 看代码,CartesianPt 类和 ManhattanPt 类有啥不同于 Salt 类和 Pepper 类

abstract class Point {}
class CartesianPt extends Point {
    int x;
    int y;
    CartesianPt(int _x, int _y){
        x = _x;
        y = _y;
    }
}
class ManhattanPt extends Point {
    int x;
    int y;
    ManhattanPt(int _x, int _y){
        x = _x;
        y = _y;
    }

}

A : 两个类中 {} 包含了三个元素。x 和 y 是表示点的坐标,但是构造函数里面包含什么呢?(批注:构造函数是同类名的函数)


Q : CartesianPt 和 ManhattanPt 各自的构造函数里面包含各自的字段值。

A : 那怎么使用构造函数呢?


Q : new 关键字作用于构造函数,会创建一个新的该类型实例。

A : 这样子呀。


Q : 比如下面的代码:

new CartesianPt(2,3);

使用 CartesianPt 的构造函数,创建一个 CartesianPt 实例。

A : 这样创建的 CartesianPt 实例 ,其字段 x 值为 2,y 值为 3。

class CartesianPt extends Point {

还有 extends 关键字表示创建的 CartesianPt 也是 Point。


Q : 对。那么下面代码是创建一个 ManhattanPt 实例吗?

new ManhattanPt(2,3);

A : 是,它也有 x 值为 2,y 值为 3。


Q : 构造函数就这样?

A : 基本是了,但在没有定义过构造函数的代码里,以前用过构造函数。这是怎么实现的呢?


Q : 比如 Salt 和 Pepper 没有任何字段,但是它们有个默认构造函数。

A : 这是正确使用默认构造函数的方式吗?


Q : 是的。默认构造函数没有包含字段值。使用 new 关键字创建实例时,创建的实例没有字段值。

A : 好,下面这段代码呢?

new Point()

Q : Point 是一个抽象类,它是不完整的类,所以 new 关键字不能创建 Point 实例。

A : 这就说得通了,继续。


Q : 以下定义的类是另一种抽象数据类型和它的具体数据类型吗?

abstract class Num {}
class Zero extends Num {}
class OneMoreThan extends Num {
    Num predecessor;
    OneMoreThan (Num p) {
        predecessor = p;
    }
}

A : 是的。它们定义了一种抽象数据类型和两个具体数据类型。
图 1.2 Num 类关系图

wechatimg5


Q : new Zero() 是 Num 的实例吗?

A : 明显是的。就像 new Salt() 是 Salt 的一个实例。


Q : new OneMoreThan(new Zero()) 是 Num 的实例吗?

A : 是。OneMoreThan 继承了 Num,new OneMoreThan() 是一个 OneMoreThan 实例,本身也是一个 Num 实例,并包含了一个 Num 实例(这里是指new Zero())。


Q : 为什么 OneMoreThan 可以这样实现?

A : new Zero() 是一个 Num 实例,OneMoreThan 的实例创建时,包含了 Zero 的实例。


Q : 包含 Zero 实例?

A : OneMoreThan 的实例拥有一个值叫做 predecessor,即这个值可以是 new Zero()。


Q : predecessor 只能是 Zero 的实例?

A : 不是的。Num predecessor 说明了 predecessor 是一个 Num,所以具体表现为 OneMoreThan 或者 Zero 的实例。


Q : 那 new OneMoreThan(new OneMoreThan(new Zero()))呢?

A : 一个 Num 的实例,最外层的 new OneMoreThan 拥有了一个 Num 的实例:new OneMoreThan(new Zero())


Q : 那 new OneMoreThan(0)?

A : 胡说,0 不是一个 Num 。


Q : new Zero() 和 0 一样吗?

A : 不一样,虽然 new Zero() 和 0 概念相似,但不一样。


Q : new OneMoreThan(new Zero()) 和 1 概念相似?

A : 是的,但是他们是不一样的。


Q : new OneMoreThan( new OneMoreThan( new OneMoreThan(new Zero())))和哪个数概念相似?

A : 3


Q : Num 比 boolean 多吗?

A : 多很多。


Q : 比 int 多吗?

A : 不。


Q : 说下 new Zero() 和 0 的区别呢?

A : 我们得区别它们。new Zero() 是 Zero 的一个实例,意味着也是 Num 。0 是一个 int。


Q : 正确。不同数据类型的实例就是不同的。

A : 类型是一系列实例的统称。


Q : 基本类型(int 和 boolean)是不同的。

A : 那些不是基本类型呢?


Q : 类定义不引入基本类型,比如上面提到的 new Zero() 不单单是 Zero 的实例,也是 Num。任何继承 Num 的数据类型都是。

A : 然后呢?


Q : 就像每个数据类型继承 Object。

A : 就是说,所有类都是一个对象。


Q : 正确,下面有所体现。

A : 好的。


建议一:
自定义数据类型时,使用抽象类
自定义具体数据类型时,使用 extends 关键词继承抽象类


Q : 看看下面的定义?

abstract class Layer {}
class Base extends Layer {
    Object o;
    Base(Object o) {
        this.o = o;
    }
}
class Slice extends Layer {
    Layer l;
    Slice(Layer l) {
        this.l = l;
    }
}

A : 定义了 Layer 抽象数据类型和其两个具体数据类型 Base 和 Slice。其中 Base 包含一个值对象 Object。


Q : new Base(new Zero()) 是什么?

A : 一个 Base 的实例,即是 Layer 又是 Object 的实例。


Q : new Base(new Salt()) 是什么?

A : 一个 Base 的实例。但 new Base(new Salt())new Base(new Zero()) 是同一个数据类型吗?


Q : 是的。因为对象实例由 new 关键字创建,而它们都是 Base。

A : 因为 Base 包含的值对象是 Object。所以可以是 new Salt()new Zero()


Q : 任何都是个 Object 吗?

A : new 关键字创建的都是对象。自然 String 、Arrays 也是对象,这里我们不展开讲。


Q : 是的。下面的是 Layer 实例吗?

new Base(5)

A : 5 没有通过 new 创建,所以这个错误的语法。


Q : 下面的是 Layer 实例吗?

new Base(false)

A : false 没有通过 new 创建,所以这个错误的语法。


Q : 下面的是 Layer 实例吗?

 new Base(new Integer(5))

A : 是的。 new Integer(5) 创建了一个 int 对象。


Q : 那么怎么创建一个 Layer 实例拥有 false?

A : 简单:

 new Base(new Boolean(false))

Q : int Integer / boolean Boolean 混乱了?

A : 以后会涉及到。


Q : 期待更多吧。

A : 迫不及待。

Posted in Java, 技术

初探设计:Java接口和抽象类何时用?怎么用?

今天犯了个错:

接口变动,伤筋动骨,除非你确定只有你一个人在用”。哪怕只是throw了一个新的Exception。哈哈,这是我犯的错误。

 

一、接口和抽象类

类,即一个对象

抽象类,就是抽象出类的基础部分,即抽象基类(抽象类)。官方定义让人费解,但是记忆方法是也不错的 — 包含抽象方法的类叫做抽象类。

接口就是把抽象的深度更深,它就像用简短的非逻辑的一些规则表示类之间的关系。可以比作协议,比如通信使用的UDP/TCP协议等。

小结类与接口是Java语言的基本抽象单元。

 

二、为什么有接口的两大原因

a. 向上转型为多个基类型

如此,会给开发带来相当大的灵活性。比如女神刘亦菲(Class),实现了 明星 和 女人 的接口。这样在复杂的继承结构的某类中使用它,以后在调用seeStar(Star star)或者seeWomen(Women women)方法时,只要传入其实现类(刘亦菲)即可。这也就是常说的接口可以多实现,达到了完全解耦

b. 可复用性

即根据接口定义,让创建类有了遵循的”协议“(规则)。whatever~ 要做的仅仅建立一个接口,为了保证生成对象的非耦合。如此而来,接口的使用让代码更具可复用性通用性灵活性。但并不是那么万能。后面使用守则会讲到。

 

三、怎么用?

前人大牛总结了一些设计模式,也就是接口衍生出的一些设计模式。设计模式就是语法糖的甜蜜吧。接口让我们尝到了甜蜜。和我身边的一杯starBucks的热巧克力一样。有点太甜。比如:

a.策略模式方法中参数使用接口,传入的参数对象(实现类)即包含了执行的代码。如图:

image
调用过程如下,在方法中出入实现而已:

image

 

b. 适配器模式接口适配器(Interface Adapter)类,可以将不同源配到同一个目标。即暴露目标接口和实现源有共同的方法,适配器类怎么适配呢?实现目标接口,并关联了实现源对象,在实现方法中调用关联实现源真正对象,然后在里面进行各种适配操作。比如再关联一个源什么的。如图:

image

这其实有点AOP的味道。比如Spring AOP框架对BeforeAdvice、AfterAdvice、ThrowsAdvice三种通知类型的支持实际上是借助适配器模式来实现的。

 

c. 工厂模式 工厂对象将生成接口某个实现的对象。从而代码将实现和接口的实现分离,比较透明地将某个实现透明地替换成另一个实现。但是这里工厂调用方法是静态的,也就是简单工厂模式(静态工厂模式)。动态工厂模式无非是使用了反射达到了动态调用。

 

四、接口与抽象类的使用守则

第一、尽可能使每一个类或成员不被外界访问

这里的外界有个度,比如包级或者公有的。这样子可以更好地模块化,模块与模块之间通过暴露的api调动。这样如果有个模块改动接口或者类。只要担心该模块,而不会涉及其他模块。

第二、适当的使用类(抽象类)继承,更多的使用复合

继承,实现了代码重用。内部中使用继承非常安全,但是要记住什么时候使用继承。即当子类真正是超类的子类型时,才适用继承。否则尽可能使用复合,即在一个类中引用另一个类的实例。也就是说将另一个类包装了一下,这也就是装饰模式所体现的。

第三、优先考虑使用接口,相比抽象类

首先Java只许单继承,这导致抽象类定义收到极大的限制。二者,接口无法实现方法。但是Java 8提供了函数式接口

但是接口在设计的时候注意,设计公有接口必须谨慎。接口如果被公开发行,则肯定会被广泛实现,那样改接口几乎不可能,会是巨大的工程。(这和我犯的错误一样。)

第四、占时没有第四了…

 

小结:

明白了 Java接口和抽象类何时用?怎么用?待续,有新的点补充吧

欢迎点击我的博客及GitHub — 博客提供RSS订阅哦!

———- http://www.bysocket.com/ ————- https://github.com/JeffLi1993 ———-

微         博:BYSocket  豆         瓣:BYSocket  FaceBook:BYSocket  Twitter    :BYSocket

 

c260f7abjw1eynwbrcghnj20qo0k00w0

Posted in Java, 技术

初探设计:Java继承何时用?怎么用?

Writer      :BYSocket(泥沙砖瓦浆木匠)

一、回顾继承

常见的如下:

1、依赖(”uses-a“)

2、聚合(”has-a“)

3、继承(”is-a“)类之间关系

也就是UML类图中常见的三种关系,另外常见的还有实现(接口与实现类的关系),组合等。

 

继承,即“is-a”关系,是一种表示特殊与一般的关系。比如,女人(特殊)是一个人(一般)。关键字extends表明正在构造的新类派生于一个存在的类。

1、已经存在的类称为 超类父类或者基类

2、新类被称为 子类或者派生类

 

有时候看着人家源码的设计。比如常见的接口,紧接着抽象类实现接口,然后继承该抽象类的各种实现:

image

一般都是这样的,行为总则都写着顶层接口抽象类实现了下面各个实现类公用的方法和字段实现类各自实现功能

但里面这些究竟怎么用呢?比如继承在什么前提下使用,什么场景前提下,下面就是思考后的小结:(Think , Write & Do)

 

二、继承何时用?怎么用

也就是说,继承设计使用的时候,有哪些技巧,有哪些需要注意的地方。

1、公共的方法和字段才放在基类(也就是父类)

这句话可能有争议,太过于吹毛求兹或者是严格。拿女人和人的问题来说,比如名字字段、age都可以放在基类人上面,但女人的那些第二特征就是独有了。

但有些时候的例子总是很疑惑:比如Java工程师实习生和Java工程师,看样子可以“实习生”extends “Java工程师”,然后很多Java工程师上的字段都是不属于Java实习生的。顾两者并没有上面太大关系,可能都是从属于一个父类—工程师。下面类关系图才是正确的:

image

子类对父类的继承是包括了父类的公有和受保护的方法和字段。但子类只需要继承父类的一部分,就没辙了。这时候记住一句话:“多用组合,少用继承”。

 

2、protect并不能保护父类

其实protect机制在父类并不能起到好的保护。子类可以在需要的的时候访问父类。但是继承无限制,即子类的子类… 无止境的。如果想侵入父类protect方法,只需要写个类,继承任意子类就可访问。二者,同一个包下能访问。

 

从上面也可以总结出:

3、在继承父类的方法与字段都有意义的时候,选择继承。否则,不要使用继承。

 

4、在覆盖父类中的行为(方法)时,不要偏离最初的设计内涵。

父类的方法实现或者定义都是指定了一种行为的内涵。所以继承父类的时候,有个重写override)方法可以改变子类的行为。但请不要改变其定义的内涵。源码中常见的有:比如 IO 中的 read write方法和Servlet中 的 get post。

 

5、继承与组合、多态

继承,子类与父类在编译期就能确定其对象。而组合或者是多态,在运行期就才能确定其对象,相比之下,组合多态达到了更多的灵活性。但,运行期未知的错误是要注意处理的。

顾,“多用组合,少用继承”。

 

三、本文小结

继承的一点一滴。泥瓦匠,这软文小结,难免有错误。欢迎指正讨论。

欢迎点击我的博客及GitHub — 博客提供RSS订阅哦!

———- http://www.bysocket.com/ ————- https://github.com/JeffLi1993 ———-

微         博:BYSocket  豆         瓣:BYSocket  FaceBook:BYSocket  Twitter    :BYSocket

7066956_21002167761444703497_thumb

Posted in Java, 技术

深入浅出: Java回调机制(异步)

Writer      :BYSocket(泥沙砖瓦浆木匠)

什么是回调?今天傻傻地截了张图问了下,然后被陈大牛回答道“就一个回调…”。此时千万个草泥马飞奔而过(逃

哈哈,看着源码,享受着这种回调在代码上的作用,真是美哉。不妨总结总结。

一、什么是回调

回调,回调。要先有调用,才有调用者和被调用者之间的回调。所以在百度百科中是这样的:

软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用

回调是一种特殊的调用,至于三种方式也有点不同。

1、同步回调,即阻塞,单向

2、回调,即双向(类似自行车的两个齿轮)。

3、异步调用,即通过异步消息进行通知。

 

二、CS中的异步回调(java案例)

比如这里模拟个场景:客户端发送msg给服务端,服务端处理后(5秒),回调给客户端,告知处理成功。代码如下:

回调接口类:

/**
 * @author Jeff Lee
 * @since 2015-10-21 21:34:21
 * 回调模式-回调接口类
 */
public interface CSCallBack {
    public void process(String status);
}

模拟客户端:

/**
 * @author Jeff Lee
 * @since 2015-10-21 21:25:14
 * 回调模式-模拟客户端类
 */
public class Client implements CSCallBack {

    private Server server;

    public Client(Server server) {
        this.server = server;
    }

    public void sendMsg(final String msg){
        System.out.println("客户端:发送的消息为:" + msg);
        new Thread(new Runnable() {
            @Override
            public void run() {
                server.getClientMsg(Client.this,msg);
            }
        }).start();
        System.out.println("客户端:异步发送成功");
    }

    @Override
    public void process(String status) {
        System.out.println("客户端:服务端回调状态为:" + status);
    }
}

模拟服务端:

/**
 * @author Jeff Lee
 * @since 2015-10-21 21:24:15
 * 回调模式-模拟服务端类
 */
public class Server {

    public void getClientMsg(CSCallBack csCallBack , String msg) {
        System.out.println("服务端:服务端接收到客户端发送的消息为:" + msg);

        // 模拟服务端需要对数据处理
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("服务端:数据处理成功,返回成功状态 200");
        String status = "200";
        csCallBack.process(status);
    }
}

测试类:

/**
 * @author Jeff Lee
 * @since 2015-10-21 21:24:15
 * 回调模式-测试类
 */
public class CallBackTest {
    public static void main(String[] args) {
        Server server = new Server();
        Client client = new Client(server);

        client.sendMsg("Server,Hello~");
    }
}

 

运行下测试类 — 打印结果如下:

客户端:发送的消息为:Server,Hello~
客户端:异步发送成功
服务端:服务端接收到客户端发送的消息为:Server,Hello~

(这里模拟服务端对数据处理时间,等待5秒)
服务端:数据处理成功,返回成功状态 200
客户端:服务端回调状态为:200

一步一步分析下代码,核心总结如下

1、接口作为方法参数,其实际传入引用指向的是实现类

2、Client的sendMsg方法中,参数final,因为要被内部类一个新的线程可以使用。这里就体现了异步

3、调用server的getClientMsg(),参数传入了Client本身(对应第一点)。

 

还有值得一提的是(逃

— 开源代码都在我的gitHub哦~

 

三、回调的应用场景

回调目前运用在什么场景比较多呢?从操作系统开发者调用

1、Windows平台的消息机制

2、异步调用微信接口,根据微信返回状态对出业务逻辑响应。

3、Servlet中的Filter(过滤器)是基于回调函数,需容器支持。

补充:其中 Filter(过滤器)和Interceptor(拦截器)的区别,拦截器基于是Java的反射机制,和容器无关。但与回调机制有异曲同工之妙。

总之,这设计让底层代码调用高层定义(实现层)的子程序,增强了程序的灵活性。

 

四、模式对比

上面讲了Filter和Intercepter有着异曲同工之妙。其实接口回调机制和一种设计模式—观察者模式也有相似之处:

观察者模式

GOF说道 — “定义对象的一种一对多的依赖关系,当一个对象的状态发送改变的时候,所有对他依赖的对象都被通知到并更新。”它是一种模式,是通过接口回调的方法实现的,即它是一种回调的体现。

接口回调

与观察者模式的区别是,它是种原理,而非具体实现。


五、心得

总结四步走:

机制,即是原理。

模式,即是体现。

记住具体场景,常见模式。

然后深入理解原理。

欢迎点击我的博客及GitHub — 博客提供RSS订阅哦!

———- http://www.bysocket.com/ ————- https://github.com/JeffLi1993 ———-

微         博:BYSocket  豆         瓣:BYSocket  FaceBook:BYSocket  Twitter    :BYSocket

Posted in Java, 技术

深入浅出: 大小端模式

Writer      :BYSocket(泥沙砖瓦浆木匠)

一、什么大小端?

大小端在计算机业界,Endian表示数据在存储器中的存放顺序。百度百科如下叙述之:

大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;

小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

这两种模式,泥瓦匠记忆宫殿:“小端低低”。这样就知道小端的模式,反之大端的模式。

比如整形十进制数字:305419896 ,转化为十六进制表示 : 0x12345678 。其中按着十六进制的话,每两位占8个位,及一个字节。如图

iostream

二、为什么有大小端模式之分呢?

如果统一使用大端或者小端,那么何来三国演义,何来一战二战呢?还有大小端也来源于战争。所以存在即是合理。

在操作系统中,x86和一般的OS(如windows,FreeBSD,Linux)使用的是小端模式。但比如Mac OS是大端模式。

在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器)。另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。

 

知道为什么有模式的存在,下面需要了解下具有有什么应用场景

1、不同端模式的处理器进行数据传递时必须要考虑端模式的不同

2、在网络上传输数据时,由于数据传输的两端对应不同的硬件平台,采用的存储字节顺序可能不一致。所以在TCP/IP协议规定了在网络上必须采用网络字节顺序,也就是大端模式。对于char型数据只占一个字节,无所谓大端和小端。而对于非char类型数据,必须在数据发送到网络上之前将其转换成大端模式。接收网络数据时按符合接受主机的环境接收。

 

三、java中的大小端

存储量大于1字节,非char类型,如int,float等,要考虑字节的顺序问题了。java由于虚拟机的关系,屏蔽了大小端问题,需要知道的话可用 ByteOrder.nativeOrder() 查询。在操作ByteBuffer中,也可以使用 ByteBuffer.order() 进行设置:

/**
 * @author Jeff Lee
 * @since 2015-10-13 20:40:00
 * ByteBuffer中字节存储次序
 */
public class Endians {
    public static void main(String[] args) {
        // 创建12个字节的字节缓冲区
        ByteBuffer bb = ByteBuffer.wrap(new byte[12]);
        // 存入字符串
        bb.asCharBuffer().put("abdcef");
        System.out.println(Arrays.toString(bb.array()));

        // 反转缓冲区
        bb.rewind();
        // 设置字节存储次序
        bb.order(ByteOrder.BIG_ENDIAN);
        bb.asCharBuffer().put("abcdef");
        System.out.println(Arrays.toString(bb.array()));

        // 反转缓冲区
        bb.rewind();
        // 设置字节存储次序
        bb.order(ByteOrder.LITTLE_ENDIAN);
        bb.asCharBuffer().put("abcdef");
        System.out.println(Arrays.toString(bb.array()));
    }
}

run下结果如图所示:

image

前两句打印说明了,ByteBuffer存储字节次序默认为大端模式。最后一段设置了字节存储次序,然后会输出,可以看出存储次序为小端模式。

 

欢迎点击我的博客及GitHub — 博客提供RSS订阅哦!

———- http://www.bysocket.com/ ————- https://github.com/JeffLi1993 ———-

微         博:BYSocket  豆         瓣:BYSocket  FaceBook:BYSocket  Twitter    :BYSocket

Posted in Java, 技术

图解 & 深入浅出Java初始化与清理:构造器必知必会

Writer      :BYSocket(泥沙砖瓦浆木匠)

微         博:BYSocket

豆         瓣:BYSocket

FaceBook:BYSocket

Twitter    :BYSocket

面向对象编程中,编程人员应该在意“资源”。比如

String hello = "hello";

在代码中,我们很在意在内存String类型hello,它是有一个生命周期的。在它生命周期中,初始化(initialization)清理(cleanup)是两个重要的环节。尤其是C程序中,很多bug会出现在对象初始化清理的环节。这样会造成一些程序安全问题。

《Think in Java》中说道:

随着计算机革命的发展,”不安全“的编程方式已逐渐成为编程代价高昂的主因之一。

一、栈与堆

在Java开发中,针对对象会在乎内存的两个区域:对象的生存空间是堆(heap) 方法调用及局部变量(也称变量)的生存空间栈(stack)。如图:

绘图1

 

二、什么是构造器,默认(无参)构造器

为了保证对象成功初始化,Java像C++一样引入了构造器(Constructor),即在创建对象的时候被自动调用的特殊方法。自然,Java额外提供了GC(垃圾回收器),对于不再使用的资源,垃圾回收机制会自动释放资源。Java通过类构造器创建新对象的时候,在使用此对象前,会调用其构造器进行初始化。如:

public class VoidConstructor {
    
    VoidConstructor(){
        // 构造器
    }
    
    void VoidConstructor() {
        // void 方法,不是构造器
    }
}

1、默认构造器

这里,VoidConstructor是一个类,如果需要创建一个VoidConstructor对象只要

VoidConstructor constr = new VoidConstructor();

调用上面的代码就可以通过这个默认构造器(即无参构造器),就生成一个VoidConstuctor对象。代码图解如下

绘图2

2、注意,构造器特殊方法,采取与类名同名。它没有返回值,这与返回值void方法不同。这里,void方法名命名也是不规范,应该是“每个方法首字母小写”,也一般不与类名相同。 

另外,就算你类没有写构造器的话,编译器会生成一个默认构造器。

三、带参构造器

下面跑个带参构造器的例子:

/**
 * @author Jeff Lee
 * @since 2015-9-7 16:54:19
 * 带参数简单构造器的展示
 */
public class SimpleConstructor2 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Child2(i);
        }
    }
}

// Child类
class Child2 {
    Child2(int i) {// 带参数的Child类构造器
        System.out.print("Child init " + i + " ");
    }
}

Eclipse中,右键Run asjava application

image

上面Child2(int)是Child类的唯一构造器,此时你先在编译器中通过 new Child2()来创建对象是不行的。

所以,构造器有助于避免开发中出现代码错误

二者,构造器可以用来初始化资源。其中,对象的创建与资源的初始化是捆绑在一起的。

休息一下吧~ 看看小广告:

开源代码都在我的gitHub上哦 — https://github.com/JeffLi1993 作者留言“请手贱,点项目star,支持支持拜托拜托

四、继承中的构造器

在创建一个对象时,所以继承下来的对象都会被调用各构造器。比如说下面这个例子:

/**
 * @author Jeff Lee
 * @since 2015-9-10 08:56:18
 * 继承中的构造器的案例
 */
public class ChildConstructor extends PersonConstructor{
	ChildConstructor() {
 //super(); 
		System.out.println("Making a Child Modle...");
	}
	
	@SuppressWarnings("unused")
	public static void main(String[] args) {
		ChildConstructor childConstructor = new ChildConstructor();
	}
}

class PersonConstructor {
	PersonConstructor() {
		System.out.println("Making a Person Modle...");
	}
}

main函数运行结果如下:

image

 

1. 先分析下类UML,ChildConstructor 继承 PersonConstructor,PersonConstructor 继承 Object:

 

绘图3

2. super关键字,super用来调用父类的构造器。如果子类中没有super关键字,编译器会自动添加默认super()方法。因此,子类通过super()方法调用父类构造器。

3. 然后main函数中,通过new指令,启动了ChildConstructor对象的创建。子类 ChildConstructor 构造器在执行时,第一件事通过super()调用父类,这又会连锁反应到Objerct类。所以,打印控制台先输出 “父类先初始化”。

这过程就是构造器链Constructor Chaining),即Child对象  is-a Person对象同时也 is-a Object对象。如果创建Child对象,也同时创建了Person对象和Object对象的部分。

4. 过程流程图详解(图中是各对象构造器在堆栈块中调用流程 图中引用两字有歧义):

绘图4

五、总结

 

构造器虽小,关键还是非常关键的。

本文小结:

1、默认构造器

2、有参构造器

3、继承中的构造器

还有第一点的知识基础补充

 

欢迎点击我的博客及GitHub — 博客提供RSS订阅哦!

———- http://www.bysocket.com/ ————- https://github.com/JeffLi1993 ———-