java--流程控制、数组、面向对象相关备忘知识点

摘自《疯狂Java讲义》

数组
数组一旦完成初始化,大占用的空间大小是不可变的,即使吧数组中的数据清空

数组的内存分配
一个数组由两部分组成,一部分是数组引用,一部分是实际的数组对象(这部分在堆内存中,无法直接访问他,只能通过数组引用来访问)

foreach
foreach用来迭代的元素是一个临时变量。所以,如果想要改变集合或者数组中的值时,不能使用foreach

public class TestDomo {

    public static void main(String[] args){
        int[] array = {1,2,3,4,5};

        for(int i:array){
            i = 10;
        }

        System.out.println(Arrays.toString(array));
    }
}

输出为[1, 2, 3, 4, 5]

栈内存与堆内存
当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块内存里,随着方法的结束,这个方法的内存栈也随着销毁。,因此,所有在方法中定义的局部变量都是放在栈内存中的;在程序中创建的一个对象,这个对象将被保存到运行时数据区中,以便反复利用,这个运行时数据区就是堆内存。堆内存中的的对象不会随着方法的结束而销毁,即使方法结束,这个对象还可能被另一个应用变量锁引用。

java内存访问
java程序不允许直接访问堆内存中的对象,只能通过对象的引用操作该对象。
当程序访问应用变量的成员变量或者方法时,实际访问的是该引用变量所引用的数组,对象的成员变量或方法

方法的参数传递机制
值传递,将实际参数的副本传入方法内,而=参数本身没有发生任何变化。

public class TestDemo {

    public static void swap(int a,int b){
        int temp = a;
        a = b;
        b = temp;
        System.out.println("方法中的a=" + a +  " 方法中的b=" + b);
    }

    public static void main(String[] args){
        int a = 1,b = 2;
        swap(a,b);
        System.out.println("main中的a=" + a + " main中的b=" + b);
    }
}

输出结果
方法中的a=2 方法中的b=1
main中的a=1 main中的b=2

但是

public class TestDemo {

    public static void swap(Data data){
        int temp = data.a;
        data.a = data.b;
        data.b = temp;

        System.out.println("方法中的a=" + data.a +  " 方法中的b=" + data.b);
    }

    public static void main(String[] args){
        Data data = new Data();
        data.a = 1;
        data.b = 2;
        swap(data);
        System.out.println("main中的a=" + data.a + " main中的b=" + data.b);
    }

    static class Data{
        public int a;
        public int b;
    }
}

输出结果
方法中的a=2 方法中的b=1
main中的a=2 main中的b=1

为什么将参数改为引用类型时,这一结论会不成立呢?,原因是你将对象那个的应用传入方法,这个参数的引用和你main方法中的引用实际所引用的是同一个对象,是同一个内存区域,所以会出现这种情况。

可变参数位置
可变参数的位置只能处于参数列表的最后,也就是说,一个方法最多只能有一个可变参数

局部变量
局部变量除了形参之外都必须显示初始化
形参的初始化在调用该方法时由系统完成

成员变量初始化和内存中的运行机制
当系统加载类或者创建该类的对象时,系统自动为成员变量分配内存,并在分配内存空间后,自动为成员变量指定初始值。

局部变量的初始化和内存中的运行机制
局部变量定义后,必须经过显示的初始化。系统不会为局部变量执行初始化,这意味着定义局部变量后,系统并未为这个变量分配内存,直到程序为这个局部变量赋初始值,系统才会分配内存,并将初始值保存在这快内存
栈内存中的变量无须系统垃圾回收,因为随着方法或者代码块的运行结束而结束

构造器
当程序调用构造器时,系统会先为该对象分配内存空间,并未这个对象执行默认初始化,这个时候对象已经产生了——这些操作在构造器执行之前就完成了。当系统开始执行构造器之前,系统已经创建了一个对象,只是这个时候还不能被外界访问,只能在该构造器通过this来引用,当构造器执行完之后,这个对象作为构造器的返回值被返回。

this构造器
使用this调用另一个重载构造器只能在构造器中使用,而且必须作为执行体的第一条语句

查找变量的顺序
如果在方法中访问名为a的变量,但是没有显示指定调用者,则系统查找a的顺序为:

  1. 查找该方法中是否有名为a的局部变量
  2. 查找当前类中是否有名为a的成员变量
  3. 查找a的直接父类中是否有名为a的成员变量,依次上溯a的所有父类,这道object

当程序创建一个对象时,系统不仅会为该类中定义的实例变量分配内存,也会为他从父类继承得到的所有实例变量分配内存,即使子类定义了与父类中同名的实例变量

如果在子类中定义了与父类同名的成员变量,则子类中定义的成员变量会隐藏父类中定义的成员变量,为了在子类中访问父类中定义的被隐藏的实例变量,或为了在子类方法中调用父类中定义的,被覆盖的方法,可以通过super.作为限定来调用这些实例变量和实例方法。

在构造器中使用super
使用super调用父类构造器必须出现在子类构造器执行体的第一行,所以this和super调用不能同时出现。

引用变量
引用变量在编译阶段只能调用其编译时的类型所具有的方法,但是运行时则执行他运行时类型所具有的方法,因此,编写java代码时,引用类型只能调用声明该变量时所用的类里包含的方法。
通过引用类型变量来访问其包含的实例变量时,系统总是试图访问他编译时类型所定义的成员变量,而不是它运行时的类型所定义的成员变量

初始化块
当创建java对象时,系统总是先调用该类里定义的初始化块,如果一个类定义了2个普通初始化块,则按定义顺序执行,而且是在构造器之前

当java创建一个对象时,系统先为该对象所有实例变量分配内存(前提是该类已经加载过了),接着程序开始对这些实例变量执行初始化,顺序是:先执行初始化块或者声明实例变量时指定的初始值(这两个顺序为定义的顺序),再执行构造器里指定的初始值。

实际上初始化块是一个假象,使用javac编译java类后,该java类中的初始化块会消失,初始化块中的代码被还原到构造器中,且位于构造器的前面

静态初始化块
系统将在类初始化阶段执行及静态初始化块,不仅会执行本类的静态初始化块,而且还会一直上溯到object,静态初始化块比普通初始化块先执行

java类加载
java系统加载并初始化类,总是保证该类的所有父类(直接父类和间接父类)全部加载并初始化
当JVM第一次主动使用某个类时,系统会在类的准备阶段为该类的所有静态变量分配内存,在初始化阶段则负责初始化这些静态成员变量,初始化静态成员变量就是执行类初始化代码或者声明类成员变量的初始值。

自动装箱问题
Integer

public class Test{

    public static void main(String[] args){
        Integer i1 = 2;
        Integer i2 = 2;
        System.out.println(i1 == i2);

        Integer b1 = 128;
        Integer b2 = 128;
        System.out.println(b1 == b2);
    }

}

输出结果:
true
false
问题,2自动装箱后相当,128自动装箱后不等
原因:
Integer源码

 private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
        }

        private IntegerCache() {}
    }

系统吧一个-128~127之间的整数自动装箱成Integer实例,并放如一个名为cache的数组中缓存起来,若以后把一个-128~127之间的整数自动装箱成一个Integer实例时,实际上是直接指向对应的数组元素,因此-128~127之间的同一个整数自动装箱Integer实例时,永远引用的是cache数组中的同一个数组元素,但每次把一个不在-128~127范围内的整数自动装箱成Integer实例时系统总是重新创建一个Integer实例。
java7增强了包装类的功能,java7为所有包装类都提供了一个静态的compare(XXX val1,XXX val2)方法,比较两个基本类型值的大小

==和equals问题
java程序中测试两个变量是否相等有两种方式:一种是利用==,另一种是equals()方法,当使用==来判断两个对象是否相等时,如果两个变量是基本类型变量,且都是数值类型,则两个变量的值相等,返回true,但是对于两个引用类型的变量,只有他们同时指向同一个对象时,==判断才会返回true。==不可以用于比较类型上没有父子关系的两个对象。

public class Test {

    public static void main(String[] args) {
        int it = 65;
        float f = 65.0f;
        System.out.println(it == f);

        char a = 'A';
        System.out.println(it == a);

        String str1 = new String("hello");
        String str2 = new String("hello");

        System.out.println(str1 == str2);

        System.out.println(str1.equals(str2));
    }

}

关于String
当java程序直接使用形如”hello”这样的字符串直接量(包括可以在编译时就计算出来的字符串值)时,JVM将会使用常量池来管理这些字符串;当使用new String(“hello”)时,JVM先使用常量池来管理”hello”直接量,再调用String类的构造器来创建一个新的String对象,新创建的String对象被保存在堆内存中,也就是说,new String(“hello”)一共产生了两个字符串对象

ps;常量池专门用于管理在编译时被确定并保存在已编译的.class文件中的一些数据。包括关于类,方法,接口中的常量,还包括字符串创常量。

JVM常量池保证相同的字符串直接常量只有一个,不会产生多个副本

public class Test {

    public static void main(String[] args) {
        String s1 = "123456";
        String s2 = "123";
        String s3 = "456";
        String s4 = "123" + "456";
        String s5 = "12" + "3" + "456";
        String s6 = s2 + s3;
        String s7 = new String("123456");

        System.out.println(s1 == s4);
        System.out.println(s1 == s5);
        System.out.println(s1 == s6);
        System.out.println(s1 == s7);
    }

}

输出结果:
true
true
false
false
s1,s4,s5所引用的字符串在可以编译期就确定下来,因此他们都将引用常量池中的同一个字符串对象。,使用new String()创建的对象是运行时创建出来的,保存在运行时的内存区,不会放入常量池中

equals方法与==没有区别,同样要求两个引用变量指向用一个对象才返回true,可以通过重写equals方法来达到自定义的判断逻辑

static初始化块只执行一次
一旦类初始化结束,静态初始化块将永远不会获得执行的机会

public class Father {
    static {
        System.out.println("Father");
    }

    public Father(){
        System.out.println("Father构造器");
    }
}
public class Son extends Father {
    static {
        System.out.println("Son");
    }

    public Son(){
        System.out.println("Son构造器");
    }

    public static void main(String[] args){
        for(int i = 1;i<=2;i++){
            new Son();
        }
    }
}

Father
Son
Father构造器
son构造器
Father构造器
son构造器

final
final修饰的成员变量必须有程序显示的指定初始值。系统不会对final成员进行隐式的初始化
系统不会对局部变量进行初始化,局部变量必须由程序员显示的初始化,因此使用final修饰局部变量时,既可以在定义时指定默认值,也可以不指定默认值

宏替换
final变量为直接常量必须满足:

  1. 使用final修饰
  2. 在定义final变量时指定了初值
  3. 该初始值可以在编译时就被确定
public class Test {

    public static void main(String[] args) {
        final int a = 5;
        System.out.println(a);
        //对于这个程序时候,变量a其实根本不存在,与执行 System.out.println(5);相同
    }

}

final修饰符的一个重要用途就是定义“宏变量”。当定义final变量时就为该变量指定了初始值,而且该初始值可以在编译时就确定下来,则这个final变量本质上就是一个宏变量,编译器会把程序中的所有用到该变量的地方直接替换成该变量的值。如果被赋值的表达式只是基本的算术表达式或字符串连接运算,没有访问普通变量,调用方法,java编译器同样会将这种final变量当成宏变量来处理。

public class Test {

    public static void main(String[] args) {
        String s1= "123456";
        String s2 = "123" + String.valueOf(456);
        System.out.println(s1 == s2);
        System.out.println(s1 .equals(s2) );

        String str1 = "123";
        String str2 = "456";

        String s4 = "123" + "456";
        String s3 = str1 + str2;

        System.out.println(s1 == s3);
        System.out.println(s1 .equals(s3) );

        System.out.println(s1 == s4);
        System.out.println(s1 .equals(s4) );
    }

}

输出结果
false
true
false
true
true
true
s4是有两个字符串直接量进行连接计算,由于在编译器可以编译阶段就确定值,所以会与s1执行形同的常量池字符串。s3,他由两个变量进行连接运算得到编译器不会执行宏替换,因此s3无法指向常量池总的字符串对象

ps:对于final实例变量而言,只有在定义该变量时指定初始值才会有宏变量的效果

定义一个不可变的类规则

  1. 使用private和final修饰符来修饰该类的成员变量
  2. 提供带参数构造器,用于根据传入参数初始化类里的成员变量
  3. 仅为该类的成员提供getter方法,不要为该类的成员变量提供setter方法

Integer的valueOf方法
如果使用new构造器来创建Integer对象,则每次返回全新的Integer对象,如果采用valueOf方法来创建Integer对象,则会缓存该方法创建的对象

public class Test {

    public static void main(String[] args) {
        Integer i1 = new Integer(6);
        Integer i2 = Integer.valueOf(6);

        Integer i3 = Integer.valueOf(6);

        System.out.println(i1 == i2);
        System.out.println(i2 == i3);

        Integer i4 = Integer.valueOf(200);
        Integer i5 = Integer.valueOf(200);
        System.out.println(i4 == i5);
    }

}

false
true
false

非静态内部类
当在非静态内部类的方法内访问某个变量时,系统优先在该方法内查找是否存在该名字的局部变量,如果存在就使用该变量,如果不存在,则到该方法所在的内部类中查找是否存在该名字的成员变量,如果存在则使用该变量,如果不存在,则到该内部类所在的外部类中查找是否存在该名字的成员变量。
如果外部类成员变量,内部类成员与内部类里方法的局部变量同名,则通过使用this,外部类类名.this作为限定区分

非静态内部类与外部类的关系
非静态内部类对象必须寄生在外部类对象里,而外部类对象则不一定有非金泰内部类对象寄生其中。如果存在一个非静态内部类对象,则一定存在一个被他寄生的外部类对象。但外部类对象存在时,外部类对象里不一定寄生了非静态内部类对象。因此,外部类对象访问非静态内部类成员时,可能非静态内部类对象根本不存在,而非静态内部类对象访问外部类对象时,外部类对象一定存在。

静态内部类不能访问外部类实例属性
静态内部类是个外部类的类相关的,而不是外部类的对象相关的,静态内部类对象不是寄生在外部类的实例中,而是寄生在外部类的类本身中。当静态内部类对象存在时,不一定存在被他寄生的外部类对象,静态内部类对象值持有外部类的类引用,没有持有外部类对象的引用

非静态内部类子类创建对象问题
非静态内部类的子类不一定是内部类,可以是一个外部类,但非静态内部类的子类实例一样需要保留一个引用,该引用指向其父类所在外部类的对象。如果有一个内部类子类的对象存在,则一定存在与之对应的外部类对象。

public class Out {

    class In{

    }
}
public class Test extends Out.In{


    public Test(Out out){
        out.super();
    }

}

内部类重写问题
定义外部类的子类,并在子类中重写外部类的内部类是不允许的
内部类的类名不再是简单的由内部类的类名组成,他实际上是把外部类的类名最为=作为一个命名空间,作为内部类类名的限制,因此,在子类中的内部类和父类中的内部类不可能完全相同,即使二者所包含的内部类的类名相同,但因为他们所处于的外部类空间不同,所以他们不可能完全同名

匿名内部类的局限性
匿名内部类必须继承一个父类,或者实现一个接口,做多只能继承(实现)一个类(接口)
匿名内部类不能是抽象的,因为系统创建匿名内部类时,会立即创建匿名内部类的对象
匿名内部类不能定义构造器,由于匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义初始化块,可以通过实例初始化块来完成构造器需要完成的事情。
当通过实现接口来创建宁内部类时,匿名内部类也不能显示的创建构造器,因此匿名内部类只有一个隐式的无参数构造器,故new接口名的括号里不能传入参数值。如果通过继承父类创建匿名内部类时,匿名内部类将拥有和父类相似的构造器,此处的相似指的是拥有相同的形参列表
java8之前,java要求局部内部类,匿名内部类访问的局部变量必须使用final修饰,从java8开始,这个限制取消了。如果局部变量被匿名内部类访问,则该变量相当于自己使用了final修饰,但是该变量必须按照final修饰的方式来使用(这个变量可以用fianl修饰,也可以不用)

Lambda表达式
限制

  1. 目标类型必须是明确的函数式接口
  2. 只能为函数式接口创建对象。Lambda表达式只能实现一个方法,因此他只能为只有一个抽象方法的接口(函数式接口)创建对象

Lambda表达式将会别当做任意类型的对象

函数式接口:只包含一个抽象方法的接口,函数式接口可以包含多个默认方法、类方法,但是只能声明一个抽象方法

ps:Lambda表达式完全可能是变化的,唯一要求是,Lambda表达式实现的匿名方法与目标类型(函数式接口)中唯一的抽象方法有相同的形参列表

枚举的特点

  1. 枚举类可以实现一个或多个接口,使用enum定义的枚举默认继承java.lang.Enum类,而不是Object类,因此枚举类不能显示继承其他父类,其中Enum类实现了Serializable接口和Comparable接口
  2. 使用enum定义、非抽象的枚举类迷人会使用final修饰,因此枚举类不能派生子类
  3. 枚举类的构造器只能使用private修饰,如果省略了,默认是private
  4. 枚举类的所有实例必须在枚举类的第一行显示列出,否则这个枚举类将永远不能产生实例

枚举类里定义抽象方法时,不能使用abstract关键字将枚举类定义成抽象的(因为系统自动添加abstract关键字)

垃圾回收特点

  1. 只负责回收堆内存中的对象,不会回收任何物理资源
  2. 无法精确控制垃圾回收的运行,垃圾回收会在适合的时候进行,当对象永久性的而失去引用后,系统就会在合适的时候回收他的内存
  3. 在垃圾回收任何对象之前,总会先调用finalize方法,该方法可能是该对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收

finalize方法的特点

  1. 永远不要主动调用某个对象的finalize方法,该方法应交给垃圾回收机制调用
  2. 该方法何时被调用,是否内调用具有不确定性
  3. 当JVM执行可恢复对象的finalize方法时,可能是该对象或系统中的其他对象重新变成可达状态
  4. 当JVM执行finalize方法时出现异常,垃圾回收机制不会报告异常,程序继续执行。

对象的引用种类

  1. 强引用:可达状态不可能被系统垃圾回收机制回收
  2. 软引用:当系统内存空间足够时,不会内回收,当系统空间不足时,系统有可能回收他。常用于对内存敏感的程序中
  3. 弱引用:当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象的内存
  4. 虚引用

ps:要使用这些特殊的引用类,就不能保留对对象的强引用,如果保留看对对象的强引用,就会浪费这些引用所提供的任何好处

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值