【Java基础系列】- 面向对象

【Java基础系列】- 面向对象


一、面向对象的三大特性

封装、继承、多态

1. 封装(Encapsulation)

指将对象的状态(属性)和行为(方法)结合在一起,并对外隐藏对象的内部细节,只通过公开的方法访问和修改对象的状态。这种机制可以提高代码的安全性和可维护性,避免外部直接访问对象的内部数据。

  • 封装的实现方式
    • 将属性声明为私有(private:防止外部直接修改对象的属性。
    • 提供公共的访问方法(gettersetter:通过这些方法控制对属性的访问和修改。
  • 实际例子
    • 假设余额变量balanceprivate 的,外部无法直接修改 balance 的值。
    • 只能通过 deposit()withdraw() 方法来存取钱,这样可以在这些方法中加入逻辑,确保金额不能为负数或超过余额。
  • 封装的优势
    • 信息隐藏:对象的内部状态对外界隐藏,只暴露必要的接口,增加了代码的安全性。
    • 灵活性:可以在不改变对象外部接口的前提下修改对象的内部实现。
    • 易维护性:因为私有属性只能通过方法访问,可以在方法中添加数据校验逻辑,确保数据的一致性和有效性。
2. 多态(Polymorphism)

面向对象最核心的机制:动态绑定或称多态,带来了强可扩展性
指同一个行为或方法在不同对象中可以有不同的表现形式。多态使得我们可以通过相同的接口或方法名,调用不同类型对象的特定实现,从而提高代码的灵活性和可扩展性。

  • 多态的两个主要形式
    • 方法重写(Override):子类重新定义父类中的方法。当使用父类引用指向子类对象时,调用的方法是子类重写后的版本。这是最常见的多态形式。在子类中可以根据需要重写从基类中继承的方法,重写后必须和被重写方法具有相同方法名、参数、返回类型。重写方法不能使用比被重写方法更严格的访问权限
    • 方法重载(Overload):在同一个类中,方法名相同但参数类型或数量不同的多种方法。这不是真正意义上的多态,但在一些情况下也被理解为多态的一种。构造方法也可重载,调用时会根据不同参数选择对应方法。
  • 实际例子
    • 当调用 myAnimal.makeSound() 时,即使myAnimal变量的类型是 Animal,实际调用的却是 DogCatmakeSound() 方法。这就是多态:相同的调用,表现出不同的行为。
  • 多态的优势
    • 简化代码:允许使用相同的代码来处理不同类型的对象。
    • 扩展性:新的子类可以轻松加入系统而无需修改现有代码,只需要定义新行为。

多态的必要条件:要有继承、重写、父类引用指向子类对象

  • 多态指在执行期间(而非编译时)判断引用对象的实际类型,根据实际类型调用其相应的方法,例如:
class Animal{
    private String name;
    Animal(String name){//构造方法
        this.name=name;
    }
    public void enjoy(){
        System.out.println("叫声");
    }
}
class Cat extends Animal{
    private String color;
    Cat(String n,Stringc){//构造方法
        super(n);
        color=c;
    }
    public void enjoy(){
        System.out.println("猫叫声");
    }
}
class Lady{
    private String name;
    private Animal pet;//创建Animal类型对象的引用pet
    Lady(String name,Animal pet){//构造方法
        this.name=name;
        this.pet=pet;
    }
    public void myPetEnjoy(){
        pet.enjoy();//调用pet引用指向的enjoy方法
    }//!!会根据传递过来的参数pet是父类型还是子类型对象来决定调用的方法是父类的还是子类的
}
public class Test{
    public static void main(String[] args){
        Cat c=new Cat("catname","blue");//创建Cat对象C
        Lady l=new Lady("l1",c);//创建Lady对象l,传递Cat类型对象引用c
        l.myPetEnjoy();//传递的c是Cat类的,故会执行Cat类中的enjoy方法而不是Animal中的
    }//父类类型的引用指向子类的对象,不能访问子类新增变量,可访问子类方法
}
2. 继承(inheritance)

!!Java只支持单继承

继承会把父类的所有都给子类一份,子类中可以访问父类的成员变量和方法,但注意因为访问修饰符的存在,子类中只可访问父类中的部分成员,并且只有所有权没有使用权

public class Aaa{
    private int i=1;
    	   int j=2;//不加访问控制符就代表default权限
    protected int k=3;
    public int m=4;
}
public class Abb extends Aaa{
    int b=1;
    public void method(){
        System.out.println(i);//错误,不可访问
        System.out.println(j);//正确,可访问
        System.out.println(k);//正确,可访问
        System.out.println(m);//正确,可访问
    }
}

继承中的构造方法

子类的构造过程必须调用其父类的构造方法

  • 子类可以在自己的构造方法中使用 super(参数) 调用父类的构造方法
  • 子类的某构造方法中使用 this(参数) 调用本类的另外构造方法
  • 如果调用super,必须写在子类构造方法第一行

如果子类构造方法没有显式调用父类构造方法,系统默认调用父类的无参数构造方法,若父类没无参数构造方法,则编译出错

权限控制

对于成员变量的访问控制修饰符如下:

修饰符类内部同一个包子类任何地方
private
default
protected
public

对于类的权限修饰符只有public和default,default默认不写

  • private修饰符表示私有成员,只有定义它们的类才能访问这些成员,确保类的内部状态不会被外部代码意外修改
  • protected修饰符与private不同,提供了更宽松的访问控制,对其所在类的子类是可访问的
    • 这意味着子类可以通过super.a,直接访问和修改继承自父类的protected成员
    • 然而,对于类的外部,protected成员类似于private成员,即它们不能被类外的代码直接访问
  • 每个继承了父类变量的子类,相当于拷贝了一份变量,对变量的修改影响也仅限于自身,不会影响到父类的变量值,更不会影响到其他子类对应的变量值
  • 在子类中不能直接访专问父属类成员域,但是可以通过父类中的公共方法访问以及修改父类成员域
  • default修饰的成员只能在同一个包内访问,public修饰的成员可以在任何一个类中使用,无论是否在同一个包中

二、面向对象设计思想

  • 可重用性、可扩展性、可维护性
  • 内存分析贯穿始终

面向对象:考虑某问题有什么类什么对象,分别有什么属性和方法,类和类之间的关系

面向过程:考虑第一步怎样,第二步怎样……

对象通过属性attribute和方法method来分别对应事物的静态和动态属性

类是一类对象的模板,对象是某一类的一个具体实例,首先定义类才有定义对象

三、面向对象具体细节

1. 类和类之间的关系
  • 关联关系:一个类的方法里的参数是另一个类
  • 继承关系:类A是一种类B,A从B继承
  • 聚合关系:类A是类B的一部分(分为聚集和组合两种)
  • 实现关系:实现接口,实现接口的具体方法
2. 创建对象
  • 必须使用new关键字创建对象
  • 同一类的每个对象有各自成员变量的存储空间,但是他们共享同一方法空间即方法代码只存一份
  • 非静态方法或成员变量只能有了对象再调用

!!对于未赋值的成员变量Java会默认初始化为0等,但对于未赋值的局部变量会编译出错

3. 关键字
  • this关键字
    声明(new)了一个类的的一个对象之后
    • 在类的方法定义中使用this关键字代表使用该方法的对象的引用
    • this就像一个成员变量,存在于该对象对应的堆区域中,this存了指向该对象的引用
    • 一般在某对象的方法中成员变量与参数重名时使用,不说明则变量默认代表最近定义的那个变量
public class Aaa{
    int i=1;
    public static void aaa(byte i){
        this.i=i;
    }//前面的i代表了成员变量,后面的i没有特殊声明则代表最近定义的i即参数
}
  • super关键字
    super像子类的一个成员变量,存于子类的堆,super是指向其父类的引用
class FatherClass{
    public int value;
    public void f(){
        value=100;
    }
}
class ChildClass extends FatherClass{
    public int value;
    public void f(){
        super.f();
        value=200;
        System.out.println(super.value);
    }
}
  • static关键字
    • static用于修饰成员变量、方法,表示静态
    • 静态成员变量,会存储于数据段(data seg)中,而非堆中,对于该类的每一个对象,其中的该成员变量都指向内存同一位置,即只会存一次
    • 静态方法不new出对象也可以用,在被调用时不会传递对象的引用,即在static方法中无法访问非stack中的成员

可通过对象引用或类名来访问静态成员,即不需实例化

  • final关键字
    • final的变量值不能被改变,包括成员变量和局部变量(形参等)
    • final的方法不能被重写
    • final的类不能被继承
4. package、import语句

解决类命名的冲突问题

package 目录1.目录2.目录3,写在第一行,可以有很多层,一层层的包可以看成是目录

编译出来的class文件必须位于正确的目录下面,才可在其他类中通过目录1.目录2.类来访问该类,对于同一子目录下的类文件不需要引入,直接调对方类就可以

//这里不写package代表他是一个裸体类
//但是在定义Aaa类的文件Aaa.java中首行为package aaa.bbb.ccc.ddd
public class Bbb{
    public static void main(String[] args){
        aaa.bbb.ccc.ddd.Aaa a = new aaa.bbb.ccc.ddd.Aaa();
    }
}

!!在执行类时必须把包名写全,即$java aaa.bbb.ccc.ddd.Aaa

!!必须class文件的最上层包的父目录位于classpath下

系统会沿着classpath指定的顺序找class

import语句

import aaa.bbb.ccc.ddd.Aaa;
//或写成import aaa.bbb.ccc.ddd.*;
public class Bbb{
    public static void main(String[] args){
        Aaa a = new Aaa();
    }
}

在这里插入图片描述

lang包不用import,其他都需要引入

需要考虑classpath问题,打成的jar包也需要位于满足

5. 内存解析

对于引用类型变量:

引用类型变量,new了之后才有值,stack内存地址,堆内存new出来的对象的具体内容,类的方法和静态变量只存一份,在方法区,所有对象用时都指向那一份

除了基本类型(8种,如int)之外的变量都是引用类型,对象通过引用对其操作

String是一个引用类型,s是一个引用变量,s的值存在stack,new之后s才不为null,并且指向堆内存中的某块,该块内存具体内容,相当于s存个地址,具体内容通过S来调用,引用变量s类似c语言的指针

在这里插入图片描述

6. 构造方法

使用new+构造方法来创建一个新的对象

构造方法是一个定义在类中的用来初始化对象的方法

构造方法与类同名并且无返回值,不用写void,如:

public class Person{
    int id;
    int age=20;
    Person(int a,int b){ //构造函数/方法
        id=a;			 //a,b都是局部变量,运行到时存在stack
        age=b;
    }
    public static void main(String[] args){
        Person Tom=new Person(12,25);//a,b会先存在stack,之后id,age存到堆中
    }					 //方法执行完后即new创建完成后a,b会释放,但堆中那俩保留
}

没有构造函数时,编译器会为类自动添加构造函数,classA(){}

约定俗成的命名规则:类名首字母大写,方法名变量名首字母小写,运用驼峰标识

Object类是所有类的父类,是最根基的

在类的声明中未使用extends关键字指明其父类时,默认父类是Object类

Object类的toString方法

public class TestToString{
    public static void main(String[] args){
        Dog d=new Dog();
        System.out.printlin("d="+d.toString);//每个不extends的类默认继承自Object
    }//Object类中定义的方法toString,Dog类从中继承toString方法
}//加不加.toString都会在输出时输出类名加一串hash值,你可以在Dog中重写该toString方法

hashcodes table

JVM有一个hashcodes table每个对象都有自己独一无二的hashcode根据其可在内存中找到该对象

Object类的equals方法

equals方法默认是比较对象的引用是否一样,即比较的是地址,等价于==

public class TestEquals{
    public static void main(String[] args){
        Cat c1=new Cat(1,2,4);
        Cat c2=new Cat(1,2,4);
        System.out.println(c1==c2);//==跟equals方法等价
        System.out.println(c1.equals(c2));//但equals方法可以重写
    }//不写extends就默认继承自Object类,故可用Object类中的equals方法
}
class Cat{
    int color,weight,height;
    public Cat(int color,int weight,int height){
        this.color=color;
        this.weight=weight;
        this.height=height;
    }
    //public boolean equals(Object obj){}可在此处重写equals方法
}
7. 对象转型casting和instanceof

一个父类的引用类型变量可以指向其子类的对象,但是不能访问子类中相对于父类新增加的成员

可以使用引用变量名 instanceof 类名来判断该引用所指向的对象是否属于该类或该类的子类

子类对象当做基类的对象称为向上转型upcasting,基类对象当做子类用称向下转型downcasting

public class Test{
    public static void main(String[] args){
        Animal a=new animal("name");
        Cat c=new Cat("catname","blue");
        Dog d=new Dog("dogname","black");//System属于lang包可以直接调用
        System.out.println(a instanceof Animal);//true
        System.out.println(c instanceof Animal);//true
        System.out.println(a instanceof Cat);//Faluse
        a=new Dog("bigyellow","yellow");//downcasting,用a不能访问Dog多出的成员
        System.out.println(a.name);//输出bigyellow
        System.out.println(a.furcolor);//产生error!!
        System.out.println(a instanceof Animal);//True
        System.out.println(a instanceof Dog);//True
        System.out.println(a instanceof Cat);//Faluse
        Dog d1=(Dog)a;//要加强制转换符,转换为Dog类型引用
        System.out.println(d1.furcolor);//可访问,输出yellow
    }
}

一种可扩展性更好的方法

public class Test{
    public static void main(String[] args){
        Test test=new Test();
        Animal a=new Animal("name");
        Cat c=new Cat("catname","blue");
        Dog d=new Dog("dogname","black");
        test.f(a);
        test.f(c);
        test.f(d);
    }
    public void f(Animal a){
        System,out.println("name"+a.name);
        if(a instanceof Cat){
            Cat cat=(Cat)a;//强制类型转换,将Animal类型引用改为Cat类型
            System.out.println(" "+cat.color);
        } else if(a instanceof Dog){
            Dog dog=(Dog)d;//强制类型转换
            System.out.println(" "+dog.color);
        }
    }
}
8. 抽象类abstract

用abstract关键字来修饰某类时,该类叫抽象类,修饰方法时叫抽象方法

  • 抽象类只需声明,而不需实现

  • 抽象方法在某方法不需要实现时使用,子类中要么继续抽象方法要么就重写,不然编译出错

  • 抽象类new不出来对象即不能被实例化,必须被继承

抽象方法就是用来被子类进行重写的,若某类中有抽象方法则该类需标为抽象类

abstract class Animal{
    private String name;
    Animal(String name){//构造方法
        this.name=name;
    }
    /*public void enjoy(){
        System.out.println("叫声");
    }*/
    public abstract void enjoy();//抽象方法,用于被子类重写,需要定义方便多态
}
9. 接口interface

用于实现多重继承

接口是抽象方法和常量值的定义的集合,只含常量和方法定义,没有常量和方法实现

接口本质上是种特殊的类,定义时将class改为interface,不用写abstract

实现类采用implements来表示实现某个接口或某些接口

interface Singer{
    public void sing();//声明抽象方法,interface里不用写abstract
    public void sleep();
}
interface Painter{
    public void paint();
    public void eat();
}
class Student implements Singer,Painter{//类实现接口用implements而不是extends
    private String name;
    Student(String name){//构造方法
        this.name=name;
    }
    public void study(){
        System.out.println("studying");
    }
    public void sing(){//重写实现
        System.out.println("student is singing");
    }
    public void sleep(){
        System.out.println("student is sleeping");
    }
    public void paint(){
        System.out.println("student is painting");
    }
    public void eat(){
        System.out.println("student is eating");
    }  //在类中必须对其所来自的所有interface全部重写或称实现
    public static void main(String[] args){
        Singer s1=new Student("Liu");
        s1.sing();s1.sleep;
        Painter p1=(Painter)s1;//类型转换,可见interface也有多态特性
        p1.paint();p1.eat();
    }
}

接口可以多重实现

  • 接口中常量不声明就默认为public static final的,也只能是public static final
  • 接口中的方法只能为抽象方法,默认为public的也只能为public
  • 接口可以继承其他接口,并添加新的属性和抽象方法
  • !!与继承关系类似,接口与实现类之间存在多态性
  • 一个类可以实现多个无关接口,多个无关类可以实现同一接口

!类和类可以相互继承extends,接口和接口之间可以相互继承,类只能实现接口implements

public interface Valuable{  //接口是特殊类,只会含常量和抽象方法
    public double getMoney();
}
public interface Protectable{  //接口
    public void beProtected();
}
abstract class Animal{  //抽象类
    private String name;
    abstract void enjoy();
}
class GoldMonkey extends Animal implements Valuable,Protectable{
    public double getMoney(){  //实现Valuable接口中方法
        return 1000;
    }
    public void beProtected(){  //实现Protectable接口中方法
        System.out.println("live in the room");
    }
    public void enjoy(){}  //实现父类Animal中方法
    public void test(){
        Valuable v=new GoldMonkey();  //接口类型引用指向实现其的类
        v.getMonkey;
        Protectable p=(Protectable)v;  //类型转换upcasting
        p.beProtected;
    }
}
interface A extends Protectable{  //接口A继承自接口Protectable
    void m();
}
class Hen implements A{  //类Hen实现接口A
    public void m(){}	 //需要实现Protectable接口的方法和A中的方法
    public void beProtected(){}  //即子接口的类实现时要把子接口和父接口的方法全实现
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值