一、類及物件
1. 類的組成成分
- 屬性(成員變數,Field)
- 方法(成員方法,函式,Method)
2. 屬性
成員變數 vs 區域性變數
- 相同點:
- 遵循變數宣告的格式: 資料型別 變數名 = 初始化值
- 都有作用域
- 不同點:
- 宣告的位置的不同 :成員變數:宣告在類裡,方法外, 區域性變數:宣告在方法內,方法的形參部分,程式碼塊內
- 成員變數的修飾符有四個:public private protected 預設,區域性變數沒有修飾符,與所在的方法修飾符相同
- 初始化值:一定會有初始化值,成員變數:如果在宣告的時候,不顯式的賦值,那麼不同資料型別會有不同的預設初始化值。 區域性變數:一定要顯式的賦值。(區域性變數沒有預設初始化值)
- byte short int long ==>0
- float double ==>0.0
- char ==>空格
- boolean ==>false
- 引用型別變數==>null
- 二者在記憶體中存放的位置不同:成員變數存在於堆空間中;區域性變數:棧空間中
- 總結:
- 按照資料型別的不同:基本資料型別(8種) & 引用資料型別
- 按照宣告的位置的不同:成員變數 & 區域性變數
3. 方法
提供某種功能的實現
public void eat(){//方法體}
public String getName(){}
public void setName(String n){}
//格式:許可權修飾符 返回值型別(void:無返回值/具體的返回值) 方法名(形參){}
- 關於返回值型別
- void:表明此方法不需要返回值
- 有返回值的方法:在方法的最後一定有return + 返回值型別對應的變數
- 方法內可以呼叫本類的其他方法或屬性,但是不能在方法內再定義方法!
4. 物件導向程式設計的思想的落地法則一:
- 設計並建立類及類的成分
- 例項化類的物件
- 通過“物件.屬性”或"物件.方法"的形式完成某項功能
5. 類的初始化記憶體解析:記憶體劃分的結構
- 棧(stack):區域性變數 、物件的引用名、陣列的引用名
- 堆(heap):new 出來的“東西”(如:物件的實體,陣列的實體),含成員變數
- 方法區:含字串常量
- 靜態域:宣告為static的變數
6. 萬事萬物皆物件
- 在Java語言範疇中,我們都能將功能、結構等封裝到類中,通過類的實體化,來呼叫具體的功能結構
- Scanner、String
- 檔案:File
- 設計到Java語言與前端Html、後端的資料庫互動時,都體現為類、物件
7. 例題
/*
* 4. 物件陣列題目:
定義類Student,包含三個屬性:學號number(int),年級state(int),成績score(int)。
建立20個學生物件,學號為1到20,年級和成績都由隨機數確定。
問題一:列印出3年級(state值為3)的學生資訊。
問題二:使用氣泡排序按學生成績排序,並遍歷所有學生資訊
提示:
1) 生成隨機數:Math.random(),返回值型別double;
2) 四捨五入取整:Math.round(double d),返回值型別long。
*
*
* // 兩位數的,隨機數 10 - 99
公式:【a,b】 : Math.random()*(b-a+1)+a 再強轉資料型別。
*
*/
public class StudentTest {
public static void main(String[] args) {
// 宣告Student型別的陣列
Student[] stu = new Student[20];
for (int i = 0; i < stu.length; i++) {
// 給陣列元素賦值
stu[i] = new Student();
stu[i].number = i + 1;
// [1,6]
stu[i].state = (int) (Math.random() * (6 - 1 + 1) + 1);
// [0,100]
stu[i].score = (int) (Math.random() * (100 - 0 + 1));
}
StudentTest test = new StudentTest();
// 問題1
test.searchState(stu,3);
System.out.println("------------------------");
//
// 問題2
test.sort(stu);
test.print(stu);
}
// 遍歷學生陣列
public void print(Student[] stu) {
for (int i = 0; i < stu.length; i++) {
System.out.println(stu[i].info());
}
}
/**
*
* @Description 查詢指定年紀的學生
* @author MD
* @date 2020年7月6日下午12:02:56
* @param stu 查詢的陣列
* @param state 指定的年紀
*/
public void searchState(Student[] stu, int state) {
for (int i = 0; i < stu.length; i++) {
if (stu[i].state == 3)
System.out.println(stu[i].info());
}
}
public void sort(Student[] stu) {
for (int i = 0; i < stu.length - 1; i++) {
for (int j = 0; j < stu.length - i - 1; j++) {
if (stu[j].score <= stu[j + 1].score) {
// 注意,這裡交換的不是成績而是物件
Student temp = stu[j];
stu[j] = stu[j + 1];
stu[j + 1] = temp;
}
}
}
}
}
class Student {
int number;
int state;
int score;
public String info() {
return "學號:" + number + " 年級:" + state + " 分數:" + score;
}
}
二、方法的過載(overload)
要求:
* 同一個類中
* 方法名必須相同
* 方法的引數列表不同(①引數的個數不同②引數型別不同)
補充:方法的過載與方法的返回值型別沒有關係!
//如下的四個方法構成過載
//定義兩個int型變數的和
public int getSum(int i,int j){
return i + j;
}
//定義三個int型變數的和
public int getSum(int i,int j,int k){
return i + j + k;
}
//定義兩個double型資料的和
public double getSum(double d1,double d2){
return d1 + d2;
}
//定義三個double型陣列的和
public void getSum(double d1,double d2,double d3){
System.out.println(d1 + d2 + d3);
}
//不能與如上的幾個方法構成過載
// public int getSum1(int i,int j,int k){
// return i + j + k;
// }
// public void getSum(int i,int j,int k){
// System.out.println(i + j + k);
// }
//以下的兩個方法構成過載。
public void method1(int i,String str){
}
public void method1(String str1,int j){
}
三、可變個數的形參的方法
- .格式:對於方法的形參: 資料型別 ... 形參名
- 可變個數的形參的方法與同名的方法之間構成過載
- 可變個數的形參在呼叫時,個數從0開始,到無窮多個都可以
- 使用可變多個形參的方法與方法的形參使用陣列是一致
- 若方法中存在可變個數的形參,那麼一定要宣告在方法形參的最後
- 在一個方法中,最多宣告一個可變個數的形參
//如下四個方法構成過載
//在類中一旦定義了過載的可變個數的形參的方法以後,如下的兩個方法可以省略
// public void sayHello(){
// System.out.println("hello world!");
// }
// public void sayHello(String str1){
// System.out.println("hello " + str1);
// }
//可變個數的形參的方法
public void sayHello(String ... args){
for(int i = 0;i < args.length;i++){
System.out.println(args[i] + "$");
}
//System.out.println("=====");
}
public void sayHello(int i,String ... args){
//public void sayHello(String ... args,int i){
System.out.println(i);
for(int j = 0;j < args.length;j++){
System.out.println(args[j] + "$");
}
}
public void sayHello1(String[] args){
for(int i = 0;i < args.length;i++){
System.out.println(args[i]);
}
}
四、 Java的值傳遞
- 方法的引數傳遞(重點、難點)
- 形參:方法宣告時,方法小括號內的引數
- 實參:呼叫方法時,實際傳入的引數的值
- java中的引數傳遞機制:值傳遞機制
- 形參是基本資料型別的:將實參的值傳遞給形參的基本資料型別的變數
- 形參是引用資料型別的:將實參的引用型別變數的值(對應的堆空間的物件實體的首地址值)傳遞給形參的引用型別變數
- 關於變數的賦值
- 如果變數是基本資料型別,此時賦值的是變數所儲存的資料值
- 如果變數是引用資料型別,此時賦值的變數是所儲存的地址值
1. 例一
public static void main(String[] args) {
TestArgsTransfer tt = new TestArgsTransfer();
int i = 10;
int j = 5;
System.out.println("i:" + i + " j:" + j);//i : 10 j : 5
// //交換變數i與j的值
// int temp = i;
// i = j;
// j = temp;
tt.swap(i, j);//將i的值傳遞給m,j的值傳遞給n
System.out.println("i:" + i + " j:" + j);//i : 10 j : 5
}
//定義一個方法,交換兩個變數的值
public void swap(int m,int n){
int temp = m;
m = n;
n = temp;
System.out.println("m:" + m + " n:" + n);
}
2. 例二
public class TestArgsTransfer1 {
public static void main(String[] args) {
TestArgsTransfer1 tt = new TestArgsTransfer1();
DataSwap ds = new DataSwap();
System.out.println("ds.i:" + ds.i + " ds.j:" + ds.j);
tt.swap(ds);
System.out.println(ds);
System.out.println("ds.i:" + ds.i + " ds.j:" + ds.j);
}
//交換元素的值
public void swap(DataSwap d){
int temp = d.i;
d.i = d.j;
d.j = temp;
System.out.println(d);//列印引用變數d的值
}
}
class DataSwap{
int i = 10;
int j = 5;
}
3. 例3
package com.atguigu.exer;
import java.io.PrintStream;
public class Test {
public static void main(String[] args) {
int a = 10;
int b = 10;
method(a,b);
System.out.println("a="+a);
System.out.println("b="+b);
}
// public static void method(int a , int b) {
// a = a * 10;
// b = b * 20;
// System.out.println(a);
// System.out.println(b);
// System.exit(0);
// }
public static void method(int a, int b) {
PrintStream ps = new PrintStream(System.out) {
public void println(String x){
if("a=10".equals(x)) {
x = "a=100";
}else if("b=10".equals(x)) {
x = "b=200";
}
super.println(x);
}
};
System.setOut(ps);
}
}
4. 例4
輸出的什麼?
public class Test1 {
public static void main(String[] args) {
int[] arr = new int[] {1,2,3};
System.out.println(arr); //地址值
char[] arr1 = new char[] {'a','b','c'};
System.out.println(arr1); //abc
}
}
五、物件導向的特徵一:封裝
- 問題:當建立了類的物件以後,如果直接通過"物件.屬性"的方式對相應的物件屬性賦值的話,可能會出現不滿足實際情況的意外,我們考慮不讓物件來直接作用屬性,而是通過"物件.方法"的形式,來控制物件對屬性的訪問。實際情況中,對屬性的要求就可以通過方法來體現
- 高內聚,低耦合
- 物件導向思想的落地法則二:
- 將類的屬性私有化
- 提供公共的方法(setter & getter)來實現呼叫
- 四種許可權修飾符
- 許可權從大到小為:public protected 預設 private
- 四種許可權都可以用來修飾屬性、方法、構造器
- 修飾類的話:public 預設
- 封裝性的體現
- 將類的屬性私有化,提供公共的方法來呼叫
- 不對外暴露的私有化方法
- 單例模式
1. 構造器
構造器的作用:①建立物件 ②給建立的物件的屬性賦值
- 設計類時,若不顯式宣告類的構造器的話,程式會預設提供一個空參的構造器
- 一旦顯式的定義類的構造器,那麼預設的構造器就不再提供
- 如何宣告類的構造器。格式:許可權修飾符 類名(形參){ }
- 類的多個構造器之間構成過載
- 類物件的屬性賦值的先後順序:
- 屬性的預設初始化
- 屬性的顯式初始化
- 通過構造器給屬性初始化
- 通過"物件.方法"的方式給屬性賦值
- 一個類中至少會有一個構造器,一般都提供一個空參的構造器
2. this關鍵字
- 使用在類中,可以用來修飾屬性、方法、構造器
- 表示當前物件或者是當前正在建立的物件
- 當形參與成員變數重名時,如果在方法內部需要使用成員變數,必須新增this來表明該變數時類成員
- 在任意方法內,如果使用當前類的成員變數或成員方法可以在其前面新增this,增強程式的閱讀性
- 在構造器中使用“this(形參列表)”顯式的呼叫本類中過載的其它的構造器,要求“this(形參列表)”要宣告在構造器的首行!
public class TestPerson {
public static void main(String[] args) {
Person p1 = new Person();
System.out.println(p1.getName() + ":" + p1.getAge());
Person p2 = new Person("BB",23);
int temp = p2.compare(p1);
System.out.println(temp);
}
}
class Person{
private String name;
private int age;
public Person(){
this.name = "AA";
this.age = 1;
}
public Person(String name){
this(); // 先呼叫空引數的
this.name = name;
}
public Person(String name,int age){
this(name);
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void eat(){
System.out.println("eating");
}
public void sleep(){
System.out.println("sleeping");
this.eat();
}
//比較當前物件與形參的物件的age誰大。
public int compare(Person p){
if(this.age > p.age)
return 1;
else if(this.age < p.age)
return -1;
else
return 0;
}
}
3. package/import
package: 宣告原始檔所在的包,寫在程式的第一行
import:
- 顯式匯入指定包下的類或介面
- 寫在包的宣告和原始檔之間
- 如果需要引入多個類或介面,那麼就並列寫出
- 如果匯入的類是java.lang包下的,如:System String Math等,就不需要顯式的宣告
- 理解.*的概念。比如java.util. *;
- 匯入java.lang.*只能匯入lang包下的所有類或介面,不能匯入lang的子包下的類或介面
- import static 表示匯入指定類的static的屬性或方法
/import java.util.Scanner;
//import java.util.Date;
//import java.util.List;
//import java.util.ArrayList;
import java.lang.reflect.Field;
import java.util.*;
import static java.lang.System.*;
public class TestPackageImport {
public static void main(String[] args) {
out.println("helloworld");
Scanner s = new Scanner(System.in);
s.next();
Date d = new Date();
List list = new ArrayList();
java.sql.Date d1 = new java.sql.Date(522535114234L);
Field f = null;
}
}
六、物件導向的特徵二:繼承
- 繼承的格式
- 通過"class A extends B"類實現類的繼承
- 子類繼承父類以後,父類中宣告的屬性、方法,子類就可以獲取到
- 當父類中有私有的屬性或方法時,子類同樣可以獲取得到,只是由於封裝性的設計,使得子類不可以直接呼叫罷了
- 子類除了通過繼承,獲取父類的結構之外,還可以定義自己的特有的成分
- java中類的繼承性只支援單繼承:一個類只能繼承一個父類。反之,一個父類可以有多個子類
- 如果沒有顯示宣告一個類的父類的話,則此類繼承於java.lang.Object類
1. 方法的重寫(override orverwrite) vs 過載(overload)
- 過載:“兩同一不同”:同一個類,同一個方法名,不同的引數列表注:方法的過載與方法的返回值無關!構造器是可以過載的
- 重寫:(前提:在繼承的基礎之上,子類在獲取了父類的結構以後,可以對父類中同名的方法進行“重構”)方法的返回值,方法名,形參列表形同;許可權修飾符不小於父類的同名方法;子類方法的異常型別不大於父類的;兩個方法要同為static或同為非static
- 注:不能重寫父類的私有的方法
- 父類被重寫的返回值型別是A型別,那麼子類重寫方法的返回值型別可以是A類或者A類的子類
class Cirlce{
//求圓的面積
public double findArea(){
}
}
class Cylinder extends Circle{
//求圓柱的表面積
public double findArea(){
}
}
2. 關鍵字 super
- super,相較於關鍵字this,可以修飾屬性、方法、構造器
- super修飾屬性、方法:在子類的方法、構造器中,通過super.屬性或者super.方法的形式,顯式的呼叫父類的指定屬性或方法。尤其是,當子類與父類有同名的屬性、或方法時,呼叫父類中的結構的話,一定要用“super.”
- 通過“super(形參列表)”,顯式的在子類的構造器中,呼叫父類指定的構造器!
- 任何一個類(除Object類)的構造器的首行,要麼顯式的呼叫本類中過載的其它的構造器“this(形參列表)”或顯式的呼叫父類中指定的構造器“super(形參列表)”,要麼預設的呼叫父類空參的構造器"super()",只能二選一
- 建議在設計類時,提供一個空參的構造器
3. 子類物件例項化的全過程
無論通過那個構造器建立子類物件,需要保證先初始化父類
目的:當子類繼承父類後,繼承父類中所有的屬性和方法,因此子類必須知道父類如何為物件進行初始化
public class TestDog {
public static void main(String[] args) {
Dog d = new Dog();
d.setAge(10);
d.setName("小明");
d.setHostName("花花");
System.out.println("name:" + d.getName() + " age:" + d.getAge()
+ "hostName:" + d.getHostName());
System.out.println(d.toString());
}
}
// 生物
class Creator {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Creator() {
super();
System.out.println("this is Creator's constructor");
}
}
// 動物類
class Animal extends Creator {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Animal() {
super();
System.out.println("this is Animal's constructor");
}
}
// 狗
class Dog extends Animal {
private String hostName;
public String getHostName() {
return hostName;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
public Dog() {
super();
System.out.println("this is Dog's constructor");
}
}
七、 物件導向的特徵三:多型
1. 多型性的表現:
①方法的過載與重寫 ②子類物件的多型性
2. 使用的前提:
①要有繼承關係 ②要有方法的重寫
3. 格式
- Person p = new Man();//向上轉型
- 通過父類的引用指向子類的物件實體,當呼叫方法時,實際執行的是子類重寫父類的方法
編譯時,認為p是Person型別的,故只能執行Person裡才有的結構,即Man裡特有的結構不能夠呼叫,子類物件的多型性,並不使用於屬性。
呼叫方法:編譯看左邊,執行看右邊
屬性:編譯和執行都看左邊
package com.atguigu.java;
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
// 多型性的體現
test.func(new Dag());
test.func(new Cat());
}
public void func(Animal an) { // Animal an = new Dag();
an.eat();
an.shot();
}
// public void func(Dag dag) {
// dag.eat();
// dag.shot();
// }
}
class Animal{
public void eat() {
System.out.println("動物:吃食物");
}
public void shot() {
System.out.println("動物:叫");
}
}
class Dag extends Animal{
public void eat() {
System.out.println("狗吃肉");
}
public void shot() {
System.out.println("汪!汪");
}
}
class Cat extends Animal{
public void eat() {
System.out.println("貓吃魚");
}
public void shot() {
System.out.println("喵!喵");
}
}
4. 關於向下轉型
有了物件的多型性之後,記憶體中實際上是載入了子類特有的屬性和方法,但是由於變數宣告為父類型別,導致了編譯時只能呼叫父類中宣告的屬性和方法,子類中特有的屬性和方法呼叫不了,所以有了向下轉型
- 向下轉型,使用強轉符:()
- 為了保證不報ClassCastException,最好在向下轉型前,進行判斷: instanceof
if (p1 instanceof Woman) {
System.out.println("hello!");
Woman w1 = (Woman) p1;
w1.shopping();
}
if (p1 instanceof Man) {
Man m1 = (Man) p1;
m1.entertainment();
}
5. 多型是編譯性行為還是執行時行為
執行時行為
package com.atguigu.java5;
import java.util.Random;
//面試題:多型是編譯時行為還是執行時行為?
//證明如下:
class Animal {
protected void eat() {
System.out.println("animal eat food");
}
}
class Cat extends Animal {
protected void eat() {
System.out.println("cat eat fish");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("Dog eat bone");
}
}
class Sheep extends Animal {
public void eat() {
System.out.println("Sheep eat grass");
}
}
public class InterviewTest {
public static Animal getInstance(int key) {
switch (key) {
case 0:
return new Cat ();
case 1:
return new Dog ();
default:
return new Sheep ();
}
}
public static void main(String[] args) {
int key = new Random().nextInt(3);
System.out.println(key);
Animal animal = getInstance(key);
animal.eat();
}
}
6. 問題1
package com.atguigu.exer;
/*
* 練習:
* 1.若子類重寫了父類方法,就意味著子類裡定義的方法徹底覆蓋了父類裡的同名方法,
* 系統將不可能把父類裡的方法轉移到子類中:編譯看左邊,執行看右邊
*
* 2.對於例項變數則不存在這樣的現象,即使子類裡定義了與父類完全相同的例項變數,
* 這個例項變數依然不可能覆蓋父類中定義的例項變數:編譯執行都看左邊
*/
class Base {
int count = 10;
public void display() {
System.out.println(this.count);
}
}
class Sub extends Base {
int count = 20;
public void display() {
System.out.println(this.count);
}
}
public class FieldMethodTest {
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.count);//20
s.display();//20
Base b = s;//多型性
//==:對於引用資料型別來講,比較的是兩個引用資料型別變數的地址值是否相同
System.out.println(b == s);//true
System.out.println(b.count);//10
b.display();//20
}
}
7. 問題2
package com.atguigu.exer;
//考查多型的筆試題目:
public class InterviewTest1 {
public static void main(String[] args) {
Base1 base = new Sub1();
base.add(1, 2, 3); //sub_1
Sub1 s = (Sub1)base;
s.add(1,2,3); //sub_2
}
}
class Base1 {
public void add(int a, int... arr) {
System.out.println("base1");
}
}
class Sub1 extends Base1 {
public void add(int a, int[] arr) {
System.out.println("sub_1");
}
public void add(int a, int b, int c) {
System.out.println("sub_2");
}
}