1、Java中的作用域有哪些?
在Java语言中,变量的类型主要有三种:成员变量、静态变量和局部变量。
类的成员变量的作用范围与类的实例化对象的作用范围相同,当类被实例化时,成员变量就会在内存中分配空间并初始化,直到这个被实例化对象的生命周期结束时,成员变量的生命周期才结束。
被static修饰的成员变量被称为静态变量或全局变量。静态变量不依赖于特定的实例,而是被所有实例所共享,也就是说,只要一个类被加载,JVM就会给类的静态变量分配存储空间。因此,就可以通过类名和变量名来访问静态变量。
局部变量的作用域与可见性为它所在的花括号内。
2、什么是构造函数?
构造函数是一种特殊的函数,用来在对象实例化时初始化对象的成员变量。在Java中,构造函数有以下特点:
1)构造函数必须与类的名字相同,并且不能有返回值(返回值也不能为void)
2)每个类可以有多个构造函数,其可以有0、1或1个以上的参数
3)构造函数总是伴随着new操作一起调用,且不能由程序的编写者直接调用,必须由系统调用。构造函数在对象实例化时会被自动调用,且只运行一次;而普通的方法是在程序执行到它的时候被调用,且可以被该对象调用多次。
4)构造函数的主要作用是完成对象的初始化工作。
5)构造函数不能被继承,因此不能被覆盖,但是构造函数能被重载,可以使用不同的参数个数和参数类型来定义多个构造函数。
6)子类可以通过super关键字来显示地调用父类的构造函数。当服务没有提供无参的构造函数时,子类的构造函数中必须显示地调用父类的构造函数。如果父类提供了无参的构造函数,此时子类的构造函数可以不显示地调用父类的构造函数。当有父类时,在实例化对象时,会先执行父类的构造函数,然后执行子类的构造函数。
引申:普通方法是否可以与构造函数有相同的方法名?
package base;
public class Test {
public Test() {
System.out.println("我是构造函数");
}
public void Test(){
System.out.println("我是普通函数");
}
public static void main(String[] args) {
Test t=new Test();//调用构造
t.Test();//调用普通
}
}
运行结果:
我是构造函数
我是普通函数
3、为什么Java中有些接口没有任何方法?
由于Java不支持多重继承,即一个类只能有一个父类,为了克服单继承的缺点,Java语言引入了接口这一概念。接口是抽象方法定义的集合(接口中也可以定义一些常量值),是一种特殊的抽象类。接口中只包含方法的定义,没有方法的实现。接口中的所有方法都是抽象的。接口中成员的作用域修饰符都是public,接口中的常量值默认使用public static final 修饰。由于一个类可以实现多个接口,因此通常可以采用实现多个接口的方法来间接达到多重继承的目的。
在Java语言中,有些接口内部没有声明任何方法,也就是说,实现这些接口的类不需要重写任何方法,这些没有任何方法声明的接口又被叫做标识接口,标识接口对实现它的类没有任何语义上的要求,它仅仅充当一个标识的作用,用来表明实现它的类属于一个特定的类型。
Java类库中已存在的标识接口Cloneable和Serializable等。在使用时会经常用instanceof 来判断实例对象的类型是否实现了一个给定的标识接口。
4、Java中的clone方法有什么作用?
Java语言取消了指针的概念,但这只是在Java语言中没有明确提供指针的概念与用法,而实质上每个new语句返回的都是一个指针的引用,只不过在大部分情况下开发人员不需要关心如何去操作这个指针而已。
package base.java;
class Obj{
private String str="default value";
public void setStr(String str){
this.str=str;
}
public String toString() {
return str;
}
}
public class TestRef {
private Obj aObj=new Obj();
private int aInt=0;
public Obj getAObj() {
return aObj;
}
public int getAInt() {
return aInt;
}
public void changeObj(Obj inObj){
inObj.setStr(" changed value");
}
public void changeInt(int inInt){
inInt=1;
}
public static void main(String[] args) {
TestRef oRef=new TestRef();
System.out.println("--------------引用类型---------");
System.out.println("调用changeObj()前:"+oRef.getAObj());
oRef.changeObj(oRef.getAObj());
System.out.println("调用changeObj()后:"+oRef.getAObj());
System.out.println("***********引用数据类型**********");
System.out.println("调用changeInt()前:"+oRef.getAInt());
oRef.changeInt(oRef.getAInt());
System.out.println("调用changeInt()后:"+oRef.getAInt());
}
}
运行结果:
--------------引用类型---------
调用changeObj()前:default value
调用changeObj()后: changed value
***********引用数据类型**********
调用changeInt()前:0
调用changeInt()后:0
结果不同,主要原因是Java在处理基本数据类型(如int、char、double等),都是采用按值传递(传递的是输入参数的复制)的方式执行,除此之外的其他类型都是按照引用传递(传递的是对象的一个引用)的方式执行。
对象除了在函数调用时是引用传递,在使用“=”赋值时也采用引用传递。
package base2;
class Obj{
private int aInt=0;
public int getAInt() {
return aInt;
}
public void setAInt(int aInt) {
this.aInt = aInt;
}
public void changeInt(){
this.aInt=1;
}
}
public class TestRef {
public static void main(String[] args) {
Obj a=new Obj();
Obj b=a; //引用传递
b.changeInt();
System.out.println("a:"+a.getAInt());
System.out.println("b:"+b.getAInt());
}
}
运行结果:
a:1
b:1
在实际编程中,经常会遇到从某个已有的对象A创建出另外一个与A具有相同状态的对象B,并且对B的修改不会影响到A的情况。在Java语言中,仅仅通过简单的赋值操作显然无法达到这个目的,而Java提供了一个简单有效的clone()方法来满足这个需求。
Java中的所有类默认都继承自Object类,而Object类中提供了一个clone()方法。这个方法的作用是返回一个Object对象的复制。这个复制函数返回的是一个新的对象而不是一个引用。
使用clone()方法的步骤
1)实现clone()的类首先需要继承Cloneable接口。Cloneable接口实际上是一个标识接口,没有任何接口方法。
2)在类中重写Object类中的Clone()方法。
3)在clone方法中调用super.clone()。无论clone类的继承结构是什么,super.clone()都会直接或间接调用java.lang.Object类的clone()方法。
4)把浅复制的引用指向原型对象的克隆体。
对上面的栗子引入clone()方法
package base2;
class Obj implements Cloneable{
private int aInt=0;
public int getAInt() {
return aInt;
}
public void setAInt(int aInt) {
this.aInt = aInt;
}
public void changeInt(){
this.aInt=1;
}
@Override
protected Object clone(){
Object o=null;
try {
o=(Obj)super.clone();
} catch (CloneNotSupportedException e) {
e.getStackTrace();
}
return o;
}
}
public class TestRef {
public static void main(String[] args) {
Obj a=new Obj();
Obj b=(Obj) a.clone();
b.changeInt();
System.out.println("a:"+a.getAInt());
System.out.println("b:"+b.getAInt());
}
}
运行结果:
a:0
b:1
在C++语言中,当开发人员自定义复制构造函数时,会存在浅复制和深复制之分。Java在重载clone()方法时也存在同样的问题,当类中只有一些基本的数据类型时,采用上述方法就可以了,但是当类中包含一些对象时,就需要深复制,实现方法是在对象调用clone()方法完成复制后,接着对对象中的非基本类型的属性也调用clone()方法完成深复制。
package base2;
import java.util.Date;
class Obj implements Cloneable{
private Date birth=new Date();
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
@SuppressWarnings("deprecation")
public void changeDate(){
this.birth.setMonth(2);
}
@Override
protected Object clone(){
Obj o=null;
try {
o=(Obj) super.clone();
} catch (CloneNotSupportedException e) {
// TODO: handle exception
e.getStackTrace();
}
//实现深复制
o.birth=(Date) this.getBirth().clone();
return o;
}
}
public class TestRef {
public static void main(String[] args) throws CloneNotSupportedException {
Obj a=new Obj();
Obj b=(Obj) a.clone();
b.changeDate();
System.out.println("a:"+a.getBirth());
System.out.println("b:"+b.getBirth());
}
}
运行结果:
a:Mon May 21 09:58:48 CST 2018
b:Wed Mar 21 09:58:48 CST 2018
在编程时如何选择使用哪种复制方式??
首先,检查类有无非基本类型(即对象)的数据成员。若没有,则返回super.clone()即可;若有,确保类中包含的所有非基本类型的成员变量都实现了深复制。
先执行浅复制
Object o=super.clone();
对每一个对象attr执行以下语句
o.attr=this.getAttr().clone();
最后返回o
return o;
需要注意的是,clone()方法的保护机制在Object中clone()是被声明为protected的。
深复制与浅复制的区别?
浅复制(Shallow Clone):被复制对象的所有变量都含有与原来对象相同的值,而所有对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
深复制(Deep Clone):被复制对象的所有变量都含有与原来对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制的新对象,而不再是原有的那些被引用的对象。换言之,深复制把复制的对象所引用的对象都复制了一遍。
5、什么是反射机制?
反射机制是Java语言中一个非常重要的特性,它允许程序在运行时进行自我检查,同时也允许对其内部的成员进行操作。实际开发时用的不多。由于反射机制能够实现在运行时对类进行装载,因此能够增加程序的灵活性。但是若使用不当,也会严重影响系统的性能。
具体而言:反射机制提供的功能主要有:
得到一个对象所属的类;
获取一个类的所有成员变量和方法;
在运行时创建对象;
在运行时调用对象的方法。
其实,反射机制非常重要的一个作用就是:可以在运行时动态地创建类的对象。
在反射机制中,class是一个非常重要的类,获取class类的方法有3种:
1)class.forName("类的路径")
2)类名.class
3)实例.getClass()
常见面试题:Java创建对象的方式有几种?
1)通过new语句实例化一个对象;
2)通过反射机制创建对象
3)通过clone()方法创建一个对象;
4)通过反序列化的方式创建一个对象
6、package有什么作用?
主要有两个作用: 包(package)由一组类(class)和接口(interface)组成。
第一,提供多层命名空间,解决命名冲突,通过使用package,使得处于不同package中的类可以存在相同的名字。
第二,对类按功能进行分类,使项目的组织更加清晰。当开发一个非常多的类的项目时,如果不使用package对类进行分类,而是把所有类都放在一个package下,这样代码不仅可读性差,而且可维护性也不好,会严重影响开发效率。
7、在Java中没有指针的概念,如何才能在Java语言中实现类似于函数指针的功能呢?
可以利用接口与类来实现同样的效果。
具体而言,应该先定义一个接口,然后在接口中声明要调用的方法,接着实现这个接口,最后把这个实现类的一个对象作为参数传递给调用程序,调用程序通过这个参数来调用指定的函数,从而实现回调函数的功能。