面向对象三大特征:封装(Encapsulation)
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说, 把该隐藏的隐藏起来,该暴露的暴露出来。 这就是封装性的设计思想。
程序设计追求“高内聚,低耦合”。
高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
低耦合 : 仅对外暴露少量的方法用于使用。
Java中通过将数据声明为私有的(private), 再提供公共的( public)方法:getXxx()和setXxx()实现对该属性的操作, 以实现下述目的:
隐藏一个类中不需要对外提供的实现细节;
使用者只能通过事先定制好的方法来访问数据, 可以方便地加入控制逻辑,限制对属性的不合理操作;
便于修改, 增强代码的可维护性;
Java权限修饰符public、 protected、(缺省)、private置于类的成员(属性,方法,内部类,构造器)定义前,用来限定对象对该类成员的访问限。
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | Yes | |||
(缺省) | Yes | Yes | ||
protected | Yes | Yes | Yes | |
public | Yes | Yes | Yes | Yes |
对于class的权限修饰只可以用public和default(缺省)。
public类可以在任意地方被访问。 default类只可以被同一个包内部的类访问
面向对象三大特征:继承(inherit)
什么是继承?
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
此处的多个类称为子类(派生类), 单独的这个类称为父类(基类或超类)。 可以理解为:“子类 is a 父类”
作用:
继承的出现减少了代码冗余,提高了代码的复用性。
继承的出现,更有利于功能的扩展。
继承的出现让类与类之间产生了关系,提供了多态的前提。
继承语法规则:class SubClass extends SuperClass
注意:
子类继承了父类,就继承了父类的所有的方法和属性。
拥有和可见性不同,子类不能直接访问父类中私有的(private)的成员变量和方法,但可以间接访问。 子类不能使用父类的私有方法,但能通过调用其他能访问的方法间接访问。这有点像父类一个不是private权限的方法为了完成自己的功能,在类中定义了一个private的方法仅仅供自己使用,仅此而已,除了在本类中,其他任何地方都不能调用该private方法。
在子类中,可以使用父类中定义的方法和属性,也可以创建新的属性和方法。
关于继承的说明:
Java只支持单继承和多层继承, 不允许多重继承
- 一个子类只能有一个父类
- 一个父类可以派生出多个子类
- 子父类是相对关系
- 直接父类与间接父类
没有直接写父类的话,它就继承java.lang.Object类
所有类(除java.lang.Object)都直接或间接继承自Object类
重写
定义:在子类中可以根据需要对从父类中继承来的方法进行改造, 也称为方法的重置、覆盖。在程序执行时,子类对象调用该方法将覆盖父类的方法。
属性,构造器没有重写这一说法
权限修饰符 返回值类型 方法名(参数列表) throws 异常类型
{
//方法体
}
要求:两同一大两小
- 子类重写的方法必须和父类被重写的方法具有相同的方法名称、 参数列表
- 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
- 父类被重写的方法的返回值如果是void,子类重写方法的返回值也得是void
- 父类被重写的方法的返回值如果是A类,子类重写方法的返回值只能是A类及其子类
- 父类被重写的方法的返回值如果是基本数据类型,子类重写方法的返回值类型也得是基本类型
子类重写的方法使用的权限修饰符不能小于父类被重写的方法的权限修饰符
- 子类不能重写父类中声明为private权限的方法
这也很好理解,因为父类的private权限的方法仅仅供本类使用,子类虽然继承了父类的属性和方法,但是看不到父类的private成员变量和方法。所以在子类中写一个与父类private权限方法同名同参数的方法不是重写。
- 子类不能重写父类中声明为private权限的方法
子类方法抛出的异常不能大于父类被重写方法的异常
子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写) 。因为static方法是属于类的,子类无法覆盖父类的方法。
super
作用:在Java类中使用super来调用父类中的指定操作:
super可用于访问父类中定义的属性
尤其当子父类出现同名成员时, 可以用super表明调用的是父类中的成员 当父类及父类的父类出现同名成员时,子类用super表名调用的是直接父类的成员
super可用于调用父类中定义的成员方法
尤其当子类重写父类方法时,可以用super表明调用的是父类的方法
super可用于在子类构造器中调用父类的构造器
super的追溯不仅限于直接父类 super和this的用法相像, this代表本类对象的引用, super代表父类的内存空间的标识
如何调用父类的构造器 :
子类中所有的构造器默认都会访问父类中空参数的构造器,写不写都有!!
当父类中没有空参数的构造器时, 子类的构造器必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的构造器。 同时, 只能”二选一”, 且必须放在构造器的首行
如果子类构造器中既未显式调用父类或本类的构造器, 且父类中又没有无参的构造器, 则编译出错
this和super的区别
一个类中最多有n-1个构造器使用this(), 最少有一个使用super(),且所有构造器第一行要么是super(), 要么是this()
No. | 区别点 | this | super |
---|---|---|---|
1 | 访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 直接访问父类中的属性 |
2 | 调用方法 | 访问本类中的方法,如果本类没有此方法则从父类中继续查找 | 直接访问父类中的方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
子类对象实例化过程⭐
1.0
没有讲继承之前的赋值过程:
赋值的位置:
① 默认初始化
② 显式初始化
③ 构造器中初始化
④ 通过“对象.属性“或“对象.方法”的方式赋值
赋值的先后顺序:① - ② - ③ - ④
2.0
讲了继承之后:
赋值的位置:
① 默认初始化
② 显式初始化
③ 构造器中初始化
④ 通过“对象.属性“或“对象.方法”的方式赋值
赋值的先后顺序:①全部属性(包括继承的)默认初始化—-②构造器从下往上执行this()/super()语句直到Object类——③从上往下每个类依次执行两个步骤:显示初始化,然后执行构造器中除super()/this()的其他语句——④通过“对象.属性“或“对象.方法”的方式赋值
3.0
讲了代码块之后
赋值的位置:
① 静态变量+静态代码块
② 默认初始化
③ 显式初始化+非静态代码块初始化
④ 构造器中初始化
⑤ 通过“对象.属性“或“对象.方法”的方式赋值
赋值的先后顺序:①第一次加载类时从其父类向下依次执行各类中的静态变量+静态代码块②全部属性(包括继承的)默认初始化—-②构造器从下往上执行this()/super()语句直到Object类——③从上往下每个类依次执行两个步骤:显示初始化+非静态代码块初始化(按照顺序),然后执行构造器中除super()/this()的其他语句——④通过“对象.属性“或“对象.方法”的方式赋值
面向对象的三大特征:多态
多态性,是面向对象中最重要的概念, 在Java中的体现:对象的多态性:父类的引用指向子类的对象
一个变量只能有一种确定的数据类型
一个引用类型变量可能指向(引用)多种不同类型的对象
可以直接应用在抽象类和接口上
Java引用变量有两个类型: 编译时类型和运行时类型。
编译时类型由声明该变量时使用的类型决定, 运行时类型由实际赋给该变量的对象决定。 简称: 编译时, 看左边;运行时, 看右边。
若编译时类型和运行时类型不一致, 就出现了对象的多态性(Polymorphism)
多态情况下, “看左边” : 看的是父类的引用(父类中不具备子类特有的方法)“看右边” : 看的是子类的对象(实际运行的是子类重写父类的方法)
子类可看做是特殊的父类, 所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法,只能访问变量所在类具有的属性和方法。 此时调用对象的某个方法(变量所在类也有该方法),但实际上执行的是子类重写的方法—动态绑定 如果是private方法,static方法,final方法或者构造器,使用静态绑定。 一般都将向上转型叫为多态。
总结
多态作用: | 提高了代码的通用性,常称作接口重用 |
---|---|
前提: | 需要存在继承或者实现关系 有方法的重写 父类变量指向子类对象 |
成员方法: | 编译时:要查看引用变量所声明的类中是否有所调用的方法。 运行时: 调用实际new的对象所属的类中的重写方法 |
成员变量: | 不具备多态性,只看引用变量所声明的类 |
package stu.zdkk.exer1;
public class FieldMethodTest {
public static void main(String[] args) {
Sub s = new Sub();
System.out.println(s.count);//20
s.display(); //20
Base b = s;
//引用数据类型比较的是地址值
System.out.println(b == s);//true
//属性不具有多态性
System.out.println(b.count);//10
//方法才具有多态性
b.display(); //20
}
}
class Base {
int count = 10;
public void display() {
System.out.println(this.count);
}
}
class Sub extends Base {
int count = 20;
public void display() {
System.out.println(this.count);
}
}
若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。
对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
instanceof关键字
向下转型时使用,由于多态性,父类的引用不能调用子类特有的属性和方法,必须向下转型才能调用。
为了确保转型正确,使用instanceof关键字
格式:
x instanceof A:检验x是否为类A的对象,返回值为boolean型。
要求x变量所属的类与类A必须是子类和父类的关系,否则编译错误。
如果x指向的对象属于类A或类A的子类B, x instanceof A值也为true。
如果x指向的对象不属于类A的子类(即x是A类的父类),返回false
使用instanceof编译没报错只能说明x变量所属的类(假设是B)与A类具有子父类关系,而instanceof的执行结果取决于x指向的对象所属的类(假设是C),因为多态性的存在,C是B及B的子类,又因为编译正确的原因,A也是B及B的子类,instanceof的执行结果在于C是否是A以及A的子类,如果是,返回true,否则返回false。这里的“是”有两种情况,要么是C是A,要么C是A的子类(这样就又是多态,这说明instanceof只能检验C是否为A类及其子类,而不能确保C就是A);这里的“否”也有两种情况,要么C是A的父类导致的false,要么C与A根本就没有子父类关系导致的false。
instanceof 运算符类似于:a instanceof B。
换句话说,instanceof 运算符在如下条件下返回 true:
1) 变量 a 存储 B 类型对象的引用
2) 变量 a 存储类继承 B 的对象的引用
3) 变量 a 存储实现接口 B 的对象的引用
否则,instanceof 运算符会返回 false。
对象类型转换 (Casting )
基本数据类型的Casting:
boolean不参与
自动类型转换:小的数据类型可以自动转换成大的数据类型
如long g=20; double d=12.0f
强制类型转换: 可以把大的数据类型强制转换(casting)成小的数据类型
如 float f=(float)12.0; int a=(int)1200L
引用数据类型的Casting:
这里引用数据类型指的是引用型变量,而不是指向的对象
对Java对象的强制类型转换称为造型
从子类到父类的类型转换可以自动进行
从父类到子类的类型转换必须通过造型(强制类型转换)实现 向下转型
理论上是这样,但实际能不能转不一定,可能有运行时异常。例如一个Person类变量p指向Man类对象,当父类是Person,子类是Woman时,理论上能进行强制类型转换,但运行就会报错,因为p指向了Man类对象,而Man与Woman不具有子父类关系
无继承关系的引用类型间的转换是非法的,即编译就不能过
只能将对象的引用向下转型成对象所在的类及其子类
在造型前可以使用instanceof操作符测试一个对象的类型
//man--->Person-->Object
//woman-->Person-->Object
//编译通过,运行不通过
//示例1
Person p1 = new Woman();
Man m1 = (Man)p1;
//示例2
Person p2 = new Person();
Man m2 = (Man)p2;
//示例3
Object o1 = new Date();
String str = (String)o1;
//编译通过,运行也通过
Person p3 = new Man();
Man m3 = (Man)p3;
Object o2 = new Man();
Person p4 = (Person)o2; //注意这种转换,还是一种多态
//编译就不通过
Man m4 = new Woman();
String str2 = new Date();
Object类
Object类是所有Java类的根父类。
如果在类的声明中未使用extends关键字指明其父类, 则默认父类为java.lang.Object类
先写一部分,没写的之后学
NO. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Object() | 构造 | 空参构造器,造的所有类都会调用该方法 |
2 | public boolean equals(Object obj) | 普通 | 对象比较 |
3 | public int hashCode() | 普通 | 取得Hash码 |
4 | public String toString() | 普通 | 对象打印时调用 |
==操作符与equals方法比较
==操作符
基本类型比较值:只要两个变量的值相等, 即为true。
boolean类型与其他基本类型没法比较,其他基本类型之间都可以进行比较(会进行自动类型转换)
引用类型比较引用(是否指向同一个对象):只有指向同一个对象时(地址相同), ==才返回true。
用“==”进行比较时, 符号两边的数据类型必须兼容(可自动转换的基本数据类型除外), 否则编译出错 即两个不同类型(不存在子父类关系)的引用变量连比较的权力都没有,编译就会报错
equals方法
所有类都继承了Object, 也就获得了equals()方法。 还可以重写。
public boolean equals(Object obj) {
return (this == obj);
}
只能比较引用类型, 其作用与“==”相同,比较是否指向同一个对象。
格式:obj1.equals(obj2)
特例:当用equals()方法进行比较时, 对类File、 String、 Date及包装类(Wrapper Class) 来说, 是比较类型及内容而不考虑引用的是否是同一个对象;
原因:在这些类中重写了Object类的equals()方法。
当自定义使用equals()时, 可以重写。 用于比较两个对象的“内容” 是否都相等
重写equals()方法的原则 :
- 对称性: 如果x.equals(y)返回是“ true” , 那么y.equals(x)也应该返回是“true” 。
- 自反性: x.equals(x)必须返回是“true” 。
- 传递性: 如果x.equals(y)返回是“true” , 而且y.equals(z)返回是“true” ,那么z.equals(x)也应该返回是“true” 。
- 一致性: 如果x.equals(y)返回是“true” , 只要x和y内容一直不变, 不管你重复x.equals(y)多少次, 返回都是“true” 。
- 任何情况下, x.equals(null), 永远返回是“false” ;x.equals(和x不同类型的对象)永远返回是“false” 。
总结
==是运算符,而equals是方法
==既可以比较两个基本数据类型值,也能比较两个引用类型对象的地址;而equlas只能比较两个引用数据类型变量指向的对象地址是否相同
在equals没有被重写的情况下,引用数据类型之间的比较==和equlas方法结果相同,都是比较对象的地址,但是如果equals被子类重写就不一定相同了
toString方法
当我们输出一个对象的引用时,实际上调用了该对象的toString方法。
Object类toString方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
子类也可以重写toString方法输出自己想输出的东西。
其他关键字
this关键字
在Java中, this关键字比较难理解,它的作用和其词义很接近。
它在方法内部使用,即这个方法所属对象的引用;
它在构造器内部使用,表示该构造器正在初始化的对象。
this 可以调用类的属性、方法和构造器
什么时候使用this关键字呢?
1. 当在方法内需要用到调用该方法的对象时,就用this。
具体的:我们可以用this来区分属性和局部变量。
比如: this.name = name;
2.可以在类的构造器中使用”this(形参列表)”的方式,调用本类中重载的其他的构造器!
明确:构造器中不能通过”this(形参列表)”的方式调用自身构造器
如果一个类中声明了n个构造器,则最多 有 n - 1个构造器中使用了”this(形参列表)”
“this(形参列表)”必须声明在类的构造器的首行!
在类的一个构造器中,最多只能声明一个”this(形参列表)”
static关键字
静态的
我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下, 某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
如果想让一个类的所有实例共享数据,就用类变量!
使用范围:
在Java类中, 可用Static修饰属性、 方法、 代码块、 内部类
被修饰后的成员具备以下特点:
随着类的加载而加载,只加载一次
优先于对象存在
修饰的成员,被所有对象所共享
访问权限允许时,可不创建对象,直接被类调用
见名知意,便于修改
类属性作为该类各个对象之间共享的变量。 在设计类时,分析哪些属性不因对象的不同而改变,将这些属性设置为类属性。
按是否使用static修饰,成员变量分为:
- 静态属性 当我们创建类的多个对象,多个对象共享同一个静态变量。当通过一个对象修改它的静态属性时,会导致其他对象调用的静态变量也被修改。
随着类的加载而加载,可以通过 类.静态变量
的方式直接调用
静态变量加载早于对象的创建
由于类只加载一次,则静态变量在内存中也只有一份:存在于方法区的静态域
- 实例属性 创建多个对象,每个对象都独立拥有一套类中的非静态属性。当修改其中一个对象的非静态属性时不会影响其他对象中同样属性值的修改
随着对象的创建而生成
类变量 | 实例变量 | |
---|---|---|
类 | yes | no |
对象 | yes | yes |
如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。
- 随之类的加载而加载,在没有对象的实例时,可以用类名.方法名()的形式访问由static修饰的类方法。
- 在static方法内部只能访问类的static修饰的属性或方法, 不能访问类的非static的结构。而在非静态方法中都能调用
- 因为不需要实例就可以访问static方法,因此static方法内部不能有this。 (也不能有super ? YES!)
- static修饰的方法不能被子类成员方法重写,即子类不能有同名同参数的成员方法,但是子类能重写父类的同名同参数的静态方法。
- 在子类没有与父类静态方法同名同参数的静态方法时:父类的静态方法可以通过子类或子类的对象去调用,这满足继承性。但是子类如果重写了父类静态方法同名同参数的静态方法后通过子类.静态方法调用的就不再是父类的方法了。
- 静态方法满足继承,也能被重写,但是不满足多态,即只与变量的类型有关,而不是与变量指向的对象有关。
static修饰代码块:类及类的成员下的代码块部分内容
什么使用用static呢?
属性可以被多个对象所共享,不会随对象不同而不同的情况下
类中的常量
操作静态属性的方法通常设置为静态,例如getter和setter
工具类中的方法,比如Math,Arrays,Collections。
package stu.zdkk.exer;
public class CircleTest {
public static void main(String[] args) {
Circle c1 = new Circle(2);
Circle c2 = new Circle(3);
System.out.println("c1的Id =" + c1.getId());
System.out.println("c1的面积 =" + c1.findArea());
System.out.println("c2的Id = " + c2.getId());
System.out.println("c2的面积 =" + c2.findArea());
System.out.println("圆的个数 = " + Circle.getTotal());
}
}
class Circle{
private double radius;
private int id;
private static int total;
private static int init = 1001;
public Circle()
{
id = init++;
total++;
}
public Circle(double radius)
{
this();
this.radius = radius;
}
public double getRadius() {
return radius;
}
public static int getTotal() {
return total;
}
public void setRadius(double radius) {
this.radius = radius;
}
public double findArea()
{
return Math.PI * this.radius * this.radius;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
final关键字
在Java中声明类、 变量和方法时, 可使用关键字final来修饰,表示“最终的”
final标记的类不能被继承。 提高安全性, 提高程序的可读性。
中国古代,什么人不能有后代,就可以被final声明, 称为“太监类”!
- String类、 System类、 StringBuffer类
final标记的方法不能被子类重写。
- 比如: Object类中的getClass()。
final标记的变量(成员变量或局部变量)即称为常量。 名称大写(遵循规范,不是强制), 必须赋值且只能被赋值一次
对于基本数据类型来说,不可变指变量的值不可变 对于引用数据类型来说,不可变指变量中存的地址不可变
- 成员变量
final标记的成员变量必须在声明时显式初始化或在每个构造器中或代码块中显式赋值, 然后才能使用。
成员变量声明时赋值:此变量为各个实例都有的一个相同的值
成员变量代码块中赋值:此变量为各个实例都有的一个相同的值
构造器中赋值:每个实例都可能有不同的值
static final:全局常量/类常量:声明时显式初始化或静态代码块中赋值,无法在构造器中赋值。
- 局部变量
必须且只能显示赋值后再使用,不能二次赋值。
abstract关键字
抽象的
超类声明一个方法但不提供实现,该方法的实现由子类提供。这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽象类。
用abstract关键字来修饰一个类, 这个类叫做抽象类。
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
抽象类的应用
例如:在航运公司系统中, Vehicle类需要定义两个方法分别计算运输工具的燃料效率和行驶距离。 但是Vehicle类的两个子类卡车(Truck)和驳船(RiverBarge)的燃料效率和行驶距离的计算方法完全不同。 Vehicle类不能提供计算方法,但子类可以。
public abstract class Vehicle{
public abstract double calcFuelEfficiency(); //计算燃料效率的抽象方法
public abstract double calcTripDistance(); //计算行驶距离的抽象方法
}
public class Truck extends Vehicle{
public double calcFuelEfficiency( ) { //写出计算卡车的燃料效率的具体方法 }
public double calcTripDistance( ) { //写出计算卡车行驶距离的具体方法 }
}
public class RiverBarge extends Vehicle{
public double calcFuelEfficiency( ) { //写出计算驳船的燃料效率的具体方法 }
public double calcTripDistance( ) { //写出计算驳船行驶距离的具体方法}
}
注意:抽象类不能实例化 new Vihicle()是非法的 抽象类也有构造器,只是不能new对象 抽象类一般有子类,不然就没用了
用abstract来修饰一个方法, 该方法叫做抽象方法。
抽象方法:只有方法的声明,没有方法的实现。以分号结束:
例如: public abstract void talk();
说明:
含有抽象方法的类必须被声明为抽象类
抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写(满足重写的规则)父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。但是抽象类中可以没有抽象方法。
不能用abstract修饰变量、代码块、构造器;
不能用abstract修饰不能被重写的方法(私有方法、静态方法/类方法、 final的方法)、 final的类。
抽象类的匿名子类
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Student(); //类的有名对象
p1.breath();
p1.eat();
p1.walk();
System.out.println("***************************");
new Student().breath();//类的匿名对象
System.out.println("***************************");
Person p2 = new Person() { //抽象类的匿名子类的有名对象
@Override
public void eat() {
// TODO Auto-generated method stub
System.out.println("老师吃食堂");
}
@Override
public void walk() {
// TODO Auto-generated method stub
System.out.println("老师走路");
}
@Override
public void breath() {
// TODO Auto-generated method stub
System.out.println("老师呼吸");
}
};
p2.breath();
p2.eat();
p2.walk();
System.out.println("********************************");
new Creature() {
public void breath()
{
System.out.println("鱼用鳃呼吸");
}
}.breath();//抽象类的匿名子类的匿名对象
}
}
//抽象类,含抽象方法
abstract class Creature{
public abstract void breath();
}
//抽象类的子类,含抽象方法,仍然是抽象类
abstract class Person extends Creature{
private String name;
private int age;
public abstract void eat();
public abstract void walk();
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = 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{
@Override
public void eat() {
// TODO Auto-generated method stub
System.out.println("学生吃有营养的食物");
}
@Override
public void walk() {
// TODO Auto-generated method stub
System.out.println("学生每天得走路上学");
}
@Override
public void breath() {
// TODO Auto-generated method stub
System.out.println("学生呼吸新鲜的没有雾霾的空气");
}
}
interface
抽象方法与接口的不同之处
一方面, 有时必须从几个类中派生出一个子类, 继承它们所有的属性和方法。 但是, Java不支持多重继承。 有了接口, 就可以得到多重继承的效果。 另一方面, 有时必须从几个类中抽取出一些共同的行为特征,而它们之间又
没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、 MP3机、手机、数码相机、移动硬盘等都支持USB连接。
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。 继承是一个”是不是”的关系,而接口实现则是 “能不能”的关系.
接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
接口(interface)是抽象方法和全局常量定义的集合(Java 1.7之前)。
Java1.8之后还能定义静态方法,默认方法
接口的特点:
- 用interface来定义。权限修饰符与类一致,只有public和不写两种。
- 接口中的所有成员变量都默认是由
public static final
修饰的(不写也是)。必须显示初始化。 - 接口中的所有抽象方法都默认是由
public abstract
修饰的,除非显示写了static或default。 - 接口中没有构造器。意味着接口不能实例化(即不能被new)。
- 接口采用多继承机制。
定义Java类的语法格式: 先写extends,后写implements[public] class SubClass extends SuperClass implements InterfaceA,InterfaceB{}
一个类可以实现多个接口(打破了单继承的局限), 接口也可以继承其它多个接口。 [public] [abstract] interface SubInterface extends SuperInterface1, SuperInterface2{}
接口也能用abstract,但是通常都不会写。
实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
接口的主要用途就是被实现类实现。 (面向接口编程)
与继承关系类似,接口与实现类之间存在多态性
接口和类是并列关系, 或者可以理解为一种特殊的类。 从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前), 而没有变量和方法的实现。
Java 8中关于接口的改进 :
Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
静态方法和默认方法也都是public的,不管写或不写都是。(JDK9后有所变化,引入了private关键字)
静态方法: 使用 static 关键字修饰。 可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。
完全不同于类中的静态方法。父类的静态方法可以通过子类或子类的对象去调用。而实现类是完全看不到接口中的静态方法的,子接口也不能看到父接口中的静态方法,只能通过接口.静态方法来调用。 总结:接口的静态方法只能自己用,实现类或者子接口都没有该静态方法,自然也可以写同名同参数的方法。
默认方法: 默认方法使用 default 关键字修饰。可以通过实现类对象来调用。有点类似于从父类中继承的成员方法,可以被实现类重写且满足多态性,我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如: java 8 API中对Collection、 List、 Comparator等接口提供了丰富的默认方法。
如果实现类重写了父接口的默认方法,子类中想调用父接口的被重写的默认方法时怎么办? 答:类似于子类调用父类被重写方法
super.父类被重写方法
,想在子类中调用父接口被重写默认方法,用父接口名.super.被重写默认方法名
若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现: 接口冲突。 解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突;覆盖不了(例如返回值不兼容)就只能删除一个接口了。 若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守: 类优先原则。 接口中具有相同名称和参数的默认方法会被忽略 但是对于属性来说就没有类优先原则。
No. | 区别点 | 抽象类 | 接口 |
---|---|---|---|
1 | 定义 | 包含抽象方法的类 | 主要是抽象方法和全局常量的集合 |
2 | 组成 | 构造方法、抽象方法、普通方法、 常量、变量 |
常量、抽象方法、 (jdk8.0:默认方法、静态方法) |
3 | 使用 | 子类继承抽象类(extends) | 子类实现接口(implements) |
4 | 关系 | 抽象类只能继承一个类,但能实现多个接口 | 接口不能继承抽象类,但允许继承多个接口 |
5 | 常见设计模式 | 模板方法 | 简单工厂、工厂方法、代理模式 |
6 | 对象 | 都通过对象的多态性产生实例化对象 | |
7 | 局限 | 抽象类有单继承的局限 | 接口没有此局限 |
8 | 实际 | 作为一个模板 | 是作为一个标准或是表示一种能力 |
9 | 选择 | 如果抽象类和接口都可以使用的话,优先使用接口,因为避免单继承的局限 |
在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。
补充
JavaBean
JavaBean是一种Java语言写成的可重用组件。所谓javaBean,是指符合如下标准的Java类:
类是公共的
有一个无参的公共的构造器
有属性,且有对应的get、 set方法