面向对象(上)
文章目录
面向对象概述
面向对象是一种符合人类思维习惯的编程思想.现实生活中存在各种形态不同的事物,这些事物之间存在着各种各样的联系.在程序中使用对象来映射现实生活中的事物,使用对象的关系来描述事物之间的联系,这种思想就叫做面向对象.
面向对象的特点可以概括为封装
,继承
,多态
.
- 封装
封装是面向对象的核心思想,将对象的属性和行为封装起来,不需要让外界知道具体实现细节,这就是封装思想.
- 继承
继承主要描述的是类与类之间的关系,通过继承,可以在不需要编写原有类的基础下,对原有类的功能进行扩展
- 多态
多态指的是一个类中定义的属性和功能被其他类继承后,当把子类对象直接赋值给父类引用变量,相同引用类型的变量调用同一个方法所呈现出的多种不同行为特征.例如,当听到cut这个单词,理发师的行为表现是剪发,而演员的行为表现是停止表演.
Java中的类与对象
面向对象的编程思想力图在程序中对事物的描述与该事务在现实中的形态保持一致.
类与对象的关系
类是对某一类事物的抽象描述,而对象用于表示现实中该类事物的个体.(类是抽象的,对象是具体的)
类的定义
在程序中创建对象,首先需要定义一个类.
以面向对象的编程思想,就可以将某一类中共同的特征和行为封装起来,把共同特征作为类的属性(也叫成员变量),把共同行为作为类的方法(也叫成员方法).
- 类的定义格式
Java中的类是通过class关键字来定义的,其语法格式如下:
[修饰符] class 类名 [extends 父类名] [implement 接口名] {
// 类体,包括类的成员变量和成员方法
}
修饰符可写可不写(default);类名要符合标识符的命名规范;extends用于说明所定义的类继承于哪个父类;implements关键字用于说明当前类实现了哪些接口,具体后面会说,这里不做赘述.
- 声明(定义)成员变量
类的成员变量也被称作类的属性,它主要用于描述对象的特征,声明成员变量的语法格式如下:
[修饰符] 数据类型 变量名 [=值];
修饰符为可选项,用于指定变量的访问权限;数据类型可以是Java中的任意类型;变量名是变量的名称,必须符合标识符的命名规则;变量可以被赋值,也可以不赋值,未赋值的变量被称为声明变量,赋值的变量被称为定义变量.
- 声明(定义)成员方法
成员方法也被称为方法,类似于C语言中的函数,它主要用于描述对象的行为.定于方法的语法格式如下:
[修饰符] [返回值类型] 方法名 ([参数类型 参数名1], [参数类型 参数名2], …) {
//方法体
…
return 返回值; // 返回值类型为void,返回值类型及其返回值可以忽略
}
上面的语法格式中,[]表示可选,各部分的解释如下:
- 修饰符: 对访问权限进行限定的(如public,protected,private); 静态修饰符static; 最终修饰符final;
- 返回值类型: 限定调用方法返回值的数据类型,不需要返回值时可以使用void关键字
- 参数类型: 限定调用方法时传入参数的数据类型
- 参数名: 是一个变量,接收调用方法时传入的数据
- return: 用于结束方法以及返回方法指定类型的值,返回类型为void时,return及其返回值可以忽略
- 返回值: 被return返回的值,该值会返回给调用者
// 定义一个Person类
public class Person {
String name; //未赋值的成员变量,声明变量
int age = 0; //赋值了的成员变量,定义变量
void speak() { // 成员方法,返回类型为void
System.out.println("我叫" +name +"我今年" +age +"岁!");
}
}
对象的创建与使用
Java程序中可以使用new关键字来创建对象,具体语法格式如下:
类名 对象名称 = new 类名();
例如:
Person me = new Person();
new对象的本质是向堆申请内存空间.
Java中的内存分为两种,即栈内存和堆内存,其中栈内存用于存放基本类型变量,堆内存用于存放由new创建的对象和数组.
Person类型的变量me被存放在栈内存中,它是一个引用,会指向真正的对象;通过new Person()创建的对象则存放在堆内存中,这才是真正的对象.
通过对象的引用来访问对象所有的成员,语法格式如下:
对象引用.对象成员
public class Example02 {
public static void main(String[] args) {
Person p1 = new Person(); //创建第一个Person对象
Person p2 = new Person(); //创建第二个Person对象
p1.age = 18; //访问对象p1的成员变量age,赋值
p1.speak(); //访问对象p1的成员方法speak()
p2.speak(); //访问对象p2的成员方法speak()
}
}
除了对象引用来访问对象成员外,可以直接使用创建的对象本身来引用对象成员,具体格式如下:
new 类名().对象成员
这种方式在通过new关键字创建实例对象的同时就访问了对象的某个成员,并且创建后只能访问其中某一个成员,由于没有对象引用的存在,在完成某一个对象成员的访问后,该对象就会变成垃圾对象.
实例化对象时,Java虚拟机会自动为成员变量进行初始化,针对不同类型的成员变量赋予不同的初始值,如下表所示
成员变量类型 | 初始值 | 成员变量类型 | 初始值 |
---|---|---|---|
byte | 0 | double | 0.0 |
short | 0 | char | 空字符’\u0000’ |
int | 0 | boolean | false |
long | 0 | 引用数据类型 | null |
float | 0.0 |
访问控制符
在Java中,针对类,成员方法和属性提供了4种访问级别,分别是private,default,protected和public
下图将4种控制级别由小到大依次列出:
- private
当前类访问级别: 如果类的成员被private访问修饰符修饰,则这个类只能由该类的其它成员来访问,其他类无法直接访问 - default
包访问级别: 一个类或者一个类的成员不适用任何修饰符修饰,则它为默认访问控制级别(package-private),其只能被本包中的其他类访问 - protected
子类访问级别: 如果一个类的成员被protected访问控制符修饰,那么这个成员既能被同一包下的其他类访问,也能被不同包下的该类的子类访问 - public
公共访问级别: 这个类或者类的成员能被所有的类访问
访问范围 | private | default | protected | public |
---|---|---|---|---|
同一类中 | ✔️ | ✔️ | ✔️ | ✔️ |
同一包中 | ✔️ | ✔️ | ✔️ | |
子类中 | ✔️ | ✔️ | ||
全局范围 | ✔️ |
tips: 如果一个源文件中定义了一个public修饰符类,这个源文件的文件名必须与public修饰的类名相同;如果Java源文件中的类没有使用public修饰,这个Java源文件的文件名可以使用任意合法文件名
类的封装
类的封装,是指将对象的状态隐藏在对象内部,不允许外部程序直接访问对象的内部信息,而是通过该类所提供的方法来实现对内部消息的操作访问.
封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别.
为什么需要封装
- 提高代码的重用性
- 提高代码的可维护性
- 解决数据存取的权限问题
如何实现封装
在定义一个类时,将类中的属性私有化,即使用private关键字修饰,私有属性只能在它所在类中被访问,如果外界想要访问私有属性,需要提供一些使用public修饰的公有方法,其中包括用于获取属性值的getXxx()方法和设置属性值的setXxx()方法
class Person{
private String name;
private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) {
if(age <= 0)
System.out.println("您输入的年龄不正确!");
else
this.age = age;
}
public void speak() { System.out.println("我叫" +name +"今年" +age +"岁!"); }
}
public class Example04{
public static void main(String[] args){
Person p = new Person();
p.setName("张三");
p.setAge(-18);
p.speak();
}
}
方法的重载和递归
方法的重载
Java允许在一个程序中定义多个名称相同,但是参数的类型或个数不同的方法,这就是方法的重载(需要在一个类中).
方法的重载必须满足3个条件:
- 方法必须在同一类中
- 方法名必须相同
- 方法的形参列表不同
* 注意: 方法的重载与返回值类型无关
public class Example06 {
public static void main(String[] args) {
int sum1 = add(1, 2);
int sum2 = add(1, 2, 3);
double sum3 = add(0.2, 5.3);
System.out.println("sum1=" +sum1);
System.out.println("sum2=" +sum2);
System.out.println("sum3=" +sum3);
}
public static int add(int num1, int num2) {
return num1 + num2;
}
public static int add(int num1, int num2, int num3) {
return num1 + num2 + num3;
}
public static double add(double num1, double num2) {
return num1 + num2;
}
}
方法的递归
方法的递归是指在一个方法的内部调用自身的过程.
递归必须要有结束条件,否则会陷入无限递归的状态,永远无法结束调用.
递归会大量消耗栈内存
public class Example07 {
public static void main(String[] args) {
int sum = getSum(4); //调用递归方法,获取1-4的和
System.out.println)("sum=" +sum);
}
public static int getSum(int num) {
if (num == 1){ return 1; } //满足条件,递归结束
int temp = getSum(n-1);
return temp + num;
}
}
构造方法
构造方法(构造器)是类中的一个特殊成员,构造方法会在类实例化对象时被自动调用,并且在实例化对象的同时就为这个对象的属性进行赋值.
构造方法的定义
构造方法需要满足3个条件:
- 方法名与类名相同
- 没有返回值类型的声明
- 不能使用return语句返回一个值(可以使用return作为方法的结束)
[修饰符] 方法名 ([参数列表]) {
方法体
}
class Person {
public Person() { // 无参构造方法
System.out.println("调用了无参的构造方法");
}
}
public class Example08 {
public static void main(String[] args) {
Person p = new Person();
//通过new Person()实例化Person对象时会自动调用该类的无参构造方法
}
}
class Person {
int age;
public Person(int num) { age = num; } //有参构造方法
public void speak() {
System.out.println("我今年" +age +"岁");
}
}
public class Example09{
public static void main(String[] args) {
Person p = new Person(18);
// 调用有参构造方法,传入int参数18,对age属性进行赋值
p.speak();
}
}
构造方法的重载
与普通方法一样,构造方法也可以重载.
一个类中的多个构造方法(参数列表的不同)是重载的.
public class Example10 {
public static void main(String[] args) {
Person p1 = new Person(18);
Person p2 = new Person("张三", 32);
p1.speak();
p2.say();
}
}
class Person {
String name;
int age;
public Person(){} //默认的无参构造方法
public Person(int num) { age = num; } //有参构造方法1
public Person(String name, int age) { //有参构造方法2
this.name = name;
this.age = age;
}
public void say() {
System.out.println("我叫" +name +"今年" +age +"岁");
}
public void speak() {
System.out.println("我今年" +age +"岁");
}
}
Java中的每个类都至少有一个构造方法,虽然如果没有显示地定义构造方法,虚拟机会自动为这个类创建一个默认的构造方法,但是一旦为该类定义了构造方法,系统将不会提供默认的无参构造方法.
所以,如果定义了有参的构造方法,最好再定义一个无参的构造方法.
* 不写构造方法,Java虚拟机在编译时会自动加上无参构造方法(这个默认的构造方法没有参数,其方法体中没有任何代码)
* 构造方法可以使用private修饰,但是可能出现"The constructor Xxx() is not visible",即构造方法不可见,无法从类的外部访问,所以构造方法通常使用public
// 使用private修饰构造方法,以下为jdk中Runtime的源码:
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
// Don't let anyone else instantiate this class
private Runtime() {
}
}
/*
* 使用private修饰构造方法,其他的类就不能直接调用该类生成新的对象,避免同一个类被反复创建,
* 这其实是单例模式的设计思想,在该种思想模式下,一个类只能对应一个对象,
* 没有其他类可以创建新的对象,也就保证了单例模式下只有一个对象
**/
this关键字
Java中提供了一个关键字this来指代当前对象,用于在方法中访问对象的其他成员.
this的常见用法:
- 通过this关键字调用成员变量,解决与局部变量名称冲突问题
class Person {
int age; // 成员变量age
public Person(int age) { // 传入参数-局部变量age
this.age = age; // 将传入参数(局部变量)age的值赋给成员变量age
}
}
- 通过this关键字调用成员方法
class Person {
public void speak() {
System.out.println("说话!");
}
public void say() {
this.speak(); // 因为speak()在同一类中,这里的this可以省略不写
}
}
- 通过this关键字调用构造方法
class Person {
public Person() { //无参构造方法
System.out.println("无参构造方法");
}
public Person(int age) { //有参构造方法
this(); //调用无参构造方法
// 可以在一个构造方法中使用"this([参数1,参数2,...])"的形式来调用其他的构造方法
System.out.println("有参构造方法");
}
}
public class Example11{
public static void main(String[] args){
Person p = new Person(18); //实例化Person对象
}
}
在使用this调用类的构造方法时,需要注意以下几点:
- 只能在构造方法中使用this调用其他的构造方法,不能在成员方法中使用
- 在构造方法中,使用this调用构造方法的语句必须是该方法的第一条执行语句,且只能出现一次
- 不能在一个类的两个构造方法中使用this互相调用
static关键字
Java中,定义了一个static关键字,它用于修饰类的成员,如成员变量,成员方法以及代码块等
- 静态类: static class School{…}
- 静态变量: static int age;
- 静态方法: static void say(){…}
静态变量
有时候,开发人员会希望某些特定数据在内存中只存在一份,而且能够被一个类的所有实例对象所共享
static关键字只能用于修饰成员变量,不能用于修饰局部
访问静态变量的语法:
类名.变量名
或者
实例对象.变量名
class Student{
static String schoolName; //声明静态变量schoolName
}
public class Example12 {
public static void main(String[] args){
Student stu1 = new Student();
Student stu2 = new Student();
Student.schoolName = "清华大学"; //为静态变量赋值
System.out.println("我是" +stu1.schoolName +"的学生");
System.out.println("我是" +stu2.schoolName +"的学生");
}
}
静态方法
不创建对象的情况下就可以调用某个方法
在一个静态方法中只能访问用static修饰的成员
,原因在于没有被static修饰的成员需要先创建对象才能访问,而静态方法在被调用时可以不创建任何对象
访问静态方法的语法格式:
类名.方法
或者
实例对象名.方法
class Person{
public static void say(){
System.out.println("Hello World!");
}
}
public class Example13{
public static void main(String[] args){
Person.say(); // 用"类名.方法名"的方式来调用静态方法
Person person = new Person(); // 实例化对象
person.say(); // 用"实例对象名.方法"的方式来调用静态方法
}
}
静态代码块
在Java中,使用一对大括号围起来的若干行代码被称为一个代码块,用static修饰的代码块被称为静态代码块
静态代码块语法格式如下:
static {
…
}
当类被加载时,静态代码块就会被执行,由于类只加载一次,因此静态代码块也只会执行一次
在程序中,经常使用静态代码块来对类的成员变量进行初始化
class Person{
static{
System.out.println("执行了Person中静态代码块");
}
}
public class Example14 {
static{
System.out.println("执行了Example14中的静态代码块");
}
public static void main(String[] args){
Person p1 = new Person();
Person p2 = new Person();
}
}
执行了Example14中的静态代码块
执行了Person中的静态代码块
在两次实例化对象的过程中,两个静态代码块的内容都只输出了一次,Example14中的静态代码块是类在加载时执行,而Person中的静态代码块是第一次实例化Person对象时才执行,并且只执行了一次,第二次实例化对象并不会再执行Person中的静态代码块.
一个代码块,用static修饰的代码块被称为静态代码块
静态代码块语法格式如下:
static {
…
}
当类被加载时,静态代码块就会被执行,由于类只加载一次,因此静态代码块也只会执行一次
在程序中,经常使用静态代码块来对类的成员变量进行初始化
class Person{
static{
System.out.println("执行了Person中静态代码块");
}
}
public class Example14 {
static{
System.out.println("执行了Example14中的静态代码块");
}
public static void main(String[] args){
Person p1 = new Person();
Person p2 = new Person();
}
}
执行了Example14中的静态代码块
执行了Person中的静态代码块
在两次实例化对象的过程中,两个静态代码块的内容都只输出了一次,Example14中的静态代码块是类在加载时执行,而Person中的静态代码块是第一次实例化Person对象时才执行,并且只执行了一次,第二次实例化对象并不会再执行Person中的静态代码块.