java的面向对象(从入门到深入)
目录
一、基本概念:
1.类
2.对象
3.继承
4.多态
5.封装
6.方法
7.接口
8.抽象
二、深入概念:
三:总结
一、基本概念:
1.类
类就是一个一个东西的蓝图,里面有着它的属性和方法。
2.对象
对象是一个类的实例化。
3.继承
继承就是拥有父类的属性和方法。
4.多态
多态就是在不同情况对象有着不同的形态,通过方法重写或重载。
5.封装
封装就是把类中的属性私有化,只能通过公共方法访问。我们的属性只能通过public的set和get方法访问,而不能直接修改。
6.方法
方法就是定义在类中的行为,比如我们的get和set方法还有work方法。
7.接口
接口定义了类中必须要实现的方法。
注意:如果我们的父类实现了接口,那么子类也可以直接使用父类的接口方法,而不需要用implements实现接口。
8.抽象
使用抽象类定义子类必须实现的方法。
注意以下几点:
(1)我们自己在写一些类的时候发现他们之间有共性,所以我们为了简化,可以将公共逻辑提取出来写写一个抽象类,这样可以减少代码冗余。
(2)抽象类只是在普通类的基础上增加了抽象方法,正因如此它不可以直接实例化,因为它的抽象方法没有方法体,所以不可方法调用,故无法实例化。
(3)抽象类必须要有子类继承,一个子类只能继承一个抽象类,并且子类必须重写抽象类的所有抽象方法,否则必须也要命名为抽象类。
(4)关于抽象类用final和static声明的问题:
final的类是不可变的,所以不可以被继承,那么自然抽象类不可以用final修饰。
外部抽象类不能直接用static修饰,而内部抽象类可以用static修饰,修饰后,别的类要继承它只需要用它的外部类.内部类即可。
二、深入概念:
1.类中变量:
一个类中存在这样一些变量,分别是局部变量,成员变量,类变量。
局部变量存在于构造方法,方法,或者语句块内,变量声明和初始化都在其中,在方法执行完后就销毁。
成员变量定义在类中,方法体之外,可以被构造方法,方法,语句块内访问。
类变量也是声明和定义在类中,方法体之外,但必须用static修饰。
2.构造方法:
如果一个类中没有构造方法,那么java虚拟机会默认提供一个无参的构造方法。
3.一个源文件只能有一个public类,可以有多个非public类。
4.三种变量的访问方法:
局部变量:直接使用。
子类成员变量:this.成员变量
父类成员变量:super.成员变量
5.内部类有4种类型:成员内部类,局部内部类,静态内部类,匿名内部类
如果内部类不想被外部类访问可以给内部类加private修饰
成员内部类:作为外部类的一个成员,可以访问外部类的所有成员,包括私有字段
方法:首先创建外部类的对象,然后通过外部类.内部类,就可以在外部类对象的基础上new出内部类对象。
局部内部类:指在方法中定义的类,只在方法中可见,可访问外部类成员以及方法内局部变量(需要声明为final)
方法:在外部类的方法中调用创建内部类对象,并调用方法
静态内部类:只能访问外部类的静态成员变量和静态成员方法。
方法:不需要创建外部类的对象就可以访问,即可以独立存在。
注意:外部类不能用static修饰。
匿名内部类:指没有类名的内部类,用于简化接口和继承
分为两种形式:第一种匿名类继承一个父类,第二种匿名类实现一个接口
下面是第一种形式:
下面是第二种形式:
6.java中的不可变类:
不可变类即在创建对象后不可改变其属性。
注意以下几点:
(1)声明的类用final修饰,防止子类继承。
(2)类中的字段都用final或private修饰。
(3)通过构造函数初始化所有字段,不提供修改字段的方法。
(4)java中常见的不可变类有String,Integer,BigDecimal,LocalDate……
不可变类优缺点:
优点:
(1)线程安全:由于不可修改,故天然就是线程安全的,无需在并发下同步。
(2)缓存友好:不可变对象可以安全的缓存和共享。
(3)防止状态不一致:避免对象被修改而导致不一致的问题。
(4)哈希值不会反复变化,提高哈希表等数据结构的性能。哈希值在第一次计算出来放进缓存中,这就提高了哈希表的性能,不会反复计算。
缺点:由于可能会频繁的创建对象,所以性能开销较大。
下面通过String类的源码讲解一下:
我们可以看到String类使用final修饰的,并且内部用的是byte[]数组存储的(JDK8中是char),那么为何不用char类型呢?其实是为了节省字符串所占有的内存空间。同时还有另一个好处,那就是减少GC(垃圾回收次数减少)
同时String类实现了Serializable接口,Comparable接口,前者意味着可以序列化,后者意味着可以用compareTo方法比较。注意:"=="是用来比较两个对象的地址的,我们可以用equals方法去比较字符串的内容。
更深入一点:
现在的JDK使用coder来表示编码,那先说说字符集吧:
ASCLL,Latin1,Unicode,UTF-8
(1)ASCLL码表实现的是阿拉伯数字和英文字母,以及一些其它的字符,对于英语国家是够用的,但无法表达别的国家的文字。ASCLL码表用一个字节储存,最高位为奇偶效验位,其余7位为存储位。其它大多数编码都是兼容ASCLL码表的。
(2)Latin1是一种单字符集,也就是说最多只能表示256个字母或符号。Latin1是Mysql默认的编码形式。
(3)Unicode是一个庞大的字符集,目前可容纳100多万个字符,几乎可以表达出地球上所有的文字。但是使用的存储空间会稍大一点。
(4)UTF-8是使用最广的一种Unicode编码的实现形式,可以随着不同的符号变换字节长度。注意如果文件中有UTF-8编码的文字,又有GB系列编码的文字,那么就会导致乱码,因为二者不兼容。
在jvm里char是占用两个字节的,使用UTF-8编码,那么也就是说如果我的String类型的字符只用一个字节就可以表示,它也要占用两个字节。
当我们使用byte类型存储,并且使用Latin1字符编码,就会比UTF-8节省很多空间。
下面再说说字符串常量池:
java中存在着字符串常量池,当我们有相同的字符串时,那么这两个字符串指向同一个对象。提高性能,降低内存开销。来想下面一行代码:
String s = new String("abc");
思考一下,它创建了几个对象。
答案是两个,为啥呢?往下看。
当我们使用new创建一个字符串对象时,jvm会先在字符串常量池找有没有这个值的对象,如果有,那么就不会在常量池中创建对象了,而是直接在堆中创建"abc"这个字符串对象。然后将它的地址返回给变量s。变量s在栈上。
如果字符串常量池没有这个对象,那么就会先在常量池中创建"abc"这个字符串对象,然后在堆上创建"abc"这个字符串对象,然后将这个地址返回给s。变量s在栈上。
在java中,对象本身都在堆中,而基本数据类型变量和对象的引用都在栈上。
但是一般我们不通过new来创建字符串对象。看下面的代码:
String s = "abc";
当我们执行上面一行代码时,jvm会先在字符串常量池中找有没有"abc"的对象,如果有,则不用创建任何对象,而是直接将字符串常量池的这个对象地址返回给s。即此时堆中无new出来的对象。只有栈中的s变量去调用字符串常量池的相应对象地址。
如果字符串常量池中没有这个对象,那么现在字符串常量池中创建"abc"这个对象。然后将这个地址返回给s。
所以,如果使用new关键字,那么堆中就有两个对象,如果不使用new关键字,那么堆中只有一个对象,即字符串常量池的那个。
再深入一下intern()方法。
我们之前说过"=="比较的是地址是否相等,equals比较的是内容是否相等。
我们看到下面的代码,s变量指向的是堆中new出来的字符串对象,而ss变量指向的是在字符串常量池中的字符串对象。所以地址不一样,返回false。
然而思考一下下面的代码:
为什么现在返回的是true呢?下面将给出解释:
首先在字符串常量池中先创建"def"和"ghi",然后在堆中创建两个匿名对象(也叫临时对象,生命周期一般较短),然后在堆上创建一个新的对象"defghi"(此时字符串常量池没有这个对象),然后b引用这个对象。继续,在字符串常量池找"defghi"这个对象是否存在,此时不存在,但是堆中已经有了这个对象了。那么现在字符串常量池就保存堆中"defghi"这个对象的引用。即现在b和bb的引用地址一直,故返回true。
再说说"+"是怎么拼接两个字符串的吧,实际上,实现创建了一个StringBuilder的对象,然后用append()把"def"和"ghi"加进去,最后使用toString()方法返回新的字符串对象。
我们知道StringBuilder是可变长的字符串变量,不是常量,那么自然不会在字符串常量池中创建。
最后在讲一讲String,StringBuffer,StringBuilder的区别:
String是不可变的,适用于字符串不会频繁变化的场景。
StringBuffer是可变的,内部使用了synchronized关键字保证了线程安全。适用于多线程下频繁改变字符串的场景。
StringBuilder是可变的,不保证线程安全,性能比StringBuffer更好(去掉了保证线程安全的部分)。减少了开销。
那么我们String类到此为止,希望能够对您有所帮助。
7.方法重载与方法重写:
方法重载为一个方法有着不同的参数。
方法重写为子类在继承父类时,可以重写父类中的一些方法,从而实现多态性。
注意:重载与返回值没有关系。重写时,子类的方法访问级别不能比父类更严格。即父类为protected,子类不能为private。子类抛出的异常必须与父类一致,或者是父类异常的子类。
8.为什么要经常重写hashCode和equals方法:
两个方法的关系:如果equals相同,那么hashcode值必须相同,反过来则不一定相同。
在使用HashMap和HashSet等集合时,这些集合利用equals和hashCode确定元素的存储位置。如果不正确重写这两个方法,会导致无法判断对象的相等性。
重写equals方法,必须重写hashCode方法,因为两个对象如果equals方法相等,那么必须hashCode也是返回相等的值。
在基于哈希的数据结构,如果要判断是相等的,那么必须要先用hashCode方法判断哈希码是否相等,再用equals方法判断是否真正相等。由于不同的对象可能有着相同的哈希码,所以会用equals方法判断是否真正相等。
9.静态方法与实例方法:
特性 | 静态方法 | 实例方法 |
---|---|---|
关键字 | static | 无 |
归属 | 类 | 对象 |
调用方式 | 通过类名或对象 | 对象 |
访问权限 | 静态变量,静态方法 | 静态变量,静态方法,实例变量,实例方法 |
用途 | 工厂类方法,工厂方法 | 改变对象的状态 |
生命期 | 类加载时存在,类卸载时消失 | 对象创建时存在,对象销毁时消失 |
10.Object类:
getClass方法:返回对象运行时的信息,通过该方法了解对象的实际类型。
常用于反射机制。
hashCode方法:返回对象的哈希码,哈希码用于在基于哈希的数据结构中,快速地查找对象。当重写equals方法时,也要重写hashCode方法,确保相等的对象有着相同的哈希码。
equals方法:比较两个对象的地址值是否相等,如要比较内容是否相等,需重写equals方法。
clone方法:创建并返回该对象的副本,默认是浅拷贝。即复制对象本身,但不复制其内部引用。
为了使用该方法,类需要实现Cloneable接口,并重写clone方法,否则会抛出异常。
toString方法:返回对象的字符串表现形式,默认返回对象的类名和内存地址的组合。
常常重写该方法返回有意义的字符串。
notify方法:唤醒一个等待在该对象上的线程。
该方法通常与synchronized和wait()方法一起使用,起到线程之间的协调。
notifyAll方法:唤醒所有等待在该对象上的线程。
wait方法有三个:
第一个:无限期等待。
第二个:等待指定的毫秒数。
第三个:等待指定的毫秒数和指定的纳秒数。
通常与synchronized一起使用,达到线程间的协调。
finalize方法:垃圾回收器回收对象时使用,用于清理工作。但不推荐使用,因为java的垃圾回收机制不可预测且过时,所以常使用try-with-resources和AutoCloseable接口进行资源管理。
11.包:
(1)将功能相似的类或者接口放在一个包下,便于类的查找与使用。
(2)包可以避免命名冲突。同一个包中的类名可以是不同的,但是不同的包的类名可以是相同的,只需要用包名加以区分即可。
(3)包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
既然说到了打包,那再来说说导包吧:
import关键字:为了使用别的包中成员,我们需要在程序中导入该包。
12.基本类型和包装类型:
基本类型:java中有8个基本类型,是直接存储数值的,变量位置取决于作用域和声明方式。
分别为short, int, long, float, double, boolean, char, byte.
包装类型:java中每个基本类型都有一个包装类型,包装类型是类,存储在堆上,可用于面向对象编程。
分别为Short, Integer, Long, Float, Double, Boolean, Character, Char, Byte.
区别:
(1)性能方面:
基本类型占用内存小,效率高。
而包装类型由于是对象,需要涉及内存分配和垃圾回收,性能较低。
(2)比较方式不同:
基本类型只需==就可以判断是否相等。
而包装类型的==是判断对象地址是否相等,使用equals是判断对象内容是否相等。
(3)默认值不同:
基本类型默认值为0或false.
而包装类型的默认值为null.
(4)初始化方式不同:
基本数据类型直接赋值即可。
而包装类型需要new一个对象。
(5)存储方式不同:
基本类型如果是局部变量那么就存储在栈上面,如果是成员变量就存储在堆上面。
而包装类型保存在堆上。
自动装箱与拆箱:
在java中,我们很多时候用到的是包装类型而不是基本类型。在集合当中,我们是不能讲int,double这些类型放进去的,因为集合的中的元素要求是Object类型的,那么为了让基本类型有对象的性质,就把它们包装起来,并添加了相应的属性和方法。
装箱:基本类型自动转为包装类型对象。
拆箱:包装类型对象自动转为基本类型。
缓存机制:
包装类型中的Byte,Short,Interger, Long对一些范围内的值提供了缓存,提升了性能。
下面会通过Integer缓存池详细讲解:
java中的Integer缓存池是为了节省内存,提升性能的,因为实践中大多数操作都集中在值很小的范围内,因此缓存下来可以节省内存分配和垃圾回收的负担,提高性能。
java中Integer缓存的值在-128到127,这些值会被缓存下来复用。
过程:java在自动装箱时,如果int的范围在-128到127之间,会直接返回一个已经缓存的Integer对象,而不是创建新的对象。因此,同一数值的包装类型对象可能是同一实例。
下面是值在缓存池范围内的情况
下面是值不在缓存池范围内的情况
额外扩展:
Long, Short,Byte缓存范围也是-128到127
Float和Double没有缓存池,因为小数可以存的很多。
Character缓存范围为0到127,即ASCLL码表。
Boolean缓存两个值,true和false。
下面给出长度的总结:
byte(Byte):1字节 -128到127
short(Short):2字节 -32768到32767
int(Integer):4字节 -2147483648到2147483647
long(Long): 8字节 -9223372036854775808到9223372036854775807
char(Character):2字节 Unicode字符集中任意字符
float(Float):4字节 约为-3.4E38到3.4E38
double(Double):8字节 约为-1.7E108到1.7E308
13.static关键字:
static的作用有许多:
首先来看下面代码
当我们如果要创建很多个学生对象时,那么每个对象的三个属性都会占用一定的内存,但是其实这些学生的school属性都是相同的,我们如果创建10000个学生对象,那么这10000个学生对象都各自占用一部分内存给school,那么是不是太浪费了?所以我们可以用static修饰,这样我们只要占用1块内存,而不是10000块。
那么此时我们的变量a在栈上,指向的是在堆中的对象的地址,其对象中有张三和18两个属性。
变量b也在栈上,指向的是在堆中对象的地址,其对象中有李四和16两个属性。
我们还有一个静态区,里面存放着school属性"MIT"。
再看下面一段代码:
我们每次创建对象,对象中都有各自的age值,自增完在打印,所以结果都为1,那么我们如果加上static修饰呢?
由于现在的age在静态区,所以我们每次都会自增静态区中的age,所以我们打印的为1,2,3。
这就是静态变量和成员变量之间的区别。
下面再说说静态方法吧:
(1)静态方法属于这个类,但是不是这个类的对象。
(2)调用静态方法时不用创建这个类的对象。
(3)静态方法可以访问静态变量。
change是一个静态方法,所以可以直接访问到静态变量age并修改,并且调用的时候直接用类名.上静态方法即可。主要,静态方法不可调用非静态方法和非静态变量。
那么我们可能会思考为什么main方法是用static修饰的呢?
public static void main(String[] args)
首先我们已经知道静态方法是不用创建对象的,所以,如果不是静态的,对于jvm而言,在执行时要先创建对象,但是main函数是程序执行的入口,创建一个对象就显得多余了,所以用static修饰。
下面再说说静态代码块吧:
来看下面的代码:
我们看到静态代码块优先于main函数执行。但是程序中没有main方法在JDK1.6是可以的,但是在JDK7就不行了。
那么实际开发中静态代码块到底有什么用呢?
a是一个静态的ArrayList,所以不太可能在初始声明时就初始化,因此我们可以在静态代码块中完成初始化。在开发中,我们常常使用静态代码块配置文件到内存中。
14.instanceof关键字
instanceof关键字是判断对象是否是指定的类型。如果不判断的话那么可能会出现ClassCastException 异常。
来看instanceof的运用
最后的结果是true,因为Bigtree是tree的子类,所以说Bigtree和Tree是is-a的关系,故返回true.
同理,对于接口也是如此。
再看以下代码,是我们比较常用的方式,先把当前对象使用instance进行类型判断,然后进行类型强制转换。以下就是两种方式:
第二种方式是JDK16的时候新增的,使代码更加便捷。
三:总结
看到这里,您是不是对于java的面向对象已经有所了解了呢?本文中的所有代码难度不大,应该对初学者比较友好,可以自己练习一下,毕竟编程最重要的就是实践,没有亲自去写,那么就不能抓住代码中的细枝末节,当然也就没有彻底理解。所以,大家要多写,多练,这样才能提高自己!
如果有疑问或者错误的地方可以私信我或者评论区留言。
感谢您的阅读,我们下篇文章详解JAVA集合时再回。