面向对象简介
面向对象是IBM
的Smalltalk
语言推广的。
面向对象的三大特性:封装性,继承性,多态性,利用面向对象设计的程序可以很好的实现代码的重用。
封装性:属性和行为封装到对象中,对外屏蔽一些属性和行为(可见性)。
继承性:使多个类共享一些属性和方法成为可能,许多编程语言不支持多继承,Java
通过接口来弥补不能多继承的缺陷。
多态性:方法重载和对象多态两种形式。
类与对象
类就是属性和行为的封装,对象就是这个类的实例,同一个类可以有多个实例,多个实例之间的区别就是属性值不同,更准确的说是内存地址不同。
属性又叫做成员,Field
,因为属性不一定是一个简单类型的变量,所以有人给它叫做成员
行为就是方法,Method
。
可以让主方法直接调用的方法必须要添加static
关键字,但是由对象调用的方法就不要加static
关键字
类的实例就是对象,通过new
关键字来产生一个实例,产生实例的方式很多,new
算是一种,new
关键字的作用就是开辟内存空间
实例中的属性和方法需要通过实例.属性名/方法名()
的方式来使用。
堆内存:使用new
关键字开辟的内存,保存每一个对象的属性内容
栈内存:保存堆内存地址
如果一个对象没有被实例化就使用,则会出现NullPointerException
异常,也就是没有开辟堆内存空间就开始使用这个对象了,相当于使用null.属性名/方法名()
,null
里面什么都没有所以会出现异常
引用传递的本质就是多个栈内存同时使用了同一块堆内存,就相当于为对象起了一个别名,不管用别名修改这个对象,还是用原来的名字修改这个对象,都修改的是同一个对象。
如果说一个堆内存没有被任何的栈内存指向,那么这块堆内存就变成了垃圾,会被垃圾收集器(GC
)回收释放掉所占用的空间
GC
不仅仅只是垃圾收集,他也会做内存分配。
垃圾回收处理方式分类:具体参照腾讯的一个大佬的博客
如果想要具体的了解,可以在B站查看相关的JVM
教程。
封装初步分析
使用private
关键字声明的属性和方法只可以被本类中的其他属性和方法访问,所以private
关键字就是封装的一部分。
为什么要给属性设置为private
?
因为private
可以禁止外部直接访问本类的属性,为private
修饰的属性提供getter
和setter
方法依然可以让外部来访问该属性,但是与直接访问属性不同的是,getter
和setter
方法可以做一些业务,比如说字段的验证。
构造方法
构造方法的方法名称和类名称必须相同,不需要写任何的返回值,void
也不用写,构造方法也是可以重载的。
构造方法用来在对象实例化的时候做一些初始化操作。
无参的构造方法默认就存在,一个类中至少会有一个构造方法
如果有了多参的构造方法,默认存在的哪个无参构造就消失了,如果还需要无参构造方法需要手动声明。
编写顺序:属性 => 构造方法 => 普通方法,顺序和语法无关,只是一种规范
如果一个对象中的某个属性没有在初始化时赋值,默认是类型的初值,引用类型的初值为null
构造方法重载的时候规范的顺序为:参数少的到参数多的,或者参数多的到参数少的,只是一种规范,不按顺序写也不会报错,只是对阅读代码有影响。
匿名对象
没有名字的对象就是匿名对象,就是那种直接new
出来不赋值给任何的变量名的对象,用内存的说法来说,就是没有栈内存指向堆内存空间的对象(不严谨)。
匿名对象使用一次之后就会产生垃圾,所以尽量不要大量使用的匿名对象。
数组
数组属于引用类型,所以传递数组参数属于引用传递
开辟数组的方法,例如:
// 动态初始化
int[] nums = new int[100];
int nums[] = new int[100];
// 静态初始化
int[] nums = {1, 2, 3};
int[] nums = new int[]{1, 2, 3};
下标从0
开始,方括号[]
在上面两个位置的写法都是可以的
超出数组的访问范围,会出现ArrayIndexOutOfBoundsException
数组提供了length()
方法来获取数组的长度
可以使用foreach
的方式来遍历数组
如果使用了未开辟空间的数组,会出现NullPointerException
数组有一个致命的缺陷,就是数组的长度不能被改变,所以开发中一般不会使用数组,使用类集框架。
二维数组就是数组中的每个下标对应的还是一个数组,定义方法是上面的变通,不演示...
依照数组的定义可以写出多维数组,甚至是下面的代码:
int[][][][] numbers = new int[1][1][1][1];
在一个方法中可以使用数组,也可以使用可变参数,JDK1.5
之后加入,一个方法只能有一个可变参数,而且这个可变参数还必须要放到最后一个参数
对象数组:就是数组中存储的类型是引用数据类型,而不是基本数据类型
数组操作方法
数组复制
System.arraycopy(原数组, 开始索引, 目标数组, 开始索引, 长度);
数组排序
java.util.Arrays.sort(数组);
String类型
String
类型的初始化,可以直接使用字面量,也可以使用构造方法
==
不能用来比较没有入池的String
字符串,因为==
比较的内存地址,而不是字符串的内容。使用equals()
方法可以比较字符串的内容。
一般在使用equels()
的时候要将字符串常量(或者不可能为null
的变量)放在左边,这样可以避免NullPointerException
的出现
字符串的字面量实际上就是String
的匿名对象
使用字面量为String
赋值可以实现堆内存空间重用。
如果使用字面量赋值,会导致两个相同的字面量用==
来比较返回结果为true
这是因为JVM
底层存在一个对象池,这个字面量保存在这个对象池中,如果出现了相同的字面量会重新使用对象池中的这个对象,不会重新开辟新的空间。如果想要通过new
关键字创建出来的String
也入池,可以使用intern()
方法来让String
入池。
String
使用直接赋字面量的方式,只会开辟一块堆内存空间,并且还会自动入池,使用new
创建的String
会开辟两个堆内存空间,也不会自动入池,所以推荐使用直接赋值的方式来创建String
对象。
String
在进行+
操作的时候会产生大量的垃圾,因为每+
一次就会新开辟两个空间,并且会改变对象的引用,这就导致一些堆内存空间会失去引用,所以产生垃圾,+
操作越多,产生的垃圾越多。
如果想要连接String
对象,可以使用StringBuffer
和StringBuilder
代替。也不是所有的连接操作都要使用这两个对象操作,对于连接次数少的还是可以使用+
操作的
String方法
API
地址:点击查看
注意一些方法里面写的是正则表达式,所以如果要使用点.
或者竖线|
等等这样的正则表达式中的特殊字符的时候一定要进行转义,转义的时候要使用两个右斜线\\
this关键字
this
有三种作用:调用本类属性,调用本类方法,表示当前对象
调用本类属性,常用在构造方法或者getter
、setter
方法中,因为这些方法中的参数名一般都写成和当前类中的属性名一样的形式,这个时候如果不使用this
,程序就根本不知道这个变量名字代表的是参数还是当前类的属性,所以会默认就近原则,就把这个变量名视为参数,所以才要使用this
,一个良好的习惯是,不管方法中的参数名和属性名是否一样,都要养成调用属性的时候写上this
的习惯。
调用本类方法,包含两种,调用本类的普通方法和调用本类的构造方法,分别是this.方法()
、this()
,this()
这种形式一般用来进行一个类中多个构造方法的相互调用,不举例了...,但是要注意的是,不能存在递归调用,否则会报错,this()
必须写在构造方法中的首行
表示当前对象,意思就是谁调用了当前类中的方法,这个this
就是谁
引用传递
之前说过,多个栈内存同时指向一块堆内存就形成了引用传递。一个用途就是实体类中可以再有实体类属性(引用关联),甚至该类中的属性类型还可以是该类(自身关联),如果一个类中全是其他实体类型做属性值,那么这个类就是多个类的合成,叫做合成设计模式。
对象比较
无法使用==
比较一个对象,==
能比较数值类型,equals()
可以比较字符串,那么对象应该用什么来比较?
对象比较,就是覆写equals()
方法,先要判断传递的参数是否为null
,再判断两个对象是否地址相等,然后再进行每个属性的相等判断
对象比较的用处很多,比如说排序的时候使用...
static关键字
static
可以用来定义多个对象的公共属性
static
定义的属性在全局数据区中,所有方法的定义都在全局代码区中。
因为static
定义的是公共属性,所以可以使用类名.属性名
来访问这个属性。
什么时候会用static
属性?比如说下载器需要一个属性存储下载位置,其他类随时可以读取这个属性,这个时候就要用static
,再比如说Math
类中的很多方法都是static
的,这种只参与算法,不需要类中属性做支撑的方法一般都可以定义为static
static
定义的方法也可以通过类名.方法名()
的方式访问
static
定义的方法限制特别多,比如说,泛型就不能使用类上面定义的泛型了,比如说static
定义的方法不能访问非static
定义的属性或方法,再比如说static
方法中不能使用this
关键字和super
关键字,等等...
总之不用static
声明的属性或方法都需要对象来调用,使用staic
定义的属性或方法叫做类属性和类方法,所以可以用类名直接调用
main
方法就是一个用static
声明的方法,main
方法中的String[] args
可以接收到用户从控制台上面传过来的参数,参数之间使用空格隔开,如果参数中需要空格,使用双引号引起来就好。
代码块
普通代码块,构造块,静态块,同步代码块(多线程)
写在方法中的代码块就是普通代码块,一般用来避免重名变量的问题,这里就涉及到了全局变量和局部变量,概念不说...
写在类里面的代码块就是构造块,构造块优先于构造方法执行
静态块,使用static
关键字在类里面定义的代码块就是静态块,静态块优先于构造块执行,如果静态块写在了主类中,静态块要优先于主方法执行,那这就意味着可以通过静态块来替代主方法?实际上不是的,JDK1.7
之后不允许这样的操作了,所以这种操作在1995
年到2012
年都是存在的。
内部类
类中定义内部类
类里面定义的类就是内部类,内部类甚至可以定义在方法或代码块中,恐怕这种写法对开发Java
时间比较长的人来说已经忘记了,只记得匿名内部类了。内部类可以访问外部类中的私有属性,就这一点来说,内部类还是很有必要的,比如说两个类,A类需要B类中的属性,那么需要将B类传递给A类,如果是内部类的话,将A类定义在B类的内部,直接可以访问B类中的属性了。
外部类也可以访问内部类的私有属性?当然了,外部类可以直接用内部类的实例来使用内部类中的私有属性。
内部类中如果想要使用this
关键字访问外部类中的私有属性呢?因为内部类中的this
指向的是内部类的实例,内部类的实例怎么会访问的到外部类的属性呢?所以这个时候写法就要发生一点变化了,外部类名.this.属性名
,当然这是一种标准写法,对于很多程序也都用到了这样的写法,比如说JavaFX
程序的多线程调度就很可能用到这样的写法。
如果想要直接实例化内部类,new 外部类().new 内部类()
有一种情况只希望内部类被外部类调用,而不能被其他类使用,可以使用private class
来定义内部类
内部类可以使用static
来定义,这样的话内部类就相当于变为了外部类,它就只能访问外部类中的static
定义的内容了,如果想要在其他类中取得这个内部类的实例,new 外部类.内部类()
方法中定义内部类
方法中直接定义一个class
,然后在方法中使用new
的方式来实例化这个方法中定义的类,就可以获取实例,然后调用实例中的方法了,从JDK1.8
开始方法中的内部类可以直接访问方法中的参数或变量了。
在JDK1.7
及以前的版本,方法中的内部类是不能直接访问方法中的参数和变量的,需要在前面加上final
关键字才可以访问,JDK1.8
之后取消了该限制,主要的原因是为了Lambda
表达式的编写方便。关于JDK1.8
中的一些奇淫写法需要抽个时间专门看一本书捋一下。