Java面对对象笔记

Object若不重写hashCode()的话,hashCode()如何计算出来的?

Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法直接返回对象的 内存地址。

为什么重写equals还要重写hashcode

为了遵守:2个对象equals,那么 hashCode一定相同规则。参与equals函数的字段,也必须都参与hashCode 的计算。

equals方法:

Object类中默认的实现方式是 : return this == obj 。那就是说,只有this 和 obj引用同一个对象,才会返回true。

而我们往往需要用equals来判断2个对象是否等价,而非验证他们的唯一性。这样我们在实现自己的类时,就要重写equals.

按照约定,equals要满足以下规则。

自反性: x.equals(x) 一定是true

对null: x.equals(null) 一定是false

对称性: x.equals(y) 和 y.equals(x)结果一致

传递性: a 和 b equals , b 和 c equals,那么 a 和 c也一定equals。

一致性: 在某个运行时期间,2个对象的状态的改变不会影响equals的决策结果,那么,在这个运行时期间,无论调用多少次equals,都返回相同的结果。

hashCode方法返回值是int类型的散列码。

在某个运行时期间,只要对象的(字段的)变化不会影响equals方法的决策结果,那么,在这个期间,无论调用多少次hashCode,都必须返回同一个散列码。

如果2个对象通过equals调用后返回是true,那么这个2个对象的hashCode方法也必须返回同样的int型散列码。

如果2个对象通过equals返回false,他们的hashCode返回的值允许相同。

1、如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等。

2、如果两个对象不equals,他们的hashcode有可能相等。(equals直接返回false,不会报错)

3、如果两个对象hashcode相等,他们不一定equals。

4、如果两个对象hashcode不相等,他们一定不equals。

重写hashCode时注意事项

重写hashCode方法时除了上述一致性约定,还有以下几点需要注意:

(1)返回的hash值是int型的,防止溢出。

(2)不同的对象返回的hash值应该尽量不同。(为了hashMap等集合的效率问题)

(3)《Java编程思想》中提到一种情况

“设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。如果在讲一个对象用put()添加进HashMap时产生一个hashCdoe值,而用get()取出时却产生了另一个hashCode值,那么就无法获取该对象了。所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码”。

构造函数,构造函数重载,复制构造函数

当新对象被创建的时候,构造函数会被调用。每一个类都有构造函数。在程序员没有给类提供构造函数的情况下,Java编译器会为这个类创建一个默认的构造函数。
Java中构造函数重载和方法重载很相似。可以为一个类创建多个构造函数。每一个构造函数必须有它自己唯一的参数列表。

复制构造函数用来复制对象,将地址传递和值传递统一起来,归根结底还是传递的是"值"(地址也是值,只不过通过它可以找到另一个值)!

Java不支持像C++中那样的复制构造函数,这个不同点是因为如果你不自己写构造函数的情况下,Java不会创建默认的复制构造函数。

方法重载(Overloading)和方法覆盖(Overriding)

Java中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。

面向对象的"六原则一法则"

  • 单一职责原则:一个类只做它该做的事情。(单一职责原则想表达的就是"高内聚",写代码最终极的原则只有六个字"高内聚、低耦合",所谓的高内聚就是一个代码模块只完成一项功能,在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。另一个是模块化,好的自行车是组装车,从减震叉、刹车到变速器,所有的部件都是可以拆卸和重新组装的,好的乒乓球拍也不是成品拍,一定是底板和胶皮可以拆分和自行组装的,一个好的软件系统,它里面的每个功能模块也应该是可以轻易的拿到其他系统中使用的,这样才能实现软件复用的目标。)
  • 开闭原则:软件实体应当对扩展开放,对修改关闭。(在理想的状态下,当我们需要为一个软件系统增加新功能时,只需要从原来的系统派生出一些新类就可以,不需要修改原来的任何一行代码。要做到开闭有两个要点:①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱,如果不清楚如何封装可变性,可以参考《设计模式精解》一书中对桥梁模式的讲解的章节。)
  • 依赖倒转原则:面向接口编程。(该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代,请参考下面的里氏替换原则。)
  • 里氏替换原则:任何时候都可以用子类型替换掉父类型。(关于里氏替换原则的描述,Barbara
    Liskov女士的描述比这个要复杂得多,但简单的说就是能用父类型的地方就一定能使用子类型。里氏替换原则可以检查继承关系是否合理,如果一个继承关系违背了里氏替换原则,那么这个继承关系一定是错误的,需要对代码进行重构。例如让猫继承狗,或者狗继承猫,又或者让正方形继承长方形都是错误的继承关系,因为你很容易找到违反里氏替换原则的场景。需要注意的是:子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。)
  • 接口隔离原则:接口要小而专,绝不能大而全。(臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。例如,琴棋书画就应该分别设计为四个接口,而不应设计成一个接口中的四个方法,因为如果设计成一个接口中的四个方法,那么这个接口很难用,毕竟琴棋书画四样都精通的人还是少数,而如果设计成四个接口,会几项就实现几个接口,这样的话每个接口被复用的可能性是很高的。Java中的接口代表能力、代表约定、代表角色,能否正确的使用接口一定是编程水平高低的重要标识。)
  • 合成聚合复用原则:优先使用聚合或合成关系复用代码。(通过继承来复用代码是面向对象程序设计中被滥用得最多的东西,因为所有的教科书都无一例外的对继承进行了鼓吹从而误导了初学者,类与类之间简单的说有三种关系,Is-A关系、Has-A关系、Use-A关系,分别代表继承、关联和依赖。其中,关联关系根据其关联的强度又可以进一步划分为关联、聚合和合成,但说白了都是Has-A关系,合成聚合复用原则想表达的是优先考虑Has-A关系而不是Is-A关系复用代码,原因嘛可以自己从百度上找到一万个理由,需要说明的是,即使在Java的API中也有不少滥用继承的例子,例如Properties类继承了Hashtable类,Stack类继承了Vector类,这些继承明显就是错误的,更好的做法是在Properties类中放置一个Hashtable类型的成员并且将其键和值都设置为字符串来存储数据,而Stack类的设计也应该是在Stack类中放一个Vector对象来存储数据。记住:任何时候都不要继承工具类,工具是可以拥有并可以使用的,而不是拿来继承的。)
  • 迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。再复杂的系统都可以为用户提供一个简单的门面,Java Web开发中作为前端控制器的Servlet或Filter不就是一个门面吗,浏览器对服务器的运作方式一无所知,但是通过前端控制器就能够根据你的请求得到相应的服务。调停者模式也可以举一个简单的例子来说明,例如一台计算机,CPU、内存、硬盘、显卡、声卡各种设备需要相互配合才能很好的工作,但是如果这些东西都直接连接到一起,计算机的布线将异常复杂,在这种情况下,主板作为一个调停者的身份出现,它将各个设备连接在一起而不需要每个设备之间直接交换数据,这样就减小了系统的耦合度和复杂度。

关联指一个类的实例A使用另外一个类的实例B,这两个对象之间为关联关系,关联关系分为单项关联和双向关联。在Java中,单向关联表现为:类A当中使用了类B,其中B作为类A的成员变量。双向关联表现为:类A当中使用了类B作为成员变量;同时类B中也使用了类A作为成员变量。

依赖指两个相对独立的对象,当一个对象负责构造另一个对象的实例,或者依赖另一个对象的服务时,这两个对象之间主要体现为依赖关系。在Java中,类A当中使用了类B,其中类B是作为类A的方法参数、方法中的局部变量、或者静态方法调用。

通过反射获取和设置对象私有字段的值

可以通过类对象的getDeclaredField()方法字段(Field)对象,然后再通过字段对象的setAccessible(true)将其设置为可以访问,接下来就可以通过get/set方法来获取/设置字段的值了。

重载和重写

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。

  1. 方法重载的规则:
    (1)方法名一致,参数列表中参数的顺序、类型、个数不同。

(2)不能根据返回类型来区分重载,存在于父类和子类,同类中。
(3)可以抛出不同的异常,可以有不同的修饰符。

  1. 方法重写的规则:
    (1)参数列表和返回值类型必须与被重写方法的一致。

(2)构造方法不能被重写,声明为final的方法不能被重写,声明为static的方法不能被重写,但是能够被再次声明。
(3)比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏替换原则)。

内部类可以引用它的包含类的成员吗?有没有什么限制?

一个内部类对象可以访问创建它的外部类对象的内容。

内部类如果不是static的,那么它可以访问创建它的外部类对象的所有属性。

内部类如果是sattic的,即为nested class,那么它只可以访问创建它的外部类对象的所有static属性。

一般普通类只有public或package的访问修饰,而内部类可以实现static,protected,private等访问修饰。当从外部类继承的时候,内部类是不会被覆盖的,它们是完全独立的实体,每个都在自己的命名空间内,如果从内部类中明确地继承,就可以覆盖原来内部类的方法。

JAVA语言如何进行异常处理

Java 通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java中,每个异常都是一个对象,它是Throwable类或其它子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这

throw用于手动地抛出异常对象。throw后面需要一个异常对象。

throws用于在方法签名中声明抛出一个或多个异常类,throws关键字后可以紧跟一个或多个异常类。

一般情况下是用try来执行一段程序,如果出现异常,系统会抛出(throws)一个异常,这时候你可以通过它的类型来捕捉(catch)它,或最后(finally)由缺省处理器来处理。

在try块中可以抛出异常吗

try块中可以抛出异常,之所以很少见,因为这样做就是多此一举,因为try本身就是检测异常并抛出的,你手动抛出有什么意义呢,这是一般情况。但是也有特殊情况,比如在try块中你加入了特殊判断条件并手动抛出其他异常。

当一个对象被当作参数传递给一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递

是值传递。Java 编程语言只有值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的内容可以在被调用的方法中改变,但对象的引用是永远不会改变的。

静态嵌套类Static Nested Class 和 内部类Inner Class的不同

Nested class分为静态Static nested class的和非静态的inner class。其中inner类又可分为三种:

(1) 在一个类(外部类)中直接定义的内部类;

(2) 在一个方法(外部类的方法)中定义的内部类;

(3) 匿名内部类.

Static Nested Class是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化。Static-Nested Class 的成员, 既可以定义为静态的(static), 也可以定义为动态的(instance).

Nested Class的静态成员(Method)只能对Outer Class的静态成员(static memebr)进行操作(ACCESS), 而不能Access Outer Class的动态成员(instance member).而 Nested Class的动态成员(instance method) 却可以 Access Outer Class的所有成员。

有一个普通的原则, 因为静态方法(static method) 总是跟 CLASS 相关联(bind CLASS), 而动态方法( (instance method) 总是跟 instance object 相关联, 所以,静态方法(static method)永远不可以Access跟 object 相关的动态成员(instance member),反过来就可以, 一个CLASS的 instance object 可以 Access 这个 Class 的任何成员, 包括静态成员(static member).

匿名内部类

当一个内部类的类声名只是在创建此类对象时用了一次,而且要产生的新类需继承于一个已有的父类或实现一个接口,才能考虑用匿名类,由于匿名类本身无名,因此它也就不存在构造方法,它需要显示地调用一个无参的父类的构造方法,并且重写父类的方法。

匿名内部类不能有构造方法。

匿名内部类不能定义任何静态成员、方法和类。

匿名内部类不能是public,protected,private,static。

只能创建匿名内部类的一个实例。

一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。

因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。

abstract class和interface区别

含有abstract修饰符的class即为抽象类,abstract 类不能创建的实例对象。含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。abstract class类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。

接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。

下面比较一下两者的语法区别:

1.抽象类可以有构造方法,接口中不能有构造方法。

2.抽象类中可以有普通成员变量,接口中没有普通成员变量(public static final)

3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

4.抽象类中的抽象方法的访问类型可以是public,protected,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。

5.抽象类中可以包含静态方法,接口中不能包含静态方法

6.抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。

7.一个类可以实现多个接口,但只能继承一个抽象类。

Overload和Override的区别

重写(Override)也称覆盖,它是父类与子类之间多态性的一种表现,而重载(Overload)是一个类中多态性的一种表现。

override(重写,覆盖) 
(1)方法名、参数、返回值相同。 
(2)子类方法不能缩小父类方法的访问权限。 
(3)子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。 
(4)存在于父类和子类之间。 
(5)方法被定义为final不能被重写。 
(6)被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。

overload(重载,过载) 
(1)参数类型、个数、顺序至少有一个不相同。 
(2)不能重载只有返回值不同的方法名。 
(3)针对于一个类而言。 
(4)不能通过访问权限、返回类型、抛出的异常进行重载; 
(5)方法的异常类型和数目不会对重载造成影响。

override是在不同类之间的行为,overload是在同一个类中的行为。

Overload的方法可以改变返回值的类型,因为它与返回值类型无关。

final, finally, finalize的区别

final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。

finally是异常处理语句结构的一部分,表示总是执行。

finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。

面向对象的特征

面向对象的编程语言有封装、继承 、抽象、多态等4个主要的特征。

  • 抽象:

我们在定义一个类的时候,实际上就是把一类事物的公有的属性和行为提取出来,形成一个物理模型,这种研究问题的方法称为抽象。

  • 封装:

封装就是把抽象的数据和对数据进行的操作封装在一起,数据被保存在内部,程序的其他部分只有通过被授权的操作(成员方法)才能对数据进行操作。

电视机的开关,对音量,颜色,频道的控制是公开的,谁都可以操作,但是对机箱后盖,主机板的操作却不是公开的,一般是由专业维修人员来玩。

  • 继承:

继承可以解决代码复用问题,让我们编程更加靠近人类的思维,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类(比如刚才的Student),在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extend语句来声明继承 父类:

class 子类 extends 父类

这样,子类就会自动拥有父类定义的属性和方法。

java中类间不支持多继承(一个继承多个,不同父类可能有同名同参同返方法,会混。解决办法:内部类,创建一个类,类内创建多个内部类分别继承,创建对象写函数调用即可),支持多层继承、多实现;接口与接口间支持多继承。

  • 多态:

所谓多态,就是指一个引用(类型)在不同情况下的多种状态,你也可以这样理解:多态是指通过指向父类的指针,来调用在不同子类中实现的方法。

多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。

多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编译时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

多态的好处:

后面创建的类是前面创建的那个类的子类,就可以通过父类创建的对象对子类的方法进行访问,一个对象可以访问多个方法。

实现多态的机制

父类或者接口定义的引用变量可以指向子类或者具体实现类的实例对象,由于程序调用方法是在运行期才动态绑定的,那么引用变量所指向的具体实例对象在运行期才确定。所以这个对象的方法是运行期正在内存运行的这个对象的方法而不是引用变量的类型中定义的方法。

静态分派:所有依赖静态类型来定位方法执行版本的分派动作。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的,而是由编译器来完成。(编译时多态)

动态分派:在运行期根据实际类型确定方法执行版本的分派动作。(运行时多态)

重载是编译时多态,重写是运行时多态。

Comparable和Comparator接口的作用以及它们的区别

Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。

Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。

Set是无序的(存入取出顺序不一致),不重复;

HashSet底层是哈希表,线程不同步;存对象时候要在对象的类中写equals和hashCode方法,这样Set才能判断自动去重(先判断hashCode是否相同,再判断equals);

TreeSet虽存入取出顺序不一致,但内部会根据字典序自动排序,内部存储BST,c++用的set跟TreeSet比较像;第一种排序方法:元素自身具有可比性,实现Comparable接口强制让自定义对象具有比较性,只有一个compareTo方法,直接只返回1就是按时间顺序,直接只返回0就是只能存一个;第二种排序方法:元素自身不具有可比性,让集合自身具有可比性;定义比较器(实现Comparator接口,覆盖compare(obj1,obj2)),将其对象作为参数(传Collection.reverseOrder()可强行逆转自然顺序)传入TreeSet构造函数;两种排序方法都存在时以比较器为主;

Java是否支持多继承

Java中类不支持多继承,只支持单继承(即一个类只有一个父类),多实现。 但是java中的接口支持多继承,,即一个子接口可以有多个父接口。(接口的作用是用来扩展对象的功能,一个子接口继承多个父接口,说明子接口扩展了多个功能,当类实现接口时,类就扩展了相应的功能)。

JAVA中反射的实现过程和作用

JAVA语言编译之后会生成一个.class文件,反射就是通过字节码文件找到某一个类、类中的方法以及属性等。反射的实现主要借助以下四个类:Class:类的对象,Constructor:类的构造方法,Field:类中的属性对象,Method:类中的方法对象。

作用:反射机制指的是程序在运行时能够获取自身的信息。在JAVA中,只要给定类的名字,那么就可以通过反射机制来获取类的所有信息。

通过反射创建对象

方法1:通过类对象调用newInstance()方法,例如:String.class.newInstance()

方法2:通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象,例如:

String.class.getConstructor(String.class).newInstance("Hello");

通过反射获取和设置对象私有字段的值

可以通过类对象的getDeclaredField()方法字段(Field)对象,然后再通过字段对象的setAccessible(true)将其设置为可以访问,接下来就可以通过get/set方法来获取/设置字段的值了。

extends 和super 泛型限定符-上界不存下界不取

extends上限通配符,用来限制类型的上限,只能传入本类和子类,add方法受阻,可以从一个数据类型里获取数据;上界的list只能get,不能add(确切地说不能add出除null之外的对象,包括Object)

super下限通配符,用来限制类型的下限,只能传入本类和父类,get方法受阻,可以把对象写入一个数据结构里;下界的list只能add,不能get。

在java泛型中,? 表示通配符,代表未知类型,< ? extends Object>表示上边界限定通配符,< ? super Object>表示下边界限定通配符。

通配符 与 T 的区别

T:作用于模板上,用于将数据类型进行参数化,不能用于实例化对象。 
?:在实例化对象的时候,不确定泛型参数的具体类型时,可以使用通配符进行对象定义。

< T > 等同于 < T extends Object>
< ? > 等同于 < ? extends Object>

如果你既想存,又想取,那就别用通配符
不能同时声明泛型通配符上界和下界

泛型

泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

public class GenericTest {
  
      public static void main(String[] args) {
          /*
          List list = new ArrayList();
          list.add("qqyumidi");
          list.add("corn");
          list.add(100);
          */
  
         List<String> list = new ArrayList<String>();
         list.add("qqyumidi");
         list.add("corn");
         //list.add(100);   // 1  提示编译错误
  
         for (int i = 0; i < list.size(); i++) {
             String name = list.get(i); // 2
             System.out.println("name:" + name);
         }
     }
 }

采用泛型写法后,在//1处想加入一个Integer类型的对象时会出现编译错误,通过List<String>,直接限定了list集合中只能含有String类型的元素,从而在//2处无须进行强制类型转换,因为此时,集合能够记住元素的类型信息,编译器已经能够确认它是String类型了。

静态变量存在什么位置

静态变量存在方法区,静态变量是共享内容,栈是不共享的,堆存放new关键字创建的对象,方法区是共享的区域。

方法区(Method Area)存储已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。程序运行时会加载类编译生成的字节码,这个过程中静态变量(类变量)和静态方法及普通方法对应的字节码加载到方法区。

栈(Stack):线程私有的内存区域。每个方法(Method)执行时,都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口信息等。

栈中所存储的变量和引用都是局部的(即:定义在方法体中的变量或者引用),局部变量和引用都在栈中(包括final的局部变量)。八种基本数据类型的局部变量(定义在方法体中的基本数据类型的变量)在栈中存储的是它们对应的值。栈中还存储局部的对象的引用(对象在堆中的地址)。

Java堆(Java Heap) :被所有线程共享的一块内存区域,在虚拟机启动时创建。Java堆(Java Heap)唯一目的就是存放对象实例。所有的对象实例及数组都要在Java堆(Java Heap)上分配内存空间。由关键字new产生的所有对象都存储于Java堆(Java Heap)。

类加载机制,双亲委派模型

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。这里的双亲其实就指的是父类,没有mother。父类也不是我们平日所说的那种继承关系,只是调用逻辑是这样。

使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行。

String,StringBuffer与StringBuilder的区别

String底层是一个final类型的字符数组,所以String的值是不可变的,每次对String的操作都会生成新的String对象,造成内存浪费。

StringBuffer是可变类,效率低,是线程安全的字符串操作类。任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量。

StringBuilder可变字符序列、效率高、线程不安全。

小结:
(1)如果要操作少量的数据用 String;

(2)多线程操作字符串缓冲区下操作大量数据 StringBuffer;

(3)单线程操作字符串缓冲区下操作大量数据 StringBuilder(推荐使用)。

StringBuffer和StringBuilder都继承了AbstractStringBuilder抽象类。

Object类

Object()默认构造方法。
clone() 创建并返回此对象的一个副本。
equals(Object obj) 指示某个其他对象是否与此对象“相等”。
finalize()当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
getClass()返回一个对象的运行时类。
hashCode()返回该对象的哈希码值。
notify()唤醒在此对象监视器上等待的单个线程。
notifyAll()唤醒在此对象监视器上等待的所有线程。
toString()返回该对象的字符串表示。
wait()导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
wait(long timeout)导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。
wait(long timeout, int nanos) 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。

String为什么不可变

不可变对象是指一个对象的状态在对象被创建之后就不再变化。不可改变的意思就是说:不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。

String 不可变是因为在 JDK 中 String 类被声明为一个 final 类,且类内部的 value 字节数组也是 final 的,只有当字符串是不可变时字符串池才有可能实现,字符串池的实现可以在运行时节约很多 heap 空间,因为不同的字符串变量都指向池中的同一个字符串。

如果字符串是可变的则会引起很严重的安全问题,譬如数据库的用户名密码都是以字符串的形式传入来获得数据库的连接,或者在 socket 编程中主机名和端口都是以字符串的形式传入,因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子改变字符串指向的对象的值造成安全漏洞。

因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享,这样便不用因为线程安全问题而使用同步,字符串自己便是线程安全的;因为字符串是不可变的所以在它创建的时候 hashcode 就被缓存了,不变性也保证了 hash 码的唯一性,不需要重新计算,这就使得字符串很适合作为 Map 的键,字符串的处理速度要快过其它的键对象,这就是 HashMap 中的键往往都使用字符串的原因。

wait方法的底层原理

wait即object的wait()和notify()或者notifyall()一起搭配使用
wait方法会将当前线程放入wait set等待被唤醒

1.将当前线程封装成objectwaiter对象node

2.通过objectmonitor::addwaiter方法将node添加到_WaitSet列表中

3.通过ObjectMonitor:exit方法释放当前的ObjectMonitor对象,这样其他竞争线程就可以获取该ObjectMonitor对象

4.最终底层的park方法会挂起线程

notiry方法就是随机唤醒等待池中的一个线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小海绵【vincewm】

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值