- 1️⃣ 类和对象
- 2️⃣ 三大特性
-
- 2.1 封装(Encapsulation)
- 2.2 继承(Inheritance)
- 2.3 多态(Polymorphism)
- 3️⃣ 面向对象编程(OOP)和面向过程编程(PP)
- 4️⃣ 方法重载和方法重写
-
- * 小结:方法重载与方法重写的区别?
- 5️⃣ 接口和抽象类
-
- * 小结:接口和抽象类的区别?
- 6️⃣ 包和访问修饰符
- 7️⃣ 构造函数和析构函数
- 8️⃣ 成员变量和局部变量
-
- * 成员变量和局部变量的区别?
- * 总结
- * 本文源码下载地址
1️⃣ 类和对象
面向对象编程(Object-Oriented Programming, OOP)是一种广泛应用于软件开发的编程范式。Java是一种面向对象的编程语言,它提供了丰富的工具和特性来支持面向对象编程。本文将详细介绍Java面向对象的全部知识。
我们可以把一个Java类想象成是一个蓝图或者模具,用来创建对象。就像制造同样的零食用品,我们需要设计和使用模具来制作各种各样的巧克力、糖果或饼干。
对象则是基于这个类创建出来的具体实例。我们可以把一个对象比作是一块巧克力,它拥有自己的形状、重量和口味。每次创建新的对象,都会有一份独特的巧克力从模具中取出。
类定义了对象可以有的属性(变量)和行为(方法)。就像巧克力可能有不同的颜色、形状和味道一样,类定义了对象可能具有的各种特征。
当我们创建一个新的对象时,我们实际上是在生产一块新的巧克力。通过给对象的属性赋值,我们可以自定义每一块巧克力的特性。例如,我们可以设置巧克力的颜色为红色、形状为方形、味道为草莓。
此外,对象还可以执行不同的操作,也就是调用类中定义的方法。类中的方法可以让我们对对象进行各种操作,就像我们能够吃掉巧克力、分享给他人或将其用作配料制作其他甜点一样。
因此,类就像是制造巧克力的模具,对象则是由该模具创建出的巧克力。通过设置属性和执行方法,我们可以个性化每一个对象并进行各种操作。
根据上边的描述,那么可以写出下面这个简单的示例代码,主要展示如何创建一个Chocolate
类来代表巧克力,并为其定义一些属性和方法:
public class Chocolate {
// 定义巧克力的属性(变量)
private String color;
private String shape;
private String flavor;
// 构造函数用于创建巧克力对象,并初始化属性
public Chocolate(String color, String shape, String flavor) {
this.color = color;
this.shape = shape;
this.flavor = flavor;
}
// 定义获取属性的方法(getter)
public String getColor() {
return color;
}
public String getShape() {
return shape;
}
public String getFlavor() {
return flavor;
}
// 定义一些操作方法
public void eat() {
System.out.println("吃掉这个" + color + "色的巧克力,味道是" + flavor);
}
public void share() {
System.out.println("分享这块" + shape + "形状的巧克力给他人");
}
public void useAsIngredient() {
System.out.println("将这个巧克力用作配料制作其他甜点");
}
}
使用以上代码,可以在其他类中创建Chocolate
对象并对其进行个性化和操作。例如:
public class Main {
public static void main(String[] args) {
// 创建一个红色、方形、草莓味的巧克力对象
Chocolate chocolate = new Chocolate("红色", "方形", "草莓");
// 调用对象的方法进行操作
chocolate.eat();
chocolate.share();
chocolate.useAsIngredient();
}
}
输出结果:
吃掉这个红色的巧克力,味道是草莓
分享这块方形形状的巧克力给他人
将这个巧克力用作配料制作其他甜点
以上示例展示了类Chocolate
的使用方式。通过在类中定义属性和方法,可以根据需要创建对象的实例,并进行个性化配置和操作。
所以在Java中,类(Class)是创建对象(Object)的蓝图。一个类可以有多个对象实例化,每个对象都具有相同的属性和方法。类定义了一个对象应该包含的数据和能够执行的操作。
2️⃣ 三大特性
2.1 封装(Encapsulation)
封装是指将数据和行为(方法)封装在一个类中,并隐藏其实现细节。这样做可以保护数据不受外部访问和修改。
Java的封装性可以用这样一个场景来形象描述:
假设你是一名饭店的顾客,进入餐厅后,你将看到一些餐桌、椅子、服务员和厨师,以及从厨房传来的美味香气。然而,你并不需要了解餐厅内部的具体细节,比如餐桌是怎么制作的、椅子使用的木材种类,或者是厨师如何调制菜肴等等。
这里,餐厅就像是一个Java类,它有自己的属性和方法。而对于你作为顾客来说,这些属性和方法都被封装起来了。封装就好比是餐厅将其内部的细节隐藏起来,只向外提供必要的接口。在这个例子中,服务员就是餐厅对外界提供的接口,她会询问你的座位需求、点餐、送上美食。
通过封装,Java类将数据(例如属性)和操作(例如方法)包装在一起,形成一个不可分割的实体,对外界隐藏了实现的细节。这样做的好处在于,封装性可以确保类的使用者不需要了解类内部的复杂实现,只需要调用提供的接口即可完成需要的功能。这种套路类似于你在餐厅点菜,你并不需要知道厨师是怎样做出这么美味的菜肴的,只需告诉服务员你的需求即可。
总结一下,Java的封装性就像是一家饭店,通过隐藏内部实现细节,提供干净整洁的接口给用户使用。这种方式让程序的使用更加简单、易懂,并且使代码更安全可靠,因为类的用户无法直接访问和修改其内部细节,防止了不必要的错误和风险。
下面是一个简单的Java类,用来展示面向对象的封装性:
public class Restaurant {
private String name;
private int numberOfTables;
private Waiter waiter;
public Restaurant(String name, int numberOfTables) {
this.name = name;
this.numberOfTables = numberOfTables;
this.waiter = new Waiter();
}
public void enter() {
System.out.println("You entered the restaurant.");
System.out.println("You see " + numberOfTables + " tables and a pleasant aroma from the kitchen.");
}
public void orderFood(String foodName) {
waiter.takeOrder(foodName);
}
// Inner class representing the waiter
private class Waiter {
public void takeOrder(String foodName) {
System.out.println("Waiter: Your order for " + foodName + " has been taken.");
serveFood(foodName);
}
private void serveFood(String foodName) {
System.out.println("Waiter: Here is your delicious " + foodName + ". Enjoy your meal!");
}
}
}
上述代码中,Restaurant
类代表了餐厅,具有 name
、numberOfTables
和 waiter
三个私有属性。通过将这些数据成员设为私有,封装了类的内部细节,外部用户无法直接访问和修改这些属性。
构造函数 Restaurant
用于初始化餐厅的名称和桌子数量,并创建一个 Waiter
对象。enter
方法展示了进入餐厅后的动作,而 orderFood
方法调用服务员提供的接口来点餐。
内部类Waiter
代表服务员,它包含了 takeOrder
和 serveFood
两个私有方法。takeOrder
接受食物的名称,并给用户提供了一个接口来点餐。serveFood
方法用于将美食送到客人面前。
通过封装性,外部用户只需要调用提供的接口(例如 enter
和 orderFood
),而无需关心具体的实现细节。这增加了代码的可读性和安全性,同时使使用者更容易理解和使用程序。如果想进一步了解和操作餐厅的内部细节,可以通过添加额外的公共方法来提供更多的功能。
2.2 继承(Inheritance)
继承是一种通过从已有类派生新类来共享代码和行为的机制。子类可以继承父类的属性和方法,并添加自己的特定功能。
同样的,也可以用一个有趣的比喻来形象描述Java的继承:
假设你是一个新手厨师,而Java的继承就像你拥有了一本神奇的食谱。这本食谱不仅包含了很多经典菜肴的制作方法,还提供了一些基础菜肴的基本步骤。
在这个比喻中,你就是新建的类,而这本食谱就是父类。通过继承,你可以轻松地获得这本食谱上的所有菜肴制作方法和基本步骤,而无需重新创建。你只需要专注于添加自己特殊的调料和烹饪方法以制作出独特的菜品。
但继续深入思考,你可能会发现这本食谱也是其他人创造的,在这里它就是父类的子类。所以如果你想要对这本食谱进行修改或者加入全新的菜肴,你可以创建另外一本新的食谱。这本新的食谱同样也可以继承原来那本食谱上的菜肴,然后再加入你自己的烹饪技巧。
通过继承机制,Java让我们可以更加灵活方便地构建代码。就像使用这本食谱,你可以避免重复劳动,只需专注于自己的菜肴创作,并且随时可以自由地修改现有的食谱或创建属于自己的新食谱。这样,你在Java世界中就可以成为一名独具特色的大厨了!
以下是一个简单的Java代码示例,展示了面向对象的继承性:
// 父类 - 食谱
class Recipe {
public void prepareIngredients() {
System.out.println("准备食材");
}
public void cook() {
System.out.println("烹饪菜肴");
}
}
// 子类 - 新建的类,拥有父类的菜肴制作方法和基本步骤
class Chef extends Recipe {
public void addSpecialSeasoning() {
System.out.println("添加特殊调料");
}
public void customizeCookingMethod() {
System.out.println("个性化的烹饪方法");
}
// 可以重写父类的方法
@Override
public void cook() {
System.out.println("根据自己的菜谱烹饪菜肴");
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Chef chef = new Chef();
chef.prepareIngredients(); // 调用从父类继承的方法
chef.cook(); // 调用子类重写过的方法
chef.addSpecialSeasoning(); // 子类新增的方法
chef.customizeCookingMethod(); // 子类新增的方法
}
}
运行结果:
准备食材
根据自己的菜谱烹饪菜肴
添加特殊调料
个性化的烹饪方法
在这个示例中,Recipe
类表示食谱,具有prepareIngredients
和cook
两种方法。Chef
类是新建的类,它通过继承Recipe
类,获得了父类的方法。此外,Chef
类还定义了自己特有的方法addSpecialSeasoning
和customizeCookingMethod
。
通过创建Chef
类的实例,我们可以调用继承自父类的方法prepareIngredients
和cook
,以及调用子类自己新增的方法addSpecialSeasoning
和customizeCookingMethod
。这样,在Java中应用继承机制,我们可以更加灵活地构建代码,并扩展已有的功能,使之符合不同的需求。
2.3 多态(Polymorphism)
多态允许不同类型的对象对同一消息作出响应。这意味着使用一个基类类型的引用可以指向其派生类的对象。
我们继续通过一个比喻来理解Java的多态:
假设有一个家族,家族的成员都有一个共同的特点,那就是能够发出声音。在这个家族中,有各种不同的人和动物,比如爸爸、妈妈、狗狗、猫咪等等。
当我们要让这些家族成员发出声音时,我们只需要用同一个命令:“发出声音”。而不需要区分不同的成员是爸爸还是狗狗。鉴于家族成员的不同特点,每一个成员都会根据自己的独特方式发出声音。比如爸爸会说话,狗狗会汪汪叫,猫咪会喵喵叫。
在这个例子中,家族成员即为对象,发出声音就是一个共同的方法。Java中的多态则允许我们使用一个通用的方法名来调用不同类的同名方法,而不用关心实际对象的类型。这是因为每个对象都具备相同的方法,但不同的对象可以选择自己特定的实现方式。
多态使得代码更加灵活、可扩展。当我们需要新增一种家族成员时,只需要继承于基类并实现自己的特定方法,而不需要修改原有的代码。这种灵活性和可扩展性使得Java多态成为面向对象编程的重要特性之一。
下面是一个使用Java实现多态性的案例代码:
// 父类:家族成员
class FamilyMember {
public void makeSound() {
System.out.println("发出声音");
}
}
// 子类:爸爸
class Dad extends FamilyMember {
@Override
public void makeSound() {
System.out.println("爸爸说话");
}
}
// 子类:狗狗
class Dog extends FamilyMember {
@Override
public void makeSound() {
System.out.println("汪汪叫");
}
}
// 子类:猫咪
class Cat extends FamilyMember {
@Override
public void makeSound() {
System.out.println("喵喵叫");
}
}
// 主程序
public class Main {
public static void main(String[] args) {
FamilyMember dad = new Dad();
dad.makeSound(); // 调用爸爸对象的makeSound方法,输出:"爸爸说话"
FamilyMember dog = new Dog();
dog.makeSound(); // 调用狗狗对象的makeSound方法,输出:"汪汪叫"
FamilyMember cat = new Cat();
cat.makeSound(); // 调用猫咪对象的makeSound方法,输出:"喵喵叫"
}
}
运行结果:
爸爸说话
汪汪叫
喵喵叫
在上述代码中,FamilyMember
是一个抽象的基类,定义了所有家族成员共同的方法 makeSound()
。Dad
、Dog
和 Cat
分别是继承自 FamilyMember
的子类,重写了 makeSound()
方法,以实现自己特定的发声方式。
在主程序中,我们创建了一个爸爸对象、一个狗狗对象和一个猫咪对象,并分别依次调用它们的 makeSound()
方法。不同类型的对象都能够使用相同的方法名进行调用,但实际执行的是各自特定的实现方式,这就体现了多态性的特点。
3️⃣ 面向对象编程(OOP)和面向过程编程(PP)
面向对象编程(Object-Oriented Programming,OOP)和面向过程编程(Procedural Programming)是两种不同的软件开发范式。下面我来全面、完整和清晰地描述它们之间的区别。
* 面向过程编程
- 面向过程编程是一种采用函数、线性代码流和数据结构的编程范式;
- 程序主要由一系列的过程或函数组成,这些过程按照固定的流程执行,并处理传递给它们的数据;
- 数据与方法(操作函数)相分离,通常以参数的形式传递给函数进行处理;
- 强调程序的执行顺序和对数据的直接操作,在程序中需要显式地控制数据和函数之间的交互关系;
- 使用全局变量来共享数据,导致代码难以维护和重用。
例如,使用面向过程编程的方式实现计算两个数的和:
# 面向过程编程示例
def add(x, y):
return x + y
result = add(3, 5)
* 面向对象编程
- ***面向对象编程是一种基于对象和类的编程范式,对象是程序中的实体,具有属性和操作(方法)***;
- 程序由一组互相协作的对象组成,通过彼此交互来完成任务;
- 将相关的数据和方法组合成一个对象,对象之间通过消息传递进行通信;
- 强调数据的封装和隐藏,每个对象仅对外提供有限的接口,以保护数据的完整性和安全性;
- 类是对象的蓝图,定义了对象的属性和行为,并可以创建多个具有相同属性和行为的对象。
例如,使用面向对象编程的方式实现计算两个数的和:
# 面向对象编程示例
class Calculator:
def __init__(self, x, y):
self.x = x
self.y = y
def add(self):
return self.x + self.y
calculator = Calculator(3, 5)
result = calculator.add()
综上所述,面向过程编程和面向对象编程在编写代码的思维方式、组织结构和数据处理方式上存在着显著差异。面向过程强调顺序执行和直接操作数据,而面向对象则强调对象和类的概念、数据封装和代码复用。 具体选择何种编程范式应取决于项目需求、代码结构和开发团队的偏好。
4️⃣ 方法重载和方法重写
* 方法重载(Method Overloading)是指在同一个类中创建多个具有相同名称但参数列表不同的方法。 编译器会根据调用时传递的参数类型和数量来确定调用哪个方法。
下面是一个定义了方法重载的代码:
class Calculation {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}
public class Main{
public static void main(String[] args) {
Calculation calc = new Calculation();
int result1 = calc.add(3, 4); // 使用 int 参数的 add 方法
double result2 = calc.add(2.5, 3.7); // 使用 double 参数的 add 方法
System.out.println(result1);
System.out.println(result2);
}
}
运行结果:
7
6.2
在Calculation
类中,我定义了两个add
方法。第一个方法接受两个整数类型的参数a
和b
,返回这两个整数的和。第二个方法接受两个双精度浮点数类型的参数a
和b
,返回这两个浮点数的和。
在Main
类的main
方法中,首先创建了一个Calculation
对象 calc
。然后,通过调用calc
对象的add
方法,分别传入不同的参数来计算两个结果。result1
是将整数3和4作为参数传递给add(int a, int b)
方法得到的结果。result2
是将双精度浮点数2.5和3.7作为参数传递给add(double a, double b)
方法得到的结果。
* 方法重写(Method Overriding)是指在派生类中重新定义继承自父类的方法。方法重写要求方法名称、参数列表和返回类型完全相同。
下面是一个定义了方法重写的代码:
class Animal {
void makeSound() {
System.out.println("The animal makes a sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("The dog barks");
}
}
public class OverridingDemo{
public static void main(String[] args) {
Animal dog = new Dog();
dog.makeSound();
Animal animal = new Animal();
animal.makeSound();
}
}
运行结果:
The dog barks
The animal makes a sound
* 小结:方法重载与方法重写的区别?
- 方法重载是指在一个类中,方法名称相同而参数列表(参数个数、类型、顺序)不同,对权限符没有要求;
- 方法重写是指子类重写父类的方法,方法的名称、参数列表全部相同,重写方法的权限符等级必须大于等于被重写方法的权限符等级。
5️⃣ 接口和抽象类
* 接口(Interface)
接口(Interface)是一种规范,定义了一组方法但没有实现。一个类可以实现一个或多个接口,强制其提供接口中定义的方法的实现。
下面是一个定义了接口及其实现类的代码:
interface Drawable {
void draw();
}
class Rectangle implements Drawable {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
public class interfaceDemo{
public static void main(String[] args) {
Drawable drawable = new Rectangle();
drawable.draw(); // 输出: "Drawing a rectangle"
}
}
运行结果:
Drawing a rectangle
上面代码定义了一个接口 Drawable
,该接口包含一个抽象方法 draw()
。接口中的方法声明只有方法名和签名,没有方法体。
接着,代码定义了一个实现了接口 Drawable
的类 Rectangle
。在该类中,使用 @Override
注解来表示该方法重写了接口中的 draw()
方法。具体实现是打印出字符串 “Drawing a rectangle”。
接下来,在 interfaceDemo
类中,我在 main
方法中创建了一个 Drawable
接口的引用 drawable
并将其赋值为一个 Rectangle
对象。通过这种方式,我们可以使用 drawable
引用调用 draw()
方法。
最后,在调用 drawable.draw()
时,将会执行 Rectangle
类中的实现。
* 抽象类(Abstract Class)
抽象类(Abstract Class)是一个不能被实例化的类,可以包含抽象方法和具体方法。抽象方法是没有实现的方法,需要在派生类中进行实现。
下面是一个定义了抽象类及其实现类的代码:
abstract class Shape {
abstract double area();
void display() {
System.out.println("Displaying shape");
}
}
class Circle extends Shape {
double radius;
@Override
double area() {
return Math.PI * radius * radius;
}
}
public class AbstractDemo{
public static void main(String[] args) {
Shape shape = new Circle();
shape.display(); // 输出: "Displaying shape"
double area = shape.area(); // 调用 Circle 中的 area 方法
System.out.println(area);
}
}
运行结果:
Displaying shape
12.566370614359172
这段代码定义了一个抽象类 Shape
和一个继承自 Shape
的子类 Circle
。
抽象类Shape
中包含两部分内容:
1、 abstractdoublearea();
:这是一个抽象方法,没有具体的实现,子类必须提供实现该方法的目的是计算图形的面积,并返回一个double
类型的值;
2、 voiddisplay()
:这是一个普通方法,它有具体的实现;
子类Circle
继承自 Shape
,它具有一个 radius
属性和一个重写了父类抽象方法 area()
的具体实现。在 Circle
类中,area()
方法使用圆的半径计算并返回其面积。
在main
方法中,首先创建了一个 Circle
对象赋值给父类 Shape
类型的引用变量 shape
。然后调用了 shape
的 display()
方法,输出字符串 “Displaying shape”。接着,通过 shape
调用 area()
方法,实际上将调用到 Circle
类中重写的 area()
方法,计算并返回了圆的面积。
* 小结:接口和抽象类的区别?
在Java中,接口和抽象类都可以用来定义对象的公共行为,但它们之间存在一些重要的区别。
具体区别说明如下:
- 定义的关键字不同:接口使用 interface 来定义,抽象类使用 abstract class来定义;
- 子类继承或实现关键字不同:接口实现使用 implements,抽象类继承使用 extends ;
- 扩展限制不同:抽象类有单继承局限,而接口是多继承,这也使得接口更加灵活;
- 方法权限修饰符:抽象类无限制,只是不能被 private 修饰;而接口默认且只能是 public 修饰;
- 属性权限修饰符:抽象类无限制,而接口默认且只能是 public 修饰;
- 结构不同:抽象类中可以有构造方法、成员变量(变量及常量)、普通方法、抽象方法;而接口中只能有常量(默认 public static final 修饰)、抽象方法,在 JDK 8 加入了 static 修饰的静态方法和 default 修饰的普通方法;
- 静态代码块的使用不同:抽象类可以有静态代码块,而接口不能有;
- 设计目的不同:抽象类主要是用于代码复用和简化设计,用于表示某个类的通用行为;而接口是一组规范,用于指导类的行为,以便多个类可以遵循同样的规范进行操作。
6️⃣ 包和访问修饰符
Java中的包(Package)是将相关类和接口组织在一起的方式。它们提供了命名空间管理、可见性控制和代码结构组织的好处。
访问修饰符(Access Modifier)用于控制对类、方法和变量的访问权限。
- public:可以从任何地方访问;
- protected:同一包内和子类中可访问;
- default(默认,无修饰符):同一包内可访问;
- private:仅在当前类中可访问。
下面这段代码展示了Java中的访问修饰符(access modifiers)的概念。
我分别定义了在本类、同包下的其他类、不同包的子类、不同包的其他类四种类,对于一个类中各种权限修饰符修饰的属性的访问权限。
以下是每个类和方法的详细代码及解释:
package com.xiaoshan.accessmodifier.onepack;
import com.xiaoshan.accessmodifier.anotherpack.AnotherPackClass;
import com.xiaoshan.accessmodifier.anotherpack.AnotherPackSonClass;
public class OneClass {
public String publicVariable = "Public Variable";
protected String protectedVariable = "Protected Variable";
String defaultVariable = "Default Variable";
private String privateVariable = "Private Variable";
public void method(){
String variable = this.publicVariable;
System.out.println("本类可以访问到:" + variable);
variable = this.protectedVariable;
System.out.println(variable);
variable = this.defaultVariable;
System.out.println(variable);
variable = this.privateVariable;
System.out.println(variable);
}
public static void main(String[] args) {
OneClass clazz = new OneClass();
clazz.method();
SamePackClass samePackClass = new SamePackClass();
samePackClass.method();
AnotherPackSonClass anotherPackSonClass = new AnotherPackSonClass();
anotherPackSonClass.method();
AnotherPackClass anotherPackClass = new AnotherPackClass();
anotherPackClass.method();
}
}
以上,我定义了一个OneClass
公开类。其中,定义了一个公开(public)变量 publicVariable
、一个受保护(protected)变量 protectedVariable
、一个默认访问修饰符(没有访问修饰符)的变量 defaultVariable
、一个私有(private)变量 privateVariable
。
然后定义了一个方法 method()
,用于演示不同访问修饰符下的变量可见性。可以发现在本类方法内部可以访问到任何权限的变量和方法: variable
可以访问到 publicVariable
、protectedVariable
、defaultVariable
以及 privateVariable
。
package com.xiaoshan.accessmodifier.onepack;
public class SamePackClass {
public void method() {
OneClass oneClass = new OneClass();
String variable = oneClass.publicVariable;
System.out.println("\n同包的类可以访问到:" + variable);
variable = oneClass.protectedVariable;
System.out.println(variable);
variable = oneClass.defaultVariable;
System.out.println(variable);
// 编译不通过,访问不到
// oneClass.privateVariable;
}
}
以上,我定义了一个 SamePackClass
的和oneClass
同包下的类。在method()
方法内部创建了 OneClass
的实例,可以发现能访问到 publicVariable
、protectedVariable
以及defaultVariable
,但无法访问 privateVariable
,因为它是 OneClass
中的私有变量。
package com.xiaoshan.accessmodifier.anotherpack;
import com.xiaoshan.accessmodifier.onepack.OneClass;
public class AnotherPackSonClass extends OneClass {
public void method() {
String variable = super.publicVariable;
System.out.println("\n不同包的子类可以访问到:" + variable);
variable = super.protectedVariable;
System.out.println(variable);
// 编译不通过,访问不到
// variable = super.defaultVariable;
// variable = super.privateVariable;
}
}
以上,我定义了一个 AnotherPackSonClass
的类,它继承自 OneClass
类。
可以发现,在method()
方法内部能通过super
访问到 父类中的 publicVariable
、protectedVariable
。
但无法访问 defaultVariable
和 privateVariable
,它们分别是 OneClass
中的默认变量和私有变量。
package com.xiaoshan.accessmodifier.anotherpack;
import com.xiaoshan.accessmodifier.onepack.OneClass;
public class AnotherPackClass {
public void method() {
OneClass oneClass = new OneClass();
String variable = oneClass.publicVariable;
System.out.println("\n不同包的无关类可以访问到:" + variable);
// 编译不通过,访问不到
// oneClass.protectedVariable;
// oneClass.defaultVariable;
// oneClass.privateVariable;
}
}
以上,我定义了一个 AnotherPackClass
的和 OneClass
不同包且无继承关系的类。
可以发现,在method()
方法内部通过创建的 OneClass
的实例只能访问到 publicVariable
,因为它是公开变量。
而无法访问 protectedVariable
、defaultVariable
和 privateVariable
,它们分别是 OneClass
中的受保护、默认和私有变量。
最后,运行结果:
本类可以访问到:Public Variable
Protected Variable
Default Variable
Private Variable
同包的类可以访问到:Public Variable
Protected Variable
Default Variable
不同包的子类可以访问到:Public Variable
Protected Variable
不同包的无关类可以访问到:Public Variable
7️⃣ 构造函数和析构函数
构造函数(Constructor)是用于初始化对象的特殊方法。它们与类具有相同的名称,并且没有返回类型。当创建对象时,构造函数会被自动调用。如果不提供构造函数,则编译器会生成一个默认的无参构造函数。
析构函数(Destructor)是在对象被销毁之前调用的方法。Java没有显式的析构函数,而是通过垃圾回收器(Garbage Collector)自动处理内存的释放。
public class Person {
String name;
int age;
// 构造函数
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// ...
}
以上就是Java面向对象的全部知识的详细介绍。通过理解和应用这些核心概念,您将能够更好地进行面向对象的编程,在Java中开发出高质量的应用程序。
8️⃣ 成员变量和局部变量
成员变量(Instance Variables)和局部变量(Local Variables)是编程语言中两种不同作用域的变量。
* 成员变量
成员变量属于一个类或对象,并且在类的声明中定义。它们存在于整个类的生存周期内,即使没有被初始化,也会有默认值。每个类的实例都拥有一组独立的成员变量。特点:
- 成员变量的作用域为整个类,在类内部的方法、构造函数里都可以使用;
- 成员变量可以被所有该类的对象访问和修改;
- 成员变量不需要显式地进行初始化,如果没有赋初值,会有一个默认值;
- 每个实例都有独立的一套成员变量。
示例:
class Car {
String color; // 成员变量
int speed; // 成员变量
void drive() {
System.out.println("正在驾驶" + color + "颜色的车,时速为" + speed + "km/h");
}
}
在上述示例中,color
和speed
是Car类的成员变量,可以被类中的所有方法访问和修改。
* 局部变量
局部变量仅在定义它的代码块(方法、循环等)内有效,超出该范围后将无法访问。局部变量必须显式地声明和初始化,否则编译器会报错。特点:
- 局部变量的作用域仅限于声明它的代码块内部;
- 只有在声明期间和初始化后才能使用;
- 每次代码块执行时都会创建一个新的局部变量,该变量只在当前代码块中有效。
示例:
void calculateSum() {
int a = 5; // 局部变量
int b = 10; // 局部变量
int sum = a + b;
System.out.println("两个数的和为:" + sum);
}
在上述示例中,a
和b
是calculateSum
方法中的局部变量,只能在该方法内部访问和使用,在方法执行完毕后将无法访问。
总之,成员变量是属于类的实例的,存在整个类的生命周期中,而局部变量只在定义它的代码块内有效,其作用范围更加有限。
* 成员变量和局部变量的区别?
变量定义时,根据变量所在的代码块范围分为成员变量(也叫实例变量或对象变量)和局部变量两种。
成员变量指定义在类中方法外的变量,局部变量则是定义在方法体或代码块内部的变量。
成员变量和局部变量的主要区别在于作用域和生命周期不同。成员变量拥有更长的生命周期和更广泛的作用域,而局部变量的生命周期和作用域均限定在其声明的代码块内或方法调用过程中。
具体区别细节如下:
- 从语法形式上看,成员变量是属于类或对象的,而局部变量是在方法中定义的变量或方法参数;
- 从修饰符上看,成员变量可以被 权限修饰符等其他关键字所修饰,而局部变量不能被权限修饰符及static所修饰,但二者都能被final所修饰;
- 从变量在内存中的存储方式及生命周期来看,成员变量是对象的一部分:被static修饰的成员变量也叫类变量,存储于方法区当中,生命周期与当前类相同;未被static修饰的成员变量也叫实例变量,存储于对象所在的堆内存当中,生命周期与对象相同。而局部变量存储于栈内存中,其生命周期随方法的调用结束而结束,变量空间会自动的释放;
- 从初始值上看,成员变量若未被赋初值,则会自动以对应数据类型的默认值来赋值(一种情况例外:被final修饰但没有被static修饰的成员变量,必须显式地赋值)。而局部变量则不会自动赋初值;
- 从使用范围上看, 局部变量只能在声明它的方法或代码块内使用,不能被其他方法或代码块访问。
* 总结
本文主要总结介绍了面向对象编程(OOP)和面向过程编程(PP)各自的编程思想范式特征,面向对象中类和对象的概念关系,面向对象编程的三大特性:封装、继承和多态。
以及类中一些相关概念及其区别:方法重载和方法重写、接口和抽象类、包和访问修饰符。此外,还包括类中成员的大致介绍:构造函数和析构函数、成员变量和局部变量。