面向对象,总结

面向对象

我们今天按照下面这个思维导图来仔细剖析面向对象的概念

在这里插入图片描述

基本概念

Java 类的定义

package com.bjsxt.Test01;

//用class 关键字定义一个类,例如:
public class Person {
    private int id;
    private int age = 20;

    //        方法定义
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getId() {
        return id;
    }
}

类的定义主要有两方面组成:成员变量和方法;
声明成员变量的格式为:
** 【modifiers】type<attr_name>【=default Value】;**
也就是 修饰符 类型 类名 默认值;
例如: private int id ; private int age = 20;
声明方法的格式为:
【modifiers】<return_type>(【<argu_list>】){
【statements】
}
也就是 修饰符 返回值类型 方法名 ( 形参){
方法体
}

例如:public int getAge(){
return age;
}

成员变量

在这里插入图片描述
在JAVA里面的任何变量首先应该要声明,然后再赋值,然后再使用。成员变量和局部变量有一个重要区别:成员变量在类里面声明时如果不进行初始化,那么JAVA会默认给它初始化,而局部变量JAVA不会默认给它初始化,所以在方法里面声明一个局部变量如果不给它初始化时就会出错。默认初始化大多数都是0,boolean类型的为false,引用类型的为null,如过不记得JAVA对成员变量默认的初始化是多少的话,那就这样做,定义一个成员变量,不给它初始化,然后直接打印这个成员变量,打印出来的结果就是JAVA默认的初始化的值。

Java面向对象的基本概念-- 引用

在这里插入图片描述

引用类型和基本类型有着巨大的区别,当声明一个int i=0时,系统会马上给这个i分配一个内存空间(在栈内存里面分配一小块区域用来装数字0),里面装着一个值为0,以后使用i这个名字马上就可以访问这个内存空间里面的值,这就是基本数据类型,所以基础类型就只占一块内存。基础类型之外的类型全都叫引用类型,我们定义一个Mouse m,这个m就是一个引用类型的数据。引用类型有什么重要的特征——引用类型占2块内存。我们定义好这个类之后,需要使用new关键字把这个类的对象实例化出来,也就是真真正正造出一个对象出来才能使用这个对象。

如何在内存中区分类和对象

类是静态的概念,是位于代码区里面。对象是new出来的,它是位于堆内存,为什么对象要位于堆内存?因为堆内存是用来动态分配内存的,只有在运行当中才会new一个对象放堆内存里面,那这个对象到底有多大个,这个东西你不知道,你没有办法提前知道,所以你没有办法提前分配内存给这个对象,你只有在运行期间才能去分配它。什么叫运行期间?敲JAVAC这个命令那是在编译期间,编译完成后再敲JAVA命令,那就是运行期间了。只有在运行期间,才能够明白这个对象到底要分配多大的空间给它,所以把它放在堆内存里面,堆内存比较大,动态分配内存用它。如果这个对象不用了,那它就是垃圾,那么就等着垃圾收集器把它收集回去,释放掉占用的内存。

记住,以后一提到引用,脑子里马上浮现引用那就是一小块内存指向一大块内存。

对象的创建和使用

对象的从创建和使用:
必须使用new关键字创建对象;
适用对象(引用),成员变量或来引用对象的成员变量;
适用对象(引用),方法(参数列表)来调用对象的方法;
同一个类的每个对象有不同的成员变量存储空间;
同一个类的每个对象共享该类的方法。

类和对象的关系

在这里插入图片描述
在内存中分析类和对象的关系

假设这里有一个类C,我们定义了一个类class C,然后在这个类里面定义了两个成员变量: int i和int j。定义好了这两个成员变量以后,我们写了一个main()方法(public static void main(Strng[] args)),程序开始执行。第一句我们写了 C c1 = new C(),这句的代码是我们相当于在堆内存里创建了一个对象,同时也创建了这个对象的一个引用对象c1,c1位于栈内存中,c1这个引用对象指向堆中一大块内存,这一大块内存里面装着new出来的那个对象。这里面我们一般来说是new出来两个对象c1和c2,当然,实际上,严格来讲,c1和c2叫做对象的引用,有时候,简称new出来了两个对象,c1和c2。你脑子里马上要浮现出两块内存,c1指向一块,c2指向一块。局部变量是分配在栈内存里面的,main方法里面的c1和c2都是局部变量,所以在栈里面分配了两小块内存出来,一块是c1的,一块是c2的,c1这块内存里面装着一个值,或者叫装着一个地址,这个地址是什么,我们不知道,我们只知道根据这个值就能找到new出来的C这个类里面的一个对象,而在这个对象里面有它自己的成员变量i和j,里面的两小块内存是分别用来装i和j的值的,因为每一个对象都有自己不同的成员变量的值,所以c1指向的那块对内存里面又分成一小块一小块内存,每一个小块的内存都装着这个对象的成员变量(或者叫属性)。如这里的第一小块装着i的值,第二小块装着j的值,所以当我们去访问第一小块里面装着的成员变量时,我们应该这样写:c1.i,这样就拿到了i的值,c1.j,这样就拿到了j的值。同理,c2这个对象也指向了一个new出来的C这个类里面的另一个对象,这个对象也有成员变量i和j,只不过和c1指向的那个对象里的i和j的值不同而已。要访问这个这个对象的成员变量时 ,也是要c2.i,c2.j这样去访问。

构造方法在这里插入图片描述

在面向对象里面有一个特殊的方法,叫构造方法。构造方法是用来创建一个新的对象的,与new组合在一起用,使用new+构造方法创建一个新的对象。你new一个东西的时候,实际上你调用的是一个构造方法,构造方法就是把自己构造成一个新的对象,所以叫构造方法,构造一个新对象用的方法叫构造方法。

构造方法比较特殊,构造方法的名字必须和类的名字完全一模一样,包括大小写,并且没有返回值。如原来定义的一个person类,在类里面声明了两个成员变量id与age,这时候你可以再为这个person类定义一个它的构造方法person(int n,int i),这个方法的名字和类名完全相同,并且没有返回值,也就是在这个方法前面不能写任何的方法的返回类型修饰符,连void都不可以写。

类和类之间的关系

类和类之间的关系我们分为三种:
Is- a ; has- a ;ues -a ;
分别代表了继承,关联,与依赖,因为我们目前只学了前两种,我们这里只分析一下前两种,后面的有机会补上~

继承 (is-a)

继承是面向对象编程的三大特征之一,它让我们更加容易实现对于已有类的扩展、更加容易实现对于现实世界的建模。
继承有两个主要作用:
1.代码复用,更加容易实现类的扩展
2.方便对事务建模

这里我们拿两个代码举个例子:

class Person {
    private String name;
    private int age;
    public String getName()
    {
        return name;
    }
    public void setName(String name)
    {
        this.name=name;
    }
    public int getAge()
    {
        return age;
    }
    public void setAge(int age)
    {
        this.age=age;
    }
}
class Student {
    private String name;
    private int age;
    private String schoolName;
    public String getName()
    {
        return name;
    }
    public void setName(String name)
    {
        this.name=name;
    }
    public int getAge()
    {
        return age;
    }
    public void setAge(int age)
    {
        this.age=age;
    }
    public String getSchoolName()
    {
        return schoolName;
    }
    public void setSchoolName(String schoolName)
    {
        this.schoolName=schoolName;
    }
}


上是我们定义类的方式,含有大量重复的代码,java语言一般不允许大量重复的代码出现,这段代码不仅从代码上重复,而且从概念上讲学生一定是人,只是学生类描述的范围小,具备更多的属性和方法,这个时候想要消除结构定义上的重复,就要用到继承。

在Java中,继承用extends关键字来实现,语法:class 子类 extends 父类{}
所以可将上述代码改为:

class Person {
    private String name;
    private int age;
    public String getName()
    {
        return name;
    }
    public void setName(String name)
    {
        this.name=name;
    }
    public int getAge()
    {
        return age;
    }
    public void setAge(int age)
    {
        this.age=age;
    }
}
class Student extends Person{ 
}
public class Test2{
    public static void main(String[] args)
    {
        Student student=new Student();
        student.setName("花花");
        student.setAge(18);
        System.out.println("姓名:"+student.getName()+"  年龄:"+student.getAge());
    }
}

通过以上代码可以发现,当发生了类继承关系之后,子类可以直接继承父类的操作,可以实现代码的重用,子类最低也维持和父类相同的功能。
  子类也可以进行功能上的扩充。
我们在使用继承的时候也要注意以下几点内容:
子类对象在进行实例化前首先调用父类构造方法,再调用子类构造方法实例化子类对象。
实际在子类构造方法中,相当于隐含了一个语句super(),调用父类的无参构造。同时如果父类里没有提供无参构造,那么这个时候就必须使用super(参数)明确指明要调用的父类构造方法。
Java只允许单继承不允许多继承(一个子类继承一个父类)

在继承时,子类会继承父类的所有结构。
在进行继承的时候,子类会继承父类的所有结构(包括私有属性、构造方法、普通方法)
显示继承:所有非私有操作属于显示继承(可以直接调用)。
隐式继承:所有私有操作属于隐式继承(不可以直接调用,需要通过其它形式调用(get或者set))

关联(has-a)

has_a 是组合 一个类中包含了其他类

一般我们认为继承可以分为两种基本的形式:实现继承和接口继承。

实现继承的主要目标是代码重用:

我们发现类B和类C存在同样的代码,因此我们设计了一个类A,用于存放通用的代码。
基于这种思路的继承称为实现继承。
接口继承不同,它是基于现实生活中的语义的,表现了IsA的关系:
我们认为存款帐户和结算帐户都是帐户的子类,这种继承我们称之为接口继承。
注意,有些文章中一个类实现一个接口的行为定义为接口继承,这和这里的接口继承是不同的概念,为了区分两种概念,我们可以使用接口继承的另一种称呼-类型继承。
继承的关键就在于如何灵活的运用两种继承方式。
这里的接口的意思和Java或者C#中的interface有着不同。
在此的接口表示的是一个类中所有公有的方法的签名。
所以IsA关系是关于行为的。

HasA关系,也称为对象组合。实际上,HasA关系有两种。
第一种,静态HasA关系。这在UML中叫做组合(composition),比如“汽车has-a引擎”。
此种关系很像聚集,除了‘部分’的生命周期由‘整体’控制。
这种控制可能是直接的或者传递的。即‘整体’可能对创建或者销毁‘部分’具有直接的职责,或者它可能获得已经创建的部分,稍后传递给其它的假定对其有职责的整体。
第二种,动态HasA关系。这在UML中叫做聚合(aggregation),比如“飞机场has-a飞机”。

对象

我们经常讨论说面向对象,那么对象归根结底究竟是什么呢?我们来探讨一下
假设我们要做一个学生管理系统,那肯定要有学生的信息,而且系统里面有很多学生信息,但是这些学生有个共同点,都有自己的学号、姓名、年龄等等信息。那么这些学生就可以归为一个大类,叫学生类。而学号、姓名、年龄是这个学生类的属性。这个类中每一个具体的学生就是对象。

java中定义一个学生类:

class Student{
 String id;
 String name;
 int age;
 }

java中产生一个学生对象(用类创建对象即实例化):new Student()

Student s=new Student();将产生的这个对象指向对象引用s,s保存的是new Student()在内存中的地址值(这里具体是堆内存,有兴趣的同学可以深入学习),通过这个地址值就可以在内存中找到这个对象。
new Student()中的“Student()”是这个类的构造函数,构造函数的命名必须和类名完全相同,利用构造函数可以实例化对象。构造函数分为无参构造和有参构造。

//无参
public Student()[
	}
//有参
public Student (String id,String name,int age ){
	this.id=id;
	this.name=name;
	this.age=age;
	}

利用无参构造创建对象Student s1=new Student();同时会给属性附初始值id=null;name=null;age=0;
利用有参构造创建对象Student s2=new Student(“001”,“张三”,21);此时会给属性附初始值id=“001”,name=“张三”,age=21。

public static void main(String[] args){
 Student s2=new Student();
 Student s1=new Student(001,张三,21);
 System.out.println(s1);
 Systen.out.println(s2);
 }

在这里插入图片描述
运行结果为:上图

记住以下四点:
一切皆为对象;
对象都有属性和行为;
对象都是唯一的;
对象都属于某个类;

还有一个概念就是消息:
调用对象的方法就是给对象发送了一个消息;
一个对象能够接受某种消息,就意味着该对象向外界提供了某种服务;

封装

封装的作用与含义

我要看电视,只需要按一下开关和换台就可以了。有必要了解电视机内部的结构吗?有必要碰碰显像管吗?制造厂家为了方便我们使用电视,把复杂的内部细节全部封装起来,只给我们暴露简单的接口,比如:电源开关。具体内部是怎么实现的,我们不需要操心。
需要让用户知道的才暴露出来,不需要让用户知道的全部隐藏起来,这就是封装。说的专业一点,封装就是把对象的属性和操作结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。
我们程序设计要追求“高内聚,低耦合”。 高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合是仅暴露少量的方法给外部使用,尽量方便外部调用。

那么封装有什么优点呢:
提高代码的安全性。
提高代码的复用性。
“高内聚”:封装细节,便于修改内部代码,提高可维护性。
“低耦合”:简化外部调用,便于调用者使用,便于扩展和协作。

这里我们举课上的一个例子:

class Person {
	String name;
	int age;
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
}
public class Test {
	public static void main(String[ ] args) {
		Person p = new Person();
		p.name = "小红";
		p.age = -45;//年龄可以通过这种方式随意赋值,没有任何限制
		System.out.println(p);
	}
}

我们都知道,年龄不可能是负数,也不可能超过130岁,但是如果没有使用封装的话,便可以给年龄赋值成任意的整数,这显然不符合我们的正常逻辑思维。执行结果如图5-7所示:
在这里插入图片描述
再比如说,如果哪天我们需要将Person类中的age属性修改为String类型的,你会怎么办?你只有一处使用了这个类的话那还比较幸运,但如果你有几十处甚至上百处都用到了,那你岂不是要改到崩溃。而封装恰恰能解决这样的问题。如果使用封装,我们只需要稍微修改下Person类的setAge()方法即可,而无需修改使用了该类的客户代码。

封装的实现

如何进行封装:
(1)私有化成员变量,使用private关键字修饰;

(2)提供公有的get和set方法,在方法体中进行合理值的判断,使用public关键字修饰;

(3)在构造方法中使用set方法进行合理值的判断;
举个例子:

/*
   编程实现Person类的封装
 */
public class Person{
  //1.私有化成员变量,使用private关键字修饰
  private String name; 
  private int age;
  private String country;
  //使用static关键字修饰成员变量表示提升为类层级只有一份被所有对象共享
  //public static String country;
  
  //3.在构造方法中调用set方法进行合理值的判断
  public Person(){
  }
  public Person(String name, int age, String country){
    setName(name);
    setAge(age);
    setCountry(country);
  }
  
  //2.提供公有的get和set方法,在方法体中进行合理值的判断
  public String getName(){
    return name;
  }
  public void setName(String name){
    this.name = name;
  }
  public int getAge(){
    return age;
  }
  public void setAge(int age){
    if(age > 0 && age < 150){
      this.age = age;
    }else{
      System.out.println("年龄不合理!!!");
    }
  }
  public String getCountry(){
    return country;
  }
  public void setCountry(String country){
    this.country = country;
  }
  
  public void show(){
    System.out.println("我是" + getName() + ",今年" + getAge() + "岁了,来自" + getCountry() + "!");
  }
  
  //自定义成员方法描述吃饭的行为
  public void eat(String food){
    System.out.println(food + "真好吃!");
  }
  //自定义成员方法描述娱乐的行为
  public void play(String game){
    System.out.println(game + "真好玩!");
  }
}

在这里我们附上访问控制符的表格;方面我们深入理解:

Java是使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露的。 Java中4种“访问控制符”分别为private、default、protected、public,它们说明了面向对象的封装性,所以我们要利用它们尽可能的让访问权限降到最低,从而提高安全性。
下面详细讲述它们的访问权限问题。其访问权限范围如表5-1所示。

访问权限修饰符
修饰符 同一个类 同一个包中 子类 所有类

1.private 表示私有,只有自己类能访问
2.default表示没有修饰符修饰,只有同一个包的类能访问
3.protected表示可以被同一个包的类以及其他包中的子类访问
4.public表示可以被该项目的所有包中的所有类访问

在这里插入图片描述

多态

多态的定义

多态指的是同一个方法调用,由于对象不同可能会有不同的行为。现实生活中,同一个方法,具体实现会完全不同。 比如:同样是调用人的“休息”方法,张三是睡觉,李四是旅游,高淇老师是敲代码,数学教授是做数学题; 同样是调用人“吃饭”的方法,中国人用筷子吃饭,英国人用刀叉吃饭,印度人用手吃饭。
多态的要点:

  1. 多态是方法的多态,不是属性的多态(多态与属性无关)。
  2. 多态的存在要有3个必要条件:继承,方法重写,父类引用指向子类对象。
  3. 父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。

同样的引用调用同样的方法却做了不同的事情;
当A系统访问B系统提供的服务时,B系统有多种提供服务的方式,但对A系统来说是透明的;

class Animal {
	public void shout() {
		System.out.println("叫了一声!");
	}
}
class Dog extends Animal {
	public void shout() {
		System.out.println("旺旺旺!");
	}
	public void seeDoor() {
		System.out.println("看门中....");
	}
}
class Cat extends Animal {
	public void shout() {
		System.out.println("喵喵喵喵!");
	}
}
public class TestPolym {
	public static void main(String[ ] args) {
		Animal a1 = new Cat(); // 向上可以自动转型
		//传的具体是哪一个类就调用哪一个类的方法。大大提高了程序的可扩展性。
		animalCry(a1);
		Animal a2 = new Dog();
		animalCry(a2);//a2为编译类型,Dog对象才是运行时类型。
		
		/*编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换。
		 * 否则通不过编译器的检查。*/
		Dog dog = (Dog)a2;//向下需要强制类型转换
		dog.seeDoor();
	}

	// 有了多态,只需要让增加的这个类继承Animal类就可以了。
	static void animalCry(Animal a) {
		a.shout();
	}

	/* 如果没有多态,我们这里需要写很多重载的方法。
	 * 每增加一种动物,就需要重载一种动物的喊叫方法。非常麻烦。
	static void animalCry(Dog d) {
		d.shout();
	}
	static void animalCry(Cat c) {
		c.shout();
	}*/
}

给大家展示了多态最为多见的一种用法,即父类引用做方法的形参,实参可以是任意的子类对象,可以通过不同的子类对象实现不同的行为方式。
由此,我们可以看出多态的主要优势是提高了代码的可扩展性,符合开闭原则。但是多态也有弊端,就是无法调用子类特有的功能,比如,我不能使用父类的引用变量调用Dog类特有的seeDoor()方法。

多态的实现

方法的重写:

子类通过重写父类的方法,可以用自身的行为替换父类的行为。方法的重写是实现多态的必要条件。
方法的重写需要符合下面的三个要点:
1.“= =”: 方法名、形参列表相同。
2.“≤”:返回值类型和声明异常类型,子类小于等于父类。
3.“≥”: 访问权限,子类大于等于父类。

public class TestOverride {
	public static void main(String[ ] args) {
		Vehicle v1 = new Vehicle();
		Vehicle v2 = new Horse();
		Vehicle v3 = new Plane();
		v1.run();
		v2.run();
		v3.run();
		v2.stop();
		v3.stop();
	}
}

class Vehicle { // 交通工具类
	public void run() {
		System.out.println("跑....");
	}
	public void stop() {
		System.out.println("停止不动");
	}
}
class Horse extends Vehicle { // 马也是交通工具
	public void run() { // 重写父类方法
		System.out.println("四蹄翻飞,嘚嘚嘚...");
	}
}

class Plane extends Vehicle {
	public void run() { // 重写父类方法
		System.out.println("天上飞!");
	}
	public void stop() {
		System.out.println("空中不能停,坠毁了!");
	}
}	

执行结果如图:
在这里插入图片描述

对象转型

父类引用指向子类对象,我们称这个过程为向上转型,属于自动类型转换。
向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法。这时,我们就需要进行类型的强制转换,我们称之为向下转型!

public class TestCasting {
	public static void main(String[ ] args) {
		Object obj = new String("北京尚学堂"); // 向上可以自动转型
		// obj.charAt(0) 无法调用。编译器认为obj是Object类型而不是String类型
		/* 编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换。
		 * 不然通不过编译器的检查。 */
		String str = (String) obj; // 向下转型
		System.out.println(str.charAt(0)); // 位于0索引位置的字符
		System.out.println(obj == str); // true.他们俩运行时是同一个对象
	}
}

在这里插入图片描述

这里我们需要注意:
在向下转型过程中,必须将引用变量转成真实的子类类型(运行时类型)否则会出现类型转换异常ClassCastException

抽象类

什么是抽象类:
包含抽象方法的类就是抽象类。通过abstract方法定义规范,然后要求子类必须定义具体实现。通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。

//抽象类
abstract class Animal {
	abstract public void shout();  //抽象方法
}
class Dog extends Animal {	
	//子类必须实现父类的抽象方法,否则编译错误
	public void shout() {
		System.out.println("汪汪汪!");
	}
	public void seeDoor(){
		System.out.println("看门中....");
	}
}
//测试抽象类
public class TestAbstractClass {
	public static void main(String[ ] args) {
		Dog a = new Dog();
		a.shout();
		a.seeDoor();
	}
}

抽象类的使用要点:
1.有抽象方法的类只能定义成抽象类
2.抽象类不能实例化,即不能用new来实例化抽象类。
3.抽象类可以包含属性、方法、构造方法。但是构造方法不能用来new实例,只能用来被子类调用。
4.抽象类只能用来被继承。
抽象方法必须被子类实现。

如果一个类有抽象方法,这个类必须被声明为抽象类;
抽象类中可以没有抽象方法,抽象类也可以有自己的构造器;
抽象类不能实例化(不能创建对象);
强烈建议:将被继承的类设计成抽象类

接口

在Java语言规范中,一个方法的特征仅包括方法的名字,参数的数目和种类,而不包括方法的返回类型,参数的名字以及所抛出来的异常。在Java编译器检查方法的重载时,会根据这些条件判断两个方法是否是重载方法。但在Java编译器检查方法的置换时,则会进一步检查两个方法(分处超类型和子类型)的返还类型和抛出的异常是否相同。
接口实现和类继承的规则不同,为了数据的安全,继承时一个类只有一个直接父类,也就是单继承,但是一个类可以实现多个接口,接口弥补了类的不能多继承缺点,继承和接口的双重设计既保持了类的数据安全也变相实现了多继承。
Java接口本身没有任何实现,因为Java接口不涉及表象,而只描述public行为,所以Java接口比Java抽象类更抽象化。但是接口不是类,不能使用new 运算符实例化一个接口。如 x=new comparable(…);//这个是错误来的。但是可以声明接口变量Comparable x; //这是允许的。
Java接口的方法只能是抽象的和公开的,Java接口不能有构造器,Java接口可以有public、static和final属性。即接口中的属性可以定义为 public static final int value=5; [1]
接口把方法的特征和方法的实现分割开来。这种分割体现在接口常常代表一个角色,它包装与该角色相关的操作和属性,而实现这个接口的类便是扮演这个角色的演员。一个角色由不同的演员来演,而不同的演员之间除了扮演一个共同的角色之外,并不要求其它的共同之处。

什么是接口:
接口是约定:实现接口的类必须重写接口中所有的方法,否则就要声明为抽象类
接口代表能力:实现了接口的类就具备了接口所描述的能力
接口是一种角色: 一个类可以实现多个接口,一个接口也可以被多个类实现

为什么需要接口?接口和抽象类的区别?
接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。全面地专业地实现了:规范和具体实现的分离。
抽象类还提供某些具体实现,接口不提供任何实现,接口中所有方法都是抽象方法。接口是完全面向规范的,规定了一批类具有的公共方法规范。
从接口的实现者角度看,接口定义了可以向外部提供的服务。
从接口的调用者角度看,接口定义了实现者能提供那些服务。
接口是两个模块之间通信的标准,通信的规范。如果能把你要设计的模块之间的接口定义好,就相当于完成了系统的设计大纲,剩下的就是添砖加瓦的具体实现了。大家在工作以后,做系统时往往就是使用“面向接口”的思想来设计系统。
接口和实现类不是父子关系,是实现规则的关系。比如:我定义一个接口Runnable,Car实现它就能在地上跑,Train实现它也能在地上跑,飞机实现它也能在地上跑。就是说,如果它是交通工具,就一定能跑,但是一定要实现Runnable接口。

区别
普通类:具体实现
抽象类:具体实现,规范(抽象方法)
接口:规范!

那么如何使用接口:
声明格式:
[访问修饰符] interface 接口名 [extends 父接口1,父接口2…] {
常量定义;
方法定义;
}
定义接口的详细说明:
访问修饰符:只能是public或默认。
接口名:和类名采用相同命名机制。
extends:接口可以多继承。
常量:接口中的属性只能是常量,总是:public static final 修饰。不写也是。
方法:接口中的方法只能是:public abstract。 省略的话,也是public abstract。

要点
子类通过implements来实现接口中的规范。
接口不能创建实例,但是可用于声明引用变量类型。
一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是public的。
JDK1.8(不含8)之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。
JDK1.8(含8)后,接口中包含普通的静态方法、默认方法。

public class TestInterface {
	public static void main(String[ ] args) {
		Volant volant = new Angel();
		volant.fly();
        System.out.println(Volant.FLY_HIGHT);
        
        Honest honest = new GoodMan();
        honest.helpOther();
	}
}
/**飞行接口*/
interface Volant { 
	int FLY_HIGHT = 100;  // 总是:public static final类型的;
	void fly();   //总是:public abstract void fly();
}
/**善良接口*/
interface Honest { 
	void helpOther();
}
/**Angel类实现飞行接口和善良接口*/
class Angel implements Volant, Honest{
	public void fly() {
		System.out.println("我是天使,飞起来啦!");
	}
	public void helpOther() {
		System.out.println("扶老奶奶过马路!");
	}
}
class GoodMan implements Honest {
 	public void helpOther() {
		System.out.println("扶老奶奶过马路!");
	}  
}
class BirdMan implements Volant {
	public void fly() {
		System.out.println("我是鸟人,正在飞!");
	}
}
接口中的默认方法与静态方法

默认方法:
Java 8及以上旧版本,允许给接口添加一个非抽象的方法实现,只需要使用 default 关键字即可,这个特征又叫做默认方法(也称为扩展方法)。

默认方法和抽象方法的区别是抽象方法必须要被实现,默认方法不是。作为替代方式,接口可以提供默认方法的实现,所有这个接口的实现类都会通过继承得到这个方法。

静态方法:
JAVA8以后,我们也可以在接口中直接定义静态方法的实现。这个静态方法直接从属于接口(接口也是类,一种特殊的类),可以通过接口名调用。
如果子类中定义了相同名字的静态方法,那就是完全不同的方法了,直接从属于子类。可以通过子类名直接调用。

默认方法可以调用静态方法,静态方法却不能调用默认方法;

接口的多继承

接口完全支持多继承。和类的继承类似,子接口扩展某个父接口,将会获得父接口中所定义的一切。
我们举个简单的例子说明:

interface A {
	void testa();
}
interface B {
	void testb();
}
/**接口可以多继承:接口C继承接口A和B*/
interface C extends A, B {
	void testc();
}
public class Test implements C {
	public void testc() {
}
	public void testa() {
	}
	public void testb() {
	}
}

Java中的接口

java中的接口通常分为三种:
单方法接口:这个唯一的方法通常是回调方法
表示接口:没有方法,但是表示了某种能力
常量接口:这是接口最不正确的用法
单方法接口
以下是引用片段:

public interface Actionlistener{
public abstract void actionPerformed(ActionEvent event);
}

仅且只有一个方法,只有实现了这个接口(重写这个接口中的唯一一个方法),你才有资格去事件监听器列表里注册(参数为Actionlistener类型),当事件源变动时,自动调用这个唯一的actionPerformed方法。
标识接口
是没有任何方法和属性的接口。标识接口不对实现它的类有任何语意上的要求,它仅仅表明了实现它的类属于一个特定的类型(传递)。
不推荐过多的使用标识接口。
常量接口
用Java接口来声明一些常量,然后由实现这个接口的类使用这些常量(以前在做画板的时候这么干过)。建议不要模仿这种常量接口的做法。

定义接口格式:
[public]interface 接口名称 [extends父接口名列表]
{
//静态常量
[public] [static] [final] 数据类型变量名=常量值;
//抽象方法
[public] [abstract] [native] 返回值类型方法名(参数列表);
}
实现接口格式:
[修饰符] class 类名[extends 父类名] [implements 接口A,接口B,···]
{
类成员变量和成员方法;
为接口A中的所有方法编写方法体,实现接口A;
为接口B中的所有方法编写方法体,实现接口B;
}
接口和抽象类的区别

Java接口和Java抽象类最大的一个区别,就在于Java抽象类可以提供某些方法的部分实现,而Java接口不可以,这大概就是Java抽象类唯一的优点吧,但这个优点非常有用。如果向一个抽象类里加入一个新的具体方法时,那么它所有的子类都一下子都得到了这个新方法,而Java接口做不到这一点,如果向一个Java接口里加入一个新方法,所有实现这个接口的类就无法成功通过编译了,因为你必须让每一个类都再实现这个方法才行,这显然是Java接口的缺点。
一个抽象类的实现只能由这个抽象类的子类给出,也就是说,这个实现处在抽象类所定义出的继承的等级结构中,而由于Java语言的单继承性,所以抽象类作为类型定义工具的效能大打折扣。在这一点上,Java接口的优势就出来了,任何一个实现了一个Java接口所规定的方法的类都可以具有这个接口的类型,而一个类可以实现任意多个Java接口,从而这个类就有了多种类型。
不难看出,Java接口是定义混合类型的理想工具,混合类表明一个类不仅仅具有某个主类型的行为,而且具有其他的次要行为。
在语法上,抽象类和接口有着以下不同:
1.abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。 继承抽象类使用的是extends关键字,实现接口使用的是implements关键字,继承写在前面,实现接口写在后面。如果实现多个接口,中间用逗号分隔。例:
public class Main extends JApplet
public class Main implements Runnable
public class Main extends JApplet implements ActionListener
public class Main extends JApplet implements ActionListener, Runnable
2.在abstract class中可以有自己的数据成员,也可以有非abstract的成员方法,而在interface中,只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在 interface中一般不定义数据成员),所有的成员方法都是abstract的。
3.abstract class和interface所反映出的设计理念不同。其实abstract class表示的是"is-a"关系,interface表示的是"like-a"关系。
4.实现接口的类必须实现其中的所有方法,继承自抽象类的子类实现所有的抽象方法。抽象类中可以有非抽象方法。接口中则不能有实现方法。
5.接口中定义的变量默认是public static final 型,且必须给其初值,所以实现类中不能重新定义,也不能改变其值。
6.抽象类中的变量默认具有 friendly权限,其值可以在子类中重新定义,也可以重新赋值。
7.接口中的方法默认都是 public abstract 类型的。

内部类

内部类是一类特殊的类,指的是定义在一个类的内部的类。实际开发中,为了方便的使用外部类的相关属性和方法,这时候我们通常会定义一个内部类。

内部类的概念:

一般情况,我们把类定义成独立的单元。有些情况下,我们把一个类放在另一个类的内部定义,称为内部类(innerclasses)。
在注意
内部类只是一个编译时概念,一旦我们编译成功,就会成为完全不同的两个类。对于一个名为Outer的外部类和其内部定义的名为Inner的内部类。编译完成后会出现Outer.class和Outer$Inner.class两个类的字节码文件。所以内部类是相对独立的一种存在,其成员变量/方法名可以和外部类的相同。

/**外部类Outer*/
class Outer {
	private int age = 10;
	public void show(){
		System.out.println(age);//10
	}
	/**内部类Inner*/
	public class Inner {
		//内部类中可以声明与外部类同名的属性与方法
		private int age = 20;
		public void show(){
			System.out.println(age);//20
		}
	}
}

内部类的作用:
内部类提供了更好的封装。只能让外部类直接访问,不允许同一个包中的其他类直接访问。
内部类可以直接访问外部类的私有属性,内部类被当成其外部类的成员。 但外部类不能访问内部类的内部属性。
接口只是解决了多重继承的部分问题,而内部类使得多重继承的解决方案变得更加完整。

内部类的使用场合:
由于内部类提供了更好的封装特性,并且可以很方便的访问外部类的属性。所以,在只为外部类提供服务的情况下可以优先考虑使用内部类。
使用内部类间接实现多继承:每个内部类都能独立地继承一个类或者实现某些接口,所以无论外部类是否已经继承了某个类或者实现了某些接口,对于内部类没有任何影响。

内部类的分类

这方面知识我还没了解很清楚,下次补上~~~

到这里我们对面向对象的总结就结束了,当然有很多的不足,欢迎补充。

这方面的知识我们可以通过下面的链接进行了解。

https://www.bilibili.com/video/av43896218?from=search&seid=5043475543295889404

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值