`

java面试题解惑 之变量初始顺序,变量覆盖,字符串池,final,finally区别

阅读更多
1,变量初始化顺序
package com.qdu.sun;
public class InitialOrderTest {
// 静态变量
public static String staticField = "静态变量";
// 变量
public String field = "变量";
// 静态初始化块
static {
		System.out.println(staticField);
		System.out.println("静态初始化块");
}
// 初始化块
{
System.out.println(field);
System.out.println("初始化块");
}
// 构造器
public InitialOrderTest() {
System.out.println("构造器");
}
public static void main(String[] args) {
new InitialOrderTest();
}
}

执行结果;
静态变量
静态初始化块
变量
初始化块
构造器

但静态变量和静态块,变量和初始化块之间的顺序要看它们在类定义中的次序
package com.qdu.sun;
class Parent {
	// 静态变量
	public static String p_StaticField = "父类--静态变量";
	// 变量
	public String p_Field = "父类--变量";
	// 静态初始化块
	static {
	System.out.println(p_StaticField);
	System.out.println("父类--静态初始化块");
	}
	// 初始化块
	{
	System.out.println(p_Field);
	System.out.println("父类--初始化块");
	}
	// 构造器
	public Parent() {
	System.out.println("父类--构造器");
	}
	}

public class SubClass extends Parent {
// 静态变量
public static String s_StaticField = "子类--静态变量";
// 变量
public String s_Field = "子类--变量";
// 静态初始化块
static {
System.out.println(s_StaticField);
System.out.println("子类--静态初始化块");
}
// 初始化块
{
System.out.println(s_Field);
System.out.println("子类--初始化块");
}
// 构造器
public SubClass() {
System.out.println("子类--构造器");
}
// 程序入口
public static void main(String[] args) {
new SubClass();
}
}

执行结果:
父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
父类--变量
父类--初始化块
父类--构造器
子类--变量
子类--初始化块
子类--构造器

2,String对象package com.qdu.sun;
public class StringStaticTest {
// 常量A
public static final String A;
// 常量B
public static final String B;


public static final String C = "AB";
//常量B
public static final String D="CD";

static {
A = "ab";
B = "cd";
}

public static void main(String[] args) {
// 将两个常量用+连接对s进行初始化
	
String s = A + B;
String s2 = C + D;
String t = "abcd";
String t2 = "ABCD";
if (s == t) {
System.out.println("s等于t,它们是同一个对象");
} else {
System.out.println("s不等于t,它们不是同一个对象");
}

if (s2 == t2) {
	System.out.println("s2等于t2,它们是同一个对象");
	} else {
	System.out.println("s2不等于t2,它们不是同一个对象");
	}
}
}

执行结果:
s不等于t,它们不是同一个对象(虽然A,B是final的,但不知何时赋初值,相当于变量)
s2等于t2,它们是同一个对象(因为C,D是final的,并有初值,编译时就确定了不会变的)
package com.qdu.sun;

public class StringTest {
	public static void main(String[] args) {

		String a = "ab";// 创建了一个对象,并加入字符串池中
		System.out.println("String a = \"ab\";");
		String b = "cd";// 创建了一个对象,并加入字符串池中
		System.out.println("String b = \"cd\";");
		String c = "abcd";// 创建了一个对象,并加入字符串池中
		String d = "ab" + "cd";
		// 如果d和c指向了同一个对象,则说明d也被加入了字符串池
		if (d == c) {
			System.out.println("\"ab\"+\"cd\" 创建的对象 \"加入了\" 字符串池中");
		}
		// 如果d和c没有指向了同一个对象,则说明d没有被加入字符串池
		else {
			System.out.println("\"ab\"+\"cd\" 创建的对象 \"没加入\" 字符串池中");
		}
		String e = a + "cd";
		// 如果e和c指向了同一个对象,则说明e也被加入了字符串池
		if (e == c) {
			System.out.println(" a +\"cd\" 创建的对象 \"加入了\" 字符串池中");
		}
		// 如果e和c没有指向了同一个对象,则说明e没有被加入字符串池
		else {
			System.out.println(" a +\"cd\" 创建的对象 \"没加入\" 字符串池中");
		}
		String f = "ab" + b;
		// 如果f和c指向了同一个对象,则说明f也被加入了字符串池
		if (f == c) {
			System.out.println("\"ab\"+ b 创建的对象 \"加入了\" 字符串池中");
		}
		// 如果f和c没有指向了同一个对象,则说明f没有被加入字符串池
		else {
			System.out.println("\"ab\"+ b 创建的对象 \"没加入\" 字符串池中");
		}
		String g = a + b;
		// 如果g和c指向了同一个对象,则说明g也被加入了字符串池
		if (g == c) {
			System.out.println(" a + b 创建的对象 \"加入了\" 字符串池中");
		}
		// 如果g和c没有指向了同一个对象,则说明g没有被加入字符串池
		else {
			System.out.println(" a + b 创建的对象 \"没加入\" 字符串池中");
		}
	}
}

执行结果:
String a = "ab";
String b = "cd";
"ab"+"cd" 创建的对象 "加入了" 字符串池中
a +"cd" 创建的对象 "没加入" 字符串池中
"ab"+ b 创建的对象 "没加入" 字符串池中
a + b 创建的对象 "没加入" 字符串池中


3,变量覆盖
package variableOverride;

class ParentClass {
	public int i = 10;
}

public class SubClass extends ParentClass {
	public int i = 30;

	public static void main(String[] args) {
		ParentClass parentClass = new SubClass();
		SubClass subClass = new SubClass();
		System.out.println(parentClass.i + subClass.i); // 40
	}
}

这个问题虽然简单,但是情况却比较复杂。因为我们不仅要考虑变量、静态变量和常量三种
情况,还要考虑private、friendly(即不加访问修饰符)、protected和public四种访问权限下对属性的不同影响。


package variableOverride;
class ParentClass {
	private String privateField = "父类变量--private";
	/* friendly */String friendlyField = "父类变量--friendly";
	protected String protectedField = "父类变量--protected";
	public String publicField = "父类变量--public";
	// private的变量无法直接访问,因此我们给他增加了一个访问方法
	public String getPrivateFieldValue() {
	return privateField;
	}
	}
public class SubClass extends ParentClass {
	private String privateField = "子类变量--private";
	/* friendly */String friendlyField = "子类变量--friendly";
	protected String protectedField = "子类变量--protected";
	public String publicField = "子类变量--public";

	// private的变量无法直接访问,因此我们给他增加了一个访问方法
	public String getPrivateFieldValue() {
		return privateField;
	}

	public static void main(String[] args) {
		// 为了便于查阅,我们统一按照private、friendly、protected、public的顺序
		// 输出下列三种情况中变量的值
		// ParentClass类型,ParentClass对象
		ParentClass parentClass = new ParentClass();
		System.out.println("ParentClass parentClass = new ParentClass();");
		System.out.println(parentClass.getPrivateFieldValue());
		System.out.println(parentClass.friendlyField);
		System.out.println(parentClass.protectedField);
		System.out.println(parentClass.publicField);
		System.out.println();
		// ParentClass类型,SubClass对象
		ParentClass subClass = new SubClass();
		System.out.println("ParentClass subClass = new SubClass();");
		System.out.println(subClass.getPrivateFieldValue());
		System.out.println(subClass.friendlyField);
		System.out.println(subClass.protectedField);
		System.out.println(subClass.publicField);
		System.out.println();
		// SubClass类型,SubClass对象
		SubClass subClazz = new SubClass();
		System.out.println("SubClass subClazz = new SubClass();");
		System.out.println(subClazz.getPrivateFieldValue());
		System.out.println(subClazz.friendlyField);
		System.out.println(subClazz.protectedField);
		System.out.println(subClazz.publicField);
	}
}

执行结果:
ParentClass parentClass = new ParentClass();
父类变量--private
父类变量--friendly
父类变量--protected
父类变量--public

ParentClass subClass = new SubClass();
子类变量--private
父类变量--friendly
父类变量--protected
父类变量--public

SubClass subClazz = new SubClass();
子类变量--private
子类变量--friendly
子类变量--protected
子类变量--public

private的变量与其它三种访问权限变量的不同,这是由于方法的重写(override)而引起的。
分析上面的输出结果就会发现,变量的值取决于我们定义的变量的类型,而不是创建的对象
的类型。
当变量类型是父类(ParentClass)时,不管我们创建的对象是
父类(ParentClass)的还是子类(SubClass)的,都不存在属性覆盖的问题
package variableOverride;
class ParentClass {
	public static String staticField = "父类静态变量";
	public final String finalField = "父类常量";
	public static final String staticFinalField = "父类静态常量";
	}
public class SubClass extends ParentClass {
public static String staticField = "子类静态变量";
public final String finalField = "子类常量";
public static final String staticFinalField = "子类静态常量";
public static void main(String[] args) {
SubClass subClass = new SubClass();
System.out.println(SubClass.staticField);
// 注意,这里的subClass变量,不是SubClass类
System.out.println(subClass.finalField);
System.out.println(SubClass.staticFinalField);
}
}

子类静态变量
子类常量
子类静态常量
虽然上面的结果中包含“子类静态变量”和“子类静态常量”,但这并不表示父类的“静态
变量”和“静态常量”可以被子类覆盖,因为它们都是属于类,而不属于对象。
总结:
1. 由于private变量受访问权限的限制,它不能被覆盖。
2. 属性的值取父类还是子类并不取决于我们创建对象的类型,而是取决于我们定义的变
量的类型。
3. friendly、protected和public修饰符并不影响属性的覆盖。
4. 静态变量和静态常量属于类,不属于对象,因此它们不能被覆盖。
5. 常量可以被覆盖。
6. 对于基本类型和对象,它们适用同样的覆盖规律。

4,final,finally,finalize
1. 在定义的时候初始化。
2. final变量可以在初始化块中初始化,不可以在静态初始化块中初始化。
3. 静态final变量可以在静态初始化块中初始化,不可以在初始化块中初始化。
4. final变量还可以在类的构造器中初始化,但是静态final变量不可以。
当final用来定义一个方法时,会有什么效果呢?正如大家所知,它表示这个方法不可以被
子类重写,但是它这不影响它被子类继承。
package variableOverride;

class ParentClass {
	public final void TestFinal() {
	System.out.println("父类--这是一个final方法");
	}
	}


public class SubClass extends ParentClass {
/**
* 子类无法重写(override)父类的final方法,否则编译时会报错,但是子类可以继承父类的final方法
*/
// public void TestFinal() {
// System.out.println("子类--重写final方法");
// }
public static void main(String[] args) {
SubClass sc = new SubClass();
sc.TestFinal();//父类--这是一个final方法
}
}

具有private访问权限的方法也可以增加final修饰,但是由于子类
无法继承private方法,因此也无法重写它。编译器在处理private方法时,是按照final方法来对待的,这样可以提高该方法被调用时的效率。不过子类仍然可以定义同父类中的
private方法具有同样结构的方法,但是这并不会产生重写的效果,而且它们之间也不存在必
然联系。
final类不允许被继承,编译器在处理时把它的所有方法都当作
final的,因此final类比普通类拥有更高的效率。而由关键字abstract定义的抽象类含有必须由继承自它的子类重载实现的抽象方法,因此无法同时用final和abstract来修饰同一个类。同样的道理,final也不能用来修饰接口。 final的类的所有方法都不能被重写,但这并不表示final的类的属性(变量)值也是不可改变的,要想做到final类的属性值不可改变,必须给它增加final修饰.
public final class FinalTest {
int i = 10;
public static void main(String[] args) {
FinalTest ft = new FinalTest();
ft.i = 99;
System.out.println(ft.i); //99
}
}

finally是处理异常时用的,不管是否发生异常,都会执行的语句,break,continue也不例外
package variableOverride;

public final class FinallyTest {
	// 测试return语句
	public ReturnClass testReturn() {
		try {
			return new ReturnClass();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			System.out.println("执行了finally语句");
		}
		return null;
	}

	// 测试continue语句
	public void testContinue() {
		for (int i = 0; i < 3; i++) {
			try {
				System.out.println(i);
				if (i == 1) {
					continue;
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				System.out.println("执行了finally语句");
			}
		}
	}

	// 测试break语句
	public void testBreak() {
		for (int i = 0; i < 3; i++) {
			try {
				System.out.println(i);
				if (i == 1) {
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				System.out.println("执行了finally语句");
			}
		}
	}

	public static void main(String[] args) {
		FinallyTest ft = new FinallyTest();
		// 测试return语句
		ft.testReturn();
		System.out.println();
		// 测试continue语句
		ft.testContinue();
		System.out.println();
		// 测试break语句
		ft.testBreak();
	}
}

class ReturnClass {
	public ReturnClass() {
		System.out.println("执行了return语句");
	}
}

执行结果:
执行了return语句
执行了finally语句

0
执行了finally语句
1
执行了finally语句
2
执行了finally语句

0
执行了finally语句
1
执行了finally语句



很明显,return、continue和break都没能阻止finally语句块的执行。从输出的结果来看,return语句似乎在 finally语句块之前执行了,事实真的如此吗?我们来想想看,return语句的作用是什么呢?是退出当前的方法,并将值或对象返回。如果 finally语句块是在return语句之后执行的,那么return语句被执行后就已经退出当前方法了,finally语句块又如何能被执行呢?因此,正确的执行顺序应该是这样的:编译器在编译return new ReturnClass();时,将它分成了两个步骤,new ReturnClass()和return,前一个创建对象的语句是在finally语句块之前被执行的,而后一个return语句是在finally语句块之后执行的,也就是说finally语句块是在程序退出方法之前被执行的。同样,finally语句块是在循环被跳过(continue)和中断(break)之前被执行的。

finalize()方法是Object类中定义的方法,是在GC清理它所从属的对象时被调用的,如果执行它的过程中抛出了无法捕
获的异常(uncaught exception),GC将终止对改对象的清理,并且该异常会被忽略;直到下一次GC开始清理这个对象时,它的finalize()会被再次调用。
package variableOverride;
public final class FinallyTest {
// 重写finalize()方法
protected void finalize() throws Throwable {
System.out.println("执行了finalize()方法");
}
public static void main(String[] args) {
FinallyTest ft = new FinallyTest();
ft = null;
System.gc(); //等价于Runtime.getRuntime().gc();
}
}

执行结果:执行了finalize()方法
调用它们的作用只是建议垃圾收集器(GC)启动,清理无用的对象释放内存空间,但是GC
的启动并不是一定的,这由JAVA虚拟机来决定。直到 JAVA虚拟机停止运行,有些对象的
finalize()可能都没有被运行过,那么怎样保证所有对象的这个方法在JAVA虚拟机停止运行之前一定被调用呢?答案是我们可以调用System类的另一个方法:
public static void runFinalizersOnExit(boolean value) {
//other code
}

传入true为参数,即可保证finalize方法一定执行,但该方法是不安全的,不建议使用。
5,java中参数传递
package variableOverride;

public class ParamTest {
	// 初始值为0
	protected int num = 0;

	// 为方法参数重新赋值
	public void change(int i) {
		i = 5;
	}

	// 为方法参数重新赋值
	public void change(ParamTest t) {
		ParamTest tmp = new ParamTest();
		tmp.num = 9;
		t = tmp; //这里的t也是局部变量,和实参指向同一个对象,但现在该引用指向了新创建的tmp
	}

	// 改变方法参数的值
	public void add(int i) {
		i += 10; //这里的i为局部变量
	}

	// 改变方法参数属性的值
	public void add(ParamTest pt) {
		pt.num += 20; //pt和实参指向同一个对象,对pt指向对象值的改变就是对源对象值改变
	}

	public static void main(String[] args) {
		ParamTest t = new ParamTest();
		System.out.println("参数--基本类型");
		System.out.println("原有的值:" + t.num);
		// 为基本类型参数重新赋值
		t.change(t.num);
		System.out.println("赋值之后:" + t.num);
		// 为引用型参数重新赋值
		t.change(t);
		System.out.println("运算之后:" + t.num);
		System.out.println();
		t = new ParamTest();
		System.out.println("参数--引用类型");
		System.out.println("原有的值:" + t.num);
		// 改变基本类型参数的值
		t.add(t.num);
		System.out.println("赋引用后:" + t.num);
		// 改变引用类型参数所指向对象的属性值
		t.add(t);
		System.out.println("改属性后:" + t.num);
	}
}

执行结果:
参数--基本类型
原有的值:0
赋值之后:0
运算之后:0

参数--引用类型
原有的值:0
赋引用后:0
改属性后:20

String的长度实际上就是它的属性--char型数组value的长度。数组是没有length()方法的,
大家知道,在JAVA中,数组也被作为对象来处理,它的方法都继承自Object类。数组有一
个属性length,这也是它唯一的属性,对于所有类型的数组都是这样。

6,java中的字符
一个中文汉字可以保存在一个char变量里呢?因为在JAVA中,一
个char是2个字节(byte),而一个中文汉字是一个字符,也是2个字节。而英文字母都是
一个字节的,因此它也能保存到一个byte里,一个中文汉字却不能
字符串反串最简单的方法是使用java api本身自带的函数
public class StringReverse {
public static void main(String[] args) {
// 原始字符串
String s = "A quick brown fox jumps over the lazy dog.";
System.out.println("原始的字符串:" + s);
System.out.print("反转后字符串:");
StringBuffer buff = new StringBuffer(s);
// java.lang.StringBuffer类的reverse()方法可以将字符串反转
System.out.println(buff.reverse().toString());
}
}

分享到:
评论

相关推荐

    JAVA面试题解惑系列合集

    它包括了从类的初始化顺序、String对象的创建数量、变量的覆盖、final关键字、传值和传引用的区别、字符串的处理、日期和时间的处理、基本类型的细节、继承和多态、多线程以及运算符相关的面试题目。下面将详细解析...

    臧圩人--JAVA面试题解惑系列合集.pdf

    **1.4 JAVA面试题解惑系列(四)——final、finally和finalize的区别** - **知识点**:阐述final关键字的用途(不可变性)、finally块的作用(确保资源释放)、以及`finalize()`方法的功能(垃圾回收前的清理工作)...

    JAVA面试题解惑系列

    【JAVA面试题解惑系列】是一系列专门针对Java开发者面试准备的文章集合,涵盖了多个核心Java概念和面试常问问题。作者臧圩人在JavaEye社区分享了这个系列,旨在帮助求职者理解和解答面试中可能出现的疑问。 1. **类...

    JAVA面试题解惑系列114页.pdf

    在Java编程语言中,面试时常会涉及到类的初始化顺序,这是理解对象生命周期和类加载机制的关键部分。在Java中,类的初始化顺序遵循以下规则: 1. **静态变量与静态初始化块**:首先,静态变量(static fields)会被...

    java面试题

    给定文件的内容主要涉及Java开发中常见的面试题解惑系列,其中涵盖了类的初始化顺序、String对象的创建、变量覆盖、final关键字的用法、引用传递以及字符串处理等重要的Java知识点。 首先,关于类的初始化顺序,...

Global site tag (gtag.js) - Google Analytics