目录
前言
前文回顾:前文中详细谈到如何构造一个类(成员变量、成员方法、构造方法)、访问修饰符、包等内容。
面向对象变成的三大特性:封装、继承、多态。上一篇文章详细讲解的内容主要体现三大特性中的“封装”,这一篇文章将详细谈谈继承和多态。
封装
封装:把属性和方法封装在一起,从而得到一个类 ,通过授权才能对数据进行操作。
封装的实现:
- 将属性私有化(不能直接修改属性)
- (public)set方法用于对属性进行判断并赋值
- (public)get方法获取属性的值
private 数据类型 属性;//属性(Xxx)
public void setXxx(数据类型 参数名) {//Xxx表示某个属性
//加入数据验证的业务逻辑
this.属性 = 参数名;
}
public 数据类型 getXxx() {//权限判断,Xxx是某个属性
return xx;
}
继承:
基本介绍:利用继承,定义一个共有属性的一般类,根据该一般类再定义具有特殊属性的子类,子类继承一般类的属性和行为(方法),并根据需要增加的新的属性和行为。
由继承得到的类称为子类(派生类),被继承的类称为父类(超类,基类)。
注:Java不支持多重继承,即子类只能有一个父类。
基本语法:class 子类 extends 父类{}
本质:子类被创建后,会建立查找关系;
注意事项:
1. 子类继承了父类所有的属性和方法 ,非私有属性和方法可以直接访问,私有属性需要通过公共的方法去间接访问。
2. 当创建子类对象时,默认情况总会去调用父类的无参构造器。父类没有无参构造器需要在子类中super指定调用父类的构造器完成父类初始化工作(子类必须调用父类的构造器),否则编译器会报错
3. Java所有类都是Object类的子类。父类的调用不限于直接父类,将一直追溯到Object类。
4. 子类最多只能继承一个父类(直接继承)即Java中是单继承机制 。子类和父类直接必须满足is-a的逻辑关系【扩展:让A类继承B类和C类——>A继承B,B继承C】
5. super在使用时,必须放在构造器第一行(super只能在构造器中使用)
6. super() 和this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
重写overwrite
基本介绍:方法赋给(重写)就是在子类中用一个和父类的某个方法的名称、返回类型、参数一样的方法覆盖父类的方法。
语法:
//父类
class A{
public A(){}
public void say(){
System.out.println("This is A...");
}
}
//子类
class B extends A{
public B(){}
public void say(){//重写
System.out.println("I am B...");
}
}
使用细节:
1. 子类的方法的参数、方法名要和父类方法的参数、方法名完全一样
2. 子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类
3. 子类方法不能缩小父类方法的访问权限【eg:protected在子类后可以改成public,但是不能改成private;public在子类中不能改成protected】
super关键字
基本介绍:代表父类的引用,用于访问父类的属性、方法、构造器
基本语法:
1. 访问父类的(非私有private)属性:super.属性名;
2. 访问父类的(非私有private)方法:super.方法名(参数列表);
3. 访问父类的构造器:super(参数列表),只能放在构造器中的第一句且只出现一次。
class Father{
public int a;
public Father(){}
public Father(int a){
this.a=a;
}
public void say(){
System.out.println("father is saying...");
}
}
class Son extends Father{
public int b;
public Son(){
super();//调用父类的无参构造器
System.out.println(super.a);//调用父类的属性名
}
public Son(int a){
super(a);//调用父类的有参构造器
super.say();//调用父类的方法
}
}
使用细节:
1. 当子类成员(属性、方法)和父类成员 重名时,需要通过super访问父类的成员,不重名super、this、直接访问都一样
2. super可访问间接父类(爷爷类)的成员,多个基类中都有同名的成员,使用super访问遵循就近原则。
final关键字
基本介绍:final可以修饰类、属性、方法和局部变量。
基本语法:
1. final将类声明为final类。final类不能被继承,即不能有子类。
2.final方法:如果将final修饰父类中的一个方法,那么这个方法将不允许被子类重写,不允许子类隐藏可以继承的final方法
2.常量:如果成员变量或局部变量被修饰为final,那么它就是常量。程序要求在声明常量时必须指定常量的值,故使用final修饰变量需要赋初值,并且之后不能修改。
//final类
final class A{
...
}
class B{
//常量
final double PI = 3.1415926;//PI是常量
public double getArea(final double r){
//r = r + 1;//非法,不允许对final变量进行更新操作
return PI * r * r;
}
//final 方法
public final void speak(){
System.out.println("Hello!");
}
}
使用细节:
1. final修饰常量必须赋初值,且不能修改。
2. final不能修饰构造器(构造方法)
3. final和static一起使用效率更高。(底层编译器做了优化处理,不会导致类加载)
4. 包装类(Integer、Double、Float、Boolean等都是final),String也是final类
多态
介绍:方法或对象具有多种形态,多态是建立在封装和继承之上的。
多态的具体体现:重写和重载
对象多态的核心:
1.一个对象的编译类型[=号的左边]和运行类型[=号的右边]可以不一致;
2.编译类型在定义对象时确定,确定后不能更改,运行类型可以改变。【运行类型可以通过getClass()来查看运行类型】
多态的前提:两个对象(类)存在继承关系。
多态的(向上/向下)转型
多态的向上转型:
1.本质:父类的引用指向了子类的对象。
2.语法:父类类型 引用名 = new 子类类型();
3.特点:可以调用父类中的所有成员(需遵守访问权限);不能调用子类中特有成员;最终运行效果看子类的具体实现;
多态的向下转型:
1.语法:子类类型 引用名 = (子类类型)父类引用
2. 使用细节:1)只能强转父类的引用,不能强转父类的对象;2)要求父类的引用必须指向的是当前目标类型的对象;3)向下转型后可以调用子类累心各种所有的成员。
public class test {
public static void main(String[] args) {
//向上转型
A a = new B();
a.say();
System.out.println(a.a);
//a.speak();//error
//System.out.println(a.b);//error
//不能调用子类独有的属性和方法
//向下转型
B b =(B) a;
a.say();
System.out.println(b.a);
b.speak();
System.out.println(b.b);
//子类的属性也可以调用
}
}
class A{
public int a = 1;
public void say(){
System.out.println("saying...");
}
}
class B extends A{
public int b = 2;
public void speak(){
System.out.println("speaking...");
}
}
instanceof运算符
instanceof运算符是Java独有的双目操作符,左边是对象,右面是类(接口),当左边的操作元素是右面的类或其子类所创建的对象时(实现接口的类所创建的对象时),instanceof运算的结果的true,否则是false。
public class test {
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = new C();
System.out.println(a instanceof A);//true
System.out.println(a instanceof B);//false
System.out.println(b instanceof A);//true
}
}
class A{
}
class B extends A{
}
Java的动态绑定机制:
1.调用对象方法时,方法会和该对象的内存地址/运行类型绑定;
2.调用对象属性时,属性没有动态绑定机制,直接使用声明时的属性
public class test {
public static void main(String[] args) {
A a = new B();
//方法会和对象的运行类型B绑定,从B开始向上找
// 找say方法,属性没有动态绑定机制,直接返回A中的a+10
System.out.println(a.say());//11
//方法有动态绑定机制,speak()方法中返回B中的getA()方法+100
//a属性没有绑定内存机制,speak方法中getA()中直接返回B中的a
System.out.println(a.speak());//102
}
}
class A{
public int a = 1;
public int getA() {
return a;
}
public int say(){
return a + 10;
}
public int speak(){
return getA() + 100;
}
}
class B extends A{
public int a = 2;
@Override
public int getA() {
return a;
}
}
抽象类
基本介绍:用于不确定如何实现父类中的某些需要声明的方法,可以将其声明为抽象方法,这个类就是抽象类
基本语法:
1. 抽象类:用abstract关键字修饰一个类
2. 抽象方法:用abstract关键字来修饰一个方法
//抽象类
abstract class Car{
//抽象方法
abstract int min(int x,int y);
}
使用细节:
1. 抽象类不能被实例化,不能用new 标识符创建对象
2. 抽象类不一定包含abstract方法,但abstract方法所在的类必须声明为abstract类
3. 抽象方法只允许声明,不能有主体(不能实现),而且不允许使用private、final和static修饰abstract方法,即abstract方法必须是非private的实例方法(访问权限必须高于private)
4. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
5. 可以使用abstract类声明对象,尽管不能使用new标识符,但是可以成为其子类对象的向上转型对象。
接口
基本介绍:将一些没有实现的方法封装到一起,到某个类要实现的时候,在根据具体情况写出方法。
语法:
interface 接口名{
属性;
方法;
}
class 类名 implements 接口{
属性;
方法;
必须实现的接口的抽象方法;
}
使用细节:
1. 接口不能被实例化。
2. 接口中所有的方法是public方法,接口中抽象方法可以不用abstract修饰,
3.普通类实现接口必须将该接口的所有方法都实现;抽象类实现接口,可以不用实现接口的方法。一个类可以同时实现多个接口。
4. 接口中的属性只能是final,而且是public static final修饰符,接口中属性的访问形式:接口名.属性名;
5.一个接口不能继承其他的类,但是可以继承多个别的接口
6.接口的修饰符只能是public和默认,与类的修饰符一样。
接口体
1.接口体中的抽象方法和常量都是public
2.接口体中的default实例方法:JDK8版本开始允许使用default关键字在接口体中定义default的实例方法,不带default的方法不能实例化
3.接口体中的static方法:JDK9版本之后为了配合接口中的default实例方法,允许在接口提中定义private的方法
interface USB{
public static final int MAX = 100;//等价于 int MAX = 100;
public default int max(int a,int b){ //default 方法
return a>b?a:b;
}
public static void f(){// static 方法
System.out.println("从Java SE 8开始的");
}
}
接口回调
Java语言中,接口回调是指可以吧实现某一接口的类创建的对象的引用赋值给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口方法以及接口提供 的default方法或类重写的default方法。(实际上,当接口变量调用被类实现的接口方法时就是通知相应的对象调用这个方法)
public class test {
public static void main(String[] args) {
A a;//声明接口变量
a = new Car();//接口变量中存放对象的引用
a.show("A");//接口回调
}
}
interface A{
void show(String s);
}
class Car implements A{
@Override
public void show(String s) {
System.out.println("Car is "+s);
}
}
函数接口与Lambda表达式
函数接口:
单接口:接口中有且只有一个abstract方法,从JDK8开始,Java使用Lambda表达式,并将单接口称为函数接口
Lambda表达式
//Lambda表达式就是一个匿名方法(函数)
(int a,int b)->{
return a+b;
}
//也可以写作
(a,b)->{
return a+b;
}
Java中的Lambda表达式主要用于在给单接口变量赋值时(即给函数接口变量赋值时)让代码简洁。
代码演示:
public class test {
public static void main(String[] args) {
A a;//声明接口变量
a = (s)->{ //接口变量中存放Lambda表达式的值
System.out.println("-------");
System.out.println(s);
System.out.println("--------");
};
a.show("My name is A...");//接口回调Lambda表达式实现的接口方法
}
}
interface A{
void show(String s);
}
接口的多态性:
1)多态参数;2)多态数组;3)多态传递
public class test {
public static void main(String[] args) {
//多态参数
USB u1=new IPhone();
u1=new Car();
//多态数组
USB[] usbs=new USB[2];
usbs[0]=new IPhone();
usbs[1]=new Car();
}
}
interface USB{}
class IPhone implements USB{
}
class Car implements USB{
}
//接口多态传递
interface USC extends USB{}
class Bus implements USC{
}
接口VS继承
子类继承了父类就自动拥有父类的功能,子类可以通过实现接口的方法进行扩展。
实现接口是对Java单继承机制的一种补充。接口在一定程度上实现了代码解耦(接口规范性+动态绑定)
继承:解决代码的复用性和可维护性;满足is-a的关系。
接口:灵活。满足like-a的关系。
接口VS抽象类
abstract 类和接口都可以有abstract方法
接口中只可以有常量,不能有变量;而abstract类可以有常量,也可以有变量。
abstract类中也可以有非abstract方法,接口可以有abstract方法、default实例方法,不可以有非default的实例方法。
总结
本篇讲解了面向对象的三大特性(封装、继承、多态),抽象类、接口等内容。继承是一种由已有的类创建新类的机制。子类可以体现多态。由接口产生的多态就是指不同的类在实现同一个接口时可能具有不同的实现方式。