Java 基础之面向对象【抽象类&接口】

小鱼儿 2023-09-29 18:30 123阅读 0赞

1. 抽象类

1.1 抽象方法

若定义的方法不需要具备任何功能,仅为了定义方法签名,给子类去覆写,那么可将该方法定义为 抽象方法

  1. // 定义抽象方法,用 abstract 修饰,且必须类本身也要用 abstract 声明才能正确编译
  2. abstract class Person {
  3. public abstract void run();
  4. }

注意:若不是抽象方法,必须有函数体才能正确编译,抽象方法可不用!

1.2 抽象类

以上 Person 类就是一个抽象类,因为 run() 是个抽象方法,Person 必须被 abstract 修饰才能编译:

  • 抽象类无法被实例化,只能被继承
  • 继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类
  • 抽象类中,可有构造方法,是供子类创建对象时,初始化父类成员使用的
  • 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类

    public abstract class Animal {

    1. public abstract void run();

    }

    public class Cat extends Animal {

    1. // 覆写 run() 方法
    2. public void run (){
    3. System.out.println("小猫在墙头走~~~");
    4. }

    }
    public class CatTest {

    1. public static void main(String[] args) {
    2. Cat c = new Cat(); // 创建子类对象
    3. c.run(); // 调用run方法
    4. }

    }

1.3 面向抽象编程

所谓面向抽象编程是指当设计某种重要的类时,不让该类面向具体的类而是面向抽象类,及所设计类中的重要数据是抽象类声明的对象,而不是具体类声明的对象。就是利用 abstract 来设计实现用户需求:

  1. public class AbstractTest {
  2. public static void main(String[] args) {
  3. Income2[] incomes = new Income2[]{
  4. new Salary2(18000),
  5. new RoyaltyIncome(3000)
  6. };
  7. System.out.println(totalTax(incomes));
  8. }
  9. public static double totalTax(Income2... incomes) {
  10. double total = 0;
  11. for (Income2 income : incomes) {
  12. total = total + income.getTax();
  13. }
  14. return total;
  15. }
  16. }
  17. abstract class Income2 {
  18. protected double income;
  19. public Income2(double income) {
  20. this.income = income;
  21. }
  22. public abstract double getTax();
  23. }
  24. class Salary2 extends Income2 {
  25. public Salary2(double income) {
  26. super(income);
  27. }
  28. @Override
  29. public double getTax() {
  30. if (income < 5000) {
  31. return 0;
  32. }
  33. return (this.income - 5000) * 0.1;
  34. }
  35. }
  36. class RoyaltyIncome extends Income2 {
  37. public RoyaltyIncome(double income) {
  38. super(income);
  39. }
  40. @Override
  41. public double getTax() {
  42. return this.income * 0.2;
  43. }
  44. }

参考文章

  • https://www.cnblogs.com/silence-hust/p/4154257.html
  • https://www.liaoxuefeng.com/wiki/1252599548343744/1260456371027744\#0

2. 接口

2.1 概述

接口是一种引用类型,是方法的集合,定义方法与类相似,但是适用 interface 关键字定义,也会被编译为 class 文件:

  • 类:内部封装了成员变量、构造方法和成员方法

    • 可以创建对象,只能单继承
  • 接口:封装了方法(含抽象方法、默认方法、静态方法和私有方法),可以说主要说封装了方法

    • 不能创建对象,但可被实现 implements,类似于被继承,可实现多继承,不能有字段

    interface 接口名称 {

    1. // 抽象方法
    2. // 默认方法
    3. // 静态方法
    4. // 私有方法

    }

含有抽象方法

使用 abstract 修饰,接口比抽象类、方法更抽象,定义抽象方法时可以省略 public abstract,没有方法体:

  1. interface InterFaceName {
  2. public abstract void method();
  3. // 简写
  4. void method();
  5. }

含默认方法和静态方法

  • 默认方法:使用 default 修饰,不可省略,供子类调用或重写
  • 静态方法:使用 static 修饰,供接口直接调用

    interface InterFaceName {

    1. public default void mehod(); {
  1. }
  2. public static void mehod(); {
  3. }
  4. }

含有私有方法

使用 private 修饰:

  1. interface InterFaceName {
  2. private void mehod(); {
  3. }
  4. }

2.2 基本实现

类与接口的关系互为实现关系,即类实现接口,该类可称为接口的实现类或接口的子类,实现的动作类似继承,格式相似,区别在于关键字,接口使用 implements

非抽象子类实现接口:

  • 必须重写接口中所有抽象方法
  • 继承了接口的默认方法,可直接调用,也可重写

格式:

  1. class 类名 implements 接口名 {
  2. // 重写接口中抽象方法【必须】
  3. // 重写接口中默认方法【可选】
  4. }

示例:

  1. public class InterfaceTest {
  2. public static void main(String[] args) {
  3. // 创建子类对象
  4. Animal a = new Animal();
  5. a.eat();
  6. a.sleep();
  7. }
  8. }
  9. // 定义接口
  10. interface LiveAbleAnimal {
  11. public abstract void eat(); // 抽象方法
  12. public abstract void sleep(); // 抽象方法
  13. // 默认方法
  14. public default void fly() {
  15. System.out.println("天上飞!");
  16. }
  17. // 静态方法
  18. public static void run() {
  19. System.out.println("奔跑!");
  20. }
  21. }

2.2.1 抽象方法的使用

抽象方法必须全部重写:

  1. // 定义实现类
  2. class Animal implements LiveAbleAnimal {
  3. @Override
  4. public void eat() {
  5. System.out.println("吃东西!");
  6. }
  7. @Override
  8. public void sleep() {
  9. System.out.println("睡觉!");
  10. }
  11. }

2.2.2 默认方法的使用

可以继承,可以重写,二选一,但是只能通过实现类的对象来调用:

  1. // 定义实现类
  2. class Animal implements LiveAbleAnimal {
  3. // 覆写
  4. @Override
  5. public void fly() {
  6. System.out.println("往天空飞翔!");
  7. }
  8. }
  9. // 调用
  10. a.fly();

2.2.3 静态方法

不能重写,不能使用实例调用,只能通过:接口名.静态方法 调用:

  1. public class InterfaceTest {
  2. public static void main(String[] args) {
  3. // 创建子类对象
  4. Animal a = new Animal();
  5. a.eat();
  6. a.sleep();
  7. a.fly();
  8. // 静态方法调用
  9. LiveAbleAnimal.run();
  10. }
  11. }

2.2.4 私有方法的使用

  • 私有方法:只有默认方法可以调用
  • 私有静态方法:默认方法和静态方法可以调用

如果一个接口中有多个默认方法,并且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法
去调用。从设计的角度讲,私有的方法是对默认方法和静态方法的辅助:

  1. public interface LiveAble {
  2. default void func(){
  3. func1();
  4. func2();
  5. }
  6. private void func1(){
  7. System.out.println("跑起来~~~");
  8. }
  9. private void func2(){
  10. System.out.println("跑起来~~~");
  11. }
  12. }

2.3 接口的多实现

一个类只能有一个父类,而对接口而言,一个类可以实现多个接口,这叫做 接口的多实现;并且一个类能继承一个父类,同时实现多个接口。

实现格式:

  1. // [ ]: 表示可选操作
  2. class 类名 [extends 父类名] implements 接口1, 接口2,接口3... {
  3. // 重写接口中抽象方法(必须)
  4. // 重写接口中默认方法(不重名时可选)
  5. }

2.3.1 抽象方法

接口中有抽象方法时,实现类必须重写抽象方法。若抽象方法有重名的,只需重写一次

  1. interface A {
  2. public abstract void showA();
  3. public abstract void show();
  4. }
  5. interface B {
  6. public abstract void showB();
  7. public abstract void show();
  8. }

实现类:

  1. class C implements A, B {
  2. @Override
  3. public void showA(){
  4. };
  5. @Override
  6. public void showB(){
  7. };
  8. @Override
  9. public void show(){
  10. };
  11. }

2.3.2 默认方法

接口中,有多个默认方法时,实现类都可继承使用。若默认方法有重名的,必须重写一次

  1. interface A {
  2. public abstract void methodA();
  3. public abstract void method();
  4. }
  5. interface B {
  6. public abstract void methodB();
  7. public abstract void method();
  8. }

实现类:

  1. class C implements A, B {
  2. @Override
  3. public void method(){
  4. };
  5. }

2.3.3 静态方法

接口中存在重名静态方法并不会有影响,因为调用时只能通过各自的接口来调用方法。

2.3.4 优先级

当一个类即继承了父类,又实现了一个或多个接口,父类中的成员方法与接口中的默认方法重名,子类就近选择执行父类的成员方法

  1. public class InterfaceTestAndExtend {
  2. public static void main(String[] args) {
  3. C1 c = new C1();
  4. c.methodA(); // BBBBBBBBBBB
  5. }
  6. }
  7. interface A1 {
  8. public default void methodA() {
  9. System.out.println("AAAAAAAAAA");
  10. }
  11. }
  12. class B1 {
  13. public void methodA() {
  14. System.out.println("BBBBBBBBBBB");
  15. }
  16. }
  17. class C1 extends B1 implements A1 {
  18. // 未重写 methodA 方法
  19. }

2.3.5 接口继承

一个接口也能继承一个或多个接口(类似于拓展了接口的功能),与继承类似,子接口也会继承父接口的方法。若父接口中的默认方法有重名的,则子接口必须重写一次

  1. public class InterfaceExtendInterface {
  2. public static void main(String[] args) {
  3. D2 d = new D2();
  4. d.method(); // C2
  5. }
  6. }
  7. interface A2 {
  8. public default void method() {
  9. System.out.println("A2");
  10. }
  11. }
  12. interface B2 {
  13. public default void method() {
  14. System.out.println("B2");
  15. }
  16. }
  17. interface C2 extends A2, B2 {
  18. @Override
  19. public default void method() {
  20. System.out.println("C2");
  21. }
  22. }
  23. class D2 implements C2 {
  24. }

3. 静态字段和静态方法

3.1 静态字段

静态字段和实例字段的区别

  • 实例字段:在类中定义的字段,称之为实例字段,拥有独立的空间,互不影响
  • 静态字段:被 static 修饰的字段,共享独立空间,一个实例修改静态字段,会影响其他实例

    public class Main {

    1. public static void main(String[] args) {
    2. Person ming = new Person("Xiao Ming", 12);
    3. Person hong = new Person("Xiao Hong", 15);
    4. ming.number = 88;
    5. System.out.println(hong.number); // 88
    6. System.out.println(ming.number); // 88
    7. }

    }

    class Person {

    1. public String name; // 实例字段
    2. public int age; // 实例字段
    3. public static int number; // 静态字段
    4. public Person(String name, int age) {
    5. this.name = name;
    6. this.age = age;
    7. }

    }

注意:调用静态字段推荐使用:类名.静态字段,而非 实例.静态字段,静态方法调用也是一样!

3.2 静态方法

静态方法内部无法访问 this、实例方法、字段,只能访问静态字段:

  1. class TestStaticMethod {
  2. public static String name;
  3. // 静态方法
  4. public static void setName(String name) {
  5. name = name;
  6. }
  7. }
  8. // 调用
  9. TestStaticMethod.setName("rose")

适用场景

常用于工具类:

  • Arrays.sort()
  • Math.random()

3.3 接口的静态字段

interface 是抽象类,不能定义实例字段。但是可以有静态字段的,并且静态字段必须为 final 类型:

  1. public interface Person {
  2. public static final int MALE = 1;
  3. public static final int FEMALE = 2;
  4. // 可简写
  5. int MALE = 1;
  6. int FEMALE = 2;
  7. }

4. 包 Package

Java 中的包是一种名字空间,可以用来解决类名冲突。一个类总是属于某个包,完整的类名是:包名.类名,在定义一个类时,需要在第一行声明这个类属于哪个包:

  1. // hong.Main.java
  2. package hong; // 声明 Main 这个类属于 hong 这个包
  3. public class Main {
  4. }

示例:

  1. package_sample
  2. └─ src
  3. ├─ hong
  4. └─ Person.java
  5. | └─ Main.java
  6. ├─ ming
  7. └─ Person.java
  8. └─ mr
  9. └─ jun
  10. └─ Arrays.java
  • hong、ming、mr.jun 分别属于三个不同的包
  • Person.java、Main.java 属于同一个包 hong
  • 如何创建 Packageidea 中 src 右键 New 选择创建一个 Package

为什么要引入包这个概念

因为在编码的时候,难免会命名重名的类,也有可能与内置的类名重名,相互调用时就难以区分具体是调用的哪个类,引入包这个概念后,通过 import 包名.类名 的方式,就很容易区分。

注意:因为在 Java 虚拟机执行的时候,JVM 只看完整类名,因此,只要包名不同,类就不同。

4.1 包的作用域

位于同一个包的类,可以访问包作用域的字段和方法,不用 public、protected、private 修饰的字段或方法就是包的作用域:

1、Person.java

  1. package hong;
  2. public class Person {
  3. // 包作用域
  4. void hello() {
  5. System.out.println("Package hong class Person Hello!");
  6. }
  7. }

2、Main.java

  1. package hong;
  2. public class Main {
  3. public static void main(String[] args) {
  4. Person p = new Person();
  5. p.hello();
  6. }
  7. }

Main、Person 属于同一个包 hong,且 hello() 在包的作用域内,因此在 Main 中可以直接访问 hello() 且不用 import

4.2 import 导包

在一个 class 中引入另一个 class,有二种方法:

  1. // ming.Person
  2. package ming;
  3. public class Person {
  4. String name;
  5. public Person(String name) {
  6. this.name = name;
  7. }
  8. public String hello() {
  9. return "Hello, " + this.name + " !";
  10. }
  11. }
  12. // mr.jun.Arrays
  13. package mr.jun;
  14. public class Arrays {
  15. }

以上 Person 属于 包 mingArrays 属于包 mr.jun

1、写完整类名(不推荐):

  1. // mr.jun.Arrays
  2. package mr.jun;
  3. public class Arrays {
  4. public static void main(String[] args) {
  5. ming.Person p = new ming.Person("rose");
  6. System.out.println(p.hello());
  7. }
  8. }

2、import 导入完整类名:

  1. // mr.jun.Arrays
  2. package mr.jun;
  3. import ming.Person; // 导入完整类名
  4. public class Arrays {
  5. public static void main(String[] args) {
  6. Person p = new Person("rose");
  7. System.out.println(p.hello());
  8. }
  9. }

注意:import ming.Person.* 可以将 Person.java 中所有 class 都导入进来,一般不推荐!

4.3 最佳实践

为了避免命名重试,需确保包名唯一,一般使用 倒置的域名 来确保唯一性:

  • org.apache
  • org.apache.commons.log
  • com.liaoxuefeng.sample

注意:包名不要与 JDK、java.lang 常用类重名,子包按照功能进行命名一般不会有冲突!

5. 作用域

public、protected、private 这些修饰符可以用来限定访问作用域:

public

  • 定义为 publicclass、interface 可以被其他任何类访问
  • 定义为 public字段、方法 可以被其他类方法,前提是 有访问该 class 的权限

private

  • 定义为 private字段、方法 无法被其他类访问
  • private 的权限被限定在 class 内部,推荐将 private 方法放在 public 方法后面,便于阅读
  • 嵌套类拥有访问 private 的权限

protected

protected 常用语继承关系中,定义为 protected 的字段、方法可以被其子类访问(以及子类的子类):

  1. package abc;
  2. public class Hello {
  3. // protected方法:
  4. protected void hi() {
  5. }
  6. }

上面的 protected 方法可以被继承的类访问:

  1. package xyz;
  2. class Main extends Hello {
  3. void foo() {
  4. // 可以访问protected方法:
  5. hi();
  6. }
  7. }

package

包的作用域在第 4 节中有提到,指的是 同一个包中,一个 class 访问另一个 class 中没有被 public、private 修饰的 class、字段或方法

1、Person.java

  1. package hong;
  2. public class Person {
  3. // 包作用域
  4. void hello() {
  5. System.out.println("Package hong class Person Hello!");
  6. }
  7. }

2、Main.java

  1. package hong;
  2. public class Main {
  3. public static void main(String[] args) {
  4. Person p = new Person();
  5. p.hello();
  6. }
  7. }

Main、Person 属于同一个包 hong,且 hello() 在包的作用域内,因此在 Main 中可以直接访问 hello() 且不用 import

final

final 修饰符与访问权限不冲突,它有很多作用:

  • 修饰 class 可阻止被继承
  • 修饰 method 可阻止被子类覆写
  • 修饰 field 可阻止被重新赋值
  • 修饰局部变量可阻止被重新赋值

    package hello

    // 阻止 Hi 被继承
    public final class Hi {

    1. private final int age = 18; // 阻止重新赋值
    2. // 阻止被覆写
    3. protected final void seehi() {
    4. // 阻止局部变量重新赋值
    5. String name = "rose";
    6. }

    }

总结

一个 .java 文件只能包含一个 public 类,但可以包含多个非 public 类。如果有 public 类,文件名必须和 public 类的名字相同,尽量减少暴露对外的字段和方法。

6. 内部类

6.1 内部类 Inner Class

所谓内部类即定义在一个类内部的类,它可以用来修改被 private 修饰的字段:

  1. package inner_class_test;
  2. public class InnerTest {
  3. public static void main(String[] args) {
  4. OuterClass oc = new OuterClass("rose");
  5. // 内部类的实例只能通过外部类的实例来创建
  6. OuterClass.InnerClass ic = oc.new InnerClass();
  7. ic.setName();
  8. }
  9. }
  10. // 外部类
  11. class OuterClass {
  12. private String name;
  13. OuterClass(String name) {
  14. this.name = name;
  15. }
  16. // 内部类
  17. class InnerClass {
  18. void setName() {
  19. OuterClass.this.name = "lila";
  20. System.out.println("Hello " + OuterClass.this.name);
  21. }
  22. }
  23. }

注意:内部类的实例必须通过外部类来创建,不能单独存在,外部类被编译后会变为:OuterClass$InnerClass.class

6.2 匿名类 Anonymous Class

匿名类不需像内部类一样明确定义类名,本质是一个带具体实现的父类或者父接口的匿名的子类对象
开发中,最常用到的内部类就是匿名内部类了。

匿名内部类必须继承一个父类或实现一个父接口,以接口为例,实现一个匿名类分为以下步骤:

  • 定义子类
  • 重新接口中的方法
  • 创建子类对象
  • 调用重写后的方法

格式:

  1. new 父类名或接口名() {
  2. // 方法重写
  3. @Override
  4. public vodi method() {
  5. // 执行语句
  6. }
  7. }

示例:

  1. package inner_class_test;
  2. public class AnonymousTest {
  3. public static void main(String[] args) {
  4. // 创建匿名内部类
  5. // 等号右边:匿名内部类,定义并创建该接口的子类对象
  6. // 等号左边:多态赋值,接口类型引用指向子类对象
  7. FlyAble f = new FlyAble() {
  8. @Override
  9. public void fly() {
  10. System.out.println("我飞了....");
  11. }
  12. };
  13. // 调用 fly(),执行重写后的方法
  14. f.fly();
  15. }
  16. }
  17. // 定义接口
  18. abstract class FlyAble {
  19. public abstract void fly();
  20. }

通常在方法的形参是接口或者抽象类时,也可以将匿名类作为参数传递:

  1. package inner_class_test;
  2. public class AnonymousTest {
  3. public static void main(String[] args) {
  4. // 创建匿名内部类
  5. // 等号右边:匿名内部类,定义并创建该接口的子类对象
  6. // 等号左边:多态赋值,接口类型引用指向子类对象
  7. FlyAble f = new FlyAble() {
  8. @Override
  9. public void fly() {
  10. System.out.println("我飞了....");
  11. }
  12. };
  13. // // 调用 fly(),执行重写后的方法
  14. // f.fly();
  15. // 当做参数传递
  16. showFly(f);
  17. }
  18. public static void showFly(FlyAble f) {
  19. f.fly();
  20. }
  21. }
  22. // 定义接口
  23. abstract class FlyAble {
  24. public abstract void fly();
  25. }

简写:

  1. package inner_class_test;
  2. public class AnonymousTest {
  3. public static void main(String[] args) {
  4. // 简写
  5. showFly(new FlyAble() {
  6. @Override
  7. public void fly() {
  8. System.out.println("我飞了....");
  9. }
  10. });
  11. }
  12. public static void showFly(FlyAble f) {
  13. f.fly();
  14. }
  15. }
  16. // 定义接口
  17. abstract class FlyAble {
  18. public abstract void fly();
  19. }

6.3 静态内部类 Static Nested Class

静态内部类是完全独立的类,不依附于外部类的示例,但是依然可以访问外部类的 private 静态字段和静态方法

  1. package inner_class_test;
  2. public class StaticInnerClass {
  3. public static void main(String[] args) {
  4. Outer.StaticClass sc = new Outer.StaticClass();
  5. sc.hello();
  6. }
  7. }
  8. class Outer {
  9. private static String NAME = "static field";
  10. private String name = "rose";
  11. Outer(String name) {
  12. this.name = name;
  13. }
  14. static class StaticClass {
  15. void hello() {
  16. System.out.println("Hello " + Outer.NAME);
  17. // System.out.println(Outer.name); // 非静态字段不能访问
  18. }
  19. }
  20. }

7. classpath

classpath 可以理解为 JVM 运行环境变量,主要用于辅助搜索 classclasspath 是一组目录的集合,设置的搜索路径与操作系统有关,在 Windows 上用 ; 分隔,带空格的目录用:"" 包裹起来,类似于:

  1. // windows
  2. C:\work\project1\bin;C:\shared;"D:\My Documents\project1\bin"
  3. // linux 上用 : 分隔
  4. /usr/shared:/usr/local/bin:/home/rose/bin

假设 classpath 是:/usr/shared:/usr/local/bin:/home/rose/bin,当 JVM 加载某个 class 时会依次从左至右查找,直至找到(停止)或为未找到(失败)为止:

  • /usr/shared/xxx/hello.class
  • /usr/local/xxx/hello.class
  • /home/rose/hello.class

classpath 设置

  • 系统环境变量中设置:不推荐(污染整个系统环境)
  • 启动 JVM 是设置:推荐

    java -classpath .;C:\work\project1\bin;C:\shared abc.xyz.Hello
    java -cp .;C:\work\project1\bin;C:\shared abc.xyz.Hello // 简写
    java abc.xyz.Hello // 默认是当前目录,即 .

7.1 jar 包

为什么要打 jar 包

给别人用的时候一般给别人的是 class 文件,如果 class 太多就不方便,把 class 变为一个压缩包(.jar 形式的)

如何查看 jar 包

压缩软件解压即可(解压后都是 .class 结尾的文件)

如何利用 idea 构建 jar 包

  1. JarTest
  2. └─ src
  3. ├─ demo
  4. └─ test
  5. | | └─ test.java
  6. | └─ util
  7. | | └─ demo.java

1、demo.java

  1. package demo.util;
  2. public class demo {
  3. public int println(int item) {
  4. return item + 100;
  5. }
  6. }

2、测试类 test.java

  1. package demo.test;
  2. import demo.util.demo;
  3. public class test {
  4. public static void main(String[] args) {
  5. demo d = new demo();
  6. System.out.println(d.println(20000));
  7. }
  8. }

3、idea 右上角选择 Project Structure,再选择左侧的 Artifacts,选择 + 添加 JAR,选择 From modules with dependencies,此时会弹出一个弹窗:

  • Module:选择 JarTest
  • Main Class:选择测试类:test.java

4、构建 jar 包:idea - Build - Build Artifacts - 再选择具体 jar 包(JarTest.jar),过几秒钟就能构建完毕!

idea 如何导入 jar 包

参考:IDEA导入jar包

META-INF MANIFEST.MF文件

MANIFEST.MF 文件,用 ideajar 包后会自动创建,指定了 Main-Class 和其他信息,JVM 会自动读取这个文件,若存在 Main-Class 则在命令行运行 jar 包时不需要指定启动的类名:

  1. // 运行 jar 包
  2. // java -jar JarTest.jar
  3. Manifest-Version: 1.0
  4. Main-Class: demo.test.test

若想在 jar 包中包含其他 jar 包,则需要在 MANIFEST.MF 文件里配置 classpath

发表评论

表情:
评论列表 (有 0 条评论,123人围观)

还没有评论,来说两句吧...

相关阅读