Java面向对象三大特性

JAVA基础–面向对象三大特性

参考:https://www.cnblogs.com/hysum/p/7100874.html#_labelTop

1 封装

1.1 封装的概念

将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。

好处:

  • 只能通过规定的方法访问数据。

  • 隐藏类的实现细节,方便修改和实现。

1.2 封装的使用

封装实现的步骤:

在这里插入图片描述

**注意:**对封装的属性不一定要通过get/set方法,其他方法也可以对封装的属性进行操作。当然最好使用get/set方法,比较标准。


1.2.1 访问修饰符

在这里插入图片描述

从表格可以看出从上到下封装性越来越差。

1.2.2 this关键字
  1. this关键字代表当前对象
  • this.属性 操作当前对象的属性

  • this.方法 调用当前对象的方法。

  1. 封装对象的属性的时候,经常会使用this关键字。

  2. 当getter和setter函数参数名和成员函数名重合的时候,可以使用this区别。如:

public void setNumber(float number){
    this.number = number;
}
1.2.3 Java 中的内部类

内部类( Inner Class )就是定义在另外一个类里面的类。与之对应,包含内部类的类被称为外部类。

那么问题来了:那为什么要将一个类定义在另一个类里面呢?清清爽爽的独立的一个类多好啊!!

答:内部类的主要作用如下:

1. 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。

2. 内部类的方法可以直接访问外部类的所有数据,包括私有的数据

3. 内部类所实现的功能使用外部类同样可以实现,只是有时使用内部类更方便。

内部类可分为以下几种:

  • 成员内部类

  • 静态内部类

  • 方法内部类

  • 匿名内部类

各个内部类的具体使用请转移到另一篇随文:http://www.cnblogs.com/hysum/p/7101974.html

2 继承

2.1 继承的概念

继承是类与类的一种关系,是一种“is a”的关系。比如“狗”继承“动物”,这里动物类是狗类的父类或者基类,狗类是动物类的子类或者派生类。如下图所示:

在这里插入图片描述

**注意:**Java中的继承是单继承,即一个类只有一个父类。

好处:

子类拥有父类的所有属性和方法(除了private修饰的属性不能拥有)从而实现了实现代码的复用;

2.2 继承的使用

只要在子类加上extends关键字继承相应的父类就可以了:

//class 子类 extends 父类
class Dog extends Animal{
    ······
} 

2.2.1 方法的重写

子类如果对继承的父类的方法不满意(不适合),可以自己编写继承的方法,这种方式就称为方法的重写。当调用方法时会优先调用子类的方法。

注意:

  • 返回值类型

  • 方法名

  • 类型及个数

都要与父类继承的方法相同,才叫方法的重写。

重载和重写的区别:

  • 方法重载:在同一个类中处理不同数据的多个相同方法名的多态手段。

  • 方法重写:相对继承而言,子类中对父类已经存在的方法进行区别化的修改。


2.2.1 继承的初始化顺序

1、初始化父类再初始化子类

2、先执行初始化对象中属性,再执行构造方法中的初始化。

基于上面两点,我们就知道实例化一个子类,java程序的执行顺序是:

父类对象属性初始化---->父类对象构造方法---->子类对象属性初始化—>子类对象构造方法

下面有个形象的图:

在这里插入图片描述


2.2.3 final关键字

使用final关键字做标识有“最终的”含义。

1. final 修饰类,则该类不允许被继承。

2. final 修饰方法,则该方法不允许被覆盖(重写)。

3. final 修饰属性,则该类的该属性不会进行隐式的初始化,所以 该final 属性的初始化属性必须有值,或在构造方法中赋值(但只能选其一,且必须选其一,因为没有默认值!),且初始化之后就不能改了,只能赋值一次。

4. final 修饰变量,则该变量的值只能赋一次值,在声明变量的时候才能赋值,即变为常量。


2.2.4 super关键字

在对象的内部使用,可以代表父类对象。

1、访问父类的属性:super.age

2、访问父类的方法:super.eat()

super的应用:

首先我们知道子类的构造的过程当中必须调用父类的构造方法。其实这个过程已经隐式地使用了我们的super关键字。这是因为如果子类的构造方法中没有显示调用父类的构造方法,则系统默认调用父类无参的构造方法。那么如果自己用super关键字在子类里调用父类的构造方法,则必须在子类的构造方法中的第一行。


2.2.5 Object类

Object类是所有类的父类,如果一个类没有使用extends关键字明确标识继承另一个类,那么这个类默认继承Object类。 Object类中的方法,适合所有子类!!!

Object类中主要的方法:

1、toString()

a. 在Object类里面定义toString()方法的时候返回的对象的哈希code码(对象地址字符串)。

我们可以发现,如果我们直接用System.out.print(对象)输出一个对象,则运行结果输出的是对象的对象地址字符串,也称为哈希code码。如:

com.imooc.Telphone@60ec2ea8

哈希码是通过哈希算法生成的一个字符串,它是用来唯一区分我们对象的地址码,就像我们的身份证一样。

b. 可以通过重写toString()方法表示出对象的属性。

如果我们希望输出一个对象的时候,不是它的哈希码,而是它的各个属性值,那我们可以通过重写toString()方法表示出对象的属性。

2、equals()

a. equals() 返回值是布尔类型。

b. 默认的情况下,比较的是对象的引用是否指向同一块内存地址-------对象实例化时,即给对象分配内存空间,该内存空间的地址就是内存地址。使用方法如:dog.equals(dog2);

c. 如果是两个对象,但想判断两个对象的属性是否相同,则重写equals()方法。

以Dog类为例,重写后的equals()方法如下(当然你可以根据自己想比较的属性来重写,这里我以age属性是否相同来重写equals()方法):

public boolean equals(Object obj){
    if(this == obj)
        return true;
    if(obj == null)
        return false;
    if(getClass() != obj.getClass())
        return false;
    Dog other = (Dog)obj;
    if(age != other.age)
        return false;
    return true;
}

上面有四个判断,它们的含义分别是:

1. 判断地址是否相同----if (this == obj),相同则返回true

2. 判断对象是否为空----if (obj == null),为空则返回false

3. getClass()可以得到类对象,判断类型是否一样-----if (getClass() != obj.getClass()),不一样则返回false

4. 判断属性值是否一样----if (age != other.age),不一样返回false

5. 如果地址相同,对象不为空,类型一样,属性值一样则返回true

这里要注意的是,理解obj.getClass()得到的类对象和类的对象的区别,以下用图形表示:

在这里插入图片描述

可以看到,对于类对象我们关心它属于哪个类,拥有什么属性和方法,比如我和你都是属于“人”这个类对象;而类的对象则是一个类的实例化的具体的一个对象。比如我和你是两个不同的人。

3 多态

3.1 多态的概念

面向对象的最后一个特性就是多态,那么什么是多态呢?多态就是对象的多种形态。

java里的多态主要表现在两个方面:

3.1.1 引用多态

  • 父类的引用可以指向本类的对象;

  • 父类的引用可以指向子类的对象;

这两句话是什么意思呢,让我们用代码来体验一下,首先我们创建一个父类Animal和一个子类Dog,在主函数里如下所示:

public static void main(String[] args){
    Animal obj1 = new Animal();//父类的引用指向本类
    Animal obg2 = new Dog();//父类的引用指向子类
} 

**注意:**我们不能使用一个子类的引用来指向父类的对象,如:

Dog obj3 = new Animal();

这里我们必须深刻理解引用多态的意义,才能更好记忆这种多态的特性。为什么子类的引用不能用来指向父类的对象呢?我在这里通俗给大家讲解一下:就以上面的例子来说,我们能说“狗是一种动物”,但是不能说“动物是一种狗”,狗和动物是父类和子类的继承关系,它们的从属是不能颠倒的。当父类的引用指向子类的对象时,该对象将只是看成一种特殊的父类(里面有重写的方法和属性),反之,一个子类的引用来指向父类的对象是不可行的!!

3.1.2 方法多态

根据上述创建的两个对象:本类对象和子类对象,同样都是父类的引用,当我们指向不同的对象时,它们调用的方法也是多态的。

  • 创建本类对象时,调用的方法为本类方法;

  • 创建子类对象时,调用的方法为子类重写的方法或者继承的方法;

使用多态的时候要注意:如果我们在子类中编写一个独有的方法(没有继承父类的方法),此时就不能通过父类的引用创建的子类对象来调用该方法!!!

注意: 继承是多态的基础。


3.2 多态的使用

3.2.1 引用类型转换

了解了多态的含义后,我们在日常使用多态的特性时经常需要进行引用类型转换。

引用类型转换:

1. 向上类型转换(隐式/自动类型转换),是小类型转换到大类型。

就以上述的父类Animal和一个子类Dog来说明,当父类的引用可以指向子类的对象时,就是向上类型转换。如:

Dog dog = new Dog();
Animal animal = dog;//自动类型提升 向上类型转换

2. 向下类型转换(强制类型转换),是大类型转换到小类型(有风险,可能出现数据溢出)。

将上述代码再加上一行,我们再次将父类转换为子类引用,那么会出现错误,编译器不允许我们直接这么做**,虽然我们知道这个父类引用指向的就是子类对象,但是编译器认为这种转换是存在风险的。**如:

Dog dog = new Dog();
Animal animal = dog;//自动类型提升 向上类型转换
Dog dog2 = animal;

那么我们该怎么解决这个问题呢,我们可以在animal前加上(Dog)来强制类型转换。如:

Dog dog2 = (Dog)animal;//向下类型转换 强制类型转换

但是如果父类引用没有指向该子类的对象,则不能向下类型转换,虽然编译器不会报错,但是运行的时候程序会出错,如:

Dog dog2 = (Dog)new Animal();

其实这就是上面所说的子类的引用指向父类的对象,而强制转换类型也不能转换!!

还有一种情况是父类的引用指向其他子类的对象,则不能通过强制转为该子类的对象。如:

Dog dog = new Dog();
Animal animal = dog;//向上类型转换 自动类型转换
Dog dog2 = (Dog)animal;
Cat cat = (Cat)animal;//1. 编译时 Cat类型 2. 运行时Dog类型

这是因为我们在编译的时候进行了强制类型转换,编译时的类型是我们强制转换的类型,所以编译器不会报错,而当我们运行的时候,程序给animal开辟的是Dog类型的内存空间,这与Cat类型内存空间不匹配,所以无法正常转换。这两种情况出错的本质是一样的,所以我们在使用强制类型转换的时候要特别注意这两种错误!!下面有个更安全的方式来实现向下类型转换。。。。

**3. instanceof运算符,**来解决引用对象的类型,避免类型转换的安全性问题。

instanceof是Java的一个二元操作符,和==,>,<是同一类东东。由于它是由字母组成的,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。

我们来使用instanceof运算符来规避上面的错误,代码修改如下:

if (animal instanceof Cat){
    Cat cat = (Cat)animal;//1. 编译时 Cat类型 2. 运行时Dog类型
}

利用if语句和instanceof运算符来判断两个对象的类型是否一致。

**补充说明:**在比较一个对象是否和另一个对象属于同一个类实例的时候,我们通常可以采用instanceof和getClass两种方法通过两者是否相等来判断,但是两者在判断上面是有差别的。Instanceof进行类型检查规则是:你属于该类吗?或者你属于该类的派生类吗?而通过getClass获得类型信息采用==来进行检查是否相等的操作是严格的判断,不会存在继承方面的考虑

**总结:**在写程序的时候,如果要进行类型转换,我们最好使用instanceof运算符来判断它左边的对象是否是它右边的类的实例,再进行强制转换。


3.2.2 抽象类

定义:抽象类前使用abstract关键字修饰,则该类为抽象类。

使用抽象类要注意以下几点:

1. 抽象类是约束子类必须有什么方法,而并不关注子类如何实现这些方法。

2. 抽象类应用场景:

a. 在某些情况下,某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现这些方法(可实现动态多态)。

b. 从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为子类的模板,从而避免子类设计的随意性。

  1. 抽象类定义抽象方法,只有声明,不需要实现。抽象方法没有方法体以分号结束,抽象方法必须用abstract关键字来修饰。如:
public abstract class Telephone{
	public abstract void call();//抽象方法,方法以分号结束,只有声明,不需实现
}

4、包含抽象方法的类是抽象类。抽象类中可以包含普通的方法,也可以没有抽象方法。如:

public abstract class Telephone{
    //这个抽象类可以没有抽象方法
    public void message(){
        System.out.println("我是抽象类的普通方法")
    }//抽象类中包含普通的方法
}

5、抽象类不能直接创建,可以定义引用变量来指向子类对象,来实现抽象方法。以上述的Telephone抽象类为例:

1 public abstract class Telephone {
2     public abstract void call();//抽象方法,方法体以分号结束,只有声明,不需要实现
3     public void message(){
4         System.out.println("我是抽象类的普通方法");
5     }//抽象类中包含普通的方法
6 }
1 public class Phone extends Telephone {
2 
3     public void call() {//继承抽象类的子类必须重写抽象方法
4         // TODO Auto-generated method stub
5         System.out.println("我重写了抽象类的方法");
6     }
7     
8 }

以上是Telephone抽象类和子类Phone的定义,下面我们看main函数里:

public class train{
    public static void main(String[] argus){
        Telephone t = new Telephone();//抽象类不能直接创建
        Telephone p = new Phone();//定义引用变量来指向子类对象
        p.cell();
        p.message()}
}

​ 运行结果(排错之后):

我重写了抽象类的方法
我是抽象类的普通方法

3.2.3 接口

1、概念

接口可以理解为一种特殊的类,由全局常量和公共的抽象方法所组成。也可理解为一个特殊的抽象类,因为它含有抽象方法。

如果说类是一种具体实现体,而接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部数据,也不关心这些类里方法的实现细节,它只规定这些类里必须提供的某些方法。(这里与抽象类相似)

2.接口定义的基本语法

修饰符 interface 接口名 [extends父接口1,2....](多继承){
	0…n常量 (public static final)                                          
	0…n 抽象方法(public abstract)                                      
}  

其中[ ]里的内容表示可选项,可以写也可以不写;接口中的属性都是常量,即使定义时不添加public static final 修饰符,系统也会自动加上;接口中的方法都是抽象方法,即使定义时不添加public abstract修饰符,系统也会自动加上。

3.使用接口

一个类可以实现一个或多个接口,实现接口使用implements关键字。java中一个类只能继承一个父类,是不够灵活的,通过实现多个接口可以补充。

继承父类实现接口的语法为:

[修饰符] class 类名 extends 父类 implements 接口1,接口2...{
	类体部分//如果继承了抽象类,需要实现继承的抽象方法;要实现接口中的抽象方法
}

注意:如果要继承父类,继承父类必须在实现接口之前,即extends关键字必须在implements关键字前

补充说明:通常我们在命名一个接口时,经常以I开头,用来区分普通的类。如:IPlayGame

以下我们来补充在上述抽象类中的例子,我们之前已经定义了一个抽象类Telephone和子类Phone,这里我们再创建一个IPlayGame的接口,然后在原来定义的两个类稍作修改,代码如下:

1 public interface IPlayGame {
2     public void paly();//abstract 关键字可以省略,系统会自动加上
3     public String name="游戏名字";//static final关键字可以省略,系统会自动加上
4 }
 1 public class Phone extends Telephone implements IPlayGame{
 2 
 3     public void call() {//继承抽象类的子类必须重写抽象方法
 4         // TODO Auto-generated method stub
 5         System.out.println("我重写了抽象类的方法");
 6     }
 7 
 8     @Override
 9     public void paly() {
10         // TODO Auto-generated method stub
11         System.out.println("我重写了接口的方法");
12     }
13     
14 }
 1 public class train {
 2     
 3 
 4     public static void main(String[] args) {
 5         // TODO Auto-generated method stub
 6         IPlayGame i=new Phone();//用接口的引用指向子类的对象
 7         i.paly();//调用接口的方法
 8         System.out.println(i.name);//输出接口的常量
 9     
10         
11 
12     }
13 }

运行结果:

我重写了接口的方法
游戏名字

4.接口和匿名内部类配合使用

接口在使用过程中还经常和匿名内部类配合使用。匿名内部类就是没有没名字的内部类,多用于关注实现而不关注实现类的名称。

语法格式:

1 Interface i =new interface(){
2     Public void method{
3         System.out.println(“利用匿名内部类实现接口1);
4     }
5 };
6 i.method();

还有一种写法:(直接把方法的调用写在匿名内部类的最后)

1 Interface i =new interface(){
2     Public void method{
3         System.out.println(“利用匿名内部类实现接口1);
4     }
5 }.method();
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值