Guava | Closer & JDBC Closer

今早講師提到的一個point讓我想做下com.google.common.io.Closer對Connection,PrepareStatment和ResultSet的可關(guān)閉性測試,然而誤解了講師的意思....

1
2
3
/**
 * 測試Closer對Connection融撞,PrepareStatment和ResultSet的可關(guān)閉性
 *
 * 【測試結(jié)果:】
 */
@Test
public void testCloser() {
    Connection connection = DBUtil.getConnection();
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
    String sql = "SELECT id, book_id, author_id, book_name from t_books";

    Closer closer = Closer.create();
    closer.register(connection);

    // 數(shù)據(jù)查詢
    try {
        preparedStatement = connection.prepareStatement(sql);
        resultSet = preparedStatement.executeQuery();
        while (resultSet.next()) {
            logger.info(resultSet.getString("book_name"));
        }
    } catch (SQLException e) {
        logger.error("從結(jié)果集中獲取查詢結(jié)果異常", e);
    }

    try {
        // 調(diào)用前的關(guān)閉情況
        logger.info("調(diào)用前的關(guān)閉情況");
        logger.info("ResultSet =>> {}", resultSet.isClosed());
        logger.info("PreparedStatement =>> {}", preparedStatement.isClosed());
        logger.info("Connection =>> {}", connection.isClosed());

        // 調(diào)用Closer
        closer.close();

        // 調(diào)用后的關(guān)閉情況
        logger.info("調(diào)用后的關(guān)閉情況");
        logger.info("ResultSet =>> {}", resultSet.isClosed());
        logger.info("PreparedStatement =>> {}", preparedStatement.isClosed());
        logger.info("Connection =>> {}", connection.isClosed());
    } catch (IOException e) {
        logger.error("Closer執(zhí)行關(guān)閉異常", e);
    } catch (SQLException e) {
        logger.error("數(shù)據(jù)庫異常", e);
    }
}
4

1、兼容Java6和Java7:為拋出try塊中的異常而采取的不同的異常處理機(jī)制

為什么這么做

我們對異常處理的try-catch-finally語句塊都比較熟悉半哟。如果在try語句塊中拋出了異常绘证,在控制權(quán)轉(zhuǎn)移到調(diào)用棧上一層代碼之前撵割,finally語句塊中的語句也會執(zhí)行。但是finally語句塊在執(zhí)行的過程中跑杭,也可能會拋出異常铆帽。如果finally語句塊也拋出了異常,那么這個異常會往上傳遞德谅,而之前try語句塊中的那個異常就丟失了爹橱。

比如下面這個例子:

public class DisappearedException {
    public void show() throws BaseException {
        try {
            Integer.parseInt("Hello");
        } catch (NumberFormatException e1) {
            throw new BaseException(e1);
        } finally {
            try {
                int result = 2 / 0;
            } catch (ArithmeticException e2) {
                throw new BaseException(e2);
            }
        }
    }
    public static void main(String[] args) throws Exception {
        DisappearedException d = new DisappearedException();
        d.show();
    }
}

class BaseException extends Exception {
    public BaseException(Exception ex){
        super(ex);
    }
    private static final long serialVersionUID = 3987852541476867869L;
}

對這種問題的解決辦法一般有兩種:一種是拋出try語句塊中產(chǎn)生的原始異常,忽略在finally語句塊中產(chǎn)生的異常窄做。這么做的出發(fā)點(diǎn)是try語句塊中的異常才是問題的根源愧驱。如例:

public class ReadFile {
    public static void main(String[] args) {
        ReadFile rf = new ReadFile();
        try {
            rf.read("F:/manifest_provider_loophole.txt");
        } catch (BaseException2 e) {
            e.printStackTrace();
        }
    }
    public void read(String filename) throws BaseException2 {
        FileInputStream input = null;
        IOException readException = null;
        try {
            input = new FileInputStream(filename);
        } catch (IOException ex) {
            readException = ex;
        } finally {
            if(input != null){
                try {
                    input.close();
                } catch (IOException ex2) {
                    if(readException == null){
                        readException = ex2;
                    }
                }
            }
            if(readException != null){
                throw new BaseException2(readException);
            }
        }
    }
}

class BaseException2 extends Exception {
    private static final long serialVersionUID = 5062456327806414216L;
    public BaseException2(Exception ex){
        super(ex);
    }
}

另外一種是把產(chǎn)生的異常都記錄下來。這么做的好處是不會丟失任何異常浸策。在java7之前冯键,這種做法需要實(shí)現(xiàn)自己的異常類惹盼,而在java7中庸汗,已經(jīng)對Throwable類進(jìn)行了修改以支持這種情況。在java7中為Throwable類增加addSuppressed方法手报。當(dāng)一個異常被拋出的時候蚯舱,可能有其他異常因?yàn)樵摦惓6灰种谱。瑥亩鵁o法正常拋出掩蛤。這時可以通過addSuppressed方法把這些被抑制的方法記錄下來枉昏。被抑制的異常會出現(xiàn)在拋出的異常的堆棧信息中,也可以通過getSuppressed方法來獲取這些異常揍鸟。這樣做的好處是不會丟失任何異常兄裂,方便開發(fā)人員進(jìn)行調(diào)試。這種做法的關(guān)鍵在于把finally語句中產(chǎn)生的異常通過 addSuppressed方法加到try語句產(chǎn)生的異常中阳藻。

public class ReadFile2 {
    public static void main(String[] args) {
        ReadFile rf = new ReadFile();
        try {
            rf.read("F:/manifest_provider_loophole.txt");
        } catch (BaseException2 e) {
            e.printStackTrace();
        }
    }
    public void read(String filename) throws IOException {
        FileInputStream input = null;
        IOException readException = null;
        try {
            input = new FileInputStream(filename);
        } catch (IOException ex) {
            readException = ex;
        } finally {
            if(input != null){
                try {
                    input.close();
                } catch (IOException ex2) {
                    if(readException != null){
                        readException.addSuppressed(ex2);    //注意這里
                    }else{
                        readException = ex2;
                    }
                }
            }
            if(readException != null){
                throw readException;
            }
        }
    }
}
  • Running on Java 7, code using this should be approximately equivalent in behavior to the same code written with try-with-resources.

    如果在Java7上運(yùn)行晰奖,希望代碼使用try-with-resources結(jié)構(gòu),那么就要使用try-with-resources語法來寫腥泥。

    Running on Java 6, exceptions that cannot be thrown must be logged rather than being added to the thrown exception as a suppressed exception.

    如果在Java6上運(yùn)行匾南,對不能拋出的異常的處理,將其記錄總比將其作為被屏蔽的異常要好蛔外。

  • If a {@code Throwable} is thrown in the try block, no exceptions that occur when attempting to close resources will be thrown from the finally block. The throwable from the try block will be thrown.

    如果在try塊中拋出了異常蛆楞,那么這個異常將被拋出,然而在finally塊中關(guān)閉資源時將不會拋出任何異常夹厌。

  • If no exceptions or errors were thrown in the try block, the <i>first</i> exception thrown by an attempt to close a resource will be thrown.

    如果try塊中沒有發(fā)生異常豹爹,那么第一個拋出的異常可能是嘗試關(guān)閉資源的異常矛纹。

  • Any exception caught when attempting to close a resource that is not thrown (because another exception is already being thrown) is suppressed.

  • An exception that is suppressed is not thrown. The method of suppression used depends on the version of Java the code is running on:

    被屏蔽的異常將不會被拋出臂聋,屏蔽異常的方法將取決于代碼運(yùn)行的Java版本:

    Java 7+: Exceptions are suppressed by adding them to the exception that will be thrown using {@code Throwable.addSuppressed(Throwable)}.

    如果是在Java7+上運(yùn)行:通過Throwable.addSuppressed(Throwable)方法將這個異常屏蔽。

    Java 6: Exceptions are suppressed by logging them instead.

    如果是在Java6上運(yùn)行:將其輸出到日志。

所以在Closer內(nèi)部逻住,有自己的異常處理機(jī)制钟哥,而且這種機(jī)制取決于代碼所處的Java版本環(huán)境。
在具體實(shí)現(xiàn)上瞎访,Closer在初始化的時候就對異常處理機(jī)制也做了初始化的選擇:

public static Closer create() {
    return new Closer(SUPPRESSOR);
}

SUPPRESSOR是一個內(nèi)部接口Suppressor的實(shí)現(xiàn)類腻贰,具體用哪個實(shí)行A類取決于當(dāng)前的Java版本。SUPPRESSOR被做了靜態(tài)初始化:

/**
 * The suppressor implementation to use for the current Java version.
 */
private static final Suppressor SUPPRESSOR =
      SuppressingSuppressor.isAvailable()
          ? SuppressingSuppressor.INSTANCE
          : LoggingSuppressor.INSTANCE;

看一看Suppressor接口的定義:

  /**
   * Suppression strategy interface.
   */
  @VisibleForTesting
  interface Suppressor {
    /**
     * Suppresses the given exception ({@code suppressed}) which was thrown when attempting to close
     * the given closeable. {@code thrown} is the exception that is actually being thrown from the
     * method. Implementations of this method should not throw under any circumstances.
     */
    void suppress(Closeable closeable, Throwable thrown, Throwable suppressed);
  }

這個接口被叫做“屏蔽策略接口(Suppression strategy interface)”扒秸,也即根據(jù)Java版本來采取具體的屏蔽策略播演,而實(shí)現(xiàn)了這個接口的兩個實(shí)現(xiàn)類LoggingSuppressor和SuppressingSuppressor正對應(yīng)了兩種不同的屏蔽策略。

  • 第一個實(shí)現(xiàn)類:SuppressingSuppressor.
    這個實(shí)現(xiàn)類適用于屏蔽Java7中的異常伴奥,根據(jù)能否通過反射獲取到Throwable的addSuppressed方法來判斷這種策略的可用性写烤,而這個判斷也被用于初始化Closer時Suppressor具體策略類選擇的判斷依據(jù)。
    在Throwable中定義了一個private List<Throwable> suppressedExceptions拾徙,而addSuppressed方法就是將異常加入到這個List當(dāng)中洲炊。這個方法只有在Java7+當(dāng)中提供,參考:Java7的異常處理新特性 - OPEN 開發(fā)經(jīng)驗(yàn)庫

    /**
     * Suppresses exceptions by adding them to the exception that will be thrown using JDK7's
     * addSuppressed(Throwable) mechanism.
     */
    @VisibleForTesting
    static final class SuppressingSuppressor implements Suppressor {
    
      static final SuppressingSuppressor INSTANCE = new SuppressingSuppressor();
    
      static boolean isAvailable() {
        return addSuppressed != null;
      }
    
      static final Method addSuppressed = getAddSuppressed();
    
      private static Method getAddSuppressed() {
        try {
          return Throwable.class.getMethod("addSuppressed", Throwable.class);
        } catch (Throwable e) {
          return null;
        }
      }
    
      @Override
      public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) {
        // ensure no exceptions from addSuppressed
        if (thrown == suppressed) {
          return;
        }
        try {
          addSuppressed.invoke(thrown, suppressed);
        } catch (Throwable e) {
          // if, somehow, IllegalAccessException or another exception is thrown, fall back to logging
          LoggingSuppressor.INSTANCE.suppress(closeable, thrown, suppressed);
        }
      }
    }
    
  • 第二個實(shí)現(xiàn)類是LoggingSuppressor尼啡,是將被屏蔽的異常以日志的方式輸出暂衡,適用于Java6:

    /**
     * Suppresses exceptions by logging them.
     */
    @VisibleForTesting
    static final class LoggingSuppressor implements Suppressor {
    
      static final LoggingSuppressor INSTANCE = new LoggingSuppressor();
    
      @Override
      public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) {
        // log to the same place as Closeables
        Closeables.logger.log(
            Level.WARNING, "Suppressing exception thrown when closing " + closeable, suppressed);
      }
    }
    
總結(jié):
  • 基于接口的、針對不同環(huán)境的異常處理策略的實(shí)現(xiàn)崖瞭。

2狂巢、基于Stack的資源收集和關(guān)閉

(1) 初始化Stack

Closer中定義了一個Stack,用來收集待關(guān)閉的資源书聚,需要注意的是唧领,這些資源都 實(shí)現(xiàn)自Closeable接口,也就是說雌续,只有實(shí)現(xiàn)自這個接口的資源才能被Closer收集和關(guān)閉斩个。

// only need space for 2 elements in most cases, so try to use the smallest array possible
  private final Deque<Closeable> stack = new ArrayDeque<Closeable>(4);

stack初始容量為4,根據(jù)注釋:一般情況下西雀,需要2個大小的空間就足夠了(需要關(guān)閉的資源數(shù)目一般不會超過2)萨驶,這里use the smallest array possible.

(2) register

在棧頂加入一個帶關(guān)閉資源closeable:

  /**
   * Registers the given {@code closeable} to be closed when this {@code Closer} is
   * {@linkplain #close closed}.
   *
   * @return the given {@code closeable}
   */
  // close. this word no longer has any meaning to me. <<-- 你是想說這里不會發(fā)生close嗎?任性...
  @CanIgnoreReturnValue
  public <C extends Closeable> C register(@Nullable C closeable) {
    if (closeable != null) {
      stack.addFirst(closeable);
    }

    return closeable;
  }
(3) close

關(guān)閉stack中的所有已經(jīng)注冊的資源艇肴,注意順序是LIFO腔呜,后加入的先關(guān)閉,先加入的后關(guān)閉:

  /**
   * Closes all {@code Closeable} instances that have been added to this {@code Closer}. If an
   * exception was thrown in the try block and passed to one of the {@code exceptionThrown} methods,
   * any exceptions thrown when attempting to close a closeable will be suppressed. Otherwise, the
   * <i>first</i> exception to be thrown from an attempt to close a closeable will be thrown and any
   * additional exceptions that are thrown after that will be suppressed.
   */
  @Override
  public void close() throws IOException {
    Throwable throwable = thrown;

    // close closeables in LIFO order
    while (!stack.isEmpty()) {
      Closeable closeable = stack.removeFirst();
      try {
        closeable.close();
      } catch (Throwable e) {
        if (throwable == null) {
          throwable = e;
        } else {
          suppressor.suppress(closeable, throwable, e);
        }
      }
    }

    if (thrown == null && throwable != null) {
      Throwables.propagateIfPossible(throwable, IOException.class);
      throw new AssertionError(throwable); // not possible
    }
  }

注意這里的異常處理再悼,正體現(xiàn)了開頭注釋中所講的那樣:如果try塊中核畴,也就是close時發(fā)生了IO異常,那么這個異常就會被拋出冲九;如果try塊中沒有發(fā)生異常谤草,那么第一個拋出的異掣可能是嘗試關(guān)閉資源的異常。

3丑孩、參考Closer封裝JDBC Closer

下面根據(jù)上面對Closer的分析冀宴,封裝一個用于關(guān)閉JDBC中資源的Closer,注意:

  • JDBC中需要關(guān)閉的資源有:Connection温学、PrepareStatment和Resultset略贮,他們的關(guān)閉順序是上述逆序,而他們都實(shí)現(xiàn)自AutoCloseable接口仗岖;

  • 不同于Closer的是逃延,考慮到JDBC數(shù)據(jù)庫操作的特點(diǎn),JdbcCloser中沒有復(fù)雜的異常處理機(jī)制轧拄,所有異常都會被直接拋出揽祥。

      package com.qunar.fresh2017.db;
      
      import com.google.common.base.Preconditions;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      
      import java.util.ArrayDeque;
      import java.util.Deque;
      
      /**
       * Copyright(C),2005-2017,Qunar.com
       * version    date      author
       * ──────────────────────────────────
       * 1.0       17-3-12   wanlong.ma
       * Description:參考 com.google.common.io.Closer 的Jdbc連接關(guān)閉類
       *
       * Others:
       * Function List:
       * History:
       */
      public class JdbcCloser implements AutoCloseable {
          private static Logger LOGGER = LoggerFactory.getLogger(JdbcCloser.class);
      
          private JdbcCloser(){}
      
          public static JdbcCloser create(){
              return new JdbcCloser();
          }
      
          // 一般JDBC需要關(guān)閉的資源只有3個,所以這里初始化容量為3
          private static Deque<AutoCloseable> stack = new ArrayDeque<>(3);
      
          /**
           * 收集待關(guān)閉的數(shù)據(jù)庫資源到Stack中檩电,并返回這個資源
           * @param autoCloseable
           * @param <A>
           * @return
           */
          public <A extends AutoCloseable> A register(A autoCloseable){
              Preconditions.checkNotNull(autoCloseable);
              if(autoCloseable != null){
                  stack.addFirst(autoCloseable);
              }
              return autoCloseable;
          }
      
          /**
           * 以LIFO的順序從Stack中關(guān)閉資源
           * @throws Exception
           */
          @Override
          public void close() {
              do{
                  AutoCloseable ac = stack.removeFirst();
                  try {
                      ac.close();
                  } catch (Exception e) {
                      LOGGER.error("關(guān)閉時發(fā)生異常",e);
                  }
              }while (!stack.isEmpty());
          }
      }
    

測試類:

    package com.qunar.fresh2017;
    
    import com.qunar.fresh2017.db.DBUtil;
    import com.qunar.fresh2017.db.JdbcCloser;
    import junit.framework.TestCase;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * Copyright(C),2005-2017,Qunar.com
     * version    date      author
     * ──────────────────────────────────
     * 1.0       17-3-12   wanlong.ma
     * Description:
     * Others:
     * Function List:
     * History:
     */
    public class JdbcCloserTest extends TestCase {
        public void testJdbcCloser() throws SQLException {
            String sql = "SELECT * FROM t_books";
            Logger logger = LoggerFactory.getLogger(JdbcCloserTest.class);
    
            JdbcCloser jdbcCloser = JdbcCloser.create();
    
            Connection connection = jdbcCloser.register(DBUtil.getConnection());
            PreparedStatement preparedStatement = jdbcCloser.register(connection.prepareStatement(sql));
            ResultSet resultSet = jdbcCloser.register(preparedStatement.executeQuery());
    
            logger.info("ResultSet是否關(guān)閉:{}",resultSet.isClosed());
            logger.info("PreparedStatement是否關(guān)閉:{}",preparedStatement.isClosed());
            logger.info("Connection是否關(guān)閉:{}",connection.isClosed());
    
            jdbcCloser.close();
    
            logger.info("ResultSet是否關(guān)閉:{}",resultSet.isClosed());
            logger.info("PreparedStatement是否關(guān)閉:{}",preparedStatement.isClosed());
            logger.info("Connection是否關(guān)閉:{}",connection.isClosed());
        }
    }

運(yùn)行結(jié)果:

ResultSet是否關(guān)閉:false
PreparedStatement是否關(guān)閉:false
Connection是否關(guān)閉:false

ResultSet是否關(guān)閉:true
PreparedStatement是否關(guān)閉:true
Connection是否關(guān)閉:true
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拄丰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子是嗜,更是在濱河造成了極大的恐慌愈案,老刑警劉巖挺尾,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鹅搪,死亡現(xiàn)場離奇詭異,居然都是意外死亡遭铺,警方通過查閱死者的電腦和手機(jī)丽柿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來魂挂,“玉大人甫题,你說我怎么就攤上這事⊥空伲” “怎么了坠非?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長果正。 經(jīng)常有香客問我炎码,道長,這世上最難降的妖魔是什么秋泳? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任潦闲,我火速辦了婚禮,結(jié)果婚禮上迫皱,老公的妹妹穿的比我還像新娘歉闰。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布和敬。 她就那樣靜靜地躺著凹炸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪昼弟。 梳的紋絲不亂的頭發(fā)上还惠,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機(jī)與錄音私杜,去河邊找鬼蚕键。 笑死,一個胖子當(dāng)著我的面吹牛衰粹,可吹牛的內(nèi)容都是我干的锣光。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼铝耻,長吁一口氣:“原來是場噩夢啊……” “哼誊爹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瓢捉,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤频丘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后泡态,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搂漠,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年某弦,在試婚紗的時候發(fā)現(xiàn)自己被綠了桐汤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡靶壮,死狀恐怖怔毛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情腾降,我是刑警寧澤拣度,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站螃壤,受9級特大地震影響抗果,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜映穗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一窖张、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蚁滋,春花似錦宿接、人聲如沸赘淮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽梢卸。三九已至,卻和暖如春副女,著一層夾襖步出監(jiān)牢的瞬間蛤高,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工碑幅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留戴陡,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓沟涨,卻偏偏與公主長得像恤批,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子裹赴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容

  • ?try-with-resources語句是一個聲明一個或多個資源的 try 語句喜庞。一個資源作為一個對象,必須在程...
    xdoyf閱讀 3,464評論 0 0
  • 初識異常(Exception) 比如我們在取數(shù)組里面的某個值得時候棋返,經(jīng)常會出現(xiàn)定義的取值范圍超過了數(shù)組的大小延都,那么...
    iDaniel閱讀 1,870評論 1 2
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法睛竣,內(nèi)部類的語法晰房,繼承相關(guān)的語法,異常的語法酵颁,線程的語...
    子非魚_t_閱讀 31,639評論 18 399
  • 前言 自學(xué)了4個多月的安卓嫉你,試著寫了一個小程序,雖然功能按照預(yù)想基本實(shí)現(xiàn)了躏惋,但是也很清楚代碼質(zhì)量肯定不好。在...
    maxwellyue閱讀 59,686評論 2 16
  • 時間流逝嚷辅,78年出生的我簿姨,人已過半百,從出生到2015年每天在茫茫然中度過簸搞。 夢想和我好像從未接觸過扁位,目標(biāo)似乎與我...
    胡芳葉夢之藍(lán)閱讀 1,357評論 0 0