目錄
- 一、包
- 1.1靜態匯入
- 1.2將類放到包中
- 1.3包的訪問權限控制
- 二、繼承
- 2.1 super關鍵字
- super與this的區別
- 2.2 protected 關鍵字
- Java 中對于欄位和方法共有四種訪問權限
- 2.3 final 關鍵字
- 三、組合
- 四、多型
- 4.1向上轉型
- 4.2 動態系結
- 多載和重寫的區別
- 4.3 向下轉型
- 4.4 理解多型
- 五、抽象類
- 六、介面
- 實作多個介面
- 三個常用的介面
- `Comparable`
- `Comparator`
- `Cloneable`
一、包
包(package) 是組織類的一種方式,使用包的主要目的是保證類的唯一性,
匯入包中的類
Java中已經提供了很多現成的類供我們使用,
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
// 得到一個毫秒級別的時間戳
System.out.println(date.getTime());
}
}
上面的代碼也可以寫成這樣:
import java.util.Date;
//import java.util.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一個毫秒級別的時間戳
System.out.println(date.getTime());
}
}
如果需要使用 java.util 中的其他類, 可以使用 import java.util.*:其中*表示通配符,意味著匯入這個包底下所有的類,但是Java在處理的時候,需要那個類,才會拿哪個類,而在C語言里面,通過include關鍵字匯入之后,就會把這個頭檔案里面的內容全部都拿過來,
1.1靜態匯入
使用 import static 可以匯入包中的靜態的方法和欄位,
例一:
import static java.lang.System.*;
public class Test {
public static void main(String[] args) {
out.println("hello");
}
}
例二:
import static java.lang.Math.*;
import static java.lang.System.*;
public class Test {
public static void main(String[] args) {
out.println(max(10,20));
}
}
通過靜態匯入的代碼可讀性不高,所以靜態匯入用的很少,
1.2將類放到包中
基本規則:
- 在檔案的最上方加上一個
package陳述句指定該代碼在哪個包中. - 包名需要盡量指定成唯一的名字, 通常會用公司的域名的顛倒形式(例如
- 包名要和代碼路徑相匹配. 例如創建
com.company.demo1的包, 那么會存在一個對應的路徑com/company/demo1來存盤代碼. - 如果一個類沒有
package陳述句, 則該類被放到一個默認包中.
1.3包的訪問權限控制
我們已經了解了類中的 public 和 private. private 中的成員只能被類的內部使用.
如果某個成員不包含 public 和 private 關鍵字, 此時這個成員可以在包內部的其他類使用, 但是不能在包外部的類使用,
包的訪問權限指的是只能在當前包當中進行使用,當你的成員變數不加任何訪問修飾限定詞的時候,它默認就是一個包訪問權限,
同一個包下的成員變數可以訪問,其他包下的成員變數無法從外部程式包中對其進行訪問,
常見的系統包:
java.lang:系統常用基礎類(String、Object),此包從JDK1.1后自動匯入,java.lang.reflect:java反射編程包;java.net:進行網路編程開發包,java.sql:進行資料庫開發的支持包,java.util:是java提供的工具程式包,(集合類等) 非常重要,java.io:I/O編程開發包,
二、繼承
封裝:不必要公開的資料成員和方法使用private關鍵字進行修飾,意義:安全性,
繼承:對共性的抽取,使用extends關鍵字進行處理的,意義:可以對代碼進行重復使用,
我們并不希望類之間的繼承層次太復雜. 一般我們不希望出現超過三層的繼承關系. 如果繼承層次太多, 就需要考慮對代碼進行重構了,
基本語法:
class 子類 extends 父類 {
}
其中Cat這樣的類, 我們稱為子類, 派生類,Animal 這樣被繼承的類, 我們稱為 父類 , 基類 或 超類,
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Cat extends Animal {
public Cat(String name) {
// 使用 super 呼叫父類的構造方法.
super(name);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly() {
System.out.println(this.name + "正在飛 ︿( ̄︶ ̄)︿");
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.name = "小黑";
System.out.println(cat.name);
cat.eat("貓糧");
Bird bird = new Bird();
bird.name = "圓圓";
System.out.println(bird.name);
bird.fly();
}
}
[注意]:
- 使用
extends繼承父類. Java中一個子類只能繼承一個父類 (而C++/Python等語言支持多繼承).即Java當中是單繼承,不能同時繼承兩個類以上的類,包括兩個,- 子類會繼承父類的所有
public的欄位和方法. - 對于父類的
private的欄位和方法, 子類中是無法訪問的. - 子類的實體中, 也包含著父類的實體. 可以使用
super關鍵字得到父類實體的參考,
2.1 super關鍵字
super:不能出現在靜態方法中,其代表的是父類實體的參考,
super的兩種常見用法:
- 使用了 super 來呼叫父類的構造器,
public Cat(String name) {
super(name);
}
- 使用 super 來呼叫父類的普通方法.
public class Bird extends Animal {
public Bird(String name) {
super(name);
}
@Override
public void eat(String food) {
// 修改代碼, 讓子呼叫父類的介面.
super.eat(food);
System.out.println("我是一只小鳥");
System.out.println(this.name + "正在吃" + food);
}
}
在這個代碼中, 如果在子類的 eat 方法中直接呼叫 eat (不加super), 那么此時就認為是呼叫子類自己的 eat (也就是遞回了). 而加上 super 關鍵字, 才是呼叫父類的方法,即:如果子類和父類欄位同名的話,則優先子類的欄位,如果想要訪問父類的欄位,則要加上super關鍵字,
子類構造的同時,要先幫助父類來進行構造,需要在子類的構造方法當中,使用super關鍵字來顯示的呼叫父類的構造方法,
super有3中表現形式:
- super();//呼叫父類的構造方法
- super.func();
- super.data;
super與this的區別
super的概念:表示子類訪問父類的屬性、方法,不查找本類,而直接呼叫父類定義,
this的概念:訪問本類中的屬性、方法,其先查找本類,如果本類沒有就呼叫父類,
2.2 protected 關鍵字
protected 關鍵字:對于類的呼叫者來說, protected 修飾的欄位和方法是不能訪問的,對于類的子類和同一個包的其他類 來說, protected 修飾的欄位和方法是可以訪問的,
Java 中對于欄位和方法共有四種訪問權限
private: 類內部能訪問, 類外部不能訪問,- 默認(也叫包訪問權限): 類內部能訪問, 同一個包中的類可以訪問, 其他類不能訪問.
protected: 類內部能訪問, 子類和同一個包中的類可以訪問, 其他類不能訪問.public: 類內部和類的呼叫者都能訪問,
也就是說:
欄位如果是public,在哪里使用都可以,
private只有在同一個包的同一個類中才能使用,
default在同一個包的同一個類及同一包中的不同類中可以使用,
protected在同一個包的同一個類中,同一包中的不同類中,及不同包中得子類中可以使用,
2.3 final 關鍵字
如果一個類不想被繼承,我們可以設定為final修飾,
final 關鍵字修飾類, 此時表示被修飾的類就不能被繼承,
1.final int a = 20;//常量,不可以被修改
2.final Class A{ //代表整個類不可以被繼承
}
3.final修飾方法
三、組合
和繼承類似, 組合也是一種表達類之間關系的方式, 也是能夠達到代碼重用的效果,
組合并沒有涉及到特殊的語法(諸如extends 這樣的關鍵字), 僅僅是將一個類的實體作為另外一個類的欄位.這是我們設計類的一種常用方式之一,
如一個學校:
public class Student {
...
}
public class Teacher {
...
}
public class School {
public Student[] students;
public Teacher[] teachers;
}
組合表示 has - a 語意,
如:一個學校中 “包含” 若干學生和教師.
繼承表示 is - a 語意,
在上面的 “動物和貓” 的例子中, 我們可以理解成一只貓也 “是” 一種動物,
四、多型
4.1向上轉型
向上轉型:表示往父類的方向轉,
向上轉型發生的時機:
- 直接賦值
在上面的例子中有:
public class Test {
public static void main(String[] args) {
//Cat cat = new Cat();
//Animal animal = cat;
Animal animal = new Cat();//父類參考 參考子類物件
}
}
- 作為函式的引數,方法傳參
public class Test {
public static void function(Animal ani){
}
public static void main(String[] args) {
//Cat cat = new Cat();
//Animal animal = cat;
Animal animal = new Cat();//父類參考 參考子類物件
Cat cat = new Cat();
function(cat);
}
}
此時形參 ani的型別是 Animal (基類), 實際上對應到 Cat (父類) 的實體,
- 作為方法回傳
public class Test {
public static Animal func2(Animal ani){
Cat cat = new Cat();
return cat;
}
public static void main(String[] args) {
Animal animal = func2();
}
}
此時方法 func2回傳的是一個 Animal 型別的參考, 但是實際上對應到 Cat的實體,
4.2 動態系結
編譯的時候不能夠確定到底呼叫誰的方法,運行的時候,才知道了呼叫哪個方法,這就叫做運行時系結也就是動態系結,動態系結也是多型的基礎,
1、父類參考參考子類的物件,
2、運行時系結:通過這個父類參考呼叫父類和子類同名的覆寫方法,
同名的覆寫方法也叫重寫/覆寫/覆寫:
1、方法名相同
2、引數串列相同(引數的個數+引數的型別)
3、回傳值相同 (特殊:回傳值也可以是協變型別)
4、父子類的的情況下
關于重寫的注意事項:
- 重寫和多載完全不一樣.
- 普通方法可以重寫,
static修飾的靜態方法不能重寫. - 重寫中子類方法的訪問權限不能低于(大于等于)父類的訪問限定.
private方法不能夠重寫.- 被
final修飾的方法不能夠重寫. - 重寫的方法回傳值型別不一定和父類的方法相同(但是建議最好寫成相同, 特殊情況除外).
編譯時系結:通過函式的多載實作的,編譯的時候,會根據你給的引數的個數和型別,在編譯期,確定你最終呼叫的方法,
多載和重寫的區別
多載:方法名相同,引數的型別和個數不同,多載的范圍是一個類,
重寫:方法名,回傳值型別、引數型別及個數完全相同,范圍是繼承關系,被覆寫的方法不能擁有比父類更嚴格的訪問控制權限,
通過父類參考只能訪問父類自己的成員,
4.3 向下轉型
向上轉型是子類物件轉成父類物件, 向下轉型就是父類物件轉成子類物件. 相比于向上轉型來說, 向下轉型沒那么常見,但是也有一定的用途,
比如:
public static void main(String[] args) {
Animal animal = new Bird();
Bird bird = (Bird)animal;
bird.fly();
}
向下轉型不安全,不是所有的型別都具有向下轉型中得方法,此時,我們可以加上instanceof判斷一下,可以判斷出一個參考是否是某個類的實體,如:
public class Test {
public static void main(String[] args) {
Animal animal = new Cat();
if(animal instanceof Bird){
Bird bird = (Bird)animal;
bird.fly();
}
}
}
4.4 理解多型
class Shape{
public void draw(){
System.out.println("Shape::draw()");
}
}
class Rect extends Shape{
public void draw(){
System.out.println("?");
}
}
class Flower extends Shape{
public void draw(){
System.out.println("?");
}
}
class Triangle extends Shape{
@Override
public void draw() {
System.out.println("△");
}
}
public class Test {
public static void drawMap(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Rect rect = new Rect();
drawMap(rect);
Flower flower = new Flower();
drawMap(flower);
Triangle triangle = new Triangle();
drawMap(triangle);
}
}
當類的呼叫者在撰寫 drawMap 這個方法的時候, 引數型別為 Shape (父類), 此時在該方法內部并不知道, 也不關注當前的 shape 參考指向的是哪個型別(哪個子類)的實體. 此時 shape 這個參考呼叫 draw 方法可能會有多種不同的表現
(和 shape 對應的實體相關), 這種行為就稱為多型,
使用多型的好處:
- 類呼叫者對類的使用成本進一步降低
- 封裝是讓類的呼叫者不需要知道類的實作細節.
- 多型能讓類的呼叫者連這個類的型別是什么都不必知道, 只需要知道這個物件具有某個方法即可.因此, 多型可以理解成是封裝的更進一步, 讓類呼叫者對類的使用成本進一步降低.
- 能夠降低代碼的 “圈復雜度”, 避免使用大量的
if - else
圈復雜度是一種描述一段代碼復雜程度的方式. 一段代碼如果平鋪直敘, 那么就比較簡單容易理解. 而如果有很多的條件分支或者回圈陳述句, 就認為理解起來更復雜.因此我們可以簡單粗暴的計算一段代碼中條件陳述句和回圈陳述句出現的個數, 這個個數就稱為 “圈復雜度”. 如果一個方法的圈復雜度太高, 就需要考慮重構,
如:
public class Test {
public static void drawMap(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Rect rect = new Rect();
Flower flower = new Flower();
Triangle triangle = new Triangle();
Shape[] shapes = {triangle, rect, triangle, rect, flower};
for (Shape shape:shapes) {
shape.draw();
}
}
}
如果使用 if - else則實作如下:
public static void main(String[] args) {
Rect rect = new Rect();
Flower flower = new Flower();
Triangle triangle = new Triangle();
String[] shapes = {"triangle", "rect", "triangle", "rect", "flower"};
for (String s:shapes) {
if(s.equals("triangle")){
triangle.draw();
}else if(s.equals("rect")){
rect.draw();
}else if (s.equals("flower")){
flower.draw();
}
}
- 可擴展能力更強.
如果要新增一種新的形狀, 使用多型的方式代碼改動成本也比較低.
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("●");
}
}
五、抽象類
在剛才的列印圖形例子中, 我們發現, 父類 Shape 中的 draw 方法好像并沒有什么實際作業, 主要的繪制圖形都是由Shape 的各種子類的 draw 方法來完成的. 像這種沒有實際作業的方法, 我們可以把它設計成一個 抽象方法(abstract method),
//抽象類
abstract class Shape{
//抽象方法
public abstract void draw();
}
注意:
- 包含抽象方法的類我們稱為抽象類(abstract class),
- 抽象方法是被abstract修飾的,沒有具體實作的方法,
- 抽象類是不可以被實體化的,
- 由于抽象類不能被實體化,所以只能被繼承,
- 抽象類當中也可以包含和普通類一樣的成員和方法,抽象類中可以包含其他的非抽象方法, 也可以包含欄位. 這個非抽象方法和普通方法的規則都是一樣的, 可以被重寫,也可以被子類直接呼叫,
- 一個普通類,繼承了一個抽象類,那么這個普通類當中需要重寫這個抽象類的所有的抽象方法,
- 抽象類最大的作用,就是為了被繼承,
- 一個抽象類A,如果繼承了一個抽象類B,那么這個抽象類A,可以不實作抽象父類B的抽象方法,
- 結合第8點,當A類再次被一個普通類繼承后,那么A和B這兩個抽象類當中的抽象方法,必須被重寫,
- 抽象類不能被
final修飾,抽象方法也不可以被final修飾, - 抽象方法不能是
private修飾的,
使用抽象類相當于多了一重編譯器的校驗,使用抽象類的場景就如上面的代碼, 實際作業不應該由父類完成, 而應由子類完成. 那么此時如果不小心誤用成父類了,
使用普通類編譯器是不會報錯的. 但是父類是抽象類就會在實體化的時候提示錯誤, 讓我們盡早發現問題.
六、介面
- 介面是使用
interface來修飾的,interface IA{} - 介面當中的普通方法,不能有具體的實作,非要實作,只能通過關鍵字
default來修飾這個方法, - 介面當中,可以有
static的方法, - 介面里面的所有的方法都是
public的,因此可以省略public, - 抽象方法,默認是
public abstract的,
//介面是使用interface來修飾的
interface IShape{
//抽象方法,默認是public abstract的,
public abstract void func1();
//介面當中的普通方法,不能有具體的實作,非要實作,只能通過關鍵字`default`來修飾這個方法
default public void draw(){
System.out.println("aaa");
}
//介面當中,可以有static的方法,
public static void func(){
System.out.println("bbb");
}
}
- 介面是不可以被通過關鍵字
new來實體化的, - 類和介面之間的關系是通過
implements實作了, - 當一個類實作了一個介面,就必須要重寫介面當中的抽象方法,
- 介面中的方法一定是抽象方法, 因此可以省略
abstract, - 介面當中的成員變數,默認是
public static final修飾的, - 當一個類實作一個介面之后,重寫這個方法的時候,這個方法前面必須加上
public,
interface IA{
//public static final int a = 10;//public static final可以省略
int A1 = 10;
void funcA();//默認為抽象方法
}
class AClass implements IA{
public void funcA(){
}
}
- 一個類可以通過關鍵字
extends繼承一個抽象類或者普通類,但是只能繼承一個類,同時,也可以通過implements實作多個介面,介面之間使用逗號隔開就好, - 介面中只能包含抽象方法. 對于欄位來說, 介面中只能包含靜態常量(final static).
如:
interface IA{
public static final int a = 10;
int A = 10;
void funcA();//默認為抽象方法
}
interface IB{
void funcB();
}
class BClass {
}
class AClass extends BClass implements IA,IB{
public void funcA(){
System.out.println("A::funcA()");
System.out.println(A);
}
@Override
public void funcB() {
System.out.println("A::funcB()");
}
}
- 介面和介面之間可以使用
extends來操作他們的關系,此時,這里面意為:拓展,一個介面IB1通過extends來拓展另一個介面IA1的功能,此時當一個類C通過implements實作這個介面IB1的時候,此時重寫的方法不僅僅是IB1的抽象方法,還有他從IA1介面拓展來的功能/方法,
如下所示:
interface IA1{
void funcA();//默認為抽象方法
}
interface IB1 extends IA1{
void funcB();
}
class C implements IB1{
@Override
public void funcA() {
System.out.println("aaa");
}
@Override
public void funcB() {
System.out.println("bbb");
}
}
使用介面也可以實作動態系結,比如下面的例子:
interface IShape{
// public abstract void draw();
void draw();
}
class Rect implements IShape{
public Rect() {
}
@Override
public void draw() {
System.out.println("fangPain");
}
}
class Flower implements IShape {
public void draw(){
System.out.println("hua");
}
}
class Triangle implements IShape{
@Override
public void draw() {
System.out.println("△");
}
}
class Cycle implements IShape {
@Override
public void draw() {
System.out.println("●");
}
}
public class Test {
public static void drawMap( IShape iShape){
iShape.draw();
}
public static void main(String[] args) {
Flower flower = new Flower();
Cycle cycle = new Cycle();
Triangle triangle = new Triangle();
drawMap(flower);
drawMap(cycle);
drawMap(triangle);
}
}
實作多個介面
有的時候我們需要讓一個類同時繼承自多個父類. 這件事情在有些編程語言通過 多繼承的方式來實作的.然而 Java 中只支持單繼承, 一個類只能 extends 一個父類. 但是可以同時實作多個介面, 也能達到多繼承類似的效果.
例:
class Animal{
protected String name;
public Animal(String name) {
this.name = name;
}
}
//不是所有的動物都會飛,所以不能寫到Animal類當中
//如果寫到別的類當中也不行,因為一個類不能繼承多個類,所以用到介面
interface IFlying{
void fly();
}
interface IRunning{
void run();
}
interface ISwimming{
void swimming();
}
class Bird extends Animal implements IFlying{
public Bird(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name + "正在飛");
}
}
class Frog extends Animal implements IRunning,ISwimming{
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在跑");
}
@Override
public void swimming() {
System.out.println(this.name + "正在游泳");
}
}
class Duck extends Animal implements ISwimming,IRunning,IFlying{
public Duck(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name + "正在飛");
}
@Override
public void run() {
System.out.println(this.name + "正在跑");
}
@Override
public void swimming() {
System.out.println(this.name + "正在游泳");
}
}
public class test04 {
public static void runFunc(IRunning iRunning){
iRunning.run();
}
public static void swimmingFunc(ISwimming iSwimming){
iSwimming.swimming();
}
public static void flyingFunc(IFlying iFlying){
iFlying.fly();
}
public static void main(String[] args) {
runFunc(new Duck("鴨子"));
runFunc(new Frog("青蛙"));
swimmingFunc(new Duck("鴨子"));
flyingFunc(new Bird("小鳥"));
flyingFunc(new Duck("鴨子"));
}
}
有了介面之后, 類的使用者就不必關注具體型別, 而只關注某個類是否具備某種能力,
三個常用的介面
如果自定義的資料型別進行大小的比較一定要實作可以比較的介面,如
Comparable
這個介面有一個很大的缺點:對類的侵入性非常強,一旦寫好了,不敢輕易改動,
例:
class Student implements Comparable<Student> {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {//誰呼叫compareTo,誰就是this
// if (this.age > o.age){
// return 1;
// }else if (this.age < o.age){
// return -1;
// }else {
// return 0;
// }
//return this.age - o.age;//從小到大排序
//return o.age - this.age;//從大到小排序
//return (int)(this.score - o.score);
return this.name.compareTo(o.name);
}
}
Comparator
靈活,對類的侵入性非常弱,
例:
class Student {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
class AgeComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
class ScoreComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return (int)(o1.score - o2.score);
}
}
class NameComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
public class test01 {
public static void main(String[] args) {
Student[] student = new Student[3];
student[0] = new Student("zHang",12,100);
student[1] = new Student("aWang",18,98);
student[2] = new Student("li",56,88);
System.out.println(Arrays.toString(student));//排序前列印
AgeComparator ageComparator = new AgeComparator();
ScoreComparator scoreComparator = new ScoreComparator();
NameComparator nameComparator = new NameComparator();
Arrays.sort(student,ageComparator);//默認是從小到大的排序
System.out.println(Arrays.toString(student));//排序后列印
}
}
Comparable、Comparator用哪個介面取決于你的業務,一般不叫推薦比較器
Cloneable
未完待續…
以上,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/356974.html
標籤:java
上一篇:IO流(Java)
