Java特性 包 继承 多态 抽象类和接口

一、包

1.1 包概念

标准的 Java包具有一个层次结构。如同硬盘的目录嵌套一样,也可以使用嵌套层次组织包。所有标准的Java 包都处于 java 和 javax 包层次中。

Java 允许使用包( package > 将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。使用包的主要原因是确保类名的唯一性。

当我们在同一个包中创建两个名称相同的Test类,显然会存在问题,这时只需要将两个类放在不同的包中,就不会产生冲突。
在这里插入图片描述
我们在src中新建了com包
在这里插入图片描述
在com包中新建一个java文件,这时该类中的第一行会多一句package com;此关键字表明当前java类所在包的位置。

package com;
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: SweiJ
 * Date: 2021-11-20
 * Time: 9:34
 * @author SweiJ
 */
public class Test {

    public static void main(String[] args) {

    }
}

1.2 类的导入

一个类可以使用所属包中的所有类, 以及其他包中的公有类( public class)。我们可以采用两种方式访问另一个包中的公有类。第一种方式是在每个类名之前添加完整的包名。
例如:

java.util.Scanner scanner = new java.util.Scanner(System.in);

这样的话会很麻烦,因此我们可以使用import语句。import 语句是一种引用包含在包中的类的简明描述。一旦使用了 import 语句,在使用类时,就不必写出包的全名了。

可以使用 import 语句导人一个特定的类或者整个包。import 语句应该位于源文件的顶部(但位于 package 语句的后面)。

注意:
java.util 和 java.sql 包都有日期 (Date) 类。 如果在程序中导入了这两个包:

import java.util.*;
import java.sql.*;

在程序使用 Date 类的时候, 就会出现一个编译错误:

Date today; // Error java.util.Date or java.sql .Date?

此时编译器无法确定程序使用的是哪一个 Date 类。可以采用增加一个特定的 import 语句来解决这个问题:

import java.util.*;
import java.sql.*;
import java.util.Date;

如果这两个 Date 类都需要使用, 又该怎么办呢? 答案是,在每个类名的前面加上完整的包名。

java.util.Date deadline = new java.util .Date();
java.sql.Date today = new java.sql .Date(...);

package和import的区别
1、package为了说明当前类所在包的位置
2、import为了导入某个包中特定的类或者包中全部的类

1.3 静态导入

import 语句不仅可以导人类,还增加了导人静态方法和静态域的功能。

例如,如果在源文件的顶部, 添加一条指令:

import static java.lang.System.*;

就可以使用 System 类的静态方法和静态域,而不必加类名前缀:

out.println("Goodbye, World!"); 

另外,还可以导入特定的方法或域:

import static java.lang.System.out;

实际上,是否有更多的程序员采用 System.out 或 System.exit 的简写形式,似乎是一件值得怀疑的事情。这种编写形式不利于代码的清晰度。不过,

sqrt(pow(x, 2) + pow(y, 2))

看起来比

Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))

清晰得多。

1.4 包访问权限

在之前基础的学习中,我们可能遇到了类中的public和private,如果标记了public可以在其他任意的类中使用,如果标记了private只能在定义它的类中使用,如果什么都没有标记,说明只能被同一个包中使用。

package com.swei.demo
public class Demo1 {
	int val = 0;
}
package com.swei.demo1
public class Test {
	public static void main(String[] args) {
		Demo1 demo = new demo();
		System.out.println(demo.val);
	}
}

上述代码会编译出错,Test类和Demo1类不在同一个包中,而且Demo1中的val没有加标记。

常见的系统包

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

Java 中对于字段和方法共有四种访问权限

  • private: 类内部能访问, 类外部不能访问
  • 默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
  • protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
  • public : 类内部和类的调用者都能访问
    在这里插入图片描述

二、继承

2.1 继承概念

java中的继承是面向对象编程的核心概念之一。当我们在对象之间有is-a关系时使用 Java 继承。Java
中的继承是使用extends关键字实现的。

首先来看下面代码

// Animal.java
public class Animal {
	private String name;
	public String getName() {
		return this.name; 
	}
	public void setName(String name) {
		this.name = name;
	}
	public Animal(String name) {
		this.name = name;
	}
	public void eat(String food) {
		System.out.println(this.name + "正在吃" + food);
	}
}
// Cat.java
class Cat {
	private String name;
	public Cat(String name) {
		this.name = name;
	}
	public void eat(String food) {
		System.out.println(this.name + "正在吃" + food);
	}
}
// Bird.java
class Bird {
	private 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 + "正在飞 ︿( ̄︶ ̄)︿");
	}
}

我们定义了AnimalCatBird 类。在这些类中,我们会发现有很多冗余代码,每个类中都有name属性和eat的方法 。

当前就可以使用继承来简化代码。利用继承,人们可以基于已存在的类构造一个新类。继承已存在的类就是复用(继承)这些类的方法和域。在此基础上,还可以添加一些新的方法和域, 以满足新的需求。

语法规则

class 子类 extends 父类 {

}

下面我们就用继承来改进代码

// Animal.java
public class Animal {
	private String name;
	public String getName() {
		return this.name; 
	}
	public Animal(String name) {
		this.name = name;
	}
	public void eat(String food) {
		System.out.println(this.name + "正在吃" + food);
	}
}
// Cat.java
class Cat extends Animal {
	public Cat(String name) {
		super(name);
	}
}
// Bird.java
class Bird extends Animal {
	public Bird(String name) {
		super(name);
	}
	public void fly() {
		System.out.println(this.getName() + "正在飞 ︿( ̄︶ ̄)︿");
	}
}

关键字 extends 表明正在构造的新类派生于一个已存在的类。 已存在的类称为超类( superclass )、 基类(base class ) 或父类(parent class); 新类称为子类(subclass) 、派生类( derived class ) 或孩子类(child class )。

1、代码重用是继承最重要的好处
2、子类不能访问超类的私有成员。但是可以使用geter和seter方法间接访问
3、子类不能继承超类构造方法
4、如果超类没有默认构造方法,子类就必须定义一个显式构造方法。子类的构造方法必须调用超类的构造方法,并且该语句在子类构造方法第一句。
5、Java不支持多继承
6、不能在Java中扩展Final类
7、可以使用instanceof来检查对象之间的继承关系

2.2 覆盖方法(重写)

然而, 超类中的有些方法对子类 Manager 并不一定适用。在Animal类中该动物吃的是包子,但是猫吃的是猫粮。为此,需要提供一个新的方法来覆盖(override)超类中的这个方法:

// Animal.java
public class Animal {
	private String name;
	public Animal(String name) {
		this.name = name;
	}
	public String getName() {
		return this.name; 
	}
	public void eat() {
		System.out.println(this.name + "正在吃包子");
	}
}
// Cat.java
class Cat extends Animal {
	public Cat(String name) {
		super(name);
	}
	@override
	public void eat() {
		System.out.println(this.getName() + "正在吃猫粮");
	}
}

注意:

  • 普通方法可以重写, static 修饰的静态方法不能重写.
  • 重写中子类的方法的访问权限不能低于父类的方法访问权限.
  • 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外)

重写和重载的区别
1、重载 方法名称相同,参数的类型及个数不同
2、重写 方法名称、返回值类型、参数的类型及个数完全相同
3、重载只作用于当前类,而重写需要有继承关系
4、重载没有权限要求,而被重写的方法不能拥有比父类更严格的访问控制权限

2.3 super关键字

我们在创建对象的时候,那么子类或父类的构造方法是怎么调用的呢?而方法中name是如何传递的呢?这个时候我们就需要super关键字。

Cat cat = new cat("小花");

创建一个cat的对象,这个对象会进入Cat类中的构造方法,而Cat类中的构造方法会首先实例化父类的构造方法,因此super(name)就是调用父类构造方法,如果Cat类中没有构造方法,那么编译器会默认有一个不带参数的构造方法,而当前父类的构造方法是带参数的,所以子类的构造方法也需要带参。

public Cat(String name) {
    super(name);
}

1、super.name 指向父类成员属性
2、super(name) 指向父类构造方法
3、super.eat() 指向父类普通方法
super不能出现在静态方法中

注意:super和this的区别
1、super指向的是直接父类中的属性,方法,而this指向的是当前对象的引用。
2、super()和this()都放在构造方法中的第一行,super()调用的是父类的构造方法,this()调用的是本类的其他构造方法。
3、super和this都指的是对象,都不能在static中使用。
4、从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。

2.4 final关键字

曾经我们学习过 final 关键字, 修饰一个变量或者字段的时候, 表示常量 (不能修改)。final 关键字也能修饰类, 此时表示被修饰的类就不能被继承。

final public class Animal {
...
}
public class Bird extends Animal {
...
}
// 编译出错
Error:(3, 27) java: 无法从Animal进行继承

三、多态

3.1 向上转型

// Animal.java
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 extends Animal {
	public Cat(String name) {
		super(name);
	}
}
// Bird.java
class Bird extends Aniaml {
	public Bird(String name) {
		super(name);
	}
	public void fly() {
		System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿");
	}
}
public class Test {
	public static void main(String[] args]) {
		Animal brid = new Brid("小鸟");
	}
}

bird的引用是父类Animal,它指向子类Bird的一个对象,这种写法称为向上转型。

向上转型可以是

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

方法传参

public class Test {
	public static void func(Animal animal) {
		animal.eat("谷子");
	}
	public static void main(String[] args]) {
		Bird brid = new Brid("小鸟");
		func(brid);
	}
}

此时bird变量作为func方法的参数,而func方法的参数类型为Animal,而变量引用的是bird对象,所以当执行animal.eat("谷子");时,首先会看父类是否存在该方法,如果不存在则报错,如果存在该方法,则会看子类是否重写该方法,如果子类有该方法,则会执行Bird类中的eat()方法。这个过程就是动态绑定。
方法返回

public class Test {
	public static Animal func() {
		Bird bird = new Bird();
		return bird;
	}
	public static void main(String[] args]) {
		Animal brid = func();
	}
}

3.2 动态绑定

当子类和父类中出现相同的方法时

// Animal.java
class Animal {
	public String name;
	public Animal(String name) {
		this.name = name;
	}
	public void eat(String food) {
		System.out.println(this.name + "正在吃" + food);
	}
}
// Bird.java
class Bird extends Aniaml {
	public Bird(String name) {
		super(name);
	}
	public void eat(String food) {
		System.out.println(this.name + "正在吃" + food);
	}
	public void fly() {
		System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿");
	}
}
public class Test {
	public static void main(String[] args]) {
		Animal animal = new Animal("动物");
		animal.eat("草");
		Animal brid = new Brid("小鸟");
		bird.eat("谷子");
	}
}

运行结果:

动物正在吃草
小鸟正在吃谷子

可以看出animalbird都是Animal类的引用,但是animal指向了Animal类的实例,animal.eat()调用了父类的方法,bird指向的是Bird类的实例,bird.eat()调用了子类的方法。

因此, 在 Java 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定

3.3 理解多态

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、可扩展能力更强

四、抽象类

abstract class Shape {
	abstract public void draw();
}

在 draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体代码).
对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类

注意:
1、抽象类不能直接实例化
2、抽象方法不能是private的
3、抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写,也可以被子类直接调用
4、抽象类不一定有抽象方法,如果抽象类没有方法实现,最好使用接口,因为java不支持多类继承
5、java中抽象类的子类必须实现所有抽象方法,除非子类也是抽象类
6、Java Abstract 类可以实现接口,甚至不需要提供接口方法的实现。

抽象类存在的最大意义就是为了被继承.
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法。

五、接口

接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法和字段,而接口中包含的方法都是抽象方法, 字段只能包含静态常量。

interface IShape {
	void draw();
}
class Cycle implements IShape {
	@Override
	public void draw() {
		System.out.println("○");
	}
}
public class Test {
	public static void main(String[] args) {
		IShape shape = new Cycle();
		shape.draw();
	}
}
  • 使用 interface 定义一个接口
  • 默认情况下,接口的任何属性都是public、static和final,所以我们不需要为属性提供访问修饰符,实现它的子类的普通方法重写该方法时,需要public修饰
  • Cycle 使用 implements 继承接口. 此时表达的含义不再是 “扩展”, 而是 “实现”
  • 在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例.
  • 接口不能有构造方法,所以不能单独被实例化,接口不能有带有主体的方法。
  • 接口提供了多继承,一个接口可以扩展多个接口,但是不能扩展任何类。
  • 实现接口的类必须为所有方法提供实现,除非它是抽象类。

实现多接口

有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.
然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果

class Animal {
	protected String name;
	public Animal(String name) {
		this.name = name;
	}
}
interface IFlying {
	void fly();
}
interface IRunning {
	void run();
}
interface ISwimming {
	void swim();
}
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 swim() {
		System.out.println(this.name + "正在蹬腿游泳");
	}
}

上述代码我们有一个动物类和青蛙类,还要飞、跑、游泳的接口。青蛙既可以跳也可以游泳,所有它有两个接口。

抽象类和接口核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT自习小空间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值