一、 类和对象
1.1 面向对象与面向过程的区别
1.面向过程
采用面向过程必须了解整个过程,每个步骤都有因果关系,每个因果关系都构成了一个步骤,多个步骤就构成了一个系统,因为存在因果关系每个步骤很难分离,非常紧密,耦合度高,当任何一步骤出现问题,将会影响到所有的系统。如:采用面向过程生产电脑,那么他不会分CPU、主板和硬盘,它会按照电脑的工作流程一次成型。
2.面向对象
面向对象对会将现实世界分割成不同的单元(对象),实现各个对象,如果完成某个功能,只需要将各个对象协作起来就可以。
1.2 面向对象的三大特征
1. 封装
2. 继承
3. 多态
1.3 类与对象的概念
类是对具有共性事物的抽象描述,是在概念上的一个定义,那么如何发现类呢?通常根据名词(概念)来发现类,如在成绩管理系统中:学生、班级、课程、成绩
学生—张三
班级—Java1059
课程—JavaSE
成绩—张三成绩
以上“张三”、“Java1059”、“JavaSE”和“张三的成绩”他们是具体存在的,称为对象,也叫实例,就是说一个类的具体化(实例化),就是对象或实例。
为什么面向对象成为主流技术,主要就是因为更符合人的思维模式,更容易的分析现实世界,所以在程序设计中也采用了面向对象的技术,从软件的开发的生命周期来看,基于面向对象可以分为三个阶段:
OOA(面向对象的分析) Object-OrientedAnalysis
OOD(面向对象的设计) Object-OrientedDesign
OOP(面向对象的编程) Object Oriented Programming
Java 就是一个纯面向对象的语言
我们再进一步的展开,首先看看学生:
学生:学号、姓名、性别、地址,班级
班级:班级代码、班级名称
大家看到以上我们分析出来的都是类的属性
接下来采用简易的图形来描述一下,来描述我们的概念
通过以上分析,大家应该了解:
类=属性+方法
属性来源于类的状态,而方法来源于动作
以上模型完全可以使用面向对象的语言,如Java 来实现
1.1 如何定义一个类
在Java中如何定义一个类!格式如下。
[类的访问修饰符] class 类名 extends 父类名称 implements 接口名称{ 类体:包括下面两方面 属性 方法 } |
定义学生类型Student,是对现实中的学生的一种抽象,是不存在的,是概念上的定义。注意:Student是一个类,是引用类型。
public class Student{ // 属性 // 学生学号 // 成员变量,非静态变量 // 成员变量是对象级别的,必须先存着对象才有意义,才能访问 // 成员变量不能使用类名.的方式进行访问 int id; // 学生姓名 String name; // 学生性别 String sex; // 学生年龄 int age; // 家庭住址 String address; // 方法 public void study(){ System.out.println("学习Java "); } } |
1.2 如何创建一个对象
在Java中如何创建一个对象呢!必须使用new关键字,当对象创建完成后,对于成员变量或成员方法才可以访问或调用,下面的例子。
public class ClassTest01 { public static void main(String[] args){ // 创建对象 // 注意:stu1对象是局部变量,是Student01类型的局部变量 // 变量stu1不是基本数据类型,是引用类型 // 引用类型保存的是对象在堆内存中的地址 // 我们通过这个引用来访问堆内存中的对象 Student01 stu1 = new Student01(); // 一个类可以创建多个对象 // 可以说,所有的学生都可以是Student01类的对象 Student01 stu2 = new Student01(); // 使用对象访问成员属性 // 访问成员属性,必须使用引用.的方式进行访问 // 注意:成员变量不能采用类名.的方式进行访问 System.out.println("id: " + stu1.id); System.out.println("name: " + stu1.name); System.out.println("age: " + stu1.age); System.out.println("sex: " + stu1.sex); System.out.println("address: " + stu1.address); // 使用对象为成员变量进行赋值 // 成员变量的读取和赋值都是采用引用.的方式 stu1.id = 1001; stu1.name = "张三"; stu1.age = 21; stu1.sex = "男"; stu1.address = "太原小店区"; // 通过对象读取成员变量的值 System.out.println("id: " + stu1.id); System.out.println("name: " + stu1.name); System.out.println("age: " + stu1.age); System.out.println("sex: " + stu1.sex); System.out.println("address: " + stu1.address); } } class Student01{ int id; String name; int age; String sex; String address; } |
一个类可以创建多个个对象,成员变量只属于当前的对象(每个对象拥有自己的成员属性,所以成员属性只属于特定的对象,不属于类),所以只有通过对象才可以访问成员变量,通过类不能直接访问成员变量。
注意:stu1只是引用,指向堆内存中对象的地址,我们只能通过这个引用对内存中的对
象进行访问。每一个对象会在内存中开创一块空间,每个对象的属性也只能属于自己的
对象,所以成员变量是属于对象的或实例的。
上面程序存在缺点,所有的成员属性全部公开,如:年龄可以赋值为负数!如何才能控制成员属性不能随意的赋值!
1.1 面向对象的封装性
如何对成员属性的值进行保护和限制,使成员变量的更安全!通过下面的例子说明。
public class ClassTest02 { public static void main(String[] args){ // 创建对象 Student02 stu1 = new Student02(); // 读取成员变量 System.out.println("age: " + stu1.age); // 使用对象为成员变量进行赋值 //stu1.age = 21; // 在程序中对age属性没有任何限制,外部程序可以随意访问属性age, // 导致age不安全。这种可以随意修改或赋值不合法的数据可能造成程序的 // 安全隐患,如何才能有效的限制不合法的数据呢! stu1.age = -1; // 通过对象读取成员变量的值 System.out.println("age: " + stu1.age); } } class Student02{ int id; String name; int age; String sex; String address; } |
在上面的例子中可以看到,程序中没有对成员属性进行任何的保护,可以随意的访问,并且将非法的值随意的赋给成员变量,这可能造成整个程序的执行结果发生错误。如何才能对成员变量进行保护呢!可以为每个成员变量的访问权限进行限制,在每个成员属性前加上访问修饰符private,这样就只有在本类中才可以访问成员属性,类的外部是不能直接对成员属性进行访问的,这样成员属性就受到保护了,如下例子。
public class ClassTest03 { public static void main(String[] args){ // 创建对象 Student03 stu1 = new Student03(); // 读取成员变量 // 编译不能通过,成员属性访问受到限制 // System.out.println("age: " + stu1.age);; // 使用对象为成员变量进行赋值 // 编译不能通过,成员属性访问受到限制 // stu1.age = -1; // 通过对象读取成员变量的值 // 编译不能通过,成员属性访问受到限制 // System.out.println("age: " + stu1.age); } } class Student03{ private int id; private String name; private int age; private String sex; private String address; } |
通过对属性访问进行限制,在类的外部不能直接操作成员属性了,成员属性更安全了,但是,如果不能对成员属性进行访问,实际上这个类对其他类来说就无意义了,所以,既能访问成员变量,又能对成员属性进行保护,如何才能实现呢!那就是封装性的第二个办法了,为每个成员属性创建两个公共的方法,一个可以为成员属性进行赋值,另一个可以获取成员属性的值,如下例子。
public class ClassTest04 { public static void main(String[] args){ // 创建对象 Student04 stu1 = new Student04(); // 使用对象为成员变量进行赋值 stu1.setId(1001); stu1.setName("张三"); stu1.setAge(-21); stu1.setSex("男"); stu1.setAddress("太原小店区"); // 通过对象读取成员变量的值 System.out.println("id: " + stu1.getId()); System.out.println("name: " + stu1.getName()); System.out.println("age: " + stu1.getAge()); System.out.println("sex: " + stu1.getSex()); System.out.println("address: " + stu1.getAddress()); } } class Student04{ private int id; private String name; private int age; private String sex; private String address; public int getId() { return id; } public void setId(int id) { this.id = id; } 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 && age <= 100){ this.age = age; }else{ System.out.println("非法数据!"); this.age = 0; } } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } } |
从上面的示例可以看出,采用方法可以控制赋值的过程,加入了对年龄的检查,避免了外部直接操纵成员属性,这就是封装,封装其实就是封装属性,让外界知道这个类的状态越少越好。属性只能通过方法访问,在方法中对属性的值进行限制就是封装。通过方法我们就可以控制对内部状态的读取权利。封装属性,公开方法。所以,将属性私有化,并且提供公共的方法对属性进行访问就是Java的封装。
提供的方法有setter和getter两种,setter方法是给属性赋值的,而getter方法是取得属性值的。关于这两个方法是有规范的,setter方法是:set + 属性名称(第一个字母大写)。getter方法是:get + 属性名称(第一个字母大写)。
1.1 类的构造函数
构造函数也称作构造方法或构造器,英文为Constructor。构造方法也是一个类的组成部分,语法格式如下。
[方法访问修饰符列表] 构造方法名(形式参数列表){ 方法体; } |
如何调用构造方法!
只能通过new关键字调用构造方法,调用会创建类的实例,过程是在堆内存开辟空间并保存类的实例。
回顾:静态方法必须使用类名.的方式进行调用。
成员方法必须使用引用.的方式进行调用。
回顾:静态方法必须使用类名.的方式进行调用。
成员方法必须使用引用.的方式进行调用。
第一个例子:创建一个类的实例或对象时实际上是调用了类的构造方法。
public class ClassTest04 { public static void main(String[] args){ // 创建对象 Student04 stu1 = new Student04(); } } class Student04{ private int id; private String name; //构造方法 public Student04() { System.out.println("创建一个类的" + "实例或对象时实际上是调用了类的构造方法"); } } |
构造方法修饰词列表:public、protected和private。
第二个例子:构造方法的访问修饰符的调用限制,public和private的区别。
public class ClassTest05 { public static void main(String[] args){ // 创建对象 Student04 stu1 = new Student04(); //不能编译通过,构造方法被private修饰访问不到 //Student05 stu2 = new Student05(); } } class Student04{ private int id; private String name; //构造方法 public Student04() { System.out.println("构造方法被public修饰"); } } class Student05{ private int id; private String name; //构造方法 private Student05() { System.out.println("构造方法被private修饰"); } } |
构造方法和普通方法一样可以重载。因为形式参数列表可以有零到多个参数。
第三个例子:构造方法可以重载,可以为不同的成员属性进行赋初值。
public class ClassTest06 { public static void main(String[] args){ // 创建对象 Student06 stu1 = new Student06(); Student06 stu2 = new Student06(1001,"张三"); } } class Student06{ private int id; private String name; //构造方法 public Student06() { System.out.println("构造方法被public修饰"); } //含参数的构造方法 public Student06(int id, String name) { this.id = id; this.name = name; } } |
构造方法的特点:
1. 构造方法的名称必须和类名相同。
2. 构造方法不能存在返回值。构造方法不具有任何返回值类型,即没有返回值,关键字void也不能存在,如果存在void关键字就不是构造方法了,就是普通方法了。
3. 任何类都有构造函数,如果一个类没有显示的定义构造函数,系统会为该类定义一个默认的构造器,这个构造器不含任何参数(无参的构造函数),如果在类中显示的定义了构造器(有参数的或无参数的构造方法),系统就不会再创建默认的无参构造器,如果需要必须自己创建。通常情况下,如果在类中手动添加了带参数的构造函数,那么也会手动的添加一个无参数的构成方法。
第四个例子:如果类没有显示的构造方法,系统会默认创建一个无参数的构造方法,如果显示的创建了一个构造方法,系统则不会在创建默认的无参数构造方法。
构造方法的作用是什么!
1. 创建对象。
2. 创建对象时为成员属性赋初值。
成员属性的赋值时机!
成员属性声明后不用手动赋初值既可以使用,因为成员属性必须实例进行调用,在创建实例或对象时,必须通过new 构造方法()的方式创建对象,系统会为成员属性赋初值。
如果调用无参数的构造方法,系统会为成员属性赋默认值,也可以通过带参数的构造方法在创建对象的时候为成员变量赋初值。
第五个例子:通过构造函数为成员属性进行赋初值。
1.1 类的实例和引用
1.1.1 Java中的内存概述
Java虚拟机启动后的内存概述。
1.1.1 Java实例的创建
Student类:
public class Student { private String id; private String name; private int age; private String sex; public static void main(String[] args){ //创建st对象 Student st = new Student(); //st对象成员变量赋值 st.id = "1001"; st.name = "张三"; st.age = 23; st.sex = "男"; } } |
第一步,JVM调用执行main 方法,将main 方法压入栈,然后new Student 对象
//创建一个对象 Student st = new Student(); |
第二步,对st实例的属性进行赋值
st.id = "1001"; st.name = "张三"; st.age = 23; st.sex = "男"; |
1.1.1 实例引用为null时出现情况
一个类的实例必须通过new关键字创建后才能访问成员属性或成员方法,如果对象没有创建即调用成员,会出现异常现象,下面通过例子说明。
第六个例子:如果不使用new关键字创建对象即使用引用.的方式访问成员属性或成员方法出现的问题。
注意:当一个对象没有引用指向他,则这个在内存中的对象将成为垃圾,等待垃圾回收器回收。
内存说明:
1.1 参数传递
在程序执行过程中可以进行参数的传递,参数类型分为基本数据类型和引用类型,下面分别进行说明。
1.1.1 参数类型为基本数据类型
参数的类型是基本数据类型,那么传递的是基本数据类型的值。形参的变量是局部变量,那么传递的值就赋给这个局部变量,在方法中对这个局部变量如何操作都不会影响都调用处变量的值。下面通过一个例子说明。
例1:ParameterTest01
public class ParameterTest01 { public static void main(String[] args){ int i = 10; add(i); System.out.println("调用处局部变量i的值:" + i); } public static void add(int i){ i *= 2; System.out.println("局部变量i的值: " + i); } } |
值传递:
1.1.1 参数类型为引用数据类型
参数是引用类型,传递的是对象的引用,也就是对象在堆内存中的地址。那么通过参数操作实际上是操作的是内存中的对象。下面通过一个例子说明。
例2:ParameterTest02
public class ParameterTest02 { public static void main(String[] args){ Student stu = new Student("张三",20); modify(stu); System.out.println("学生姓名:" + stu.name + " 年龄:" + stu.age); } public static void modify(Student stu){ stu.name = "李四"; stu.age = 23; } } class Student{ public String name; public int age; public Student(String name,int age){ this.name = name; this.age = age; } } |
通过一张图说明:
引用类型的参数传递的是对象在内存中的地址,实际上操作的是内存中的对象,所以对象的属性值被modify()方法改变了。
1.1 Java中的this关键字
this关键字在Java的类中代表什么含义!
1.this是一个引用,指向当前对象在内存中的引用(地址)。就是当前对象。
2.创建的每一个Java对象中都有一个this引用。
3.this引用保存的是当前对象在内存中的地址,每一个Java对象中都有一个this引用指向对象自身。
第一个例子:创建的对象在内存中,每个对象中都有一个this引用保存的是当前对象的内存地址。
public class Student { private String id; private String name; private int age; private String sex; public static void main(String[] args){ //创建str1对象 Student str1 = new Student(); // str1对象成员变量赋值 str1.id = "1001"; str1.name = "张三"; str1.age = 23; str1.sex = "男"; //创建str2对象 Student str2 = new Student(); // str2对象成员变量赋值 str2.id = "1002"; str2.name = "李四"; str2.age = 22; str2.sex = "女"; } } |
用图说明:
this关键字能用在哪些地方!
1.this可以用在成员方法中,代表当前对象。this保存当前对象的引用,指向当前对象在内存中的地址。
第二个例子:this关键字用在成员方法中。(this关键字不能用在静态方法中)
public class ClassTest06 { public static void main(String[] args){ // 创建对象 Student06 stu1 = new Student06(); Student06 stu2 = new Student06(1001,"张三"); stu2.method(); } } class Student06{ private int id; private String name; //构造方法 public Student06() { } //含参数的构造方法 public Student06(int id, String name) { this.id = id; this.name = name; } //成员方法 public void method(){ this.id = 8888; System.out.println(id); } } |
第三个例子:this关键字可以区分成员属性和局部变量。
public class ClassTest06 { public static void main(String[] args){ // 创建对象 Student06 stu1 = new Student06(); stu1.method(); } } class Student06{ private int id; private String name; //构造方法 public Student06() { } //成员方法 public void method(){ String name = "局部变量"; this.name = "成员属性"; System.out.println("name:" + name); System.out.println("this.name" + this.name); } } |
第四个例子:this关键字不能使用在静态方法中,因为静态方法的执行根本不需要Java对象的存在。直接使用类名.的方式访问。而this代表的是当前对象。所以在静态方法中根本就没有当前对象。
2.this可以用在构造方法中,用来调用当前类中其他的构造函数。
语法:this(实参);
通过在构造方法调用当前类中另一个构造方法来实现代码重用。
注意:通过this(实参);调用当前类中其他构造函数的语句必须出现在构造方法的第一
行。否则编译不能通过。
第五个例子:this关键字用在构造方法中用来调用其他的构造方法,好处是代码可以重用。但是要注意,如果在构造函数中通过this调用其他的构造函数,this语句必须是构造方法中的第一行。
public class ClassTest06 { public static void main(String[] args){ // 创建对象 Student06 stu1 = new Student06(); System.out.println(stu1.id + stu1.name); } } class Student06{ int id; String name; //构造方法 public Student06() { this(1001,"张三"); } //带参构造 public Student06(int id, String name) { this.id = id; this.name = name; } } |
1.1 Java中的static关键字
在Java中,static修饰符可以修饰:变量、方法和代码块。
1.static修饰的变量叫做静态变量。
2.static修饰的方法叫做静态方法。
3.static还可以定义静态语句块。
1.1.1 静态语句块
用static 修饰的变量和方法,可以采用类名.的方式进行访问。
用static声明的代码块为静态代码块,执行时机是类加载阶段,在程序入口程序main()方法执行之前执行,只执行一次,是自上而下的顺序执行(常用于初始化工作,创建对象)。
静态语句块的语法格式:
static{ 语句块; } |
第一个例子:创建静态语句块,静态语句块在类加载时执行,只执行一次。
因为静态变量、静态语句块在类加载阶段执行,所以,变量和语句块声明的顺序是有关系的。
第二个例子:静态变量和静态语句块的声明顺序
1.1.2 实例语句块
实例语句块,每一次调用构造方法之前会执行一次。实例语句块执行顺序也是自上而下。
实例语句块语法格式:
{ 语句块; } |
第三个例子:创建实例语句块。
1.1.3 静态方法
static修饰的方法叫做静态方法,也成为类方法。通常情况下工具类中的方法大部分都是静态方法,不需要创建对象既可以直接以类名.的方式进行访问,不需要创建对象。
第三个例子:静态方法使用类名.的方式进行访问,静态方法中可以直接调用静态属性,静态方法中不能访问成员,如果要访问成员需要创建实例,所以在静态方法中没有this的概念。
1.1.4 静态变量
static修饰的变量叫做静态变量,也成为类变量。
在什么情况下将变量声明成静态变量!如果这个属性所有的对象都有,并且这个属性的值是相同的,具有共性的属性值的属性,则该属性声明成静态的属性。
第四个例子:静态变量在方法区,不在堆区。所有的对象共享。
成员变量在创建Java对象的时候初始化。而静态变量在类加载阶段赋值,并且只赋值一次。因为只保存一份。
1.1.5 静态变量和静态块的执行顺序
在Java中,只有静态变量和静态块在代码中是有顺序的,成员方法是没有顺序的。
第五个例子:静态属性和静态块的执行顺序问题。
总结:我们已经知道在一个类中,可以存在静态方法、静态变量、成员方法、成员属性、构造方法、静态语句块、实例语句块和this关键字。
1.2 单例模式
单例模式是种设计模式中最简单的一种设计模式。是较为常见的一种设计模式,单例模式就是在任何时候都保证某个Java类的实例只有一个,主要是为了节省内存空间。通常情况下,工具类一般采用单例模式,就一个对象,所有对这个工具类中的成员方法的调用都是通过这个唯一的实例调用的。
如何实现单例模式!实现单例模式的原则:
1.构造方法私有化。
2.对外提供一个公开的静态的获取当前类型对象的方法。
3.提供一个当前类型的私有的静态变量。
单例模式分为两种模式:
饿汉式单例:在类加载阶段就创建了对象。
懒汉式单例:用到对象的时候才会创建对象。
第一个例子:创建一个饿汉式单例模式。
package weixin911; /* * 单例模式:保证类在内存中只有一个对象。 * * 如何保证类在内存中只有一个对象呢? * A:把构造方法私有 * B:在成员位置自己创建一个对象 * C:通过一个公共的方法提供访问 */ public class StudentDemo { //饿汉式 public static void main(String[] args) { // Student s1 = new Student(); // Student s2 = new Student(); // System.out.println(s1 == s2); // false // 通过单例如何得到对象呢? // Student.s = null; Student s1 = Student.getStudent(); Student s2 = Student.getStudent(); System.out.println(s1 == s2); System.out.println(s1); // null,[email protected] System.out.println(s2);// null,[email protected] } } class Student { private Student(){}//构造方法私有化 //成员位置自己造一个对象 private static Student s = new Student(); //提供公共的外界访问方式 public static Student getStudent(){ return s; } } |
第二个例子:创建一个懒汉式单例模式。
/* * 单例模式: * 饿汉式:类一加载就创建对象 * 懒汉式:用的时候,才去创建对象 * * 面试题:单例模式的思想是什么?请写一个代码体现。 * * 开发:饿汉式(是不会出问题的单例模式) * 面试:懒汉式(可能会出问题的单例模式) * A:懒加载(延迟加载) * B:线程安全问题 * a:是否多线程环境是 * b:是否有共享数据是 * c:是否有多条语句操作共享数据 是 */ public class TeacherDemo { public static void main(String[] args) { Teacher t1 = Teacher.getTeacher(); Teacher t2 = Teacher.getTeacher(); System.out.println(t1 == t2); System.out.println(t1); // [email protected] } } class Teacher{ private Teacher(){}; private static Teacher t = null; public synchronized static Teacher getTeacher(){ if(t == null){ t = new Teacher(); } return t; } } |