Java面向对象编程

目录

 

导入包中的类

将类放到包中

包的访问权限控制

常见的系统包

继承

语法规则

protected 关键字

final 关键字

组合

多态

向上转型

 动态绑定

方法重写

理解多态

向下转型

在构造方法中调用重写的方法(一个坑)

抽象类

接口

实现多个接口

Clonable 接口和深拷贝

Comparable和Comparator接口


(package) 是组织类的一种方式 .
使用包的主要目的是保证类的唯一性 .


导入包中的类

例如:

import java.util.Arrays;

public class TestDemo {
    public static void main(String[] args) {
        int[] array = {1, 2, 3, 4, 5, 6};
        System.out.println(Arrays.toString(array));
    }
}

使用toString就必须导入Arrays这个包,即java.util.Arrays。因为这个方法由类名Arrays进行调用则这个方法就是静态方法。

并且只能导入一个具体的类,不能导入一个具体的包。

或者可以这样写:

public class TestDemo {
    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();
        // 得到一个毫秒级别的时间戳
        System.out.println(date.getTime());
    }
}
可以使用 java.util.Date 这种方式引入 java.util 这个包中的 Date .
但是这种写法比较麻烦一些 , 可以使用 import 语句导入包 .
如果需要使用 java.util 中的其他类 , 可以使用 import java.util.*
import java.util.*;
public class Test {
    public static void main(String[] args) {
        Date date = new Date();
        // 得到一个毫秒级别的时间戳
        System.out.println(date.getTime());
   }
}

 import java.util.*中的*叫做通配符。作用:导入这个包低下所有的类。

并且在Java处理的时候,用到谁,就拿谁。

但是我们更建议显式的指定要导入的类名 . 否则还是容易出现冲突的情况
import java.util.*;
import java.sql.*;
public class Test {
    public static void main(String[] args) {
        // util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
        Date date = new Date();
        System.out.println(date.getTime());
   }
}
// 编译出错
Error:(5, 9) java: 对Date的引用不明确
  java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配
在这种情况下需要使用完整的类名
import java.util.*;
import java.sql.*;
public class Test {
    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();
        System.out.println(date.getTime());
   }
}

静态导入
使用 import static 可以导入包中的静态的方法和字段
import static java.lang.System.*;
public class Test {
    public static void main(String[] args) {
        out.println("hello");
   }
}
静态导入的方式写起来更方便一些

但是静态导入用的比较少,了解即可


将类放到包中

基本规则
  • 在文件的最上方加上一个 package 语句指定该代码在哪个包中.
  • 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 ).
  • 包名要和代码路径相匹配 . 例如创建 com.bit.demo1 的包 , 那么会存在一个对应的路径 com/bit/demo1 来存储代码.
  • 如果一个类没有 package 语句 , 则该类被放到一个默认包中 .
例如 , 你在代码中写了一个 Test . 然后你的同事也可能写一个 Test . 如果出现两个同名的类 , 就会冲突 , 导致代码不能编译通过

操作步骤

1) IDEA 中先新建一个包 : 右键 src -> 新建 ->

 2) 在弹出的对话框中输入包名, 例如 com.bit.demo1

 3) 在包中创建类, 右键包名 -> 新建 -> , 然后输入类名即可.

 4) 此时可以看到我们的磁盘上的目录结构已经被 IDEA 自动创建出来了

 5) 同时我们也看到了, 在新创建的 Test.java 文件的最上方, 就出现了一个 package 语句

 包名必须是小写字母。


包的访问权限控制

我们已经了解了类中的 public private包访问权限,(只能在当前包中使用)当你不加任何访问修饰限定词的时候。
如:
在同一个包中

 在不同包中

 此时val默认包访问权限,使用报错


常见的系统包

1. java.lang: 系统常用基础类 (String Object), 此包从 JDK1.1 后自动导入。
2. java.lang.reflflect:java 反射编程包 ;
3. java.net: 进行网络编程开发包。
4. java.sql: 进行数据库开发的支持包。
5. java.util: java 提供的工具程序包。 ( 集合类等 ) 非常重要
6. java.io:I/O 编程开发包。

继承

面向对象编程

封装:不必公开的数据成员和方法 使用private进行修饰      意义:安全性

继承:对共性的抽取。使用关键字进行处理的。     意义:对代码进行重复的使用

多态:

组合:

代码中创建的类 , 主要是为了抽象现实中的一些事物 ( 包含属性和方法 ).
有的时候客观事物之间就存在一些关联关系 , 那么在表示成类和对象的时候也会存在一定的关联 .
// Animal.java 
public class Animal { 
     public String name; 
 
     public Animal(String name) { 
     this.name = name; 
} 
 
     public void eat(String food) { 
          System.out.println(this.name + "正在吃" + food); 
     } 
} 
// Cat.java 
class Cat { 
    public String name; 
 
    public Cat(String name) { 
        this.name = name; 
    } 
 
    public void eat(String food) { 
        System.out.println(this.name + "正在吃" + food); 
    } 
} 
// Bird.java 
class Bird { 
    public String name; 
 
    public Bird(String name) { 
        this.name = name; 
    } 
 
    public void eat(String food) { 
        System.out.println(this.name + "正在吃" + food); 
    } 
 
    public void fly() { 
        System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿"); 
    } 
}
这个代码我们发现其中存在了大量的冗余代码 .
仔细分析 , 我们发现 Animal Cat 以及 Bird 这几个类中存在一定的关联关系 :
  • 这三个类都具备一个相同的 eat 方法, 而且行为是完全一样的.
  • 这三个类都具备一个相同的 name 属性, 而且意义是完全一样的.
  • 从逻辑上讲, Dog  Bird 都是一种 Animal (is - a 语义).
Animal 这样被继承的类, 我们称为 父类 , 基类 超类 , 对于像 Cat 和 Bird 这样的类, 我们称为 子类 , 派生类 和现实中的儿子继承父亲的财产类似, 子类也会继承父类的字段和方法, 以达到 代码重用的效果。

 如:

package com.bit.demo2;

class Animal {
    public String name;
    public int age;

    public void eat() {
        System.out.println("eat()");
    }
}
class Dog extends Animal{
    public void eat() {
        System.out.println("eat()");
    }
}

class Bird extends Animal{

    public String wing;

    public void eat() {
        System.out.println("eat()");
    }
    public void fly() {
        System.out.println(name+"fly()");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Dog dog = new Dog();
        System.out.println(dog.name);
        dog.eat();
    }
}


语法规则

基本语法
class 子类 extends 父类 { 
 
} 
  • 使用 extends 指定父类.
  • Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承).
  • 子类会继承父类的所有 public 的字段和方法.
  • 对于父类的 private 的字段和方法, 子类中是无法访问的.
  • 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用

 当子类构造的同时,先帮助父类进行构造。并且通过super 来调用父类的构造器

class Animal {
    public String name;
    public int age;

    public Animal(String name,int age) {
        this.name = name;
        this.age  = age;
    }
    public void eat() {
        System.out.println("eat()");
    }
}
class Dog extends Animal{
    public Dog(String name, int age) {
        super(name, age);
    }

    public void eat() {
        System.out.println("eat()");
    }
}

一个类没有构造方法时,它会默认有一个不带参数的构造方法。但是一个类提供了带有参数的构造方法时就不会有不带参数的构造方法了

而super只能在当前构造器中使用,并且只能在第一行。

 super的使用:

 super:【不能出现在静态方法中】父类对象的引用

  1. super();//调用父类的构造方法
  2. super.func();
  3. super.data;

 下面是他们的内存布局:

class Animal {
    public String name;
    public int age;
    private int count;

    public Animal(String name,int age) {
        this.name = name;
        this.age  = age;
    }
    public void eat() {
        System.out.println("eat()");
    }
}
class Dog extends Animal{
    public Dog(String name, int age) {
        super(name, age);
    }

    public void eat() {
        System.out.println("eat()");
    }
}

class Bird extends Animal{

    public String wing;

    public Bird(String name, int age,String wing) {
        super(name, age);
        this.wing =wing;
    }
    public void fly() {
        System.out.println(super.name+"fly()"+super.age);
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Dog dog = new Dog("haha",19);
        dog.eat();

        Bird bird = new Bird("heihei",18,"我要的飞翔");
        bird.fly();
        bird.eat();
    }
}

 注意:当子类和父类有同名的字段时优先访问自己的字段,除非用super关键字。


protected 关键字

刚才我们发现 , 如果把字段设为 private, 子类不能访问 . 但是设成 public, 又违背了我们 " 封装 " 的初衷 .
两全其美的办法就是 protected 关键字 .
  • 对于类的调用者来说, protected 修饰的字段和方法是不能访问的
  • 对于类的 子类 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的
小结 : Java 中对于字段和方法共有四种访问权限
  • private: 类内部能访问, 类外部不能访问
  • 默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
  • protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
  • public : 类内部和类的调用者都能访问

 

什么时候下用哪一种呢 ?
  • 我们希望类要尽量做到 "封装", 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.
  • 因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用public.
  • 另外, 还有一种 简单粗暴的做法: 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是对访问权限的滥用, 还是更希望同学们能写代码的时候认真思考, 该类提供的字段方法到底给 "" 使用(是类内部自己用, 还是类的调用者使用, 还是子类使用)


final 关键字

修饰一个变量或者字段的时候 , 表示 常量 ( 不能修改 ).
final 关键字也能修饰类, 此时表示被修饰的类就不能被继承.
如果一个类不想被继承,我们可以设置为final修饰。
final int a = 10; //常量 不可以被修改。
final class A     //代表整个类不可以被继承

 我们平时是用的 String 字符串类, 就是用 fifinal 修饰的, 不能被继承。


组合

和继承类似 , 组合也是一种表达类之间关系的方式 , 也是能够达到代码重用的效果 .
例如表示一个学校:
public class Student { 
     ... 
} 
public class Teacher { 
     ... 
} 
public class School { 
    public Student[] students; 
    public Teacher[] teachers; 
}
组合并没有涉及到特殊的语法 ( 诸如 extends 这样的关键字 ), 仅仅是将一个类的实例作为另外一个类的字段 .这是我们设计类的一种常用方式之一.


多态

向上转型

Dog dog =new Dog("haha",19);
Animal animal = dog;
这个代码也可以写成这个样子。
Animal animal = new Dog("haha",19);

如上代码,父类引用    引用    子类对象这种写法称为 向上转型.

什么时候是向上转型的时机呢?

  • 直接赋值
  • 方法传参
  • 方法返回

public class TestDemo {

    public static void func(Animal animal) {

    }

    public static Animal func2() {
        Dog dog = new Dog("heihei",20);
        return dog;
    }
    public static void main(String[] args) {
        Dog dog = new Dog("xiaoxiao",21); 
        Animal animal = new Dog("haha",19);//直接赋值
        func(dog);                         //方法传参
        func2();                           //方法返回
    }
}


 动态绑定

当子类和父类中出现同名方法的时候 , 再去调用会出现什么情况呢 ?
class Animal {
    public String name;
    public int age;
    private int count;

    public Animal(String name,int age) {
        this.name = name;
        this.age  = age;
    }
    public void eat() {
        System.out.println("eat()");
    }
}
class Dog extends Animal{
    public Dog(String name, int age) {
        super(name, age);
    }

    public void eat() {
        System.out.println(name+"狼吞虎咽的eat()");
    }
}

class Bird extends Animal{

    public String wing;

    public Bird(String name, int age,String wing) {
        super(name, age);
        this.wing =wing;
    }
    public void fly() {
        System.out.println(super.name+"fly()"+super.age);
    }
}
public class TestDemo {

    public static void func(Animal animal) {

    }

    public static Animal func2() {
        Dog dog = new Dog("heihei",20);
        return dog;
    }
    public static void main(String[] args) {
        Animal animal = new Dog("haha",19);
        animal.eat();
    }
}

结果是:
haha狼吞虎咽的eat()

 ​​​​​


方法重写

子类实现父类的同名方法 , 并且参数的类型和个数完全相同 , 这种情况称为 覆写 / 重写 / 覆盖 (Override) .
关于重写的注意事项
        1. 重写和重载完全不一样 . 不要混淆 ( 思考一下 , 重载的规则是啥 ?)
        2. 普通方法可以重写 , static 修饰的静态方法不能重写 .       
        3. 重写中子类的方法的访问权限不能低于父类的方法访问权限 .
        4. 重写的方法返回值类型不一定和父类的方法相同 ( 但是建议最好写成相同, 特殊情况                  除外).

 

 Java 有两种多态:1、编译时多态。      2、运行时多态。


理解多态

有了面的向上转型 , 动态绑定 , 方法重写之后 , 我们就可以使用 多态 (polypeptide) 的形式来设计程序了 . 我们可以写一些只关注父类的代码, 就能够同时兼容各种子类的情况 .
代码示例 : 打印多种形状
class Shape { 
    public void draw() { 
 
    } 
} 
class Cycle extends Shape { 
    @Override 
    public void draw() {
        System.out.println("○"); 
    } 
} 
class Rect extends Shape { 
    @Override 
    public void draw() { 
        System.out.println("□"); 
    } 
} 
class Flower extends Shape { 
    @Override 
    public void draw() { 
        System.out.println("♣"); 
    } 
} 
 
// Test.java 
public class Test { 
    public static void main(String[] args) { 
        Shape shape1 = new Flower(); 
        Shape shape2 = new Cycle(); 
        Shape shape3 = new Rect(); 
        drawMap(shape1); 
        drawMap(shape2); 
        drawMap(shape3); 
    } 
 // 打印单个图形
    public static void drawShape(Shape shape) { 
        shape.draw(); 
    } 
}
在这个代码中 , 分割线上方的代码是 类的实现者 编写的 , 分割线下方的代码是 类的调用者 编写的 .
当类的调用者在编写 drawMap 这个方法的时候 , 参数类型为 Shape ( 父类 ), 此时在该方法内部并 不知道 , 也不关注 当 前的 shape 引用指向的是哪个类型 ( 哪个子类 ) 的实例 . 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现 (和 shape 对应的实例相关 ), 这种行为就称为 多态
使用多态的好处是什么 ?
1) 类调用者对类的使用成本进一步降低 .
2) 能够降低代码的 " 圈复杂度 ", 避免使用大量的 if - else
3) 可扩展能力更强 .
例如
class Shape {
    public void draw() {
        System.out.println("Shape::draw()");
    }
}

class Rect extends Shape{
    @Override
    public void draw() {
        System.out.println("♦");
    }
}

class Flower extends Shape {
    @Override
    public void draw() {
        System.out.println("❀");
    }
}

class Triangle extends Shape{
    @Override
    public void draw() {
        System.out.println("△");
    }
}
class Cycle extends  Shape {
    @Override
    public void draw() {
        System.out.println("●");
    }
}

public class TestDemo {
    
    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,new Cycle()};
        for (Shape shape : shapes) {
            shape.draw();
        }
    }

 输出结果:







向下转型

class Animal {
    public String name;
    public int age;
    private int count;

    public Animal(String name,int age) {
        this.name = name;
        this.age  = age;
    }
    public void eat() {
        System.out.println("eat()");
    }
}
class Dog extends Animal{
    public Dog(String name, int age) {
        super(name, age);
    }

    public void eat() {
        System.out.println(name+"狼吞虎咽的eat()");
    }
}

class Bird extends Animal{

    public String wing;

    public Bird(String name, int age,String wing) {
        super(name, age);
        this.wing =wing;
    }
    public void fly() {
        System.out.println(super.name+"fly()"+super.age);
    }
}
public class TestDemo {

    public static void func(Animal animal) {

    }

    public static Animal func2() {
        Dog dog = new Dog("heihei",20);
        return dog;
    }
    public static void main(String[] args) {
        Animal animal = new Dog("haha",19);
        Dog dog = (Dog) animal;
        dog.eat();
    }
}

 以上就是向下转型。

注意事项
编译过程中 , animal 的类型是 Animal, 此时编译器只知道这个类中有一个 eat 方法 , 没有 fly 方法 .
虽然 animal 实际引用的是一个 Bird 对象 , 但是编译器是以 animal 的类型来查看有哪些方法的 .
对于 Animal animal = new Dog("haha",19); 这样的代码 ,
  • 编译器检查有哪些方法存在, 看的是 Animal 这个类型
  • 执行时究竟执行父类的方法还是子类的方法, 看的是 Bird 这个类型.
那么想实现刚才的效果 , 就需要向下转型

 但是这样的向下转型有时是不太可靠的. 例如

 animal 本质上引用的是一个Dog 对象, 是不能转成 Bird 对象的. 运行时就会抛出异常.

 所以, 为了让向下转型更安全, 我们可以先判定一下看看 animal 本质上是不是一个 Bird 实例, 再来转换

Animal animal = new Dog("haha",19); 
if (animal instanceof Bird) { 
    Bird bird = (Bird)animal; 
    bird.fly(); 
}
instanceof 可以判定一个引用是否是某个类的实例 . 如果是 , 则返回 true. 这时再进行向下转型就比较安全了


在构造方法中调用重写的方法(一个坑)

一段有坑的代码 . 我们创建两个类 , B 是父类 , D 是子类 . D 中重写 func 方法 . 并且在 B 的构造方法中调用 func
class B { 
 public B() { 
     // do nothing 
     func(); 
 } 
 public void func() { 
     System.out.println("B.func()"); 
 } 
} 
class D extends B { 
 private int num = 1; 
 @Override 
 public void func() { 
     System.out.println("D.func() " + num); 
 } 
} 
public class Test { 
 public static void main(String[] args) { 
     D d = new D();
 } 
} 
// 执行结果
D.func() 0
  • 构造 D 对象的同时, 会调用 B 的构造方法.
  • B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
  • 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0.
结论 : " 用尽量简单的方式使对象进入可工作状态 ", 尽量不要在构造器中调用方法 ( 如果这个方法被子类重写 , 就会触发动态绑定, 但是此时子类对象还没构造完成 ), 可能会出现一些隐藏的但是又极难发现的问题

抽象类

1.包括抽象方法的类叫做抽象类。

2.什么是抽象方法,一个没有具体实现的方法,被abstract修饰。

3.抽象类是不可以被实例化的。new

4.因为不能实例化,使用只能被继承。

5.抽象类中也可以包含普通类一样的成员和方法

6.一个普通类继承一个抽象类,那么这个普通类当中,需要重写这个抽象类当中所有的抽象     方法。

7.抽象类最大的作用就是被继承。

8.一个类A如果继承了一个类B,那么这个抽象类A,可以不用实现抽象父类B的抽象方法。

9.结合第八点,当类A再次被普通类继承后,那么A和B这两个抽象类当中的抽象方法,必须     重写。

10.抽象类不能被final修饰,抽象方法也不可能被final修饰。

11.抽象方法不能是 private

如:

abstract class Shape {
    public abstract void draw();//抽象方法
}

abstract class A extends Shape{
    public abstract void funcA();
}
class B extends A {
    @Override
    public void funcA() {

    }

    @Override
    public void draw() {

    }
}

class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("♦");
        //super.func();
    }
}
class Flower extends Shape {
    @Override
    public void draw() {
        System.out.println("❀");
    }
}

class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("△");
    }
}
class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("●");
    }
}

public class TestDemo {

    public static void drawMap(Shape shape) {
        shape.draw();
    }

    public static void main(String[] args) {
        Shape shape = new Rect();
        drawMap(shape);
        Cycle cycle = new Cycle();
        drawMap(cycle);
        //Shape shape2 = new Shape();
    }

 注意事项         
1) 抽象类不能直接实例化 .
Shape shape = new Shape(); 
// 编译出错
Error:(30, 23) java: Shape是抽象的; 无法实例化
2) 抽象方法不能是 private
abstract class Shape { 
 abstract private void draw(); 
} 
// 编译出错
Error:(4, 27) java: 非法的修饰符组合: abstract和private
3) 抽象类中可以包含其他的非抽象方法 , 也可以包含字段 . 这个非抽象方法和普通方法的规则都是一样的 , 可以被重写 , 也可以被子类直接调用  
abstract class Shape { 
    abstract public void draw(); 
    void func() { 
        System.out.println("func"); 
    } 
} 
class Rect extends Shape { 
    ... 
} 
public class Test { 
    public static void main(String[] args) { 
        Shape shape = new Rect(); 
        shape.func(); 
    } 
} 
// 执行结果
func


接口

接口:

  1. 使用interface来修饰的。interface  IA{ }
  2. 接口当中的普通方法,不能有具体的实现。非要实现,只能通过关键字default来修饰,这个方法。
  3. 接口当中,可以有static的方法。
  4. 里面的所有的方法都是public的。
  5. 抽象方法,默认是public abstract的
  6. 接口是不可以被通过关键字new来实例化的。
  7. 类和接口之间的关系是通过implements实现了。
  8. 当一个类实现了一个接口,就必须要重写接口当中的抽象方法。
interface IShape {
    void draw();//抽象方法
   /* default public  void func(){
        System.out.println("fsafa");
    }*/
    /* default public  void func2(){
        System.out.println("fsafa");
    }
    public static void funcStatic() {
        System.out.println("fafa");
    }*/
}
class Rect implements IShape {

    @Override
    public void draw() {
        System.out.println("♦");
    }

    /*@Override
    public void func() {
        System.out.println("重写接口当中的默认方法");
    }*/
}

class Flower implements IShape {
    @Override
    public void draw() {
        System.out.println("❀");
    }
}

class Triangle implements IShape {
    @Override
    public void draw() {
        System.out.println("△");
    }
}
class Cycle implements IShape {
    @Override
    public void draw() {
        System.out.println("●");
    }
}
public class TestDemo {

    public static void drawMap(IShape iShape) {
        iShape.draw();
    }

    public static void main(String[] args) {
        Rect rect = new Rect();
        Flower flower = new Flower();
        drawMap(rect);
        drawMap(flower);

        //IShape iShape = new IShape();error
        IShape iShape = new Rect();
        iShape.draw();
    }
}
  1. 接口当中的成员变量,默认是publicstatic final修饰的
  2.  当一个类实现一个接口之后,重写这个方法的时候,这个方法前面必须加上public
  3. 一个类可以通过extends继承一个抽象类或者普通类,但是只能继承一个类。同时,也可以通过implements实现多个接口,接口之间用逗号隔开就好。
interface IA {
    int A = 10;
    void funcA();//public abstract
}
interface IB {
    void funcB();
}
abstract 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()");
    }
}

public class TestDemo {

    public static void main(String[] args) {
        final int a = 10;
        System.out.println(a);
    }
}
接口中只能包含抽象方法 . 对于字段来说 , 接口中只能包含静态常量 (final static).
interface IShape { 
    void draw(); 
    public static final int num = 10; 
}

 其中的 public, static, final 的关键字都可以省略. 省略后的 num 仍然表示 public 的静态常量.

提示 :
1. 我们创建接口的时候 , 接口的命名一般以大写字母 I 开头 .
2. 接口的命名一般使用 " 形容词 " 词性的单词 .
3. 阿里编码规范中约定 , 接口中的方法和属性不要加任何修饰符号 , 保持代码的简洁性

 一个错误的代码

interface IShape { 
    abstract void draw() ; // 即便不写public,也是public 
} 
class Rect implements IShape { 
    void draw() { 
        System.out.println("□") ; //权限更加严格了,所以无法覆写。
    } 
}

 接口与接口直接可以通过extends来操作他们的关系,此时,这里面意为:拓展

一个接口A 通过 extends来 拓展另一个接口B的功能。此时当一个类C 通过implements实现这个A接口时,此时重写的方法不仅仅是A的抽象方法,还有他从C接口拓展来的方法。

interface IA1 {
    void funcA();
}

interface IB1 extends IA1{
    void funcB();
}

class C implements IB1{
    @Override
    public void funcB() {
        System.out.println("fsafsa");
    }

    @Override
    public void funcA() {
        System.out.println("ewqrewqrq");
    }
}


public class TestDemo {
}

实现多个接口

有的时候我们需要让一个类同时继承自多个父类 . 这件事情在有些编程语言通过 多继承 的方式来实现的 .
然而 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 IRunning,ISwimming,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+" 正在游泳!");
    }
}
//机器人
class Roobot implements IRunning{

    @Override
    public void run() {
        System.out.println("机器人再跑!");
    }
}

public class Test4 {

    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("青蛙"));
        runFunc(new Roobot());
    }
}
上面的代码展示了 Java 面向对象编程中最常见的用法 : 一个类继承一个父类 , 同时实现多种接口 .
继承表达的含义是 is - a 语义 , 而接口表达的含义是 具有 xxx 特性。
这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力.


Clonable 接口和深拷贝

Java 中内置了一些很有用的接口 , Clonable 就是其中之一 .
Object 类中存在一个 clone 方法 , 调用这个方法可以创建一个对象的 " 拷贝 ". 但是要想合法调用 clone 方法 , 必须要先实现 Clonable 接口 , 否则就会抛出 CloneNotSupportedException 异常 .

class Animal implements Cloneable { 
     private String name; 
     @Override 
     public Animal clone() { 
         Animal o = null; 
         try { 
             o = (Animal)super.clone(); 
         } catch (CloneNotSupportedException e) {
             e.printStackTrace(); 
           } 
         return o; 
     } 
} 
public class Test { 
     public static void main(String[] args) { 
         Animal animal = new Animal(); 
         Animal animal2 = animal.clone(); 
         System.out.println(animal == animal2); 
     } 
} 
// 输出结果
// false

深拷贝与浅拷贝

浅拷贝 指的是你的类本身被拷贝,而没有拷贝类本身属性中的类
深拷贝 指的是包含类本身和属性类在内的所有类的拷贝。

简单点说:
就是浅拷贝的两个对象中的属性还会指向同一个类,而深拷贝则全部单独了。也就是说深拷贝把关联关系也拷贝了。

浅拷贝 VS 深拷贝
Cloneable 拷贝出的对象是一份 " 浅拷贝 "
观察以下代码 :
public class Test { 
     static class A implements Cloneable { 
         public int num = 0; 
         @Override 
         public A clone() throws CloneNotSupportedException { 
             return (A)super.clone(); 
         } 
     } 
     static class B implements Cloneable { 
         public A a = new A(); 
         @Override 
         public B clone() throws CloneNotSupportedException { 
             return (B)super.clone(); 
         } 
     } 
     public static void main(String[] args) throws CloneNotSupportedException { 
         B b = new B(); 
         B b2 = b.clone(); 
         b.a.num = 10; 
         System.out.println(b2.a.num); 
     } 
} 
// 执行结果
10
通过 clone 拷贝出的 b 对象只是拷贝了 b 自身 , 而没有拷贝内部包含的 a 对象 . 此时 b b2 中包含的 a 引用仍然是指向同一个对象. 此时修改一边 , 另一边也会发生改变。

Comparable和Comparator接口

当排序一个类的数组时:

import java.util.Arrays;
class Student {
    public int age;
    public String name;
    public double score;

    public Student(int age, String name, double score) {
        this.age = age;
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}
public class TestDemo {
    public static void main(String[] args) {

        int[] array = {1,21,3,14,5,16};
        System.out.println(Arrays.toString(array));
        Arrays.sort(array);
        System.out.println(Arrays.toString(array));
    }
}
//结果
//[1, 21, 3, 14, 5, 16]
//[1, 3, 5, 14, 16, 21]

但如果换成类的数组

Student[] students = new Student[3];
students[0] = new Student(12,"bit",98.9);
students[1] = new Student(6,"abc",88.9);
students[2] = new Student(18,"zhangsan",18.9);
Arrays.sort(students);
Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to java.lang.Comparable

因为它不知道排序什么元素。

如果自定义的类型数据要进行比较 一定要实现可以比较的接口。

如Comparable接口:

class Student implements Comparable<Student>{
    public int age;
    public String name;
    public double score;

    public Student(int age, String name, double score) {
        this.age = age;
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", score=" + score +
                '}';
    }

    //谁调用这个方法 谁就是this
    @Override
    public int compareTo(Student o) {
        return this.age-o.age;

        //根据不同的类型去比较
        //return this.age-o.age;
        //return (int) (this.score-o.score);
        //return this.name.compareTo(o.name);
    }
}
public class Test {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student(12,"bit",98.9);
        students[1] = new Student(6,"abc",88.9);
        students[2] = new Student(18,"zhangsan",18.9);
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}
//输出结果:
//[Student{age=6, name='abc', score=88.9}, Student{age=12, name='bit', score=98.9}, 
  Student{age=18, name='zhangsan', score=18.9}]
 
sort 方法中会自动调用 compareTo 方法 . compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象 .
然后比较当前对象和参数对象的大小关系 ( 按分数来算 ).
  • 如果当前对象应排在参数对象之前, 返回小于 0 的数字;
  • 如果当前对象应排在参数对象之后, 返回大于 0 的数字;
  • 如果当前对象和参数对象不分先后, 返回 0;
注意事项 : 对于 sort 方法来说 , 需要传入的数组的每个对象都是 " 可比较 " , 需要具备 compareTo 这样的能力 . 通过重写 compareTo 方法的方式 , 就可以定义比较规则。
  • 这个接口有个非常大的缺点:就是侵入性非常强,一旦写好不敢轻易改动。

接下来有比Comparable更好的方法

Comparator根据比较器进行比较

import java.util.Arrays;
import java.util.Comparator;

class Student {
    public int age;
    public String name;
    public double score;

    public Student(int age, String name, double score) {
        this.age = age;
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", 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 TestDemo {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student(12,"bit",98.9);
        students[1] = new Student(6,"abc",88.9);
        students[2] = new Student(18,"zhangsan",18.9);
        System.out.println(Arrays.toString(students));
        AgeComparator ageComparator = new AgeComparator();
        ScoreComparator scoreComparator = new ScoreComparator();
        NameComparator nameComparator = new NameComparator();
        Arrays.sort(students,nameComparator);//默认是从小到大的排序

        System.out.println(Arrays.toString(students));
    }
}

结果:

  1. [Student{age=12, name='bit', score=98.9}, Student{age=6, name='abc', score=88.9}, Student{age=18, name='zhangsan', score=18.9}]
  2. [Student{age=6, name='abc', score=88.9}, Student{age=12, name='bit', score=98.9}, Student{age=18, name='zhangsan', score=18.9}]

Comparator接口更加灵活,侵入性非常弱。

  • 11
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值