JAVA 基础

Math.round(-1.5) 等于 -1,因为四舍五入的原理是在参数上加 0.5 然后进行向下取整

普通类和抽象类有什么区别

  • 普通类不能包含抽象方法,可以直接实例化
  • 抽象类可以包含抽象方法,不能直接实例化

抽象类可以使用 final 修饰符吗

定义抽象类就是让其他类继承的,如果定义为final该类就不能被继承,编译器也会提示错误信息

抽象类和接口的区别

  • 接口不能保存状态,抽象类可以保存状态
  • 我们能实现多个接口,但是只能继承一个抽象类
  • 接口没有构造方法,抽象类有构造方法

Map 实现类的区别

  • HashMap 通过哈希表实现,内部数据无序,线程不安全
  • TreeMap 通过红黑树实现,内部数据会根据 Key 值进行排序,线程不安全
  • LinkedHashMap 通过哈希表 + 双向链表实现,Entry 除了保存当前对象的引用外,还保存了before 和 after,可以保证插入顺序或访问顺序
  • ConcurrentHashMap 底层采用分段数组 + 链表实现,线程安全,通过把整个 Map 分为 N 个 Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍
  • HashTable 是线程安全的,键值对都不允许为空,保留类
  • WeakHashMap 的键值是通过弱引用 + 弱引用队列实现的,可以自动回收键值对

Collection 和 Collections 的区别

  • Collection 是一个集合接口,提供了集合类的基本方法
  • Collections 是一个工具类,包含了很多静态方法,比如:Collections.sort、Collections.synchronizedMap、Collections.unmodifiableCollection

ArrayList 和 LinkedList 的区别

  • ArrayList 采用动态数组实现,且仅实现了 List 功能
  • LinkedList 采用双向链表实现,且实现了 List、Deque(双端队列)、Queue

ArrayList 和 Vector 的区别

  • Vector 使用了 Synchronized 来实现线程同步,是线程安全的,Vector 每次扩容增加一倍
  • ArrayList 是非线程安全的,ArrayList 每次扩容只会增加一半

Array 和 ArrayList 区别

  • Array 是固定大小的,可以存储基本类型和对象
  • ArrayList 可以动态扩容,但是只能存储对象

创建线程的三种方法

  1. 继承 Thread 重写 run 方法
  2. 实现 Runnable 接口,Runnable 没有返回值
  3. 实现 Callable 接口,Callable 可以拿到返回值

sleep、wait、yield 的区别

  • yield 是 Thread 类的方法,不释放锁,可以在任意位置调用,yield 可以给同优先级或高优先级的线程让出 CPU 占有权,但让出的时间是不可设定的
  • sleep 是 Thread 类的方法,不释放锁,可以在任意位置调用,需要传入休眠的时间
  • wait 是 Object 类的方法,会释放锁,必须在同步控制块中调用,可以指定等待时间

Thread.join 的作用

当前线程等待子线程结束。join 方法内部会调用 lock 对象的 wait 方法,也就会阻塞调用的线程,其中,isAlive 是子线程的状态,而 wait 和线程对象无关,总是阻塞当前运行的线程

public final void join(long millis) throws InterruptedException {
    synchronized(lock) {
        long base = System.currentTimeMillis();
        long now = 0;
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (millis == 0) {
            while (isAlive()) {
                lock.wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                lock.wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
}

创建线程池的四种方法

  • newFixedThreadPool:固定数量的核心线程
  • newSingleThreadExecutor:单个核心线程,finalize 时会自动调用 shutdown 方法
  • newCachedThreadPool:没有核心线程、Integer.MAX_VALUE 个工作线程
  • newScheduledThreadPool:固定数量的核心线程,Integer.MAX_VALUE 个工作线程,支持周期执行任务,通过封装 Runnable 实现
public class Executors {
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
}

线程池的真正实现类是 ThreadPoolExecutor,它的主要参数有哪些

  • 核心线程数
  • 最大线程数
  • 超时时间
  • BlockingQueue / SynchronousQueue
  • ThreadFactory
  • RejectedExecutionHandler
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);
    public Thread newThread(Runnable r) {
        return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    }
};

private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128);

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
                                                KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                                                sPoolWorkQueue, sThreadFactory);

BlockingQueue 的作用

  1. 线程通过 getTask 获取要执行的任务,如果当前线程数大于 coreSize,则调用 BlockingQueue.poll 方法,并设置超时时间(KeepAliveTime),如果超时时间没有获取到 task,则销毁当前线程,如果当前线程数小于 coreSize,则调用 BlockingQueue.take
  2. 在用户调用 execute 时,会调用 BlockingQueue.offer 方法而不是 BlockingQueue.put 方法,该方法不会阻塞,而是立即返回成功或失败,用于执行拒绝策略

SynchronousQueue 的作用

  1. 例外的是,newCachedThreadPool 传入的是 SynchronousQueue(同步队列,其内部没有容量,消费者线程和生产者线程必须交替执行,也就是说,生产者和消费者都必须等待对方就绪),因为 CachedThreadPool 要么复用空闲的线程,要么就新建线程,所以不会阻塞

submit 和 execute 的区别

  • execute 只能执行 Runnable 类型的任务
  • submit 可以执行 Runnable 和 Callable 类型的任务

如何安全的停止线程

  • stop(已废弃)
  • interrupt + catch + isInterrupted

类加载:forName 默认会执行初始化的,loadClass 默认只执行加载

Java 对象生命周期

  • Created(创建阶段):类加载、分配对象空间、执行父类/当前类的构造方法
  • In use(应用阶段):至少被一个强引用持有
  • Invisible(不可见阶段):即使有强引用持有对象,但是这些强引用对于程序来说是不能访问的
  • public void run() {
        try {
            Object foo = new Object();
            foo.doSomething();
        } catch (Exception e) {
            // whatever
        }
        while (true) { /* do stuff */ } // 此时 foo 就是不可见的
    }
  • Unreachable(不可达阶段):GC Root 不可达的对象
  • Collected(收集阶段):如果该对象已经重写了 finalize 方法,并且没有被执行过,则执行该方法的操作。否则直接进入终结阶段
  • Finalized(终结阶段):当对象执行完 finalize 方法后仍然处于不可达状态时,该对象进入终结阶段。在该阶段,等待垃圾回收器回收该对象空间
  • Deallocated(重新分配阶段):内存被回收或再分配,对象消失

Java 读取和写入文件时如果不指定字符集,那么都是采用操作系统默认的字符集,Java 内部采用 UTF-16 编码格式

String encoding = System.getProperty("file.encoding");
String defaultCharsetName=Charset.defaultCharset().displayName();

Error 指系统环境出现了错误,可以捕获但不要捕获,应该交给系统处理,Exception 指程序运行中出现了异常,可以被捕获,Error 和 Exception 都继承自 Throwable,Exception 还分为执行异常(空指针、找不到类)和检查异常两种

Java 创建对象的 5 种方式

  1. 使用 new 关键字
  2. 使用 Class 类的 newInstance 方法
  3. 使用 Constructor 的 newInstance 方法
  4. 使用 clone 方法
    1. 对于基本类型不需要考虑浅拷贝和深拷贝,使用等号便可复制值
    2. 对于复合类型(数组)使用等号浅拷贝,使用 clone 方法或者 System.arrayCopy 进行深拷贝
    3. 对于对象类型,使用 clone 仅能实现浅拷贝
  5. 使用反序列化

try / catch 中遇到 return 方法后执行 finally 代码,finally 代码中有 return 则提前返回

访问权限本类本包类子类非子类的外包类
public
protected
default
private

String 是不可改变的,StringBuffer 是线程安全的,StringBuilder 是非线程安全的

字符串拼接五种方法

  1.  “+”:写法简单,底层调用 StringBuilder#append
  2. String#concat:null 对象无法调用 concat、参数必须是 String 类型,concat每次都会返回一个 String,本质和加号一样,如果参数长度是 0,则返回 this 本身
  3. String#join:join 适用于 list 类型的拼接,底层调用的 StringBuilder#append
  4. StringBuffer#append:线程安全
  5. StringBuilder#append:非线程安全
String[] tmpStr={abc,def,ghi};
String join = String.Join(“-“, tmpStr); //join = ”abc-def-ghi”;

"+"会被编译器优化成 StringBuilder#append,但是不适用于复杂逻辑

    @Test
    public void test() {
        String str = "";
        for (int i = 0; i < 10000; i++) {
            str += "asjdkla";
        }
    }

    @Test
    public void test() {
        String str = null;
        for (int i = 0; i < 10000; i++) {
            str = new StringBuilder().append(str).append("asjdkla").toString();
        }
    }

String#concat 和 StringBuilder#append 原理相同,底层也是采用数组拷贝方式,但是由于StringBuilder的capability默认是 16 字节,对于少数字符串拼接会导致多次扩容和数组拷贝,效率低于 concat

    public StringBuilder() {
        super(16);
    }
    public StringBuilder(int capacity) {
        super(capacity);
    }
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

    public native String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }

生产者消费者模式(synchronized、wait+notifyAll)

public class ProduceConsumer {
    private final int MAX_SIZE = 100;
    private final LinkedList<Object> list = new LinkedList<>();
    private final Object produce = new Object();
    private final Object consume = new Object();

    public void produce(String producer) {
        synchronized (list) {
            while (list.size() == MAX_SIZE) {
                System.out.println("仓库已满,【" + producer + "】: 暂时不能执行生产任务!");
                try {
                    produce.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            list.add(new Object());
            System.out.println("【" + producer + "】:生产了一个产品\t【现仓储量为】:" + list.size());
            consume.notifyAll();
        }
    }

    public void consume(String consumer) {
        synchronized (list) {
            while (list.size() == 0) {
                System.out.println("仓库已空,【" + consumer + "】: 暂时不能执行消费任务!");
                try {
                    consume.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            list.remove();
            System.out.println("【" + consumer + "】:消费了一个产品\t【现仓储量为】:" + list.size());
            produce.notifyAll();
        }
    }
}

生产者消费者模式(BlockingQueue)

public class ProduceConsumer {

  private final int MAX_SIZE = 100;
  private final LinkedBlockingQueue<Object> list = new LinkedBlockingQueue<Object>(100);

  public void produce(String producer) {
    try {
      list.put(new Object());
    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("【" + producer + "】:生产了一个产品\t【现仓储量为】:" + list.size());
  }
  
  public void consume(String consumer) {
    try {
      list.take();
    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("【" + consumer + "】:消费了一个产品\t【现仓储量为】:" + list.size());
  }
}

Java会确保类成员在准备阶段获得一个默认值,然而确保初始化的方法并不适用于局部变量,如果没有赋值,Java会在编译时返回一个错误(许多C++编译器会对未初始化变量给予警告,而Java则视为是错误):这里理解错了,准备阶段还没创建对象呢,该阶段是将静态变量初始化为零值,类成员是在初始化阶段初始化为零值的

final变量是可以反射修改的,但是需要注意的是,对于特定代码,虚拟机做了一定的优化

public class Test {
    private final String NAME = "亦袁非猿";
    public String getName() {
        return NAME;
    }
}

//即使NAME变量被修改了,但是getName还是返回原来值
public class Test {
    private final String NAME = "亦袁非猿";
    public String getName() {
        return "亦袁非猿";
    }
}

Java 注解

通过 @interface 关键字进行定义

元注解:@Retention、@Documented、@Target、@Inherited、@Repeatable

@Retention:RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME

@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {}

@Documented:将注解中的元素包含到 Javadoc 中去

@Target:ElementType.ANNOTATION_TYPE(给注解进行注解)、ElementType.CONSTRUCTOR、ElementType.FIELD、ElementType.LOCAL_VARIABLE、ElementType.METHOD、ElementType.PACKAGE、ElementType.PARAMETER、ElementType.TYPE

@Inherited:标签可以继承(富豪标签)

@Repeatable:注解数组必须要有一个 value 的属性

@interface Persons {
    Person[] value();
}

@RequiresApi(api = Build.VERSION_CODES.N)
@Repeatable(Persons.class)
@interface Person {
    String role() default "";
}

@Person(role = "artist")
@Person(role = "coder")
@Person(role = "PM")
public class SuperMan {

}

注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
	public int id() default -1;
	public String msg() default "Hi";
}

@TestAnnotation(id=3,msg="hello annotation")
public class Test {

}

如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内

public @interface Check {
	String value();
}

@Check("hi")
int a;

还需要注意的一种情况是一个注解没有任何属性,那么在应用这个注解的时候,括号都可以省略

Java预置的注解:@Deprecated、@Override、@SuppressWarnings

注解通过反射获取:

boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);

Field a = Test.class.getDeclaredField("a");
Check check = a.getAnnotation(Check.class);

Method testMethod = Test.class.getDeclaredMethod("testMethod");
Annotation[] ans = testMethod.getAnnotations();

Java 内部类:内部类可以无条件访问外部类的所有成员属性和成员方法

成员内部类是最普通的内部类,成员内部类可以拥有private、protected、public以及包访问权限,可把它当做是外部类的一个成员变量来看。如果成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要通过:外部类.this.成员变量、外部类.this.成员方法

非静态内部类里面不能定义静态方法、静态成员变量、静态初始化块:使用静态方法不用new对象,但是使用非静态内部类又必须要new一个对象,这就自相矛盾了。违背了静态成员的使用不依赖于对象的原则

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问权限仅限于方法内部

匿名内部类有以下特点: 

  • 没有名字(因为没有名字,没有构造函数)
  • 只能被实例化一次
  • 通常被声明在方法或代码块的内部,以一个带有分号的带有花括号结尾
  • 匿名内部类也是不能有访问修饰符和static修饰符的
  • 匿名类的一个重要作用就是简化代码

静态内部类

  • 静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static
  • 静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似
  • 静态内部类不能够访问外部类的非静态成员

为什么成员内部类可以无条件访问外部类的成员

  • 编译器自动为内部类添加一个成员变量, 这个成员变量就是指向外部类对象的引用
  • 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型
  • 在调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用
  • 编译器会为外部类智能生成 access 方法,用于访问私有方法

为什么局部内部类和匿名内部类只能访问局部 final 变量

局部内部类和匿名内部类的生命周期可能比外部的类要长,因此访问外部局部变量有可能是访问不到的。 那怎么办呢?Java语言为了实现这种特性, 只好将外部的局部变量偷偷的赋值了一份给匿名内部类。那这样匿名内部类就可以肆无忌惮的访问外部局部变量了。 问题又来了,这种通过赋值的形式有一个缺陷,匿名内部类不可以修改“原来的局部变量”,因为是一份“复制品”,修改复制品对原变量没什么影响啊。 那怎么办? Java语言干脆强制要求被匿名内部类访问的外部局部变量必须是final的,什么意思呢?就是“一刀切”,不让修改了

Compelling reasons for using nested classes include the following:

  • It is a way of logically grouping classes that are only used in one place: If a class is useful to only one other class, then it is logical to embed it in that class and keep the two together. Nesting such "helper classes" makes their package more streamlined.

    • WeakHashMap.Entry、ThreadLocalMap.Entry、HashMap.EntrySet

  • It increases encapsulation: Consider two top-level classes, A and B, where B needs access to members of A that would otherwise be declared private. By hiding class B within class A, A's members can be declared private and B can access them. In addition, B itself can be hidden from the outside world.

    • HashMap.KeyIterator、HashMap.ValueIterator

  • It can lead to more readable and maintainable code: Nesting small classes within top-level classes places the code closer to where it is used.

    • View.OnClickListener

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

little-sparrow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值