牛客网刷题笔记
练练不忘,必有回响。
垃圾收集
以前我是堆,你是栈
你总是能精准的找到我,给我指明出路
后来有一天我明白了
我变成了栈,你却隐身堆海
我却找不到你了,空指针了
我不愿意如此,在下一轮 full gc 前
我找到了 object 家的 finalize
又找到了你,这次我不会放手
在世界重启前,一边躲着 full gc 一边老去
一个对象成为垃圾,是因为没有引用指向它
一个对象成为垃圾之后,等待垃圾回收器收集
finalize方法也可以主动使用
在java中,对象的内存在哪个时刻回收,取决于垃圾回收器何时运行
一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法, 并且在下一次垃圾回收动作发生时,才会真正的回收对象占用的内存(《java 编程思想》)
在C++中,对象的内存在哪个时刻被回收,是可以确定的,在C++中,析构函数和资源的释放息息相关,能不能正确处理析构函数,关乎能否正确回收对象内存资源。
在java中,对象的内存在哪个时刻回收,取决于垃圾回收器何时运行,在java中,所有的对象,包括对象中包含的其他对象,它们所占的内存的回收都依靠垃圾回收器,因此不需要一个函数如C++析构函数那样来做必要的垃圾回收工作。当然存在本地方法时需要finalize()方法来清理本地对象。在《java编程思想》中提及,finalize()方法的一个作用是用来回收“本地方法”中的本地对象
编译
- Java在运行时才进行翻译指令
- Java程序在运行时字节码才会被JVM翻译成机器码
对象序列化
- 使用transient修饰的变量不会被序列化
- 对象序列化的所属类需要实现Serializable接口
- 能够对对象进行传输的貌似只有ObjectOutputStream和ObjectInputStream这些以Object开头的流对象
- transient 修饰的变量在对象串化的时侯并不会将所赋值的值保存到传中,串化的对象从磁盘读取出来仍然是null
面向对象程序设计方法优点
- 可重用性 多态
- 可扩展性 继承
- 易于维护与管理 封装
内部类
class Enclosingone {
public class InsideOne {}
}
public class inertest {
public static void main(string[]args) {
EnclosingOne eo = new EnclosingOne();
//insert code here
}
}
可以为
InsideOne ei = eo.new InsideOne();
// Or
EnclosingOne.InsideOne ei = eo.new InsideOne();
内部类其实和类的属性没什么区别,只是在声明的时候必须是Outer.Inner a,就像int a 一样,
静态内部类
Outer.Inner a = new Outer.Inner();
// 要把Outer.Inner看成一部分,就像类变量一样
非静态内部类
Outer.Inner a = new Outer().new Inner();
// 非静态,先有Outer对象才能有属性
Switch
- char 和 Character
- byte 和 Byte
- short 和 Short
- int 和 Integer
- Enum 枚举
- String Java1.7以后
ArrayList vs LinkedList
Object obj=new Object();
List aList=new ArrayList();
List bList=new LinkedList();
long t1=System.currentTimeMillis();
for(int i=0;i<50000;i++){
aList.add(0,obj);
}
long t2=System.currentTimeMillis()-t1;
t1=System.currentTimeMillis();
for(int i=0;i<50000;i++){
bList.add(0,obj);
}
long t3=System.currentTimeMillis()-t1;
ArrayList内部是动态数组实现,在增加空间时会复制全部数据到新的容量大一些的数组中。而LinkedList内部为双向链表,可以按需分配空间,扩展容量简单,因此LinkedList用时少。
所以t2 > t3
String
String str =
"";
System.out.print(str.split(",").length);
split 这个方法默认返回一个数组。如果没有找到分隔符,会把整个字符串当成一个长度为1的字符串数组。
因为是数组,所以是.length
输出结果为:
1
多线程
以下哪种JAVA得变量声明方式可以避免程序在多线程竞争情况下读到不正确的值( )
- volatile √ volatile保证读写原子性,不保证更新(读=>改变值=>写)原子性
- static volatile √
- synchronized × synchronized不是修饰变量的 它修饰方法或代码块或对象
Math
double d1 =- 0.5;
System.out.println("Ceil d1="+Math.ceil(d1));
System.out.println("floor d1="+Math.floor(d1));
ceil:大于等于 x,并且与它最接近的整数。
floor:小于等于 x,且与 x 最接近的整数。
Ceil d1 = -0.0;
floor d1 = -1.0;
包装类
基本数据类型 | 包装类 | 大小 |
---|---|---|
byte | Byte | 8bit |
boolean | Boolean | - |
char | Character | 16bit |
short | Short | 16bit |
int | Integer | 32bit |
long | Long | 64bit |
float | Float | 32bit |
double | Double | 64bit |
HashMap vs Hashtable
- Hashtable 已被淘汰,可以使用 ConcurrentHashMap 替代,这一点可以在 Hashtable 源码中的注释说明中找出。
- Hashtable 的 key、value 都不可以为 null。HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。
- HashMap和Hashtable都实现了Map接口。
- 相比 HashMap,Hashtable 是线程安全的。因为 Hashtable 的几个重要方法都加上了 synchronized 关键字。
- Hashtable、HashMap 都使用了 Iterator。而由于历史原因,Hashtable 还使用了 Enumeration 的方式。
- HashMap允许将 null 作为一个 entry 的 key 或者 value,而 Hashtable 不允许。
- Hashtable 直接使用对象的 hashCode。而 HashMap 重新计算 hash 值。
- Hashtable 中的 hash 数组初始大小是 11,增加的方式是 old*2+1。HashMap 中 hash 数组的默认大小是 16,而且一定是 2 的指数。
- HashMap 有 containsvalue 和 containsKey 两个方法,Hashtable 使用的是 contains 方法。
public class Hashtable extends Dictionary implements Map
public class HashMap extends AbstractMap implements Map
包
包(package)由一组类(class)和接口(interface)组成(有其中一个即可)
一个.java文件中定义多个类
- public权限类只能有一个(也可以一个都没有,但最多只有一个)
- 这个.java文件名只能是public 权限的类的类名
- 倘若这个文件中没有public 类,则它的.java文件的名字是随便的一个类名
- 当用javac命令生成编译这个.java 文件的时候,则会针对每一个类生成一个.class文件
访问权限
修饰符 | 类内部 | 同一个包 | 子类 | 任何地方 |
---|---|---|---|---|
private | Yes | |||
default/friendly | Yes | Yes | ||
protected | Yes | Yes | Yes | |
public | Yes | Yes | Yes | Yes |
缓存
Integer i01 = 59;
int i02 = 59;
Integer i03 = Integer.valueOf(59);
Integer i04 = new Integer(59);
System.out.println(i01 == i02); // 1
System.out.println(i01 == i03); // 2
System.out.println(i03 == i04); // 3
System.out.println(i02 == i04); // 4
- 缓存,true
- 缓存,true
- false
- 数值相同,true
- 无论如何,Integer与new Integer不会相等。不会经历拆箱过程
- 两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false
- Java在编译Integer i2 = 128的时候,被翻译成-> Integer i2 = Integer.valueOf(128);而valueOf()函数会对-128到127之间的数进行缓存
- 两个都是new出来的,都为false
- int和integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比
依赖注入
- 依赖注入能够独立开发各组件,然后根据组件间关系进行组装
- 依赖注入提供使用接口编程
- 依赖注入指对象在使用时动态注入
- 依赖注入的动机就是减少组件之间的耦合度,使开发更为简洁
默认类型
- 整数不指定类型,默认为int类型
- 小数不指定类型,默认为double类型
- 如果指定长整形,则类似于1000L
- 如果指定单精度,则类似与3.14F
初始值
类中声明的变量有默认初始值;方法中声明的变量没有默认初始值,必须在定义时初始化,否则在访问该变量时会出错。
数据类型 | 初始值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
char | ‘u0000’ |
float | 0.0f |
double | 0 |
boolean | false |
所有引用类型 | null |
算法
算法包括0个或多个输入,1个或多个输出,中间有穷个处理过程。存储结构不属于算法结构。
数组
数据的创建方式
float f[][] = new float[6][6]; float []f[] = new float[6][6]; float [][]f = new float[6][6]; float [][]f = new float[6][]; // 数组命名时名称与[]可以随意排列 // 但声明的二维数组中第一个中括号中必须要有值
数组是一个对象,不同类型的数组具有不同的类。因为可以调用方法,例如下方array与array_1类不同。
public static void main(String[] args) { int[] i = new int[]{1,2,3}; int[] j = new int[]{1,2,3}; int[] k = i; int[] array = new int[100]; double[] array_1 = new double[100]; System.out.println(array); System.out.println(array_1); System.out.println(i.equals(j)); System.out.println(i.equals(k)); }
[I@6d6f6e28 [D@135fbaa4 false true
数组是一个连续的存储结构,Java中的数组中的数据是连续存储在一块内存中的,所以可以通过下标(即偏移量)的方式访问。
查看源码可以知道数组的equals方法是object的equals,比较的是内存地址。
数组长度是不能动态调整的。
可以二维数组,且可以有多维数组,都是在Java中合法的。
Arrays.equal()可以比较数组元素。
int[] array=new int [100]; int array[]=new int [100];
final
- final修饰变量,则等同于常量
- final修饰方法中的参数,称为最终参数
- final修饰类,则类不能被继承
- final修饰方法,则方法不能被重写
- final 不能修饰抽象类
- final不能修饰接口
- final修饰的方法可以被重载 但不能被重写
重载 vs 重写
- 重载 指可以有同样修饰符、返回值、方法名,但参数个数或顺序不同的方法。
- 重写 指子类继承父类后,重写父类的方法。
表达式转型规则
Java表达式转型规则由低到高转换:
- 所有的byte,short,char型的值将被提升为int型;
- 如果有一个操作数是long型,计算结果是long型;
- 如果有一个操作数是float型,计算结果是float型;
- 如果有一个操作数是double型,计算结果是double型;
- 被fianl修饰的变量不会自动改变类型,当2个final修饰相操作时,结果会根据左边变量的类型而转化。
题目
- 表达式
(short)10/10.2*2
运算后结果是什么类型?
- 首先,要注意是
(short)10/10.2*2
,而不是(short) (10/10.2*2)
,前者只是把10强转为short,又由于式子中存在浮点数,所以会对结果值进行一个自动类型的提升,浮点数默认为double,所以答案是double;后者是把计算完之后值强转short。 - 强制类型转换的优先级高于
+ - * /
- 小转大随便转,不同类型运算结果类型向右看齐:
char<short< int< float <double
- 下列代码片段中,存在编译错误的语句是()
byte b1=1,b2=2,b3,b6,b8;
final byte b4=4,b5=6,b7;
b3=(b1+b2); /*语句1*/
b6=b4+b5; /*语句2*/
b8=(b1+b4); /*语句3*/
b7=(b2+b5); /*语句4*/
System.out.println(b3+b6);
- 语句1错误:b3=(b1+b2);自动转为int,所以正确写法为b3=(byte)(b1+b2);或者将b3定义为int;
- 语句2正确:b6=b4+b5;b4、b5为final类型,不会自动提升,所以和的类型视左边变量类型而定,即b6可以是任意数值类型;
- 语句3错误:b8=(b1+b4);虽然b4不会自动提升,但b1仍会自动提升,所以结果需要强转,b8=(byte)(b1+b4);
- 语句4错误:b7=(b2+b5); 同上。同时注意b7是final修饰,即只可赋值一次,便不可再改变。
byte a = 2; a = a - 10;
由于10是整型int,因此表达式a - 10
将自动转换成int类型,int类型在赋值给byte,会编译错误byte a = 2; a -= 10;
不会发生编译错误,因为复合运算符是一个带有隐式转换的运算符,将右侧表达式类型自动转换成左侧表达式类型