谈到类和对象,自然而然的想到面向对象程序设计(OOP),百度百科给的解释是
面向对象程序设计(Object Oriented Programming)作为一种新方法,其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。模型是用来反映现实世界中事物特征的。任何一个模型都不可能反映客观事物的一切具体特征,只能对事物特征和变化规律的一种抽象,且在它所涉及的范围内更普遍、更集中、更深刻地描述客体的特征。通过建立模型而达到的抽象是人们对客体认识的深化。
我个人的看法是,C语言是面向过程的,更多的是算法+数据结构,一个程序成百上千的“过程”,以至于特别的繁琐;某天某人推出了一个“面向对象的架构”,先在关系数据库验证了一下下,发现把显示世界抽象为机器世界蛮方便的,只要一个模版,一切都可以化身为对象, 那么这也需要我们有“上帝视角”,提前对此抽象、模型。
so,面向对象,更多的是考虑数据结构,而非算法。
类是什么,看看刚刚百度百科的定义呗。
其本质是以建立模型,体现出来的抽象思维过程和面向对象的方法。
《Java核心技术》的定义:类是构造对象的模版或者蓝图。由类构造对象的过程称为类的实例化。
对象,也就是现实世界的抽象化实体,对象有三个特征(不是面向对象),分别为
对象的状态:描述当前特征信息。(正常调用方法才会改变,非正常改变就是封装有问题)
对象的行为:对象里面有什么方法,可以对它施加那些操作。
对象标识 :唯一识别身份的。(数据库中的主码/操作系统中的进程控制块?)
说完上面的概念的东西,那就看看Java语言中的类,String类用的多,那就看看这个吧!
public final class String {//删除了非关键信息
private final char value[];
private int hash;
private static final long serialVersionUID = -6849794470754667710L;
public String() {
this.value = "".value;
}
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
}
String类是一个很大的类,里面有很多的方法,上面删除了很多东西,切记!
String str = new String();//方式一:实例化对象
String str = new String(str);//方法二:实例化对象
上面是两种使用构造器生成对象的方法。
String() 称为构造器,无返回值。
new 称为操作符,开辟新堆内存,返回一个引用(一个栈,存堆地址)。
new String()是在堆中开辟一块空间,把String对象存进去,返回值是一个引用(一个栈,存堆地址)。
String str为声明String类型变量,并存储这个引用。
堆内存空间:保存真正的数据,对象的属性信息。
栈内存空间:保存的堆内存的地址,堆内存操作权,简单理解为‘对象名称’。
这样我们就生成一个对象了,这里可以看到“方法二:实例化对象”,是可以把同类型对象作为参数传入,创建新对象的,类似构造器中参
数个数和参数类型不同,这种特征叫做“重载”,后面再讲!!(有意思,构造函数的隐式参数的概念this。)
注意,如果编写一个类没有编写构造器,那么系统会提供一个无参数构造器。
int hash;
private int hash;
private final char value[];
public static final long serialVersionUID = -6849794470754667710L;
上面hash,hash,value称为实例域,默认值分别为0,0,\u0000(空格)。
private称为私有的,本类对象才可以调用,外部类不可以使用,这也就是“封装”。
final修饰符,“最终的,最后的,常量”,表示存储在value变量中的字符数组对象引用不会再指示其他(一个栈,存堆地址),由于不能改变栈,只能通过对象中的方法进行改变数组内容。
serialVersionUID,使用了static和final修饰符,称为静态常量/静态域。静态域或者静态常量都是属于类的,而不是属于任何独立对象的,它只生成一份,大家用同一个值/引用。
long num = String.serialVersionUID;//静态域,属于类,不需要对象,即可直接调用
下面介绍的修饰符,也可以使用在类名、方法名前面。(后面再讲)
访问权限
实例域的值,可以通过构造器或者方法等进行修改。
public int hashCode() {}
只要有构造器,那么自己的类就算是完成了,可以正常的生成对象的,构造器和类是同名的。
有属性(实例域)和函数(方法),那么就可以对现实世界进行抽象,使用构造器生成对象了。
我们可以把Java提供的String类改变一下就是用户自定义类。
public final class String2 {//删除了非关键信息
private final char value[];
public String2() {
this.value = "".value;
}
public int hashCode() {
......
}
}
这样,我们就创建了一个类。每一个类中都有一个默认的构造器(构造方法)。
这个类编译了之后,会通过Java编译器,生成一个String2.class文件。(字节码文件)
当创建这个类实例,会通过String2.class文件,使用字节码解释器,生成机器码文件,系统就可以运行。
方法比较重要,特地拿出来细谈。
public static void main(String[] args) {
}
main方法是一个程序的入口,会根据方法中的语句,依此执行,每个类都可以有一个main方法(在类中直接单元测试)。
main方法前面有static修饰符,表示此方法是静态的。
Java预定义类 ,Math类中有很多静态方法,我们就拿Math类学习学习呗!
public final class Math {
public static final double PI = 3.14159265358979323846;
public final double PI = 3.14159265358979323846;//注意
......
public static int max(int a, int b) {
return (a >= b) ? a : b;
}
}
根据上面的静态域,可以推出静态方法的特性。同理可得
在下面两种情况下使用静态方法
1.一个方法不需要访问对象状态(不需要对象),其所需的参数都是通过显式参数提供的。
int a = Math.max(4,8);//返回最大的数,显示参数
2.一个方法只需要访问类的静态域
double pi = Math.PI;
这个留到设计模式再说,其实就是工厂流水线的形式,生产标准的对象,带上静态工厂呢!就是类不需要访问对象状态,自己访问静态方法,方法中设置对象的生产步骤.....。
(new LocalDate).new//工厂模式
NumberFormat.getGurrencyInstance//静态工厂
方法参数,什么是方法参数呢??
public final class Math {
......
public static int max(int a, int b) {
return (a >= b) ? a : b;
}
}
类似Math类,调用静态方法Math.max(1,5),1和5就是参数,称为实参,实际传过去的值。
max(int a, int b)中a、b称为形参,形式上的参数,变量名可以随便,规定调用此方法需要传递的数据类型和个数。
方法参数中,有一个概念,引用传递和值传递。
package com.yingqi;
public class Demo {
int a = 1;
int b = 3;
public void Multiple(int a, int b) {
a = a * 3;
b = b * 3;
System.out.println("a=" + a + ",b=" + b);// 输出a=3,b=9
}
public static void main(String[] args) {
Demo demo = new Demo();
demo.Multiple(demo.a, demo.b);
System.out.println("a=" + demo.a + ",b=" + demo.b);// 输出a=1,b=3
}
}
从main函数开始,我们调用Multiple函数,把a,b的值穿进去,并且放大3倍,第一次在Multiple中输出3倍的结果,但是第二次输出并没有放大3倍,还是原来的数字,我们从内存分析看吧。
值传递
其实,可以看到方法中的a,b是拷贝 对象中的ab值。
package com.yingqi;
public class Demo {
String str = "hello";
public void Quote(String str) {
str = "word!!";
System.out.println(str);//输出word!!!
}
public static void main(String[] args) {
Demo demo = new Demo();
demo.Quote(demo.str);
System.out.println(demo.str);//hello
}
}
看样子,好像和刚刚没什么区别嘛,你哄我喔!但是,我可以说引用传递绝大多数会发生改变的,那为什么这个没有改变呢?字符串
我可以明确告诉你是字符串的问题,讲个正常的再解释这个!
package com.yingqi;
public class Demo {
public void Quote(StringBuilder str2) {
str2.append(" word!!");
System.out.println(str2);//hello word!!
}
public static void main(String[] args) {
StringBuilder str1 = new StringBuilder("hello");
Demo demo = new Demo();
demo.Quote(str1);
System.out.println(str1);//hello word!!
}
}
那这个为什么输出相同的//hello word!!呢,我们也用内存介绍吧。
引用传递
这图我画的好丑啊,但也可以看。
我们的引用传递,方法中的形参也是拷贝了一份相同的值(一个栈,存堆地址值),只不过这个值是指向同一个对象的,我们是改变了对象的内容,并没有改变这个值。那么输出结果就是Hello World!!
所以,Java的参数传递,只传递的是值,不是引用,我们是习惯了这样,叫这个名称罢了。
回到上一题,字符串的问题,str="word!!"相当与str= new String();也就相当改变了传递的值(回到值传递)!
我们先从一段代码中,学习什么是包,以及怎么使用,有什么用。
package java.lang;
import java.util.Random;
import sun.misc.FloatConsts;
import sun.misc.DoubleConsts;
public final class Math {
......
}
package java.lang这个就是包路径,Math类的源码、.calss文件都在这个目录的文件夹下。
包有什么作用,我们知道Java语音是发展了很久的,有很多类,也就是很复杂,一般复杂的东西,我们都会想到分层概念,包呢?也可以这样理解,每个包之间都是独立的,不同包中可以有相同名称的类。
import java.util.Random;
import sun.misc.FloatConsts;
import sun.misc.DoubleConsts;
使用import关键字,后面接着包路径和名称,这样的语句就是导入包了,现在大家都用ide等开发工具,一般情况下,都会自动导包的,当遇到类冲突的时,我们需要手动导包,这个过程就不说了,慢慢就熟悉。
package com.yingqi;
import static java.lang.System.*;
public class Demo {
public static void main(String[] args) {
out.print("yes");//yes
}
}
import static 包路径 这样的语句,我们称为静态导入,导入包的静态资源(静态域,静态方法),那就可以直接使用了。
package java.lang;
类似的,package 包路径 这样的语句,我们称为把这个类放入到这个lang包中,包路径我们常用反写域名的形式。com.sum.*。
public class Math {
......
}
class关键字前面public修饰符,代表可以被任意的类使用。
这些修饰符也可以使用在类名、方法名、域... 前面(后面再讲)
访问权限
这后面应该还可以谈一下类路径(ClassPath),很多语言都涉及到这个,但说出来却很复杂,我也有点绕(类加载器、全局设置、打包等...),一般我们使用ide等开发工具都不会涉及到这个问题。
这些是很基础的东西,绝大多是编程语言都支持,并且相差不会很大,上面说的是远远不够的,大家要看更优秀的作品,这只是自己一年前的总结(原因在这里),这些基础敲多了,就自然的熟悉了。