think in java
数据存储
- 寄存器:最快的存储区,位于存储器内部,数量及其有限,无法直接控制
- 堆栈:
- 堆:
- 常量存储:存代码中或ROM(只读存储器,例:字符串池)
- 非RAM存储:文件或者数据库数据等
特列:基本类型 存于堆栈中使程序变得更高效
基本类型 | 大小 | 最小值 | 最大值 | 包装器类型 |
---|---|---|---|---|
boolean | - | - | - | Boolean |
char | 16bit | Unicode 0 | Unicode 216 - 1 | Character |
byte | 8bits | -128 | +127 | Byte |
short | 16bits | -215 | 215-1 | Short |
int | 32bits | -231 | 231-1 | Integer |
long | 64bits | -263 | 263-1 | Long |
float | 32bits | IEEE754 | IEEE754 | Float |
double | 64bits | IEEE754 | IEEE754 | Boolean |
void | - | - | - | Void |
main方法
public static void mian(String[] args){
}
参数args虽然未用到,但是java编译器要求必须这样做,因为args要用来存储命令行参数。
基本类型转换
- 窄化转换:需要显示声明转换,不然编译器会报错
- 扩展转换:编译器自动转换,无需显示声明
- java允许基本类型转换成别的任何基本类型,boolean类型除外
对基本类型数据类型执行算数运算或者按位运算,只要比int小,在运算前会自动转换为int类型
this关键字的本质
java中调用类中的某个方法(非静态)时,编译器会隐式的将调用该方法的对象实列作为方法的第一个参数传入,此时若方法中需要用到当前调用方法的对象实列,就可以通过this关键字获取。
静态方法则不存在隐式传参,这也就是为什么静态方法里面不能调用非静态方法的原因。静态方法中无法获取到当前调用对象实列(this)
相比较于c++,java不需要开发者销毁对象(释放空间),而对象的销毁由java虚拟机(垃圾回收器)来完成。虚拟机要实现这样的功能,那么虚拟机就需要知道对象是如何创建的,内存上是存在来哪里,这样虚拟机才知道如何去释放这些对象内存。而这就要借助构造函数。
构造函数
构造函数存在的重大意义在于Java虚拟机通过构造函数统一创建对象。这为虚拟机之后对对象销毁内存回收创造了条件。(谁创建的谁销毁,解铃还须系铃人)
finalize()方法
垃圾回收器在认为某个对象满足回收条件后,会调用一次finalize()方法,下一次回收时才会回收这个对象。
- 虚拟机负责java对象的创建与销毁,但有一部分内存但使用虚拟机是没法感知并回收的(本地方法中使用到的内存)。这时候,在销毁回收对象时,就需要开发者告诉虚拟机如何回收这部分内存。这就是finalize()方法的用法之一。
- 有的对象销毁回收前,虚拟机认为该对象可以销毁,但是开发者还需要用这个对象,还不想回收销毁。这时就可以在finalize()方法里面阻止虚拟机销毁回收该对象
垃圾回收机制
垃圾回收主要工作:清除无用对象回收其所占内存,使内存循环利用;使内存有序分配,减少内存碎片,提高创建对象效率。
回收基本原理
- 停止-复制:停止程序,将所有存活的对象复制到另一个堆,没有被复制的全都是垃圾。当对象复制到新堆后,是一个挨着一个的,所有新堆保持紧凑排序。
缺点:1.占用内存大,需要消耗实际内存的两倍。针对这个问题按需从堆中分配几块较大的内存块,复制动作在这些大块内存之间(分代收集);2.程序进入稳定状态后,垃圾往往很少。这样两个堆之间来回捣腾,非常浪费性能。针对这个问题,引入了标记-清扫方案 - 标记-清扫:从堆栈和静态存储区出发,遍历所有引用,进而找出所有存活的对象。每当找到一个存活的对象,就会给对象设一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理动作才开始。清理过程中,没有被标记的对象将会被释放。
缺点:1.速度非常慢,但是只会产生少量垃圾甚至不会产生垃圾时,速度就很块了;2.回收完成后,剩下的堆空间是不连续的,垃圾回收器要是希望得到连续空间的话,就得重新整理剩下的对象。
前文说述,Java虚拟机中,内存分配为较大的块。如果对象较大,会占用单独的块。停止-复制回收内存时,这些大的内存块不会被复制。每个块都有相应的代数来记录它是否存活,每有一处引用时,计数块会增加。
java虚拟机有内存监控机制,当内存碎片过多时采用停止-复制算法;程序趋于稳定,不再产生大量的垃圾时,用标记-清扫算法。如此配合往复回收。
成员初始化
初始化顺序:静态变量成员初始化 ——> 非静态成员初始化(默认值)——>构造器赋值
对象创建过程
前2步只有在对象首次创建的时候才执行
访问权限控制
- public
- protected 同包 + 子类
- 默认 同包
- private
多态
运行时绑定、动态绑定、后期绑定
继承
基类私有/final方法不能被覆盖,即使导出类虽然也可以有相同的方法。
基类的成员变量不会被导出类成员变量覆盖
public class Parent {
private final String print(){
return "Parent";
}
}
public class Child extends Parent{
//不会报错,因为基类私有,两个不在一个域,即使方法名一样也不会报错
public void print(){
}
}
多接口实现问题
同时实现多个接口,多个接口中含有相同的方法名,只是返回类型不同,则编译报错
interface a{
int f();
}
interface b{
void f();
}
class c implements a,b{
//不管返回类型是void还是int都会报错
void f();
}
java类图
-
类图域的表示
[+] ---->public
[-] ---->private
[#] ---->protected -
类之间的关系表示
- 关联(B关联A)
class A{
}
class B{
public A a;
}
- 组合(A组合B,需要注意的是,B的生命周期和A是绑定的)
class A{
class B{}
}
- 聚合(A聚合B)
class A {
List<B> b;
}
class B{
}
- 泛化(继承)
class A{
}
class B extends A{
}
- 实现
interface A{
}
class B implements A{
}
String
String a="buffer";
String b = a + "sdjf";
for(int i=0;i<10;i++){
a += i
}
StringBuilder a1 = new StringBuilder(a);
for(int i=0;i<10;i++){
a1.append(i);
}
编译器在编译时会生成一个StringBuilder对象来优化计算效率。因此简单的字符串拼接我们可以直接用+号。复杂的字符串拼接(循环拼接字符串)时,虽然编译器也会借助StringBuilder来优化,但是会生成多个StringBuilder对象。而若是代码里面使用StringBuilder则是生成一个StringBuilder对象。
jdk动态代理
public interface Interface {
void doSomething();
void somethingElse(String args);
}
/**被代理的类
* @author jinWen Wang
* @date 2021年08月02日 16:29
*/
public class RealObject implements Interface{
@Override
public void doSomething() {
System.out.println("RealObject doSomething");
}
@Override
public void somethingElse(String args) {
System.out.println("RealObject somethingElse "+args);
}
}
/** 动态代理类
* @author jinWen Wang
* @date 2021年08月02日 14:57
*/
public class DynamicProxy implements InvocationHandler {
private final Object proxied;
public DynamicProxy(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("***** proxy: "+proxy.getClass()+". method: "
+method + ".args: " + Arrays.toString(args));
if(args != null){
for (Object arg : args) {
System.out.println(" "+arg);
}
}
return method.invoke(proxied,args);
}
}
/**
* @author jinWen Wang
* @date 2021年08月02日 16:23
*/
public class SimpleDynamicProxy {
public static void consumer(Interface iface){
iface.doSomething();
iface.somethingElse("nnnn");
}
public static void main(String[] args) {
RealObject realObject = new RealObject();
Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(),
new Class[]{Interface.class},
new DynamicProxy(realObject));
consumer(proxy);
}
}
调用静态方法Proxy.newProxyInstance()可以创建一个动态代理类。
- 第一个参数是类加载器(跟Interface.class类关系不大,这里这样使用只是为了从已加载的类中获取类加载器);
- 第二个参数是需要代理类实现的接口列表(一个类可以实现多个接口,且只能是接口,不能是类或抽象类。ps:spring AOP的jdk方式实现原理);
- 第三个是实现了InvocationHandler接口的调用处理器。动态代理可以将所有调用重定向到调用处理器(DynamicProxy),因此需要在调用处理器的构造方法传入“真实”的对象引用,调用处理器在执行其中介任务时可以将请求转发
总结:
RealObject是被代理的类,DynamicProxy需要实现InvocationHandler接口,并在invoke方法里面做代理类需要处理的逻辑,然后转发到被代理的类。Proxy.newProxyInstance()根据以上的信息动态的生成一个代理类。
RTTI :通过匿名基类的引用来发现类型信息
class A{
public void print(){
System.out.print("A---A")
}
}
class B extends A{
public void print(){
System.out.print("B---B")
}
}
class test{
public static void action(A a){
a.print();
}
public static void main(String[] args){
B b = new B();
action(b);
//输出:B---B
}
}
通过匿名的基类A引用发现B类型信息,进而调用B的print()方法。
泛型类型擦除,泛型内部并没有类型区别,都是持有一个Object对象
Class<?> a = new ArrayList<Integer>().getClass();
Class<?> b = new ArrayList<String>().getClass();
System.out.println(a == b);
//输出 true
注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {
}
元注解
@Target 用来定义注解用于什么地方
ElementType参数包括:
- CONSTRUCTOR — 构造器声明
- FIELD — 域声明
- LOCAL_VARIABLE — 局部变量声明
- METHOD — 方法声明
- PACKAGE — 包声明
- PARAMETER — 参数声明
- TYPE — 类声明,类、接口、注解或enum声明
@Retention 用来定义注解在哪一个级别可用
可选RetentionPolicy参数包括:
- SOURCE — 源代码中
- CLASS — 类文件中
- RUNTIME — 运行时
@Document 将此注解包含在javadoc中
@InHerited 允许子类继承父类中的注解
java线程之后台模式(后台线程/守护线程/精灵线程)
- 代表:jvm垃圾回收线程
- 开启方式:调用Thread的setDaemon(true)方法
- 特点:所有非后台线程结束,程序终止,所以进程中的后台线程会被杀死。杀死意味着守护线程的finaly也不会执行。
多线程异常捕捉处理
关键点
- 实现Thread.UncaughtExceptionHandler接口做为异常处理器
- 在线程中设置异常处理器
/**
* 异常处理器
*/
public class ThreadExceptionHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
String name = t.getName();
System.out.println("线程:["+name+"]发生异常");
e.printStackTrace();
if(e instanceof BizException){
BizException bizException = (BizException)e;
System.out.println("线程:["+name+"]发生异常,异常信息:"+bizException);
}
}
}
public class MyRunnable implements Runnable{
final boolean exception;
final String name;
public MyRunnable(boolean exception, String name) {
this.exception = exception;
this.name = name;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(this.name+"["+threadName+"]开始");
if(exception){
System.out.println(this.name+"["+threadName+"]抛出异常");
throw new BizException("自定义异常",name,threadName);
}
System.out.println(this.name+"["+threadName+"]正常结束");
}
}
/**
*自定义异常
*/
public class BizException extends RuntimeException{
private String name;
private String thread;
}
/**
* 测试类
*/
public class ThreadExceptionTest {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
Thread t = new Thread(new MyRunnable(i%2==0,"test" + i));
//设置异常处理器
t.setUncaughtExceptionHandler(new ThreadExceptionHandler());
t.start();
}
}
}
执行结果
Thread.UncaughtExceptionHandler接口是java1.5开始提供的接口,它允许再每个Thread上附着一个异常处理器。Thread.UncaughtExceptionHandler.uncaughtException()方法会在线程因未捕获的异常而临近死亡时被调用。
---------java编程思想
double/long非原子性
JVM可以将64位的读取和写入当作两个分离的32位操作来执行,这就产生读取和写入之间的上下文切换,从而导致不同的任务可以看到不正确结果的可能。这种现象也叫做字撕裂。使用volati