【Java基础系列】- 面向对象
文章目录
一、面向对象的三大特性
封装、继承、多态
1. 封装(Encapsulation)
指将对象的状态(属性)和行为(方法)结合在一起,并对外隐藏对象的内部细节,只通过公开的方法访问和修改对象的状态。这种机制可以提高代码的安全性和可维护性,避免外部直接访问对象的内部数据。
- 封装的实现方式:
- 将属性声明为私有(
private
):防止外部直接修改对象的属性。 - 提供公共的访问方法(
getter
和setter
):通过这些方法控制对属性的访问和修改。
- 将属性声明为私有(
- 实际例子:
- 假设余额变量
balance
是private
的,外部无法直接修改balance
的值。 - 只能通过
deposit()
和withdraw()
方法来存取钱,这样可以在这些方法中加入逻辑,确保金额不能为负数或超过余额。
- 假设余额变量
- 封装的优势:
- 信息隐藏:对象的内部状态对外界隐藏,只暴露必要的接口,增加了代码的安全性。
- 灵活性:可以在不改变对象外部接口的前提下修改对象的内部实现。
- 易维护性:因为私有属性只能通过方法访问,可以在方法中添加数据校验逻辑,确保数据的一致性和有效性。
2. 多态(Polymorphism)
面向对象最核心的机制:动态绑定或称多态,带来了强可扩展性
指同一个行为或方法在不同对象中可以有不同的表现形式。多态使得我们可以通过相同的接口或方法名,调用不同类型对象的特定实现,从而提高代码的灵活性和可扩展性。
- 多态的两个主要形式:
- 方法重写(Override):子类重新定义父类中的方法。当使用父类引用指向子类对象时,调用的方法是子类重写后的版本。这是最常见的多态形式。在子类中可以根据需要
重写从基类中继承的方法
,重写后必须和被重写方法具有相同方法名、参数、返回类型。重写方法不能使用比被重写方法更严格的访问权限
。 - 方法重载(Overload):在同一个类中,方法名相同但参数类型或数量不同的多种方法。这不是真正意义上的多态,但在一些情况下也被理解为多态的一种。构造方法也可重载,调用时会根据不同参数选择对应方法。
- 方法重写(Override):子类重新定义父类中的方法。当使用父类引用指向子类对象时,调用的方法是子类重写后的版本。这是最常见的多态形式。在子类中可以根据需要
- 实际例子:
- 当调用
myAnimal.makeSound()
时,即使myAnimal
变量的类型是Animal
,实际调用的却是Dog
或Cat
的makeSound()
方法。这就是多态:相同的调用,表现出不同的行为。
- 当调用
- 多态的优势:
- 简化代码:允许使用相同的代码来处理不同类型的对象。
- 扩展性:新的子类可以轻松加入系统而无需修改现有代码,只需要定义新行为。
多态的必要条件:要有继承、重写、父类引用指向子类对象
- 多态指在执行期间(而非编译时)判断引用对象的实际类型,根据实际类型调用其相应的方法,例如:
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(){} //即子接口的类实现时要把子接口和父接口的方法全实现
}