菜鸟学习笔记:Java面向对象篇上
Java面向对象的思想
在之前的教程中往往都会反复强调什么c语言是面向过程的语言啦,Java是面向对象的语言啦,什么面向对象和面向过程有很大区别了。。。。。。感觉总是把人给绕的一头雾水,其实从程序执行流程(这里是程序执行,不包括类加载之类的JVM内部操作)的角度来说Java和C没啥区别,同样是从main函数开始从上往下一行一行依次执行,区别在于Java可以将一部分属性和方法绑定在一起,从而组成一个对象,在主程序中直接对对象进行操作,(专业概念是以类方式组织代码,以对象方式封装数据)这种思想使得我们可以把一个种类的事务进行统一管理,让编程更加符合现实生活。
类是对具体事物的抽象,具有一定相同属性和行为的事务都可以归为一类,在同一类当中又可以根据其事务的不同点进一步划分出子类。下面举例来说明:
生物类是对所有有生命事物的抽象,而生物又可以分为植物、动物和微生物,它们有生物所有的属性和行为,但又有着自己独有的特征,所以它们是生物类的子类。同样的鱼类鸟类也是动物类的子类。
对象则是某个类的具体实例,像小明这个人就属于哺乳类的一个实例,同时也是动物类、生物类的一个实例,人有自己的生活环境、有自己的寿命,也可以成长、繁殖等等生物类所定义的属性都具有,同样动物类所有的属性和行为也都具备,这也是之后要说的多态。
Java类和对象的概念大致就是如此,对象是Java的核心。下面用一个学生的例子展示Java之中的类定义方法:
class Student { //注意类的命名习惯
String name; //属性
int id;
public void study(){ //方法
System.out.println(name + "在学习"); //在使用String类型时"+"代表字符串拼接,与int类型不同。
}
}
我们在在main函数中使用这个类
public static void main(String[] args) {
Student oStu1 = new Student(); //Student属于引用类型,驼峰命名法中一般用o代表对象
Student oStu2 = new Student();
oStu1.name = "小明"; //设置属性
oStu1.id = 123;
oStu2.name = "老王";
oStu2.id = 456;
oStu1.study(); //调用方法会打印小明在学习。
}
这里说明一下属性是类中定义的每个成员应有的值,对每个对象都是不同的,而方法则是每个这一类所以实例都可以进行的操作。
在对象初始化时如果不对属性进行赋值则会保持其默认值。
Java运用引用类型的变量来表示一个对象,通常对象存储在内存中,引用类型通过存储对象的地址来找到对象。
Java程序执行过程内存分析
这一块本打算将类的加载过程也融入进来一块讲解,但写着写着发现不太适合Java新手阅读,所以单独抽出来作为一篇文章,大家想了解可以看 这里。
首先介绍几个概念:
栈
当前执行线程的独占空间,以栈的数据结构出现。线程私有,它保存一个线程方法的调用状态(方法中的变量也在其中),压入栈的单位为栈帧。程序执行时main方法的栈帧先入栈,如果main方法中调用了A方法,那么在执行到A方法时它的栈帧入栈,等A方法执行完成后栈帧出栈,继续执行main方法。
堆
是一块不连续的内存空间,JVM运行过程中最大的一块内容,被所有线程共享,主要用来存放new出来的对象以及数组。
方法区
是堆的一部分,用来存放字面量(文本字符串、八种基本类型的值、被声明为final的常量等)以及符号引用(类和方法的全限定名、字段的名称和描述符、方法的名称和描述符),它是所有的线程共享的一块区域,当方法区不能满足内存分配需求时会报出OutOfMemoryError。
对于类加载过程的说明我们继续以上面学生类为例。方便大家理解简化一下main函数
public static void main(String[] args) {
Student oStu1 = new Student(); //Student属于引用类型,驼峰命名法中一般用o代表对象
oStu1.name = "小明"; //设置属性
oStu1.id = 123;
oStu1.study(); //调用方法会打印小明在学习。
}
首先声明,程序执行时针对的是.class字节码文件,字节码文件我们看不懂,但执行的情况也是按照Java程序中一行一行来的,所以以Java程序来讲解,程序执行过程中首先main函数入栈,从第一行开始执行,当运行到Student oStu1 = new Student();时,程序会调用类加载器加载类。这时在方法区就会有类的信息(注意ststic静态代码块会在这个时候执行)。
类加载完后再堆栈中变量oStu1(oStu1就是引用变量)会被存储。
这样Student oStu1这半句已经执行完,到后半句new Student()时,在堆的非方法区部分会生成Student类的实例,实例中的属性会被赋予属性类型的默认值,方法会指向方法区Student类存储的方法,最后引用变量oStu1会指向这一实例,整个内存结构如图所示:
讲到这里大家应该了解引用变量了,其实就是在这个变量中存储的是对象的地址(实际在那个空间存储的是如十六进制的08A4这样的数,它代表对象在内存中的存储地址,程序根据地址可以找到变量并取值,这个过程图中用箭头表示),我们访问时可以通过地址直接取到对象。
然后程序继续执行,到oStu1.name = "小明"执行时,首先会根据oStu1的地址找到Student对象,然后改变对象中属性的值,oStu1.id = 123也是一个道理。
oStu1.study()首先会根据oStu1的地址找到Student对象,再根据对象的study指针找到方法区中的方法代码,将方法代码压入堆栈执行方法,执行到打印函数时System.out.println()函数入栈,栈结构如下:
输出结束后执行完成后System.out.println()方法出栈,继续执行study()函数,发现函数执行完成,study()函数出栈,继续执行main函数中的代码,然后main函数也执行完成,这时main函数出栈,整个程序结束。
Java垃圾回收机制
上面讲到再用new关键字创建对象时,堆中会分配一块内存给对象,但如果一真建立下去内存肯定会占满,所以及时清理不用的对象释放资源非常必要,在C语言中需要我们在完成操作后手动的释放资源(比如释放链表),在Java中的垃圾回收机制可以帮我们解决这个问题,在当没有引用变量指向一个对象时,这个对象将被Java的垃圾回收器自动清除,比如在上例中我们在添加一句:
public static void main(String[] args) {
Student oStu1 = new Student(); //Student属于引用类型,驼峰命名法中一般用o代表对象
oStu1.name = "小明"; //设置属性
oStu1.id = 123;
oStu1.study(); //调用方法会打印小明在学习。
oStu1 = null; //这时堆中的Student对象就会被垃圾回收器清理掉
}
在Java中程序员无权调用垃圾回收器,虽然可以通过System.gc()方法通知GC运行或者finalize释放资源,但一般情况都靠程序自己来处理。
构造方法
构造方法又称构造器,用于构造类的实例(也就是对象),它是一种特殊的方法,方法名称与类名相同,会在执行new语句时调用。
下面举例来说明,同样是学生类,我们加上构造方法:
class Student { //注意类的命名习惯
String name; //属性
int id;
public Student(String name, int id){ //构造方法
this.name = name;
this.id = id;
}
public void study(){ //方法
System.out.println(name + "在学习"); //在使用String类型时"+"代表字符串拼接,与int类型不同。
}
}
这时实例化对象就可以这样来:
public static void main(String[] args) {
Student oStu1 = new Student("小明",123);
oStu1.study(); //调用方法会打印小明在学习。
}
大家应该明白构造方法是干嘛得了吧,但为什么之前的例子中没有这个构造方法不会报错那,因为如果在类中没有写构造方法那相当于:
//这个类的定义和最初例子中的类一致
class Student { //注意类的命名习惯
String name; //属性
int id;
public Student(){ } //最初的例子默认自动添加
public void study(){ //方法
System.out.println(name + "在学习");
}
}
构造方法的作用是帮助我们在对象初始化时进行一定的操作,比如对属性赋初值(static静态代码块是在类初始化时执行,先于构造方法,这个在面试中常考)。
方法重载(overload)
方法重载这个概念非常简单,但初学者比较容易和以后要讲的方法重写给搞混了,这就需要大家随用随看,多过几遍自然记住了。
方法重载就是指在一个类中可以有多个同名方法,在调用时根据参数数量不同选择不同的方法进行调用,构造方法同样可以重载,话不多说直接上例子,现在上面学生例子中需要添加学习的具体科目,所以可以在添加一个study方法,同时还需要在定义学生对象时就设置好姓名和id,所以代码可以修改成如下形式:
class Student { //注意类的命名习惯
String name; //属性
int id;
public Student(){ } //构造方法
public Student(String name, int id){ //构造方法重载
this.name = name;
this.id = id;
}
public void study(){ //方法
System.out.println(name + "在学习");
}
public void study(String str){ //方法重载
System.out.println(name + "在学习" + str);
}
}
然后就可以这样调用:
public static void main(String[] args) {
Student oStu1 = new Student("小明",123);
oStu1.study(); //调用方法会打印小明在学习。
oStu1.study("语文"); //调用方法会打印小明在学习语文。
Student oStu2 = new Student(); //另一种构造方式
oStu2.name = "老王";
oStu2.id = 456;
oStu2.study("数学"); //调用方法会打印老王在学习数学。
}
注意参数不同表示参数类型、个数或顺序不同,如果只是形参的名称不同则不构成方法重载。
public Student(String name, int id){ }
public Student( int id, String name){ }
//这是两个不同方法形成方法重载
public Student(String name, int id){ }
public Student( String name1,int id1){ }
//不构成重载会报错
public void study(String str){}
public int study(String str){}
//也不构成重载会报错
static关键字
上面例子中的变量和方法调用必须要把类进行实例化,而由一类变量或方法直接通过类名就可以调用,这就是用static关键字修饰的类名和方法。实际项目中static变量主要运用于定义方法工具栏,我们将一些共性变量和方法用static类型存储在一个类中,以便我们在程序中随时调用。
static方法修饰的变量和方法由一个特点,在类被载入时就完成了初始化,一个类型只有一份,并且它被所有类的实例所共享。在调用时用“类名.属性”来调用。还是上面的例子
class Student { //注意类的命名习惯
static String cType = "学生类";
public static void getType(){
System.out.println("这是" + cType);
}
String name; //属性
int id;
public void study(){ //方法
System.out.println(name + "在学习,"+ "我是" + cType);
}
}
在调用时
public static void main(String[] args) {
System.out.println(Student.cType); //打印学生类
System.out.println(Student.getType()); //打印这是学生类
Student oStu1 = new Student("小明",123);
oStu1.study(); //调用方法会打印小明在学习,我是学生类。表明对象可以使用static变量。
}
static变量存放在方法区中,在之前讲解类加载过程时已经说明 ,形式如下:
注意由于静态属性和方法在类加载完成后就完成了初始化,所以静态方法不能调用非静态属性和方法。
对static关键字还有一个点就是静态代码块static{},这里的代码会在类初始化完成后进行,它可以使用静态变量。
对于静态变量方法与普通变量方法的区别是面试的常考问题,根据上面的原理基本可以分析出来,我在这里找了一篇比较好的总结大家可以看看。
链接
this关键字
相信学过JavaScript的同学对this关键字不太陌生,也相当头疼(本人也是这么过来的,以后有机会我也会发一遍JavaScript教程,希望大家也能支持),Java中this作用与其类似但没有那么恶心。
this被称为隐式参数,当我们调用方法时即使没有定义参数,程序也会默认传入一个名为this的参数,在普通方法中使用它会指向正在调用该方法的对象。而在构造方法中调用它会指向正要初始化的对象。
class Student { //注意类的命名习惯
public Student(){
System.out.println(“构造方法被调用”);
}
public Student(String name, int id){
this.name = name;//这里是this的主要用途,因为此时该函数中的name代表外边传入的name,我们只能通过this获取类中的name属性。
this.id = id;
}
String name; //属性
int id;
public void study(){ //方法
this(); //this的特殊用法,用this调用构造方法,了解即可
System.out.println(this.name + "在学习,");
}
}
public static void main(String[] args) {
Student oStu1 = new Student("小明",123); //这里调用this指向正要初始化的对象。也就是oStu1指向的对象
Student oStu2 = new Student(); //另一种构造方式
oStu2.name = "老王";
oStu2.id = 456;
oStu2.study(); //调用方法会构造方法被调用换行后在打印打印老王在学习,this正在调用该方法的对象也就是也就是oStu2指向的对象。
}
this关键字不能用于static修饰的静态方法,因为静态方法随类而创建,此时还没有对象,根据前面的知识很好理解这一点。
上一篇:菜鸟学习笔记:Java基础篇2(变量、运算符、流程控制语句、方法)
下一篇:菜鸟学习笔记:Java基础篇4(面向对象三大特征).