《Java Puzzlers》——高级谜题

4 篇文章 0 订阅
2 篇文章 0 订阅

有毒的括号

问题:给定一个合法的 Java 表达式,添加一个注解赋值顺序的括号就变成不合法的了。

解决方案:补码的不对称性,给最小负数加一个括号就不合法了。

等价的自反性、传递性和对称性

  1. 不符合自反性,例如 Double.NaN == Double.NaNfalse
  2. 符合对称性;
  3. 不符合传递性,由于整数与浮点数之间的转换有可能会出现丢失精度的问题,所以可能破坏 == 的传递性。
public class Transitive {
    public static void main(String[] args) throws Exception {
        /*
         * If you can come up with a set of primitive types and values
         * that causes this program to print "true true false", then
         * you have proven that the == operator is not transitive.
         */
        long x = Long.MAX_VALUE;
        double y = (double)Long.MAX_VALUE;
        long z = Long.MAX_VALUE - 1;

        System.out.print((x == y) + " ");
        System.out.print((y == z) + " ");
        System.out.println(x == z);
    }
}
输出:
true true false

原生类型

问题:

import java.util.*;

public class Pair<T> {
    private final T first;
    private final T second;

    public Pair(T first, T second) {
        this.first = first;
        this.second = second;
    }

    public T first() {
        return first;
    }
    public T second() {
        return second;
    }
    public List<String> stringList() {
        return Arrays.asList(String.valueOf(first),
                             String.valueOf(second));
    }

    public static void main(String[] args) {
        Pair p = new Pair<Object>(23, "skidoo");
        System.out.println(p.first() + " " + p.second());
        for (String s : p.stringList())
            System.out.print(s + " ");
    }
}
编译错误

解析:对于一个原生类型,它的所有实例成员都要擦除掉对应部分。具体来说,一个实例方法声明中出现的每个参数化的类型都要被对应的原生部分所取代。即 stringList 方法的返回值会被替换为原生类型 List 。所以最终导致循环中返回的是 Object 而不是 String

解决方案:为局部变量 p 提供一个合适的参数化声明: Pair<Object> p = new Pair<Object>(23, "skidoo");

原生类型 List 和参数化类型 List<Object> 不一样,前者允许添加任意类型元素,不是类型安全的,后者声明可以包含任意类型的元素,是类型安全的。(好像没啥区别,只是一个没明说,一个显式说明?)

泛型

问题:

public class LinkedList<E> {
    private Node<E> head = null;

    private class Node<E> {
        E value;
        Node<E> next;

        // Node constructor links the node as a new head
        Node(E value) {
            this.value = value;
            this.next = head;
            head = this;
        }
    }

    public void add(E e) {
        new Node<E>(e);
        // Link node as new head
    }

    public void dump() {
        for (Node<E> n = head; n != null; n = n.next)
            System.out.println(n.value + " ");
    }

    public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<String>();
        list.add("world");
        list.add("Hello");
        list.dump();
    }
}
编译错误

解析:泛型内部类访问泛型外部类的类型参数…顶不住了,搞不懂。

解决方案:

  1. 这里内部类的类型参数和外部类一样,所以可以直接去掉内部类的类型参数。
  2. 优先使用静态成员类,并且应该只在一个类自己的方法中修改该类的实例域。
class LinkedList<E> {
    private Node<E> head = null;
    private static class Node<T> {
        T value;
        Node<T> next;
        Node(T value, Node<T> next) {
            this.value = value;
            this.next = next;
        }
    }
    
    public void add(E e) {
        head = new Node<E>(e, head);
    }
    public void dump() {
        for (Node<E> n = head; n != null; n = n.next)
            System.out.println(n.value + " ");
    }

    public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<String>();
        list.add("world");
        list.add("Hello");
        list.dump();
    }
}

序列化问题

问题:

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

public class SerialKiller {
    public static void main(String[] args) {
        Sub sub = new Sub(666); 
        sub.checkInvariant();

        Sub copy = (Sub) deepCopy(sub);
        copy.checkInvariant();
    }

    // Copies its argument via serialization (See Puzzle 80)
    static public Object deepCopy(Object obj) {
        try {
            ByteArrayOutputStream bos = 
                new ByteArrayOutputStream();
            new ObjectOutputStream(bos).writeObject(obj);
            ByteArrayInputStream bin =
                new ByteArrayInputStream(bos.toByteArray());
            return new ObjectInputStream(bin).readObject(); 
        } catch(Exception e) {
            throw new IllegalArgumentException(e); 
        }
    }
}

class Super implements Serializable {
    final Set<Super> set = new HashSet<Super>();
} 

final class Sub extends Super {
    private int id;
    public Sub(int id) {
        this.id = id;
        set.add(this); // Establish invariant
    }

    public void checkInvariant() {
        if (!set.contains(this))
            throw new AssertionError("invariant violated");
    }

    public int hashCode() {
        return id;
    }

    public boolean equals(Object o) {
        return (o instanceof Sub) && (id == ((Sub)o).id);
    }
}
编译错误

解析:HashSet 底层依赖于 HashMap ,所以在反序列化的时候,为了组装正在被反序列化的散列集合,HashSet.readObject 方法会调用 HashMap.put 方法,而这个方法会去调用每个键的 hashCode 方法。具体来说,在上述程序反序列化时,先反序列化父类的 set ,但是在反序列化 set 时,会调用到该 set 上每个元素的 hashCode 方法,而 set 中只有一个 Sub 的对象实例,其 set 字段正在被反序列化, id 字段尚未被初始化,所以值为 0。所以此时调用 hashCode 方法将返回 0。所以会把该对象放在错误的位置。set 反序列化完后,才把 id 赋值为 666。

匿名内部类的继承

问题:

public class Twisted {
    private final String name;
    Twisted(String name) {
        this.name = name;
    }
    private String name() {
        return name;
    }
    private void reproduce() {
        new Twisted("reproduce") {
            void printName() {
                System.out.println(name());
            }
        }.printName();
    }
    public static void main(String[] args) {
        new Twisted("main").reproduce();
    }
}
输出:
main

解析:首先,在顶层的类型中,所有本地的、内部的、嵌套的和匿名的类都可以访问彼此的成员。但是,匿名内部类并不能继承到 “reproduce” 实例中的 name() 方法,所以它会关联到外围实例 “main” 上边。

常量表达式

问题:将第一个类作为客户端,第二个类作为库类,编译执行。然后将库类改变为下边的版本,重新编译库类,不编译客户端。会输出什么。

public class PrintWords {
    public static void main(String[] args) {
        System.out.println(Words.FIRST  + " " + 
                           Words.SECOND + " " +
                           Words.THIRD);
    }
}

public class Words {
    private Words() { };  // Uninstantiable

    public static final String FIRST  = "the";
    public static final String SECOND = null;
    public static final String THIRD  = "set";
} 

// The second version of the Words class appears below (commented out)
//
// public class Words {
//     private Words() { };  // Uninstantiable
//
//     public static final String FIRST  = "physics";
//     public static final String SECOND = "chemistry";
//     public static final String THIRD  = "biology";
// }

解析:输出:the chemistry set。对于常量字段的引用会在编译期转换为它们所表达的常量的值,这样的字段称为常量变量。客户端程序会将这些常量值编译进class文件,所以不重新编译客户端程序结果是不变的。不过 null 不是一个常量变量,所以 Words.SECOND 字段会动态从库类中读取,所以可以读取到最新的值。

解决方案:使用非常量的表达式去初始化一个字段,甚至是 final 字段,那么这个字段就不是常量,比如可以通过将一个常量表达式传给一个方法,该方法直接返回输入参数:

public class Words {
    private Words() { };  // Uninstantiable

    public static final String FIRST  = ident("the");
    public static final String SECOND = ident(null);
    public static final String THIRD  = ident("set");

    private static String ident(String s) {
        return s;
    }
} 

或者使用枚举,对于使用枚举的客户端程序,总是会从库类中加载最新值。

随机shuffle的公平性

问题:

import java.util.Random; 

public class Shuffle {
    private static Random rnd = new Random();
    public static void shuffle(Object[] a) {
        for (int i = 0; i < a.length; i++)
            swap(a, i, rnd.nextInt(a.length));
    }

    private static void swap(Object[] a, int i, int j) {
        Object tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
}

解析:shuffle 方法有n的n次方种打乱动作,但是不同的排列只有n!种,这两者并不是一一对应的关系。具体数学推导过程…跳过了

总结:首先,最好使用类库中有的方法(Collections.shuffle(List))。此外,类库中的随机数生成器针对具体的应用场景并不总是严格随机。

最终章,一些小知识

问题:

public class ApplePie {
    public static void main(String[] args) {
        int count = 0;
        for (int i = 0; i < 100; i++); {
            count++;
        }
        System.out.println(count);
    }
}
输出:
1
import java.util.*;
public class BananaBread {
    public static void main(String[] args) {
        Integer[] array = { 3, 1, 4, 1, 5, 9 };
        Arrays.sort(array, new Comparator<Integer>() {
            public int compare(Integer i1, Integer i2) {
                return i1 < i2 ? -1 : (i2 > i1 ? 1 : 0);
            }
        });
        System.out.println(Arrays.toString(array));
    }
}
原样输出
public class ChocolateCake {
    public static void main(String[] args) {
        System.out.println(true?false:true == true?false:true);
    }
}
输出:
false

解析:

  1. for 语句后边多了个 ;,所以执行了空循环,count++ 是额外作为一个代码块的,只执行了一次。(Java 中 ; 的这种用法类似于 Python 中的 pass 关键字。)

  2. 这比较器写的…是个废话,并没有完成比较。

  3. 优先级问题。第一个三元运算符计算完后,直接计算的是 == 而不是后边一个三元运算符。对于这种可能存在优先级隐患的,还是老老实实加括号吧…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值