java學習5:類
1、 stdout、stdin、 stderr
- System.out:代表標準輸出裝置
- System.in: 代表標準輸入裝置
Scanner input = new Scanner(System.in); // 建立Scanner型別物件,讀取來自System.in的輸入
1、變數型別
public class Variable{ static int allClicks=0; // 類變數 String str="hello world"; // 例項變數 public void method(){ int i =0; // 區域性變數 } }
Java 區域性變數(類似於C/C++中自動變數,但C/c++中沒有初始化的自動變數值預設為0)
區域性變數是在方法內部定義的變數,如果區域性變數和一個類變數(例項變數或靜態變數)名相同,區域性變數優先。同名類變數將被隱藏,是為隱藏資料域
區域性變數作用域在語句塊內或方法內,但不可重複定義,會出錯
區域性變數宣告在方法、構造方法或者語句塊中;
區域性變數在方法、構造方法、或者語句塊被執行的時候建立,當它們執行完成後,變數將會被銷燬;
訪問修飾符不能用於區域性變數;
區域性變數只在宣告它的方法、構造方法或者語句塊中可見;
區域性變數是在棧上分配的。
區域性變數沒有預設值,所以區域性變數被聲明後,必須經過初始化,才可以使用。
例項變數(類變數或資料域,類似於python中例項屬性)
例項變數宣告在一個類中,但在方法、構造方法和語句塊之外;
當一個物件被例項化之後,每個例項變數的值就跟著確定;
例項變數在物件建立的時候建立,在物件被銷燬的時候銷燬;
例項變數的值應該至少被一個方法、構造方法或者語句塊引用,使得外部能夠通過這些方式獲取例項變數資訊;
例項變數的作用域是整個類:例項變數可以宣告在使用前或者使用後;
訪問修飾符可以修飾例項變數;
例項變數對於類中的方法、構造方法或者語句塊是可見的。一般情況下應該把例項變數設為私有。通過使用訪問修飾符可以使例項變數對子類可見;
例項變數具有預設值。數值型變數的預設值是0,布林型變數的預設值是false,引用型別變數的預設值是null。變數的值可以在宣告時指定,也可以在構造方法中指定;
靜態變數(類變數或資料域),類似於python中的類屬性
類變數也稱為靜態變數,在類中以 static 關鍵字宣告,但必須在方法之外。
無論一個類建立了多少個物件,類只擁有類變數的一份拷貝。
靜態變數除了被宣告為常量外很少使用,靜態變數是指宣告為 public/private,final 和 static 型別的變數。靜態變數初始化後不可改變。
靜態變數儲存在靜態儲存區。經常被宣告為常量,很少單獨使用 static 宣告變數。
靜態變數在第一次被訪問時建立,在程式結束時銷燬。
靜態變數的作用域是整個類。但為了對類的使用者可見,大多數靜態變數宣告為 public 型別。
預設值和例項變數相似。數值型變數預設值是 0,布林型預設值是 false,引用型別預設值是 null。變數的值可以在宣告的時候指定,也可以在構造方法中指定。此外,靜態變數還可以在靜態語句塊中初始化。
靜態變數可以通過:ClassName.VariableName的方式訪問。也可以使用ObejectReference.VariableName 訪問,或者同一個類中直接使用VaribaleName方法
類變數被宣告為 public static final 型別時,類變數名稱一般建議使用大寫字母。如果靜態變數不是 public 和 final 型別,其命名方式與例項變數以及區域性變數的命名方式一致。
變數的作用域
1、區域性變數的作用域從宣告變數的地方開始,直到包含該變數的塊結束為止。Java和C/C++都有塊作用域,而python則沒有。
2、引數也是一個區域性變數,方法的引數作用域覆蓋整個方法。
3、for迴圈頭中初始動作部分宣告的變數,其作用域是整個for迴圈;而迴圈體內宣告的變數,其作用域只限於迴圈體內從它的宣告處開始到包含該變數的快結束位置:
4、可在不同的程式碼塊中宣告同名的區域性變數;不能再且套塊中或同一塊中兩次宣告同一個區域性變數(在java中這隻會報錯,而在C中則前者會被後者覆蓋)。
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) { // i的作用域是整個for迴圈
System.out.println("i = " + i);
int j = 5; // j的作用域是到for迴圈塊結束
System.out.println("j = " + j);
System.out.println("i = " + i);
}
}
}
2、修飾符
2.1、訪問控制修飾符
Java中,可以使用訪問控制符來保護對類、變數、方法和構造方法的訪問。Java 支援 4 種不同的訪問許可權。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Lfd99egm-1609685038156)(https://i.imgur.com/xhVFwp2.png)]
- default 預設訪問修飾符:
對同一包內的類可見,不使用任何修飾符。使用物件:類、介面、變數、方法。
接口裡的變數都隱式宣告為 public static final,而接口裡的方法預設情況下訪問許可權為 public
- private 私有訪問修飾符(類似python的私有變數):
物件只能被所屬類訪問。使用物件:變數、方法。 注意:不能修飾類和介面
private宣告的物件不能被子類繼承,也不能被子類訪問
隱藏類的實現細節和保護類的資料:
宣告為私有訪問的變數只能通過類中公共的getter方法訪問和setter方法修改
- public 公有訪問修飾符:
對所有類可見。使用物件:類、介面、變數、方法
類的所有公有方法和變數都能被子類繼承
- protected 受保護的訪問修飾符:
對同一包內的類和所有子類可見。使用物件:變數、方法。 注意:不能修飾類和介面。
子類與基類在同一包中:被宣告為 protected 的變數、方法和構造器能被同一個包中的任何其他類訪問;
子類與基類不在同一包中:那麼在子類中,子類例項可以訪問其從基類繼承而來的 protected 方法,而不能訪問基類例項的protected方法。
介面及介面的成員變數和成員方法不能宣告為 protected:
2.1.1、 訪問控制和繼承
- 父類中宣告為 public 的方法在子類中也必須為 public。
- 父類中宣告為 protected 的方法在子類中要麼宣告為 protected,要麼宣告為 public,不能宣告為 private。
- 父類中宣告為 private 的方法,不能夠被繼承。
2.3、 非訪問修飾符
- static 修飾符,用來修飾類方法和類變數
宣告靜態變數和靜態方法,它們是類所有,類及其例項物件都可訪問它們,“類名.靜態變數或方法”、“物件名.靜態變數或方法”
宣告靜態變數(即類變數):
用於宣告獨立於物件的靜態變數(類變數),無論例項多少物件,靜態變數只有一份拷貝,靜態變數為所有例項所共享。
靜態變數儲存在一個公共的記憶體地址:某個例項修改靜態變數,那麼同類的所有物件都會收到影響。-- 和python大大的不同:無法修改類變數
區域性變數不能宣告為static變數
宣告靜態方法:
宣告獨立於物件的靜態方法,靜態方法不能直接訪問類的非靜態變數或非靜態方法
只能例項化類後,使用例項物件呼叫物件的非靜態變數和非靜態方法。
靜態方法從引數列表得到資料,然後計算這些資料
public class Main {
int aa = 5;
public Main(int caa) {
aa = caa;
}
public static void main(String[] args) {
int[] intArr1 = null;
printData(intArr1);
int[] intArr2 = {1, 2, 3, 4, 5};
printData(intArr2);
Main cc = new Main(88);
System.out.println(cc.aa);
cc.test();
}
private static void printData(int[] intArr) {
if (intArr == null) {
System.out.println("資料異常");
return ;
}
}
public void test() {
System.out.println("1234");
}
}
- final 修飾符,
用來修飾類、方法和變數,final 修飾的類不能夠被繼承,修飾的方法不能被繼承類重新定義,修飾的變數為常量,是不可修改的。
- abstract 修飾符,用來建立抽象類和抽象方法(類似於python的抽象基類)
抽象性類不能用於例項化物件,抽象類可以包含抽象方法和非抽象方法,而抽象方法沒有實現。繼承抽象類的具體子類應實現所有抽象方法。
public abstract class SuperClass{
abstract void m(); //抽象方法
}
class SubClass extends SuperClass{
//實現抽象方法
void m(){
.........
}
}
- synchronized 修飾符
synchronized 關鍵字宣告的方法同一時間只能被一個執行緒訪問。synchronized 修飾符可以應用於四個訪問修飾符。
public synchronized void showDetails(){
.......
}
-
transient 修飾符
序列化的物件包含被 transient 修飾的例項變數時,java 虛擬機器(JVM)跳過該特定的變數。
該修飾符包含在定義變數的語句中,用來預處理類和變數的資料型別。 -
volatile 修飾符
volatile 修飾的成員變數在每次被執行緒訪問時,都強制從共享記憶體中重新讀取該成員變數的值。而且,當成員變數發生變化時,會強制執行緒將變化值回寫到共享記憶體。這樣在任何時刻,兩個不同的執行緒總是看到某個成員變數的同一個值。
一個 volatile 物件引用可能是 null。
構造方法
每個類都有構造方法,如果沒有顯示的為類定義構造方法,Java編譯器會為類提供一個預設的構造方法.
構造方法沒有返回值型別,連void也沒有
建立物件時會呼叫構一個構造方法,構造方法必須與類同名,一個類可以有多個構造方法(例項物件時,依據引數的不同調用不同的構造方法)
public class Puppy{
public Puppy(){
}
public Puppy(String name){
// 這個構造器僅有一個引數:name
}
建立物件
類是一種引用型別。當類的例項化物件引用計數為0時,就會被自動垃圾回收。
宣告一個類的物件引用變數: “物件型別 物件名”。 引用變數沒有引用任何物件時,預設值為null。
例項化建立一個物件: “new 物件型別(引數或無引數)”
初始化:使用new建立物件時,會呼叫建構函式初始化物件
public class Puppy{
public Puppy(String name){
//這個構造器僅有一個引數:name
System.out.println("小狗的名字是 : " + name );
}
public static void main(String[] args){
// 下面的語句將建立一個Puppy物件
Puppy myPuppy = new Puppy( "tommy" );
}
}
陣列被看作是物件,因為使用new建立的。陣列變數是包含陣列引用的變數
String是在java中是類,所有也是建立物件。
訪問例項變數和方法
通過已建立物件來訪問成員變數和成員方法
/* 例項化物件 */
Object referenceVariable = new Constructor();
/* 訪問類中的變數 */
referenceVariable.variableName;
/* 訪問類中的方法 */
referenceVariable.methodName();
基本型別變數:對應記憶體所儲存的是基本型別值
引用型別變數: 對應記憶體所儲存的是一個引用,即物件的儲存地址。為某個物件的引用變數賦null值,物件的引用計數減一。
原始檔宣告規則
一個原始檔中只能有一個 public 類
一個原始檔可以有多個非 public 類
如果一個類定義在某個包中,那麼package語句應在原始檔的行首
如果原始檔包含 import 語句,那麼應該放在 package 語句和類定義之間。如果沒有 package 語句,那麼 import 語句應該在原始檔中最前面
包的使用:組織類,區別類的namespace
this 引用
關鍵字this引用物件自身,this類似於python的self。
- 用this關鍵字引用物件的例項變數
public class Main {
private int i = 5;
public static void main(String[] args) {
Main cc = new Main();
System.out.println(cc.getter());
}
public int getter() {
return this.i; // 顯式地引用物件例項變數成員,通常this是省略掉的
}
}
- 使用this引用隱藏資料域
隱藏資料域的兩種情況:
1、指類變數(例項變數和靜態變數都是類變數)和區域性變數同名時,類變數將被隱藏。
2、資料域名和方法的引數名相同時時,該資料域名在該方法中也會被隱藏,因為實際操作的是作為區域性變數的同名實參。
情況一,類變數和區域性變數同名時,使用關鍵字可以引用物件的隱藏資料域:
public class Main() {
public static void main(String[] args) {
SimpleCircle circle = new SimpleCircle();
circle.p();
}
}
class SimpleCircle {
double radius;
SimpleCircle() {
radius = 1.0;
}
public void p() {
double radius = 5.0; // 區域性變數和資料域同名,物件的資料域變數radisu將被隱藏
System.out.println(radius); // 顯示區域性變數radius的值
System.out.println(this.radius); // 使用this引用隱藏域變數radius
}
情況二,setf方法中,將資料域名作為引數,使用關鍵字可以引用物件的隱藏資料域:
public class Main() {
public static void main(String[] args) {
F f1 = new F();
f1.setI(2);
System.out.println(f1.i);
F f2 = new F();
f2.setI(13);
System.out.println(f2.i);
F.setK(33);
System.out.println(F.k);
}
}
class F {
public int i = 5;
public static double k = 0;
public void setI(int i) { // 引數名和資料域名i相同,資料域變數i將被隱藏
this.i = i; // 需要使用thsi引用隱藏的資料域名,才能給其設定新值
}
public static void setK(double k) { // 引數名和資料域名k相同
F.k = k; // 使用"類名.靜態變數"的方式引用隱藏的靜態變數k
}
}
setI方法使用和資料域名相同的引數名,這變導致資料域變數i被隱藏,要設定資料域變數必須顯示的指定this引用物件自身。
如果引數名與資料域名不相同則不會導致資料域隱藏。
- 使用this讓構造方法呼叫同一個類的另一個構造方法
public class Main {
public static void main(String[] args) {
F f = new F();
System.out.println(f.radius);
}
}
class F {
public double radius;
public F(double radius) {
this.radius = radius;
}
public F() {
this(1.0); // this關鍵字呼叫另一個重構的構造方法
}
}
構造方法中,this語句應在任何其它可指向語句之前出現
簡化程式碼:無引數或引數少的構造方法應使用"this(引數列表)"呼叫引數多的構造方法
類中出現重名時,變數具有最高引用級別
一個變數或一個型別可以遮蓋一個包
一個變數可以遮蓋具有相同名字的一個型別,只要它們都在同一個範圍內:
public class Main {
public static void main(String[] args) {
System.out.println(X.Y.Z); // 顯示"White"
}
}
class X {
static C Y = new C(); // 變數Y會被優先引用,而不會是下面的型別Y
static class Y {
static String Z = "Black";
}
}
class C {
String Z = "White";
}
繼承
extends:只要透過 extends 關鍵字,子類別將可以擁有父類別的所定義屬性欄位、方法等功能。
class 父類名{
// todo sth...
}
class 子類名 extends 父類名{
// todo sth...
}
super
super 關鍵字:通過 super 關鍵字來存取父類屬性或呼叫父類的方法。
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat();
}
}
class Animal {
void eat() {
System.out.println("Aninal: eat");
}
}
class Dog extends Animal {
void eat() {
System.out.println("Dog: eat");
super.eat(); // 呼叫父類方法eat()
}
}
/* output
Dog: eat
Animal: eat
*/
與python對比:
python的super是超類代理物件,可以選中繼承鏈中任何一個超類,並呼叫它的方法。
多層初始化
例項化物件時,如果是多層繼承,則父類先初始化,再繼續子類初始化
public class Main {
public static void main(String[] args) {
C c = new C();
}
}
class A{
public A(){
System.out.println("執行A構造方法...");
}
}
class B extends A{
public B(){
System.out.println("執行B構造方法...");
}
}
class C extends B{
public C(){
System.out.println("執行C構造方法...");
}
}
/* output
執行A構造方法...
執行B構造方法...
執行C構造方法...
*/
與python的比較:
python僅會自動呼叫當前子類的__init__構造方法,而不會呼叫父類的構造方法,你需要主動呼叫父類的構造方法。
靜態繫結與動態繫結
繫結指的是一個方法的呼叫與方法所在的類(方法主體)關聯起來
繫結分為靜態繫結和動態繫結
靜態繫結使用的是類資訊,而動態繫結使用的是物件資訊
- 靜態繫結
靜態繫結發生編譯時,由編譯器執行。
java當中的方法只有final,static,private和構造方法都是靜態繫結
java的屬性(例項變數和靜態變數)都是靜態繫結(讓我們在編譯器就發現錯誤。提高程式執行效率)
一個方法不可被繼承或者繼承後不可被覆蓋,那麼這個方法就採用的靜態繫結
- 動態繫結
在程式執行時根據具體物件的型別進行繫結。方法除了final,static,private
和構造方法是靜態繫結外,其他的方法(虛方法)全部為動態繫結。
對方法採取動態繫結是為了實現多型,多型是java的一大特色。多型也是面向物件的關鍵技術之一,所以java是以效率為代價來實現多型這是很值得的。
而動態繫結的典型發生在父類和子類的轉換宣告之下:
比如:Parent p = new Children();
其具體過程細節如下:
1:編譯器檢查物件的宣告型別和方法名。
假設我們呼叫x.f(args)方法,並且x已經被宣告為C類的物件,那麼編譯器會列舉出C 類中所有的名稱為f 的方法和從C 類的超類繼承過來的f 方法。
2:接下來編譯器檢查方法呼叫中提供的引數型別。
如果在所有名稱為f 的方法中有一個引數型別和呼叫提供的引數型別最為匹配,那麼就呼叫這個方法,這個過程叫做“過載解析”。
3:當程式執行並且使用動態繫結呼叫方法時,虛擬機器必須呼叫同x所指向的物件的實際型別相匹配的方法版本。
假設實際型別為D(C的子類),如果D類定義了f(String)那麼該方法被呼叫,否則就在D的超類中搜尋方法f(String),依次類推
若一種語言實現了後期繫結,同時必須提供一些機制,可在執行期間判斷物件的型別,並分別呼叫適當的方法。也就是說,編譯器此時依然不知道物件的型別,但方法呼叫機制能自己去調查,找到正確的方法主體。不同的語言對後期繫結的實現方法是有所區別的。但我們至少可以這樣認為:它們都要在物件中安插某些特殊型別的資訊。
動態繫結的過程:
1、虛擬機器提取物件的實際型別的方法表;
2、虛擬機器搜尋方法簽名;
3、呼叫方法。
動態繫結為解決實際的業務問題提供了很大的靈活性,是一種非常優美的機制。
驗證屬性是靜態繫結,而虛擬函式方法是動態繫結:
在向上轉型的情況下,物件的方法可以找到子類,而物件的屬性(成員變數)還是父類的屬性(子類對父類成員變數的隱藏)
public class Main {
public static void main(String[] args) {
Super sup = new Sub();
System.out.print(sup.field+" "+sup.getField());//對於域的訪問由編譯器決定(靜態繫結)
}
}
class Super {
public int field=0; // 例項變數是靜態繫結
public int getField() {
System.out.println("Super");
return field;
}
}
class Sub extends Super {
public int field=1;
public int getField() { // 該方法是動態繫結
System.out.println("Sub");
return field;
}
public int getSuperField(){
return super.field;
}
}
/* output
Sub
0 1
*/
向上轉型和向下轉型
Java轉型的核心是父類引用指向子類物件(注意:子類引用不能指向父類物件),轉型的作用是提升程式碼的可擴充套件性
向上轉型(upcasting)
將子類物件直接賦給父類引用叫upcasting向上轉型,向上轉型不用強制轉換:
Father f1 = new Son(); // Father是父類,Son是子類:這是 upcasting (向上轉型),父類引用f1指向子類物件
向上原型後,父類引用指向子類物件:
JAVA 虛擬機器呼叫一個類方法(靜態方法)或類變數(靜態變數或例項變數),它會基於物件引用的型別(通常在編譯時可知)來選擇所呼叫的方法或變數。相反,當虛擬機器呼叫一個例項方法時,它會基於物件實際的型別(只能在執行時得知)來選擇所呼叫的方法,這就是動態繫結,是多型的一種
向上轉型時會遺失除與父類物件共有的其他方法:
public class Main {
public static void main(String[] args) {
Animal b = new Bird(); // 向上轉型
b.eat();
//! error: b.fly(); b雖指向子類物件,但此時丟失fly()方法
}
}
class Animal {
public void eat(){
System.out.println("animal eatting...");
}
}
class Bird extends Animal{
public void eat(){
System.out.println("bird eatting...");
}
public void fly(){
System.out.println("bird flying...");
}
}
/* output
bird eatting...
*/
父類為形參,呼叫子類作為實參,就是利用了向上轉型。這樣使程式碼變得簡潔,並體現Java多型和抽象的程式設計思想:
public class Main {
public static void main(String[] args) {
sleep(new Male()); // 向上轉型:實參是子類,而形參是父類
sleep(new Female());
}
public static void sleep(Human h) { /* 引數是父類*/
h.sleep();
}
}
class Human {
public void sleep() {
System.out.println("父類人類 sleep..");
}
}
class Male extends Human {
@Override
public void sleep() {
System.out.println("男人 sleep..");
}
}
class Female extends Human {
@Override
public void sleep() {
System.out.println("女人 sleep..");
}
}
/* output
男人 sleep..
女人 sleep..
*/
向下轉型(downcasting)
將指向子類物件的父類引用賦給子類引用叫向下轉型(downcasting),要強制轉換:
Father f1 = new Son();
Son s1 = (Son)f1; // 這是 downcasting (向下轉型)
public class Main {
public static void main(String[] args) {
Super sup = new Sub();
Sub sup1 = (Sub)sup; // 安全的向下轉型,將父類引用物件轉為子類物件
System.out.println(sup1.field); // 呼叫的子類的屬性
System.out.println(sup1.getSuperField()); // 呼叫子類的方法
System.out.println(sup1.getField()); // 呼叫的是子類的方法
}
}
class Super {
public int field=0;
public int getField() {
System.out.println("Super");
return field;
}
}
class Sub extends Super {
public int field=1;
public int getField() {
System.out.println("Sub");
return field;
}
public int getSuperField(){
return super.field;
}
}
/* output
1
0
Sub
1
*/
- 不安全的向下轉型
Fruit f=new Fruit();
Apple aaa=(Apple)f; //-不安全的---向下轉型,編譯無錯但會執行會出錯
aaa.myName();
aaa.myMore();
f是父類物件,子類的例項aaa肯定不能指向父類f啊~~~
Java為了解決不安全的向下轉型問題,引入泛型的概念
為了安全的型別轉換,最好先用 if(A instanceof B) 判斷一下
虛擬函式
虛擬函式的存在是為了多型。
C++中普通成員函式加上virtual關鍵字就成為虛擬函式
Java中其實沒有虛擬函式的概念,它的普通函式就相當於C++的虛擬函式,動態繫結是Java的預設行為。如果Java中不希望某個函式具有虛擬函式特性,可以加上final關鍵字變成非虛擬函式
Java抽象函式(純虛擬函式)
抽象函式或者說是純虛擬函式的存在是為了定義介面。
C++中純虛擬函式形式為:virtual void print() = 0;
python中純虛擬函式形式為: @abstractmethod裝飾的函式,也稱抽象方法
Java中純虛擬函式形式為:abstract void print();
Java抽象類
Java抽象類的存在是因為父類中既包括子類共性函式的具體定義,也包括需要子類各自實現的函式介面。抽象類中可以有資料成員和非抽象方法。
C++中抽象類只需要包括純虛擬函式,既是一個抽象類。如果僅僅包括虛擬函式,不能定義為抽象類,因為類中其實沒有抽象的概念。 python中抽象基類只需要包括純虛擬函式(即抽象方法),就是一個抽象基類。
Java抽象類是用abstract修飾宣告的類。
PS: 抽象類其實是一個半虛半實的東西,可以全部為虛,這時候變成介面。
Java介面
Java中介面的存在是為了形成一種規約。介面中不能有普通成員變數,也不能具有非純虛擬函式。
使用關鍵字interface定義一個介面:
public interface Info{ // 修飾符 interfase 介面名 {
final int AA = 5; /** 常量宣告 **/
void getInfo(); /** 方法簽名 **/
} //}
使用關鍵字implements讓類實現介面
class 類名 implements 介面1,介面2,...介面x{
//
}
介面定義和實現的程式碼示範:
interface A {
void getAInfoData();
}
interface B {
void getBInfoData();
}
class Main implements A, B {
public static void main(String[] args) {
}
@Override
public void getAInfoData() {
}
@Override
public void getBInfoData() {
}
}
介面繼承介面
介面也是類,也有繼承特性。可以將多個介面組合成一個介面,該介面可被簡單使用
public interface 介面名 extends 介面1, 介面2 {
// ...
}
程式碼示範:
interface A {
void getAData();
}
interface B extends A {
void getBData();
}
public class Main implements B { // 等同 介面定義和實現的程式碼示範中的"implements A, B"
public static void main(String[] args) {
}
@Override
public void getBData() {
}
@Override
public void getAData() {
}
}
C++中介面其實就是全虛基類。
python中介面就是抽象基類, 或鴨子型別。
Java中介面是用interface修飾的類,。