Java OOP
对象和封装
Java(面向对象设计):从现实世界中抽象出程序开发中的类,实现从现实到虚拟的转化;再对抽象出的类进行操作,实现对现实世界中行为的模拟;然后对抽象出的类进行优化,通过封装隐藏类的内部信息以提高安全性。
为什么使用面向对象
面向对象就是采用“现实模拟”的方法设计和开发程序。面向对象技术利用“面向对象的思想”去描述“面向对象的世界”,实现了虚拟世界和现实世界的一致性,符合人们的思维习惯,使客户和软件设计开发人员之间、软件设计开发人员内部交流更加流畅,同时有代码重用性高、可靠性高等优点,大大提高了软件尤其是大型软件的设计和开发效率。
面向过程和面向对象的区别:
面向过程的核心是函数,以功能为中心,实现了函数级别的代码重用。
面向对象的核心是封装了属性和方法(行为)的类,以数据为中心,实现了类级别的代码重用。
面向对象因为采用了类,所以具有继承和多态特性,可以进一步重用代码和简化编程,而面向过程中没有继承和多态特性。
使用面向对象进行程序设计
面向对象设计的过程就是抽象的过程,根据业务需求,关注与业务相关的属性和行为,忽略不必要的属性和行为,由现实世界中的“对象”抽象出软件开发中的“对象”。
通过在需求中找出名词的方式确定类和属性,通过找出动词的方式确定方法,并根据需要实现业务的相关程度进行筛选。
面向对象设计的过程分为三步完成:
- 发现类
- 发现类的属性
- 发现类的方法
抽象时应遵循的原则:
- 属性和方法的设置是为了解决业务问题。
- 关注主要属性和方法。
- 如没有必要,勿增加额外的类、属性和方法。
创建类和对象
类(Class)和对象(Object)是面向对象中的两个核心概念。类是对某一类事物的描述,是抽象的、概念上的定义。对象是实际存在的、具体的事物个体。类和对象就好比模具和铸件的关系,建筑物图样和建筑物实体的关系,我们可以由一个类创建多个对象。
Java中创建对象,调用属性及方法的语法和C#中是相同的。
- 通过构造方法来创建对象
- 通过对象名.属性名的方式调用属性
- 通过对象名.方法名的方式调用方法
构造方法及其重载
构造方法是一个特殊的方法,当用户没有定义方法时,系统回默认提供一个空的无参方法。
构造方法的名称和类名相同,没有返回值类型。构造方法的主要作用就是在创建对象时执行一些初始化操作,如给成员属性赋初值。
public class Pet(){
public String name;
public Pet(){
this.name = "小白";
}
}
方法的重载:
在同一个类中,方法名相同,参数列表不同,与方法返回值和方法修饰符没有任何关系。通过构造方法的重载来实现多种初始化行为,在创建对象时可以根据需要选择合适的构造方法。
public class Pet(){
public String name;
public String type;
public Pet(){
this.name = "小白";
}
public Pet(String name,String type){
this.name = name;
this.type = type;
//通过new对象时调用有参构造方法,实现传值
}
}
static修饰符
static可以用来修饰属性、方法和代码块。static修饰的变量属于这个类所有,即由这个类创建的所有对象共用一个static变量。通常把static修饰的属性和方法称为类属性(类变量)和类方法。不使用static修饰符的属性和方法属于单个对象,通常称为实例属性(实例变量)和实例方法。
注意:
- 在加载类的过程中,完成静态变量的内存分配,再执行静态块,两者是在创建对象之前执行的。
- 类属性和类方法可以通过类名和对象名访问,实例属性和实例方法只能通过对象名访问。
- 类方法只能访问类属性和其他类方法。
- 静态方法中不能使用this和super关键字。
封装
封装是面向对象的三大特性之一,就是将类的状态信息隐藏在类内部,不允许外部程序直接访问,而通过该类提供的方法来实现对隐藏信息的操作和访问。
封装的具体步骤:
修改属性的可见性来限制对属性的访问;为每一个属性创建一对赋值(setter)方法和取值(getter)方法,用于对这些属性的存取;在赋值方法中,加入对属性的存取控制语句。
封装的好处:
隐藏类的实现细节;让使用者只能通过程序规定的方法来访问数据;可以方便地加入存取控制语句,限制不合理操作。
访问修饰符(访问权限从小到大):
private:成员变量和方法只能在其定义的类中被访问,具有类可见性。
默认(friendly):成员变量和方法只能被同一个包里的类访问,具有包可见性。
protected:可以被同一个包中的类访问,被同一个项目中不同包中的子类访问。
public:可以被同一个项目中的所有类访问,具有项目可见性。
继承
定义:
子承父,将具有相同行为的类的代码提取为一个公共类。
语法:
修饰符 SubClass extends SuperClass{
//类定义部分
}
继承(inheritance)通过extends关键字来实现,其中的SubClass称为子类,SuperClass称为父类、基类或超类。修饰符如果是public,则该类在整个项目可见;若无public修饰符,则该类只在当前包可见;不可以使用private和protected修饰类。
继承是面向对象的三大特性之一,是Java中实现代码重用的重要手段之一,Java中只支持单继承,即每个类只能有一个直接父类。
在Java中,所有的Java类都直接或间接地继承了java.lang.Object类,所以说Object类是所有没有使用extends关键字类的父类。
子类与父类之间的关系
- 子类继承public和protected修饰的属性和方法,无论子类父类是否在同一个包里。
- 子类继承默认权限修饰符的属性和方法,但子类和父类必须在同一个包里。
- 子类无法继承private修饰的属性和方法。
- 子类无法继承父类的构造方法。
访问修饰符的访问权限:
访问修饰符 | 本类 | 同包 | 子类 | 其他 |
---|---|---|---|---|
private | √ | |||
friendly | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
重写和继承关系中的构造方法
子类重写父类方法
在子类中可以根据需求对父类继承的方法进行重新编写,称为方法的重写或方法的覆盖(overriding),方法重写必须满座如下要求:
- 重写方法和被重写方法必须具有相同的方法名。
- 重写方法和被重写方法必须具有相同的参数列表。
- 重写方法的返回值类型必须和被重写方法的返回值类型相同或是其子类。
- 重写方法不能缩小被重写方法的访问权限。
如果在子类中想调用父类的被重写的方法,应如何实现呢?在子类方法中通过“super.方法名”实现。
super代表对当前对象的直接父类对象的默认引用。在子类中可以通过super关键字来访问父类的成员。
- super必须出现在子类(子类的方法和构造方法)中,而不是其他位置。
- 可以访问父类的成员,如父类的属性、方法、构造方法。
- 注意访问权限的限制,无法通过super访问private成员。
继承关系中的构造方法
继承条件下构造方法的调用规则:
- 如果子类的构造方法中没有通过super显示调用父类的有参构造方法,也没有通过this显示调用自身的其他构造方法,则系统会默认先调用父类的无参构造方法。
- 如果子类的构造方法中通过super显示调用父类的有参构造方法,则执行父类相应的的构造方法,而不执行父类的无参构造方法。
- 如果子类的构造方法中通过this显示调用自身的其他构造方法,则在相应构造方法中应用以上两条规则。
- 如果存在多级继承关系,则在创建一个子类对象时,以上规则会多次向更高一级父类应用,一直到执行顶级父类Object类的无参构造方法为止。
注意:
- 在构造方法中如果有this或super语句出现,只能是第一条语句。
- 在一个构造方法中不允许同时出现使用this和super语句调用构造方法(否则会有两条第一条语句)。
- 在类方法中不允许出现this或super关键字。
- 在实例方法中,this和super语句不要求在第一条语句,可以共存。
抽象类
抽象类和抽象方法
特点:
- 抽象类和抽象方法都通过abstract关键字修饰。
- 抽象类不能实例化。抽象类中可以没有、有一个或多个抽象方法,甚至全部方法都可以是抽象方法。
- 抽象方法只有方法声明,没有方法实现。有抽象方法的类必须声明为抽象类。子类必须重写所有的抽象方法才能实例化,否则子类还是一个抽象类。
注意:
abstract可以用来修饰类和方法,但不能用来修饰属性和构造方法。
抽象类可以有构造方法,其构造方法可以被本类的其他构造方法调用,若此构造方法不是由private修饰,也可以被本类的子类中的构造方法调用。
final修饰符
- 用final修饰的类,不能再被继承
- 用final修饰的方法不能被子类重写
- 用final修饰的变量(包括成员变量和局部变量)将变成常量,只能赋值一次
多态
多态(polymorphism)是具有表现多种形态的能力的特征,或者可以说是同一个实现接口,使用不同的实例而执行不同的操作。
实现条件:
- 继承的存在(继承是多态的基础,没有基础就没有多态)。
- 子类重写父类的方法(多态下调用子类重写后的方法)。
- 父类引用变量指向子类对象(子类到父类的类型转换)。
子类到父类的转换(向上转型)
将一个父类的引用指向一个子类对象,称为向上转型(upcasting),自动进行类型转换。
public class Pet{
public void eat(){
System.out.print("进食");
}
}
public class Dog extends Pet{
@Override
public void eat(){
System.out.print("咬骨头");
}
}
public class Test{
public static void main(String[] args){
Pet pet = new Dog(); //子类到父类的转换
pet.eat(); //输出“咬骨头”
}
}
规则:
- 通过父类引用变量调用的方法是子类覆盖或继承父类的方法,不是父类的方法。
- 通过父类引用变量无法调用子类特有的方法。
父类到子类的转换(向下转型)
将一个指向子类对象的父类引用赋给一个子类的引用,称为向下转型,此时必须进行强制类型转换。
必须转换为父类指向的真实子类类型,否则会出现类型转换异常ClassCastException。
public class Pet{
public void eat(){
System.out.print("进食");
}
}
public class Dog extends Pet{
@Override
public void eat(){
System.out.print("咬骨头");
}
}
public class Test{
public static void main(String[] args){
Pet pet = new Dog(); //子类到父类的转换
pet.eat(); //输出“咬骨头”
Dog dog = (Dog)pet; //父类到子类的转换,还原子类的真实面目
}
}
instanceof运算符
该运算符用来判断一个对象是否属于一个类或者实现了一个接口,结果为true或false。
语法:
对象 instanceof 类或接口
public class Pet{
public void eat(){
System.out.print("进食");
}
}
public class Dog extends Pet{
@Override
public void eat(){
System.out.print("咬骨头");
}
public void play(){
System.out.print("喜欢踢足球");
}
}
public class Test{
public static void main(String[] args){
Pet pet = new Dog();
pet.eat();
if(pet instanceof Dog){ //判断是否是Dog类
Dog dog = (Dog)pet;
dog.play();
}
}
}
在进行引用类型转换时,先通过instanceof运算符进行类型判断,再进行相应的强制类型转换,这样可以有效地避免出现类型转换异常。
接口
在生活中,接口是一套规范,只要是满足这个规范的设备,就可以将它们组装到一起,从而实现该设备的功能。
而在软件中,接口同样是一种规范和标准,它们可以约束类的行为,是一些方法特征的集合。
语法:
[修饰符] interface 接口名 extends 父接口1,父接口2...{
//常量定义
//方法定义
}
class 类名 extends 父类名 implements 接口1,接口2....{
//类成员
}
说明:
- 接口的命名规则与类相同。如果修饰符是public,则该接口在整个项目中可见;如果省略修饰符,则该接口只在当前包可见。
- 接口中可以定义常量,不能定义变量。接口中的属性都会自动用public static final修饰,即接口中的属性都是全局静态常量。接口中的常量必须在定义时指定初始值。
- 接口中所有方法都是抽象方法。接口中的方法都会自动用public abstract修饰,即接口中只有全局抽象方法。
- 和抽象类一样,接口也不能实例化,接口中不能由构造方法。
- 接口之间可以通过extends实现继承关系,一个接口可以继承多个接口,但接口不能继承类。
- 接口的实现类必须实现接口的全部方法,否则必须定义为抽象类。
- 一个类只能有一个直接父类,但可以通过implements实现多个接口。当类在继承父类的同时又实现了多个接口时,extends关键字必须位于implements关键字之前。
接口表示一种能力
//门
public abstract class Door{
public abstract void open(); //开
public abstract void close(); //关
}
//锁,接口
public interface Lock{
void lockUp(); //上锁
void openLock(); //开锁
}
//防盗门类
public class TheftproofDoor extends Door implements Lock{
@Override
public void lockUp(){
System.out.println("锁上门了!");
}
@Override
public void openLock(){
System.out.println("锁打了!");
}
@Override
public void open(){
System.out.println("用力推,打开门了!");
}
@Override
public void close(){
System.out.println("轻轻拉门,门关上了!");
}
}
一个类实现了某个接口,就表示这个类具备了某种能力
接口不仅弥补了Java只支持单继承的缺点,还利于代码的扩展和维护。接口类似于一个组件,需要时可以自由组装。从使用角度来讲,接口和抽象类的区别在于:抽象类利于代码复用,接口利于代码维护。
接口表示一种约定
生活中两相电源插座中接口的形状、两个接头间的距离和两个接头的电压都遵循统一的约定。
接口体现了约定和实现相分离的原则,通过面向接口编程,可以降低代码间的耦合性,提高代码的可扩展性和可维护性。面向接口编程就意味着:开发系统时,主体构架使用接口,接口构成系统的骨架,这样就可以通过更换实现接口的类来实现更换系统。