目录点击跳转
包
包(
package
)组织类的一种方式!
使用包的目的:使类具有唯一性!
包是组织类的一种方式,我们java
中的多个类可以放到一个包中。
就好比学校里的班级就是一个包,我们学生就是一个类,相同班级的我们都放在同一个包下!
目的:使类具有唯一性!
就是说不同包下的类我们可以有同名的类,如果没有包的话,类名相同,类就无法区分,不唯一!就好比我们填写我们的信息不止填姓名,还会加上班级等进行限定!
无法在一个包下创建相同类名的类!
在不同的包下,我们可以创建相同类名的类,所以类保证了类的唯一性!我们可以看到我们的Test
类在demo_1
和demo
两个包下都有!
但是我们可以看到两个类的第一行都有声明在那个包下的类!
语法格式
package
包名
;
在demo_1
包下的Test
类
在demo_2
下的Test
类
包的命名方法
我们java
一般采用域名命名法!
例如我们可以用我们的邮箱域名命名我们的包
eg: 域名:bugguo.qq.com
那我的包名就可以以com.qq.buguo
命名!
我们可以看到.
将一个包分成了多层文件夹!
所以也可以说包就是一个文件夹!
命名规范
所有命名规则必须遵循以下规则:
1)、名称只能由字母、数字、下划线、$符号组成
2)、不能以数字开头
3)、名称不能使用JAVA中的关键字。
4)、坚决不允许出现中文及拼音命名。
导入包中的类
java
下提供了很多写好的类给我们使用,这也是java
便利快捷的编写java
程序!
import
关键字:
顾名思义:import有导入输入的意思
我们可以通过import
关键字导入系统中的类
我们有时可能不止使用java.util
下的一个类,但我们要使用多个类时!
-
分别导入
-
将相同包下的所有类一起导入
值得注意的是java
并不是像C语言
下的#include
一样,java
下的导入包中的类,并不会真正的将该包下的全部内容全部导入到代码中,而是该代码会使用到的类java
才会去真正意义导入!
所以不要有整体导入包就会使java
代码效率更低的顾虑! -
当一个类在不同的包下时
我们已经知道了,java
中的包目的就是:使类具有唯一性!
但我们使用java
类是两个包下都有该类时,我们就需要正确完整导入该类。
不能整体导入,否者会产生编译错误!
我们可以看到Date
类在java.util
包下和java.sql
包下都有,我们使用时应该导入完整类!
系统包的介绍
我们java
下已经写好了很多系统包,但我们要使用到某个功能时,只需要将该包下的类导入即可!
java.lang
:系统常用基础类(String、Object
),此包从JDK1.1
后自动导入。java.lang.reflect:java
反射编程包;java.net
:进行网络编程开发包。java.sql
:进行数据库开发的支持包。java.util
:是java提供的工具程序包。(集合类等) 非常重要java.io
:I/O
编程开发包。
可以看到我们就接触了java.util
工具包,最常用!
以后的系统包。我们会一一接触!
注意事项
import
关键字并不可以导入指定的某个包,
它的作用是导入包中的类,如果要将该包下所有的类导入,就使用.*
代替该包下的所有类!
继承
顾名思义java
继承就是和我们所说的继承应该是一样的,子女继承父母!
java
继承也是如此,java
类可以继承父类!~
//简单的一个继承代码!
//父类
class Animal{
public String name;
public String age;
}
class Dog extends Animal{
//extends继承了父类Animal
//Dog类就有了父类Animal的属性
//name和age
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog(); //创建一个Dog对象
dog.name = "bug郭"; //给继承的属性赋值
dog.age = "18";
System.out.println(dog.name+":"+dog.age);
}
}
运行结果
可以看到我们使用extends
关键字便实现了继承!
父类Anmial
的属性,子类dog
便有了该属性!
看完这个代码,我们好像对继承有了一定理解~
继承语法
class 子类名 extends 父类名 {
}
我们知道extends
英文意为:扩展,延伸,继承。
那继承有什么用呢?
我们先看下面代码:
class Birdie{
public String name;
public int age;
public void eat(){
System.out.println(name+"eat()!");
}
public void fly(){
System.out.println(name+"fly()!");
}
}
class Dog{
public String name;
public int age;
public void eat(){
System.out.println(name+"eat()!");
}
public void run(){
System.out.println(name+"run()!");
}
}
class Frog{
public String name;
public int age;
public void eat(){
System.out.println(name+"eat()!");
}
public void jump(){
System.out.println(name+"jump()!");
}
}
我们可以看到Birdie
,Dog
,Frog
类都有一些相同的属性和方法!如果我们利用extends
继承,就可以使代码更简洁!
我们可以将所有的类相同的属性抽离出来成一个Anmial
父类~所有的类都是Anmial
的子类,(is a)
的关系, 例如 Birdie is a Anmial
我们就可以采用关键字extends
继承关键字!
class Animal{
public String name;
public int age;
public void eat(){
System.out.println(name+"eat()!");
}
}
class Birdie extends Animal{
public void fly(){
System.out.println(name+"fly()!");
}
}
class Dog extends Animal{
public void run(){
System.out.println(name+"run()!");
}
}
class Frog extends Animal{
public void jump(){
System.out.println(name+"jump()!");
}
}
这就是继承简单的作用!继承实现了代码重写的效果!
基础知识
我们所继承的父类,在java
中又被称为超类
,基类
,父类
;
而子类又称为派生类,子类
- 子类要使用
extends
关键字指定父类; - 子类中通过
super
指代父类对象的引用; - 子类可以继承父类的所有
public
方法和属性; - 子类无法继承父类中的
private
方法和字段; java
只能单继承,只能继承一个父类,无法实现多继承;- 子类可以继承父类
final
和static
修饰的属性和方法;
父类中的private修饰的私有属性和方法无法被继承
class Anmail{
public String name;
public int age;
private String sex = "男";
private int height;
public void eat(){
System.out.println(name+":eat()!");
}
}
class Bired extends Anmail{
}
public class Test {
public static void main(String[] args) {
Bired bired = new Bired();
bired.sex;//无法继承父类中的私有属性和方法
}
}
final
修饰的属性无法被更改,可以被子类继承!!
class Anmail{
public String name;
public int age;
public final String sex = "男";
public static int height;
public void eat(){
System.out.println(name+":eat()!");
}
}
class Bired extends Anmail{
}
public class Test {
public static void main(String[] args) {
Anmail anmail = new Anmail();
anmail.sex = "女"; //error 无法改变final修饰的属性和方法
Bired bired = new Bired();
bired.sex = "女";//error 子类也无法改变父类中的final属性和方法
System.out.println(bired.sex); //子类可以继承final修饰的属性和方法
}
}
子类可以继承static
修饰的属性和方法
构造方法
当我们子类有构造方法时,要先帮助父类进行构造!
基础语法
我们利用
sumper
关键字帮助父类构造,调用合适的构造方法即可
class 父类{
public 父类(){
// 父类构造方法
}
}
class 子类 extends 父类{
public 子类(){
sumper(); //帮助父类构造
}
}
//父类只有不带参数的构造方法时
class Animal{
protected String name;
protected int age;
public void eat(){
System.out.println("Animal::eat()");
}
public Animal(){
}
}
class Bird extends Animal {
}
我们知道编译器会自动帮助我们编写不带参数的构造方法!
所以其实子类中也有构造方法
//当父类中有含有参数的构造方法时
class Animal{
protected String name;
protected int age;
public void eat(){
System.out.println("Animal::eat()");
}
public Animal(String name,int age){
this.name = name;
this.age = age;
}
}
class Bird extends Animal {
//子类也需要帮助父类构造含参数的构造方法
public Bird(String name, int age){
super(name,age);
}
}
//当父类有多个构造方法时
class Animal{
protected String name;
protected int age;
public void eat(){
System.out.println("Animal::eat()");
}
public Animal(String name,int age){
this.name = name;
this.age = age;
}
public Animal(){
}
}
class Bird extends Animal {
public Bird(String name, int age){
super(); //调用父类不带参数的构造方法
}
}
如果我们不帮父类构造编译器就会报错
_
protected
修饰符
- 通常我们的父类中的字段或者方法会用
protected
修饰,但也因情况而定 protected
修饰的字段和方法权限:相同包的类,不同包中的子类;
也就是说,protected
修饰,无法在不同包中的非子类访问。很好的包装了父类!
package demo;
public class Animal{
protected String name;
protected int age;
}
package demo_2;
import demo.Animal;
public class Bird extends Animal {
public void setName(String name){
super.name = name; //继承了demo包下Animal类中的protected方法,字段
}
public void setAge(int age){
super.age = age;
}
}
demo
包下的Animal
类
demo_2
包下的Bird
类
组合
我们知道继承是is a
的关系,就是什么是什么,bired is anmail
组合就是has a
的关系,什么有什么!
我们写代码也会使用到
例如一个学校类,我们需要老师和学生类,
而学校对象就有老师和学生对象!
class Student{
public String name = "bug郭";
public int age = 18;
}
class Teacher{
public String name ="CSDN";
public int age = 66;
}
class School{
//组合school has student and teacher.
Student student = new Student();
Teacher teacher = new Teacher();
public void show(){
System.out.println(student.name+":"+student.age);
System.out.println(teacher.name+":"+teacher.age);
}
}
public class Test_2 {
public static void main(String[] args) {
School school = new School();
school.show();
}
}
this
和super
关键字
this
父类对象的引用
this()
调用类中的构造方法
只能在构造方法中使用
并且只能在构造方法中的第一行使用
在构造方法中的第一行中使用
this.字段名
调用当前类中的字段
this.方法名()
调用当前对象的方法
super
super 表示获取到父类实例的引用. 涉及到两种常见用法。
-
super()
调用父类的构造方法
-
super.字段名
调用父类的字段和方法
重载和重写
重载
方法重载就是,在一个类中方法名相同,参数列表不同,返回值无关
class Sum{
public int add(int a,int b){
return a+b;
}
public int add(int a,int b,int c){
return a+b;
}
public double add(double a, double b){
return a+b;
}
}
上面add
方法构成方法重载!
重写
啥是重写呢?
重写就是子类中有父类同名的方法就构成了重写
package demo;
public class Animal{
protected String name;
protected int age;
public void eat(){
System.out.println("Animal::eat()");
}
}
class Bird extends Animal{
//和父类相同的方法
public void eat(){
System.out.println("Bird::eat()");
}
}
重写注意事项
- 方法的返回值和参数列表必须和父类相同才能构成重写
方法的返回值不同,重写失败
方法的参数不同,重写失败
方法名,返回值,参数相同,构成重写
- 重写的方法的修饰符的权限要高于或等于父类中的修饰符权限
- 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外)
协变类型
就是子类重写的方法可以返回父类方法返回值的子类!!!
可以看到重写方法的返回值是父子类关系时,也能构成重写!!!
重写和重载的区别
-
方法重载指的是在一个类中同名的方法
条件:方法名相同,参数列表不同,返回值一般不做要求! -
方法重写指的是子类中的方法与父类中的方法同名,且方法的参数列表和返回值都要与父类相同!且子类重写方法的访问修饰限定符的权限要求大于或等于父类中的权限!
条件:继承关系,方法名,参数列表,返回值相同,权限高于或等于父类。 -
重载对方法的权限不做要求!
访问修饰限定符
java
中的字段和方法的四种访问权限
-
public
可以在不同包中的类访问! -
protected
不同包中继承关系访问! -
默认包访问权限,只能在同一包中的类中访问!
-
privated
只能在同一个类中访问
NO | 范围 | public | protected | default | privated |
---|---|---|---|---|---|
1 | 不同包中的非子类 | ✔️ | |||
2 | 不同包中的子类 | ✔️ | ✔️ | ||
3 | 同一包中的不同类 | ✔️ | ✔️ | ✔️ | |
4 | 同一包中的同一类 | ✔️ | ✔️ | ✔️ | ✔️ |
多态
在编程语言和类型论中,多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口。 多态类型(英语:polymorphic type)可以将自身所支持的操作套用到其它类型的值上。多态(百度词条)
向上转型
子类对象赋值给了父类引用
该对象只能访问父类的字段和方法!
直接赋值
子类对象赋值给了父类引用
class Animal{
protected String name;
protected int age;
public void eat(){
System.out.println("animal eat()!");
}
}
class Dog extends Animal{
protected int height;
public void running(){
System.out.println("dog running()!");
}
}
public class Test_1 {
public static void main(String[] args) {
Animal animal = new Animal();
animal = new Dog(); //1.子类对象赋值给了父类引用
Animal animal1 = new Dog(); //2.和 1 等价
animal.eat(); //调用父类中的方法
//error animal只能访问父类中的字段和方法!
animal.height=1;
animal.running();
}
}
方法传参
//方法传参
class Animal{
protected String name;
protected int age;
public void eat(){
System.out.println("animal eat()!");
}
}
class Dog extends Animal{
protected int height;
public void running(){
System.out.println("dog running()!");
}
}
public class Test_2 {
public static void func(Animal animal){
animal.eat();
}
public static void main(String[] args) {
Dog dog = new Dog();
func(dog);
func(new Dog()); //子类对象赋值给了父类引用!
}
}
方法返回
//方法返回
class Animal{
protected String name;
protected int age;
public void eat(){
System.out.println("animal eat()!");
}
}
class Dog extends Animal{
protected int height;
public void running(){
System.out.println("dog running()!");
}
}
public class Test_2 {
public static Animal func(){
return new Dog(); //子类对对象返回给了父类引用!
}
public static void main(String[] args) {
Animal animal = func();
animal.eat();
}
}
动态绑定
动态绑定就是,当子类重写了父类的方法时,向上转型后,对象调用与重写的方法,访问的是子类对象中的重写方法!
//运行时绑定
class Animal{
protected String name;
protected int age;
public void eat(){
System.out.println("animal eat()!");
}
}
class Dog extends Animal{
protected int height;
@Override
public void eat() {
System.out.println("dog eat()!");
}
public void running(){
System.out.println("dog running()!");
}
}
public class Test_2 {
public static void main(String[] args) {
Animal animal = new Dog();
animal.eat(); //运行时绑定!
}
}
为啥要叫运行时绑定呢?
难道说编译的时候没有绑定?
确实如此,当我们查看java
的反汇编代码时就会发现,编译期间anmial
调用的是自己的eat
方法,但是运行时却绑定了子类的eat()
!
java
反汇编代码步骤:
- 找到,我们需要反汇编代码类的字节码文件
- 在命令符的窗口下,输入
javap -c 类名
代码 回车即可!
理解多态
我们想一想多态可以帮助我们做些什么!
bug郭的理解
- 运行时绑定使用场景
我们可以重写父类的方法,实现多态。调用重写的方法,一个父类可以有多个子类,不同的子类重写了不同的方法,实现了真正意义上的多态!
eg:打印图形
//类的实现者
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("♣");
}
}
//类的调用者
public class Test_1{
public static void main(String[] args) {
Shape shape1 = new Flower();
Shape shape2 = new Cycle();
Shape shape3 = new Rect();
drawShape(shape1);
drawShape(shape2);
drawShape(shape3);
}
// 打印单个图形
public static void drawShape(Shape shape) {
shape.draw();
}
}
使用多态的好处是什么?
- 类调用者对类的使用成本进一步降低。
封装是让类的调用者不需要知道类的实现细节。
多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可。
因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低。
- 能够降低代码的 “圈复杂度”, 避免使用大量的
if - else
。
“圈复杂度” :就是代码中的分支和循环; - 可扩展能力更强。
如果要新增一种新的形状,使用多态的方式代码改动成本也比较低。
向下转型
我们知道向上转型是子类对象赋值给了父类引用!
那向下转型莫不就是:父类对象赋值给了子类引用~
并不常见~了解一下即可!
//向下转型
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println("我是一只小动物");
System.out.println(this.name + "正在吃" + food);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void eat(String food) {
System.out.println("我是一只小鸟");
System.out.println(this.name + "正在吃" + food);
}
public void fly() {
System.out.println(this.name + "正在飞");
}
}
public class Test_2 {
public static void main(String[] args) {
Animal animal = new Bird("鸽鸽"); //先借助向上转型
animal.eat("脐橙");
//animal.fly; //error
Bird bird;
bird = (Bird)animal; //向下转型需要强转
bird.fly();
}
}
可以看到向下转型步骤比较繁琐,通常要借助向上转型!
而且我们需要确定是否为父子类关系!避免异常!
利用
instanceof
关键字可以判定一个引用是否是某个类的实例。
若真返回true
,若假返回false
!
构造方法中调用一个重写的方法(一个坑!)
//坑
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_3{
public static void main(String[] args) {
D d = new D();
}
}
bug郭
看了半天愣是没整明白为啥这个代码运行结果是这样!!!
我的理解:创建子类对象d会调用自己的构造方法,子类要先帮助父类构造,而父类中调用了子类重写的方法,动态绑定了;
我们并有执行子类中的 private int num = 1;
语句!所以num
此时并没有赋值!所以为0
!
正解
- 构造
D
对象的同时, 会调用B
的构造方法. B
的构造方法中调用了func
方法, 此时会触发动态绑定, 会调用到D
中的func
- 此时
D
对象自身还没有构造, 此时num
处在未初始化的状态, 值为0
.
结论: “用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏但又及难发现的问题!
抽象类
基本语法
在刚才的打印图形例子中, 我们发现, 父类Shape
中的 draw
方法好像并没有什么实际工作, 主要的绘制图形都是由Shape
的各种子类的 draw
方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstractmethod)
, 包含抽象方法的类我们称为 抽象类(abstract class)
。
abstract class Shape {
abstract public void draw();
}
在 draw
方法前加上abstract
关键字, 表示这是一个抽象方法。同时抽象方法没有方法体(没有{ }
, 不能执行具体代码)。
对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类。
注意事项
- 抽象类不能直接实例化。
- 抽象类可以有一般类一样的字段和方法,语法相同。
abstract class Shape {
protected int longth;
protected int wide;
public int area(){
return longth*wide;
}
abstract public void draw();
}
-
子类继承父类抽象类,必须重写父类中的抽象方法!
-
抽象方法不能是
private
的
我们知道private
修饰只能在当前类中访问,而抽象方法需要子类实现!
抽象类的作用
抽象类存在的最大意义就是为了被继承。
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类。然后让子类重写抽象类中的抽象方法。
有些同学可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?
确实如此. 但是使用抽象类相当于多了一重编译器的校验。
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,
使用普通类编译器是不会报错的。但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题!
接口
基本语法
//定义接口类型
interface IAnimal{
//抽象方法
// public static final 字段
}
-
使用
interface
定义一个接口
-
接口中的方法一定是抽象方法, 因此可以省略
abstract
接口中的方法一定是public
, 因此可以省略public
-
接口中只能包含抽象方法,对于字段来说, 接口中只能包含静态常量
(final static)
interface IAnimal{
// public static final 字段 可以省略public static final
String name = "animal"; //因为是final 修饰,需要赋初值!
int age = 18;
//抽象方法
public abstract void speak();
void eat(); //省略public abstract
}
-
子类 使用
implements
继承接口。 此时表达的含义不再是 “扩展”, 而是 “实现” -
在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例。
-
接口不能单独被实例化。
-
扩展
(extends)
vs 实现(implements)
扩展指的是当前已经有一定的功能了, 进一步扩充功能。
实现指的是当前啥都没有, 需要从头构造出来。
- 接口不能用限定符修饰
实现多个接口
接口弥补了java
无法多继承的缺陷!
我们可以实现多个接口
基本语法
有时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的。然而 Java 中只支持单继承, 一个类只
extends
一个父类。 但是可以同时实现多个接口, 也能达到多继承类似的效果。
class 类名 implement interfa1,interface2,....interfaceN{
//实现所有接口中的方法!
}
实例一
下面我们通过类来表示张三
//实现多个接口
interface IAnimal{
String name = "animal";
int age = 18;
void speak();
void eat();
}
interface IPeople{
String iQ = "140";
void study();
}
class Zhansan implements IAnimal,IPeople{
@Override
public void speak() {
System.out.println("Speak Chinese!");
}
@Override
public void eat() {
System.out.println("Eat food!");
}
@Override
public void study() {
System.out.println("Study java!");
}
}
public class Test_6 {
public static void main(String[] args) {
Zhansan zhansan = new Zhansan();
zhansan.eat();
zhansan.speak();
zhansan.study();
}
}
实例二
现在我们通过一个类来表示一组动物。
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 Cat extends Animal implements IRunning {
public Cat(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在用四条腿跑");
}
}
鱼, 是会游的。
class Fish extends Animal implements ISwimming {
public Fish(String name) {
super(name);
}
@Override
public void swim() {
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 swim() {
System.out.println(this.name + "正在蹬腿游泳");
}
}
提示,
IDEA
中使用ctrl + i
快速实现接口
还有一种神奇的动物, 水陆空三栖, 叫做 “鸭子”
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 swim() {
System.out.println(this.name + "正在漂在水上");
}
}
上面的代码展示了 Java
面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口。
继承表达的含义是
is - a
语义, 而接口表达的含义是 具有xxx
特性 。
猫是一种动物, 具有会跑的特性。
青蛙也是一种动物, 既能跑, 也能游泳
鸭子也是一种动物, 既能跑, 也能游, 还能飞
这样设计有什么好处呢?
时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力!
例如, 现在实现一个方法, 叫 “散步”
public static void walk(IRunning running) {
System.out.println("我带着伙伴去散步");
running.run();
}
在这个walk
方法内部, 我们并不关注到底是哪种动物, 只要参数是会跑的, 就行
Cat cat = new Cat("小猫");
walk(cat);
Frog frog = new Frog("小青蛙");
walk(frog);
甚至参数可以不是 “动物”, 只要会跑
class Robot implements IRunning {
private String name;
public Robot(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(this.name + "正在用轮子跑");
}
}
Robot robot = new Robot("机器人");
walk(robot);
接口的使用实例
我们java
系统包中的很多类都实现了很多接口,使得该类具有某种属性
给对象排序!
//创建student类
class Student{
private String name;
private double score;
public Student(String name,double score){
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
public static void main(String[] args) {
//学生对象数组
Student[] students = new Student[]{
new Student("张三",88.7),
new Student("李四",67),
new Student("王五",98),
};
Arrays.sort(students); //排序
System.out.println(students);
}
显然,我们无法排序对象类型的数据,
报异常,说我们Student
类没有实现Comparable
接口!
Comparable
接口中的抽象方法 compareTo
方法详细信息
int compareTo(T o)
将此对象与指定的对象进行比较以进行排序。 返回一个负整数,零或正整数,因为该对象小于,等于或大于指定对象。
实现程序必须确保sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
所有x
和y
。 (这意味着x.compareTo(y)
必须抛出异常if y.compareTo(x)
引发异常。)
实施者还必须确保关系是可传递的: (x.compareTo(y)>0 && y.compareTo(z)>0)表示x.compareTo(z)>0
。
最后,实施者必须确保x.compareTo(y)==0意味着sgn(x.compareTo(z)) == sgn(y.compareTo(z))
,对于所有z
。
强烈建议,但不要严格要求(x.compareTo(y)==0) == (x.equals(y))
。 一般来说,任何实现Comparable
接口并违反这种情况的类应清楚地表明这一点。 推荐的语言是“注意:此类具有与equals
不一致的自然排序”。
在前面的描述中,符号sgn( )
表达式表示数学符号函数,其定义根据表达式的值是否为负,零或正返回的-1
一个,0
,或1
。
参数
o -
要比较的对象。
结果
负整数,零或正整数,因为该对象小于,等于或大于指定对象。
异常
NullPointerException
- 如果指定的对象为空
ClassCastException
- 如果指定的对象的类型阻止它与该对象进行比较。
看到这么多文字,是不是头都大了!
没有关系,bug郭
也头大,不过我知道咋用
//创建student类并且实现Comparable接口
class Student implements Comparable{
private String name;
private double score;
public Student(String name,double score){
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
@Override //实现compareTo方法
public int compareTo(Object o) {
Student s = (Student)o; //对o强转, o指的是待比较对象
if (this.score > s.score) { //当前对象的score值大
return -1;
} else if (this.score< s.score) { //对象o的score值大
return 1;
} else {
return 0;
}
}
}
我们可以看到,这时排的升序,如果要降序就只需将大于号变成小于号即可!
在 sort
方法中会自动调用compareTo
方法. compareTo
的参数是 Object
, 其实传入的就是 Student
类型的对象.
然后比较当前对象和参数对象的大小关系(按分数来算).
如果当前对象应排在参数对象之前, 返回小于0 的数字;
如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回0;
注意事项: 对于 sort
方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备compareTo
这样的能力. 通过重写compareTo
方法的方式, 就可以定义比较规则。
为了进一步加深对接口的理解, 我们可以尝试自己实现一个sort
方法来完成刚才的排序过程(使用冒泡排序)!
public static void sort(Comparable[] array) {
for (int bound = 0; bound < array.length; bound++) {
for (int cur = array.length - 1; cur > bound; cur--) {
if (array[cur - 1].compareTo(array[cur]) > 0) {
// 说明顺序不符合要求, 交换两个变量的位置
Comparable tmp = array[cur - 1];
array[cur - 1] = array[cur];
array[cur] = tmp;
}
}
}
}
接口间的继承
接口可以继承一个接口, 达到复用的效果. 使用
extends
关键字!
interface IAmphibious extends IRunning, ISwimming {
}
class Frog implements IAmphibious {
@Override
public void run() {
System.out.println("Frog run!");
}
@Override
public void swim() {
System.out.println("Frog swim!");
}
}
public class Test_1 {
public static void main(String[] args) {
Frog frog = new Frog();
frog.run();
frog.swim();
}
}
通过接口继承创建一个新的接口
IAmphibious
表示 “两栖的”. 此时实现接口创建的Frog
类, 就继续要实现run
方法,也需要实现swim
方法!
接口间的继承相当于将多个接口合并在一起!