JAVA面向对象思想
目录:
一、面向对象和面向过程思想的区别与联系
二、类
三、封装
四、JAVA的几个重要关键字
五、继承
六、多态
七、集合
—————————————————————————————————————————————————
一、面向对象和面向过程思想的区别与联系:
C和C++的面向过程思想应当说是我们正常人的逻辑思维,当我们需要干一件事的时候,首先想到的是第一步做什么,其次做什么,最后做什么,按照次序顺序执行,而Java的面向对象思想在解决某个逻辑的时候,首先思考的是这个问题中有哪些类和对象,然后分析这些对象和类有哪些属性和方法(也就是函数),最后分析这些属性和方法之间的联系与调用次序,直到将其串联成一个完整的逻辑问题,这就是面向对象
二、类的使用
1、 匿名内部类(匿名对象)的使用
①:当对方法只进行一次调用的时候,可以使用匿名对象。
②:当对象对成员进行多次调用时,不能使用匿名对象。必须给对象起名字。
2、 main函数的作用:
不是每一个类都需要main函数,main函数是一个程序的入口,提供给Java虚拟机JVM调用,来保证类的独立运 行,如果一个类不需要独立运行就不需要main函数。
3、成员变量和局部变量的区别:
②成员变量在这个类中有效;局部变量只在定义的作用域中有效(也就是自己所在的大括号中)
③成员变量存储在堆内存中,随着类对象的生命周期而创建或者消失;局部变量存储于栈内存中,随着作用域的运行而产生,随着其消失而消失
4、构造函数
构造函数:用于给对象进行初始化,是给与之对应的对象进行初始化,它具有针对性,函数中的一种。
①:函数名称和类名称必须相同
②:函数没有返回值类型
③:函数没有具体的返回值
记住:所有对象创建时,都需要初始化才可以使用。
注意事项:一个类在定义时,如果没有定义过构造函数,那么该类中会自动生成一个空参数的构造函数,为了方便该类创建对象,完成初始化。如果在类中自定义了构造函数,那么默认的构造函数就没有了。
一个类中,可以有多个构造函数,因为它们的函数名称都相同,所以只能通过参数列表来区分。所以,一个类中如果出现多个构造函数。它们的存在是以重载体现的。
构造代码块和构造方法的区别:“”构造代码块,是给所有的对象进行初始化,也就是说所有的对象都会调用一个构造代码块,只要对象一建立,就会调用这个构造代码块
构造方法,它是具有针对性的,给与之对应的对象进行初始化
public class TestClass {
static {
System.out.print("这是静态代码块");
}
{
System.out.print("这是构造代码块");
}
public TestClass() {
System.out.print("这是无参构造方法");
}
public TestClass(String a) {
System.out.print("这是带参数构造方法");
}
}
1、执行顺序:(优先级从高到低)静态代码块>main函数>构造代码块>构造方法,其中静态代码块只执行一次构造代码块在每次创建对象的时候都会执行2、静态代码块的使用场景,比如说调用C语言动态库的时候,将.so文件放到这个来调用
3、构造代码块的作用:(将不同构造方法*性的东西写在构造代码块中),比如说不论任何机型的手机都有开机这个功能,我们可以把开机这个功能写在这个方法中
5、创建一个对象在内存中都干了什么??
Object object = new Object();
- 先将硬盘位置上object.class文件加载进内存
- 执行main方法时,在栈内存中开辟main方法的空间(压栈—进栈),然后在main方法的栈区分配一个变量p
- 在堆内存中开辟一个实体空间分配一个内存首地址值, new
- 在该实体空间进行属性值的空间分配,并进行默认初始化
- 对空间中的属性进行显式初始化
- 进行实体的构造代码块初始化
- 调用该实体对象的构造方法,进行构造方法初始化
- 将首地址赋值给变量p,p变量就引用了该实体,指向了该对象
三、JAVA面向对象特性------封装
概念:封装指隐藏对象的属性和实现细节,对外提供公共访问方式
优势:安全性、提高复用率、便于代码解耦、易于使用
封装原则:将不需要对外提供访问的内容都隐藏起来,隐藏其属性,提供方法供外界使用
使用场景:我们将多次使用的方法体抽成工具类,对外提供一个方法以供其他使用,例如我们将dp2px,sp2px等不同分辨率相互转换的时候,将这些方法抽成一个工具类,对外提供分辨率转换的方法,而其他的类要使用它只需将dp参数传进来,而不需关注方法本身是怎么实现的,这就是封装的思想
四、JAVA的几个重要关键字
1、this 代表对象,当前函数所属对象的引用,也就是说哪个对象调用了当前函数,这个this就代表谁,就是哪个对象的引用,在开发中,什么时候会用到this,如果该方法内部使用了调用该方法的对象,这时候就用this来代表这个对象,this还能用于构造函数间的调用。调用格式
this(实际参数);this后跟 .调用的是成员属性和成员方法,this后跟 ()调用的是当前类中对应参数的构造方法。(super后跟 . 或者 () 就是调用父类中对应的成员属性或者对应参数的构造方法 )注意:用this调用对象的构造函数,必须定义在构造函数的第一行,因为构造函数是用来初始化的,这个动作一定要执行,否则编译失败
2、static 关键字,是一个修饰符,用来修饰成员变量和成员方法
- static变量,按照是否静态的对类成员变量进行修饰可以将其分为俩类,一是被static修饰的成员变量;一种是没有被修饰的成员变量,两者的区别是:对于静态成员内存中只有一次拷贝(为了节省内存),JVM只为静态成员分配一次内存,在加载类的过程中完成静态变量的内存分配,然后用类名来直接访问 ;对于普通成员变量,每创建一次实例变量,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响。
-
static方法,静态方法可以用类名调用,任何其实例对象也可以调用,所以在静态方法中不可以使用this和super,不能访问当前类的实例方法和实例成员变量(也就是带static关键字的方法不可以调用没有static修饰的方法和成员变量),因为static方法独立于其他实例方法,所以不可以抽象,必须实现它
-
static代码块,也叫静态代码块,是在类中独立于类成员的static语句块,可以有很多个,位置随意放,他不在任何的方法体中,JVM加载类的时候会执行这些静态代码块,如果在这个类中有很多的静态代码块,JVM将按照其在类中出现的顺序依次执行且只会执行一次,它的作用是可以完成类的初始化,静态代码块随着类的加载而执行,而且只执行一次,(就是说多次new对象,静态代码块只执行一次),如果和main函数在同一个类中,优先于main函数执行
- static和final一起使用,static final用来修饰成员变量和方法,可以简单理解为全局常量,对于变量,表示一旦给定就不能修改,且可以通过类名调用;对于方法,代表不可覆盖,且可以通过类名直接调用
- 有些数据是对象特有的数据,不能被静态修饰,如果被修饰了会变成对象的共享数据,所以在定义静态时,要确定是不是可以被对象所共享。
- 静态方法只能访问静态成员,非静态成员是不能访问的,这句话是针对同一个类的环境下,比如说,一个类有很多成员(属性、方法、字段),静态方法A,只能访问同类名下的静态成员,不是静态成员就不行,这是因为静态方法加载时是优先于对象的,也就是说静态方法运行时有可能对象中普通方法和变量还没有拷贝在内存中,那么我们当然就不能使用。
- 静态方法中不能使用this super这两个关键字,因为this代表对象,而在静态时,对象有可能还不存在所以就不能在静态方法中使用
静态成员和成员变量的区别:
1、静态变量属于类,所以也叫类变量;成员变量属于对象,所以也叫实例变量
2、静态变量存在于方法区中;成员变量存在于堆内存中。
3、静态变量随着类的加载而存在,随着类的消失而消亡;成员变量随着对象的实例化而存在,随着对象的消亡而消失。
4、成员变量只能被对象所调用,静态变量是类名和对象都可以调用,所以成员变量叫做对象的特有数据,而静态变量是类的共享数据。
3、final 根据程序上下午的意思Java关键字final有"这是无法改变的"或者"终态的"含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变、设计或效率。
$ final类不能被继承,没有子类,类中的方法默认是final方法
$ final方法不能被子类方法覆盖,但是可以被继承
$ final成员变量表示常量,只能被赋值一次,赋值后不可被改变
$ final不能修饰构造方法
注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的
1、final类
①:final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。
②:final方法 如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。使用final方法的原因有二:
第一、把方法锁定,防止任何继承类修改它的意义和实现。
第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。
③:final变量(常量)
用final修饰的成员变量表示常量,值一旦给定就无法改变! final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。 从下面的例子中可以看出,一旦给final变量初值后,值就不能再改变了。另外,final变量定义的时候,可以先声明,而不给初值,这中变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。但是,final空白在final关键字final的使用上提供了更大的灵活性,为此,一个类中的final数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。
2、生成JAVA帮助文档:命令格式 javadoc -d 文件夹名称 - author - version *.java
五、Java的特性 继承
java中只能单继承,不可以多继承,不过我们可以实现多个接口,如果我们调用父类的属性值,使用super这个关键字就好
一、成员变量:当子类和父类出现相同的属性时,子类对象调用属性,值就是子类对象的属性值,如果想要调用父类中的属性值,需要使用super关键字,
this --- 代表本类类型的对象引用
super --- 代表是子类所属父类中的内存空间引用
我们需要注意的是:子父类一般是不会出现相同变量名的成员变量,如果在父类中定义了,子类中通过继承直接就能使用了,无需再次定义
二、成员函数:当子父类中出现相同的方法时,运行子类对象会运行子类对象的方法,好像父类中的方法被覆盖掉一样,所以这种情况是函数的另一个特性:重写
三、构造函数:当子类构造方法运行的时候,会先运行父类的构造方法,这是为什么?
原因:子类的所有构造方法中第一行,都有一条隐形的语句,super();
super():表示父类的构造函数,且会调用与参数相对应的父类中的构造函数,既然无参数,那么它就是调用父类中的无参构造方法,为什么子类对象在初始化的时候都需要调用父类中的构造方法呢?是因为子类继承父类,会继承到父类的各种公有的成员变量和方法,很显然我要使用这些属性和方法的时候首先是对父类初始化,所以当我们在对子类进行初始化的时候,会先一步将调用父类构造函数将父类初始化,这就是子类的实例化过程。
注意:
- 子类的所有构造方法都会默认访问父类的无参构造方法,因为每一个子类中的构造方法第一行都有一句隐身的super();
- 如果父类中没有无参构造方法,那么子类的构造方法中,必须通过super语句制定要访问的父类构造方法。
- 如果子类构造方法中用this来制定调用子类自己的构造方法,那么被调用的构造方法也一样会访问父类的构造方法。
问题:
1、super()和this()是否可以同时出现在一个构造方法中?
答:不可以,两个语句都是必须定义在第一行,所以只能选用一行。
2、super()和this()语句为什么只能定义在第一行
答:因为super()和this()都是调用构造方法,是初始化类用的,初始化的步骤必须先执行。
在重写方法时,要注意两点:
- 子类重写父类方法的时候,必须要保证,子类方法的权限大于等于福诶方法的权限,可以实现继承,否则编译会报错的,(比如说,在父类中是public的方法,如果子类中将其降低访问权限为private,那么子类中重写以后的方法对于外部对象是不可访问的,这个就破坏了继承的含义)
- 重写时,要么都静态,要么都普通的方法(静态只能重写静态方法,或者被静态方法重写)
3、继承的一个弊端是打破了封装性,对于一些类或者类中的某些功能,是需要被继承或者被重写的,那么我们怎么解决这个问题?使用final 这个关键字
- 这个关键字是一个修饰符,可以修饰类,方法,变量。
- 被final修饰的类是最终的类,不可被继承
- 被final修饰的方法是最终的方法,不可被重写
- 被final修饰的属性是一个常量,只能赋值一次
六、JAVA特性---多态
概念:多态是指一种事物有着不同的具体的体现
体现:父类引用或者接口的引用指向了子类对象。//Animal a = new God ();父类可以调用子类中重写过的方法(必须是父类中有的)
多态的好处:提高了程序的扩展性。继承的父类或接口一般是类库中的(如果要修改某个方法的具体实现方式)只有通过子类去重写要改变的那个方法,这样在通过将父类的应用指向子类的实例去调用重写过的方法就行了
多态的弊端:当父类引用指向子类对象的时候,虽然提高了扩展性,但是只能访问父类中具备的方法,不可以访问子类中特有的方法(前期不能访问后期产生的功能,即访问的局限性)
多态的前提:
- 必须要有关系,继承或者实现
- 通常会有重写操作
如果想用子类对象的特有方法,如何判断对象那个具体的子类类型?可以通过一个关键字instanceof//判断对象是否实现了指定的接口或继承了指定的类
格式:<对象 instanceof 类型> 判断一个对象是否属于指定的类型。
Cat instanceof Animal= true;//Cat继承了Animal类
Object类:所有类的直接或者间接的父类,Java认为所有的对象都具备一些基本的共性内容,这些内容可以不断的向上抽取,最终就抽取到了一个最顶层的类中,该类中定义的就是所有对象都具备的功能
具体方法:
- boolean equals(Object obj):用于比较两个对象是否相等,其实内部比较的就是两个对象地址。
- String toString():将对象变成字符串;默认返回的格式:类名@哈希值
= getClass().getName() + '@' + Integer.toHexString(hashCode())
为了对象对应的字符串内容有意义,可以通过复写,建立该类对象自己特有的字符串表现形式。
public String toString(){
return "person : "+age;
} - Class getClass():获取任意对象运行时的所属字节码文件对象。
-
int
hashCode():返回该对象的哈希码值。支持此方法是为了提高哈希表的性能。将该对象的内部地址转换成一个整数来实现的。
通常equals,toString,hashCode,在应用中都会被复写,建立具体对象的特有的内容。
内部类:如果A类需要直接访问B类中的成员,而B类又需要建立A类的对象。这时,为了方便设计和访问,直接将A类定义在B类中。就可以了。A类就称为内部类。内部类可以直接访问外部类中的成员。而外部类想要访问内部类,必须要建立内部类的对象。
class Outer{
int num = 4;
class Inner {
void show(){
System.out.println("inner show run "+num);
}
}
public void method(){
Inner in = new Inner();//创建内部类的对象。
/**
* 调用内部类的方法。 //内部类直接访问外部类成员,用自己的实例对象;
* 外部类访问内部类要定义内部类的对象;
* */
in.show();
}
}
当内部类定义在外部类中的成员位置上,可以使用一些成员修饰符修饰 private、static。1:默认修饰符。直接访问内部类格式:外部类名.内部类名 变量名 = 外部类对象.内部类对象;
Outer.Inner in = new Outer.new Inner();//这种形式很少用。
但是这种应用不多见,因为内部类之所以定义在内部就是为了封装。想要获取内部类对象通常都通过外部类的方法来获取。这样可以对内部类对象进行控制。
2:私有修饰符。
通常内部类被封装,都会被私有化,因为封装性不让其他程序直接访问。
3:静态修饰符。
如果内部类被静态修饰,相当于外部类,会出现访问局限性,只能访问外部类中的静态成员。
注意;如果内部类中定义了静态成员,那么该内部类必须是静态的。
内部类编译后的文件名为:"外部类名$内部类名.java";
为什么内部类可以直接访问外部类中的成员呢?
那是因为内部中都持有一个外部类的引用。这个是引用是 外部类名.this
内部类可以定义在外部类中的成员位置上,也可以定义在外部类中的局部位置上。
当内部类被定义在局部位置上,只能访问局部中被final修饰的局部变量。
匿名内部类:没有名字的内部类。就是内部类的简化形式。一般只用一次就可以用这种形式。匿名内部类其实就是一个匿名子类对象。想要定义匿名内部类:需要前提,内部类必须继承一个类或者实现接口。
匿名内部类的格式:new 父类名&接口名(){ 定义子类成员或者覆盖父类方法 }.方法。
匿名内部类的使用场景:
当函数的参数是接口类型引用时,如果接口中的方法不超过3个。可以通过匿名内部类来完成参数的传递。其实就是在创建匿名内部类时,该类中的封装的方法不要过多,最好两个或者两个以内。
举例面试题:
//1
new Object() {
void show () {
System.out.println("show run");
}
}.show();
写法和编译都没问题
//2
Object obj = new Object(){
void show(){
System.out.println("show run");
}
};
obj.show();
写法正确,编译会报错
1和2的写法正确吗?有区别吗?说出原因。
写法是正确,1和2都是在通过匿名内部类建立一个Object类的子类对象。
区别:第一个可是编译通过,并运行。第二个编译失败,因为匿名内部类是一个子类对象,当用Object的obj引用指向时,就被提升为了Object类型,而编译时会检查Object类中是否有show方法,此时编译失败。
七、集合
1、list接口:有序,元素有索引,可以重复
集合特点:
- ArrayList:ArrayList实现了可变大小的数组,它可以存储所有元素,包括null,ArrayList不是同步的,底层是基于数组实现的数据结构,查找元素有下标,所以查询元素快,操作元素(增删改查)涉及到数组元素移动等内存操作,所以会比较慢
- LinkedList:底层是基于双向链表实现的数据结构,按序号索引数据需要向前或者后遍历,所以查询数据快,但是插入数据时只需要记录本项前后项即可,所以插入数据比较快,使用了synchronized方法,是线程安全的存储方法,所以性能上要比ArrayList差
- Vector:Vector与ArrayList非常类似,但是vector是线程同步的,由Vector创建的Iterator和ArrayList创建的Iterator虽然是同一个接口,但是由于Vector的同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(比如增删了一些元素),这是调用Iterator的方法会抛出ConcurrentModification,必须捕获异常,底层是基于数组实现的数据结构
集合和数组的区别:
集合和数组一样,都可以存储8种数据类型和object对象,那么它俩的区别在哪里:就是数组是定死的长度,而集合是可以随时改变元素个数的,这样说来集合就可以被称为可变长度的数组,那么这种特殊的数组--可以改变长度的原理是什么:当元素超出数组长度,会产生一个新的数组,将原数组的数据复制到新数组中,再将新的元素添加到新数组中,
ArrayList:是按照原数组的50%延长,构造一个初始容量为10的空列表;
Vector:是按照原数组的100%延长。
2、Set集合:set接口取出元素的方式只有迭代器的方式。
- HasSet:底层数据结构是哈希表,线程是不同步的,元素无序,不可重复,因为是线程不同步,所以效率高;HashSet是怎么保证集合元素的唯一性?通过元素的hashCode方法和equals方法完成的,当元素的HashCode值相同时,才继续判断元素的equals是否为true,如果为true那么视为相同元素,不存。如果为false,那么存储,如果hashCode值不同,则不判断equals,从而提高对象的比较速度
- LinkedHashSet:有序,hashSet的子类
- TreeSet:对Set集合中的元素进行指定顺序的排序,线程不同步,TreeSet底层的数据结构是二叉树,
对于ArrayList集合判断元素是否存在,或者删除元素底层一句都是equals方法;而HashSet集合判断元素是否存在,或者删除元素,底层依据是hashCode方法和equals方法。
3、Map集合:
- HashTable:底层是哈希表数据结构,是线程同步的,不可以存储null键和null值
- HashMap:底层是哈希表数据结构,线程不同步,可以存储null键和null值
- TreeMap:底层是二叉树数据结构,可以对map集合中的键进行指定顺序的排序
如何获取map集合中的所有元素:map集合中是没有迭代器的,collection具备迭代器,只要将map集合转成Set集合,就可以使用迭代器,之所以转成set,是因为map集合具备键的唯一性,实际上set集合就是来自于map,set集合底层用的就是map的方法。那么我们该如何将map集合转成set?Java提供了两个API,分别是:
1、 Set keySet();
Set<String> set = new HashSet<>();
Map<Integer, String> map = new HashMap<>();
private void initData() {
Set<Integer> keySet = map.keySet();
Iterator<Integer> iterator = keySet.iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
String value = map.get(key);
}
}
2、set entrySet();
Entry就是Map接口中的内部接口:为什么要定义在map内部?entry是访问键值关系的入口,对set集合进行迭代,迭代完 成后再通过get方法对获取到的键进行值的获取。以下是map取值的两种方式:
Set<String> set = new HashSet<>();
Map<Integer, String> map = new HashMap<>();
private void initData() {
Set<Map.Entry<Integer, String>> keySet = map.entrySet();
Iterator<Map.Entry<Integer, String>> iterator = keySet.iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> map = iterator.next();
Integer key = map.getKey();
String value = map.getValue();
}
}
将非同步的方法转换成同步的方法:Collections中的XXX synchronizedXXX(),原理是定义一个类,将集合所有的方法加同一把锁返回。
1、List synchronizedList(list);
List<Integer> list = new ArrayList<>();
Set<String> set = new HashSet<>();
Map<Integer, String> map = new HashMap<>();
private void initData() {
List<Integer> list = Collections.synchronizedList(this.list);
}
2、Map synchronizedMap(map);List<Integer> list = new ArrayList<>();
Set<String> set = new HashSet<>();
Map<Integer, String> map = new HashMap<>();
private void initData() {
List<Integer> list = Collections.synchronizedList(this.list);
Map<Integer, String> map = Collections.synchronizedMap(this.map);
}
Collection 和 Collections的区别:
- Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作。
-
Collection是个java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。
上一篇: Java面向对象--多态
下一篇: 浅谈Java面向对象思想