Java 基础之面向对象【抽象类&接口】
1. 抽象类
1.1 抽象方法
若定义的方法不需要具备任何功能,仅为了定义方法签名,给子类去覆写,那么可将该方法定义为 抽象方法:
// 定义抽象方法,用 abstract 修饰,且必须类本身也要用 abstract 声明才能正确编译
abstract class Person {
public abstract void run();
}
注意:若不是抽象方法,必须有函数体才能正确编译,抽象方法可不用!
1.2 抽象类
以上 Person
类就是一个抽象类,因为 run()
是个抽象方法,Person
必须被 abstract
修饰才能编译:
- 抽象类无法被实例化,只能被继承
- 继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类
- 抽象类中,可有构造方法,是供子类创建对象时,初始化父类成员使用的
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
public abstract class Animal {
public abstract void run();
}
public class Cat extends Animal {
// 覆写 run() 方法
public void run (){
System.out.println("小猫在墙头走~~~");
}
}
public class CatTest {public static void main(String[] args) {
Cat c = new Cat(); // 创建子类对象
c.run(); // 调用run方法
}
}
1.3 面向抽象编程
所谓面向抽象编程是指当设计某种重要的类时,不让该类面向具体的类,而是面向抽象类,及所设计类中的重要数据是抽象类声明的对象,而不是具体类声明的对象。就是利用 abstract
来设计实现用户需求:
public class AbstractTest {
public static void main(String[] args) {
Income2[] incomes = new Income2[]{
new Salary2(18000),
new RoyaltyIncome(3000)
};
System.out.println(totalTax(incomes));
}
public static double totalTax(Income2... incomes) {
double total = 0;
for (Income2 income : incomes) {
total = total + income.getTax();
}
return total;
}
}
abstract class Income2 {
protected double income;
public Income2(double income) {
this.income = income;
}
public abstract double getTax();
}
class Salary2 extends Income2 {
public Salary2(double income) {
super(income);
}
@Override
public double getTax() {
if (income < 5000) {
return 0;
}
return (this.income - 5000) * 0.1;
}
}
class RoyaltyIncome extends Income2 {
public RoyaltyIncome(double income) {
super(income);
}
@Override
public double getTax() {
return this.income * 0.2;
}
}
参考文章
- https://www.cnblogs.com/silence-hust/p/4154257.html
- https://www.liaoxuefeng.com/wiki/1252599548343744/1260456371027744\#0
2. 接口
2.1 概述
接口是一种引用类型,是方法的集合,定义方法与类相似,但是适用 interface
关键字定义,也会被编译为 class
文件:
类:内部封装了成员变量、构造方法和成员方法
- 可以创建对象,只能单继承
接口:封装了方法(含抽象方法、默认方法、静态方法和私有方法),可以说主要说封装了方法
- 不能创建对象,但可被实现
implements
,类似于被继承,可实现多继承,不能有字段
interface 接口名称 {
// 抽象方法
// 默认方法
// 静态方法
// 私有方法
}
- 不能创建对象,但可被实现
含有抽象方法
使用 abstract
修饰,接口比抽象类、方法更抽象,定义抽象方法时可以省略 public abstract
,没有方法体:
interface InterFaceName {
public abstract void method();
// 简写
void method();
}
含默认方法和静态方法
- 默认方法:使用
default
修饰,不可省略,供子类调用或重写 静态方法:使用
static
修饰,供接口直接调用interface InterFaceName {
public default void mehod(); {
}
public static void mehod(); {
}
}
含有私有方法
使用 private
修饰:
interface InterFaceName {
private void mehod(); {
}
}
2.2 基本实现
类与接口的关系互为实现关系,即类实现接口,该类可称为接口的实现类或接口的子类,实现的动作类似继承,格式相似,区别在于关键字,接口使用 implements
。
非抽象子类实现接口:
- 必须重写接口中所有抽象方法
- 继承了接口的默认方法,可直接调用,也可重写
格式:
class 类名 implements 接口名 {
// 重写接口中抽象方法【必须】
// 重写接口中默认方法【可选】
}
示例:
public class InterfaceTest {
public static void main(String[] args) {
// 创建子类对象
Animal a = new Animal();
a.eat();
a.sleep();
}
}
// 定义接口
interface LiveAbleAnimal {
public abstract void eat(); // 抽象方法
public abstract void sleep(); // 抽象方法
// 默认方法
public default void fly() {
System.out.println("天上飞!");
}
// 静态方法
public static void run() {
System.out.println("奔跑!");
}
}
2.2.1 抽象方法的使用
抽象方法必须全部重写:
// 定义实现类
class Animal implements LiveAbleAnimal {
@Override
public void eat() {
System.out.println("吃东西!");
}
@Override
public void sleep() {
System.out.println("睡觉!");
}
}
2.2.2 默认方法的使用
可以继承,可以重写,二选一,但是只能通过实现类的对象来调用:
// 定义实现类
class Animal implements LiveAbleAnimal {
// 覆写
@Override
public void fly() {
System.out.println("往天空飞翔!");
}
}
// 调用
a.fly();
2.2.3 静态方法
不能重写,不能使用实例调用,只能通过:接口名.静态方法
调用:
public class InterfaceTest {
public static void main(String[] args) {
// 创建子类对象
Animal a = new Animal();
a.eat();
a.sleep();
a.fly();
// 静态方法调用
LiveAbleAnimal.run();
}
}
2.2.4 私有方法的使用
- 私有方法:只有默认方法可以调用
- 私有静态方法:默认方法和静态方法可以调用
如果一个接口中有多个默认方法,并且方法中有重复的内容,那么可以抽取出来,封装到私有方法中,供默认方法
去调用。从设计的角度讲,私有的方法是对默认方法和静态方法的辅助:
public interface LiveAble {
default void func(){
func1();
func2();
}
private void func1(){
System.out.println("跑起来~~~");
}
private void func2(){
System.out.println("跑起来~~~");
}
}
2.3 接口的多实现
一个类只能有一个父类,而对接口而言,一个类可以实现多个接口,这叫做 接口的多实现;并且一个类能继承一个父类,同时实现多个接口。
实现格式:
// [ ]: 表示可选操作
class 类名 [extends 父类名] implements 接口1, 接口2,接口3... {
// 重写接口中抽象方法(必须)
// 重写接口中默认方法(不重名时可选)
}
2.3.1 抽象方法
接口中有抽象方法时,实现类必须重写抽象方法。若抽象方法有重名的,只需重写一次:
interface A {
public abstract void showA();
public abstract void show();
}
interface B {
public abstract void showB();
public abstract void show();
}
实现类:
class C implements A, B {
@Override
public void showA(){
};
@Override
public void showB(){
};
@Override
public void show(){
};
}
2.3.2 默认方法
接口中,有多个默认方法时,实现类都可继承使用。若默认方法有重名的,必须重写一次:
interface A {
public abstract void methodA();
public abstract void method();
}
interface B {
public abstract void methodB();
public abstract void method();
}
实现类:
class C implements A, B {
@Override
public void method(){
};
}
2.3.3 静态方法
接口中存在重名静态方法并不会有影响,因为调用时只能通过各自的接口来调用方法。
2.3.4 优先级
当一个类即继承了父类,又实现了一个或多个接口,父类中的成员方法与接口中的默认方法重名,子类就近选择执行父类的成员方法:
public class InterfaceTestAndExtend {
public static void main(String[] args) {
C1 c = new C1();
c.methodA(); // BBBBBBBBBBB
}
}
interface A1 {
public default void methodA() {
System.out.println("AAAAAAAAAA");
}
}
class B1 {
public void methodA() {
System.out.println("BBBBBBBBBBB");
}
}
class C1 extends B1 implements A1 {
// 未重写 methodA 方法
}
2.3.5 接口继承
一个接口也能继承一个或多个接口(类似于拓展了接口的功能),与继承类似,子接口也会继承父接口的方法。若父接口中的默认方法有重名的,则子接口必须重写一次:
public class InterfaceExtendInterface {
public static void main(String[] args) {
D2 d = new D2();
d.method(); // C2
}
}
interface A2 {
public default void method() {
System.out.println("A2");
}
}
interface B2 {
public default void method() {
System.out.println("B2");
}
}
interface C2 extends A2, B2 {
@Override
public default void method() {
System.out.println("C2");
}
}
class D2 implements C2 {
}
3. 静态字段和静态方法
3.1 静态字段
静态字段和实例字段的区别
- 实例字段:在类中定义的字段,称之为实例字段,拥有独立的空间,互不影响
静态字段:被
static
修饰的字段,共享独立空间,一个实例修改静态字段,会影响其他实例public class Main {
public static void main(String[] args) {
Person ming = new Person("Xiao Ming", 12);
Person hong = new Person("Xiao Hong", 15);
ming.number = 88;
System.out.println(hong.number); // 88
System.out.println(ming.number); // 88
}
}
class Person {
public String name; // 实例字段
public int age; // 实例字段
public static int number; // 静态字段
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
注意:调用静态字段推荐使用:
类名.静态字段
,而非实例.静态字段
,静态方法调用也是一样!
3.2 静态方法
静态方法内部无法访问 this
、实例方法、字段,只能访问静态字段:
class TestStaticMethod {
public static String name;
// 静态方法
public static void setName(String name) {
name = name;
}
}
// 调用
TestStaticMethod.setName("rose")
适用场景
常用于工具类:
Arrays.sort()
Math.random()
3.3 接口的静态字段
interface
是抽象类,不能定义实例字段。但是可以有静态字段的,并且静态字段必须为 final
类型:
public interface Person {
public static final int MALE = 1;
public static final int FEMALE = 2;
// 可简写
int MALE = 1;
int FEMALE = 2;
}
4. 包 Package
Java
中的包是一种名字空间,可以用来解决类名冲突。一个类总是属于某个包,完整的类名是:包名.类名
,在定义一个类时,需要在第一行声明这个类属于哪个包:
// hong.Main.java
package hong; // 声明 Main 这个类属于 hong 这个包
public class Main {
}
示例:
package_sample
└─ src
├─ hong
│ └─ Person.java
| └─ Main.java
├─ ming
│ └─ Person.java
└─ mr
└─ jun
└─ Arrays.java
hong、ming、mr.jun
分别属于三个不同的包Person.java、Main.java
属于同一个包hong
- 如何创建
Package
:idea 中 src
右键New
选择创建一个Package
为什么要引入包这个概念
因为在编码的时候,难免会命名重名的类,也有可能与内置的类名重名,相互调用时就难以区分具体是调用的哪个类,引入包这个概念后,通过 import 包名.类名
的方式,就很容易区分。
注意:因为在
Java
虚拟机执行的时候,JVM
只看完整类名,因此,只要包名不同,类就不同。
4.1 包的作用域
位于同一个包的类,可以访问包作用域的字段和方法,不用 public、protected、private
修饰的字段或方法就是包的作用域:
1、Person.java
:
package hong;
public class Person {
// 包作用域
void hello() {
System.out.println("Package hong class Person Hello!");
}
}
2、Main.java
:
package hong;
public class Main {
public static void main(String[] args) {
Person p = new Person();
p.hello();
}
}
Main、Person
属于同一个包 hong
,且 hello()
在包的作用域内,因此在 Main
中可以直接访问 hello()
且不用 import
。
4.2 import 导包
在一个 class
中引入另一个 class
,有二种方法:
// ming.Person
package ming;
public class Person {
String name;
public Person(String name) {
this.name = name;
}
public String hello() {
return "Hello, " + this.name + " !";
}
}
// mr.jun.Arrays
package mr.jun;
public class Arrays {
}
以上 Person
属于 包 ming
,Arrays
属于包 mr.jun
。
1、写完整类名(不推荐):
// mr.jun.Arrays
package mr.jun;
public class Arrays {
public static void main(String[] args) {
ming.Person p = new ming.Person("rose");
System.out.println(p.hello());
}
}
2、import
导入完整类名:
// mr.jun.Arrays
package mr.jun;
import ming.Person; // 导入完整类名
public class Arrays {
public static void main(String[] args) {
Person p = new Person("rose");
System.out.println(p.hello());
}
}
注意:
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
- 定义为
public
的class、interface
可以被其他任何类访问 - 定义为
public
的 字段、方法 可以被其他类方法,前提是 有访问该 class 的权限
private
- 定义为
private
的 字段、方法 无法被其他类访问 private
的权限被限定在class
内部,推荐将private
方法放在public
方法后面,便于阅读- 嵌套类拥有访问
private
的权限
protected
protected
常用语继承关系中,定义为 protected
的字段、方法可以被其子类访问(以及子类的子类):
package abc;
public class Hello {
// protected方法:
protected void hi() {
}
}
上面的 protected
方法可以被继承的类访问:
package xyz;
class Main extends Hello {
void foo() {
// 可以访问protected方法:
hi();
}
}
package
包的作用域在第 4 节中有提到,指的是 同一个包中,一个 class 访问另一个 class 中没有被 public、private 修饰的 class、字段或方法:
1、Person.java
:
package hong;
public class Person {
// 包作用域
void hello() {
System.out.println("Package hong class Person Hello!");
}
}
2、Main.java
:
package hong;
public class Main {
public static void main(String[] args) {
Person p = new Person();
p.hello();
}
}
Main、Person
属于同一个包 hong
,且 hello()
在包的作用域内,因此在 Main
中可以直接访问 hello()
且不用 import
。
final
final
修饰符与访问权限不冲突,它有很多作用:
- 修饰
class
可阻止被继承 - 修饰
method
可阻止被子类覆写 - 修饰
field
可阻止被重新赋值 修饰局部变量可阻止被重新赋值
package hello
// 阻止 Hi 被继承
public final class Hi {private final int age = 18; // 阻止重新赋值
// 阻止被覆写
protected final void seehi() {
// 阻止局部变量重新赋值
String name = "rose";
}
}
总结
一个 .java
文件只能包含一个 public
类,但可以包含多个非 public
类。如果有 public
类,文件名必须和 public
类的名字相同,尽量减少暴露对外的字段和方法。
6. 内部类
6.1 内部类 Inner Class
所谓内部类即定义在一个类内部的类,它可以用来修改被 private
修饰的字段:
package inner_class_test;
public class InnerTest {
public static void main(String[] args) {
OuterClass oc = new OuterClass("rose");
// 内部类的实例只能通过外部类的实例来创建
OuterClass.InnerClass ic = oc.new InnerClass();
ic.setName();
}
}
// 外部类
class OuterClass {
private String name;
OuterClass(String name) {
this.name = name;
}
// 内部类
class InnerClass {
void setName() {
OuterClass.this.name = "lila";
System.out.println("Hello " + OuterClass.this.name);
}
}
}
注意:内部类的实例必须通过外部类来创建,不能单独存在,外部类被编译后会变为:
OuterClass$InnerClass.class
6.2 匿名类 Anonymous Class
匿名类不需像内部类一样明确定义类名,本质是一个带具体实现的父类或者父接口的匿名的子类对象。
开发中,最常用到的内部类就是匿名内部类了。
匿名内部类必须继承一个父类或实现一个父接口,以接口为例,实现一个匿名类分为以下步骤:
- 定义子类
- 重新接口中的方法
- 创建子类对象
- 调用重写后的方法
格式:
new 父类名或接口名() {
// 方法重写
@Override
public vodi method() {
// 执行语句
}
}
示例:
package inner_class_test;
public class AnonymousTest {
public static void main(String[] args) {
// 创建匿名内部类
// 等号右边:匿名内部类,定义并创建该接口的子类对象
// 等号左边:多态赋值,接口类型引用指向子类对象
FlyAble f = new FlyAble() {
@Override
public void fly() {
System.out.println("我飞了....");
}
};
// 调用 fly(),执行重写后的方法
f.fly();
}
}
// 定义接口
abstract class FlyAble {
public abstract void fly();
}
通常在方法的形参是接口或者抽象类时,也可以将匿名类作为参数传递:
package inner_class_test;
public class AnonymousTest {
public static void main(String[] args) {
// 创建匿名内部类
// 等号右边:匿名内部类,定义并创建该接口的子类对象
// 等号左边:多态赋值,接口类型引用指向子类对象
FlyAble f = new FlyAble() {
@Override
public void fly() {
System.out.println("我飞了....");
}
};
// // 调用 fly(),执行重写后的方法
// f.fly();
// 当做参数传递
showFly(f);
}
public static void showFly(FlyAble f) {
f.fly();
}
}
// 定义接口
abstract class FlyAble {
public abstract void fly();
}
简写:
package inner_class_test;
public class AnonymousTest {
public static void main(String[] args) {
// 简写
showFly(new FlyAble() {
@Override
public void fly() {
System.out.println("我飞了....");
}
});
}
public static void showFly(FlyAble f) {
f.fly();
}
}
// 定义接口
abstract class FlyAble {
public abstract void fly();
}
6.3 静态内部类 Static Nested Class
静态内部类是完全独立的类,不依附于外部类的示例,但是依然可以访问外部类的 private
静态字段和静态方法:
package inner_class_test;
public class StaticInnerClass {
public static void main(String[] args) {
Outer.StaticClass sc = new Outer.StaticClass();
sc.hello();
}
}
class Outer {
private static String NAME = "static field";
private String name = "rose";
Outer(String name) {
this.name = name;
}
static class StaticClass {
void hello() {
System.out.println("Hello " + Outer.NAME);
// System.out.println(Outer.name); // 非静态字段不能访问
}
}
}
7. classpath
classpath
可以理解为 JVM
运行环境变量,主要用于辅助搜索 class
。classpath
是一组目录的集合,设置的搜索路径与操作系统有关,在 Windows
上用 ;
分隔,带空格的目录用:""
包裹起来,类似于:
// windows
C:\work\project1\bin;C:\shared;"D:\My Documents\project1\bin"
// linux 上用 : 分隔
/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 包
JarTest
└─ src
├─ demo
│ └─ test
| | └─ test.java
| └─ util
| | └─ demo.java
1、demo.java
:
package demo.util;
public class demo {
public int println(int item) {
return item + 100;
}
}
2、测试类 test.java
:
package demo.test;
import demo.util.demo;
public class test {
public static void main(String[] args) {
demo d = new demo();
System.out.println(d.println(20000));
}
}
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
文件,用 idea
打 jar
包后会自动创建,指定了 Main-Class
和其他信息,JVM
会自动读取这个文件,若存在 Main-Class
则在命令行运行 jar
包时不需要指定启动的类名:
// 运行 jar 包
// java -jar JarTest.jar
Manifest-Version: 1.0
Main-Class: demo.test.test
若想在 jar
包中包含其他 jar
包,则需要在 MANIFEST.MF
文件里配置 classpath
。
还没有评论,来说两句吧...