【Rookie初学Java】
前言:
本篇博客来自作者自己学习的总结和查询,如果存在错误和不足欢迎大家的指出,谢谢。
1 变量,内存
-
变量分类
-
成员变量:声明在类中的变量,也是成员属性
-
局部变量:定义在方法内部,也可以作为形参,或者代码块中的变量
-
-
作用域(生存期)
关于变量的作用域或者说是生存期,在经过测试,在Java中,每一对“{}”内就是一个作用域
public class Test02 { public static void main(String[] args) { int x = 12; { int y=96; System.out.println("x is"+x); System.out.println("y is "+y); } y=x; System.out.println("x is "+x); } } 在这个程序中,我们将y定义在{}中,那么对于y来说,一旦出了这个{}意味着它将被释放,结束掉它的生存期 编译通不过,报以下错误 /* Test02.java:9: 错误: 找不到符号 y=x; ^ 符号: 变量 y 位置: 类 Test02 1 个错误 */
-
static修饰的成员包括方法都是静态的,在类加载的时候会被放在方法区中,可以先于对象而产生,这也是为什么对于静态方法我们不需要对象而可以直接调用的原因
-
关于变量定义的过程,在内存中都发生了什么呢?
找一段代码我们来研究一下
public class Person { private int status; private double income; private double tax; /* get...set.... */ } public class Test { public static void main(String[] args) { Scanner s = new Scanner(System.in); System.out.println("继续输入纳税人请输入1"); while(s.nextInt()==1) { Person p = new Person(); System.out.println("请输入纳税人类型:"); int index = s.nextInt(); p.setStatus(index); System.out.println("请输入收入:"); double in = s.nextDouble(); p.setIncome(in); System.out.println("纳税人需要缴纳的税额为" + p.money() + "¥"); System.out.println("继续输入纳税人请输入1"); } } }
内存一共被分为三块:栈,堆和方法区
-
在类加载的时候,class文件的代码片段会被加载到方法区中,就是将代码加载进去
-
然后main方法进栈(如果没有static{}代码块的时候)
-
代码块顺序执行
-
当遇到new 构造方法()的时候将进行如下操作:
-
在堆内存中分配空间(new负责做这件事)
-
初始化附值
-
填充属性
-
设置“this值”,即内存地址的值
-
例如(方法区中还存在static代码块)
其中关于变量名,他只是地址的一个引用,就像我们在c语言中用到的指针,它指向堆内存空间中的一块内存
如果我们把x定义为static变量呢?
public class Test02 {
static int x ;
public static void main(String[] args) {
int y = 9;
System.out.println("x is "+x);
System.out.println("y is "+y);
y=x;
System.out.println("x is "+x);
}
}
运行结果如下
/*
x is 0
y is 9
x is 0
*/
其实对于static修饰的变量和方法,在类加载的时候会被放入方法区中,这也是为什么我们无法在static方法中使用对象的属性,正是因为它先于对象产生,是类的属性和方法
之后对于他们的调用,我们可以直接在代码块中使用,并且可以直接用类名.方法名来调用,可以不用创建出对象,是不是很熟悉呢?没错,在System类中也有一个静态变量我们很熟悉那就是System.out.println(),而out就是System里面的一个静态数据成员,而且这个成员是java.io.PrintStream类的引用。被关键字static修饰的成员可以直接通过"类名.成员名"来引用,而无需创建类的实例。所以System.out是调用了System类的静态数据成员out。
- 构造方法
构造方法的方法名就是类名,,其实java的实例构造器只负责初始化,不负责创建对象,创建对象是由new指令完成的,这一点有点像c语言中的malloc,构造方法,或者说所有方法中都隐含了一个参数,这个参数是参数列表的第一个参数“this”,静态方法中不能使用this,而构造器中是可以用的,相当于指针一样。因此可以推测,构造方法不是静态方法,更像是类中的另一个特殊的组成部分。值得一提的是,如果自己写了构造方法,那么系统提供的默认的构造方法也就不能用了。
2 哈希值与toString方法
-
哈希值
public class Test02 { public static void main(String[] args) { System.out.println(args); } } /* D:\EditPlus\Test>java Test02 [Ljava.lang.String;@15db9742 */
打印出来的结果怎么会这样呢?
如果我们没有写toString方法,将其转化为String类型,那么将会打印出变量的哈希值,哈希值是JVM虚拟出来的地址,并不是真实的物理内存地址。哈希值是逻辑上的唯一性,而内存是物理上的唯一性。
-
hashcode()
public class Test02 { public static void main(String[] args) { System.out.println(args.hashCode()); } } /* 1836019240 */
public class ComAddr{ public static void main(String[] args) throws Exception { String s1 = "nihao"; String s2 = "nihao"; String s3 = new String("nihao"); System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); System.out.println(s3.hashCode()); } } /* 输出结果: 104818427 104818427 104818427 */
由上面三个例子我们是否可以猜测:哈希值是jvm根据物理地址生成的一段特有的,唯一的值;而hashcode()会根据对象的各个字段不同生成一个整数,就像数学函数中的一一对应一样,不同的值会生成不同的整数?
-
equals()
equals是根类Obeject中的方法。
public boolean equals(Object obj) { return (this == obj); }
可见默认的equals方法,直接调用==,比较对象地址。String类重写了equals(),判断字符串是否相等。String类中的equals首先比较地址,如果是同一个对象的引用,可知对象相等,返回true。如果不是同一个对象,equals方法挨个比较两个字符串对象内的字符,只有完全相等才返回true,否则返回false。
-
“==”
“==”对于基本数据类型的判断,就是比较他们的值
对于引用数据类型“==”比较的是他们的内存地址
String s1="abc"; String s2="abc"; String s3=new String("abc"); System.out.println("s1和s2 引用地址是否相同:"+(s1 == s2)); //true System.out.println("s1和s2 值是否相同:"+s1.equals(s2)); //true System.out.println("s1和s3 引用地址是否相同:"+(s1 == s3)); //false System.out.println("s1和s3 值是否相同:"+s1.equals(s3)); //true
对于这一段代码,s1==s2为true,我有一个猜想,不到一定对,关于方法区中的常量是否也有一个地址?
-
结论
关于哈希值的算法是否存在很多种?当我们调用hashcode()方法的时候得到的哈希值和直接打印出来的哈希值不一样,是不是说明了哈希值存在很多种计算方法。
关于比较元素的时候,其实是先比较hashcode是否相等,如果相等,那么将会继续使用equals方法对每一个区域进行比较,而“==”对于引用数据类型则会直接比较他们的内存地址。
- toString()
就像字面上的意思,toString()方法就是告诉jvm返回该对象的字符串表示,如果在该类中我们没有写toString(),那么我们在使用对象的引用的时候会返回一个哈希值,反之,则会根据toString()返回相应String类型,就像是文本一样。
public class Test02 {
public static void main(String[] args) {
Student stu = new Student();
System.out.println(stu);
}
}
/*
D:\EditPlus\Test>java Test02
Student@15db9742
*/
如果重写了toString()
public class Student
{
int id;
String name;
int age;
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'\'' +
'}';
}
}
public class Test02 {
public static void main(String[] args) {
Student stu = new Student();
System.out.println(stu);
}
}
/*
D:\EditPlus\Test>java Test02
Student{id=0, name='null', age=0'}
*/
3 未证明的问题
- 哈希值存在多种算法(查询后发现应该是这样)
- 方法区常量池中的常量是否也有地址?(度娘和博客没有相关答案,但是根据“==”我们有理由猜测方法区中的常量是有地址的)
后记:
- 关于JVM内存图不仅仅是被简单的分为三部分,其中涉及的内容非常丰富,我还将继续深入学习并作总结
- toString更像是一个规范,让JVM知道变量应该怎么来称呼,就像名字一样
- 方法区中的常量池确实是有地址的,已经的以证明。