`

java 相关问题(二)

    博客分类:
  • java
 
阅读更多

七、ThreadLocal 详解

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过 ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。 各个线程中访问的是不同的对象。

另外,说 ThreadLocal 使得各线程能够保持各自独立的一个对象,并不是通过 ThreadLocal.set() 来实现的,而是通过每个线程中的 new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。
通 过ThreadLocal.set() 将这个新创建的对象的引用保存到各线程的自己的一个 map中,每个线程都有这样一个 map,执行 ThreadLocal.get()时,各线程从自己的map 中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作 为 map 的 key 来使用的。

如果 ThreadLocal.set() 进去的东西本来就是多个线程共享的同一个对象,那么多个线程的 ThreadLocal.get() 取得的还是这个共享对象本身,还是有并发访问问题。


下面来看一个 hibernate 中典型的 ThreadLocal 的应用:

    private static final ThreadLocal threadSession = new ThreadLocal();

    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
    }
 

 

可以看到在getSession()方法中,首先判断当前线程中有没有放进去session,如果还没有,那么通过 sessionFactory().openSession()来创建一个session,再将session set到线程中,实际是放到当前线程的ThreadLocalMap这个map中,这时,对于这个session的唯一引用就是当前线程中的那个 ThreadLocalMap(下面会讲到),而threadSession作为这个值的key,要取得这个session可以通过 threadSession.get()来得到,里面执行的操作实际是先取得当前线程中的ThreadLocalMap,然后将 threadSession作为key将对应的值取出。这个session相当于线程的私有变量,而不是public的。
显然,其他线程中是取不到这个session的,他们也只能取到自己的ThreadLocalMap中的东西。要是session是多个线程共享使用的,那还不乱套了。
试想如果不用ThreadLocal怎么来实现呢?可能就要在action中创建session,然后把session一个个传到service 和dao中,这可够麻烦的。或者可以自己定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中,应该也 行,这也是一般人的想法,但事实上,ThreadLocal的实现刚好相反,它是在每个线程中有一个map,而将ThreadLocal实例作为key, 这样每个map中的项数很少,而且当线程销毁时相应的东西也一起销毁了,不知道除了这些还有什么其他的好处。

总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1。 每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2。 将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行 的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。


当然如果要把本来线程共享的对象通过ThreadLocal.set()放到线程中也可以,可以实现避免参数传递的访问方式,但是要注意 get()到的是那同一个共享对象,并发访问问题要靠其他手段来解决。但一般来说线程共享的对象通过设置为某类的静态变量就可以实现方便的访问了,似乎没 必要放到线程中。

ThreadLocal的应用场合,我觉得最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。

下面来看看ThreadLocal的实现原理(jdk1.5源码)

public class ThreadLocal<T> {
    /**
     * ThreadLocals rely on per-thread hash maps attached to each thread
     * (Thread.threadLocals and inheritableThreadLocals).  The ThreadLocal
     * objects act as keys, searched via threadLocalHashCode.  This is a
     * custom hash code (useful only within ThreadLocalMaps) that eliminates
     * collisions in the common case where consecutively constructed
     * ThreadLocals are used by the same threads, while remaining well-behaved
     * in less common cases.
     */
    private final int threadLocalHashCode = nextHashCode();

    /**
     * The next hash code to be given out. Accessed only by like-named method.
     */
    private static int nextHashCode = 0;

    /**
     * The difference between successively generated hash codes - turns
     * implicit sequential thread-local IDs into near-optimally spread
     * multiplicative hash values for power-of-two-sized tables.
     */
    private static final int HASH_INCREMENT = 0x61c88647;

    /**
     * Compute the next hash code. The static synchronization used here
     * should not be a performance bottleneck. When ThreadLocals are
     * generated in different threads at a fast enough rate to regularly
     * contend on this lock, memory contention is by far a more serious
     * problem than lock contention.
     */
    private static synchronized int nextHashCode() {
        int h = nextHashCode;
        nextHashCode = h + HASH_INCREMENT;
        return h;
    }

    /**
     * Creates a thread local variable.
     */
    public ThreadLocal() {
    }

    /**
     * Returns the value in the current thread's copy of this thread-local
     * variable.  Creates and initializes the copy if this is the first time
     * the thread has called this method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            return (T)map.get(this);

        // Maps are constructed lazily.  if the map for this thread
        // doesn't exist, create it, with this ThreadLocal and its
        // initial value as its only entry.
        T value = initialValue();
        createMap(t, value);
        return value;
    }

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Many applications will have no need for
     * this functionality, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current threads' copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     * @param map the map to store.
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    .......

    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

    ........

    }

}
 

 

可以看到ThreadLocal类中的变量只有这3个int型:

    private final int threadLocalHashCode = nextHashCode();  
    private static int nextHashCode = 0;  
    private static final int HASH_INCREMENT = 0x61c88647;  
 

 

而作为 ThreadLocal 实例的变量只有 threadLocalHashCode 这一个,nextHashCode 和 HASH_INCREMENT 是 ThreadLocal 类的静态变量,实际上 HASH_INCREMENT 是一个常量,表示了连续分配的两个 ThreadLocal 实例的 threadLocalHashCode 值的增量,而 nextHashCode 的表示了即将分配的下一个ThreadLocal实例的 threadLocalHashCode 的值。

可以来看一下创建一个ThreadLocal 实例即 new ThreadLocal() 时做了哪些操作,从上面看到构造函数 ThreadLocal() 里什么操作都没有,唯一的操作是这句:

 private final int threadLocalHashCode = nextHashCode();  

 那么nextHashCode()做了什么呢:

 

    private static synchronized int nextHashCode() {  
        int h = nextHashCode;  
        nextHashCode = h + HASH_INCREMENT;  
        return h;  
    }  
 

 

就是将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,然后nextHashCode的值增加HASH_INCREMENT这个值。

因此ThreadLocal实例的变量只有这个threadLocalHashCode,而且是final的,用来区分不同的 ThreadLocal实例,ThreadLocal类主要是作为工具类来使用,那么ThreadLocal.set()进去的对象是放在哪儿的呢?

看一下上面的set()方法,两句合并一下成为

ThreadLocalMap map = Thread.currentThread().threadLocals;  
 

 

这个ThreadLocalMap 类是ThreadLocal中定义的内部类,但是它的实例却用在Thread类中:

    public class Thread implements Runnable {  
        ......  
      
        /* ThreadLocal values pertaining to this thread. This map is maintained 
         * by the ThreadLocal class. */  
        ThreadLocal.ThreadLocalMap threadLocals = null;    
        ......  
    }  

 

再看这句:

    if (map != null)  
        map.set(this, value);  
 

也就是将该ThreadLocal实例作为key,要保持的对象作为值,设置到当前线程的ThreadLocalMap 中,get()方法同样大家看了代码也就明白了

 

 

八、Timer,TimerTask 详解

这两个类使用起来非常方便,可以完成我们对 定时器 的绝大多数需求
     Timer 类是用来执行任务的类,它接受一个 TimerTask 做参数
     Timer 有两种执行任务的模式,最常用的是 schedule ,它可以以两种方式执行任务:1:在某个时间(Data),2:在某个固定的时间之后(int delay).这两种方式都可以指定任务执行的频率.看个简单的例子:

 import java.io.IOException;
 import java.util.Timer;
 
  public class TimerTest {
       
      public static void main(String[] args){
         Timer timer = new Timer();
         timer.schedule(new MyTask(), 1000, 2000);//在1秒后执行此任务,每次间隔2秒,如果传递一个Data参数,就可以在某个固定的时间执行这个任务.
          while(true){//这个是用来停止此任务的,否则就一直循环执行此任务了
              try {
                 int ch = System.in.read();
                  if(ch-'c'==0){
                     timer.cancel();//使用这个方法退出任务
                    
                 }
              } catch (IOException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
             }
         }
     }
    
      static class MyTask extends java.util.TimerTask{
 
         @Override
          public void run() {
             // TODO Auto-generated method stub
             System.out.println("________");
         }
     }
 }

 

 
    如果你使用的是JDK 5+,还有一个 scheduleAtFixedRate 模式可以用,在这个模式下,Timer 会尽量的让任务在一个固定的频率下运行,举例说明:在上面的例子中,我们想让 MyTask 在1秒钟后,每两秒钟执行一次,但是因为 java 不是实时的(其实 java 实时性很差.....),所以,我们在上个程序中表达的原义并不能够严格执行.如果我们调用的是 scheduleAtFixedRate,那么,Timer 会尽量让你的 Task 执行的频率保持在2秒一次.运行上面的程序,假设使用的是 scheduleAtFixedRate,那么下面的场景就是可能的:1秒钟后,MyTask 执行一次,因为系统繁忙,之后的2.5秒后MyTask 才得以执行第二次,然后,Timer 记下了这个延迟,并尝试在下一个任务的时候弥补这个延迟,那么,1.5秒后,MyTask 将执行的三次."以固定的频率而不是固定的延迟时间去执行一个任务"
果然很方便吧^_^
下面给出一个复杂点的例子,其中告诉大家怎么退出单个TimerTask,怎么退出所有Task

package MyTimerTest;
 
 import java.io.IOException;
 import java.util.Timer;
 
 
  /*
  * 本类给出了使用Timer和TimerTaske的主要方法,其中包括定制任务,添加任务
  * 退出任务,退出定时器.
  * 因为TimerTask的status域是包级可访问的,所以没有办法在java.util.包外
  * 得到其状态,这对编程造成一些不便 .我们不能判断某个Task的状态了.
  *
  */
 
  public class TimerTest {
 
      public static void main(String[] args) {
         Timer timer = new Timer();
         MyTask myTask1 = new MyTask();
         MyTask myTask2 = new MyTask();
         myTask2.setInfo("myTask-2");
         timer.schedule(myTask1, 1000, 2000);
         timer.scheduleAtFixedRate(myTask2, 2000, 3000);
          while (true) {
              try {
                 byte[] info = new byte[1024];
                 int len = System.in.read(info);
                 String strInfo = new String(info, 0, len, "GBK");//从控制台读出信息
                  if (strInfo.charAt(strInfo.length() - 1) == ' ') {
                     strInfo = strInfo.substring(0, strInfo.length() - 2);
                 }
                  if (strInfo.startsWith("Cancel-1")) {
                     myTask1.cancel();//退出单个任务
                     // 其实应该在这里判断myTask2是否也退出了,是的话就应该break.但是因为无法在包外得到
                     // myTask2的状态,所以,这里不能做出是否退出循环的判断.
                  } else if (strInfo.startsWith("Cancel-2")) {
                     myTask2.cancel();
                  } else if (strInfo.startsWith("Cancel-All")) {
                     timer.cancel();//退出Timer
                     break;
                  } else {
                     // 只对myTask1作出判断,偷个懒^_^
                     myTask1.setInfo(strInfo);
                 }
              } catch (IOException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
             }
         }
     }
 
      static class MyTask extends java.util.TimerTask {
         String info = "^_^";
 
         @Override
          public void run() {
             // TODO Auto-generated method stub
             System.out.println(info);
         }
 
          public String getInfo() {
             return info;
         }
 
          public void setInfo(String info) {
             this.info = info;
         }
 
     }
 
 }
 

 

九、Java 作用域 (public、private、protected的作用域,以及不写时的区别)

 

public、private、protected的作用域,以及不写时的区别?


    public,protected,friendly,private的访问权限如下:

   

关键字   当前类 同一package
子孙类  其他package
public
protected     ×
friendly × ×
private
× × ×

 

 

 

 

 

 

 

 

 

 

 

 

 

 

   

 

     不写时默认为friendly

    public声明的变量及方法,表明在整个包内包外都可使用。
    private 声明的变量及方法,只在声明的类内可以使用。
    protected包外不可使用。包内可以使用。
   

    不使用关键字默认为包内使用。

 

 

 

 

十、HashMap,LinkedHashMap,TreeMap,HashTable的区别

 

     java为数据结构中的映射定义了一个接口java.util.Map;它有四个实现类,分别是HashMap Hashtable LinkedHashMap 和TreeMap


     Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允许值重复。


     Hashmap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。HashMap最多只允许一条记录的键为Null;允 许多条记录的值为 Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。


     Hashtable与 HashMap类似,它继承自Dictionary类,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。


     LinkedHashMap 保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排 序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢, 因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。

     LinkedHashMap 是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列,像连接池中可以应 用。


    TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。

一般情况下,我们用的最多的是HashMap,HashMap里面存入的键值对在取出的时候是随机的,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。在Map 中插入、删除和定位元素,HashMap 是最好的选择。
TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。


以下代码实例可以看出HashMap,LinkedHashMap,TreeMap的区别:


import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;


public class Ceshi {

    public static void main(String[] args) {

        //HashMap
        System.out.println("------HashMap无序输出------");
        HashMap hsMap = new HashMap();
        hsMap.put("3", "Value3");
        hsMap.put("1", "Value1");
        hsMap.put("2", "Value2");
        hsMap.put("b", "ValueB");
        hsMap.put("a", "ValueA");
        Iterator it = hsMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry e = (Map.Entry)it.next();
            System.out.println("Key: " + e.getKey() + "--Value: " + e.getValue());
        }

        //TreeMap
        System.out.println("------TreeMap按Key排序输出------");
        TreeMap teMap = new TreeMap();
        teMap.put("3", "Value3");
        teMap.put("1", "Value1");
        teMap.put("2", "Value2");
        teMap.put("b", "ValueB");
        teMap.put("a", "ValueA");
        Iterator tit = teMap.entrySet().iterator();
        while (tit.hasNext()) {
            Map.Entry e = (Map.Entry)tit.next();
            System.out.println("Key: " + e.getKey() + "--Value: " + e.getValue());
        }

        //LinkedHashMap
        System.out.println("--LinkedHashMap根据输入的顺序输出--");
        LinkedHashMap lhsMap = new LinkedHashMap();
        lhsMap.put("3", "Value3");
        lhsMap.put("1", "Value1");
        lhsMap.put("2", "Value2");
        lhsMap.put("b", "ValueB");
        lhsMap.put("a", "ValueA");
        Iterator lit = lhsMap.entrySet().iterator();
        while (lit.hasNext()) {
            Map.Entry e = (Map.Entry)lit.next();
            System.out.println("Key: " + e.getKey() + "--Value: " + e.getValue());
        }
    }

}

 执行结果为:
------HashMap无序输出------
Key: 3--Value: Value3
Key: 2--Value: Value2
Key: 1--Value: Value1
Key: b--Value: ValueB
Key: a--Value: ValueA
------TreeMap按Key排序输出------
Key: 1--Value: Value1
Key: 2--Value: Value2
Key: 3--Value: Value3
Key: a--Value: ValueA
Key: b--Value: ValueB
--LinkedHashMap根据输入的顺序输出--
Key: 3--Value: Value3
Key: 1--Value: Value1
Key: 2--Value: Value2
Key: b--Value: ValueB
Key: a--Value: ValueA


十一、DecimalFormat 详解

 

DecimalFormat 的主要功能

DecimalFormat 是 NumberFormat 的一个具体子类,用于格式化十进制数字。该类设计有各种功能,使其能够分析和格式化任意语言环境中的数,包括对西方语言、阿拉伯语和印度语数字的支持。它还支持不同类型的数,包括整数 (123)、定点数 (123.4)、科学记数法表示的数 (1.23E4)、百分数 (12%) 和金额 ($123)。所有这些内容都可以本地化。

DecimalFormat 包含一个模式 和一组符号

符号含义:


 0       一个数字

 #       一个数字,不包括 0

 .       小数的分隔符的占位符

 ,       分组分隔符的占位符

 ;       分隔格式。

 -       缺省负数前缀。

 %       乘以 100 和作为百分比显示

 ?       乘以 1000 和作为千进制货币符显示;用货币符号代替;如果双写,用

         国际货币符号代替。如果出现在一个模式中,用货币十进制分隔符代

         替十进制分隔符。

 X       前缀或后缀中使用的任何其它字符,用来引用前缀或后缀中的特殊字符。

 

 

例子:

        DecimalFormat df1 = new DecimalFormat("0.0");

        DecimalFormat df2 = new DecimalFormat("#.#");

        DecimalFormat df3 = new DecimalFormat("000.000");

        DecimalFormat df4 = new DecimalFormat("###.###");

       

        System.out.println(df1.format(12.34));//结果:12.3

        System.out.println(df2.format(12.34));//结果:12.3

        System.out.println(df3.format(12.34));//结果:012.340

        System.out.println(df4.format(12.34));//结果:12.34
       DecimalFormat format = new DecimalFormat("###,####.000");

       System.out.println(format.format(111111123456.1227222));//结果:1111,1112,3456.123

      

       Locale.setDefault(Locale.US);

       DecimalFormat usFormat = new DecimalFormat("###,###.000");

       System.out.println(usFormat.format(111111123456.1227222));//结果:111,111,123,456.123


      

       DecimalFormat addPattenFormat = new DecimalFormat();

       addPattenFormat.applyPattern("##,###.000");

       System.out.println(addPattenFormat.format(111111123456.1227));//结果:111,111,123,456.123

      

       DecimalFormat zhiFormat = new DecimalFormat();

       zhiFormat.applyPattern("0.000E0000");

       System.out.println(zhiFormat.format(10000));//结果:1.000E0004

       System.out.println(zhiFormat.format(12345678.345));//结果:1.235E0007

      

       DecimalFormat percentFormat = new DecimalFormat();

       percentFormat.applyPattern("#0.000%");

       System.out.println(percentFormat.format(0.3052222));//结果:30.522%
 
DecimalFormat myformat1 = new DecimalFormat("###,###.0000");//使用系统默认格式 
        System.out.println(myformat1.format(111111123456.12));//111,111,123,456.1200

        Locale.setDefault(Locale.US);
        DecimalFormat myformat2 = new DecimalFormat("###,###.0000");//使用美国的格式  
        System.out.println(myformat2.format(111111123456.12));

        //----------------------------also use applypattern------------------------------//  

        DecimalFormat myformat3 = new DecimalFormat();
        myformat3.applyPattern("##,###.000");
        System.out.println(myformat3.format(11112345.12345));
        //      -----------------控制指数输出-------------------------------------------------//  

        DecimalFormat myformat4 = new DecimalFormat();
        myformat4.applyPattern("0.000E0000");
        System.out.println(myformat4.format(10000));
        System.out.println(myformat4.format(12345678.345));
        //      ------------------百分数的输出-------------------------------------------//  
        /*     DecimalFormat是NumberFormat的一个子类,其实例被指定为特定的地区。因此,你可以使用NumberFormat.getInstance 指定一个地区, 
         然后将结构强制转换为一个DecimalFormat对象。文档中提到这个技术可以在大多情况下适用,但是你需要用try/catch 块包围强制转换以防转 
         换不能正常工作 (大概在非常不明显得情况下使用一个奇异的地区)。    */
        DecimalFormat myformat5 = null;
        try {
            myformat5 = (DecimalFormat)NumberFormat.getPercentInstance();
        } catch (ClassCastException e) {
            System.err.println(e);
        }
        myformat5.applyPattern("00.0000%");
        System.out.println(myformat5.format(0.34567));
        System.out.println(myformat5.format(1.34567));

        /*---------------------------------运行结果--------------------------------------- 
         D:\google>java DecimalFormatSample 
         111,111,123,456.1200 
         111,111,123,456.1200 
         11,112,345.123 
         1.000E0004 
         1.235E0007 
         34.5670% 
         134.5670% 
         */
 

 

如果使用具有多个分组字符的模式,则最后一个分隔符和整数结尾之间的间隔才是使用的分组大小。所以 "#,##,###,####" == "######,####" == "##,####,####"。

 

 

十二、Java 类排序(Comparable,Comparator)

两个问题:一、如果对类排序,一定要把实现什么接口。二、实现了这个接口,Java怎么知道一个类是否实现了某个接口。于是带着这个问题做了一翻查找。
对于类数组排序,调用Arrays.sort()即可,但是也只是对于基本类型的支持,如果对类进行排序,有如下两种方法:
方法一 ,该类一定要实现Comparable<T>接口,并且实现public int compareTo(T o);方法。比较结果大的返回1,相等返回0,小于返回-1。该接口实现了泛型,如果声明,则compareTo的参数则为Object。

 

实体类Student:

public class Student implements Comparable<Student>{
	private String name = null;
	private int age = 0;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString() {
		return String.format("Name=%s Age=%d", this.name, this.age);
	}
	@Override
	public int compareTo(Student o) {
		// 按名字排序
		return this.name.compareTo(o.getName());
	}
}
 

声明一个Student数组,并且调用Arrays.sort()进行排序,然后输出

 

Student[] stus = new Student[3];
stus[0] = new Student("Flowers", 12);
stus[1] = new Student("Boys", 13);
stus[2] = new Student("Zero", 21);
Arrays.sort(stus);
for(Student s : stus){
	System.out.println(s.toString());
}

/*
结果: 

Name=Boys Age=13
Name=Flowers Age=12
Name=Zero Age=21
*/

 

 

方法二 ,如果Student类并未实现Comparable<T>接口,则在调用Arrays.sort()时,要指定一个“比较器”,一个接口类Comparator<T>,所以使用时同时要写出实现int compare(T o1, T o2);方法的代码。调用代码如下:

 

Arrays.sort(stus, new Comparator<Student>(){
			@Override
			public int compare(Student o1, Student o2) {
				return o1.getName().compareTo(o2.getName());
			}
});

 

 

   对于集合的排列,如ArrayList等实现了Collection<T>接口,List<T>是继承于Collection<T>,所以实现List<T>的同样适用。集合类的排序主要是用Collections.sort方法,Collections和Collection是不一样的,前者是类,后者是接口
一般我们主要使用两个方法:

 

1. Collection.sort(List arg0);
这种是最简单的一种排序方法,只需要实现他的Comparable 接口及实现public int compareTo(Object arg0)方法即可。

ArrayList<Student> list = new ArrayList<Student>(3);
list.add(new Student("Flowers", 36));
list.add(new Student("Dog", 23));
list.add(new Student("About", 67));
Collections.sort(list);
 

2. Collection.sort(List arg0,Comparator arg1)

 

 

这种加入了比较器,具有更大的灵活性,便于管理,比较器可作为内部静态类的,以便于管理。比较器必须实现Comparator接口。

Collections.sort(list, new Comparator<Student>(){
	@Override
	public int compare(Student o1, Student o2) {
               // 按年龄排序
	       return o1.getAge() > o2.getAge()? 1:(o1.getAge() == o2.getAge()? 0: -1);
	}
});

/*
以上两种方法,得到的结果都一样:
Name=Dog Age=23
Name=Flowers Age=36
Name=About Age=67
*/
 

 

查看 Collection.sort 的源代码,不难看出 Java 的思路,先讲集合类转化为数组,然后调用 Arrays.sort 方法进行排序,同时传递过去比较器,最后利用集合的迭代器将结果赋值回集合类中。

public static <T> void sort(List<T> list, Comparator<? super T> c) {
	Object[] a = list.toArray();
	Arrays.sort(a, (Comparator)c);
	ListIterator i = list.listIterator();
	for (int j=0; j<a.length; j++) {
	    i.next();
	    i.set(a[j]);
	}
}
 

 

 

 

 

 

 

 

 

分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

    java实现二维数组转json的方法示例

    主要介绍了java实现二维数组转json的方法,涉及java数组遍历及json格式数据构造相关操作技巧,需要的朋友可以参考下

    Java相关课程系列笔记

    Java相关课程系列笔记之二Oracle学习笔记(建议用WPS打开) Java相关课程系列笔记之九Servlet学习笔记(建议用WPS打开) Java相关课程系列笔记之六HTML学习笔记(建议用WPS打开) Java相关课程系列笔记之七CSS学习...

    JAVA实验报告二Java面向对象程序设计.docx

    实验二Java面向对象程序设计 (1)定义表示课程的类Course,课程的属性包括课程名、编号、先修课号;方法包括设置课程名、设置编号、设置先修课号,以及打印课程名、课程号、先修课号。编写主程序,创建Course类的对象...

    java源码包---java 源码 大量 实例

     Java二进制IO类与文件复制操作实例,好像是一本书的例子,源代码有的是独立运行的,与同目录下的其它代码文件互不联系,这些代码面向初级、中级Java程序员。 Java访问权限控制源代码 1个目标文件 摘要:Java源码,...

    java,二次方程求解

    java,有关一元二次方程的求解,详细代码,已调试成功

    计算机程序设计(Java)-教案--单元二--Java语言开发基础.docx.docx

    计算机程序设计(Java)-教案--单元二--Java语言开发基础全文共8页,当前为第1页。计算机程序设计(Java)-教案--单元二--Java语言开发基础全文共8页,当前为第1页。课堂教学计划表 计算机程序设计(Java)-教案--...

    java 核心技术第九版卷一卷二 英文版

    全书共14章,包括Java基本的程序结构、对象与类、继承、接口与内部类、图形程序设计、事件处理、Swing用户界面组件、部署应用程序和Applet、异常日志断言...,从而帮助读者充分理解Java语言以及Java类型库的相关特性...

    java核心技术卷二(英文版mobi格式)

    《JAVA核心技术卷Ⅱ:高级特征》对Java技术的阐述精确到位,叙述方式深入浅出,并包含大量示例,从而帮助读者充分理解Java语言以及Java类库的相关特性。  《JAVA核心技术卷2:高级特征》适合软件开发人员、高等院校...

    JAVA_API1.6文档(中文)

    java.awt.geom 提供用于在与二维几何形状相关的对象上定义和执行操作的 Java 2D 类。 java.awt.im 提供输入方法框架所需的类和接口。 java.awt.im.spi 提供启用可以与 Java 运行时环境一起使用的输入方法开发的...

    java一维数组二维数组

    Java数组相关资料,部分题目。有兴趣的同学可以了解一下

    数据结构与问题求解Java语言

    本书的目的是从抽象思维和问题求解的观点提供对数据结构的实用介绍,试图包含有关数据结构、算法分析及其Java实现的所有重要的细节。作者采用了独特的方法将数据结构分成说明和实现两部分,并充分利用了已有的数据...

    java源码包4

     Java二进制IO类与文件复制操作实例,好像是一本书的例子,源代码有的是独立运行的,与同目录下的其它代码文件互不联系,这些代码面向初级、中级Java程序员。 Java访问权限控制源代码 1个目标文件 摘要:Java源码...

    线程 JAVA java线程 java线程第3版 java线程第2版第3版合集

    电子书相关:包含4个有关JAVA线程的电子书(几乎涵盖全部有关线程的书籍) OReilly.Java.Threads.3rd.Edition.Sep.2004.eBook-DDU Java Thread Programming (Sams) java线程第二版中英文 java线程第二版中英文 ...

    疯狂java讲义.第二版.part5.rar

    本书深入介绍了Java编程的相关方面,全书内容覆盖了Java的基本语法结构、Java的面向对象特征、Java集合框架体系、Java泛型、异常处理、Java GUI编程、JDBC数据库编程、Java注释、Java的IO流体系、Java多线程编程、...

    《Java程序设计第二版》ppt课件

    包括程序课件 及实验指导书 ...第二章 Java面向对象编程基础 第三章 常用工具 第四章 SWT图形用户界面 第五章 Java网络程序设计及相关技术 第六章 J2ME无线应用编程 第七章 Java数据库编程 第八章 JSP编程基础

    java源码包3

     Java二进制IO类与文件复制操作实例,好像是一本书的例子,源代码有的是独立运行的,与同目录下的其它代码文件互不联系,这些代码面向初级、中级Java程序员。 Java访问权限控制源代码 1个目标文件 摘要:Java源码...

    疯狂Java讲义第二版配套光盘源码

    的要求,读者可以参考相关案例来完成《疯狂Java讲义》的课后习题。 二、运行环境 本书中的程序在以下环境调试通过: (1) 安装jdk-7-windows-i586.exe,安装完成后,添加CLASSPATH环境变量,该环境变量 的值...

    125集专攻JAVA基础 JAVA零基础入门学习视频教程 动力节点JAVA视频教程.txt

    北京动力节点-Java编程零基础教程-028-Java语言概述-第一个程序-有关系统变量classpath.avi 北京动力节点-Java编程零基础教程-029-Java语言概述-第一个程序-Java程序的执行流程.avi 北京动力节点-Java编程零基础...

    JAVA上百实例源码以及开源项目

     Java二进制IO类与文件复制操作实例,好像是一本书的例子,源代码有的是独立运行的,与同目录下的其它代码文件互不联系,这些代码面向初级、中级Java程序员。 Java访问权限控制源代码 1个目标文件 摘要:Java源码,...

    java相关书籍:并发和effictive java

    该压缩包里含有两本pdf书籍:effictive java第二版和java并发实战,希望能对大家提升技术有所帮助。

Global site tag (gtag.js) - Google Analytics