面向对象编程(上)

1.面向过程和面向对象

  • 面向对象:Object Oriented Programming
  • 面向过程:Procedure Oriented Programming

二者都是一种思想,面向对象是相对于面向过程而言的。面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做。面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。

面向对象的三大特征:封装,继承,多态

2.类和对象

2.1 面向对象的思想概述

  • 类(Class)和对象(Object)是面向对象的核心概念。
    • 类是对一类事物的描述,是抽象的、概念上的定义
    • 对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。

类是对一类事物的描述,是抽象的。 对象是一类事物的实例,是具体的类是对象的模板,对象是类的实体

2022-09-07_185219

2.2 Java类及类的成员

现实世界的生物体,大到鲸鱼,小到蚂蚁,都是由最基本的细胞构成的。同理,Java 代码世界是由诸多个不同功能的类构成的。

现实生物世界中的细胞又是由什么构成的呢?细胞核、细胞质、… 那么,Java 中用类 class 来描述事物也是如此。常见的类的成员有:

  • 属性:对应类中的成员变量
  • 行为:对应类中的成员方法

类的成员构成 version 1.0

2022-09-07_185552

类的成员构成verson 2.0

2022-09-08_104327

2.3 类的语法格式

2022-09-08_104527

举例:

public class Person{
	private int age ; //声明私有变量 age
	public void showAge(int i) { //声明方法showAge( )
		age = i; 
    } 
}

2.4 对象的创建和使用

创建对象:

类名 对象名 = new 类名();

使用对象访问类中的成员:

对象名.成员变量; 
对象名.成员方法()

举例:

Animal类:

public class Animal {
	public int legs;
	public void eat(){
		System.out.println(Eating.);
	}
	public viod move(){
		System.out.println(Move.);
	}
}

Zoo类:

public class Zoo{
	public static void main(String args[]){
	//创建对象
	Animal xb=new Animal();
	xb.legs=4;//访问属性
	System.out.println(xb.legs);
	xb.eat();//访问方法
	xb.move();//访问方法
	} 
}

2022-09-08_105220

2.5 成员变量的默认值

数据类型默认值
基本类型整数(byte,short,int,long)0
浮点数(float,double)0.0
字符(char)'\u0000'
布尔(boolean)false
引用类型数组,类,接口null

2.6 对象内存解析

  • 堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
  • 通常所说的栈(Stack),是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char 、 short 、 int 、 float 、 long 、double)、对象引用(reference类型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。
  • 方法区(Method Area),用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

2022-09-08_113515

一个对象,调用一个方法内存图

2022-09-08_110225

通过上图,我们可以理解,在栈内存中运行的方法,遵循"先进后出,后进先出"的原则。变量p指向堆内存中

的空间,寻找方法信息,去执行该方法。

但是,这里依然有问题存在。创建多个对象时,如果每个对象内部都保存一份方法信息,这就非常浪费内存

了,因为所有对象的方法信息都是一样的。那么如何解决这个问题呢?请看如下图解。

两个对象,调用同一方法内存图

2022-09-08_110417

对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。这样哪怕是多个对象,方法信息

只保存一份,节约内存空间。

一个引用,作为参数传递到方法中内存图

2022-09-08_110553

引用类型作为参数,传递的是地址值。

3.类的成员之一:属性

3.1 语法格式:

修饰符 数据类型 属性名 = 初始化值 ;

修饰符

常用的权限修饰符有:private、缺省、protected、public

其他修饰符:static、final (暂不考虑)

数据类型

任何基本数据类型(如int、Boolean) 或 任何引用数据类型

属性名:

属于标识符,符合命名规则和规范即可

3.2 成员变量与局部变量

  • 在方法体外,类体内声明的变量称为成员变量。

  • 在方法体内部声明的变量称为局部变量。

2022-09-08_111714

注意:二者在初始化值方面的异同

**同:**都有生命周期

**异:**局部变量除形参外,均需显式初始化。

3.2.1 成员变量(属性)和局部变量的区别

2022-09-08_111904

3.2.2 对象属性的默认初始化赋值

当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。

2022-09-08_112201

4.类的成员之二:方法

4.1 语法格式

修饰符 返回值类型 方法名(参数类型 形参1, 参数类型 形参2,.){
	方法体程序代码
	return 返回值;

修饰符:

public,缺省,private, protected等

返回值类型

  • **没有返回值:**void

  • 有返回值:声明出返回值的类型。与方法体中“return返回值”搭配使用

方法名:

属于标识符,命名时遵循标识符命名规则和规范,“见名知意”

形参列表

可以包含零个,一个或多个参数。多个参数时,中间用“,”隔开

返回值:

方法在执行完毕后返还给调用它的程序的数据。

4.2 方法的分类

2022-09-08_113014

注 意:

  • 方法被调用一次,就会执行一次

  • 没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中可以不必使用return语句。如果使用,仅用来结束方法。

  • 定义方法时,方法的结果应该返回给调用者,交由调用者处理。

  • 方法中只能调用方法或属性,不可以在方法内部定义方法。

5.再谈方法

5.1 方法的重载

5.1.1 重载的概念

在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。

5.1.2 重载的特点

与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。

例如,System.out.println()方法就是典型的重载方法,其内部的声明形式如下:

public void println(byte x)
public void println(short x)
public void println(int x)
public void println(long x)
public void println(float x)
public void println(double x)
public void println(char x)
public void println(double x)
public void println()
……

5.2 可变个数的形参

JavaSE 5.0 中提供了**Varargs(variable number of arguments)**机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。

  • JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a ,String[] books);
  • JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String…books);

说明:

  1. 声明格式:方法名(参数的类型名 ...参数名)

  2. 可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个

  3. 可变个数形参的方法与同名的方法之间,彼此构成重载

  4. 可变参数方法的使用与方法参数部分使用数组是一致的

  5. 方法的参数部分有可变形参,需要放在形参声明的最后

  6. 在一个方法的形参位置,最多只能声明一个可变个数形参

5.3 方法参数的值得传递机制

方法,必须由其所在类或对象调用才有意义。若方法含有参数:

形参:方法声明时的参数

**实参:**方法调用时实际传给形参的参数值

Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。

  • 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参

  • 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参

5.3.1 基本数据类型的参数传递

public static void main(String[] args) {
int x = 5;
System.out.println("修改之前x = " + x);// 5
// x是实参
change(x);
System.out.println("修改之后x = " + x);// 5
}
public static void change(int x) {
System.out.println("change:修改之前x = " + x);
x = 3;
System.out.println("change:修改之后x = " + x);
}

2022-09-08_115841

5.3.2 引用数据类型的参数传递

public static void main(String[] args) {
	Person obj = new Person();
	obj.age = 5;
	System.out.println("修改之前age = " + obj.age);// 5
	// x是实参
	change(obj);
	System.out.println("修改之后age = " + obj.age);// 3
	}
	public static void change(Person obj) {
	System.out.println("change:修改之前age = " + obj.age);
	obj.age = 3;
	System.out.println("change:修改之后age = " + obj.age);
	}

其中Person类定义为:
class Person{
	int age; 
}

2022-09-08_120007

6.面向对象特征之一:封装与隐藏

6.1 概述

面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。 封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的 方式。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。 将属性隐藏起来,若需要访问某个属性,提供公共方法对其访问。

6.2 封装的步骤

  1. 使用 private 关键字来修饰成员变量。

  2. 对需要访问的成员变量,提供对应的一对 getXxx 方法 、 setXxx 方法。

class Animal {
	private int legs;// 将属性legs定义为private,只能被Animal类内部访问
	public void setLegs(int i) { // 在这里定义方法 eat() 和 move()
		if (i != 0 && i != 2 && i != 4) {
			System.out.println("Wrong number of legs!");
			return; 
        }
		legs = i; 
    }
	public int getLegs() {
		return legs; 
    } 
}

public class Zoo {
public static void main(String args[]) {
	Animal xb = new Animal();
	xb.setLegs(4); // xb.setLegs(-1000);
	//xb.legs = -1000; // 非法
	System.out.println(xb.getLegs());
	} 
}

6.3 四种访问权限修饰符

Java权限修饰符public、protected、(缺省)、private置于类的成员定义前,用来限定对象对该类成员的访问权限。

2022-09-08_124618

对于class的权限修饰只可以用public和default(缺省)。

  • public类可以在任意地方被访问。

  • default类只可以被同一个包内部的类访问。

6.4 封装优化1:this关键字

this代表所在类的当前对象的引用(地址值),即对象自己的引用。

记住 :方法被哪个对象调用,方法中的this就代表那个对象。即谁在调用,this就代表谁。

使用 this 修饰方法中的变量,解决成员变量被隐藏的问题,代码如下:

public class Student { 
    private String name; 
    private int age; 
    public void setName(String name) { 
        //name = name; 
        this.name = name; 
    }
    public String getName() { 
        return name; 
    }
    public void setAge(int age) { 
        //age = age; 
        this.age = age; 
    }
    public int getAge() { 
        return age;
    } 
}

小贴士:方法中只有一个变量名时,默认也是使用 this 修饰,可以省略不写。

6.5 封装优化2:构造方法

当一个对象被创建时候,构造方法用来初始化该对象,给对象的成员变量赋初始值。

小贴士:无论你与否自定义构造方法,所有的类都有构造方法,因为Java自动提供了一个无参数构造方法,

一旦自己定义了构造方法,Java自动提供的默认无参数构造方法就会失效。

举例:

public class Student { 
    private String name; 
    private int age; 
    // 无参数构造方法 
    public Student() {} 
    // 有参数构造方法 
    public Student(String name,int age) { 
        this.name = name; 
        this.age = age; 
    } 
}

7.类的成员之三:构造器

7.1 构造器的特征

  • 它具有与类相同的名称

  • 它不声明返回值类型。(与声明为void不同)

  • 不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值

7.2 构造器的作用

创建对象,给对象初始化。

  • 如:Order o = new Order(); Person p = new Person(“Peter”,15);

  • 如同我们规定每个“人”一出生就必须先洗澡,我们就可以在“人”的构造器中加入完成“洗澡”的程序代码,于是每个“人”一出生就会自动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们要“洗澡”了。

语法格式:

修饰符 类名 (参数列表) {
	初始化语句;
}

举例:

创建Animal类的实例:Animal a = new Animal();调用构造器,将legs初始化为4。

2022-09-08_125250

根据参数不同,构造器可以分为如下两类:

  • 隐式无参构造器(系统默认提供)

  • 显式定义一个或多个构造器(无参、有参)

注 意:

  • Java语言中,每个类都至少有一个构造器

  • 默认构造器的修饰符与所属类的修饰符一致

  • 一旦显式定义了构造器,则系统不再提供默认构造器

  • 一个类可以创建多个重载的构造器

  • 父类的构造器不可被子类继承

7.3 构造器重载

构造器一般用来创建对象的同时初始化对象。如

class Person{
	String name;
	int age;
	public Person(String n , int a){ 
        name=n; 
        age=a;
    }
}

构造器重载使得对象的创建更加灵活,方便创建各种不同的对象。

public class Person{
	public Person(String name, int age, Date d) {this(name,age);}
	public Person(String name, int age) {}
	public Person(String name, Date d) {}
	public Person(){}
}

构造器重载,参数列表必须不同

public class Person { 
private String name;
private int age;
private Date birthDate;
public Person(String n, int a, Date d) {
	name = n;
	age = a;
	birthDate = d; 
}
public Person(String n, int a) {
	name = n;
	age = a; 
}
public Person(String n, Date d) {
	name = n;
	birthDate = d; 
}
public Person(String n) {
	name = n;
	age = 30;
} 
}

7.4 总结:属性赋值过程

截止到目前,我们讲到了很多位置都可以对类的属性赋值。现总结这几个位置,并指明赋值的先后顺序。

  • 赋值的位置:

① 默认初始化

② 显式初始化

③ 构造器中初始化

④ 通过“对象.属性“或“对象.方法”的方式赋值

  • 赋值的先后顺序:

    ① - ② - ③ - ④

8.JavaBean

JavaBean 是 Java语言编写类的一种标准规范。是一种Java语言写成的可重用组件。符合 JavaBean 的类,要求类必须是具体的和公共的,并且具有无参数的构造方法,提供用来操作成员变量的 set 和 get 方法。

所谓javaBean,是指符合如下标准的Java类:

  • 类是公共的

  • 有一个无参的公共的构造器

  • 有属性,且有对应的get、set方法

用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。

以学生类为例,标准代码如下:

public class Student { 
    //成员变量
    private String name; 
    private int age; 
    //构造方法 
    public Student() {} 
    public Student(String name,int age) { 
        this.name = name; 
        this.age = age; 
    }
    //成员方法 
    public void setName(String name) { 
        this.name = name; 
    }
    public String getName() { 
        return name; 
    }
    publicvoid setAge(int age) { 
        this.age = age; 
    }
    public int getAge() { 
        return age; 
    } 
}

9.关键字:this

  • 在Java中,this关键字比较难理解,它的作用和其词义很接近。

    • 它在方法内部使用,即这个方法所属对象的引用;
    • 它在构造器内部使用,表示该构造器正在初始化的对象。
  • this 可以调用类的属性、方法和构造器

  • 什么时候使用this关键字呢?

    • 当在方法内需要用到调用该方法的对象时,就用this。
    • 具体的:我们可以用this来区分属性和局部变量。比如:this.name = name;
  1. 在任意方法或构造器内,如果使用当前类的成员变量或成员方法可以在其前面添加this,增强程序的阅读性。不过,通常我们都习惯省略this。

  2. 当形参与成员变量同名时,如果在方法内或构造器内需要使用成员变量,必须添加this来表明该变量是类的成员变量

  3. 使用this访问属性和方法时,如果在本类中未找到,会从父类中查找

  4. this可以作为一个类中构造器相互调用的特殊格式

注意:

  • 可以在类的构造器中使用"this(形参列表)"的方式,调用本类中重载的其他的构造器!
  • 明确:构造器中不能通过"this(形参列表)"的方式调用自身构造器
  • 如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了"this(形参列表)"
  • "this(形参列表)"必须声明在类的构造器的首行!
  • 在类的一个构造器中,最多只能声明一个"this(形参列表)"

10.关键字:package

10.1 概述

package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。它的格式为:

package 顶层包名.子包名 ;

**举例:**pack1\pack2\PackageTest.java

package pack1.pack2; //指定类PackageTest属于包pack1.pack2
public class PackageTest{
	public void display(){
	System.out.println("in method display()");
	} 
}
  • 包对应于文件系统的目录,package语句中,用“.” 来指明包(目录)的层次;

  • 包通常用小写单词标识。通常使用所在公司域名的倒置:com.baidu.xxx

包的作用:

  • 包帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式

  • 包可以包含类和子包,划分项目层次,便于管理

  • 解决类命名冲突的问题

  • 控制访问权限

10.2 MVC设计模式

MVC是常用的设计模式之一,将整个程序分为三个层次:视图模型层控制器层,与数据模型层。这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。

2022-09-08_133318

2022-09-08_133431

10.3 JDK中主要的包介绍

1. java.lang----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能

2. java.net----包含执行与网络相关的操作的类和接口。

3. java.io **----**包含能提供多种输入/输出功能的类。

4. java.util----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。

5. java.text----包含了一些java格式化相关的类

6. java.sql----包含了java进行JDBC数据库编程的相关类/接口

7. java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。 B/S C/S

11.关键字:import

11.1 概述

为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类(.*)。import语句告诉编译器到哪里去寻找类。

语法格式

import 包名.类名;

举例

import pack1.pack2.Test; //import pack1.pack2.*;表示引入pack1.pack2包中的所有结构
public class PackTest{
public static void main(String args[]){
	Test t = new Test(); //Test类在pack1.pack2包中定义
	t.display();
	} 
}

注意:

  1. 在源文件中使用import显式的导入指定包下的类或接口

  2. 声明在包的声明和类的声明之间。

  3. 如果需要导入多个类或接口,那么就并列显式多个import语句即可

  4. 举例:可以使用java.util.*的方式,一次性导入util包下所有的类或接口。

  5. 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。

  6. 如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的是哪个类。

    import java.util.*;
    Date date = new Date();
    java.sql.Date date1 = new java.sql.Date(5243523532535L);
    
  7. 如果已经导入java.a包下的类。那么如果需要使用a包的子包下的类的话,仍然需要导入。

  8. import static组合的使用:调用指定类或接口下的静态的属性或方法

    import static java.lang.System.*;
    import static java.lang.Math.*;
    out.println("hello");
    long num = round(123.434);