当前位置: 首页 > article >正文

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字节 -21474836482147483647

long(Long): 8字节 -92233720368547758089223372036854775807

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集合时再回。


http://www.kler.cn/a/385025.html

相关文章:

  • [linux]docker基础
  • Python小白学习教程从入门到入坑------第二十八课 文件基础操作文件读写(语法进阶)
  • vue+websocket实现即时聊天平台
  • Spring面向切面编程
  • 【数学二】线性代数-向量-向量组的秩、矩阵得秩
  • 如何快速搭建一个spring boot项目
  • harmony os 四层架构分析
  • Elasticsearch(三):Elasticvue使用及DSL执行新增、查询操作
  • Hive:explode 和 lateral view
  • 算法通关(3) -- kmp算法
  • leetcode155:最小栈
  • Java中怎样将bytes转换为long类型?
  • blender中,渲染是指渲染图片or视频 ,还是模型?
  • 前端开发实现自定义勾选/自定义样式,可复选,可取消勾选
  • Maven的安装配置
  • Docker与Cgroups资源控制实战
  • 如何通过网络加速器提升TikTok创作与观看体验
  • ORACLE _11G_R2_ASM 常用命令
  • 设置域名跨越访问
  • 第J5周:DenseNet+SE-Net实战
  • DNS服务器
  • 使用QtWebEngine的Mac应用如何发布App Store
  • PHP实现全站静态化
  • C++学习笔记----10、模块、头文件及各种主题(四)---- 头文件
  • 论文阅读《Structure-from-Motion Revisited》
  • Excel 无法打开文件