Original on www.norvig.com

Java якасці паветра ў памяшканні:
Нячаста адказаў на пытанні

Peter Norvig


Пыт: Што такое нячаста адказаў на пытанне?

Пытанне нярэдка адказала або таму, што мала хто ведае адказ ці таму, што гаворка ідзе пра малавядомы, тонкі момант (але кропкі, якія могуць мець вырашальнае значэнне для вас). Я думаў, што вынайшаў гэты тэрмін, але ён таксама з'яўляецца на вельмі інфарматыўны About.com Гарадскія легенды сайце. Ёсць шмат Java Пытанні і адказы па крузе, але гэта адзіны Java якасці паветра ў памяшканні. (Ёсць некаторыя нячаста задаваныя пытанні спісы, у тым ліку сатырычных адзін на C.)

Пыт: У кодзе, нарэшце, становішча ніколі не атрымаецца выканаць, ці не так?

Ну, ці наўрад калі-небудзь. Але вось прыклад, калі, нарэшце, код не будзе выконвацца, незалежна ад кошту лагічны выбар:
  try {
    if (choice) {
      while (true) ;
    } else {
      System.exit(1);
    }
  } finally {
    code.to.cleanup();
  }


Пыт: У рамках метаду м у класе C, не this.getClass () заўсёды C?

Не, гэта выключана, што для некаторага аб'екта х, што з'яўляецца прыкладам некаторых падклас C1 З ці няма C1.m () метад, ці іншым спосабам на х завецца super.m (). У любым выпадку, this.getClass () С1, а не C у целе Cm (). Калі З з'яўляецца канчатковым, то ўсё ў парадку.

Пыт: Я вызначыў метад equals, але Hashtable, ігнаруе яго. Чаму?

equals метады на здзіўленне цяжка атрымаць правы. Тут месцы паглядзець на першае пытанне:
  1. Вы вызначылі няправільна роўна метаду. Напрыклад, Вы пісалі:
     
    public class C {
      public boolean equals(C that) { return id(this) == id(that); }
    }

    Але для таго каб table.get (с) для працы трэба зрабіць equals метад прыняць аб'ект у якасці аргументу, а не C:

     
    public class C {
      public boolean equals(Object that) { 
        return (that instanceof C) && id(this) == id((C)that); 
      } 
    }

    Чаму? Код для Hashtable.get выглядае наступным чынам:

     
    public class Hashtable {
      public Object get(Object key) {
        Object entry;
        ...
        if (entry.equals(key)) ...
      }
    }

    У наш час метад выклікаецца entry.equals (ключ), залежыць ад фактычнага часу выканання тып аб'екта, на які высылаецца запіс, і заявіў, падчас кампіляцыі тыпу зменнай ключа. Такім чынам, калі вы, як заклік table.get карыстачоў (у новым C (...)), гэта выглядае ў класе C для equals метад з аргументам тыпу аб'екта. Калі вам пашчасціла мець вызначаны роўна метад з аргументам тыпу C, гэта irrelevent. Ён ігнаруе гэты метад, і шукае метад з подпісам роўна (Object), у выніку знайшоў Object.equals (Object). Калі вы жадаеце ездзіць па-метад, вы павінны супадаць тыпы аргументаў сапраўды. У некаторых выпадках вы можаце ёсць два спосабу, так што вы не плаціце накладных ліцця, калі вы ведаеце, што аб'ектам права клас:

    public class C {
      public boolean equals(Object that) {
        return (this == that) 
                || ((that instanceof C) && this.equals((C)that)); 
      }
     
      public boolean equals(C that) { 
        return id(this) == id(that); // Or whatever is appropriate for class C
      } 
    }
    
  2. Ты не належным чынам ажыццяўляць роўна як прэдыкат роўнасці: роўна павінен быць сіметрычным, транзітыўнае і рэфлексіўнае. Сіметрычныя сродкаў a.equals (б) павінна мець то ж значэнне, b.equals (а). (Гэта адзін самых людзі заблыталіся.) Пераходныя азначае, што калі a.equals (б) і b.equals (з), то a.equals (з) павінна быць праўдзівым. Рэфлексіўныя азначае, што a.equals (а) павінна быць праўдзівым, і з'яўляецца чыннікам (гэта == што) тэст вышэй (гэта таксама часцяком добрая практыка, каб уключыць гэта з-за меркаванняў эфектыўнасці: тэставанне на == хутчэй, чым гледзячы на ўсе слоты аб'екта, а таксама часткова разарваць рэкурсіі праблемы на аб'ектах, якія могуць мець кругавы паказальнік сеткі).
  3. Вы забыліся хэш-код метаду. У любы час вы вызначыць equals метад, вы павінны таксама вызначыць метадам хэш-код. Вы павінны пераканацца, што дзве роўныя аб'екты маюць аднолькавы хэш-код, і калі вы жадаеце лепш хэш прадукцыйнасці, вы павінны паспрабаваць зрабіць большасць не роўныя аб'екты маюць розныя hashCodes. Некаторыя класы кэш хэш-код у дзелі слот аб'екта, так што неабходна быць вылічана толькі адзін раз. Калі гэта так, то вам, верагодна, зэканоміць час пры equals калі вы ўключыце радок, якая кажа, што калі (this.hashSlot! = That.hashSlot) вярнуцца false.
  4. Вы не займаўся спадчына належным чынам. Першым чынам, падумайце, калі два аб'ектаў рознага класа могуць быць роўнымі. Перад тым як сказаць "Няма! Вядома, не!" Разглядаецца клас прастакутнік з шырынёй і вышынёй палі, і клас Box, якая мае два палі вышэй плюс глыбіні. Ёсць Box з глыбінёй == 0 у памеры, эквівалентным прастакутнік? Магчыма, вы захочаце, каб сказаць "ды". Калі вы маеце справу з не-выпускным класе, то цалкам магчыма, што ваш клас можа быць падкласы, і вы жадаеце быць добрым грамадзянінам, у стаўленні Вашага падкласа. У прыватнасці, вы жадаеце, каб пашыральнік вашага класа C выкарыстоўваць C.equals метадам супер наступным чынам:
     
    public class C2 extends C {
     
      int newField = 0;
     
      public boolean equals(Object that) {
        if (this == that) return true;
        else if (!(that instanceof C2)) return false;
        else return this.newField == ((C2)that).newField && super.equals(that);
      }
     
    } 

    Каб гэта працавала, вы павінны быць асцярожныя, як вы ставіцеся класаў у вызначэнні C.equals. Напрыклад, праверце, што instanceof C, а не that.getClass () == C.class. Гл. папярэдняе пытанне якасці паветра ў памяшканні, каб пазнаць, чаму. Выкарыстанне this.getClass () == that.getClass (), калі вы ўпэўнены, што два аб'екта павінны быць таго ж класа, каб лічыцца роўнымі.

  5. Вы не апрацоўваць цыклічныя спасылкі правільна. Падумайце:
    public class LinkedList {
     
      Object contents;
      LinkedList next = null;
     
      public boolean equals(Object that) {
        return (this == that) 
          || ((that instanceof LinkedList) && this.equals((LinkedList)that)); 
      }
     
      public boolean equals(LinkedList that) { // Buggy!
       return Util.equals(this.contents, that.contents) &&
              Util.equals(this.next, that.next); 
      }
     
    } 

    Тут у мяне ёсць мяркуецца, Util класа:

     
      public static boolean equals(Object x, Object y) {
        return (x == y) || (x != null && x.equals(y));
      } 

    Я жадаю гэты метад у аб'ект, без яго вы заўсёды павінны кідаць у цестах супраць нулявой. Ва ўсякім разе, метад LinkedList.equals ніколі не вернецца, калі прапануецца параўнаць два LinkedLists з кругавой спасылкі ў іх (паказальнік ад аднаго элемента злучанага спісу да іншага элемента). Гл. апісанне функцыі Lisp Агульны спіс даўжыні для тлумачэння таго, як гэта праблема вырашаецца за лінейны час толькі два словы дадатковых Storge. (Я не дае адказу тут, у выпадку, калі вы жадаеце паспрабаваць, каб зразумець гэта для сябе ў першую чаргу.)


Пыт: Я паспрабаваў накіраваць метад супер, але гэта часам не працуе. Чаму?

Гэты код у пытанні, спрошчаны для дадзенага прыкладу:
 
/** A version of Hashtable that lets you do
 * table.put("dog", "canine");, and then have
 * table.get("dogs") return "canine". **/
 
public class HashtableWithPlurals extends Hashtable {
 
  /** Make the table map both key and key + "s" to value. **/
  public Object put(Object key, Object value) {
    super.put(key + "s", value);
    return super.put(key, value);
  }) 

Вы павінны быць асцярожныя пры пераходзе да супер, што вы ў поўнай меры зразумець, што супер метад. У гэтым выпадку дамова на Hashtable.put тым, што яна будзе запісваць адпаведнасць паміж ключом і значэннем у табліцы. Аднак, калі хэш становіцца занадта поўнай, то Hashtable.put вылучыць шырэйшы круг для табліцы, скапіяваць усе старыя аб'екты больш, а затым рэкурсіўна паўторнага выкліку table.put (ключ, значэнне). Зараз, бо Java вырашае метады, заснаваныя на часе выканання тып мэты, у нашым прыкладзе гэта рэкурсіўны выклік у кодзе для Hashtable пойдзе на HashtableWithPlurals.put (ключ, значэнне), а канчатковым вынікам з'яўляецца тое, што час ад часу (калі памер табліцы перапаўнення ў самы непадыходны момант), вы атрымаеце запіс "dogss", а таксама для "сабак" і "сабака". Цяпер гэта дзяржава дзе-небудзь у дакументацыі пакласці, што рабіць гэта рэкурсіўны выклік магчымасць? № У падобных выпадках, ён упэўнены, дапамагае мець зыходны код доступу да JDK.


Пыт: Чаму ўласцівасці аб'екта ігнараваць па змаўчанні, калі я атрымаю?

Вы не павінны рабіць патрапіць на ўласцівасці аб'екта, вы павінны рабіць, а не деьргорегьу. Шматлікія людзі лічаць, што з той толькі розніцай, што мае деьргорегьу заявіў які вяртаецца тып String, а атрымаць абвешчаны вяртанні аб'екта. Але насамрэч ёсць вялікая розніца: деьргорегьу глядзіць на значэнні па змаўчанні. Атрымаць па спадчыне ад Hashtable, і ён ігнаруе па змаўчанні, што робіць менавіта тое, што апісана ў Hashtable класа, але, верагодна, не тое, што вы чакаеце. Іншыя метады, якія ўспадкаваны ад Hashtable (як IsEmpty і ToString) таксама будзе ігнараваць значэнні па змаўчанні. Прыклад кода:
Properties defaults = new Properties();
defaults.put("color", "black");
 
Properties props = new Properties(defaults);
 
System.out.println(props.get("color") + ", " + 
props.getProperty(color));
// This prints "null, black"

Ці з'яўляецца гэта апраўдана дакументацыю? Можа быць. Дакументацыі ў Hashtable распавядае пра запіс у табліцы, а таксама паводзіны ўласцівасцяў з'яўляецца ўзгодненым, калі вы мяркуеце, што не defauls запісаў у табліцы. Калі па нейкіх чынніках вы думаеце па змаўчанні былі запісы (як гэта можа быць прымусілі паверыць паводзінамі), то вам будзе збіты з толку.


Пыт: Успадкоўванне здаецца памылак. Як я магу абараніцца ад гэтых памылак?

Папярэднія два пытання паказваюць, што праграміст neeeds быць вельмі асцярожнымі пры пашырэнні класа, а часам і проста ў выкарыстанні клас, які пашырае іншага класа. Такія праблемы, як гэтыя два прывесці Джон Остераут сказаць: "Ажыццяўленне ўспадкоўвання выклікае такое ж перапляценне і далікатнасці, якія назіраліся пры доьо празмернае выкарыстанне. У выніку, О. сістэмы часта пакутуюць ад складанасці і адсутнасці паўторнага выкарыстання". (Сцэнары, IEEE Computer, сакавік 1998) і Эдзгер Дэйкстра да нібы сказаў "аб'ектна-арыентаванага праграмавання з'яўляецца вылучна дрэннай ідэяй, якая магла быць толькі паўстала ў Каліфорніі." (Ад збору подпісаў файлаў). Я не думаю, што ёсць агульнага спосаб застрахаваць быць бяспечны, але Ёсць некалькі рэчаў, каб быць у курсе:
  • Пашырэнне класа, што вы не маеце зыходны код заўсёды рызыкоўна; дакументацыя можа быць няпоўным такім чынам, вы не можаце прадбачыць.
  • Выклік супер імкнецца, каб гэтыя праблемы неспадзяваных выскачыць.
  • Вы павінны надаваць гэтулькі ж увагі на метады, якія вы не больш чым паездкай, як метады, якія вы робіце. Гэта адно з вялікіх памылак аб'ектна-арыентаванага праектавання з выкарыстаннем успадкоўвання. Гэта праўда, што ўспадкоўванне дазваляе пісаць менш кода. Але вам усё адно прыйдзецца думаць пра код, вы не пішаце.
  • Ты адмыслова шукаў непрыемнасцяў, калі падклас змены кантракт з любым з метадаў, ці класа ў цэлым. Цяжка сказаць, калі дамова зменены, паколькі кантракты з'яўляюцца нефармальнымі (ёсць афіцыйны ўдзел у подпіс тыпу, а астатнія з'яўляецца толькі ў каментарах). У гэтым прыкладзе ўласцівасці, не ясна, калі дамову была парушана, бо не ясна, калі па змаўчанні лічацца "запісы" ў табліцы ці няма.

Пыт: Якія альтэрнатывы спадчына?

Дэлегацыя з'яўляецца альтэрнатывай успадкоўвання. Дэлегацыя азначае, што вы ўключыць асобнік іншага класа, як зменнай асобніка, а таксама перасылаць паведамленні інстанцыі. Вельмі часта бяспечней, чым спадчыны, паколькі яна прымушае вас думаць пра кожны паведамленне наперад, бо асобнік вядомага класа, а не новага класа, таму што ён не прымусіць вас прыняць усе метады суперкласса : вы можаце забяспечыць толькі метады, якія сапраўды маюць сэнс. З іншага боку, яна прымушае вас пісаць больш кода, і гэта цяжэй, паўторнае выкарыстанне (таму што ён не з'яўляецца падкласам).

Для прыкладу HashtableWithPlurals, дэлегацыя жадала б даць вам гэта (звернеце ўвагу: па стане на JDK 1.2, слоўнік лічыцца састарэлым; выкарыстоўваць замест Карта):

/** A version of Hashtable that lets you do
 * table.put("dog", "canine");, and then have
 * table.get("dogs") return "canine". **/
 
public class HashtableWithPlurals extends Dictionary {
 
  Hashtable table = new Hashtable();
 
  /** Make the table map both key and key + "s" to value. **/
  public Object put(Object key, Object value) {
    table.put(key + "s", value);
    return table.put(key, value);
  }
 
 ... // Need to implement other methods as well
}

Напрыклад Уласцівасці, калі вы жадаеце забяспечыць выкананне тлумачэнне значэння па змаўчанні запісу, было б лепш зрабіць з дэлегацыяй. Чаму гэта было зроблена з успадкоўваннем, ці што? Таму што каманда ўкаранення Java быў дастаўлены, і ўзяў курс, які неабходна пісьмова менш кода.


Пыт: Чаму няма глабальных зменных у Java?

Глабальныя зменныя лічацца благім тонам па цэлым шэрагу чыннікаў:
  • Даданне зменных станы парываў спасылкавай празрыстасці (вы не можаце зразумець заяву ці выраз, па сваёй уласнай, вы павінны разумець гэта ў кантэксце налады глабальных зменных).
  • Зменныя станы паменшыць згуртаванасці праграмы: вы павінны ведаць больш, каб зразумець, як штосьці працуе. Асноўныя кропкі аб'ектна-арыентаванага праграмавання з'яўляецца разбурэнне глабальнага стану ў больш зразумелай калекцыі мясцовых дзяржаўных.
  • Пры даданні адной зменнай, можна абмежаваць выкарыстанне вашай праграмы ў 1 асобніку. Што вы думалі, носіць глабальны характар, хтосьці яшчэ можа думаць, як мясцовыя: яны жадаюць запусціць дзве копіі праграмы адначасова.
Па гэтых чынніках, Java пастанавіў забараніць глабальных зменных.

Пыт: Я ўсё яшчэ нуджуся глабальных зменных. Што я магу зрабіць замест гэтага?

Гэта залежыць ад таго, што вы жадаеце зрабіць. У кожным выпадку неабходна вырашыць дзве рэчы: як шмат дзід гэтаму так званаму глабальнаму зменнаму мне трэба? І дзе б зручнае месца для яго? Вось некаторыя агульныя рашэнні:

Калі вы сапраўды жадаеце толькі адну копію кожных разоў, калі карыстач запускае Java шляхам запуску віртуальнай машыны Java, тыя вы, верагодна, жадаеце статычная зменная інстанцыі. Напрыклад, у вас ёсць клас MainWindow у вашым прыкладанні, і вы жадаеце, каб палічыць колькасць вокнаў, што карыстач адкрыў і прыступіць да "Сапраўды выйсці?" дыялог, калі карыстач зачыніў апошнюю. Для гэтага трэба:
// One variable per class (per JVM) 
public Class MainWindow {
  static int numWindows = 0;
 ...
  // when opening: MainWindow.numWindows++;  
  // when closing: MainWindow.numWindows--;
}
In many cases, you really want a class instance variable. For example, suppose you wrote a web browser and wanted to have the history list as a global variable. In Java, it would make more sense to have the history list be an instance variable in the Browser class. Then a user could run two copies of the browser at once, in the same JVM, without having them step on each other.
// One variable per instance 
public class Browser {
  HistoryList history = new HistoryList();
 ...
  // Make entries in this.history 
}
Зараз выкажам здагадку, што вы завяршылі дызайн і вялікую частку рэалізацыі вашага браўзара, і вы выявіце, што глыбока ў падрабязнасці, скажам, клас Cookie, усярэдзіне класа Http, трэба адлюстраваць паведамленне пра памылку. Але вы не ведаеце, дзе для адлюстравання паведамлення. Вы можаце лёгка дадаць зменную асобніка да класа браўзара правесці струмень дысплея ці кадраў, але вы не прайшлі бягучы асобнік браўзара ўніз метадаў у класе Cookie. Вы ж не жадаеце, каб змяніць подпісы шмат метадаў, каб прайсці ўздоўж браўзара. Вы не можаце выкарыстоўваць статычныя зменныя, таму што там можа быць некалькі браўзараў працуе. Аднак, калі вы можаце гарантаваць, што будзе толькі адзін браўзар працуе на струмень (нават калі кожны браўзар можа мець некалькі струменяў), гэта значыць добрае рашэнне: захаваць табліцу нітка да браўзар адлюстраванняў у выглядзе статычнай зменнай у браўзары клас, і шукаць правільны браўзар (і, такім чынам, дысплей) для выкарыстання па бягучай тэме:
// One "variable" per thread 
public class Browser {
  static Hashtable browsers = new Hashtable();
  public Browser() { // Constructor
    browsers.put(Thread.currentThread(), this);
  }
 ...
  public void reportError(String message) {
    Thread t = Thread.currentThread();
    ((Browser)Browser.browsers.get(t))
     .show(message)
  }
}
Нарэшце, выкажам здагадку, што вы жадаеце, каб значэнне глабальнай зменнай захоўваецца паміж выклікамі JVM, ці будуць размяркоўвацца паміж некалькімі віртуальнымі машынамі Java у сеткі машын. Тады вы, магчыма, варта выкарыстоўваць базу дадзеных якога вы атрымліваеце доступ праз JDBC, ці вы павінны серыялізацыі дадзеных і запісаць яго ў файл.

Пыт: Ці магу я напісаць грэх (х), а Math.sin (х)?

Кароткі адказ: Перш чым Java 1.5, не. Па стане на Java 1.5, так, з выкарыстаннем статычнага імпарту; зараз вы можаце напісаць імпарт статычных java.lang.Math.*, а затым выкарыстоўваць грэх (х) з беспакаранасцю. Але звернеце ўвагу, папярэджанне ад Sun: "Такім чынам, калі вы павінны выкарыстоўваць статычны імпарт? Вельмі эканомна!"

Вось некаторыя з варыянтаў, якія могуць быць скарыстаны да Java 1.5:

Калі вы жадаеце толькі некалькі метадаў, вы можаце пакласці ў іх званкі ў межах свайго класа:
 	
public static double sin(double x) { return Math.sin(x); }
public static double cos(double x) { return Math.cos(x); }
...
sin(x)
Статычныя метады прыняць мэтавыя (рэч, злева ад кропкі), якія або імя класа, або аб'ект, значэнне якога не ўлічваецца, але павінны быць абвешчаны правы класа. Такім чынам, можна захаваць 3 знакаў на выклік, выканаўшы:
the right class. So you could save three characters per call by doing:	
// Can't instantiate Math, so it must be null.
Math m = null; 
... 
m.sin(x)
java.lang.Math з'яўляецца канчатковым класа, таму не можа ўспадкоўваць ад яго, але калі ў вас ёсць свой уласны набор статычных метадаў, якія вы жадалі б падзяліцца сярод шматлікіх уласныя класы, тыя вы можаце спакаваць іх і ўспадкоўванне іх:
 
public abstract class MyStaticMethods { 
  public static double mysin(double x) {... } 
}
 
public class MyClass1 extends MyStaticMethods { 
 ... 
  mysin(x)
}

Піцер ван драпак Линден, аўтар проста Java, рэкамендуе як апошнія два практыкі ў FAQ. Я згодзен з ім, што матэматыка м = NULL з'яўляецца дрэннай ідэяй, у большасці выпадкаў, але я не ўпэўнены, што MyStaticMethods дэманструе "вельмі бедныя ООП стыль, выкарыстоўваны для атрымання спадчыны трывіяльным абрэвіятура (а не для выраза тыпу іерархіі). "Перш усяго, дробныя ў вачах які глядзіць; скарачэнне можа быць значным. (Гл. прыклад таго, як я выкарыстоўваў гэты падыход да таго, што я думаў добры эфект.) Па-другое, гэта даволі саманадзейна казаць, што гэта вельмі дрэнны стыль аб'ектна-арыентаванага праграмавання. Вы маглі б зрабіць справу, што гэта дрэнна стылі Java, але ў Мовы са множным успадкоўваннем, гэта ідыёма была б больш прымальнай.

Яшчэ адзін спосаб зірнуць на гэта, што асаблівасці Java (і на любой мове), неабходна прыцягваць кампрамісаў і аб'ядноўваць шматлікім пытанням. Я згодзен, што гэта дрэнна выкарыстоўваць успадкоўванне такім чынам, што вы ўводзяць карыстача ў памылку, думаючы, што MyClass1 успадкоўваюць паводзіны ад MyStaticMethods, і гэта дрэнна для забароны MyClass1 ад падавання любы іншы клас, яна сапраўды жадае падоўжыць. Але ў Java клас з адзінкай інкапсуляцыі, кампіляцыі (галоўным чынам), і назва вобласці. Падыход MyStaticMethod адзнакі негатыўных момантаў ад тыпу пярэдняй іерархіі, але дадатныя моманты на пярэдняй рамкі імя. Калі вы скажаце, што дадзены тып зроку іерархіі з'яўляецца важнейшым, я не буду з вамі спрачацца. Але я буду спрачацца, калі вы думаеце пра клас рабіць толькі адна рэч, а не шмат рэчаў адначасова, і калі вы думаеце пра стыль кіраўніцтва, як абсалютныя, а не кампрамісы.


Пытанне: Ці з'яўляецца нулявы аб'ект?

Абсалютна няма. Пад гэтым я маю на ўвазе (нулявое значэнне instanceof Object) з'яўляецца false. Некаторыя іншыя рэчы, якія вы павінны ведаць о нулявы:
  1. Вы не можаце выклікаць метад нулявой: XM () з'яўляецца памылкай, калі х з'яўляецца несапраўдным і м не з'яўляецца статычным метадам. (Пры т статычны метад гэта звычайна, таму што клас х, пытанні; гэта значэнне ігнаруецца.)
  2. Існуе толькі адзін нуль, а не адзін для кожнага класа. Такім чынам, ((String) нулявы == (Hashtable) нуль), напрыклад.
  3. Гэта звычайна прайсці нулявы якасці аргументу метаду, паколькі метад чакаў. Некаторыя метады рабіць, а некаторыя няма. Так, напрыклад, System.out.println (нуль) у парадку, але string.compareTo (нулявы) гэта не так. Для метадаў вы пішаце, вашы каментары Javadoc павінен сказаць, нулявы гэта звычайна, калі гэта не відавочна.
  4. У JDK 1.1 да 1.1.5, абыходзячы нулявы, як літаральнае аргумент канструктар ананімнага ўнутранага класа (напрыклад, новыя SomeClass (NULL) (...) выклікала памылку кампіляцыі. Гэта звычайна перадаць выраз, значэнне якога нулявы, ці перайсці пад прымусам, нулявы, як і новыя SomeClass ((String) NULL) (...)
  5. Ёсць прынамсі тры розных значэнні, што нулявы звычайна выкарыстоўваецца для выраза:
    • Неинициализированные. Зменнай ці слот, які яшчэ не быў прызначаны яго рэальны кошт.
    • Non-existant/not плату. Так, напрыклад, канчатковых вузлоў у бінарным дрэве можа быць прадстаўлены чарговы вузел з нулявымі паказальнікамі дзіцяці.
    • Пуста. Напрыклад, вы можаце выкарыстоўваць нулявы ўяўляць пустое дрэва. Заўважым, што гэта трохі адрозніваецца ад папярэдняга выпадку, хоць некаторыя людзі робяць памылку, змешваючы два выпадку. Розніца ці з'яўляецца нуль з'яўляецца прымальным вузел дрэва, ці ж гэта з'яўляецца сігналам, каб не разглядаць гэты лік як дрэва. Параўнаеце наступныя тры рэалізацыі бінарнага дрэва з мэтай спосаб друку ў:

// null means not applicable
// There is no empty tree.
 
class Node {
  Object data;
  Node left, right;
 
  void print() {
    if (left != null)
      left.print();
    System.out.println(data);
    if (right != null)
      right.print();
  }
}
 
// null means empty tree
// Note static, non-static methods
 
class Node {
  Object data;
  Node left, right;
 
  void static print(Node node) {
    if (node != null) node.print();
  }
 
  void print() {
    print(left);
    System.out.println(data);
    print(right);
  }
}
 
// Separate class for Empty
// null is never used
 
interface Node { void print(); }
 
class DataNode implements Node{
  Object data;
  Node left, right;
 
  void print() {
    left.print();
    System.out.println(data);
    right.print();
  }
}
 
class EmptyNode implements Node { 
  void print() { }
}

Пытанне: Наколькі вялікі аб'ект? Чаму няма sizeof?

C мае з! Гео аператара, і ён павінен мець адзін, таму што карыстач павінен кіраваць выклікамі на танос і таму, што памер прымітыўных тыпаў (напрыклад, даўжыню) не нармуецца. Java не трэба з! Гео, але гэта ўсё адно было б зручным дапамогі. Бо яго там няма, вы можаце зрабіць наступнае:
static Runtime runtime = Runtime.getRuntime();
...
long start, end;
Object obj;
runtime.gc();
start = runtime.freememory();
obj = new Object(); // Or whatever you want to look at
end =  runtime.freememory();
System.out.println("That took " + (start-end) + " 
bytes.");

Гэты метад небяспечны, паколькі збор смецця можа адбыцца ў сярэдзіне кода вы інструментальных, скідаючы колькасць байт. Акрамя таго, калі вы выкарыстоўваеце сапраўды ў момант кампілятар, некаторыя байты могуць паступаць ад генерацыі кода.

Вы можаце быць здзіўлены тым, што аб'ект займае 16 байт, ці 4 словамі, у Sun JDK VM. Гэта выглядаюць наступным чынам: Існуе два слоў загалоўка, у якім адно слова паказальнік на аб'ект класа, а іншы паказвае на зменныя асобніка. Хоць Аб'ект не мае зменных асобніка, Java па-ранейшаму вылучае адно слова для зменных. Нарэшце, ёсць "паступіць", якая з'яўляецца яшчэ паказальнік слоў, загаловак 2. ВС кажа, што гэта дадатковы ўзровень косвенности робіць смецця прасцей. (Там былі высокую прадукцыйнасць Lisp і Smalltalk зборшчыкі смецця, якія не выкарыстоўваюць дадатковы ўзровень, прынамсі 15 гадоў. Я чуў, але не пацвердзіў, што Microsoft JVM не мае дадатковы ўзровень ускоснага звароту.)

Пустыя новыя String () мае 40 байт, ці 10 слоў: 3 словы паказальніка над галавой, 3 словы для асобніка зменных (пачатковы індэкс, індэкс канец, і масіў знакаў), і 4 словы пусты масіў знакаў. Стварэнне падрадком радка існых займае "ўсяго" 6 слоў, бо масіў знакаў з'яўляецца агульным. Увод ключавых Integer і Integer значэнне ў Hashtable займае 64 байт (у дадатак да 4 байт, якія былі папярэдне вылучана ў Hashtable масіў): Я дам вам працу, чаму.


Пытанне: У якім парадку выконваецца код ініцыялізацыі? Што я павінен пакласці дзе?

Асобнік зменнай ініцыялізацыі код можа ісці ў трох месцах у класе:

У ініцыялізацыі зменнай асобніка для класа (ці суперкласса).
 
	
class C {
    String var = "val"; 

У канструктару класа (ці суперкласса).
 
 public C() { var = "val"; } 

У блоку ініцыялізацыі аб'екта. Гэта з'яўляецца новым у Java 1.1, яго гэтак жа, як статычны блок ініцыялізацыі, але без ключавога слова статычным.
 { var = "val"; }
} 
)

Для адзнакі (ігнаруючы з праблем з памяццю), калі вы кажаце, новы C () гэта:

  1. Call канструктар суперкласса ў C (калі аб'ект З, у гэтым выпадку яно не мае суперкласса). Яна заўсёды будзе канструктар без аргументаў, калі праграміст відавочна закадаваных супер (...) у якасці самай першай заявы канструктара.
  2. Пасля супер канструктар вярнуўся, выконваць любыя инициализаторы зменных асобніка і инициализатор аб'екта ў тэкставых блокаў (злева-направа) парадку. Не бянтэжцеся тым, што Javadoc і javap выкарыстоўваць у алфавітным парадку, то тут не важна.
  3. Зараз выканаць пакінутую частку цела для канструктара. Гэта можна ўсталяваць, напрыклад зменныя ці рабіць што-небудзь яшчэ.
Увогуле, у вас ёсць шмат вольнага выбару любога з гэтых трох формаў. Мая рэкамендацыя складаецца ў выкарыстанні зменнай initailizers напрыклад, у тых выпадках, калі гэта зменная, якая прымае і то ж значэнне, незалежна ад якой канструктар выкарыстоўваецца. Выкарыстанне блокаў инициализатор аб'екта толькі тады, калі ініцыялізацыі комплексу (напрыклад, яна патрабуе цыклу), і вы не жадаеце паўтарыць яго ў некалькі канструктараў. Выкарыстанне канструктара для адпачынку.

Вось яшчэ адзін прыклад:

Праграма:
class A {
    String a1 = ABC.echo(" 1: a1");
    String a2 = ABC.echo(" 2: a2");
    public A() {ABC.echo(" 3: A()");}
}
 
class B extends A {
    String b1 = ABC.echo(" 4: b1");
    String b2;
    public B() { 
        ABC.echo(" 5: B()"); 
        b1 = ABC.echo(" 6: b1 reset"); 
        a2 = ABC.echo(" 7: a2 reset"); 
    }
}
 
class C extends B {
    String c1; 
    { c1 = ABC.echo(" 8: c1"); }
    String c2;
    String c3 = ABC.echo(" 9: c3");
 
    public C() { 
        ABC.echo("10: C()"); 
        c2 = ABC.echo("11: c2");
        b2 = ABC.echo("12: b2");
    }
}
 
public class ABC {
    static String echo(String arg) {
        System.out.println(arg);
        return arg;
    }
 
    public static void main(String[] args) { 
        new C(); 
    }
}

Output:
 
 1: a1
 2: a2
 3: A()
 4: b1
 5: B()
 6: b1 reset
 7: a2 reset
 8: c1
 9: c3
10: C()
11: c2
12: b2


Пыт: А як наконт ініцыялізацыі класа?

Важна адрозніваць ініцыялізацыі класа ад стварэння асобніка. Напрыклад ствараецца пры выкліку канструктара з новым. Класа C ініцыялізуецца першы раз ён актыўна выкарыстоўваецца. У той час ініцыялізацыі код для класа запуску ў тэкставым парадку. Ёсць два выгляду класа код ініцыялізацыі: статычныя блокі ініцыялізацыі (статычная (...)), і клас инициализаторы зменных (статычная уаг String =...).

Актыўнае выкарыстанне вызначаецца як першы раз зрабіць адну з наступных дзеянняў:

  1. Стварэнне асобніка класа C, выклікаючы канструктар;
  2. Call статычны метад, які вызначаецца ў C (не ўспадкоўваюцца);
  3. Прызначэнне ці доступу да статычнай зменнай, заявіў (а не ўспадкавалі) у C. Гэта не ў рахунак, калі статычная зменная ініцыялізуецца са сталым выразам (1 з удзелам толькі прымітыўныя аператары (напрыклад, + ці | |), литералы і статычныя канчатковага зменных), таму што гэтыя ініцыялізуюцца падчас кампіляцыі.

Вось прыклад:

Program:
 
class A {
    static String a1 = ABC.echo(" 1: a1");
    static String a2 = ABC.echo(" 2: a2");
}
 
class B extends A {
    static String b1 = ABC.echo(" 3: b1");
    static String b2;
    static { 
        ABC.echo(" 4: B()"); 
        b1 = ABC.echo(" 5: b1 reset"); 
        a2 = ABC.echo(" 6: a2 reset"); 
    }
}
 
class C extends B {
    static String c1; 
    static { c1 = ABC.echo(" 7: c1"); }
    static String c2;
    static String c3 = ABC.echo(" 8: c3");
 
    static { 
        ABC.echo(" 9: C()"); 
        c2 = ABC.echo("10: c2");
        b2 = ABC.echo("11: b2");
    }
}
 
public class ABC {
    static String echo(String arg) {
        System.out.println(arg);
        return arg;
    }
 
    public static void main(String[] args) { 
        new C(); 
    }
}

Output:
 
 1: a1
 2: a2
 3: b1
 4: B()
 5: b1 reset
 6: a2 reset
 7: c1
 8: c3
 9: C()
10: c2
11: b2


Пыт: У мяне ёсць клас з 6 зменныя асобніка, кожны з якіх можа быць ініцыялізавана ці няма. Ці павінен я напісаць 64 канструктараў?

Вядома, вам не трэба (2 +6) будаўнікоў. Скажам у вас ёсць клас C вызначаецца наступным чынам:
 
public class C { int a,b,c,d,e,f; }

Вось некаторыя рэчы, якія вы можаце зрабіць для будаўнікоў:

  1. Адгадайце, у якой камбінацыі зменных, верагодна, будзе трэба, і забяспечыць канструктараў для гэтых камбінацый. Pro: Вось як гэта робіцца звычайна. Con: Цяжка адгадаць; шмат залішняга кода пісаць.
  2. Вызначыць сетараў, якія могуць быць каскаднымі, таму што яны вярнуць. Гэта значыць, вызначыць прыкладам для кожнага асобніка зменнай, а затым выкарыстоўваць іх амбасадару выкліку канструктара па змаўчанні:
    public C setA(int val) { a = val; return this; }
    ...
    new C().setA(1).setC(3).setE(5);

    Pro: Гэта досыць просты і эфектыўны падыход. Аналагічная ідэя абмяркоўваецца Bjarne Stroustrop на стар. 156 о Дызайн і эвалюцыя C + +. Con: Вы павінны напісаць усе маленькія сетараў, яны не JavaBean-сумяшчальны (бо яны вярнуць не пустэча), яны Дон ' т працаваць, калі Ёсць узаемадзеянні паміж двума значэннямі.

  3. З дапамогай канструктара па змаўчанні для ананімных суб-клас з нестатычных ініцыялізацыі:
     
    new C() {{ a = 1; c = 3; e = 5; }}
    
     

    Pro: Вельмі кароткі, няма блытаніны з сетараў. Con: напрыклад зменныя не могуць быць дзелямі, у вас ёсць накладныя выдаткі на суб-клас, ваш аб'ект насамрэч не маюць у якасці C клас (хоць яна ўсё яшчэ будзе instanceof C), ён працуе толькі калі ў вас ёсць даступныя зменныя асобніка, і шматлікія людзі, у тым ліку дасведчаныя праграмісты Java, не зразумеюць. Насамрэч, яго даволі проста: вы вызначаеце новы безназоўны (ананімныя) падклас C, без якіх-небудзь новых метадаў ці зменных, але з ініцыялізацыі блока, які ініцыялізуе, З і Е. Нароўні з вызначэннем гэтага класа, вы таксама інстанцыі. Калі я паказаў гэта Гай Стыл, ён сказаў: "хе-хе! Гэта вельмі міла, усё ў парадку, але я не ўпэўнены, што будзе выступаць за шырокага выкарыстанні..." Як звычайна, Гай мае рацыю. (Дарэчы, вы можна таксама выкарыстоўваць для стварэння і ініцыялізацыі вектару. Вы ведаеце, як гэта выдатна для стварэння і ініцыялізацыі, напрыклад, масіў String новыя String [] ("1", "2", "3"). Зараз унутраныя класы Вы можаце зрабіць тое ж самае для вектару, дзе раней вы думалі, прыйдзецца выкарыстоўваць assignement заявы: новы вектар (3) ((дадаць ("1"); дадаць ("2"), дадаць ("3") )).)

  4. Вы можаце перайсці на мову, які непасрэдна падтрымлівае гэту ідыёму.. Так, напрыклад, C + + мае дадатковыя аргументы. Такім чынам, вы можаце зрабіць наступнае:
     
    class C {
    public: C(int a=1, int b=2, int c=3, int d=4, int e=5);
    }
    ...
    new C(10); // Construct an instance with defaults for b,c,d,e
    
     

    Common Lisp і Python ёсць ключавыя аргументы, а таксама дадатковыя аргументы, так што вы можаце зрабіць наступнае:

      
    C(a=10, c=30, e=50)            # Construct an instance; use defaults for b and d.
    
     


Пыт: Калі я павінен выкарыстоўваць канструктары, і калі я павінен выкарыстоўваць іншыя метады?

Бойкі адказ складаецца ў выкарыстанні канструктараў, калі вы жадаеце новы аб'ект, вось што ключавое слова для новых. Рэдкія адказу з'яўляецца тое, што канструктары часцяком празмернай эксплуатацыі, як у, калі іх завуць, і колькі яны павінны рабіць. Вось некаторыя моманты, разгледзець пытанне о
  • Мадыфікатары: Як мы бачылі ў папярэднім пытанні, можна ісці за борт у падаванні занадта шмат будаўнікоў. Як правіла, лепш звесці да мінімуму лік будаўнікоў, а затым забяспечыць мадыфікатар метадаў, каб зрабіць адпачынак ініцыялізацыі. Калі мадыфікатары вярнуць, тыя вы можаце стварыць карысны прадмет у адзін выраз, а калі няма, то вам трэба выкарыстоўваць шэраг заяў. Мадыфікатары добра, таму што часта змены, якія трэба зрабіць падчас будаўніцтва таксама змены вы жадалі б зрабіць пазней, так чаму б дубляваць код, канструктараў і метадаў.
  • Фабрыкі: Часта вы жадаеце стварыць штосьці асобнік якога-небудзь класа ці інтэрфейсу, але вы або не хвалюе, які менавіта падклас ствараць, ці вы жадаеце, каб адкласці гэта рашэнне да выканання. Напрыклад, калі вы пішаце калькулятар аплет, вы, магчыма, пажадае, што вы маглі б назваць новы нумар (радок), і гэта вяртанне падвойнай, калі радок у фармаце плывучай кропкай, ці доўга, калі радок знаходзіцца у цэлалікавы фармат. Але вы не можаце зрабіць гэта па двух чынніках: Лік з'яўляецца абстрактным класам, так што вы не можаце выклікаць яго канструктар прама, і ніводнага званка, каб канструктар павінен вярнуць новы асобнік гэтага класа непасрэдна, а не з падкласа. Метад, які вяртае аб'екты, як канструктар, але гэта не мае больш волі, у тым, як аб'ект зрабіў (а які ён ёсць), завецца заводу. Java не мае ўбудаванай падтрымкі ці канвенцый, для заводаў, але вам трэба будзе вынаходзіць канвенцый па іх выкарыстанні ў кодзе.
  • Кэшаванне і перапрацоўка: канструктара неабходна стварыць новы аб'ект. Але стварэнне новага аб'екта з'яўляецца даволі дарагой аперацыяй. Гэтак жа, як у рэальным свеце, вы можаце пазбегнуць дарагіх смецця на перапрацоўку. Напрыклад, новыя булевых (х) стварае новы лагічны, але вы амаль заўсёды павінны выкарыстоўваць замест (х? Boolean.TRUE: Boolean.FALSE), якая выдаліць існыя каштоўнасці, а не марнатраўна ствараць новую. Java было б лепш, калі яна рэкламуецца метад, які рабіў менавіта гэта, а не рэклама канструктара. Булевых гэта толькі адзін прыклад, вы павінны таксама разгледзець магчымасць перапрацоўкі іншых нязменлівых класаў, у тым ліку характар, Integer, і, магчыма, шматлікія з вашых уласных класаў. Ніжэй прыведзены прыклад рэцыркуляцыі завод нумара. Калі ў мяне выбар, я б назваў гэта Number.make, але, вядома, я не магу дадаць метады класа Колькасць, так што прыйдзецца ісці ў іншае месца.

     
      public Number numberFactory(String str) throws NumberFormatException {
        try {
          long l = Long.parseLong(str);
          if (l >= 0 && l < cachedLongs.length) {
    
            int i = (int)l;
            if (cachedLongs[i] != null) return cachedLongs[i];
            else return cachedLongs[i] = new Long(str);
          } else {
            return new Long(l);
          }
        } catch (NumberFormatException e) {
          double d = Double.parseDouble(str);
          return d == 0.0 ? ZERO : d == 1.0 ? ONE : new Double(d);
        }
      }
     
      private Long[] cachedLongs = new Long[100];
      private Double ZERO = new Double(0.0);
      private Double ONE = new Double(1.0);
Мы бачым, што новае карысным канвенцыі, аднак, што заводы і перапрацоўка гэтак жа карысныя. Java вырашылі падтрымаць толькі новыя, таму што гэта простая магчымасць, а філасофія Java з'яўляецца захаванне самай мовы як мага прасцейшым. Але гэта не азначае, што ваша бібліятэкі класаў павінны прытрымвацца нізкіх назоўніку. (І гэта не павінна азначаць, што ўбудаваныя ў бібліятэках стаялі на сваім, але, нажаль, яны і зрабілі.)

Пыт: Ці магу я атрымаць забіты накладных стварэнні аб'екта і GC?

Выкажам здагадку, што прыкладанне павінна рабіць з маніпулявання мноства 3D-геаметрычных кропак. Відавочнай выявай Java зрабіць гэта мець клас Пойнт са здвоенымі х, у, г каардынат. Але вылучэнні і збору смецця шмат момантаў сапраўды можа выклікаць праблемы з прадукцыйнасцю. Вы можаце дапамагчы праекту, кіруючы ўласнай захоўванні ў адзіны пул рэсурсаў. Замест вылучэння кожнай кропкі, калі гэта неабходна, можна вылучыць вялікі масіў кропак напачатку праграмы. Масіў (загорнутых у класе) выступае ў якасці завод кропкі, але гэта сацыяльна-свядомых утылізацыі заводу. Выкліку метаду pool.point (х, у, г) мае першы нескарыстаны момант у масіў, ставіць сваёй 3 палі для паказанага значэння, і пазначае яго як выкарыстоўваць. Зараз вы, як праграміст нясе адказнасць за вяртанне Ачкі ў басейн, калі яны больш не патрэбныя. Ёсць некалькі спосабаў гэта зрабіць. Самы просты, калі вы ведаеце, што будзе вылучэнне пунктаў у блокі, якія выкарыстоўваюцца на некаторы час, а затым адкідаюцца. Значыць, вы Int руж = pool.mark (), каб адзначыць бягучую пазіцыю ў басейн. Калі вы скончыце з часткай код, вы выклікаеце pool.restore (POS), каб усталяваць знак зваротна на пазіцыю. Калі Ёсць некалькі момантаў, якія вы жадалі б захаваць, проста вылучыць іх з розных басейн. Пула рэсурсаў ратуе вас ад смецця выдаткі (калі ў вас ёсць добры прыклад, калі вашы аб'екты будуць вызвалены), але вы дагэтуль першапачатковыя выдаткі на стварэнне аб'екта. Вы можаце абыйсці гэта, абраўшы "зваротна ў Fortran": з дапамогай масіваў х, у і г каардынат, а не асобныя кропкавыя аб'екты. У вас ёсць клас кропак, але не клас для пэўнай кропцы. Лічыце, што гэта пул рэсурсаў клас:
  
public class PointPool {
  /** Allocate a pool of n Points. **/
  public PointPool(int n) {
    x = new double[n];
    y = new double[n];
    z = new double[n];
    next = 0;
  }
  public double x[], y[], z[];
 
  /** Initialize the next point, represented as in integer index. **/
  int point(double x1, double y1, double z1) { 
    x[next] = x1; y[next] = y1; z[next] = z1;
    return next++; 
  }
 
  /** Initialize the next point, initilized to zeros. **/
  int point() { return point(0.0, 0.0, 0.0); }
 
  /** Initialize the next point as a copy of a point in some pool. **/
  int point(PointPool pool, int p) {
    return point(pool.x[p], pool.y[p], pool.z[p]);
  }
 
  public int next;
}
Вы павінны выкарыстоўваць гэты клас наступным чынам:
  
PointPool pool = new PointPool(1000000);
PointPool results = new PointPool(100);
...
int pos = pool.next;
doComplexCalculation(...);
pool.next = pos;
 
...
 
void doComplexCalculation(...) {
 ...
  int p1 = pool.point(x, y, z);
  int p2 = pool.point(p, q, r);
  double diff = pool.x[p1] - pool.x[p2];
 ...
  int p_final = results.point(pool,p1);
 ...
}

Вылучэнне мільёнаў кропак, узяўшы палову другога падыходу для PointPool і 6 секунд просты падыход, які вылучае мільён асобнікаў класа Point, так што гэта 12-кратнае паскарэнне.

Не было б хвалебна, калі можна абвясціць P1, P2 і p_final як кропка, а не Int? У мове C ці C + +, вы можаце проста зрабіць ЬУРЕЙЕЕ Int Point, але Java не дазваляе гэтага. Калі вы прыгод, вы можаце наладзіць каб файлы для запуску файлаў з дапамогай препроцессора C да кампілятар Java, а затым вы можаце рабіць # вызначыць кропкі Int.


Пыт: У мяне складаны выраз усярэдзіне цыклу. Для падвышэння эфектыўнасці, я б жадаў вылічэнняў зрабіць толькі адзін раз. Але для выгоды ўспрымання, я жадаю, каб знаходжанне ў цыкл, у якім яна выкарыстоўваецца. Што я магу зрабіць?

Выкажам здагадку, напрыклад, калі матч рэгулярнага выраза адпавядаюць звычайнай справай, а кампіляваць кампілюе радок у канчатковых аўтаматаў, якія могуць быць скарыстаны матчу:
for(;;) {
 ...
  String str =...
  match(str, compile("a*b*c*"));
 ...
}

Бо Java не мае макрасы і не кантралюе час выканання, ваш выбар абмежаваны тут. Адным з магчымых варыянтаў, хоць і не вельмі прыгожая, з'яўляецца выкарыстанне ўнутранага інтэрфейсу са зменнай ініцыялізацыі:

 
for(;;) {
 ...
  String str =...
  interface P1 {FSA f = compile("a*b*c*);}
  match(str, P1.f);
 ...
}

Значэнне для P1.f ініцыялізуецца на першым выкарыстанні P1, і не змяняецца, бо зменныя інтэрфейсаў няяўна статычныя канчатковым. Калі вам не падабаецца, што вы можаце пераключыцца на мову, які дае вам лепшы кантроль. In Common Lisp, паслядоўнасць знакаў #. Сродкаў для адзнакі наступны выраз пры чытанні (кампіляцыі) час, а не падчас выканання. Такім чынам, можна напісаць:

 
(loop
 ...
  (match str #.(compile "a*b*c*"))
 ...)


Пыт: Якія іншыя аперацыі на здзіўленне павольна?

Дзе я магу пачаць? Вось некалькі, якія найболей карысныя ведаць. Я напісаў часу ўтыліта, якая працуе фрагменты кода ў цыкле, справаздач пра вынікі ў плане тысяч ітэрацый у секунду (K / сек) і мікрасекунд на ітэрацыі (uSecs). Тэрміны было зроблена на Sparc 20 з JDK 1.1.4 JIT кампілятар. Я жадаў бы адзначыць наступнае:
  • Усё гэта было зроблена ў 1998 году. Кампілятары змянілася з тых часоў.
  • Зваротны адлік (напрыклад, для (INT я = п; я> 0; я -)) у два разу хутчэй, чым падлічваць: мая машына можа разлічваць да 144 млн у секунду, але да ўсяго толькі 72 млн. дол. ЗША.
  • Выклік Math.max (а, б) 7 раз павольней, чым (A> B)? : б. Гэта кошт выкліку метаду.
  • Масівы ад 15 да 30 раз хутчэй, чым Вектары. Hashtables з'яўляюцца 2 / 3 гэтак жа хутка, як вектары.
  • Выкарыстанне bitset.get (я) у 60 раз павольней, чым біт і 1 <<я. Гэта кошт сінхранізаваны выкліку метаду, галоўным чынам. Вядома, калі вы жадаеце атрымаць больш, чым 64 біт, вы не можаце выкарыстоўваць сваё ўкладанне-ражон прыціх. Вось схема разоў для атрымання і ўсталёўкі элементаў розных структур дадзеных:
     
      K/sec     uSecs          Code           Operation 
    =========  ======= ====================  ===========
      147,058    0.007 a = a & 0x100;        get element of int bits
          314    3.180 bitset.get(3);        get element of Bitset
       20,000    0.050 obj = objs[1];        get element of Array
        5,263    0.190 str.charAt(5);        get element of String
          361    2.770 buf.charAt(5);        get element of StringBuffer
          337    2.960 objs2.elementAt(1);   get element of Vector
          241    4.140 hash.get("a");        get element of Hashtable
     
          336    2.970 bitset.set(3);        set element of Bitset
        5,555    0.180 objs[1] = obj;        set element of Array
          355    2.810 buf.setCharAt(5,' ')  set element of StringBuffer
          308    3.240 objs2.setElementAt(1  set element of Vector
          237    4.210 hash.put("a", obj);   set element of Hashtable

  • Java кампілятары вельмі бедныя на адмену константные выразы з завесы. C / Java цыклу дрэннай абстракцыі, таму што яна стымулюе паўторнае вылічэнне значэння ў канцы Найболей тыповы выпадак. Такім чынам, для (INT = 0; я <str.length (); я + +) у тры разу павольней, чым даўжыня = Int str.length (), для (INT = 0; я <даўжыня; я + +)

Пыт: Ці магу я атрымаць карысныя рады ад кнігі па Java?

Ёсць шмат кніг, Java там, валячыся на тры класа:

Дрэнна. Большасць Java кнігі, напісаныя людзьмі, якія не могуць уладкавацца на працу ў якасці праграміста Java (так праграмавання амаль заўсёды плаціць больш, чым кніга пісьмова, я ведаю, таму што я зрабіў і іншае). Гэтыя кнігі поўныя памылак, дрэнных рад, і дрэнныя праграмы. Гэтыя кнігі ўяўляюць небяспеку для пачаткоўцаў, але лёгка вядомыя і адпрэчаць праграміст нават з невялікім досведам на іншай мове.

Выдатна. Ёсць невялікая колькасць выдатных кніг Java. Мне падабаецца афіцыйнай спецыфікацыі і кнігі Арнольда і Гослинг, Марти зала, і Піцер ван драпак Линден. Для даведкі Мне падабаецца Java у двух словах серыі і анлайн спасылкі на ВС (скапіяваць Javadoc API, а таксама спецыфікацыі мовы і яго папраўкі да майго лакальным дыску і закладка іх у сваім браўзары, каб я заўсёды мець хуткі доступ.)

Ненадзейны. У прамежку паміж гэтымі дзвюма крайнасцямі ўяўляе сабою складанка неахайны пісьмовым выглядзе людзей, якія павінны ведаць лепш, але або не знайшлі час, каб сапраўды зразумець, як Java працуе, ці проста не спяшаліся штосьці апублікавана хутка. Адным з такіх прыкладаў полуправды з'яўляецца Эдвард Йордон у Java і новыя парадыгмы праграмавання Інтэрнэт з Узлёт і Ўваскрэсенні амерыканскага праграміста. Вось што кажа Йордан пра тое, як розныя Java гэта:

  • "Функцыі, былі ўхілены" Гэта праўда, што няма "функцыя" ключавое слова ў Java. Java заве іх метадаў (і Perl заве іх падпраграм, а таксама схемы іх заве працэдур, але вы б не сказаў, што гэтыя Мовы ўхілілі функцыі). Вы цалкам можаце сказаць, што Ёсць няма глабальных функцый у Java. Але я думаю, было б больш дакладным сказаць, што Ёсць функцыі глабальных маштабах, яго проста, што яны павінны быць вызначаны ў класе, і завуцца "статычны метад Cf" замест "функцыі /".
  • "Аўтаматычная прымус тыпаў дадзеных, былі ўхілены" Гэта праўда, што Існуюць абмежаванні на прымус, што зрабіў, але яны далёка не ўхілены. Вы ўсё яшчэ можаце сказаць (1,0 + 2) і 2, будуць аўтаматычна прымушаюць да падвойнай. Ці вы можаце сказаць ("1" + 2) і 2 будуць прымушаць да радка.
  • "Паказальнікі і арыфметычныя аперацыі над паказальнікамі былі ўхілены" Гэта праўда, што відавочныя арыфметычныя аперацыі над паказальнікамі была ліквідавана (і з лёгкім ветрам). Але паказальнікі застаюцца сапраўды, усё спасылкі на аб'ект паказальнік. (Вось чаму мы NullPointerException.) Нельга быць кампетэнтны праграміст Java, не разумеючы гэтага. Кожны праграміст Java павінен ведаць, што калі вы робіце:
     
        int[] a = {0, 1, 2};
        int[] b = a;
        b[0] = 99;
    
    Затым [0], паколькі 99 і б з'яўляюцца паказальнікамі (ці спасылкі) для таго ж аб'екта.
  • "Таму што структуры сышлі, а масівы і радкі прадстаўлены ў выглядзе аб'ектаў, неабходнасць паказальнікаў у значнай ступені знікла". Гэта таксама памылка. Па-першае, структуры не прайшло, яны проста пераназваны ў "класы". Што пройдзе, гэта кантроль ці над праграмістам структура / класа асобнікі ў кучы ці ў стэку. У Java усе аб'екты ў кучы. Таму няма неабходнасці для сінтаксічных маркераў (напрыклад, *) для паказальнікаў - калі ён высылаецца на аб'ект у Java, гэта паказальнік. Yourdan правільна сказаць, што з паказальнікамі на сярэдзіне радка ці масіва лічыцца добрай, ідыяматычныя выкарыстанні ў C і на асэмблеры (і некаторыя людзі ў C + +), але яно не падтрымліваецца ні прапусцілі ў іншых Мовы.
  • Йордан таксама складаецца з шэраг малаважных памылак друку, як і казаў, што масівы маюць даўжыню () метад (замест даўжыня палі) і мадыфікавальныя радкі прадстаўлены StringClass (замест StringBuffer). Гэта раздражняе, але не так шкодна, як больш асноўных полуправды.