面向对象编程(OOP)
初识面向对象
面向对象编程(Object-Oriented Programming,OOP),适合处理一些复杂问题,适合需要多人协作处理的问题,面向对象就是以类的方式组织代码,以对象的形式组织数据。
- 对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
- 类:类是一个模板,它描述一类对象的行为和状态。所有的狗是一个类,每一条具体的狗就是对象。
面向对象的一些基本概念
封装、继承、多态、抽象、类、对象、实例、方法、重载。
面向对象三大特征:
封装、继承、多态
从认识论的角度是先有对象后有类,对象是具体的事物,类是抽象的,是对对象的抽象;从代码运行角度考虑是先有类后有对象,类是对象的模板。
类与对象
类与对象关系
类是一种抽象的数据类型,它是对某一类型事物整体描述/定义,但是并不能代表某一具体的事物,类可以看成是一类对象的模板。一个类可以包含以下类型变量:
- 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
- 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
- 类变量:类变量也声明在类中,方法体之外,但必须声明为 static 类型。
一个类可以拥有多个方法,但是一个项目应该只存在一个main方法;对象是抽象概念的具体实例,所有的对象都包含类的属性和方法。
package com.oop.demo02;
public class Application {
public static void main(String[] args) {
Students student=new Students(); //类实例化之后会返回一个自己的对象,
System.out.println(student.name);
System.out.println(student.age);
student.name="zhangsan";
student.age=10;
System.out.println(student.name);
System.out.println(student.age);
}
}
创建与初始化对象
对象是根据类创建的。在Java中,使用关键字 new 来创建一个新的对象。创建对象需要以下三步:
- 声明:声明一个对象,包括对象名称和对象类型;
- 实例化:使用关键字 new 来创建一个对象;
- 初始化:使用 new 创建对象时,会调用构造方法初始化对象。
创建对象时,除了分配内存空间以外,还会给创建好的对象进行默认的初始化以及类中构造器的调用。
类中的构造器:
构造器也叫做构造方法,每个类都有构造方法。如果没有显式地为类定义构造方法,Java 编译器将会为该类提供一个默认构造方法。在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,一个类可以有多个构造方法。
下面是一个构造方法示例:
public class Puppy{
public Puppy(){
}
public Puppy(String name){
// 这个构造器仅有一个参数:name
}
}
- 必须与类名字相同
- 没有返回值类型,也不能写void。
- 一旦定义了有参构造,无参构造一定要显示定义(除非不用无参构造,但是一般建立的有参构造,就会定义无参构造)
构造器的作用:
- 使用new关键字,必须要有构造器,new本质是在调用构造器;
- 用来初始化属性。
创建对象的内存分析
可以参考这篇博文
Java中对象的创建过程(内存分析)_南瓜灯cc-CSDN博客_java创建对象内存分析
实例:
有一个类:
package cn.sg.oop.second;
public class Cat {
String name;
int age;
public void eat(){
System.out.println("小猫正在吃......");
}
}
然后是main函数
package cn.sg.oop.second;
public class Animals {
public static void main(String []args){
Cat c=new Cat();
c.name="小喵喵";
c.age=18;
c.eat();
}
}
逐行分析:
Cat c=new Cat();
Cat c :在内存中把c放入栈中
new Cat:在堆中创建cat,包括cat类中的属性(初始化)和方法
Cat c=new Cat(): 把c的地址传给c,需要注意的是这里是地址引用
c.name=“小喵喵”:这条语句首先在栈中找到c,通过地址的指向,找到堆中的name,赋值为“小喵喵”;
c.age=18:同上
c.eat():同上
类与对象小节
**类与对象:**类是模板,对象是一个具体实例
**方法:**定义和调用
**对象的引用:**除了基本类型之外都是引用类型,对象是通过引用类型操作的,引用就是指向对象的一个地址
**属性:**成员变量,默认初始化;
修饰符 属性类型 属性名=属性值;
对象的创建和使用:
必须使用new关键字,必须有构造器;
对象的属性与方法用点操作符;
源文件声明规则
在本节的最后部分,我们将学习源文件的声明规则。当在一个源文件中定义多个类,并且还有import语句和package语句时,要特别注意这些规则:
- 一个源文件中只能有一个 public 类,但是可以有多个非 public 类;
- 源文件的名称应该和 public 类的类名保持一致;
- 如果一个类定义在某个包中,那么 package 语句应该在源文件的首行;
- 如果源文件包含 import 语句,那么应该放在 package 语句和类定义之间,如果没有 package 语句,那么 import 语句应该在源文件中最前面。
- import 语句和 package 语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。
Java 包
包主要用来对类和接口进行分类,当开发 Java 程序时,可能编写成百上千的类,因此很有必要对类和接口进行分类。Java包的作用是为了更好的组织类与接口。
Java 包(package) | 菜鸟教程 (runoob.com)
包的作用
- 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用;
- 如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突;
- 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。
语法格式为:
package pkg1[.pkg2[.pkg3…]];
创建包
创建包的时候,需要为这个包取一个合适的名字,如果其他的一个源文件包含了这个包提供的类、接口、枚举或者注释类型的时候,都必须将这个包的声明放在这个源文件的开头。包声明应该在源文件的第一行,每个源文件只能有一个包声明,这个文件中的每个类型都应用于它。如果一个源文件中没有使用包声明,那么其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)中。
import 语句
在 Java 中,如果给出一个完整的限定名,包括包名、类名,那么 Java 编译器就可以很容易地定位到源代码或者类。import 语句就是用来提供一个合理的路径,使得编译器可以找到某个类。
import语句可以有一条,也可以有多条:
import package1[.package2…].(classname|*);
如果想导入某个包下的所有类,用用*号代替类名。
import java.io.*;
package 的目录结构
类放在包中会有两种主要的结果:
- 包名成为类名的一部分,正如我们前面讨论的一样。
- 包名必须与相应的字节码所在的目录结构相吻合。
下面是管理你自己 java 中文件的一种简单方式:
将类、接口等类型的源码放在一个文本中,这个文件的名字就是这个类型的名字,并以.java作为扩展名。例如:
// 文件名 : Car.java package vehicle; public class Car { // 类实现 }
接下来,把源文件放在一个目录中,这个目录要对应类所在包的名字。
…\vehicle\Car.java
现在,正确的类名和路径将会是如下样子:
- 类名 -> vehicle.Car
- 路径名 -> vehicle\Car.java (在 windows 系统中)
通常,一个公司使用它互联网域名的颠倒形式来作为它的包名.例如:互联网域名是 runoob.com,所有的包名都以 com.runoob 开头。包名中的每一个部分对应一个子目录。
封装
基本概念
程序设计追求:高内聚、低耦合,高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合就是仅暴露少了的方法给外部使用。封装(数据的隐藏):通常应该禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,称为信息隐藏。
**封装重点是对属性而言。**属性私有,需要通过类的get/set函数实现对类私有属性的访问。
Java 封装
在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。
- 封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问;
- 要访问该类的代码和数据,必须通过严格的接口控制;
- 封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段;
- 适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
封装的优点
- 良好的封装能够减少耦合。
- 类内部的结构可以自由修改。
- 可以对成员变量进行更精确的控制。
- 隐藏信息,实现细节。
封装的步骤
- 修改属性的访问权限为私有
- 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问get、set alt+inster自动生成方法
封装不仅可以保护属性,而且可以使得程序更安全,例如对属性赋值,可以用set函数对输入的值判断是否合法。
package com.oop.demo04;
public class Student {
//属性私有
private String name;
private int id;
private char sex;
//可以操作属性的方法
public void setName(String name){
this.name=name;
}
public String getName(){
return this.name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
}
封装的意义
- 提高程序的安全性,保护数据;
- 隐藏代码实现细节;
- 统一接口;
- 系统可维护性增加;
继承
基本概念
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。继承的本质是对某一批类的抽象,extands的意思是扩展,子类是父类的扩展,子类可以继承父类所有方法,以及所有public类型的属性。
- 继承是类与类之间的一种关系,除此之外还有依赖、组合、聚合等;
- 继承关系的两个类,一个为子类,一个为父类,子类继承父类,使用关键字extands表示
- 子类和父类之间,从意义上讲应该具有“is a”的关系
**修饰符:**public,protected,defalut,private。
**意义:**提高代码的复用性。
类的继承格式
在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:
class 父类 { }
class 子类 extends 父类 { }
继承分类
java中只有单继承,没有多继承
继承的特性
- 子类拥有父类非 private 的属性、方法;
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展;
- 子类可以用自己的方式实现父类的方法(重写);
- Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
- 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
继承关键字
继承可以使用 extends 和 implements (实现这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。
extends关键字:
在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
implements关键字
使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
final修饰的类不能被继承。
Super详解
用来调用父类的方法和属性(非private类型的)
构造器
子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)了父类的构造器。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器
注意点:
- super()调用父类的构造方法,必须在构造方法第一个;
- super必须只能出现在子类的方法或者构造方法中;
- super和this不能同时调用构造方法;
与this的区别
代表对象不同:
this:本身调用者对象;
super:父类对象的应用;
使用前提不同:
this没有继承也可以用;
super只能在继承条件下用;
构造方法:
this()本类的构造方法;
super()父类的构造方法;
final关键字
final 关键字声明类可以把类定义为不能继承的,即最终类;
或者用于修饰方法,该方法不能被子类重写;
-
声明类:
final class 类名 {//类体}
-
声明方法:
修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}
注:实例变量也可以被定义为 final,被定义为 final 的变量不能被修改。被声明为 final 类的方法自动地声明为 final,但是实例变量并不是 final。
方法重写
Java 重写(Override)与重载(Overload) | 菜鸟教程 (runoob.com)
- 重写是对方法而言,与属性无关,而且只跟非静态有关,与静态方法无关,不能是private。
- 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
- 重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
- 重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,抛出 IOException 异常或者 IOException 的子类异常。
- 在面向对象原则里,重写意味着可以重写任何现有方法
方法的重写规则
- 参数列表与被重写方法的参数列表必须完全相同;
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同);
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected;
- 父类的成员方法只能被它的子类重写;
- 声明为 final 的方法不能被重写;
- 声明为 static 的方法不能被重写,但是能够被再次声明;
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法;
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法;
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以;
- 构造方法不能被重写;
- 如果不能继承一个类,则不能重写该类的方法。
当需要在子类中调用父类的被重写方法时,要使用 super 关键字。
静态方法,final,private不可以重写
一个实例
如果不重写,默认调用父类的函数。
public class A extends B{
public static void test(){
System.out.println("B->tetst");
}
}
package com.oop.demo05;
public class B {
public static void test(){
System.out.println("B->tetst");
}
}
package com.oop;
import com.oop.demo05.A;
import com.oop.demo05.B;
public class Application {
public static void main(String[] args) {
A a=new A();
a.test();
B b=new A();
b.test();
}
}
重写
package com.oop.demo05;
public class A extends B{
@Override
public void test() {
System.out.println("A-test");
}
}
重写的快捷键:alt+insert
与重载的区别
重载(overloading) 是在一个类里面,方法名字相同,而参数不同,返回类型可以相同也可以不同,每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表,最常用的地方就是构造器的重载。
重载规则:
- 被重载的方法必须改变参数列表(参数个数或类型不一样);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准。
多态
多态是同一个行为具有多个不同表现形式或形态的能力,多态就是同一个接口,使用不同的实例而执行不同操作。
多态的优点
- 消除类型之间的耦合关系
- 可替换性
- 可扩充性
- 接口性
- 灵活性
- 简化性
多态存在的三个必要条件
- 继承
- 重写
- 父类引用指向子类对象:Parent p = new Child();
public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法
Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat,如果子类没有重写eat方法,则调用父类的eat
Cat c = (Cat)a; // 向下转型 如果需要调用子类独有的方法,需要强制类型转换
c.work(); // 调用的是 Cat 的
}
public static void show(Animal a) {
a.eat();
// 类型判断
if (a instanceof Cat) { // 猫做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}
}
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
小结
- 动态编译,可扩展性更强;
- 一个对象的实际类型是确定的,但是可以指向的引用类型不确定(父类的引用可以指向子类的类型Person p=new Student());
- 如果子类没有重写父类的方法,那么调用的都是父类的方法,如果子类重写了,调用子类的;
- 对象能够使用那些方法,主要看左边的类型,与右边的关系不大;
- 多态是针对方法说的,与属性没关系;
- 父类和子类之间有联系;
- 存在条件:继承关系,方法需要重写,父类引用指向子类对象 father f1=new son();
instanceof
X instanceof Y
判断X对象跟Y是否有关系,就是Y是不是X的子类。
类型转换
- 子类转换成父类可以直接转换,但是子类独有的方法会丢掉
- 父类转换成子类需要强制类型转换
Person p=new Student()
父类的引用指向了子类,但是p不能调用子类独有的方法,如果想用子类的方法,需要强制转换成子类:
((student)p).子类独有的方法
强制类型转换是为了方便方法调用,减少重复代码。
static 关键字
- 静态属性可以直接用类名访问,所有对象共用一个属性;
- 非静态属性必须实例化时候,通过对象名访问。
static关键字可以修饰类或者方法,静态方法可以用类名或者直接用方法名调用,静态方法只能调用静态方法或者属性,因为静态方法和属性是类创建时候就有。
静态代码块,类加载的时候就执行,且执行一次。
static{
}
package com.oop.demo07;
import com.oop.demo06.Person;
public class Student {
{
System.out.println("代码块");
}
static {
System.out.println("静态代码块");
}
public Student(){
System.out.println("构造方法");
}
public static void main(String[] args) {
Student person=new Student();
System.out.println("====");
Student person1=new Student();
}
}
static修饰的代码块值执行一次,其他的在每次创建对象的时候都执行一次。
静态导入包
静态导入包之后,就可以直接用类的名字来调用类的功能。
import static java.lang.Math.random;
package com.oop.demo07;
import static java.lang.Math.random;
public class Test {
public static void main(String[] args) {
System.out.println(random());
}
}
static的四种用法:[java]static关键字的四种用法 - dotgua - 博客园 (cnblogs.com)
抽象类
概述
抽象类
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
在 Java 语言中使用 abstract class 来定义抽象类。
抽象方法
如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号:
public abstract double computePay();
- 如果一个类包含抽象方法,那么该类必须是抽象类;
- 任何子类必须重写父类的抽象方法,或者声明自身为抽象类;
继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。
实例
父类(抽象类)
package com.oop.demo08;
public abstract class Action {
//抽象方法,只有方法名字,没有实现
//抽象方法必须在抽象类中,而抽象类不一定非得是抽象方法。
//是一种约束
public abstract void doSomething();
//抽象类不能new,只能靠子类实现,是一种约束
}
子类(实例化)
package com.oop.demo08;
public class A extends Action{
//继承抽象类,子类必须重写抽象类的抽象方法
@Override
public void doSomething() {
}
}
抽象类小结
- 抽象类不能被实例化(初学者很容易犯的错),抽象类的非抽象子类可以创建对象。
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类;
- 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能;
- 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法;
- 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类;
**抽象类存在构造器吗?**存在
**抽象类存在的意义?**抽象出来,提高开发效率
接口
概述
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
接口与类相似点:
- 一个接口可以有多个方法;
- 接口文件保存在 .java 结尾的文件中,文件名使用接口名;
- 接口的字节码文件保存在 .class 结尾的文件中;
- 接口相应的字节码文件必须在与包名称相匹配的目录结构中;
接口与类的区别:
- 接口不能用于实例化对象;
- 接口没有构造方法;
- 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法;
- 接口不能包含成员变量,除了 static 和 final 变量;
- 接口不是被类继承了,而是被类实现;
- 接口支持多继承(实现);
接口特性
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错);
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误);
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
抽象类和接口的区别
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行;
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的;
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法;
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
**普通类:**只有具体实现
抽象类:具体实现和规范都有,但是不可以New
**接口:**只有规范,没有方法具体实现,实现约束和实现分离,面向接口的编程。
接口有以下特性:
- 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字;
- 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字;
- 接口中的方法都是公有的;
注意
- JDK 1.8 以后,接口里可以有静态方法和方法体了。
- JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。更多内容可参考 Java 8 默认方法。
- JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。更多内容可参考 Java 9 私有接口方法。
接口的继承
一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。在Java中,类的多继承是不合法,但接口允许多继承。在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。
标记接口
最常用的继承接口是没有包含任何方法的接口。标记接口是没有任何方法和属性的接口,它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。标记接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权。
标记接口主要用于以下两种目的:
-
建立一个公共的父接口:正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。
-
向一个类添加数据类型:这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。
实例
接口的声明
接口的声明语法格式如下:
[可见度] interface 接口名称 [extends 其他的接口名] {
// 声明变量
// 抽象方法
}
当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。
类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。
一个接口
package com.oop.demo09;
public interface TimeService {
void timer();
}
另一个接口
package com.oop.demo09;
public interface UserService {
//接口中的所有内容都是抽象的public
void run();
//静态常量
int AGE=9;
}
接口实现
package com.oop.demo09;
//实现了接口的类,就要重写接口中的方法
public class UserServiceImp1 implements UserService,TimeService{
@Override
public void run() {
}
@Override
public void timer() {
}
}
重写接口中声明的方法时,需要注意以下规则:
- 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
- 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
- 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。
在实现接口的时候,也要注意一些规则:
- 一个类可以同时实现多个接口。
- 一个类只能继承一个类,但是能实现多个接口。
- 一个接口能继承另一个接口,这和类之间的继承比较相似。
总结
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是…则必须能…”的思想;
- 接口的本质是契约;
- OO的精髓,是对对象的抽象;
- 接口中的所有内容都是public abstract;
- 接口可以继承多个(伪继承);
- 接口中的属性都是常量,静态常量;
作用:
1、约束
2、定义一些方法,让不同的人实现
3、方法都是共有,抽象的
4、属性都是静态常量
5、接口不能被实例化,接口中没有构造方法
6、implements可以实现多个接口
7、必须要重写接口中的方法
内部类
概述
一个类的内部定义一个类。
内部类的分类:
- 成员内部类:一个类作为另一个类的成员变量
- 静态内部类:一个类作为另一个类的静态成员变量
- 局部内部类:定义在方法中的类
- 匿名内部类:不用把实例保存到变量中,还可以有匿名的内部接口
内部类实例化
通过外部类实例化内部类
Outer outer=new Outer();
//通过外部类实例化内部类
Outer.Inner inner=outer.new Inner();
内部类可以获取外部类的私有属性和私有方法,而静态的内部类不可以
实例
成员内部类
package com.oop.demo10;
public class Outer {
private int id;
public void out(){
System.out.println("外部类的方法");
}
public class Inner{
public void in(){
System.out.println("内部类方法");
}
public void getId(){
System.out.println(id);
}
}
}
package com.oop;
import com.oop.demo10.Outer;
public class Application {
public static void main(String[] args){
Outer outer=new Outer();
//通过外部类实例化内部类
Outer.Inner inner=outer.new Inner();
inner.in();
}
}
局部内部类
package com.oop.demo10;
public class Outer {
private int id;
public void out(){
System.out.println("外部类的方法");
}
public void method(){
class Inner2{
public void in2(){
}
}
}
}
匿名内部类
package com.oop.demo10;
public class Test {
public static void main(String[] args) {
new Apple().eat();
new Service(){
@Override
public void run(){
}
};
}
}
class Apple{
public void eat(){
System.out.println("1");
}
}
interface Service{
void run();
}
System.out.println(id);
}
}
}
```java
package com.oop;
import com.oop.demo10.Outer;
public class Application {
public static void main(String[] args){
Outer outer=new Outer();
//通过外部类实例化内部类
Outer.Inner inner=outer.new Inner();
inner.in();
}
}
局部内部类
package com.oop.demo10;
public class Outer {
private int id;
public void out(){
System.out.println("外部类的方法");
}
public void method(){
class Inner2{
public void in2(){
}
}
}
}
匿名内部类
package com.oop.demo10;
public class Test {
public static void main(String[] args) {
new Apple().eat();
new Service(){
@Override
public void run(){
}
};
}
}
class Apple{
public void eat(){
System.out.println("1");
}
}
interface Service{
void run();
}