`

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核心技术卷二

    针对这种情况,我们只能忽略这部分内容,而无法从这里提取与标题“java核心技术卷二”和描述“java核心技术第二卷 高级特性 提供给大家学习与交流”相关的知识点。由于缺少可读和可理解的内容,我们无法对这部分进行...

    国二java考试系统

    4. **文件操作**:在Java中,可以使用File类和相关的IO流来读写文件,考生需学会创建、读取、写入和删除文件。 5. **多线程**:Java支持多线程编程,考生需要了解Thread类和Runnable接口,以及同步机制如...

    疯狂java讲义第二版代码

    《疯狂Java讲义第二版》是一本深受程序员喜爱的Java编程教材,它的代码库涵盖了丰富的Java编程实践和设计模式应用。这本书深入浅出地讲解了Java语言的核心概念、语法以及面向对象编程思想,旨在帮助读者从初级到高级...

    二级java模拟软件

    软件包含了与二级Java考试相关的各种问题类型,能够全面检验考生的Java语言理解、程序设计和问题解决能力。 【标签】"二级Java"是指中国计算机技术职业资格认证指导中心组织的全国计算机等级考试中的第二级别Java...

    java二级考试操作题

    从提供的文件内容中,我们可以提取出多个知识点,这些知识点围绕Java编程语言和相关API的使用,以及Java二级考试中可能出现的操作题目。以下是详细的知识点说明: 1. Java Applet程序设计: - 文件中提到的`import...

    【博客 Java调用MATLAB,将文件转换为二进制流】java+matlab程序

    需要注意的是,Java代码需要正确配置MATLAB的运行环境,包括MCR路径和相关的系统属性设置。 4. **处理二进制流**:MATLAB转换后的二进制数据在Java中表现为字节数组,可以进一步处理,比如写入到文件、在网络上传输...

    经典算法问题的java实现<二>

    【标题】:“经典算法问题的Java实现&lt;二&gt;” 在这个主题中,我们将深入探讨Java编程语言在解决经典算法问题上的应用。算法是计算机科学的基础,它们是解决问题的步骤和方法,而Java作为一种强大的面向对象的语言,...

    java后端大华摄像头二次开发demo

    "java后端大华摄像头二次开发demo"是一个项目,旨在演示如何利用Java后端技术对接大华摄像头进行二次开发,以实现更高级的功能或者定制化需求。在这个过程中,开发者通常会用到大华提供的SDK(Software Development ...

    Head First Java.第二版.中文完整高清版.pdf

    6. 与现实世界的例子相结合:为了帮助读者更好地理解如何将Java应用于真实世界的问题,书中可能会提供与现实生活相关的案例研究和示例代码。 7. 最佳实践:在介绍基础和高级特性的同时,书中可能还会包含一些编写高...

    java中文乱码问题详解--- java中文乱码问题详解

    #### 二、Java中文问题的根本原因 Java中文问题的根本原因可以归纳为编码不一致或者编码转换错误。具体来说,可以从以下几个方面来理解: 1. **操作系统默认编码**:不同的操作系统默认支持不同的编码格式。例如,...

    6.openPlant JAVA 二次开发手册_openplant_openPlantjava_happyu91_java_ca

    《6.openPlant JAVA 二次开发手册》是针对OpenPlant软件的JAVA API进行深入解析的一份技术文档,由openplant、openPlantjava、happyu91等领域的专家共同编撰,旨在帮助开发者更好地理解和利用JAVA语言进行OpenPlant...

    JAVA面试问题,自己和别人总结

    JAVA面试问题总结 JAVA是一种广泛应用的编程语言,作为一名JAVA开发者,需要具备扎实的基础知识和实践经验。本文总结了常见的JAVA面试问题,涵盖了JAVA基础、JSP、Servlet、XML、J2EE、MVC、数据库等方面的知识点。...

    java面试问题个人总结

    ### Java面试问题个人总结 #### 一、面试整体流程 1. **简单的自我介绍** - 基本格式:我是xxxx, 工作xxx年。...通过以上总结,希望能够帮助大家更好地准备Java相关的面试,无论是基础知识还是面试技巧都有所提升。

    Java二级.zip

    2. **包**:包是Java中用于组织和命名空间管理的一组相关类的集合。例如,`java.util` 包含了许多实用工具类,如集合框架。掌握如何导入和创建自定义包能帮助我们更好地管理和重用代码。 3. **构造方法**:构造方法...

    疯狂java讲义第二版随书源代码光盘文件

    通过阅读和运行代码,读者可以更好地理解Java语言的特性和编程模式,提高解决问题的能力。此外,这些代码还可以作为项目开发的参考模板,帮助读者在实际工作中快速上手。 总的来说,《疯狂Java讲义》第二版的源代码...

    二级Java上机模拟软件

    "二级Java上机南开模拟软件"进一步明确了软件的来源和针对性,表明这是专门为国家二级Java上机考试设计的,与南开大学有关。"二级Java模拟软件"则概括了软件的整体性质,即提供Java编程的模拟测试。 【压缩包子文件...

    java实现二十四节气计算

    二十四节气是中国传统历法的重要组成部分,它反映了地球在公转轨道上的位置变化,与农业生产、气候、民俗活动等紧密相关。以下是对这个话题的详细阐述: 首先,我们需要了解二十四节气的基本概念和计算原理。二十四...

    java二级试题

    8. **JVM和内存管理**:"java06-9"可能涉及到JVM相关问题,比如内存模型、垃圾回收机制。考生需要理解堆、栈、方法区的概念,以及如何优化内存使用。 9. **泛型**:"java07-9"可能包含泛型题目。泛型提供了一种在...

    历年计算机二级java笔试试题及答案

    ### 历年计算机二级Java笔试知识点解析 #### 数据的存储结构定义 - **知识点**:数据的存储结构,指的是数据元素在计算机中的存储方式及其相互之间的逻辑关系的表现形式。它不仅包括数据元素本身占据的空间,还包括...

Global site tag (gtag.js) - Google Analytics