Java-Note-为何泛型容器中的方法参数保留Object

今天在阅读的时候突然发现一个问题, 官方工具库里面的泛型容器有一部分方法并未使用泛型方法, 而是依旧沿用JDK1.5之前的Object, 例如:

  • Map

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    boolean containsKey(Object key);

    boolean containsValue(Object value);

    V get(Object key);

    V remove(Object key);

    boolean equals(Object o);

    ...
    • List

      1
      2
      3
      4
      5
      6
      7
      boolean contains(Object o);

      boolean remove(Object o);

      boolean equals(Object o);

      ...
    • 原因

      1. 这些含Object类型参数的方法都有一个共同特点, 即需要与容器内的Key或者Value进行比较, 而在Java的规范中, 比较是不需要类型完全相同的, 例如Number类型的实例可以和Long类型的实例相比较
      2. 在Java中比较方法需要重写Object中的equals(Object o), 所以用到equals(Object o)的方法的参数自然用Object而不是泛型
    • 你们以为这就完了吗?
    • 反驳: 使用Object可能造成的错误比原因1中的好处要大得多, 而且涉及到equals完全可以重新创建一个方法例如getByEquals(Object o)get(K k)一起使用
    • 个人结论: 可能存在设计缺陷(不要打我…)
    • 佐证: 在比Java新的C#中, Map(Dictonary)的get方法参数是(K k)

Java-Note-Java中潜在类型机制的实现和优化

  • 潜在类型机制
    • 介绍: 具有潜在类型机制的语言只要求实现某个方法子集, 而不是某个特定的接口, 这样可以使代码更加泛化. 潜在类型机制不关心使用的类型是否来自同一个接口, 只要该些类型具有相同的方法.
  • Java中潜在类型机制的简单模拟

    • 方法: 首先想到的就是利用反射来模拟
    • 例子:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      class Clazz1 {
      public void f() {
      System.out.println("Class1 f()");
      }

      public void g() {
      System.out.println("Class1 g()");
      }
      }

      class Clazz2 {
      public void f() {
      System.out.println("Class2 f()");
      }

      public void g() {
      System.out.println("Class2 g()");
      }
      }

      class ReflectTest {
      public static void perform(Object object) {
      Class clazz = object.getClass();
      try {
      Method method = clazz.getMethod("f");
      method.invoke(object);
      } catch (NoSuchMethodException e) {
      e.printStackTrace();
      } catch (IllegalAccessException e) {
      e.printStackTrace();
      } catch (InvocationTargetException e) {
      e.printStackTrace();
      }
      try {
      Method method = clazz.getMethod("g");
      method.invoke(object);
      } catch (NoSuchMethodException e) {
      e.printStackTrace();
      } catch (IllegalAccessException e) {
      e.printStackTrace();
      } catch (InvocationTargetException e) {
      e.printStackTrace();
      }
      }
      }

      public class Test2 {
      public static void main(String[] args) {
      ReflectTest.perform(new Clazz1());
      ReflectTest.perform(new Clazz2());
      }
      }
  • 简单模拟的缺陷: 反射提供了一种模拟潜在类型机制的方法, 但是它将所有的类型检查都转移到了运行时, 因此无法进行编译期类型检查. 需要进行优化

  • 对简单模拟的优化
    • 潜在类型机制的实质: 其创建了一个包含所需方法的隐式接口
    • 优化方法: 通过手工编写必须的接口(注意: 这里的接口与前文中的潜在类型机制不需要实现特定接口中的接口是不同的, 这里的接口是不改变已存在的拥有相同方法的类的代码的), 就可以实现潜在类型机制, 而这样不会用到反射, 从而可以进行编译期类型检查
    • 例子:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      package test;

      interface AdapterInterface {
      void f();

      void g();
      }

      class Clazz1 {
      void f() {
      System.out.println("Clazz1 f()");
      }

      void g() {
      System.out.println("Clazz1 g()");
      }
      }

      class Clazz2 {
      void f() {
      System.out.println("Clazz2 f()");
      }

      void g() {
      System.out.println("Clazz2 g()");
      }
      }

      class ClassAdapter1 implements AdapterInterface {
      private Clazz1 class1;

      public ClassAdapter1(Clazz1 class1) {
      this.class1 = class1;
      }

      @Override
      public void f() {
      class1.f();
      }

      @Override
      public void g() {
      class1.g();
      }
      }

      class ClassAdapter2 implements AdapterInterface {
      private Clazz2 class2;

      public ClassAdapter2(Clazz2 class2) {
      this.class2 = class2;
      }

      @Override
      public void f() {
      class2.f();
      }

      @Override
      public void g() {
      class2.g();
      }
      }

      class Performer {
      public static void perform(AdapterInterface adapterInterface) {
      adapterInterface.f();
      adapterInterface.g();
      }
      }

      public class Test {
      public static void main(String[] args) {
      Performer.perform(new ClassAdapter1(new Clazz1()));
      Performer.perform(new ClassAdapter2(new Clazz2()));
      }
      }

Java-Note-Pattern-动态代理个人心得

  • 介绍: 通过实现InvocationHandler接口, 动态的生成代理类.
  • 优点:
    1. 无需重写要修改的方法, 在普通代理代码变得很复杂的情况下, 动态代理可以减少代码量.
    2. 通过动态代理可以实现AOP(面向切面编程)
  • 缺点: 基类必须实现接口, 没有普通代理方便.
  • 要点:
    1. 代理类中有被代理类的成员变量
    2. 代理类必须初始化被代理类的实例
    3. 代理类中的invoke(Object proxy, Method method, Object[] args)用来动态执行需要修改的方法以及在其之前的操作
    4. 要用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)来生成代理类的实例
      1. 第二个参数Class<?>[] interfaces表明基类必须实现接口且可以由多个基类和其接口
  • 例子:

    • 接口

      1
      2
      3
      4
      5
      interface Interface {
      void doSomething();

      void somethingElse(String arg);
      }
    • 基类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      class RealObject implements Interface {
      @Override
      public void doSomething() {
      print("doSomething");
      }

      @Override
      public void somethingElse(String arg) {
      print("somethingElse "+arg);
      }
      }
    • 代理类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      class SimpleProxy implements Interface {
      private Interface proxied;

      public SimpleProxy(Interface proxied) {
      this.proxied = proxied;
      }

      @Override
      public void doSomething() {
      print("SimpleProxy doSomething");
      proxied.doSomething();
      }

      @Override
      public void somethingElse(String arg) {
      print("SimpleProxy somethingElse" + arg);
      proxied.somethingElse(arg);
      }
      }
    • 测试类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public class SimpleProxyDemo {
      public static void consumer(Interface iface) {
      iface.doSomething();
      iface.somethingElse("bonobo");
      }
      public static void main(String[] args) {
      consumer(new RealObject());
      consumer(new SimpleProxy(new RealObject()));
      }
      }

Java-Note-Pattern-代理模式与装饰器模式对比

Java-Note-Pattern-代理模式个人心得

  • 代理模式属于Structure Design Pattern, 可以在不改动被代理类指定方法的情况下修改它, 比如增加功能, 权限控制等
  • 要点:
    1. 代理类中需要有接口的成员变量并被正常初始化, 以修改指定实例的方法并保证修改类在利用其它修改类进行初始化时
  • 例子:

    • 接口及基类:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      interface Coffee {
      void get();
      }

      class CoffeeImpl implements Coffee {
      @Override
      public void get() {
      System.out.println("get coffee");
      }
      }
    • 代理类:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      class CoffeeProxy implements Coffee {
      Coffee coffee;

      public CoffeeProxy(Coffee coffee) {
      this.coffee = coffee;
      }

      @Override
      public void get() {
      System.out.println("add water");
      coffee.get();
      }
      }
    • 测试类:

      1
      2
      3
      4
      5
      6
      public class CoffeeProxyTest {
      public static void main(String[] args) {
      Coffee proxy = new CoffeeProxy(new CoffeeImpl());
      proxy.get();
      }
      }

Java-Note-Pattern-装饰器模式个人心得

  • 作用 : 装饰器模式属于Structure Design Pattern, 利用Java语言的多态性, 可以在运行时多层次的修改一个实例的特定方法, 并且该实例的其它部分不受影响
  • 要点 :
    1. 装饰器类扩展自被装饰的类(下文称为基类)的接口(下文称为接口), 修改类扩展自装饰器类, 这样的链式扩展保证了修改类可以利用其它修改类进行初始化, 从而为基类增加更多的功能
    2. 装饰器类中需要有接口的成员变量并被正常初始化, 以修改指定实例的方法并保证修改类在利用其它修改类进行初始化时, 增加的功能不会被被利用的修改类覆盖
  • 例子 :

    • 接口及基类 :

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      interface Coffee {
      public void get();
      }

      class CoffeeImpl implements Coffee {
      @Override
      public void get() {
      System.out.println("get coffee");
      }
      }
    • 装饰器类 :

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      class CoffeeDecorator implements Coffee {
      private Coffee coffee;


      public CoffeeDecorator(Coffee coffee) {
      this.coffee = coffee;
      }

      @Override
      public void get() {
      coffee.get();
      }
      }
    • 装饰类 :

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      class MilkCoffee extends CoffeeDecorator {

      public MilkCoffee(Coffee coffee) {
      super(coffee);
      }

      @Override
      public void get() {
      System.out.println("add milk");
      super.get();
      }
      }

      class ChocolateCoffee extends CoffeeDecorator {
      public ChocolateCoffee(Coffee coffee) {
      super(coffee);
      }
      @Override
      public void get() {
      System.out.println("add chocolate");
      super.get();
      }
      }
    • 测试类 :

      1
      2
      3
      4
      5
      6
      public class Test {
      public static void main(String[] args) {
      ChocolateCoffee coffee = new ChocolateCoffee(new MilkCoffee(new CoffeeImpl()));
      coffee.get();
      }
      }

DigitCircuit-Note-触发器的原理的简单个人理解

计算机中的寄存器是由触发器(Flip-flop)组成的, 其作用是储存之前的状态, 即当触发器的两个输入都是低电平时(不管这两个输入之前是什么状态), 输出的状态保持不变, 而如果不使用触发器, 则无法保持之前的状态. 讲一个极端的情况: 当断电时, 两个输入都是低电平, 此时依然保存着之前的信息

Java-Note-对动态代理的一些理解

动态代理可以动态的创建代理并动态地处理对所代理方法的调用. 在动态代理上所做的所有调用都会被重定向到单一的调用处理器(InvocationHandler)上.

动态代理的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class DynamicProxyHandler implements InvocationHandler {
private Object proxied;

public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("*** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args);
if (args != null) {
for (Object arg : args) {
System.out.println(" " + arg);
}
}
Object result = method.invoke(proxied, args);
System.out.println("proxy info: " + proxy.toString());
return result;
}
}

public class SimpleDynamicProxy {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}

public static void main(String[] args) {
RealObject real = new RealObject();
consumer(real);
Interface proxy = (Interface) Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{Interface.class},
new DynamicProxyHandler(real)
);
consumer(proxy);
}
}

在例子中调用proxy的方法如doSomething()会被指向invoke(), 而如果在invoke()中再调用proxy的方法如toString()时,会陷入循环调用的陷阱, 进而抛出Stack Overflow的错误.

参考文献: Thinking in Java Fourth Edition

Person-Target

  • develop intellij plugin:
    • jump from source file to compiled file
    • timestamp convert

Ant-Note-编译多个源并且在其中排除一部分文件

在开发过程中需要用到apache ant来编译源码, 在使用过程中遇到一个比较特殊的问题: 需要编译多个源目录并且在其中要排除掉部分文件. 编译多个源需要用到javac标签下面的src标签, 但如果使用两个src标签如:

1
2
3
4
<javac ...>
<src path="src1">
<src path="src2">
</javac>

则在下面加入exclude标签时, 不会将exclude下的文件排除在外, 如:

1
2
3
4
5
<javac ...>
<src path="src1">
<src path="src2">
<exclude name="package/dir/srccode.java">
</javac>

由于exclude使用的是相对路径, 原因可能是因为多路径模式下无法判断该exclude标签属于哪个src, 有懂的兄弟请指出
为了exclude有效, 应该使用以下结构:

1
2
3
4
5
6
<javac ...>
<src path="src">
<include name="src1">
<include name="src2">
<exclude name="package/dir/code.java">
</javac>`

用一个更大的路径作为主src, 而将需要编译的src放到include里面, 这样exclude就是单个src下面的相对路径了, 经测试, 编译成功