=====第一個java程序=====
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
命令
$ javac HelloWorld.java
$ java HelloWorld
Hello World
javac 後面跟着的是java文件的文件名,例如 HelloWorld.java。
該命令用於將 java 源文件編譯爲 class 字節碼文件
java 後面跟着的是java文件中的類名,例如 HelloWorld 就是類名,
如: java HelloWorld。
=====package=====
1.解決類名衝突的問題
2.同一個包中的類不須要被導入,當代碼使用外部包中的類時,須要用import語句導入包含該類的包。
3.代碼使用外部包中的類,另一個方法是在代碼中使用類的徹底限定名稱。例如,在使用Scanner的代碼中,若是省略了導入Scanner的語句,則須要在使用Scanner類的位置使用Java.util.Scanner
4.Java編譯器默認爲全部的Java程序引入了JDK的Java.lang 包中的全部的類。其中定義了一些經常使用類:System、String、Object、Math等。所以咱們能夠直接使用這些類,而沒必要顯式引入。但使用其餘包中的類時,則必須先引入、後使用。
5.把功能類似或相關的類或接口組織在同一個包中,方便類的查找和使用
6.包也限定了訪問權限,擁有包訪問權限的類才能訪問某個包中的類
7.package 包名
8.包聲明必定做爲源代碼的第一行
9.包的名稱通常爲小寫
10.若是在一個包中,一個類想要使用本包中的另外一個類,那麼該包名能夠省略。
11.一般,一個公司使用它互聯網域名的顛倒形式來做爲它的包名.例如:互聯網域名是 runoob.com,全部的包名都以 com.runoob 開頭。包名中的每個部分對應一個子目錄.例如:有一個 com.runoob.test 的包,這個包包含一個叫作 Runoob.java 的源文件,那麼相應的,應該有以下面的一連串子目錄:
....\com\runoob\test\Runoob.java
=====import=====
1.import Java.(包名).(方法名);
2.
它不像#include 同樣,會將其餘java文件的內容載入進來。import 只是讓編譯器編譯這個java文件時把沒有姓的類別加上姓,並不會把別的文件程序寫進來。你開心的話能夠不使用import,只要在用到類別的時候,用它的所有姓名來稱呼它就好了(就像例子一開始那樣),這樣跟使用import功能徹底同樣。
3.
import java.lang.*;
import java.io.*;
表示文件裏面說到的類不是java.lang包的就是java.io包的。編譯器會幫咱們選擇與類名對應的包。
4.
單類型導入(single-type-import)
(例:import java.util.ArrayList; )
按需類型導入(type-import-on-demand)
(例:import java.util.*;)
java以這樣兩種方式導入包中的任何一個public的類和接口(只有public類和接口才能被導入)
上面說到導入聲明僅導入聲明目錄下面的類而不導入子包,這也是爲何稱它們爲類型導入聲明的緣由。
5.
導入的類或接口的簡名(simple name)具備編譯單元做用域。這表示該類型簡名能夠在導入語句所在的編譯單元的任何地方使用.這並不意味着你可使用該類型全部成員的簡名,而只能使用類型自身的簡名。
例如: java.lang包中的public類都是自動導入的,包括Math和System類.可是,你不能使用它們的成員的簡名PI()和gc(),而必須使用Math.PI()和System.gc().你不須要鍵入的是java.lang.Math.PI()和java.lang.System.gc()。
6.
程序員有時會導入當前包或java.lang包,這是不須要的,由於當前包的成員自己就在做用域內,而java.lang包是自動導入的。java編譯器會忽略這些冗餘導入聲明(redundant import declarations)。即便像這樣
import java.util.ArrayList;
import java.util.*;
屢次導入,也可編譯經過。編譯器會將冗餘導入聲明忽略.
7.
在Java程序中,是不容許定義獨立的函數和常量的。即什麼屬性或者方法的使用必須依附於什麼東西,例如使用類或接口做爲掛靠單位才行(在類裏能夠掛靠各類成員,而接口裏則只能掛靠常量)。
若是想要直接在程序裏面不寫出其餘類或接口的成員的掛靠單元,有一種變通的作法 :
將全部的常量都定義到一個接口裏面,而後讓須要這些常量的類實現這個接口(這樣的接口有一個專門的名稱,叫(「Constant Interface」)。這個方法能夠工做。可是,由於這樣一來,就能夠從「一個類實現了哪一個接口」推斷出「這個類須要使用哪些常量」,有「會暴露實現細節」的問題。
8.
J2SE 1.5裏引入了「Static Import」機制,藉助這一機制,能夠用略掉所在的類或接口名的方式,來使用靜態成員。static import和import其中一個不一致的地方就是static import導入的是靜態成員,而import導入的是類或接口類型。
以下是一個有靜態變量和靜態方法的類
package com.assignment.test;
public class staticFieldsClass {
static int staticNoPublicField = 0;
public static int staticField = 1;
public static void staticFunction(){}
}
平時咱們使用這些靜態成員是用類名.靜態成員的形式使用,即staticFieldsClass.staticField或者staticFieldsClass.staticFunction()。
如今用static import的方式:
//**精準導入**
//直接導入具體的靜態變量、常量、方法方法,注意導入方法直接寫方法名不須要括號。
import static com.assignment.test.StaticFieldsClass.staticField;
import static com.assignment.test.StaticFieldsClass.staticFunction;
//或者使用以下形式:
//**按需導入**沒必要逐一指出靜態成員名稱的導入方式
//import static com.assignment.test.StaticFieldsClass.*;
public class StaticTest {
public static void main(String[] args) {
//這裏直接寫靜態成員而不須要經過類名調用
System.out.println(staticField);
staticFunction();
}
}
這裏有幾個問題須要弄清楚:
Static Import無權改變沒法使用原本就不能使用的靜態成員的約束,上面例子的StaticTest和staticFieldsClass不是在同一個包下,因此StaticTest只能訪問到staticFieldsClass中public的變量。使用了Static Import也一樣如此。
導入的靜態成員和本地的靜態成員名字相同起了衝突,這種狀況下的處理規則,是「本地優先。
不一樣的類(接口)能夠包括名稱相同的靜態成員。例如在進行Static Import的時候,出現了「兩個導入語句導入同名的靜態成員」的狀況。在這種時候,J2SE 1.5會這樣來加以處理:
若是兩個語句都是精確導入的形式,或者都是按需導入的形式,那麼會形成編譯錯誤。
若是一個語句採用精確導入的形式,一個採用按需導入的形式,那麼採用精確導入的形式的一個有效。
你們都這麼聰明上面的幾個特性我就不寫例子了。
static import這麼叼那它有什麼負面影響嗎?
答案是確定的,去掉靜態成員前面的類型名,當然有助於在頻繁調用時顯得簡潔,可是同時也失去了關於「這個東西在哪裏定義」的提示信息,理解或維護代碼就呵呵了。
可是若是導入的來源很著名(好比java.lang.Math),這個問題就不那麼嚴重了。
9.
使用按需導入聲明是否會下降Java代碼的執行效率?
絕對不會!
1、import的按需導入
import java.util.*;
public class NeedImportTest {
public static void main(String[] args) {
ArrayList tList = new ArrayList();
}
}
編譯以後的class文件 :
//import java.util.*被替換成import java.util.ArrayList
//即按需導入編譯過程會替換成單類型導入。
import java.util.ArrayList;
public class NeedImportTest {
public static void main(String[] args) {
new ArrayList();
}
}
2、static import的按需導入
import static com.assignment.test.StaticFieldsClass.*;
public class StaticNeedImportTest {
public static void main(String[] args) {
System.out.println(staticField);
staticFunction();
}
}
上面StaticNeedImportTest 類編譯以後 :
//能夠看出 :
//一、static import的精準導入以及按需導入編譯以後都會變成import的單類型導入
import com.assignment.test.StaticFieldsClass;
public class StaticNeedImportTest {
public static void main(String[] args) {
//二、編譯以後「打回原形」,使用原來的方法調用靜態成員
System.out.println(StaticFieldsClass.staticField);
StaticFieldsClass.staticFunction();
}
}
這是否意味着你老是可使用按需導入聲明?
是,也不是!
在相似Demo的非正式開發中使用按需導入聲明顯得頗有用。
然而,有這四個理由讓你能夠放棄這種聲明:
編譯速度:在一個很大的項目中,它們會極大的影響編譯速度.但在小型項目中使用在編譯時間上能夠忽略不計。
命名衝突:解決避免命名衝突問題的答案就是使用全名。而按需導入偏偏就是使用導入聲明初衷的否認。
說明問題:畢竟高級語言的代碼是給人看的,按需導入看不出使用到的具體類型。
無名包問題:若是在編譯單元的頂部沒有包聲明,Java編譯器首選會從無名包中搜索一個類型,而後纔是按需類型聲明。若是有命名衝突就會產生問題。
===== equals和==的區別 =====
1.值類型是存儲在內存中的堆棧(簡稱棧),而引用類型的變量在棧中僅僅是存儲引用類型變量的地址,而其自己則存儲在堆中。
2.==操做比較的是兩個變量的值是否相等,對於引用型變量表示的是兩個變量在堆中存儲的地址是否相同,即棧中的內容是否相同
3.equals操做表示的兩個變量是不是對同一個對象的引用,即堆中的內容是否相同。
4.==比較的是2個對象的地址,而equals比較的是2個對象的內容,顯然,當equals爲true時,==不必定爲true
===== 數據類型 =====
1.boolean 類型數據只容許取值true 或 false(不可使用0 或非0的整數來代替true和false,區分於C語言)。
2.基本數據類型:byte、short、int、long、float、double、char、boolean
引用數據類型:對象、數組等,另外爲符合面向對象特徵,Java中每一種基本數據類型都有對應的包裝類:Byte、Short、Integer、Long、Float、Double、Character、Boolean,而且提供自動拆裝箱功能。
3.
存儲在什麼地方?
1.寄存器(register):因爲寄存器是在CPU內部的,因此它的速度最快,可是數量有限,因此由編譯器根據需求進行分配。
2.棧(stack):位於通用RAM中,經過棧指針的移動來分配和釋放內存,指針向下移動分配新的內存;指針向上移動則釋放內存。速度僅次於寄存器。建立程序時,Java編譯器必須知道存儲在棧內全部數據的確切大小和生命週期,由於它必須生成相應的代碼,以便上下移動棧指針,這就限制了程序的靈活性。因此java中的對象並不存放在棧當中,但對象的引用存放在棧中。
3.堆(heap):也是位於RAM中的內存池,用於存放全部的JAVA對象。編譯器不須要知道要從堆裏分配多少存儲區域,也不須要知道存儲的數據在堆裏面存活多長時間,所以堆要比棧靈活不少。當你new建立一個對象時,編譯器會自動在堆裏進行存儲分配。固然,爲這種靈活性必需要付出相應的代碼。用堆進行存儲分配比用棧進行存儲存儲須要更多的時間。
4.靜態存儲(static storage):這裏的「靜態」是指「在固定的位置」(也在RAM裏)。靜態存儲裏存放程序運行時一直存在的數據。你可用關鍵字static來標識一個對象的特定元素是靜態的,即存放類中的靜態成員,但JAVA對象自己歷來不會存放在靜態存儲空間裏。
5. 常量存儲(constant storage):存放字符串常量和基本類型常量(public static final)。常量值一般直接存放在程序代碼內部,它們永遠不會被改變。有時,在嵌入式系統中,常量自己會和其餘部分分割離開,因此在這種狀況下,能夠選擇將其放在ROM中。
簡單描述下垃圾回收機制
垃圾回收回收的是無任何引用的對象佔據的內存空間(堆)而不是對象自己,要注意如下3點:
1)對象可能不會被回收,即垃圾回收不必定會執行;
2)垃圾回收並不等於析構;+
3)垃圾回收只與內存有關。
引用計數器:一種簡單可是速度很慢的垃圾回收策略。即每一個對象都有一個引用計數器,當有引用鏈接至對象時計數器加1;當引用離開時計數器減1。垃圾回收器會在含有所有對象的列表中遍歷,發現某個對象的引用計數器爲0時,就釋放其佔用的內存。
優勢:引用計數收集器能夠很快的執行,交織在程序運行中。對程序不被長時間打斷的實時環境比較有利。
缺點:沒法檢測出循環引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數永遠不可能爲0。
自適應、分代的、中止——複製、標記——清掃 垃圾回收方法:
中止——複製:先暫停程序的運行,而後將全部活的對象從當前堆複製到另外一個堆,沒有被複制的都是垃圾。當對象從一個堆複製到另外一個堆,它們的排列是一個挨着一個的,因此新堆保持緊湊排列。
標記——清掃:遍歷全部的引用,找出全部活的對象,而後對它們進行標記,這個過程不會回收任何對象,只有所有標記工做完成時纔開始清除工做。沒有被標記的對象將會被釋放,不發生任何複製動做,因此剩下的堆空間不是連續的。
建立了幾個對象?
String s="abc"; 建立了幾個對象?
毫無疑問,這裏面只建立了一個對象——「abc";
String s1="abc"; String s2=s1;建立了幾個對象?
仍然只有一個對象——「abc";
String s1="abc"; String s2=」abc";建立了幾個對象?
這裏仍然只有一個對象——「abc";
String s="abc"+"def";建立了幾個對象?
注意,這裏建立了三個對象:「abc"、」def"、「abcdef";
String s=new String("abc");建立了幾個對象?
你們也都知道是兩個對象。其實是"abc"自己就是文字池中的一個對象,在運行new String()時,把文字池即pool中的字符串"abc"複製到堆中,並把這個對象的應用交給s,因此建立了兩個String對象,一個在pool中,一個在堆中。
String s1=new String("abc");String s2=new String("abc");建立了幾個對象?
三個對象。"abc"是文字池中的一個對象,而後又在堆中用new String()建立了兩個對象。
===== 局部變量和成員變量 =====
1.
局部變量:方法體內有效。
成員變量:不管何種權限,均可以在類的全部方法中使用。
2.
局部變量:
不可自動初始化,要求在程序中顯示地給其賦值,
只有當方法被調用執行時,局部變量才被分配內存空間,
調用完畢後,所佔空間釋放。
3.
成員變量:
可自動初始化,數值型爲0,邏輯型爲false,引用型
爲null,可在聲明是進行,也可在方法中實現,但不能在聲明
和方法之間進行賦值
4.基本類型的局部變量若是值相同也都指向同一個地址。只有後面的和前面的不想等的時候纔會指向新的地址,這就是爲何基本局部變量相互之間不會影響的緣由。
5.局部變量和形參帶final。
在一個線程A中開起另外一個線程B,若是線程B要使用線程A的局部變量,那麼A的局部變量須要定義成final。理由:局部變量是線程內部共享的,每個線程內的不能訪問其餘線程的局部變量,可是上訴的狀況卻違背了這一原則,那麼加上final爲何就能夠了呢?緣由是加上final以後,在建立B線程的時候會把final標記的變量做爲線程B的構造方法的參數傳給B,如此一來就解決了此問題,這是一個比較巧妙的作法,經過class文件反編譯能夠看出這個道理
6.Java的String變量比較特殊,他所定義的變量的值所有都存放在常量池裏面,無論是否是final的。下面的結果所有爲true。而且若是相等都指向同一個地址。
public class StringTest {
private static String s1 = "123";
static final String s2 = "123";
public static void main(String[] args) {
String s3 = "123";
final String s4 = "123";
System.out.println(s1 == s2);
System.out.println(s3 == s4);
System.out.println(s1 == s3);
}
}
一個咱們編寫的java源碼類(機器碼)要想被正式運行,必須先編譯成字節碼(class文件),而後虛擬機通過類加載過程後才能真正使用。
而這個類加載過程包括了對字節碼 加載 驗證 準備 解析 初始化等過程。在這個過程當中,咱們會對咱們定義的成員變量進行兩次初始化,一次賦默認初值(0值,boolean賦爲false),一次賦咱們定義的初值,如:
class Test{
int a = 2;
}
先賦0,再賦2.
而方法,須要進棧執行,這個過程是沒有賦初值過程的。成員變量和局部變量賦不賦初值的緣由就在這裏,成員變量咱們不主動初始化賦初值,有大佬照顧,給他賦零值,而局部變量,姥姥不疼,舅舅不愛,必須自力更生,咱們必須主動初始化進行賦值,不然編譯器不經過。
若是再也不須要某個對象時,也就是不引用該對象,能夠將引用類型變量賦值null,表示引用爲空。 若建立的對象沒有被任何變量所引用,JVM會自動回收它所佔的空間。
===== 靜態相關 =====
1.
public class test { //1.第一步,準備加載類
public static void main(String[] args) {
new test(); //4.第四步,new一個類,但在new以前要處理匿名代碼塊
}
static int num = 4; //2.第二步,靜態變量和靜態代碼塊的加載順序由編寫前後決定
{
num += 3;
System.out.println("b"); //5.第五步,按照順序加載匿名代碼塊,代碼塊中有打印
}
int a = 5; //6.第六步,按照順序加載變量
{ // 成員變量第三個
System.out.println("c");//7.第七步,按照順序打印c
}
test() { // 類的構造函數,第四個加載
System.out.println("d");//8.第八步,最後加載構造函數,完成對象的創建
}
static { // 3.第三步,靜態塊,而後執行靜態代碼塊,由於有輸出,故打印a
System.out.println("a");
}
static void run() // 靜態方法,調用的時候才加載// 注意看,e沒有加載
{
System.out.println("e");
}
}
通常順序:靜態塊(靜態變量)——>成員變量——>構造方法——>靜態方法
靜態代碼塊(只加載一次) 二、構造方法(建立一個實例就加載一次)三、靜態方法須要調用纔會執行,因此最後結果沒有e
2.
public class Print {
public Print(String s){
System.out.print(s + " ");
}
}
public class Parent{
public static Print obj1 = new Print("1");
public Print obj2 = new Print("2");
public static Print obj3 = new Print("3");
static{
new Print("4");
}
public static Print obj4 = new Print("5");
public Print obj5 = new Print("6");
public Parent(){
new Print("7");
}
}
public class Child extends Parent{
static{
new Print("a");
}
public static Print obj1 = new Print("b");
public Print obj2 = new Print("c");
public Child (){
new Print("d");
}
public static Print obj3 = new Print("e");
public Print obj4 = new Print("f");
public static void main(String [] args){
Parent obj1 = new Child ();
Parent obj2 = new Child ();
}
}
執行main方法,程序輸出順序爲: 1 3 4 5 a b e 2 6 7 c f d 2 6 7 c f d
輸出結果代表,程序的執行順序爲:
若是類尚未被加載:
一、先執行父類的靜態代碼塊和靜態變量初始化,而且靜態代碼塊和靜態變量的執行順序只跟代碼中出現的順序有關。
二、執行子類的靜態代碼塊和靜態變量初始化。
三、執行父類的實例變量初始化
四、執行父類的構造函數
五、執行子類的實例變量初始化
六、執行子類的構造函數
若是類已經被加載:
則靜態代碼塊和靜態變量就不用重複執行,再建立類對象時,只執行與實例相關的變量初始化和構造方法。
修飾變量:static 數據類型 變量名
修飾方法:【訪問權限修飾符】 static 方法返回值 方法名(參數列表)
特色:
1.static能夠修飾變量,方法
2.被static修飾的變量或者方法是獨立於該類的任何對象,也就是說,這些變量和方法不屬於任何一個實例對象,而是被類的實例對象所共享。
3.在類被加載的時候,就會去加載被static修飾的部分。
4.被static修飾的變量或者方法是優先於對象存在的,也就是說當一個類加載完畢以後,即使沒有建立對象,也能夠去訪問。
被static修飾的成員變量叫作靜態變量,也叫作類變量,說明這個變量是屬於這個類的,而不是屬因而對象,沒有被static修飾的成員變量叫作實例變量,說明這個變量是屬於某個具體的對象的。
實例變量:每次建立對象,都會爲每一個對象分配成員變量內存空間,實例變量是屬於實例對象的,在內存中,建立幾回對象,就有幾份成員變量。
靜態變量:靜態變量因爲不屬於任何實例對象,是屬於類的,因此在內存中只會有一份,在類的加載過程當中,JVM爲靜態變量分配一次內存空間。
被static修飾的方法也叫作靜態方法,由於對於靜態方法來講是不屬於任何實例對象的,那麼就是說在靜態方法內部是不能使用this的,由於既然不屬於任何對象,那麼就更談不上this了。
對象.靜態變量(不推薦的)
若是某個成員變量是被全部對象所共享的,那麼這個成員變量就應該定義爲靜態變量。
在靜態方法中沒有this關鍵字由於靜態是隨着類的加載而加載,而this是隨着對象的建立而存在的。靜態比對象優先存在。
靜態能夠訪問靜態的,可是靜態不能訪問非靜態的。
非靜態的能夠去訪問靜態的。
===== 修飾符 =====
public,公共修飾符,被其修飾的類、屬性或方法在項目中任意類中訪問。
protected,保護修飾符,被其修飾的類、屬性或方法在當前類所屬包或當前類的子類中可訪問。
default,默認修飾符,沒有明確聲明修飾符時默認採用此修飾符,被其修飾的類、屬性或方法只能被當前類所屬包中的類訪問。
private,私有修飾符,被其修飾的類、屬性或方法僅在當前類中可訪問。
特別注意的內容:
default 修飾的類、屬性或方法若是是在不一樣包下,即便是子類也沒法訪問。
protected 修飾的類、屬性或方法能夠在不一樣包子類中訪問,可是沒法經過該子類的實例進行訪問。例如 A 是 B 的父類,二者分屬不一樣包下,A 中的方法 a() 使用 protected 進行修飾,此時咱們能夠在 B 的方法 b() 中調用 super.a(),可是沒法經過實例化進行調用, new B().a() 則沒法調用。
===== 做用域 =====
在同一做用域範圍的包裹下成員變量名和局部變量名是能夠變量名相同的
在同一個做用域範圍的包裹下局部變量和局部變量不能夠變量名相同(做用域內不能重複命名)
在方法中使用變量的時候若是不指明使用成員變量仍是局部變量,那麼默認的就是使用局部的那個變量,可是若是局部變量超出了它自己的做用域範圍則會失效,被JVM垃圾回收,那麼則能夠重複命名此變量,並使用最新定義的這個局部變量。
對象的做用域
Java對象不具有與主類型同樣的存在時間。用new關鍵字建立一個Java對象的時候,它會超出做用域的範圍以外。因此倘若使用下面這段代碼:
{
String s = new String("a string");
} /* 做用域的終點 */
那麼句柄s,也就是引用會在做用域的終點處消失。然而,s指向的String對象依然佔據着內存空間。在上面這段代碼裏,咱們沒有辦法繼續使用這個對象,由於指向它的惟一一個句柄已經超出了做用域的邊界。
這樣形成的結果是:對於用new建立的對象,只要咱們願意,它們就會一直保留下去。這個編程問題在C和C++裏特別突出。在C++裏遇到的麻煩最大:因爲不能從語言得到任何幫助,因此在須要對象的時候,根本沒法肯定它們是否可用。並且最麻煩的是,在C++裏,一旦完成工做,必須保證將對象手動清除。
這樣便帶來了一個有趣的問題。假如 Java 讓對象依然故我,怎樣才能防止它們大量充斥內存,並最終形成程序的「凝固」呢。在 C++裏,這個問題最令程序員頭痛。但 Java 之後,狀況卻發生了改觀。 Java 有一個特別的「垃圾收集器」,它會查找用 new 建立的全部對象,並辨別其中哪些再也不被引用。隨後,它會自動釋放由那些閒置對象佔據的內存,以便能由新對象使用。這意味着咱們根本沒必要操心內存的回收問題。只需簡單地建立對象,一旦再也不須要它們,它們就會自動離去。這樣作可防止在 C++裏很常見的一個編程問題:因爲程序員忘記釋放內存形成的「內存溢出」
===== 常量 =====
Java中的常量池實際上分爲兩種形態:
靜態常量池:即*.class文件中的常量池,class文件中的常量池不只僅包含字符串(數字)字面量,還包含類、方法的信息,佔用class文件絕大部分空間。
運行時常量池:則是jvm虛擬機在完成類裝載操做後,將class文件中的常量池載入到內存 中,並保存在方法區中,咱們常說的常量池,就是指方法區中的運行時常量池。
定義
常量表明程序運行過程當中不能改變的值。
語法格式
[訪問修飾符] final 數據類型 常量名稱 = 值;關鍵字final不可缺,常量名稱要求必須大寫。其中中括號內容是可選項,
特色
1.有關鍵字final
2.在Java編碼規範中,要求常量名必須大寫
3.必須聲明,後使用。能夠在聲明時賦值,也能夠在使用前任什麼時候間賦值,但只能賦值一次。
注意:全局常量能夠不手動賦值,系統會初始化這些全局常量的值。局部常量必須賦值,不然使用時編譯錯誤
做用
1.表明常數(也稱經常使用的值),在項目開發實踐中,會把這些經常使用到的值抽取出來放到一個類中方便其餘類中調用這些常量,這樣既能夠防止疏忽出錯,還便於之後維護代碼,也就是說只要修改個地方就能夠了。
2.加強程序的可讀性。使用一些有意義的名稱代替一些值。例如 DOWN一看就知道這個常數是表明向下的意思。
例子
final double PI = 3.14;
public final double PI = 3.14;
在Java語法中,常量也能夠首先聲明,而後再進行賦值,可是隻能賦值一次,
final int UP;UP = 1;
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // true
System.out.println(s1 == s4); // false
System.out.println(s1 == s9); // false
System.out.println(s4 == s5); // false
System.out.println(s1 == s6); // true
s1 == s2這個很是好理解,s一、s2在賦值時,均使用的字符串字面量,說白話點,就是直接把字符串寫死,在編譯期間,這種字面量會直接放入class文件的常量池中,從而實現複用,載入運行時常量池後,s一、s2指向的是同一個內存地址,因此相等。
s1 == s3這個地方有個坑,s3雖然是動態拼接出來的字符串,可是全部參與拼接的部分都是已知的字面量,在編譯期間,這種拼接會被優化,編譯器直接幫你拼好,所以String s3 = "Hel" + "lo";在class文件中被優化成String s3 = "Hello";,因此s1 == s3成立。
s1 == s4固然不相等,s4雖然也是拼接出來的,但new String("lo")這部分不是已知字面量,是一個不可預料的部分,編譯器不會優化,必須等到運行時才能夠肯定結果,結合字符串不變定理,鬼知道s4被分配到哪去了,因此地址確定不一樣
s1 == s9也不相等,道理差很少,雖然s七、s8在賦值的時候使用的字符串字面量,可是拼接成s9的時候,s七、s8做爲兩個變量,都是不可預料的,編譯器畢竟是編譯器,不可能當解釋器用,因此不作優化,等到運行時,s七、s8拼接成的新字符串,在堆中地址不肯定,不可能與方法區常量池中的s1地址相同。
s4 == s5已經不用解釋了,絕對不相等,兩者都在堆中,但地址不一樣。
s1 == s6這兩個相等徹底歸功於intern方法,s5在堆中,內容爲Hello ,intern方法會嘗試將Hello字符串添加到常量池中,並返回其在常量池中的地址,由於常量池中已經有了Hello字符串,因此intern方法直接返回地址;而s1在編譯期就已經指向常量池了,所以s1和s6指向同一地址,相等。
至此,咱們能夠得出三個很是重要的結論:
必需要關注編譯期的行爲,才能更好的理解常量池。
運行時常量池中的常量,基原本源於各個class文件中的常量池。
程序運行時,除非手動向常量池中添加常量(好比調用intern方法),不然jvm不會自動添加常量到常量池。
以上所講僅涉及字符串常量池,實際上還有整型常量池、浮點型常量池等等,但都大同小異,只不過數值類型的常量池不能夠手動添加常量,程序啓動時常量池中的常量就已經肯定了,好比整型常量池中的常量範圍:-128~127,只有這個範圍的數字能夠用到常量池。
首先說明一點,在java 中,直接使用==操做符,比較的是兩個字符串的引用地址,並非比較內容,比較內容請用String.equals()。java