1.Java语言的三大特性
1.封装:
首先,属性可用来描述同一类事物的特征,方法可描述一类事物可做的操作。封装就是把属于同一类事物的共性(包括属性与方法)归到一个类中,以方便使用。
概念:封装也称为信息隐藏,是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其他部分只有通过包裹在数据外面的被授权的操作来与这个抽象数据类型交流与交互。也就是说,用户无需知道对象内部方法的实现细节,但可以根据对象提供的外部接口(对象名和参数)访问该对象。
好处:
(1)实现了专业的分工。将能实现某一特定功能的代码封装成一个独立的实体后,各程序员可以在需要的时候调用,从而实现了专业的分工。
(2)隐藏信息,实现细节。通过控制访问权限可以将可以将不想让客户端程序员看到的信息隐藏起来,如某客户的银行的密码需要保密,只能对该客户开发权限。
2.继承:
就是个性对共性的属性与方法的接受,并加入个性特有的属性与方法
1.概念:一个类继承另一个类,则称继承的类为子类,被继承的类为父类。
2.目的:实现代码的复用。
3.理解:子类与父类的关系并不是日常生活中的父子关系,子类与父类而是一种特殊化与一般化的关系,是is-a的关系,子类是父类更加详细的分类。如class dog extends animal,就可以理解为dog is a animal.注意设计继承的时候,若要让某个类能继承,父类需适当开放访问权限,遵循里氏代换原则,即向修改关闭对扩展开放,也就是开-闭原则。
4.结果:继承后子类自动拥有了父类的属性和方法,但特别注意的是,父类的私有属性和构造方法并不能被继承。
另外子类可以写自己特有的属性和方法,目的是实现功能的扩展,子类也可以复写父类的方法即方法的重写。
3.多态:
多态的概念发展出来,是以封装和继承为基础的。
多态就是在抽象的层面上实施一个统一的行为,到个体(具体)的层面上时,这个统一的行为会因为个体(具体)的形态特征而实施自己的特征行为。(针对一个抽象的事,对于内部个体又能找到其自身的行为去执行。)
1.概念:相同的事物,调用其相同的方法,参数也相同时,但表现的行为却不同。
2.理解:子类以父类的身份出现,但做事情时还是以自己的方法实现。子类以父类的身份出现需要向上转型(upcast),其中向上转型是由JVM自动实现的,是安全的,但向下转型(downcast)是不安全的,需要强制转换。子类以父类的身份出现时自己特有的属性和方法将不能使用。
2.Java语言主要特性
- Java语言是易学的。 Java语言的语法与C语言和C++语言很接近,使得大多数程序员很容易学习和使用Java。
- Java语言是强制面向对象的。 Java语言提供类、接口和继承等原语,为了简单起见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制(关键字为implements)。
- Java语言是分布式的。Java语言支持Internet应用的开发,在基本的Java应用编程接口中有一个网络应用编程接口(java net),它提供了用于网络应用编程的类库,包括URL、URLConnection、Socket、ServerSocket等。Java的RMI(远程方法激活)机制也是开发分布式应用的重要手段。
- Java语言是健壮的。 Java的强类型机制、异常处理、垃圾的自动收集等是Java程序健壮性的重要保证。对指针的丢弃是Java的明智选择。
- Java语言是安全的。 Java通常被用在网络环境中,为此,Java提供了一个安全机制以防恶意代码的攻击。如:安全防范机制(类ClassLoader),如分配不同的名字空间以防替代本地的同名类、字节代码检查。
- Java语言是体系结构中立的。 Java程序(后缀为java的文件)在Java平台上被编译为体系结构中立的字节码格式(后缀为class的文件),然后可以在实现这个Java平台的任何系统中运行。
- Java语言是解释型的。 如前所述,Java程序在Java平台上被编译为字节码格式,然后可以在实现这个Java平台的任何系统的解释器中运行。(一次编译,到处运行)
- Java是性能略高的。与那些解释型的高级脚本语言相比,Java的性能还是较优的。
- Java语言是原生支持多线程的。 在Java语言中,线程是一种特殊的对象,它必须由Thread类或其子(孙)类来创建。
3. JDK 和 JRE 有什么区别
- JDK:Java Development Kit 的简称,Java 开发工具包,提供了 Java 的开发环境和运行环境。
- JRE:Java Runtime Environment 的简称,Java 运行环境,为 Java 的运行提供了所需环境。
具体来说 JDK 其实包含了 JRE,同时还包含了编译 Java 源码的编译器 Javac,还包含了很多 Java 程序调试和分析的工具。简单来说:如果你需要运行 Java 程序,只需安装 JRE 就可以了,如果你需要编写 Java 程序,需要安装 JDK。
4.Java基本数据类型及其封装类
Tips:boolean类型占了单独使用是4个字节,在数组中又是1个字节
基本类型所占的存储空间是不变的。这种不变性也是Java具有可移植性的原因之一。
基本类型放在栈中,直接存储值。
所有数值类型都有正负号,没有无符号的数值类型。
为什么需要封装类?
因为泛型类包括预定义的集合,使用的参数都是对象类型,无法直接使用基本数据类型,所以Java又提供了这些基本类型的封装类。
基本类型和对应的封装类由于本质的不同。具有一些区别:
1.基本类型只能按值传递,而封装类按引用传递。
2.基本类型会在栈中创建,而对于对象类型,对象在堆中创建,对象的引用在栈中创建,基本类型由于在栈中,效率会比较高,但是可能存在内存泄漏的问题。
5.如果main方法被声明为private会怎样?
能正常编译,但运行的时候会提示”main方法不是public的”。在idea中如果不用public修饰,则会自动去掉可运行的按钮。
6.说明一下public static void main(String args[])这段声明里每个关键字的作用
public: main方法是Java程序运行时调用的第一个方法,因此它必须对Java环境可见。所以可见性设置为pulic.
static: Java平台调用这个方法时不会创建这个类的一个实例,因此这个方法必须声明为static。
void: main方法没有返回值。
String是命令行传进参数的类型,args是指命令行传进的字符串数组。
7.==与equals的区别
==比较两个对象在内存里是不是同一个对象,就是说在内存里的存储位置一致。两个String对象存储的值是一样的,但有可能在内存里存储在不同的地方 。
比较的是引用而equals方法比较的是内容。public boolean equals(Object obj) 这个方法是由Object对象提供的,可以由子类进行重写。默认的实现只有当对象和自身进行比较时才会返回true,这个时候和是等价的。String, BitSet, Date, 和File都对equals方法进行了重写,对两个String对象 而言,值相等意味着它们包含同样的字符序列。对于基本类型的包装类来说,值相等意味着对应的基本类型的值一样。
public class EqualsTest { public static void main(String[] args) { String s1 = “abc”; String s2 = s1; String s5 = “abc”; String s3 = new String(”abc”); String s4 = new String(”abc”); System.out.println(”== comparison : ” + (s1 == s5)); System.out.println(”== comparison : ” + (s1 == s2)); System.out.println(”Using equals method : ” + s1.equals(s2)); System.out.println(”== comparison : ” + s3 == s4); System.out.println(”Using equals method : ” + s3.equals(s4)); } }
结果:
== comparison : true == comparison : true Using equals method : true false Using equals method :true
8.Object有哪些公用方法
Object是所有类的父类,任何类都默认继承Object
clone 保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
equals 在Object中与==是一样的,子类一般需要重写该方法。
hashCode 该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
getClass final方法,获得运行时类型
wait 使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。 wait() 方法一直等待,直到获得锁或者被中断。 wait(long timeout) 设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生
1、其他线程调用了该对象的notify方法。 2、其他线程调用了该对象的notifyAll方法。 3、其他线程调用了interrupt中断该线程。 4、时间间隔到了。 5、此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
notify 唤醒在该对象上等待的某个线程。
notifyAll 唤醒在该对象上等待的所有线程。
toString 转换成字符串,一般子类都有重写,否则打印句柄。
9.为什么Java里没有全局变量?
全局变量是全局可见的,Java不支持全局可见的变量,因为:全局变量破坏了引用透明性原则。全局变量导致了命名空间的冲突。
10.while循环和do循环有什么不同?
while结构在循环的开始判断下一个迭代是否应该继续。do/while结构在循环的结尾来判断是否将继续下一轮迭代。do结构至少会执行一次循环体。
11.char型变量中能不能存储一个中文汉字?为什么?
可以。Java默认Unicode编码。Unicode码占16位。 char两个字节刚好16位。
12.public,private,protected的区别,继承方法与访问权限
Tips:不写默认default
13.float f=3.4;是否正确?
不正确。3.4是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成float f =3.4F。
14.short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗?
对于short s1 = 1; s1 = s1 + 1;由于1是int类型,因此s1+1运算结果也是int 型,需要强制转换类型才能赋值给short型。而short s1 = 1; s1 += 1;+=操作符会进行隐式自动类型转换,是 Java 语言规定的运算符;Java编译器会对它进行特殊处理,因此可以正确编译。因为s1+= 1;相当于s1 = (short)(s1 + 1)。
15.&和&&的区别?
- &:(1)按位与;(2)逻辑与。
按位与: 0 & 1 = 0 ; 0 & 0 = 0; 1 & 1 = 1
逻辑与: a == b & b c (即使ab已经是 false了,程序还会继续判断b是否等于c)
2.&&: 短路与
a== b && b== c (当a==b 为false则不会继续判断b是否等与c)
比如判断某对象中的属性是否等于某值,则必须用&&,否则会出现空指针问题。
16.IntegerCache
public class IntegerTest { public static void main(String[] args) { Integer a = 100, b = 100 ,c = 129,d = 129; System.out.println(a==b); System.out.println(c==d); } }
结果:
true false
小朋友,你是否有很多问号?
来解释一下:
/** * Cache to support the object identity semantics of autoboxing for values between * -128 and 127 (inclusive) as required by JLS. * * The cache is initialized on first usage. The size of the cache * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option. * During VM initialization, java.lang.Integer.IntegerCache.high property * may be set and saved in the private system properties in the * sun.misc.VM class. */ private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }
public static Integer valueOf(int i) { assert IntegerCache.high >= 127; if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
通过源码,我们可以看出, -128~127之间做了缓存。考虑到高频数值的复用场景,这样做还是很合理的,合理优化。最大边界可以通过-XX:AutoBoxCacheMax进行配置。
17.Locale类是什么?
Locale类用来根据语言环境来动态调整程序的输出。
18.Java中final、finally、finalize的区别与用法
- final
final是一个修饰符也是一个关键字。
- 被final修饰的类无法被继承
- 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;
如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。但是它指向的对象的内容是可变的。 - 被final修饰的方法将无法被重写,但允许重载
注意:类的private方法会隐式地被指定为final方法。
- finally
finally是一个关键字。
- finally在异常处理时提供finally块来执行任何清除操作。不管有没有异常被抛出或者捕获,finally块都会执行,通常用于释放资源。
- finally块正常情况下一定会被执行。但是有至少两个极端情况:
如果对应的try块没有执行,则这个try块的finally块并不会被执行
如果在try块中jvm关机,例如system.exit(n),则finally块也不会执行(都拔电源了,怎么执行) - finally块中如果有return语句,则会覆盖try或者catch中的return语句,导致二者无法return,所以强烈建议finally块中不要存在return关键字
- finalize
finalize()是Object类的protected方法,子类可以覆盖该方法以实现资源清理工作。
GC在回收对象之前都会调用该方法
finalize()方法是存在很多问题的:
- java语言规范并不保证finalize方法会被及时地执行,更根本不会保证它们一定会被执行
- finalize()方法可能带来性能问题,因为JVM通常在单独的低优先级线程中完成finalize的执行
- finalize()方法中,可将待回收对象赋值给GC Roots可达的对象引用,从而达到对象再生的目的
- finalize方法最多由GC执行一次(但可以手动调用对象的finalize方法)
19.hashCode()和equals()的区别
下边从两个角度介绍了他们的区别:一个是性能,一个是可靠性。他们之间的主要区别也基本体现在这里。
1.equals()既然已经能实现对比的功能了,为什么还要hashCode()呢?
因为重写的equals()里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高。
2.hashCode()既然效率这么高为什么还要equals()呢?
因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠,所以我们可以得出(PS:以下两条结论是重点,很多人面试的时候都说不出来):
equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的。
hashCode()相等的两个对象他们的equals()不一定相等,也就是hashCode()不是绝对可靠的。
扩展
1.阿里巴巴开发规范明确规定:
只要重写 equals,就必须重写 hashCode;
因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法;
如果自定义对象做为 Map 的键,那么必须重写 hashCode 和 equals;
String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象作为 key 来使用;
2、什么时候需要重写?
一般的地方不需要重载hashCode,只有当类需要放在HashTable、HashMap、HashSet等等hash结构的集合时才会重载hashCode。
3、那么为什么要重载hashCode呢?
如果你重写了equals,比如说是基于对象的内容实现的,而保留hashCode的实现不变,那么很可能某两个对象明明是“相等”,而hashCode却不一样。
这样,当你用其中的一个作为键保存到hashMap、hasoTable或hashSet中,再以“相等的”找另一个作为键值去查找他们的时候,则根本找不到。
4、为什么equals()相等,hashCode就一定要相等,而hashCode相等,却不要求equals相等?
因为是按照hashCode来访问小内存块,所以hashCode必须相等。
HashMap获取一个对象是比较key的hashCode相等和equals为true。
之所以hashCode相等,却可以equal不等,就比如ObjectA和ObjectB他们都有属性name,那么hashCode都以name计算,所以hashCode一样,但是两个对象属于不同类型,所以equals为false。
5、为什么需要hashCode?
通过hashCode可以很快的查到小内存块。
通过hashCode比较比equals方法快,当get时先比较hashCode,如果hashCode不同,直接返回false。
20.深拷贝和浅拷贝的区别是什么?
浅拷贝
(1)、定义
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。
简而言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象
(2)、浅拷贝实例
package com.test; public class ShallowCopy { public static void main(String[] args) throws CloneNotSupportedException { Teacher teacher = new Teacher(); teacher.setName("riemann"); teacher.setAge(27); Student2 student1 = new Student2(); student1.setName("edgar"); student1.setAge(18); student1.setTeacher(teacher); Student2 student2 = (Student2) student1.clone(); System.out.println("拷贝后"); System.out.println(student2.getName()); System.out.println(student2.getAge()); System.out.println(student2.getTeacher().getName()); System.out.println(student2.getTeacher().getAge()); System.out.println("修改老师的信息后——————"); // 修改老师的信息 teacher.setName("Games"); System.out.println(student1.getTeacher().getName()); System.out.println(student2.getTeacher().getName()); } } class Teacher implements Cloneable { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } class Student2 implements Cloneable { private String name; private int age; private Teacher teacher; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; } public Object clone() throws CloneNotSupportedException { Object object = super.clone(); return object; } }
输出结果:
拷贝后 edgar 18 riemann 27 修改老师的信息后—————— Games Games
结果分析: 两个引用student1和student2指向不同的两个对象,但是两个引用student1和student2中的两个teacher引用指向的是同一个对象,所以说明是浅拷贝。
深拷贝
(1)、定义
深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
(2)、深拷贝实例
package com.test; public class DeepCopy { public static void main(String[] args) throws CloneNotSupportedException { Teacher2 teacher = new Teacher2(); teacher.setName("riemann"); teacher.setAge(27); Student3 student1 = new Student3(); student1.setName("edgar"); student1.setAge(18); student1.setTeacher(teacher); Student3 student2 = (Student3) student1.clone(); System.out.println("拷贝后"); System.out.println(student2.getName()); System.out.println(student2.getAge()); System.out.println(student2.getTeacher().getName()); System.out.println(student2.getTeacher().getAge()); System.out.println("修改老师的信息后——————"); // 修改老师的信息 teacher.setName("Games"); System.out.println(student1.getTeacher().getName()); System.out.println(student2.getTeacher().getName()); } } class Teacher2 implements Cloneable { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Object clone() throws CloneNotSupportedException { return super.clone(); } } class Student3 implements Cloneable { private String name; private int age; private Teacher2 teacher; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Teacher2 getTeacher() { return teacher; } public void setTeacher(Teacher2 teacher) { this.teacher = teacher; } public Object clone() throws CloneNotSupportedException { // 浅复制时: // Object object = super.clone(); // return object; // 改为深复制: Student3 student = (Student3) super.clone(); // 本来是浅复制,现在将Teacher对象复制一份并重新set进来 student.setTeacher((Teacher2) student.getTeacher().clone()); return student; } }
输出结果:
拷贝后 edgar 18 riemann 27 修改老师的信息后—————— Games riemann
结果分析:
两个引用student1和student2指向不同的两个对象,两个引用student1和student2中的两个teacher引用指向的是两个对象,但对teacher对象的修改只能影响student1对象,所以说是深拷贝。
程序员的56大Java基础面试问题及答案(二):https://developer.aliyun.com/article/1416643