《Thinking in Java》学习笔记(四)

1.Java中的闭包与回调

  闭包(Closure)是一种能被调用的对象,它保存了创建它的作用域的信息。JAVA并不能显式地支持闭包,但是在JAVA中,闭包可以通过“接口+内部类”来实现,因为对于非静态内部类而言,它不仅记录了其外部类的详细信息,还保留了一个创建非静态内部类的引用,通过它可以访问外部类的私有成员,因此可以把非静态内部类当成面向对象领域的闭包。通过内部类提供闭包的功能是优良的解决方案,比指针更为灵活、安全。

  回调的价值在于它的灵活性—它可以动态地决定需要调用什么方法,在Swing中,回调被大量使用。简单地说来就是类A提供了一个方法C,但是A不直接调用C,而是通过另外一个类B来调用。

  通过内部类实现闭包时,内部类可以轻松访问外部类,完成回调。

public interface Teachable{
void work();
}
public class Programmer {
private String name;
public Programmer(){} public Programmer(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void work(){
System.out.println(name + "在灯下敲键盘...");
}
}

  那么假如有一个人既是教师又是程序员,也就是需要定义一个类既继承Programmer父类又要实现Teachable,问题在于这个类只能定义一个work方法,那怎么去实现这个需求呢,这里就可以利用内部类可以访问外部类的私有成员的特点来解决这个问题。

  这里需要注意的是内部类Closure 是私有的,只能通过外部类的getCallbackReference获得。获得Closure对象后又可以轻松地访问到外部类的私有方法。

package com.home;

public class TeachableProgrammer extends Programmer {

    public TeachableProgrammer() {
super();
} public TeachableProgrammer(String name) {
super(name);
} // 教学工作任然由TeachableProgrammer定义
private void teach() {
System.out.println(getName() + "正在讲课");
} private class Closure implements Teachable { @Override
public void work() {
// 非静态内部类实现Teachable的work方法,作用仅仅是向客户类提供一个回调外部类的途径
teach();
}
} // 返回一个非静态内部类的引用,允许外部类通过该引用来回调外部类的方法
public Teachable getCallbackReference() {
return new Closure();
}
}

  测试类:

package com.home;

public class TestClosure {

    public static void main(String[] args) {
TeachableProgrammer tp = new TeachableProgrammer("xx");// 直接调用TeachableProgrammer从Programmer类继承下来的work方法
tp.work();
// 表明上看是调用的Closure的work方法,实际上是通过通过work方法回调TeachableProgrammer的teach方法
tp.getCallbackReference().work();
} }

2.容器类

  Java容器类的用途是用来保存对象,可以分为以下的两个大类:

  1>Collection,一个用于存储对象的序列。又可以具体的分为List,Set和Queue。

  2>Map,一个以键值对来存储对象的序列。

  Arrays和Collections类中提供了很多实用的静态方法。 

  1.创建一个空的collection,向collection中添加元素或者集合,这种添加的方式效率是比较高的

  public boolean addAll(Collection<? extends E> coll)

List<String> c = new ArrayList<String>();
Collection col = new ArrayList();
Collections.addAll(col, 1,2,3,c);

  2.根据元素自然顺序 对指定列表按升序排序。此方法内部调用了Comparable 的compareTo()方法。

   public static <T extends Comparable<? super T>> void sort(List<T> list)

  3.根据元素的自然顺序,返回给定 collection 的最大元素

  public <T extends Object &   Comparalbe<? super T>> T max(Collection<? extends T> col)

  4.使用指定元素替换指定列表中的所有元素

  public static <T> void fill(List<? super T> list,T obj)

  5. 使用另一个值替换列表中出现的所有某一指定值

  public static T replacAll(List<T>,T oldValue,T newValue)

  6.反转指定列表中元素的顺序

  public static void reverse(List<?> list)

  7.如果搜索键包含在列表中,则返回搜索键的索引,否则返回 (-(插入点) - 1)

  public static <T> int binarySearch(List<? extends t> list,T key,Comparator<? extends T> c)

  Arrays常用的方法:

  1.二分查找
   private static int binarySearch0(double[] a, int fromIndex, int toIndex,double key)  

  2.复制

  public static <T> T[] copyOf(T[] original, int newLength) 

  3.复制部分

  public static <T> T[] copyOfRange(T[] original, int from, int to)

  4.排序

  Arrays.sort();

  5.填充  

  Arrays.fill()
  6.将数组转为List

  Arrays.asList()

  注:System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)也能实现数组复制的功能,只是System.arraycopy是native的,效率也是较高的。

  新的程序中不应该出现过时的Vector、HashTable和Stack。

  a.如果需要大量的随机访问,就应该使用ArrayList,如果需要从表中间进行大量的插入或删除操作,则应该使用LinkedList。

  b.如果访问速度优先考虑,则应该使用HashMap,如果希望顺序访问键值对中的键,则应该使用TreeMap,LinkedHashMap保持了元素的插入顺序,同时也进行了hash散列。

  c.Set中没有重复的元素,和Map类似,HashSet访问速度快,TreeSet是有序的,LinkedTreeSet保持了插入的顺序。可以在构造TreeSet时传入自己的Comparator来按照自己的意愿完成排序。

  d.可以使用iterator()方法要求集合返回一个迭代器,来顺序遍历和操作集合中的元素。迭代器的功能也可以通过增强for循环来完成。 

        List<String> c = new ArrayList<String>();
Collections.addAll(c, "l","o","v","e"); Iterator<String> it = c.iterator();
while(it.hasNext()){
System.out.println("get by iterator: "+ it.next());
} for(String s : c){
System.out.println("get by for: "+ s);
}

  e.Iterator只能向后移动,ListIterator是其的子类,但是却可以实现前后移动,只适用于List类型的容器。

  f.尽管Java中已经提供了栈(Stack)、队列(Queue)、双端队列的实现,但是自己用LinkedList来实现功能更为强大的队列和栈也是不错的。

  g.LinkedList实现了Queue接口,可以向上转型为Queue

Queue<String> queue = new LinkedList<String>();

  h.Queue默认的是先进先出,使用Queue的子类PriorityQueue可以实现按优先级排序,让下一个弹出的对象是优先级最高的对象,可以通过自己的Comparator来改变弹出的顺序。

  i.通过EntrySet来访问Map类型的容器

        Map<String,String> map=new HashMap<String,String>();
for (Map.Entry<String, String> m : map.entrySet()) {
System.out.println("key:"+m.getKey()+" value"+m.getValue());
}

3.comparator和comparable的区别  

  都是为了完成对象的排序功能,不同的是Comparable和类本身进行了绑定,Comparator和类本身是没有直接的关系。

class Person implements Comparable {
String name;
int age; @Override
public int compareTo(Object o) {
Person another = (Person) o;
int i = 0;
i = name.compareTo(another.name); // 使用字符串的比较
if (i == 0) { // 如果名字一样,比较年龄, 返回比较年龄结果
return age - another.age;
}
else {
return i; // 名字不一样, 返回比较名字的结果.
}
}
}

  可以直接用 Collections.sort( personList ) 对其排序。

class PersonComparator implements Comparator {
@Override
public int compare(Object o1, Object o2) {
Person one = (Person) o1;
Person another = (Person) o2;
int i = 0;
i = one.name.compareTo(another.name); // 使用字符串的比较
if (i == 0) { // 如果名字一样,比较年龄,返回比较年龄结果
return one.age - another.age;
}
else {
return i; // 名字不一样, 返回比较名字的结果.
}
} }

  Collections.sort( personList , new PersonComparator()) 可以对其排序。

4.equals和hashcode

  hashCode()方法和equals()方法是在Object类中就已经定义了的,所以在java中定义的任何类都会有这两个方法。

   Object类中的equals()方法用来比较两个对象内存地址的值,而原始的hashCode()方法用来返回其所在对象的物理地址。

   如果在比较两个对象时,比较的是对象的内容而不是内存地址,据需要对类中的equals()进行重写。在重写了equals()方法后一定不要忘了重写hashCode()方法,否则该类的对象在HashMap、HashSet中使用时可能会出现问题。

  

   覆盖equals方法看起来似乎很简单,但是如果覆盖不当会导致错误,正确的依据应是:  

  • 自反性。对于任何非null的引用值x,x.equals(x)必须返回true。
  • 对称性。对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true
  • 传递性。对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
  • 一致性。对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用该x.equals(y)就会一直地返回true,或者一致地返回false。
  • 对于任何非null的引用值x,x.equals(null)必须返回false。

  hashCode()方法和equals()方法间的关系应该是:

  如果X.equals(Y)成立,那么它们的X和Y的hashCode值一定相同。

  如果两个对象X和Y的hashCode相同,但是X.equals(Y)不一定成立。但是X.equals(Y)不成立时,返回不同的hashCode,会提高散列的性能。

5.JAVA重定向输入输出

  重定向标准输入和输出Java的标准输入和输出分别通过system.in和system.out来代表,默认情况下他们分别代表键盘和显示器。

  在system类中提供了3个重定向标准输入和输出的方法:

  setErr(PrintStream err) 重新分配“标准”错误输出流。

  setIn(InputStream in) 重新分配“标准”输入流。

  setOut(PrintStream out) 重新分配“标准”输出流。

  当然我们可以自己来写程序,修改我们应用程序的输入和输出。比如说现在不希望标准输出到显示器上,而是我自己的一个文件里面,或者是说我现在不需要从键盘上来录入内容,而是读我本地的一个文本。

   重定向输出和错误的例子:

import java.io.FileOutputStream;
import java.io.PrintStream; public class RedirectOut {
public static void main(String [ ] args) {
PrintStream ps = null;
try {
ps = new PrintStream(new FileOutputStream("src/Redirect.txt"));
System.setOut(ps);
System.setErr(ps); System.out.println("这里重定向了标准输出");
System.out.println("结束");
throw new RuntimeException("exception happens");
}
catch (Exception e) {
e.printStackTrace();
}
finally {
if (ps != null) {
ps.close();
}
}
}
}

  重定向输入的例子:

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Scanner; public class RedirectIn {
public static void main(String [ ] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("src/Redirect.txt");
System.setIn(fis);
// 使用system.in创建Scanner对象,用来获取标准输入
Scanner sc = new Scanner(System.in);
sc.useDelimiter("\n");// 增加分隔符
while (sc.hasNext()) {
System.out.println(sc.next());
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
if (fis != null) {
try {
fis.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

6.Process和子进程的通信

  Runtime对象有exec方法,他可以运行jvm命令行,该方法产生一个Process对象,Process对象代表由该Java程序启动的子进程,Process类有以下方法,可以和子进程通信;

  InputStream getErrorStream():获取子进程的错误流

  InputStream getInputStream():获取子进程的输入流

  OutputStream getOutputStream():获取子进程的输入流

上一篇:Vmware虚拟机进入BIOS方法


下一篇:.Net魔法堂:史上最全的ActiveX开发教程——开发篇