X32专项练习部分11
StringBuffer扩容规则
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer(10);
s1.append("1234");
System.out.println(s1.length()); // 4
System.out.println(s1.capacity()); // 10
/*
length 返回当前长度
如果字符串长度没有初始化长度大,capacity返回初始化的长度
如果append后的字符串长度超过初始化长度,capacity返回增长后的长度
StringBuffer和StringBuilder的默认大小为16
ArrayList和LinkedList的默认大小10
*/
StringBuffer sb = new StringBuffer("abcd");
StringBuffer buffer1 = new StringBuffer(10);
StringBuffer buffer2 = new StringBuffer(3);
StringBuffer buffer3 = new StringBuffer(1);
buffer1.append("abcd");
buffer2.append("abcd"); // 扩容的长度为原来的2倍+2
buffer3.append("abcd"); // 2*1<4 扩容长度为4
System.out.println(buffer1.length() + " && " + buffer2.length() + " && " + buffer3.length() + " && " + sb.length());
System.out.println(buffer1.capacity() + " && " + buffer2.capacity() + " && " + buffer3.capacity() + " && " + sb.capacity());
/*
输出结果:
4 && 4 && 4 && 4
10 && 8 && 4 && 20
这是扩容规则
if(str.length>2*x+2) capacity = str.length;
else capacity = 2*x+2;
*/
}
Math方法返回值类型
public static void main(String[] args) {
double d = Math.floor(-8.5);
System.out.println(d); // (double)-9.0
double d1 = Math.ceil(-1.5);
System.out.println(d1); // (double)-1.0
long l = Math.round(-2.6);
System.out.println(l); // (long)-3
// String s;
// System.out.println("s=" + s); // 不能通过编译,必须初始化
}
临时修改引用
/*
参数修改问题
关于Java中参数传递的说法,哪个是错误的
正确答案: D
在方法中,修改一个基础类型的参数不会影响原始参数值
在方法中,改变一个对象参数的引用不会影响到原始引用
在方法中,修改一个对象的属性会影响原始对象参数
在方法中,修改集合和Maps的元素不会影响原始集合参数
*/
class Test2 {
/*
A 选项
*/
public static void main(String []args){
int i = 5;
func(i);
System.out.println(i);
}
static void func(int j){
j = 10;
}
/*
输出结果 5
所以不会影响原始参数值
*/
/*
B选项
在方法中改变了对象的引用,指的是仅仅改变引用而已,对象还是那个对象
就相当于你银行卡丢了,你重新补办换了一张卡,变的是银行卡,而你卡里的钱一分都没少
public static void main(String []args){
User rabbiter = new User();
rabbiter.setName("rabbiter");
func(rabbiter);
System.out.println(rabbiter.getName());
}
static void func(User user){
user = new User();
user.setName("zhangsan");
}
输出结果 rabbiter
*/
/*
C选项
public static void main(String []args){
User rabbiter = new User();
rabbiter.setName("rabbiter");
func(rabbiter);
System.out.println(rabbiter.getName());
}
static void func(User user){
user.setName("zhangsan");
}
输出结果 zhangsan
*/
/*
D选项
Map和集合也是引用
C和D选项与B不同
临时修改了引用
不会改变原有引用指向的值
*/
}
类的初始化过程(复杂)
class Test1 {
public static void main(String[] args) {
System.out.println(new B().getValue());
/*
main方法执行后
创建B类实例
掉用B的无参构造
B的无参构造显式调用了A类构造函数
但由于是B类实例
所以实际上调用的是B类重写的setValue方法
但B实例的setValue调用的是父类的setValue方法
把B类成员变量value设置为2*5 = 10
--- 这里为了提高可读性分割一下 --- --- 这里为了提高可读性分割一下 ---
然后B类构造函数中super(5);
执行完毕
继续执行下面的setValue(getValue() - 3);
语句
先执行getValue方法
B类中没有重写getValue方法
因此调用父类A的getValue方法
value++执行后
B的成员变量value值为11
此时开始执行到return语句
把11这个值作为getValue方法的返回值返回出去
但是由于getValue块被try finally块包围
因此finally中的语句无论如何都将被执行
所以步骤2中11这个返回值会先暂存起来
到finally语句块执行完毕后
再真正返回出去
--- 这里为了提高可读性分割一下 --- --- 这里为了提高可读性分割一下 ---
finally语句块中
this.setValue(value)方法调用的是B类的setValue方法
因为此刻B类构造函数还未执行完毕
正在初始化的是B类的一个对象,运行时多态
就像最开始第一步提到的一样
而且这里用了使用了this关键词显式指明了调用当前对象的方法
而当前对象就是B类正在初始换的对象
因此,此处会再次调用B类的setValue方法
同上,super关键词显式调用A的setValue方法
把B的value值设置成为了2 * 11 = 22
因此第1个打印项为22
此时finally语句执行完毕
会把刚刚暂存起来的11返回出去
也就是说这么经历了这么一长串的处理
getValue方法最终的返回值是11
回到前面B类构造函数的代码语句
其最终结果为setValue(11-3)=>setValue(8)
会是B的setValue方法
B的value值再次变成了2*8 = 16
--- 这里为了提高可读性分割一下 --- --- 这里为了提高可读性分割一下 ---
到了这里
B类已经初始化完成
调用getValue方法之前
B的成员变量value值为16
B类没有getValue方法
只能调用A类的getValue方法
value++ 执行后, B的成员变量value值为17
此时执行到return语句,会将17这个值作为getValue方法的返回值返回出去
但是由于getValue块被try finally块包围而finally中的语句无论如何都一定会被执行
所以步骤2中17这个返回值会先暂存起来,到finally语句块执行完毕后再真正返回出去。
finally语句块中继续和上面说的一样
this.setValue(value)方法调用的是B类的setValue()方法将B的value值设置成为了2 * 17 = 34。
因此第二个打印项为34
finally语句执行完毕 会把刚刚暂存起来的17返回出去。
因此new B().getValue()最终的返回值是17
到这里
运行main方法的打印方法
将刚刚返回的值打印出来,也就是第三个打印项:17
*/
}
static class A {
protected int value;
public A(int v) {
setValue(v);
}
public void setValue(int value) {
this.value = value;
}
public int getValue() {
try {
value++;
return value;
} finally {
this.setValue(value);
System.out.println(value);
}
}
}
static class B extends A {
public B() {
super(5);
setValue(getValue() - 3);
}
public void setValue(int value) {
super.setValue(2 * value);
}
}
}
Java异常易混点
/*
以下关于JAVA语言异常处理描述正确的有?
正确答案: C D
throw关键字可以在方法上声明该方法要抛出的异常。
throws用于抛出异常对象。
try是用于检测被包住的语句块是否出现异常,如果有异常,则捕获异常,并执行catch语句。
finally语句块是不管有没有出现异常都要执行的内容。
在try块中不可以抛出异常
throw用于抛出异常。
throws关键字可以在方法上声明该方法要抛出的异常,然后在方法内部通过throw抛出异常对象。
try是用于检测被包住的语句块是否出现异常,如果有异常,则抛出异常,并执行catch语句。
cacth用于捕获从try中抛出的异常并作出处理。
finally语句块是不管有没有出现异常都要执行的内容
补充异常知识
异常分为两种,一种为运行异常RuntimeException
另一种为检查异常CheckedException
对于运行异常,编译器没有强制对其进行捕获
会把异常一直往上层抛出,直到遇到处理代码为止
常见的运行异常有:NullPointerException(空指针异常),IndexOutOfBoundException(数组下标越界越界异常)
检查异常,所有继承自Exception并且不是运行异常的都是检查异常,在程序中需要用try catch进行捕获
常见的有IO异常和SQL异常
*/
instanceof方法
class Test4 {
public static void main(String[] args) {
// 向上转型 父类引用指向子类对象
A obj = new D();
System.out.println(obj instanceof B); // true
System.out.println(obj instanceof C); // false
System.out.println(obj instanceof D); // true
System.out.println(obj instanceof A); // true
}
/*
instanceof是判断前者是否可以类型可以转化为后者
可以转化即为true
分为向上转型和向下转型BD都是A的子类向下转型
D属于B,D属于A,D属于D,D不属于C
所以instanceof是一条直线链判断是否在链上
*/
}
构造方法内部调用构造方法
/*
在JAVA中,假设A有构造方法A(int a)
则在类A的其他构造方法中调用该构造方法和语句格式应该为()
this.A(x)
this(x)
super(x)
A(x)
正确答案:B
*/
class MyClass {
/*
构造方法可以重载
这个没什么大的原理,this()和super()在构造器中就是这么用的
一个构造器的中默认的第一行就是super()
这就是为什么一旦一个类的直接父类没有无参构造的情况下
子类构造器必须显式调用父类或者子类中别的构造器
构造方法里面调用其它构造方法
格式为this(param)
*/
public MyClass(){}
public MyClass(int i){
this(1.2);
}
public MyClass(double l){}
}
修饰符列表
/*
要使某个类能被同一个包中的其他类访问
但不能被这个包以外的类访问,可以
正确答案: A
让该类不使用任何关键字
使用private关键字
使用protected关键字
使用void关键字
default指代的就是缺省修饰符
子类的话也有可能在外面的包里
外部包 > 子类 > 本包 > 该类内部
public > protected > default > private
*/
文本文件和二进制文件
/*
对于文件的描述正确的是( )
正确答案: D
文本文件是以“.txt”为后缀名的文件,其他后缀名的文件是二进制文件。
File类是Java中对文件进行读写操作的基本类。
无论文本文件还是二进制文件,读到文件末尾都会抛出EOFException异常。
Java中对于文本文件和二进制文件,都可以当作二进制文件进行操作。
A. 文件分为文本文件和二进制文件,计算机只认识二进制,所以实际上都是二进制的不同解释方式
文本文件是以不同编码格式显示的字符,例如ASCII、Unicode等
window中文本文件的后缀名有".txt",".log",各种编程语言的源码文件等
二进制文件就是用文本文档打开是看不懂乱码,
下一句话很重要
只要能用文本打开的文件都可以算是文本文件
只是显示的结果不是你想要的
二进制文件只有用特殊的应用才能读懂的文件,例如".png",".bmp"等
计算机中大部分的文件还是二进制文件。
B. File类是对文件整体或者文件属性操作的类
例如创建文件、删除文件、查看文件是否存在等功能,不能操作文件内容;文件内容是用IO流操作的。
C. 当输入过程中意外到达文件或流的末尾时,抛出EOFException异常
正常情况下读取到文件末尾时,返回一个特殊值表示文件读取完成,例如read()返回-1表示文件读取完成。
D. 上面A选项已经说了,不论是文本文件还是二进制文件
在计算机中都是以二进制形式存储的,所以都当做二进制文件读取
*/
编译器优化与堆栈数据共享(复杂)
// 下列程序的输出结果是
class StringDemo{
private static final String MESSAGE="taobao";
public static void main(String [] args) {
String a ="tao"+"bao";
String b="tao";
String c="bao";
System.out.println(a==MESSAGE); // true
System.out.println((b+c)==MESSAGE); // false
}
/*
补充:栈内存数据共享
栈内存的一个特点是数据共享,这样设计是为了减小内存消耗
前面定义了i=1,i和1都在栈内存内,如果再定义一个j=1,此时将j放入栈内存,然后查找栈内存中是否有1
如果有则j指向1。如果再给j赋值2,则在栈内存中查找是否有2,如果没有就在栈内存中放一个2,然后j指向2
也就是如果常量在栈内存中,就将变量指向该常量
如果没有就在该栈内存增加一个该常量,并将变量指向该常量
如果j++,这时指向的变量并不会改变,而是在栈内寻找新的常量(比原来的常量大1)
如果栈内存有则指向它,如果没有就在栈内存中加入此常量并将j指向它
这种基本类型之间比较大小和我们逻辑上判断大小是一致的
如定义i和j是都赋值1,则i==j结果为true
==用于判断两个变量指向的地址是否一样
i==j就是判断i指向的1和j指向的1是同一个吗,当然是了
对于直接赋值的字符串常量
(如String s="Hello World";中的Hello World)
也是存放在栈内存中,而new出来的字符串对象(即String对象)是存放在堆内存中
如果定义String s=“Hello World”和String w=“Hello World”,s==w吗
肯定是true,因为他们指向的是同一个Hello World
--- 这里为了提高可读性分割一下 --- --- 这里为了提高可读性分割一下 ---
堆内存没有数据共享的特点
前面定义的String s = new String("Hello World");
变量s在栈内存内,Hello World这个String对象在堆内存内
如果定义String w = new String("Hello World");
则会在堆内存创建一个新的String对象,变量w存放在栈内存,w指向这个新的String对象
堆内存中不同对象(指同一类型的不同对象)的比较如果用==则结果肯定都是false
比如s==w 当然不等,s和w指向堆内存中不同的String对象
如果判断两个String对象相等呢,用equals方法
--- 这里为了提高可读性分割一下 --- --- 这里为了提高可读性分割一下 ---
String a = "tao" + "bao" ;和String a = "taobao";
编译出的字节码是一样的
所以等到运行时,根据上面说的栈内存是数据共享原则
a和MESSAGE指向的是同一个字符串
对于后面的(b+c)又是什么情况呢
b+c只能等到运行时才能判定是什么字符串,编译器不会优化,想想这也是有道理的
编译器怕你对b的值改变,所以编译器不会优化
运行时b+c计算出来的"taobao"和栈内存里已经有的"taobao"是一个吗
不是
b+c计算出来的"taobao"应该是放在堆内存中的String对象
这可以通过System.out.println((b+c)== MESSAGE);的结果为false来证明这一点
如果计算出来的b+c也是在栈内存,那结果应该是true
Java对String的相加是通过StringBuffer实现的
先构造一个StringBuffer里面存放”tao”,然后调用append()方法追加”bao”
然后将值为”taobao”的StringBuffer转化成String对象
StringBuffer对象在堆内存中,那转换成的String对象理所应当的也是在堆内存中
--- 这里为了提高可读性分割一下 --- --- 这里为了提高可读性分割一下 ---
这里改造一下
下面改造一下这个语句
System.out.println((b+c).intern()== MESSAGE);
结果是true
是因为intern方法把原本在堆内存中的字符串引用
放到了常量池
再改造一下
再把变量b和c的定义改一下,
final String b = "tao";
final String c = "bao";
System.out.println((b+c)== MESSAGE);
现在b和c不可能再次赋值了,所以编译器将b+c编译成了”taobao”
因此,这时的结果是true
在字符串相加中,只要有一个是非final类型的变量,编译器就不会优化
因为这样的变量可能发生改变,所以编译器不可能将这样的变量替换成常量
例如将变量b的final去掉,结果又变成了false
这也就意味着会用到StringBuffer对象,计算的结果在堆内存中
*/
对象地址改变
class foo {
public static void main(String sgf[]) {
StringBuffer a=new StringBuffer("A");
StringBuffer b=new StringBuffer("B");
operate(a,b);
System.out.println(a+"."+b); // AB.B
}
final static void operate(StringBuffer x,StringBuffer y) {
x.append(y);
y=x; // 这一步有个坑,y和b都是指针,这里只改了y的引用,对b毫无影响
}
}
/*
引用a指向对象A 引用b指向对象B 引用x指向对象A 引用y指向对象B
在operate方法中
引用x指向的对象A被连接了B,对象A也就被改变为AB
然后又把引用y指向了x所指向的对象地址
也就是此时引用a,x,y指向同一个对象AB
x进行对象操作,所指向对象地址没变,对象内容变成了AB
而y进行赋值,使指向的对象地址变了,变成了现在x指向的地址
而其原来指向地址的对象还是那样
即b所指向的对象B并没有变
如果加入b = a;
则程序输出AB.AB
*/