面向对象是一种程序设计方法,但不表示世界上所有的开发者都认同面向对象。因为有很多开发者认为面向对象过于复杂,所以更多人愿意使用函数式编程。
面向对象的前身属于面向过程,如果想要笼统的解释这两种开发模式的区别,最好的例子:面向过程是解决问题,而面向对象是模块化设计。对于现在的程序开发,更多情况下就像汽车组装一样,不同的工厂生产不同的配件。将所有的零件组装在一起,就可以想成一辆汽车,并且当某一个零件坏了的时候,还可以进行配装。面向过程缺少了可重用设计。
面向对象的基本特征:
在面向对象开发的时候有三个阶段:OOA(面向对象分析)、OOD(面向对象设计)、OOP(面向对象编程)。
所有的程序的开发原则都离不开实际生活,从程序的开发角度讲,面向对象可以进行生活的抽象。面向对象的概念需要通过大量的代码进行理解和验证,在Java中不光要考虑面向对象,还要考虑内存分配问题。
类与对象是整个面向对象设计的核心所在,也是所有概念的基础。类本身属于引用数据类型,所以类的使用上会牵扯到内存的分配。
所谓的类描述的就是共性特征。任何时候只是依靠共性的特征是无法进行精准描述的,那么就需要一些个性化特点。就可以描述一个个独立的事物。所以这样就可以推断出:类是一个共性的概念,而对象指的是一个具体的可以使用的事物。
很明显,在实际的开发过程中一定需要首先产生类,而后才可以产生对象。那么对象的所有操作行为都一定在类中进行的完整定义。类中没有定义的功能,对象一定无法使用。
类中的组成:方法(操作的行为)、属性(变量,描述每一个对象的具体特点)。
如果在程序之中要进行类的定义可以使用class关键字完成,定义语法如下:
类的定义语法 |
---|
class 类名称 { // 所有的程序都要求以“ {} ”作为分界符 属性 1; 属性 2; 方法(); } |
此时的方法不再由主类进行调用,而是要通过对象进行调用。
范例:定义类
代码
class Person { // 定义一个类,类名称每个单词首字母要求大写
String name; // 表示人的姓名
int age; // 表示人的年龄
public void info () {
System.out.println ("name = " + name + ",age = " + age);
}
}
在这个类中,只定义了两个属性和一个方法,需要注意的是,类中可以定义的属性数量和方法数量是没有限制的,而且在编写的时候,每一个方法中的代码尽量不要特别长。
类定义完成之后是不能够直接去使用的,如果要想使用类那么必须产生对象,而对象的定义分为以下两种语法形式:
引用数据类型的最大特征在于内存的分配操作,而只要出现有关键字new,那么就只有一个解释:开辟新内存(内存不可能无限开辟,所以所谓的性能调优调整的就是内存问题)。
所有的对象只有在实例化之后才可以真正使用。而对象的使用都是围绕着类进行的,那么此时就有两种形式:
范例:声明并实例化对象
代码
class Person { // 定义一个类,类名称每个单词首字母要求大写
String name; // 表示人的姓名
int age; // 表示人的年龄
public void info () {
System.out.println ("name = " + name + ",age = " + age);
}
}
public class Test {
public static void main (String args[]) {
// 类名称 对象名称 = new 类名称();
Person per = new Person(); // 实例化了一个per对象
per.name = "张三"; // 设置对象中属性
per.age = 18; // 设置对象中属性
per.info(); // 调动类中的方法
}
} // 输出结果:name = 张三 、 age = 18
以上就实现了一个最基本的类的定义以及对象的使用操作形式。
如果要想进行对象的产生分析,那么首先就必须清楚引用类型。引用类型指的是内存空间的操作。对于现在的内存,主要使用两块内存空间:
对于对象的产生一共会有两种格式,现在使用的是声明并实例化对象方式,也可以使用分步方式完成。
代码
public class Test {
public static void main (String args[]) {
Person per = null; // 声明一个新的对象
per = new Person(); // 实例化对象per
per.name = "张三"; // 设置对象中属性
per.age = 18; // 设置对象中属性
per.info(); // 调动类中的方法
}
}
千万记住:
对象(所有的引用数据类型)必须在其开辟空间之后才可以使用。如果使用了为开辟内存空间的引用数据类型,则将出现NullPointerException。
代码
class Person { // 定义一个类,类名称每个单词首字母要求大写
String name; // 表示人的姓名
int age; // 表示人的年龄
public void info () {
System.out.println ("name = " + name + ",age = " + age);
}
}
public class Test {
public static void main (String args[]) {
Person per = null; // 声明一个新的对象
per.name = "张三"; // 设置对象中属性
}
}
这个时候只声明了对象,而没有为其开辟对内存空间,此时编译不会出错,但是执行后会报出如下错误提示:
错误提示 |
---|
Exception in thread “main” java.lang.NullPointerException at Test.main(Test.java:13) |
NullPointerException,只有引用数据类型(数组、类、接口),才能产生此类异常,以后出这类错误,就在错误位置观察该对象是否实例化。
对于初学者来说最难的部分就是引用传递分析。以后的开发都是引用传递。
引用的本质就在于别名,而这个这个别名只不过是放在了栈内存之中,即:一块堆内存可以被多个栈内存所指向。
范例:观察一个引用传递
代码
class Person { // 定义一个类,类名称每个单词首字母要求大写
String name; // 表示人的姓名
int age; // 表示人的年龄
public void info () {
System.out.println ("name = " + name + ",age = " + age);
}
}
public class Test {
public static void main (String args[]) {
// 类名称 对象名称 = new 类名称();
Person per1 = new Person(); // 实例化了一个per1对象
per1.name = "小于子"; // 设置对象中属性
per1.age = 30; // 设置对象中属性
// 此步骤就是引用传递的操作
Person per2 = per1; // 采用同样的类型接收
per2.name = "狗剩"; // 设置一个名字
per1.info(); // 调动类中的方法
}
} // name = 狗剩,age = 30
范例:观察一个引用传递
代码
class Person { // 定义一个类,类名称每个单词首字母要求大写
String name; // 表示人的姓名
int age; // 表示人的年龄
public void info () {
System.out.println ("name = " + name + ",age = " + age);
}
}
public class Test {
public static void main (String args[]) {
// 类名称 对象名称 = new 类名称();
Person per1 = new Person(); // 实例化了一个per1对象
per1.name = "小于子"; // 设置对象中属性
per1.age = 30; // 设置对象中属性
Person per2 = new Person();
per2.name = "张三";
per2.age = 20;
// 此步骤就是引用传递的操作
per2 = per1; // 采用同样的类型接收
per2.name = "狗剩"; // 设置一个名字
per1.info(); // 调动类中的方法
}
} // name = 狗剩,age = 30
在程序开发过程中,所谓的垃圾空间指的是没有任何栈内存指向的堆内存空间,所有的垃圾空间将不定期被Java中的垃圾收集器(GC,Garbage Collector)回收以实现内存空间的释放,不过从实际开发来讲,虽然Java会提供GC,但是GC会造成程序性能的下降,所以开发过程里一定要控制好对象的产生数量,即:无用的对象尽可能少产生。
封装是整个Java最复杂的概念,本次讲解的也只是最基本的概念。
如果没有封装会怎么样?
范例:观察如下的一段程序
代码
public class Test {
public static void main (String args[]) {
Person per = new Person();
per.age = -200; // 年龄设置为-200
}
}
这时程序不会出现任何语法错误,int的取值允许取-200,但是没有一个人的年龄会是-200,这就属于业务逻辑出错。
此时若想回避此类问题,那么首先要解决的就是如何让对象不能够直接操作年龄的属性,或者指的是让类的外部如何不能够操作类中的敏感内容。所以此时解决问题的核心观念:如何让内部的操作对外部不可见,此时就可利用private关键字来实现。
范例:利用private类实现封装
代码
class Person {
private String name;
private int age;
public void info () {
System.out.println ("name = " + name + ",age = " + age);
}
}
public class Test {
public static void main (String args[]) {
Person per = new Person();
per.name = "张三";
per.age = -200;
per.info();
}
}
类中的属性和方法都可用private定义,但是很少在方法上使用private。一旦属性的声明上使用了private定义之后,那么如果其它类直接进行该属性访问的时候,就将出现如下错误提示:
错误提示 |
---|
Test.java:13: 错误: name可以在Person中访问private per.name = “张三”; Test.java:14: 错误: age可以在Person中访问private per.age = -200; |
此时使用了private声明之后属性安全了,外部无法直接操作了,但是新的问题又来了。现在如果想要进行private私有属性的访问,按照Java的设计原则,就可以使用setter、getter方法。
范例:扩展Person类中的内容
代码
class Person {
private String name;
private int age;
public void setName(String n){
name = n;
}
public void setAge(int a){
if (age >= 0 && age <= 200) { //检测年龄
age = a;
}else{
age = 0;
}
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void info () {
System.out.println ("name = " + name + ",age = " + age);
}
}
public class Test {
public static void main (String args[]) {
Person per = new Person();
per.setName("张三");
per.setAge(200);
per.info();
}
} // 输出结果:name = 张三,age = 0
如果非要进行检测,可以在setter里完成,但是意义不大。
类的设计原则:
在编写类的时候类的属性必须使用private封装。而使用private封装的属性,如果需要被外部所使用,那么就按照格式定义相应的setter、getter方法。
private实现封装的最大特征在于:只允许本类访问,不允许外部访问。且private只是封装的第一步。
实例化对象产生格式:
对于以上格式观察组成部分:
构造方法指的是在使用关键字new实例化新对象的时候类进行调用的操作方法,定义构造方法的原则如下:方法名称必须与类名称相同,并且构造方法没有返回值类型声明,同时,每一个类中一定会至少存在有一个构造方法,如果类中没有明确的定义任何一个构造方法,那么将自动生成一个无参的,什么都不做的构造方法。
范例:定义一个无参的、什么都不做的构造方法
代码
class Person {
private String name;
private int age;
// 构造方法名称与类名称相同,并且没有返回值类型声明
public Person() { // 如果你现在的类中不写词定义也会在变异后自动生成
System.out.println("************************");
}
public void setName(String n){
name = n;
}
public void setAge(int a){
if (age >= 0 && age <= 200) { //检测年龄
age = a;
}else{
age = 0;
}
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void info () {
System.out.println ("name = " + name + ",age = " + age);
}
}
public class Test {
public static void main (String args[]) {
Person per = new Person();
per.setName("张三");
per.setAge(200);
per.info();
}
} // 输出结果:
// ************************
// name = 张三,age = 0
构造方法在new时调用,且一个对象只能new一次,也就是说只能调用一次。
疑问:既然构造方法中没有返回数据,为什么不使用void定义?
对于类中可以自动生成的无参构造方法前提:类中没有定义任何构造方法,如果类中已经定义了构造方法,那么此类默认的构造方法将不会自动生成。
范例:类中定义一个有参构造方法
代码
class Person {
private String name;
private int age;
// 定义一个有参构造,则默认的无参构造将不会在编译时自动生成
public Person(String n,int a) {
name = n; // 也可以写setName(n);
setAge(a);
}
public void setName(String n){
name = n;
}
public void setAge(int a){
if (age >= 0 && age <= 200) { // 检测年龄
age = a;
}else{
age = 0;
}
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void info () {
System.out.println ("name = " + name + ",age = " + age);
}
}
public class Test {
public static void main (String args[]) {
// Person per = new Person(); 此代码出错
Person per = new Person("张三",20);
per.info();
}
} // 输出结果:name = 张三,age = 20
构造方法作用:
构造方法也可以重载,构造方法名与类名相同,无返回值类型,因此只能实现参数的类型或个数不同的重载。
范例:构造方法重载
代码
class Person {
private String name;
private int age;
public Person() {}
public Person(String n){
name = n;
}
public Person(String n,int a) {
name = n; // 也可以写setName(n);
setAge(a);
}
public void setName(String n){
name = n;
}
public void setAge(int a){
if (age >= 0 && age <= 200) { // 检测年龄
age = a;
}else{
age = 0;
}
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void info () {
System.out.println ("name = " + name + ",age = " + age);
}
}
public class Test {
public static void main (String args[]) {
Person per = new Person("张三");
per.setAge(18);
per.info();
}
} // 输出结果:name = 张三,age = 18
重载时注意定义结构:建议若干个构造方法按照参数个数的升序或降序排列。
请按照如下顺序进行类定义:①属性;②构造方法;③普通方法。
范例:匿名对象
代码
public class Test {
public static void main (String args[]) {
new Person("张三",20).info();
}
} // 输出结果:name = 张三,age = 20
匿名对象不会被任何栈内存指向,使用一次后就会成为垃圾。
总结:
1.构造方法每个类中至少存在一个;
2.构造方法名称与类名称相同,无返回值类型;
3.构造方法重载只考虑参数的类型或个数不同即可。
简单Java开发要求:
范例:编写程序类
代码
class Emp { // 类名称可以明确描述出某一类事物
private int empno;
private String ename;
private String job;
private double sal;
private double comm;
public void setEmpno(int eno) {
empno = eno;
}
public void setEname(String ena) {
ename = ena;
}
public void setJob(String j) {
job = j;
}
public void setSal(double s) {
sal = s;
}
public void setComm(double c) {
comm = c;
}
public int getEmpno() {
return empno;
}
public String getEname() {
return ename;
}
public String getJob() {
return job;
}
public double getSal() {
return sal;
}
public double getComm() {
return comm;
}
public String getInfo() {
return "empno = " + empno + "\n" + "ename = " + ename + "\n" + "job = " + job + "\n" + "sal = " + sal + "\n" + "comm = " + comm;
}
public Emp(){};
public Emp(int eno,String ena,String j)
{
setEmpno(eno);
setEname(ena);
setJob(j);
}
}
public class Test {
public static void main (String args[]) {
System.out.println(new Emp(1234,"SJZ","CLERK").getInfo());
}
} /* 输出结果:
empno = 1234
ename = SJZ
job = CLERK
sal = 0.0
comm = 0.0 */