面向对象的基本特征
文章目录
面向对象的基本特征:
1、封装
2、继承
3、多态
抽象 封装 继承 多态
1、面向对象的基本特征之一: 封装
啥是封装
封装指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。
1、好处:
(1)隐藏实现细节,方便使用者使用
(2)安全,可以控制可见范围
隐藏实现细节 方便使用者使用
安全 可以控制可见范围
2、如何实现封装?
通过权限修饰符
public default 缺省的 protected public
面试题:请按照可见范围从小到大(从大到小)列出权限修饰符?
修饰符 | 本类 | 本包 | 其他包的子类 | 任意位置 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
缺省的 package-private(包私有的) 包内部可以访问的
权限修饰符可以修饰什么?
类(类、接口等)、属性、方法、构造器、内部类
类 接口 属性 方法 构造器 内部类
类(外部类):public和缺省
类(外部类)可以用权限修饰符 public 缺省(不写的)来修饰
属性、方法、构造器、内部类 可以用private、缺省、protected、public来修饰的
属性:4种
方法:4种
构造器:4种
内部类:4种
3、通常属性的封装是什么样的?
当然属性的权限修饰符可以是private、缺省、protected、public。但是我们大多数时候,见到的都是private,然后给它们配上get/set方法。
private来修饰属性
示例代码:标准Javabean的写法
public class Student{
//属性私有化
private String name;
private int age;
private boolean marry;
//公共的get/set
public void setName(String n){
name = n;//这里因为还没有学习this等,可能还会优化
//this.name = name;
}
public String getName(){
return name;
}
public void setAge(int a){
age = a;
}
public int getAge(){
return age;
}
public void setMarry(boolean m){
marry = m;
}
public boolean isMarry(){//boolean类型的属性的get方法,习惯使用把get换成is
return marry;
}
}
2、 构造器
构造方法
构造器最大的用处就是在创建对象时执行初始化。当创建一个对象时,系统为这个对象的实例变量进行默认初始化,这种默认的初始化把所有基本类型的实例变量设为0(对数值型实例变量)浮点型 0.0或false(对布尔型实例变量),把所有引用类型的实例变量设为null。如果想改变这种默认的初始化,想让系统创建对象时就为该对象的实例变量显式指定初始值,就可以通过构造器来实现。默认值 初始值
构造器 创建对象时执行初始化
显示指定初始值
1、构造器的作用:
(1)和new一起使用创建对象
//调用无参构造创建对象
类名 对象名 = new 类名();
//调用有参构造创建对象
类名 对象名 = new 类名(实参列表);
(2)可以在创建对象的同时为属性赋值
public class Circle{
private double radius;
public Circle(){
}
public Circle(double r){
radius = r;//为radius赋值
}
}
2、声明构造器的语法格式:
【修饰符】 class 类名{
【修饰符】 类名(){//无参构造
}
【修饰符】 类名(形参列表){//有参构造
}
}
3、构造器的特点:
(1)所有的类都有构造器
(2)如果一个类没有显式/明确的声明一个构造器,那么编译器将会自动添加一个默认的无参构造
有一个默认的无参构造
(3)如果一个类显式/明确的声明了构造器,那么编译器将不再自动添加默认的无参构造,如果需要,那么就需要手动添加
有,不会添加默认的无参构造,需要,可以手动添加。
(4)构造器的名称必须与类名相同
(5)构造器没有返回值类型
(6)构造器可以重载
示例代码:
public class Circle{
private double radius;
public Circle(){
}
public Circle(double r){
radius = r;//为radius赋值
}
}
3、 关键字this
Java提供了一个this关键字,this关键字总是指向调用该方法的对象。
1、this关键字:
意思:当前对象
(1)如果出现在构造器中:表示正在创建的对象
(2)如果出现在成员方法中:表示正在调用这个方法的对象
2、this的用法:
(1)this.属性
当局部变量与成员变量同名时,那么可以在成员变量的而前面加“this.”用于区别
(2)this.方法
调用当前对象的成员方法,完全可以省略“this.”
(3)this()或this(实参列表)
this()表示调用本类的无参构造
this(实参列表)表示调用本类的有参构造
this()或this(实参列表)要么没有,要么必须出现在构造器的首行
示例代码:
public class Student{
private String name;
private int score;
public Student(){
}
public Student(String name){
this.name = name;
}
public Student(String name, int score){
this(name);
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setScore(int score){
this.score = score;
}
public int getScore(){
return score;
}
}
3、成员变量与局部变量的区别?
这里只讨论实例变量(关于类变量见static部分)
(1)声明的位置不同
成员变量:类中方法外
局部变量:方法中或代码中
①方法的形参列表
②方法体中局部变量
③代码块中的局部变量
(2)运行时在内存中的存储位置不同
成员变量:堆
局部变量:栈
基本数据类型的变量在栈中,引用数据类型的变量在堆中:不准确
(3)修饰符
成员变量:有很多修饰符,例如:权限修饰符
局部变量:不能加权限修饰符,唯一的能加的是final
(4)初始化
成员变量:有默认值
局部变量:没有默认值,必须手动初始化
(5)生命周期
成员变量:随着对象的创建而创建,随着对象被回收而消亡,即与对象同生共死。每一个对象都是独立的。
局部变量:方法调用时才分配,方法运行结束就没有了。每一次方法调用,都是独立的
4、 包
1、包的作用:
(1)可以避免类重名
有了包之后,类的全名称就变为:包.类名
(2)分类组织管理众多的类
例如:java.lang包,java.util包,java.io包…
(3)可以控制某些类型或成员的可见范围
如果某个类型或者成员的权限修饰缺省的话,那么就仅限于本包使用
本包使用
2、声明包的语法格式:
package 包名;
注意:
(1)必须在源文件的代码首行
(2)一个源文件只能有一个
3、包的命名规范和习惯:
(1)所有单词都小写,每一个单词之间使用.分割
(2)习惯用公司的域名倒置
例如:com.awen.xxx;
top.liuawen.xxx
建议大家取包名时不要使用“java.xx"包
4、使用其他包的类:
前提:被使用的类或成员的权限修饰符是>缺省的
(1)使用类型的全名称
例如:java.util.Scanner input = new java.util.Scanner(System.in);
(2)使用import 语句之后,代码中使用简名称
5、import语句
import 包.类名;
import 包.*;
注意:当使用两个不同包的同名类时,例如:java.util.Date和java.sql.Date。
一个使用全名称,一个使用简名称
示例代码:
package com.awen.test;
import java.util.Scanner;
public class Test{
public static void main(String[] args){
Scanner input = new Scanner(System.in);
}
}
5、 面向对象的基本特征之二:继承
继承是面向对象的三大特征之一,也是实现软件复用的重要手段。Java的继承具有单继承的特点,每个子类只有一个直接父类。
软件复用 Java的继承具有单继承的特点 每个子类只有一个直接父类
1、为什么要继承?继承的好处?
(1)代码的复用
(2)代码的扩展
继承 复用、扩展
2、如何实现继承?
语法格式:
【修饰符】 class 子类 extends 父类{
}
extends
3、继承的特点
Java的继承通过extends关键字来实现,实现继承的类被称为子类,被继承的类被称为父类,有的也称其为基类、超类。父类和子类的关系,是一种一般和特殊的关系。
(1)子类会继承父类的所有特征(属性、方法)
但是,私有的在子类中是不能直接使用的
(2)子类不会继承父类的构造器
因为,父类的构造器是用于创建父类的对象的
(3)子类的构造器中又必须去调用父类的构造器
在创建子类对象的同时,为从父类继承的属性进行初始化用,可以借助父类的构造器中的代码为属性赋值。
(4)Java只支持单继承:一个子类只能有一个“直接”父类
(5)Java又支持多层继承:父类还可以有父类,特征会代代相传
(6)一个父类可以同时拥有很多个子类
注意:子类只能从被扩展的父类获得成员变量、方法和内部类(包括内部接口、枚举),不能获得构造器和初始化块。
6、 关键字super
如果需要在子类方法中调用父类被覆盖的实例方法,则可使用super限定来调用父类被覆盖的实例方法。
super关键字:引用父类的,找父类的xx
用法:
(1)super.属性
当子类声明了和父类同名的成员变量时,那么如果要表示某个成员变量是父类的,那么可以加“super.”
(2)super.方法
当子类重写了父类的方法,又需要在子类中调用父类被重写的方法,可以使用"super."
(3)super()或super(实参列表)
super():表示调用父类的无参构造
super(实参列表):表示调用父类的有参构造
注意:
(1)如果要写super()或super(实参列表),必须写在子类构造器的首行
(2)如果子类的构造器中没有写:super()或super(实参列表),那么默认会有 super()
(3)如果父类没有无参构造,那么在子类的构造器的首行“必须”写super(实参列表)
7、 方法的重写
1、方法的重写(Override)
大部分时候,子类总是以父类为基础,额外增加新的成员变量和方法。但当子类继承了父类的方法时,又觉得父类的方法体的实现不适合于子类,那么子类可以选择进行重写。
2、方法的重写的要求
(1)方法名:必须相同
(2)形参列表:必须相同
(3)修饰符
权限修饰符: >=
(4)返回值类型
如果是基本数据类型和void:必须相同
如果是引用数据类型:<=
在Java中我们认为,在概念范围上:子类 <父类
方法的重写要遵循“两同两小一大”规则,“两同”即方法名相同、形参列表相同;“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。尤其需要指出的是,覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法。
3、重载(Overload)与重写(Override)的区别
重载(Overload):在同一个类中,方法名相同,形参列表不同,和返回值类型无关的两个或多个方法。
重写(Override):在父子类之间。对方法签名的要求见上面。
因为重载主要发生在同一个类的多个同名方法之间,而重写发生在子类和父类的同名方法之间。它们之间的联系很少,除二者都是发生在方法之间,并要求方法名相同之外,没有太大的相似之处。当然,父类方法和子类方法之间也可能发生重载,因为子类会获得父类方法,如果子类定义了一个与父类方法有相同的方法名,但参数列表不同的方法,就会形成父类方法和子类方法的重载。
特殊的重载:
public class TestOverload {
public static void main(String[] args) {
B b = new B();
//b对象可以调用几个a方法
b.a();
b.a("");//从b对象同时拥有两个方法名相同,形参不同的角度来说,算是重载
}
}
class A{
public void a(){
//...
}
}
class B extends A{
public void a(String str){
}
}
8、 非静态代码块
1、语法格式
【修饰符】 class 类名{
{
非静态代码块
}
}
2、作用
目的:在创建的过程中,为对象属性赋值,协助完成实例初始化的过程
3、什么时候执行?
(1)每次创建对象时都会执行
(2)优先于构造器执行
9、 实例初始化过程
1、概念描述
-
实例初始化过程:实例对象创建的过程
-
实例初始化方法:实例对象创建时要执行的方法
-
实例初始化方法的由来:它是有编译器编译生成的
-
实例初始化方法的形式:()或(形参列表)
-
实例初始化方法的构成:
①属性的显式赋值代码
②非静态代码块的代码
③构造器的代码
其中
①和②按顺序执行,从上往下
③在①和②的后面
因此一个类有几个构造器,就有几个实例初始化方法。
2、单个类实例初始化方法
示例代码:
class Demo{
{
System.out.println("非静态代码块1");
}
private String str = assign();//调用方法,来为str进行显式赋值
public Demo(){
System.out.println("无参构造");
}
public Demo(String str){
this.str = str;
System.out.println("有参构造");
}
{
System.out.println("非静态代码块2");
}
public String assign(){
System.out.println("assign方法");
return "hello";
}
}
图解:
3、父子类的实例初始化
注意:
(1)原先super()和super(实参列表)说是调用父类的构造器,现在就要纠正为调用父类的实例初始化方法了
(2)原先super()和super(实参列表)说是必须在子类构造器的首行,现在要纠正为必须在子类实例初始化方法的首行
结论:
(1)执行顺序是先父类实例初始化方法,再子类实例初始化方法
(2)如果子类重写了方法,通过子类对象调用,一定是执行重写过的方法
示例代码:
class Ba{
private String str = assign();
{
System.out.println("(1)父类的非静态代码块");
}
public Ba(){
System.out.println("(2)父类的无参构造");
}
public String assign(){
System.out.println("(3)父类的assign()");
return "ba";
}
}
class Er extends Ba{
private String str = assign();
{
System.out.println("(4)子类的非静态代码块");
}
public Er(){
//super() ==>调用父类的实例初始化方法,而且它在子类实例初始化方法的首行
System.out.println("(5)子类的无参构造");
}
public String assign(){
System.out.println("(6)子类的assign()");
return "er";
}
}
class Test{
public static void main(String[] args){
new Er();//612645
}
}
图解:
10、 面向对象的基本特征之三:多态
Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态(Polymorphism)。
1、多态:
语法格式:
父类 引用/变量 = 子类的对象;
2、前提:
(1)继承
(2)方法的重写
(3)多态引用
3、现象:
编译时看左边/“父类”,运行时看右边/“子类”。
编译时,因为按父类编译,那么只能父类有的方法,子类扩展的方法是无法调用的;
执行时一定是运行子类重写的过的方法体。
示例代码:
class Person{
public void eat(){
System.out.println("吃饭");
}
public void walk(){
System.out.println("走路");
}
}
class Woman extends Person{
public void eat(){
System.out.println("细嚼慢咽的吃饭");
}
public void walk(){
System.out.println("婀娜多姿走路");
}
public void shop(){
System.out.println("买买买...");
}
}
class Man extends Person{
public void eat(){
System.out.println("狼吞虎咽的吃饭");
}
public void walk(){
System.out.println("大摇大摆的走路");
}
public void smoke(){
System.out.println("吞云吐雾");
}
}
class Test{
public static void main(String[] args){
Person p = new Woman();//多态引用
p.eat();//执行子类重写
p.walk();//执行子类重写
//p.shop();//无法调用
}
}
4、应用:
(1)多态参数:形参是父类,实参是子类对象
(2)多态数组:数组元素类型是父类,元素存储的是子类对象
示例代码:多态参数
class Test{
public static void main(String[] args){
test(new Woman());//实参是子类对象
test(new Man());//实参是子类对象
}
public static void test(Person p){//形参是父类类型
p.eat();
p.walk();
}
}
示例代码:多态数组
class Test{
public static void main(String[] args){
Person[] arr = new Person[2];//多态数组
arr[0] = new Woman();
arr[1] = new Man();
for(int i=0; i<arr.length; i++){
all[i].eat();
all[i].walk();
}
}
}
5、向上转型与向下转型:父子类之间的转换
(1)向上转型:自动类型转换
当把子类的对象赋值给父类的变量时(即多态引用时),在编译时,这个对象就向上转型为父类。此时就看不见子类“特有、扩展”的方法。
(2)向下转型:强制转换。有风险,可能会报ClassCastException异常。
当需要把父类的变量赋值给一个子类的变量时,就需要向下转型。
要想转型成功,必须保证该变量中保存的对象的运行时类型是<=强转的类型
示例代码:
class Person{
//方法代码省略...
}
class Woman extends Person{
//方法代码省略...
}
class ChineseWoman extends Woman{
//方法代码省略...
}
public class Test{
public static void main(String[] args){
//向上转型
Person p1 = new Woman();
//向下转型
Woman m = (Woman)p1;
//p1变量中实际存储的对象就是Woman类型,和强转的Woman类型一样
//向上转型
Person p2 = new ChineseWoman();
//向下转型
Woman w2 = (Woman) p2;
//p2变量中实际存储的对象是ChineseWoman类型,强制的类型是Woman,ChineseWoman<Woman类型
}
}
6、instanceof
表达式语法格式:
对象/变量 instanceof 类型
运算结果:true 或 false
作用:
用来判断这个对象是否属于这个类型,或者说,是否是这个类型的对象或这个类型子类的对象
示例代码:
class Person{
//方法代码省略...
}
class Woman extends Person{
//方法代码省略...
}
class ChineseWoman extends Woman{
//方法代码省略...
}
public class Test{
public static void main(String[] args){
Person p = new Person();
Woman w = new Woman();
ChineseWoman c = new ChineseWoman();
if(p instanceof Woman){//false
}
if(w instanceof Woman){//true
}
if(c instanceof Woman){//true
}
}
}