1 this关键字

1.1 this关键字引入

先看一段代码

package java_test2;
/*
* 2019/7/24
* this关键字相关测试
* */
class Banana{
void peel(int i) {
//do something
}
}
public class BananaPeel {
public static void main(String[] args) {
Banana a=new Banana(),
b=new Banana();
a.peel(1);
b.peel(2);
}
}

peel方法是怎么知道剥的哪一个香蕉的皮呢?实际上编译器暗自做了一些工作,它吧“所操作对象的引用”作为参数传递给了peel()方法。让上述两个方法变成了这样

Banana.peel(a,1);
Banana.peel(b,2);

所以如果我们想在方法内部获得当前对象的引用,但是我们直到程序执行才知道这个引用的标识符怎么办呢,这时候就要使用this关键字,this关键字:this关键字只能在方法内部使用,表示对"调用方法的那个对象"的引用。

上述代码又变成了这样

Banana.peel(this,1);
Banana.peel(this,2);

1.2 在构造器中调用构造器

说白了就是this.s=s;

1.3 static的含义

注意下面几点就行了

  • 在static方法中只能调用static方法。
  • static方法具有全局函数的语义,有些人认为他不是“面相对象”的。

2 初始化与清理复习笔记-垃圾回收器

2.1 对象存活判定算法

  • 引用计数法:给对象添加一个引用计数器,每当一个地方引用他时,计数器值就加一,当引用失效时,计数器就减一;任何时候计数器为0的对象就是不可能再被使用的(缺点–很难解决对象之间的相互循环引用问题;主流的Java虚拟机并没有采用引用计数法管理内存)
  • 可达性分析算法:通过一系列成为“GC Roots”的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连接(用图论的说法是,从GC到这个对象不可达),证明此引用是不可用的(另一种解释是《Java 编程思想》对任何活的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用,那么我们可通过遍历在堆栈或静态存储区的引用找到所有活的对象,然后再找到这些对象包含的所有引用,如此反复。)

2.2 再谈引用

在JDK 1.2 之后Java 将引用分为四种

  • 强引用–Object object=new Object()
  • 软引用–内存不够的时候清理,如果还不够抛出异常–SoftReference类实现软引用
  • 弱引用–无论内存够不够,清理 只被弱引用关联的对象–WeakReference类实现弱引用
  • 虚引用–PhantomReference类实现

可达性分析中的不可达对象也并非是非死不可, 一个对象被判定为不可达对象实际上是一种缓刑,他将会被标记并进行一次筛选,筛选的条件就是是否有必要执行finalize()方法,当这个对象没有覆盖finalize()方法或者他的finalize方法已经执行,他就被认为是没必要执行(就是没必要活下去了),相反则将他放在了一个F-Queue队列中,并由finalizer线程去执行他,GC会对其进行标记,如果他想活下去就必须与引用关联,否则就活不下去

  • 值得注意的是,一个对象只能通过他自己的finalize()方法自救一次,下一次遇到GC就死了

回收方法区

  • 废弃常量:常量池中没有被任何引用过的常量
  • 无用类:(1)该类的所有实例已经被回收,就是Java堆中不存在该类的任何实例(2)加载该类的类加载器(ClassLoader)已经被回收(3)该类对应的java.lang.Class对象没有在任何地方被引用,无任何方法通过反射访问该类的方法

垃圾收集算法

年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区)。
年轻代:对象被创建时(new)的对象通常被放在Young(除了一些占据内存比较大的对象),经过一定的Minor GC(针对年轻代的内存回收)还活着的对象会被移动到年老代(一些具体的移动细节省略)。
年老代:就是上述年轻代移动过来的和一些比较大的对象。Minor GC(FullGC)是针对年老代的回收.
永久代:存储的是final常量,static变量,常量池。
-Xmx:最大堆大小
-Xms:初始堆大小
-Xmn:年轻代大小
-XXSurvivorRatio:年轻代中Eden区与Survivor区的大小比值(这个是与其中一块survivor的比值大小)

年轻代:对象被创建时(new)的对象通常被放在Young(除了一些占据内存比较大的对象),经过一定的Minor GC(针对年轻代的内存回收)还活着的对象会被移动到年老代(一些具体的移动细节省略)。

年老代:就是上述年轻代移动过来的和一些比较大的对象。Minor GC(FullGC)是针对年老代的回收

永久代:存储的是final常量,static变量,常量池。

年轻代:对象被创建时(new)的对象通常被放在Young(除了一些占据内存比较大的对象),经过一定的Minor GC(针对年轻代的内存回收)还活着的对象会被移动到年老代(一些具体的移动细节省略)。

年老代:就是上述年轻代移动过来的和一些比较大的对象。Minor GC(FullGC)是针对年老代的回收

永久代:存储的是final常量,static变量,常量池。

  • 标记-清除算法:标记所有需要回收的对象(需要被回收的对象和 活的对象好像不是一样的),在标记完成后统一回收所有被标记的对象–不足:(1)效率问题,标记和清除两个过程的效率都不高(2)另一个是空间问题,标记清楚之后会产生大量不连续的碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配大量的对象时,无法找到连续的内存而不得不七千出发另一次垃圾收集动作
  • 复制算法:(1)分成两块大小相同的两块,当一块的内存使用完了,就将其上活的对象复制到另一块上面,然后把之前的内存空间一次清理掉–不足:这种算法的代价是将内存缩小为原来的一半,代价过高(2)通过新生代(Eden)+2*survivor+老年代, Eden 80% survivor 10% 老年代作为担保(公式 Eden+S1=S2+(老年代))
  • 标记-整理算法:和标记-清除算法一样,不过后续步骤是让所有存活对象都向一端移动,然后直接清理掉端边界意外的内存
  • 分代收集算法:新生代(对象存活率低 复制算法)+老年代(对象存活率高 标记-清除/标记-整理)

基本enum特性

enum的一些基本方法

通过下面的例子演示enum提供的一些功能(摘抄自《java编程思想》)

import static net.mindview.util.Print.*;

enum Shrubbery{
GROUND,
CRAWLING,
HANGING
}

public class EnumClass {
public static void main(String[] args) {
for(Shrubbery s:Shrubbery.values()) {
print(s+"ordinal:"+s.ordinal());
printnb(s.compareTo(Shrubbery.CRAWLING)+" ");
print(s==Shrubbery.CRAWLING);
print(s.getDeclaringClass());
print(s.name());
print("-----------------");
}

for(String s:"HANGING CRAWLING GROUND".split(" ")) {
Shrubbery shrub=Enum.valueOf(Shrubbery.class, s);
print(shrub);
}
}
}

上述代码输出结果

GROUNDordinal:0
-1 false
class enumTest.Shrubbery
GROUND
-----------------
CRAWLINGordinal:1
0 true
class enumTest.Shrubbery
CRAWLING
-----------------
HANGINGordinal:2
1 false
class enumTest.Shrubbery
HANGING
-----------------
HANGING
CRAWLING
GROUND

(1)values()

values()方法返回enum实例的数组,所以可以通过foreach语句实现enum实例的遍历。

(2)ordinal()

ordinal()方法返回一个int 值,这是每个实例在声明时的次序,从零开始。

(3)compareTo()

这个方法返回两个enum实例的ordinal()值的差值,返回int值。

(4)==

用==来比较enum实例,它会自动调用equals()方法和hashCode()方法,返回一个布尔值。

(5)name()

name()返回enum实例声明时候的名字。

(6)valueOf()

根据给定的名字返回相应的enum实例,如果不存在给定名字的实例,就会抛出异常。

将静态导入用于enum

这个使用的很少。。。

向enum中添加新方法

除了不能继承自一个ebum之外,我们基本上可以就将enum视为一个常规的类。我们可以向enum中添加方法,enum中甚至可以有main()方法。

需要注意的地方

(1)如果需要在enum中添加方法或属性,那么就必须在enum实例序列最后添加一个分号;

(2)java要求你必须先定义enum实例,如果你定义的方法或属性出现在enum实例之前,那么在编译时就会得到错误信息;

(3)在enum中可以有main方法,甚至构造器,但是其构造器不能为public,一般是使用private,其实这种权限修饰没有什么作用,我们只能在enum定义的内部使用其构造器。

下面通过代码演示:

public enum OzWitch {
WEST("白日依山尽"),
NORTH("黄河入海流"),
EAST("欲穷千里目"),
SOUTH("更上一层楼");

private String description;

private OzWitch(String description) {
this.description=description;
}

public String getDescription() {
return description;
}

public static void main(String[] args) {
for(OzWitch s:values()) {
System.out.println(s+":"+s.getDescription());
}
}
}

输出结果如下:

WEST:白日依山尽
NORTH:黄河入海流
EAST:欲穷千里目
SOUTH:更上一层楼

有关括号后面的值以及这里的description是什么意思?可以发现这个description是String类型,和括号里面的类型一致,而上面程序输出结果输出description的时候,直接就输出了括号里面的值这里不详细说,因为我也不特别熟悉,这里给一个超链接,可以自己查阅。​​括号赋值详解​

覆盖enum的方法

下面是覆盖toString()方法的代码:

public enum SpaceShip {
SCOUT,CARGO,TRANSPORT,CRUISER,BATTLESHIP,MOTHERSHIP;

public String toString() {
String id=name();
String lower=id.substring(1).toLowerCase();
return id.charAt(0)+lower;
}

public static void main(String[] args) {
for(SpaceShip s:values()) {
System.out.println(s);
}
}
}

上面的代码重写了toString()方法,目的是取得SpaceShip的名字,然后以首字母大写其他字母小写的方式输出

(1)public String substring(int beginIndex),一般用于返回一个新的字符串,它是此字符串的一个子字符串。该子字符串始于

指定索引处的字符,一直到此字符串末尾。

(2)charAt(int index)方法返回字符串的第index个字符。

(3)通过+将这个字符和id的子字符串拼接,达到目的。

初始化与清理复习笔记-问题摘要

  • 为什么要进行初始化和清理?
    初始化:许多C程序的错误都源于程序员忘记初始化变量,如果在用变量之前没有进行初始化,那么就会报错。清理:当使用完一个元素后,他对你已经没有任何影响了,如果你对它不进行清理,这些无用元素所占用的内存空间就会越来越大,直至内存资源用尽。
  • 用构造器确保初始化
    在创建一个对象时,将会为其叉分配内存空间,对于类中的每一个域,会将其初始化为二进制的零。然后调用相应的构造器。值得注意的是构造器名称和类名称完全相同,所以“每个方法名称必须首字母小写”的编码风格并不适用于构造器。
  • 构造器的重载
    1)参数顺序不同可以区分两个构造器(或者方法),但是这么用会使代码难以维护。
    2)涉及基本类型的重载我们知道char和int等数据类型在内存中占用的空间大小是不一样的,当参数列表类型“大”,而实际传入的类型“小”的时候,也就是小到大,在传入时实际参数类型被提升至“大”(对于char类型有所不同,他会直接提升至int类型然后在按照int类型提升);如果传入的实际参数较大,就只能通过类型转换执行窄化转换,如果不这么做编译器就会报错。
    3)返回值能否作为区分重载的方法呢?答案是不行的,查看下面的代码
void f() {
//do something1
}
int f() {
//do something2
return 1;
}

当我们使用 int x=f();我们的确能区分这里的f()方法是下面的一个。但是当我们需要的是一个方法的副作用而不是他的返回值,比如我需要使用f()来做something1,而我只写了f(),那么编译器怎么知道不去做something2而去做something1呢?所以 ,返回值来区分重载方法是行不通的

  • 默认构造器:比如我创建了一个类A,假如我们没有为他创建一个构造器,那么编译器会为我们创建一个无参构造器,而当我们创建了一个带参数的构造器,并没有主动创建一个无参构造器,编译器并不会为我们创建一个无参构造器,那么这时候如果我们使用 new A(),编译器就会报错:“没有找到匹配的构造器”
  • 关于泛型接口泛型类泛型方法的概念 关于Pattern的用法,官方给出的用法是:Pattern p = Pattern.compile(“a*b”);
  • Matcher m = p.matcher(“aaaaab”); boolean b = m.matches();这三句,
    第一句:将给定的正则表达式编译为模式,第二句:创建一个匹配器,匹配给定的输入与此模式。第三句:尝试将整个区域与模式进行匹配;
  • 静态数据的初始化:static不能应用于局部变量,因此他只能作用于域,如果一个域是静态的基本类型域,且没有对他进行初始化,那么他就是获得基本类型的标准初值;如果他是一个对象引用就会被置为null,关于实例化构造器之后类内部的行为以后专门写。。
  • 显示的静态数据初始化:如果一个类中用一个静态块的形式初始化了一些对象,那么当其他类调用这些对象而没有实例化该类对象的时候,并不会执行构造器的相关代码,而只会执行静态块的对象实例化(看不懂看书P97)
  • 非静态实例初始化:先初始化0、null,再初始化、再构造器