Java基础之面向对象概念(详细介绍)
面向对象的3个特征
封装、继承,多态
封装:把客观的事物封装成类,包括数据和方法。封装把对象的设计者和对象的使用者分开,这样有助于提高类和系统的安全性。
继承:继承就是指建立一个新的类,也就是派生类,继承原有的类的数据和方法,也可以重新定义或者添加数据和方法。
多态:指的是,同一个操作作用于不同类中,将会产生不同的反应。也就是不同类的对象接收到相同消息时,将会得到不一样的结果。
什么是面向对象?
面向对象程序设计是一种抽象思维,就是针对现实中客观的事物,例如人,小猫等。定义人和小猫一些属性,还有动作(也就是方法)。我们可以针对这些属性以及方法进行相应的操作,还可以规定人这个对象跟猫这个对象的一些关系。这样可以降低代码的耦合度,使程序更加的灵活。
代码中如何实现多态?
- 接口实现
- 继承父类重写方法
- 同一个类中进行方法的重载
接口的意义
规范、扩展、回调
多态的好处
多态可以允许不同类对象对同一个消息作出不同的反应,也就是多个子类对象继承父类对象,但是可以重新定义父类的方法,做到“一个接口,多种实现”。
好处总结有2点
- 提高了代码的维护性(继承保证)
- 提高了代码的扩展性(多态保证)
抽象类的意义
为其他子类提供一个公共的类型
封装子类中重复定义的内容
定义抽象方法,子类虽然有不同的实现,但是定义时一致的
接口跟抽象类的区别
比较 |
抽象类 |
接口 |
默认方法 |
抽象类可以有默认的方法实现 |
java 8之前,接口中不存在方法的实现. |
实现方式 |
子类使用extends关键字来继承抽象类.如果子类不是抽象类,子类需要提供抽象类中所声明方法的实现 |
子类使用implements来实现接口,需要提供接口中所有声明的实现 |
构造器 |
抽象类中可以有构造器, |
接口中不能 |
访问修饰符 |
抽象方法可以有public,protected和default等修饰 |
接口默认是public,不能使用其他修饰符 |
多继承 |
一个子类只能存在一个父类 |
一个子类可以存在多个接口 |
访问新方法 |
想抽象类中添加新方法,可以提供默认的实现,因此可以不修改子类现有的代码 |
如果往接口中添加新方法,则子类中需要实现该方法. |
父类的静态方法能否被子类重写
不能。重写只适用于实例方法,不能用于静态方法,而子类当中含有和父类相同名的静态方法,我们一般称之为隐藏。
什么是不可变对象
不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer及其它包装类。不可变对象不会改变内部状态,是线程安全地,我们可以在多线程中安全地共享使用。
静态变量和实例变量的区别?
静态变量存储在方法区,属于类所有。实例变量存储在堆当中,其引用存在当前线程栈。
在语法定义上有区别:静态变量前要加上static关键字,而实例变量前则不加。
在程序运算上有区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。
静态的变量不属于某个实例变量,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何的实例的对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接的使用类名来引用。
能否创建一个包含可变对象的不可变对象?
当然可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点,不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝。最常见的例子就是对象中包含一个日期对象的引用。
面向对象和面向过程的区别
面向过程
优点:性能比面向对象高,因为类调用的时候需要实例化,开销比较大,耗费资源。像单片机、嵌入式、Linux等考虑性能的一般采用面向过程开发。
缺点:没有面向对象易维护、易复用、易扩展
面向对象
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态的特征,可以设计出低耦合的系统,让系统更加的灵活、更加的易于维护。
缺点:性能比面向对象低
值传递和引用传递
对于基本类型来说,传递都是值传递
基本类型有:boolean(1)、byte(8)、short(16)、char(16)、int(32)、float(32)、double(64)、long(64) //注,后面数字代表位数,1字节 = 8位
值传递:
结果
由上面可以看出来,int类型的传递,当a2的值发生改变的时候,a1的值并不会受到影响,也就是说,基本类型的传递是根据值来传递的,传递之后,各个变量之间的值的改变不相互影响。
引用传递:
创建一个对象User,里面有name属性,进行测试
结果:
结论:从上面的结果可以看出来,将u1对象赋给u2,实际上u1和u2都是指向同一个地址,所以当u1,u2的值任何一个改变的时候,另外一个跟着改变,因为是同一个地址。跟u3重新new一个对象不同,u3是开辟了另外的一个地址,所以当u1,u2改变的时候,u3是不会跟着改变的。一般认为,Java 内的传递都是值传递,Java 中实例对象的传递是引用传递。
switch中能否使用string做参数
在jdk 1.7之前,switch只能支持byte, short, char, int或者其对应的封装类以及Enum类型。从jdk 1.7之后switch开始支持String。
switch能否作用在byte, long上?
可以用在byte上,但是不能用在long上。
在switch(expr1)中,expr1只能是一个整数表达式或者枚举常量(更大字体),整数表达式可以是int基本类型或Integer包装类型,由于,byte,short,char都可以隐含转换为int,所以,这些类型以及这些类型的包装类型也是可以的。显然,long和String类型都不符合sitch的语法规定,并且不能被隐式转换成int类型,所以,它们不能作用于swtich语句中。
Java内存分配之堆、栈、常量池
Java内存分配主要包括以下几个区域:
1. 寄存器:我们在程序中无法控制
2. 栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中
3. 堆:存放用new产生的数据
4. 静态域:存放在对象中用static定义的静态成员
5. 常量池:存放常量
6. 非RAM(随机存取存储器)存储:硬盘等永久存储空间
Java内存分配中的栈
在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
Java内存分配中的堆
堆内存用来存放由new创建的对象和数组。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
在堆中产生了一个数组或对象后,还可以 在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。引用变量就相当于是为数组或者对象起的一个名称。
引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍 然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。这也是 Java 比较占内存的原因。
实际上,栈中的变量指向堆内存中的变量,这就是Java中的指针!
常量池 (constant pool)
常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用,比如:
类和接口的全限定名;
字段的名称和描述符;
虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(string,integer和 float ing point常量)和对其他类型,字段和方法的符号引用。
对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引 用。说到这里,对常量池中的字符串值的存储位置应该有一个比较明了的理解了。
在程序执行的时候,常量池会储存在Method Area,而不是堆中。
堆与栈
Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、 anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态 分配内存,存取速度较慢。
栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量数据(int, short, long, byte, float, double, boolean, char)和对象句柄(引用)。
这里我们主要关心栈,堆和常量池,对于栈和常量池中的对象可以共享,对于堆中的对象不可以共享。栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性。
字符串内存分配
对于字符串,其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。
如以下代码:
这里解释一下橙色这3个箭头,对于通过new产生一个字符串(假设为“china”)时,会先去常量池中查找是否已经有了“china”对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此”china”对象的拷贝对象。
这也就是有道面试题:String s = new String(“xyz”);产生几个对象?一个或两个,如果常量池中原来没有”xyz”,就是两个。
存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的 intern()方法就是扩充常量池的 一个方法;当一个String实例str调用intern()方法时,Java 查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用
如下代码:
输出结果:
false
false
true
True
String常量池问题的几个例子:
分析:
【1】中,JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即"a" + bb无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b。所以上面程序的结果也就为false。
【2】和【1】中唯一不同的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。故上面程序的结果为true。
【3】JVM对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b,故上面程序的结果为false。
结论:
字符串是一个特殊包装类,其引用是存放在栈里的,而对象内容必须根据创建方式不同定(常量池和堆).有的是编译期就已经创建好,存放在字符串常 量池中,而有的是运行时才被创建.使用new关键字,存放在堆中。
基础类型的变量和常量在内存中的分配
对于基础类型的变量和常量,变量和引用存储在栈中,常量存储在常量池中。
如以下代码:
编译器先处理int i1 = 9;首先它会在栈中创建一个变量为i1的引用,然后查找栈中是否有9这个值,如果没找到,就将9存放进来,然后将i1指向9。接着处理int i2 = 9;在创建完i2的引用变量后,因为在栈中已经有9这个值,便将i2直接指向9。这样,就出现了i1与i2同时均指向9的情况。最后i3也指向这个9。
java当中的四种引用
强引用,软引用,弱引用,虚引用。不同的引用类型主要体现在GC上:
强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。
软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。
弱引用:具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象。
虚引用:顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。
WeakReference与SoftReference的区别?
虽然 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而软引用虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。
为什么要有不同的引用类型
在Java中有时候我们需要适当的控制对象被回收的时机,因此就诞生了不同的引用类型,可以说不同的引用类型实则是对GC回收时机不可控的妥协。有以下几个使用场景可以充分的说明:
利用软引用和弱引用解决OOM问题:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题.
equals()和hashcode()的联系
hashCode()是Object类的一个方法,返回一个哈希值。如果两个对象根据equal()方法比较相等,那么调用这两个对象中任意一个对象的hashCode()方法必须产生相同的哈希值。
如果两个对象根据eqaul()方法比较不相等,那么产生的哈希值不一定相等(碰撞的情况下还是会相等的。)
a.hashCode()有什么用?
将对象放入到集合中时,首先判断要放入对象的hashcode是否已经在集合中存在,不存在则直接放入集合。如果hashcode相等,然后通过equal()方法判断要放入对象与集合中的任意对象是否相等:如果equal()判断不相等,直接将该元素放入集合中,否则不放入。
有没有可能两个不相等的对象有相同的hashcode
有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。如果两个对象相等,必须有相同的hashcode 值,反之不成立。
可以在hashcode中使用随机数字吗?
不行,因为同一对象的 hashcode 值必须是相同的
a==b与a.equals(b)有什么区别
如果a 和b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true,而 a.equals(b) 是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。例如,String 类重写 equals() 方法,所以可以用于两个不同对象,但是包含的字母相同的比较。
基本类型比较用==,比较的是他们的值。默认下,对象用==比较时,比较的是内存地址,如果需要比较对象内容,需要重写equal方法。
什么是自动拆装箱?
自动装箱和拆箱,就是基本类型和引用类型之间的转换。
把基本数据类型转换成包装类的过程就是打包装,为装箱。
把包装类转换成基本数据类型的过程就是拆包装,为拆箱。
为什么要转换?
如果你在 Java5 下进行过编程的话,你一定不会陌生这一点,你不能直接地向集合( Collection )中放入原始类型值,因为集合只接收对象。
通常这种情况下你的做法是,将这些原始类型的值转换成对象,然后将这些转换的对象放入集合中。使用 Integer、Double、Boolean 等这些类,我们可以将原始类型值转换成对应的对象,但是从某些程度可能使得代码不是那么简洁精炼。
为了让代码简练,Java5 引入了具有在原始类型和对象类型自动转换的装箱和拆箱机制。
但是自动装箱和拆箱并非完美,在使用时需要有一些注意事项,如果没有搞明白自动装箱和拆箱,可能会引起难以察觉的 Bug 。
3*0.1==0.3返回值是什么
false,因为有些浮点数不能完全精确的表示出来。
a=a+b与a+=b有什么区别吗?
+=操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换。如:
byte a = 127;
byte b = 127;
b = a + b; // error : cannot convert from int to byte
b += a; // ok
(译者注:这个地方应该表述的有误,其实无论 a+b 的值为多少,编译器都会报错,因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错)
short s1= 1; s1 = s1 + 1; 该段代码是否有错,有的话怎么改?
有错误,short类型在进行运算时会自动提升为int类型,也就是说s1+1的运算结果是int类型。
数组有没有length()这个方法? String有没有length()这个方法
答: 数组没有length()这个方法,有length的属性。String有有length()这个方法
Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型
答: 方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型
int 和 Integer 有什么区别
答: Java 提供两种不同的类型:引用类型和原始类型(或内置类型)。Int是java的原始数据类型,Integer是java为int提供的封装类。Java为每个原始类型提供了封装类。引用类型和原始类型具有不同的特征和用法,它们包括:大小和速度问题,这种类型以哪种类型的数据结构存储,当引用类型和原始类型用作某个类的实例数据时所指定的缺省值。对象引用实例变量的缺省值为 null,而原始类型实例变量的缺省值与它们的类型有关
& 和 &&的区别
&运算符有两种用法:(1)按位与;(2)逻辑与。&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true。&&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为:username != null &&!username.equals(“”),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
一个java文件内部可以有类?(非内部类)
只能有一个public公共类,但是可以有多个default修饰的类。
如何正确的退出多层嵌套循环?
使用标号和break;
通过在外层循环中添加标识符
内部类的作用
内部类可以有多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立.在单个外围类当中,可以让多个内部类以不同的方式实现同一接口,或者继承同一个类.创建内部类对象的时刻不依赖于外部类对象的创建。内部类并没有令人疑惑的”is-a”管系,它就像是一个独立的实体。
内部类提供了更好的封装,除了该外围类,其他类都不能访问。
clone()是哪个类的方法?
java.lang.Cloneable 是一个标示性接口,不包含任何方法,clone 方法在 object 类中定义。并且需要知道 clone() 方法是一个本地方法,这意味着它是由 c 或 c++ 或 其他本地语言实现的。
深拷贝和浅拷贝的区别是什么?
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
static都有哪些用法?
被static所修饰的变量/方法都属于类的静态资源,类实例所共享。
static也用于静态块,多用于初始化操作:
此外static也多用于修饰内部类,此时称之为静态内部类。
最后一种用法就是静态导包,可以用来指定导入某个类中的静态资源,并且不需要使用类名。资源名,可以直接使用资源名,比如:
final有哪些用法
final也是很多面试喜欢问的地方,能回答下以下三点就不错了:
1.被final修饰的类不可以被继承
2.被final修饰的方法不可以被重写
3.被final修饰的变量不可以被改变。如果修饰引用,那么表示引用不可变,引用指向的内容可变。
4.被final修饰的方法,JVM会尝试将其内联,以提高运行效率
5.被final修饰的常量,在编译阶段会存入常量池中。
回答出编译器对final域要遵守的两个重排序规则更好:
1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
2.初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
String, StringBuffer和StringBuilder区别
String是字符串常量,final修饰:StringBuffer字符串变量(线程安全);
StringBuilder 字符串变量(线程不安全)。
String和StringBuffer
String和StringBuffer主要区别是性能:String是不可变对象,每次对String类型进行操作都等同于产生了一个新的String对象,然后指向新的String对象。所以尽量不在对String进行大量的拼接操作,否则会产生很多临时对象,导致GC开始工作,影响系统性能。
StringBuffer是对对象本身操作,而不是产生新的对象,因此在有大量拼接的情况下,我们建议使用StringBuffer。
但是需要注意现在JVM会对String拼接做一定的优化:
String s=“This is only ”+”simple”+”test”会被虚拟机直接优化成String s=“This is only simple test”,此时就不存在拼接过程。
StringBuffer和StringBuilder
StringBuffer是线程安全的可变字符串,其内部实现是可变数组。StringBuilder是jdk 1.5新增的,其功能和StringBuffer类似,但是非线程安全。因此,在没有多线程问题的前提下,使用StringBuilder会取得更好的性能。
什么是编译器常量?使用它有什么风险?
公共静态不可变(public static final )变量也就是我们所说的编译期常量,这里的 public 可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。为了避免这种情况,当你在更新依赖 JAR 文件时,确保重新编译你的程序。
java当中使用什么类型表示价格比较好?
如果不是特别关心内存和性能的话,使用BigDecimal,否则使用预定义精度的 double 类型。
如何将byte转为String
可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不同。
可以将int强转为byte类型么?会产生什么问题?
我们可以做强制转换,但是Java中int是32位的而byte是8 位的,所以,如果强制转化int类型的高24位将会被丢弃,byte 类型的范围是从-128到128
Java 中 ++ 操作符是线程安全的吗?
不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。
final、finalize 和 finally 的不同之处?
final 是一个修饰符,可以修饰变量、方法和类。如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,但是什么时候调用 finalize 没有保证。finally 是一个关键字,与 try 和 catch 一起用于异常的处理。finally 块一定会被执行,无论在 try 块中是否有发生异常。
是否可以在static环境中访问非static变量?
static变量在Java中是属于类的,它在所有的实例中的值是一样的。当类被Java虚拟机载入的时候,会对static变量进行初始化。如果你的代码尝试不用实例来访问非static的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。
Java中,什么是构造方法?什么是构造方法重载?什么是复制构造方法?
当新对象被创建的时候,构造方法会被调用。每一个类都有构造方法。在程序员没有给类提供构造方法的情况下,Java编译器会为这个类创建一个默认的构造方法。
Java中构造方法重载和方法重载很相似。可以为一个类创建多个构造方法。每一个构造方法必须有它自己唯一的参数列表。
Java不支持像C++中那样的复制构造方法,这个不同点是因为如果你不自己写构造方法的情况下,Java不会创建默认的复制构造方法。
Java支持多继承么?
Java中类不支持多继承,只支持单继承(即一个类只有一个父类)。 但是java中的接口支持多继承,,即一个子接口可以有多个父接口。(接口的作用是用来扩展对象的功能,一个子接口继承多个父接口,说明子接口扩展了多个功能,当类实现接口时,类就扩展了相应的功能)。
Java反射机制的作用:
1)在运行时判断任意一个对象所属的类。
2)在运行时判断任意一个类所具有的成员变量和方法。
3)在运行时任意调用一个对象的方法
4)在运行时构造任意一个类的对象
什么是反射机制?
简单说,反射机制值得是程序在运行时能够获取自身的信息。在java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。
哪里用到反射机制?
jdbc中有一行代码:Class.forName('com.MySQL.jdbc.Driver.class').newInstance();那个时候只知道生成驱动对象实例,后来才知道,这就是反射,现在
很多框架都用到反射机制,hibernate,struts都是用反射机制实现的。
反射机制的优缺点?
静态编译:在编译时确定类型,绑定对象,即通过
动态编译:运行时确定类型,绑定对象。动态编译最大限度的发挥了java的灵活性,体现了多态的应用,有利于降低类之间的耦合性。
一句话,反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中
它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编
译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如
这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能
的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功
能。
它的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它
满足我们的要求。这类操作总是慢于只直接执行相同的操作。
什么是动态代理
代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
error和exception有什么区别
error表示系统级的错误,是java运行环境内部错误或者硬件问题,不能指望程序来处理这样的问题,除了退出运行外别无选择,它是Java虚拟机抛出的。
exception 表示程序需要捕捉、需要处理的异常,是由与程序设计的不完善而出现的问题,程序必须处理的问题
运行时异常和一般异常有何不同
Java提供了两类主要的异常:runtimeException和checkedException
一般异常(checkedException)主要是指IO异常、SQL异常等。对于这种异常,JVM要求我们必须对其进行cathc处理,所以,面对这种异常,不管我们是否愿意,都是要写一大堆的catch块去处理可能出现的异常。
运行时异常(runtimeException)我们一般不处理,当出现这类异常的时候程序会由虚拟机接管。比如,我们从来没有去处理过NullPointerException,而且这个异常还是最常见的异常之一。
出现运行时异常的时候,程序会将异常一直向上抛,一直抛到遇到处理代码,如果没有catch块进行处理,到了最上层,如果是多线程就有Thread.run()抛出,如果不是多线程那么就由main.run()抛出。抛出之后,如果是线程,那么该线程也就终止了,如果是主程序,那么该程序也就终止了。
其实运行时异常的也是继承自Exception,也可以用catch块对其处理,只是我们一般不处理罢了,也就是说,如果不对运行时异常进行catch处理,那么结果不是线程退出就是主程序终止。
如果不想终止,那么我们就必须捕获所有可能出现的运行时异常。如果程序中出现了异常数据,但是它不影响下面的程序执行,那么我们就该在catch块里面将异常数据舍弃,然后记录日志。如果,它影响到了下面的程序运行,那么还是程序退出比较好些。
Java中异常处理机制的原理
Java通过面向对象的方式对异常进行处理,Java把异常按照不同的类型进行分类,并提供了良好的接口。当一个方法出现异常后就会抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并对异常进行处理。Java的异常处理是通过5个关键词来实现的:try catch throw throws finally。
一般情况下是用try来执行一段程序,如果出现异常,系统会抛出(throws),我们可以通过它的类型来捕捉它,或最后由缺省处理器来处理它(finally)。
try:用来指定一块预防所有异常的程序
catch:紧跟在try后面,用来捕获异常
throw:用来明确的抛出一个异常
throws:用来标明一个成员函数可能抛出的各种异常
finally:确保一段代码无论发生什么异常都会被执行的一段代码。
你平时在项目中是怎样对异常进行处理的。
(1)尽量避免出现runtimeException 。例如对于可能出现空指针的代码,带使用对象之前一定要判断一下该对象是否为空,必要的时候对runtimeException
也进行try catch处理。
(2)进行try catch处理的时候要在catch代码块中对异常信息进行记录,通过调用异常类的相关方法获取到异常的相关信息,返回到web端,不仅要给用户良好
的用户体验,也要能帮助程序员良好的定位异常出现的位置及原因。例如,以前做的一个项目,程序遇到异常页面会显示一个图片告诉用户哪些操作导致程序出现
了什么异常,同时图片上有一个按钮用来点击展示异常的详细信息给程序员看的。
final、finally、finalize的区别
(1)、final用于声明变量、方法和类的,分别表示变量值不可变,方法不可覆盖,类不可以继承
(2)、finally是异常处理中的一个关键字,表示finally{}里面的代码一定要执行
(3)、finalize是Object类的一个方法,在垃圾回收的时候会调用被回收对象的此方法。
try()里面有一个return语句,那么后面的finally{}里面的code会不会被执行,什么时候执行,是在return前还是return后?
你曾经自定义实现过异常吗?怎么写的?
1、自定义异常的步骤
(1)继承 Exception 或 RuntimeException
(2)定义构造方法
(3)使用异常
2、自定义异常类
/**IllegalAgeException:非法年龄异常,继承Exception类*/
class IllegalAgeException extends Exception {
//默认构造器
public IllegalAgeException() {
}
//带有详细信息的构造器,信息存储在message中
public IllegalAgeException(String message) {
super(message);
}
}
3、自定义异常类的使用
class Person {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) throws IllegalAgeException {
if (age < 0) {
throw new IllegalAgeException("人的年龄不应该为负数");
}
this.age = age;
}
public String toString() {
return "name is " + name + " and age is " + age;
}
}
public class TestMyException {
public static void main(String[] args) {
Person p = new Person();
try {
p.setName("Lincoln");
p.setAge(-1);
} catch (IllegalAgeException e) {
e.printStackTrace();
System.exit(-1);
}
System.out.println(p);
}
}
运行结果:
throw和throws有什么区别?
抛出异常有三种形式,一是throw,一个throws,还有一种系统自动抛异常。
下面它们之间的异同
一、系统自动抛异常
当程序语句出现一些逻辑错误、主义错误或类型转换错误时,系统会自动抛出异常
二、throw
1、throw是语句抛出一个异常,一般是在代码块的内部,当程序 现某种逻辑错误时由程序员主动抛出某种特定类型的异常
2、定义在方法体内
3、创建的是一个异常对象
4、确定了发生哪种异常才可以使用
三、throws
1、在方法参数列表后,throws后可以跟着多个异常名,表示抛出的异常用逗号隔开
2、表示向调用该类的位置抛出异常,不在该类解决
3、可能发生哪种异常
throws用在方法声明后面,跟的是异常类名,throw用在方法体内,跟的是异常对象名。
throws可以跟多个异常类名,用逗号隔开,throw只能抛出一个异常对象名。
throws表示抛出异常,由该方法的调用者来处理,throw表示抛出异常,由方法体内的语句处理。
throws表示出现异常的一种可能性,并不一定会发生这些异常,throw则是抛出了异常,执行throw则一定抛出了某种异常。
四、异常
异常概述:
异常:异常是指在程序的运行过程中所发生的不正常的事件,它会中断正在运行的程序。简单来说就是程序出现了不正常的情况。异常本质就是Java当中对可能出现的问题进行描述的一种对象体现。
异常处理的时候,finally代码块的重要性是什么?
无论是否抛出异常,finally代码块总是会被执行。就算是没有catch语句同时又抛出异常的情况下,finally代码块仍然会被执行。最后要说的是,finally代码块主要用来释放资源,比如:I/O缓冲区,数据库连接。
异常的使用的注意地方?
不要将异常处理用于正常的控制流(设计良好的 API 不应该强迫它的调用者为了正常的控制流而使用异常)。
对可以恢复的情况使用受检异常,对编程错误使用运行时异常。
避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)。
优先使用标准的异常。
每个方法抛出的异常都要有文档。
保持异常的原子性
不要在 catch 中忽略掉捕获到的异常。
请列出 5 个运行时异常?
NullPointerException
IndexOutOfBoundsException
ClassCastException
ArrayStoreException 当你试图将错误类型的对象存储到一个对象数组时抛出的异常
BufferOverflowException 写入的长度超出了允许的长度
反射的概述
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射就java中class.forName()和classLoader都可用来对类进行加载。
class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
而classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。是把java类中的各种成分映射成一个个的Java对象
反射的功能:
在运行时构造一个类的对象。
判断一个类所具有的成员变量和方法。
调用一个对象的方法。
生成动态代理。
反射的应用很多,很多框架都有用到:
反射的用途:
Spring 框架的 IoC 基于反射创建对象和设置依赖属性。
Spring MVC 的请求调用对应方法,也是通过反射。
JDBC 的 Class#forName(String className) 方法,也是使用反射。
反射中,Class.forName 和 ClassLoader 区别
java中class.forName()和classLoader都可用来对类进行加载。
class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
而classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
Java动态代理的两种实现方法
jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。
总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。。
jdk动态代理是jdk原生就支持的一种代理方式,它的实现原理,就是通过让target类和代理类实现同一接口,代理类持有target对象,来达到方法拦截的作用,这样通过接口的方式有两个弊端,一个是必须保证target类有接口,第二个是如果想要对target类的方法进行代理拦截,那么就要保证这些方法都要在接口中声明,实现上略微有点限制。
Cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理:
为什么要用动态代理
他可以在不修改别代理对象代码的基础上,通过扩展代理类,进行一些功能的附加与增强
静态代理与动态代理的区别
动态代理使我们免于去重写接口中的方法,而着重于去扩展相应的功能或是方法的增强,与静态代理相比简单了不少,减少了项目中的业务量
动态代理机制
Proxy这个类的作用就是用来动态创建一个代理对象的类。每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
什么是 Java 序列化?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。
可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
序列化是为了解决在对对象流进行读写操作时所引发的问题。
反序列化的过程,则是和序列化相反的过程。
另外,我们不能将序列化局限在 Java 对象转换成二进制数组,例如说,我们将一个 Java 对象,转换成 JSON 字符串,或者 XML 字符串,这也可以理解为是序列化。
如何实现 Java 序列化?
如下的方式,就是 Java 内置的序列化方案,实际场景下,我们可以自定义序列化的方案,例如说 Google Protobuf 。
将需要被序列化的类,实现 Serializable 接口,该接口没有需要实现的方法,implements Serializable 只是为了标注该对象是可被序列化的。
序列化
然后,使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象
接着,使用 ObjectOutputStream 对象的 #writeObject(Object obj) 方法,就可以将参数为 obj 的对象写出(即保存其状态)。
反序列化
要恢复的话则用输入流。
Java 序列话中,如果有些字段不想进行序列化怎么办?
对于不想进行序列化的变量,使用 transient 关键字修饰。
当对象被序列化时,阻止实例中那些用此关键字修饰的的变量序列化。
当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。
transient 只能修饰变量,不能修饰类和方法。
Comparable 和 Comparator 的区别?
Comparable 接口,在 java.lang 包下,用于当前对象和其它对象的比较,所以它有一个 #compareTo(Object obj) 方法用来排序,该方法只有一个参数。
Comparator 接口,在 java.util 包下,用于传入的两个对象的比较,所以它有一个 #compare(Object obj1, Object obj2) 方法用来排序,该方法有两个参数
本文地址:https://blog.csdn.net/weixin_39240837/article/details/107892242
上一篇: Java基础语法之包装类和装箱、拆箱
下一篇: JS中正则表达式的使用