Java入门笔记(五)java深入理解
变量及其传递
变量
- 基本数据类型与引用数据类型:基本数据类型的值直接存在变量中,也就是栈中的,而引用类型的变量占据一定的存储空间(指针),他引用的实体也要占据空间,是在堆中的
Person a = new Person();
Person b = a;
// b和a表示的是同一个实例的对象,即a和b都指向同一块存储空间
- 字段变量与局部变量:字段变量是对象的一部分,在堆中,随着对象的产生创建,可以自动赋初始值;局部变量是方法中定义的变量或者参变量,存在栈中,只有在方法调用的时候才被创建,必须显式赋值,不能够被访问控制符以及static修饰
变量的传递
java是值传递,对于引用型变量,传递的是引用值,而不是复制实体,但是可以通过引用改变对象属性
void modify(int a){ a++; }
void modify(int[] b) {b[0] ++; b = new int [5];}
void main(){
int a = 0;
modify(a); // 此时a仍旧为0
int b = new int [1];
modify(b); // 此时b[0]就是1了,因为modify中第二句的new其实是对b的复制的引用的修改,而不是当前的b
}
多态
编译时多态:重载
运行时多态:覆盖、动态绑定(虚方法调用)
上溯造型:把派生类型当做基本类型,如Person p = new Teacher();或者person做形参时传入Teacher
虚方法调用
- 虚方法调用:子类重载父类方法时,运行时系统根据调用该方法的实例的类型来选择哪个方法调用,此时所有的非final方法都会自动进行动态绑定,这就是虚方法调用的过程
Person{
sayHello(){}
}
Teacher extends Person{
sayHello(){ code1 }
}
Student extends Person{
sayHello(){ code2 }
}
Hello(Person p){ p.sayHello(); }
main(){
Hello(Teacher); // code1
Hello(Student); // code2
}
- 动态类型确定:instanceof可以用来判断是否是某个实例究竟是什么对象,返回boolean值,如果是当前类型或者是他的子类型,会返回true
void main(){
Object [] things = new Object[2];
things[0] = new Integer(4);
things[0] = new double(3.14);
if(things[0] instanceof Interger){} // true
if(things[1] instanceof double){} // true
}
- java的默认方法都是虚方法,static private的方法不是虚方法调用,他们与虚方法编译后用的指令是不同的,invokevirtual/invokespecial/invokestatic
(1) static方法一声明类型为准,与实例类型无关
(2) private方法子类不可见,不会被虚化
(3) final方法子类不能覆盖,不存在虚化问题
对象构造与初始化
构造方法
对象都有构造方法,如果没有,编译器会添加一个default构造方法,抽象类也是有构造方法的
this调用本类的其他构造方法、super调用父类的构造方法,但是this和super只能有一条,且要放在第一句。如果既没有this也没有super,编译器会自动加上父类的构造方法(优先使用默认构造方法,如果已有自定义构造方法就会调用自定义的构造方法)
class A{
A(int a){}
}
class B extends A{
B(String b){} // 报错,无法正确调用A的构造方法A(int a)
}
构造方法的执行过程
步骤:
-
调用本类或父类的构造方法,直到最高一层
-
按照声明顺序执行字段的初始化赋值
-
执行构造函数中的各语句
public class Test{
int a = 2000;
Test(){
this.a = 3000;
}
}
// 先调用object的super构造方法,然后执行int a = 2000;,最后执行构造函数中的语句
Q:如果构造方法中有虚方法怎么办?语法合法但是不合理,所以尽可能避免调用方法,唯一能够安全调用的是具有final属性的方法
初始化方法
-
创建对象时初始化:当没有构造函数却又要赋值的时候可以使用双括号Person p = new Person(){{age=18; name="李明" }};
-
实例初始化:在类中直接用写{语句},会先于构造方法中的语句执行
-
静态初始化:static {语句}第一次使用这个类时执行,执行时机,但是一定在实例初始化之前
public class Test{
Test(int a){
this.a = a;
System.out.println("构造方法初始化 a="+a);
}
int a;
{
System.out.println("实例初始化");
}
static{
System.out.println("静态初始化");
}
public static void main(String args[]){
Test test = new Test(4);
}
}

对象清除与垃圾回收
垃圾回收线程
java中,不需要认为使用delete清除对象,java可以自动清除,也就是垃圾回收
垃圾回收由java虚拟机的垃圾回收线程完成,任何对象都有一个引用计数器,当值为0时,对象就可以被回收 (很早期的算法了)
可以使用static方法System.gc()要求系统进行垃圾回收,也只是一个建议,没办法强制进行
finalize()方法
protected void finalize() throws Throwable{}
java中没有析构方法,但是Object的finalize有类似的功能,系统再回收时会自动调用对象的finalize()方法,一般来说,子类的finalize方法中应该调用父类的finalize方法,一般是不会用的
try-with-resource
关闭打开的文件、清除一些非内存资源等工作可以用try-with-resource,对于实现了java.lang.AutoCloseable的对象可以使用,最后会调用其close()方法
try(Scanner scanner = new Scanner(...)){
}
内部类 Inner class
定义
-
将一个类的定义嵌入到另一个类的内部,这个类就是内部类,生成xxx$xxx这样的class文件
-
在封装它的类的内部使用内部类则与普通类相同,在其他地方使用时需要冠上外部类的名字,也要在new前面冠以对象变量
public class Test{
int a = 2000;
public Inner in;
Test(){
this.a = 3000;
}
class Inner{
private int i;
Inner(int i){
this.i = i;
}
int value(){
return this.i;
}
}
void setIn(Inner in){
this.in = in;
}
void printValue(){
System.out.println(this.in.value());
}
public static void main(String args[]){
Test test = new Test();
Test.Inner in = test.new Inner(5);
test.setIn(in);
test.printValue();
}
}
-
内部中可以直接访问外部类的字段和方法,即便是private也可以
-
如果内部类和外部类有同名的字段或方法,则可以用
外部类名.this.字段/方法的写法
修饰符
-
内部类可以正常修饰,但是外部类要用public
-
如果用static修饰内部类,那么内部类其实是一种外部类,也有人认为是嵌套类(nested calss)
(1) 实例化static时,new前面就不需要对象实例了
(2)static内部类不能访问外部类中的非static字段和方法
(3)static方法中不能访问非static的域及方法,也不能够不带前缀地new一个非static的内部类
Outer.Inner io = new Outer.Inner();
局部类 local class
在一个函数中定义的类教方法中的内部类,也叫局部类
class Outer{
private int a = 100;
public Object makeTheInner(){
final int finalLocalVar = 99;
class Inner{
public String toString(){
return ("Inner class"+finalLocalVar);
}
}
return new Inner();
}
}
-
和局部变量一样,不可以用public private protected static修饰,但是可以用final或者abstract修饰
-
可以访问外部类的成员,但是不能访问该方法中的局部变量,除非是final局部变量(方法结束之后局部变量就消失了,但是final局部变量是只读的,可以预测的)
匿名类 anonymous class
局部类通常定义万了就使用,所以我们更多使用匿名类,匿名类在定义的同时就生成了该对象的一个实例,是一个一次性使用的类,一般用来扩展一个类或者实现一个接口
class Outer{
private int a = 100;
public Object makeTheInner(){
final int finalLocalVar = 99;
return new Object(){
public String toString(){
return ("Inner class"+finalLocalVar);
}
}
}
}
-
不取名字,直接使用父类或者接口的名字,编译器会生成xxxx$1之类的名字
-
new 类名或者接口名(){} -
构造对象时使用父类的构造方法,因为它没有名字,如果new 对象时要带参数,就用父类中这样的构造方法,一般很少带参数
-
典型应用
(1)注册一个事件监听器
(2)作为一个方法的参数,排序比较大小的接口Comparator
Lambda表达式
- 基本写法:(params) -> result,如(String s) -> s.length(),相当于其他语言的匿名函数或者函数指针,其实是匿名类的一个实例
// LambdaRunnable.java
Runnable doIt = new Runnable(){
public void run(){
System.out.println("aaa");
}
}
new Thread(doIt).start();
// ->
Runnable doIt = ()->System.out.println("aaa");
new Thread(doIt).start();
// ->
new Thread(()->System.out.println("aaa")).start();
实际上是接口或者接口函数的简写,要求这个接口包含且最多只能有一个抽象函数,这样的接口可以用注记@FunctionalInterface来表示,称为函数式接口
高级语法
装箱与拆箱
基本类型的包装类:Boolean Byte Character Integet Long Float Double
正常 Integer I = new Integer(10); 这样很麻烦
装箱boxing后就是 Integet I = 10;
拆箱unboxing就是 int i = I;
实际是
Integer I = Integer.valueOf(10);
int i = I.intValue();
枚举 enum
用法与其他语言的枚举相似
enum Light{Red, Yellow, Green};
Light light = Light.Red;
// 实际上等于 final class Light extends java.lang.Enum<Light>
可以自定义枚举
注解 annotation
所有注解都是java.lang.annotation.Annotation的子类
-
@Override - 覆盖父类方法
-
@Deprecated - 过时的方法
-
@SuppressWarnings - 让编译器不产生警告
其他
- 引用就是指针,但是他是受控的,安全的:检查空指引、没有指针运算、不能访问没有引用到的内容、自动回收垃圾
==
-
基本数据类型的是比较值,引用数据类型的是比较引用,如果要判断引用的内容是否一样,需要重写equals方法,如果重写equals方法,最好重写hashCode()方法
-
浮点数最好不要直接用==
-
Double.NAN == Double.NAN 结果是false
-
boolean类型和int类型无法比较
-
String判断相等要用equals,不要用==,因为String是引用,但是字符串常量一般是可以判断相等的
String hello = "Hello", lo = "lo";
System.out.println(hello == "Hello"); // true
System.out.println(hello == Other.hello); // true
System.out.println(hello == new String("Hello")); // false
System.out.println(hello == "Hel"+"lo"); // true
System.out.println(hello == "Hel"+lo); // false
System.out.println(hello == ("Hel"+lo).intern()); // true

浙公网安备 33010602011771号