1.面向对象编程简述
面向过程编程缺少了可重用设计性
1.1.面向对象三大特征
封装性
:所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。简而言之,内部操作对外部而言不可见(保护性
)继承性
:继承是一种能力,指的是他可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展多态性
:所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口
1.2.名词扩展
- OOA:面向对象分析
- OOD:面向对象设计
- OOP:面向对象编程
面向对象最大的特征就是可以进行生活的抽象
2.类与对象的定义和使用
2.1.类与对象的概念
类
:共性的概念对象
:一个具体的可使用的事物
编程中首先产生类(类是生产对象的蓝图
),而后再产生对象。对象的所有行为一定在类中进行了完整的定义
。
类的组成:
方法
:操作行为属性
:操作数据,描述对象的具体特点
2.2.类与对象的定义和使用
定义一个类的语法如下:
class 类名称{
属性1;
属性2;
...
属性n;
方法1(){}
方法2(){}
...
方法n(){}
}
如上是一个类的完整定义,此时类中的方法要由类的对象去调用
。Person类定义示例如下:
class Person{
public String name;
public int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String getPersonInfo(){
return "姓名:" + name + "、年龄:" + age;
}
}
有了类,现在我们可以定义对象了,生产对象的语法如下:
类名称 对象名称 = new 类名称();
以Person类为例生产一个Person对象:
Person per1 = new Person("邹大",18);
通过对象调用实例变量与实例方法,代码如下:
class Person{
public String name;
public int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String getPersonInfo(){
return "姓名:" + name + "、年龄:" + age;
}
}
public class Test{
public static void main(String[] args) {
Person per1 = new Person("邹大",18);
System.out.println(per1.name);
System.out.println(per1.age);
System.out.println(per1.getPersonInfo());
}
}
//邹大
//18
//姓名:邹大、年龄:18
!!!!注
:只要出现了关键字new
,就开辟了新的内存
,而Java中常说的性能调优其实就是调整内存
。
2.3.对象内存分析
我们可以简单将Java中的内存区域分为栈内存
和堆内存
两块区域(实际Java内存区域的划分远比这个复杂)
- 栈内存(
虚拟机局部变量表
):存放的是局部变量
(包含编译期可知的各种基本数据类型
,对象引用
——即堆内存的地址,可以简单理解为对象名称),Java栈是与线程对应起来的,每当创建一个线程
,JVM就会为这个线程创建一个对应的Java栈
- 堆内存:保存的是
真正的数据
,即对象的属性信息
通过如下代码分析两部分内存:
class Person{
String name;
int age;
}
public class Test {
public static void main(String[] args) {
Person per = new Person();
per.name = "tim";
per.age = 18;
}
}
main方法中的第一行代码:
Person per = new Person();
如上出现了关键字new,表名在堆上分配了内存并且产生了Person类的对象per引用这部分内容,内存图如下:
接下来两句代码:
per.name = "tim";
per.age = 18;
通过per引用设置堆中属性值,内存图如下:
对象(引用数据类型)必须在实例化后调用
,否则就会产生NullPointerException(运行时
错误),编译时不会出错。“NullPointerException”在开发中会一直存在,只有引用类型(数组、类、接口)才会产生此类异常
。一旦出现此类异常,就要根据出错位置查看引用数据类型变量是否已经初始化
。
2.4.引用传递分析
引用传递的本质
:一块堆内存可以被多个栈内存所指向
。
Person per1 = new Person();
Person per2 = new Person();
per2 = per1;
前两句代码内存图如下:
那么当per2 = per1执行后,内存图如下:
总结:
垃圾内存:指没有任何栈内存指向的堆内存空间
- 所有的垃圾空间都会不定期
GC(垃圾回收)
,GC会影响性能
,所以开发中要控制好对象产生数量
(无用的对象尽量少产生)。
3.private实现封装处理以及构造方法(匿名对象)
3.1.private实现封装处理
封装
:封装是一个非常复杂的概念,使用private关键字实现的封装处理只是封装的第一步
,更多的了解要建立在继承和多态学习上。
无封装程序示例:
class Person{
String name;
int age;
public void getPersonInfo(){
System.out.println("姓名:" + name + "、年龄:" + age);
}
}
public class Test {
public static void main(String[] args) {
Person per = new Person();
per.name = "邹大";
per.age = 18;
per.getPersonInfo();
}
}
//姓名:邹大、年龄:18
此时,要回避此类问题,让内部操作对外部不可见(对象不能直接操作属性),可以使用private进行封装,用private封装属性示例如下:
private String name;
private int age;
此时使用了private对属性进行封装,要访问私有属性,按照Java设计原理必须提供一下两种法:
- setter方法:主要用于对属性值得设置和修改
- getter方法:主要用于属性内容的取得
使用private封装属性示例如下:
class Person{
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void getPersonInfo(){
System.out.println("姓名:" + name + "、年龄:" + age);
}
}
public class Test {
public static void main(String[] args) {
Person per = new Person();
per.setName("tim");
per.setAge(18);
per.getPersonInfo();
}
}
//姓名:tim、年龄:18
通过如上代码我们可以发现,private实现封装
的最大特征在于:只允许本类访问而不允许外部类访问
。
类的设计原则:
- 以后编写类时,类中的所有属性必须使用private进行封装
- 属性若要被外部访问,必须定义getter和setter方法
3.2.构造方法与匿名对象
3.2.1.构造方法简述
根据如下对象产生过程展开分析:
(1)类名称 (2)对象名称 = (3)new (4)类名称();
- (1)任何对象都应该有其对应的类,因为类是产生对象的蓝图
- (2)对象名称是一个唯一的标记,引用一块堆内存
- (3)new表示开辟新的堆内存空间
- (4)是构造方法
通过如上分析可知,所谓构造方法就是使用关键字new实例化新对象时来调用的操作方法
。对于构造方法的定义要遵循以下原则:
- 方法名称必须
与类名相同
- 构造方法
没有返回值类型声明
- 每个类至少存在一个构造方法(
没有明确定义,系统会默认生成一个无参构造
) - 若类中
定义了构造方法,则默认的无参构造就不会再生成
问题:构造方法无返回值,为什么没有void声明?
解释上述问题要先明确类中的组成:属性、构造方法、普通方法
- 属性是在对象开辟堆内存时开辟空间
- 构造方法是在使用new后调用
- 普通方法是在空间开辟了、构造方法执行之后可以被多次调用的
public Person(){}//无参构造方法
编译器是
根据程序结构来区分普通方法和构造方法的
,所以在构造方法前没有返回值类型声明。
使用构造方法设置对象属性示例如下:
class Person{
private String name;
private int age;
public Person(String n,int a){
setName(n);
setAge(a);
}
public void setName(String n){
name = n;
}
public void setAge(int a) {
if(a > 0 && a <= 150){
age = a;
}else{
age = 0;
}
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void getPersonInfo(){
System.out.println("姓名:" + getName() + "、年龄:" + getAge());
}
}
public class Test{
public static void main(String[] args) {
Person per = new Person("Tim",18);
Person per1 = new Person("Tim",200);
per.getPersonInfo();
per1.getPersonInfo();
}
}
//姓名:Tim、年龄:18
//姓名:Tim、年龄:0
构造方法的调用和对象内存分配几乎是同时完成的
,因此我们可以利用构造方法来为为类中的属性进行初始化操作(可以避免多次setter操作
)
3.2.2.构造方法
构造方法重载:参数类型或个数不同
构造参数重载示例:
public Person(){
System.out.println("===无参构造===");
}
public Person(String n){
name = n ;
System.out.println("===有参构造===");
}
建议:
- 如果有若干构造方法,一般参照参数个数升序或降序排列
- 在进行类定义时:(1)定义属性——(2)定义构造方法——(3)定义普通方法
匿名对象示例:
new Person("张三",18).getPersonInfo();
由于匿名对象不会有任何的栈空间指向,所以使用一次之后就会成为垃圾空间。
4.this关键字
this关键字主要有三个方面的用途:
- this调用本类属性
- this调用本类方法
- this表示当前对象
4.1.this调用本类属性
class Person{
private String name;
private int age;
public Person(String name,int age){
name = name;
age = age;
}
public String getPersonInfo(){
return "姓名:" + name + "、年龄:" + age;
}
}
public class Test{
public static void main(String[] args) {
Person per = new Person("Tim",18);
System.out.println(per.getPersonInfo());
}
}
//姓名:null、年龄:0
通过如上代码我们可以发现,当参数与类中属性名相同时,类中属性无法被正常赋值
。因此我们需要加上this关键字,这样便可以正确给对象属性赋值,如下:
class Person{
private String name;
private int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String getPersonInfo(){
return "姓名:" + name + "、年龄:" + age;
}
}
public class Test{
public static void main(String[] args) {
Person per = new Person("Tim",18);
System.out.println(per.getPersonInfo());
}
}
//姓名:Tim、年龄:18
只要在类中方法访问类中属性一定要加this关键字
4.2.this调用本类方法
this调用本类方法有如下两种情况:
- 调用普通方法
this.方法名称(参数)
- 调用构造方法
this(参数)
this调用普通方法示例:
class Person{
private String name;
private int age;
public Person(String name,int age){
this.name = name;
this.age = age;
this.print();//调用普通方法
}
public void print(){
System.out.println("**********");
}
public String getPersonInfo(){
return "姓名:" + name + "、年龄:" + age;
}
}
public class Test{
public static void main(String[] args) {
Person per = new Person("Tim",22);
System.out.println(per.getPersonInfo());
}
}
//**********
//姓名:Tim、年龄:22
虽然调用本类方法不加this也可以正常调用,但是一般建议加上,加上this的目的是可以区分方法的定义来源
(在继承中有用)
观察构造方法中存在的问题:
class Person{
private String name;
private int age;
public Person(){
System.out.println("***产生一个新的Person对象***");
}
public Person(String name){
System.out.println("***产生一个新的Person对象***");
this.name = name;
}
public Person(String name,int age){
System.out.println("***产生一个新的Person对象***");
this.name = name;
this.age = age;
}
public String getPersonInfo(){
return "姓名:" + name + ",年龄:"+age;
}
}
public class Test{
public static void main(String[] args) {
Person per1 = new Person();
Person per2 = new Person("张三");
Person per3 = new Person("李四",18);
System.out.println(per1.getPersonInfo());
System.out.println(per2.getPersonInfo());
System.out.println(per3.getPersonInfo());
}
}
//姓名:null,年龄:0
//姓名:张三,年龄:0
//姓名:李四,年龄:18
上述代码中存在问题:有大量重复代码
Java中支持构造方法的互相调用(this)
,所以修改如上代码:
class Person{
private String name;
private int age;
public Person(){
System.out.println("***产生一个新的Person对象***");
}
public Person(String name){
this();//调用本类无参构造
this.name = name;
}
public Person(String name,int age){
this(name);//调用本类有参构造
this.age = age;
}
public String getPersonInfo(){
return "姓名:" + name + ",年龄:"+age;
}
}
public class Test{
public static void main(String[] args) {
Person per1 = new Person();
Person per2 = new Person("张三");
Person per3 = new Person("李四",18);
System.out.println(per1.getPersonInfo());
System.out.println(per2.getPersonInfo());
System.out.println(per3.getPersonInfo());
}
}
!!!!!!this调用构造方法时要注意
:
this调用构造方法的语句必须在构造方法首行
- 使用this调用构造方法时,请
留有出口
(换句话说就是调用的构造方法是已经实现好的、正确的,一定避免出现互相调用
,那样没有出路就会出错),如下代码就没有出路
public Person(String name.int age){
this();
}
public Person(){
this("s",18);
}
4.3.使用this表示当前对象
this表示当前对象示例代码如下:
class Person{
public void print(){
System.out.println("[PRINT]方法" + this);
}
}
public class Test{
public static void main(String[] args) {
Person p1 = new Person();
System.out.println("[MAIN]方法" + p1);
p1.print();
System.out.println("______________");
Person p2 = new Person();
System.out.println("[MAIN]方法" + p2);
p2.print();
}
}
//[MAIN]方法Person@1b6d3586
//[PRINT]方法Person@1b6d3586
//______________
//[MAIN]方法Person@4554617c
//[PRINT]方法Person@4554617c
只要对象调用了本类中的方法,方法中的这个this就表示当前执行的对象
5.static关键字
static可以修饰属性和方法
5.1.static属性(类属性)
class Person{
String country = "中国";
String name;
int age;
public void getPersonInfo(){
System.out.println("姓名:" + this.name + "、年龄:" + this.age + "、国家:" + this.country);
}
}
public class Test{
public static void main(String[] args) {
Person per1 = new Person();
per1.name = "邹大";
per1.age = 29;
Person per2 = new Person();
per2.name = "邹二";
per2.age = 18;
per1.getPersonInfo();
per2.getPersonInfo();
}
}
//姓名:邹大、年龄:29、国家:中国
//姓名:邹二、年龄:18、国家:中国
上述代码的内存分析图如下:
传统属性
所具备特征:保存在堆内存
中,且每个对象独享属性
描述共享属性
:在属性前添加static关键字即可
static属性
:又称类属性
,保存在全局数据区
的内存之中,所有对象都可以进行该数据区的访问
修改上述代码:
static String country = "中国";
修改后代码内存分析图如下:
结论:
- 访问static属性(类属性)应使用
类名.属性名
,如下:
System.out.println(Person.country);
- 所有的非static属性(实例变量)必须在对象实例化后使用,
而static属性不受对象实例化控制
- 修改static属性,所有对象都同步此属性值
定义时如何选择实例变量和类属性:
- 在定义类时,99%的情况都不会考虑static属性,
以非static属性(实例变量)为主
- 如果需要描述
共享属性概念
或者不受对象实例化控制
,就使用static
5.2.static方法(类方法)
使用static定义方法,通过类名称直接访问,示例如下:
class Person {
static String country;
String name;
int age;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public static void setCountry(String c){
country = c ;
}
public void getPersonInfo() {
System.out.println("姓名:" + this.name + "、年龄:" + this.age + "、国家:" + this.country);
}
}
public class Test{
public static void main(String[] args) {
Person.setCountry("中国");
Person per = new Person("张三",19);
per.getPersonInfo();
}
}
//姓名:张三、年龄:19、国家:中国
关于static方法:
所有的静态方法不允许调用普通属性或方法
:普通属性或方法依赖对象而生,无对象产生是无法访问的,静态方法只能访问类中静态属性- 所有
普通方法允许访问类中静态方法或静态属性
,也可以访问类中普通属性
使用static定义方法只有目的
:使某些方法不受到类的控制
,即可以在没有实例化对象的时候执行
(广泛存在于工具类中),如:
Arrays.copyOf();
Array.sort();
System.arraycopy();