语言基础

default 关键字

一般用于 switch

int flag = 2;

switch (flag){
    case 1:
        System.out.println("1");
        break;
    case 2:
        System.out.println("2");
        break;
    default:
        System.out.println("default");
}

JDK 1.8 之后 可以在接口中定义默认方法

package demo;

interface Message {
    // 抽象方法
    public void print();

    // 默认方法 JDK>=1.8
    default void getMessage() {
        System.out.println("Message");
    }
}

class MessageImpl implements Message{

    @Override
    public void print() {
        System.out.println("message");
    }
}

public class DefaultDemo {
    public static void main(String[] args) {
        Message message = new MessageImpl();
        message.getMessage();
        message.print();
    }
}

Java 内存模型

内存划分
GC

Runtime
单例设计模式
内存信息取得,系统垃圾收集处理

数据单位:字节 /1024 -> KB /1024 -> M

Runtime runtime = Runtime.getRuntime();
System.out.println("maxMemory " + runtime.maxMemory()/1024/1024);
System.out.println("totalMemory " + runtime.totalMemory()/1024/1024);
System.out.println("freeMemory " + runtime.freeMemory()/1024/1024);
/**
    * maxMemory 1820
    * totalMemory 123
    * freeMemory 121
    */

内存空间划分

  1. 伊甸园区:新生对象,如果被占满,会执行 gc
  2. 旧生代区:如果发现其一直会被使用,就会进入旧生代,空间不足继续 gc
  3. 永久区:数据不会被清除

默认大小内存的 1/4

调整内存大小
-Xms2048M 出始分配内存大小,默认物理内存 1/64 但小于 1G
-Xmx2048M 最大分配内存,默认物理内存 1/4 但小于 1G
-Xmn1024M 设置年轻代(伊甸园区)堆内存大小

二叉树 BinaryTree

数据存储:左节点、右节点
树的遍历:前序、中序、后序

考虑大小关系,数据应该以 Comparable 为主

package demo;

import java.util.Arrays;

class BinaryTree {
    private class Node {
        private Comparable data;

        private Node left;
        private Node right;

        public Node(Comparable data) {
            this.data = data;
        }

        public void addNode(Node node) {
            if (this.data.compareTo(node.data) < 0) {
                if (this.right == null) {
                    this.right = node;
                } else {
                    this.right.addNode(node);
                }
            } else {
                if (this.left == null) {
                    this.left = node;
                } else {
                    this.left.addNode(node);
                }
            }
        }

        public void toArrayNode() {
            if (this.left != null) {
                this.left.toArrayNode();
            }

            BinaryTree.this.list[BinaryTree.this.foot++] = this.data;

            if (this.right != null) {
                this.right.toArrayNode();
            }
        }
    }

    // 根节点
    private Node root;

    // 统计个数
    private int count;

    private Comparable[] list;

    private int foot;

    public void add(Comparable data) {
        Node node = new Node(data);
        // 没有根节点
        if (this.root == null) {
            this.root = node;
        }
        // 交给Node处理,需要比较大小
        else {
            this.root.addNode(node);
        }
        this.count++;

    }

    public Comparable[] toArray() {
        if (this.root == null) return null;

        this.foot = 0;
        this.list = new Comparable[this.count];
        this.root.toArrayNode();
        return this.list;
    }
}

public class BinaryTreeDemo {
    public static void main(String[] args) {
        BinaryTree tree = new BinaryTree();
        tree.add("A");
        tree.add("C");
        tree.add("E");
        tree.add("D");
        tree.add("B");

        System.out.println(Arrays.toString(tree.toArray()));
        // [A, B, C, D, E]
    }
}

ClassLoader 加载

Java 中的类加载器不止一个,自定义加载器是最后执行的

package demo;

class User {
}


public class ClassLoaderDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> cls = Class.forName("demo.User");

        System.out.println(cls.getClassLoader());
        // 3. sun.misc.Launcher$AppClassLoader@135fbaa4

        System.out.println(cls.getClassLoader().getParent());
        // 2. sun.misc.Launcher$ExtClassLoader@2503dbd3

        System.out.println(cls.getClassLoader().getParent().getParent());
        // 1. null Bootstrap


    }
}

所有的 Java 程序类加载器一共分为两套类加载器:双亲加载

如果是用户自定义的类,会使用其他类加载器:

AppClassLoader、ExtClassLoader、用户自定义类加载器

默认情况下通过 CLASSPATH 加载

自定义类加载器可以通过远程或文件加载

HashMap 源代码

HashMap 数据增加到一定数量后,就会有链表变为红黑树

阈值:

static final int TREEIFY_THRESHOLD = 8;

如果 达到 16 * 0.75 个数时,容量会扩充一倍 变为 32

扩充

static final float DEFAULT_LOAD_FACTOR = 0.75f;

HashMap 方法都是异步处理,非线程安全

package demo;

import java.util.HashMap;

public class HashMapDemo {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                for (int j = 0; j < 9; j++) {
                    map.put(Thread.currentThread().getName(), "y=" + j);
                }
            }).start();
        }

        System.out.println(map);
        /**
         * 可能输出值:
         * {Thread-0=y=8, Thread-1=y=8, Thread-2=y=8}
         * {Thread-0=y=8, Thread-1=y=8}
         * java.util.ConcurrentModificationException
         * {}
         */
    }
}

Hash 冲突

HashMap 中 Hash 冲突严重时会影响性能

  1. 开放定址法:为一个哈希冲突求的一个地址序列
  2. 链地址法:将所有哈希冲突的内容保存到一个链表里(HashMap 实现原理)
  3. 再哈希法:重新做一个 Hash 计算

Node 内部类, Map.Entry 内部接口

ArrayList 原理分析

ArrayList 线性复杂度是 1
类似数组,可以直接通过索引取值

在数量固定的情况下,ArrayList(数组动态操作) 避免开辟额外的空间
不确定数量使用 LinkedList(链表实现)

package demo;

import java.util.ArrayList;
import java.util.List;

public class ListDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        System.out.println(list);
        // [A, B]
    }
}

Set 集合

Set 集合不允许元素重复

子类
HashSet 无序存放,由 HashMap 实现
TreeSet 由 TreeMap 实现,使用 Comparable 接口排序
LinkedHashSet 顺序保存

public HashSet() {
    map = new HashMap<>();
}

断点调试

  1. step over 单步跳过
  2. step into 单步进入
  3. step out 单步跳出

this 关键字

访问本类属性加 this
访问本类方法加 this
调用本类其他构造方法 this()
方法内的 this,表示当前实例对象

Java 系列小问题

1、字符串是匿名对象

没有明确指向就是匿名对象

"Hello".length();

有明确指向就不是匿名对象

String str = "Hello";

2、直接赋值的方式实例化一个字符串对象,引用地址保存在哪里?

只要是引用对象,就保存在堆内存里面,我们能进行控制的内存只有堆内存

3、对象池(常量池)和栈是什么关系?
对象池(常量池)保存到堆内存

4、不同操作系统的虚拟机

JVM 有三种实现标准
我们使用的是 HotSpot 实现标准

JVM 核心优化策略

1、取消伸缩区 total=max;

(1)CMS 问题,频繁 CMS 会导致性能下降

(2)伊甸园区,存活区,老年代

(3)JDK1.8 之后取消了永久代,使用元空间代替

2、如果内存过大要使用 G1 收集器来进行收集

3、Tomcat 使用 JVM, 需要设置 JAVA_OPTS 指令 可将全部内存供 Tomcat 使用

(1)默认最大使用内存为全部内存的 1/4

(2)默认 total 内存为全部内存的 1/64

JVM 内存组成

  1. 栈内存 只保存堆内存的引用地址,可以保存基本类型
  2. 堆内存 分为若干子内存空间:伊甸园区,存活区,老年代
  3. 方法区(JDK1.8 之前:永久代,之后:元空间 )

原则:

1、减少无用的内存空间,会引发频发 GC

2、初始化空间大小等于整个堆内存的分配大小,避免伸缩区

Map-Resize

HashMap 需要扩容时,如何得到扩容因子

// 移位运算,相当于*2
newCap = oldCap << 1

Runtime 相关说明

Runtime 是单例

区别:RuntimeException 和 非 RuntimeException

package demo;

public class RuntimeDemo {
    public static void main(String[] args) {
        // 单例设计模式
        Runtime runtime = Runtime.getRuntime();
        // 最大内存, 默认是整个电脑的1/4
        System.out.println("maxMemory " + runtime.maxMemory()/(double)1024/1024);
        // 初始化内存空间, 默认是电脑的1/64
        System.out.println("totalMemory " + runtime.totalMemory()/(double)1024/1024);
        System.out.println("freeMemory " + runtime.freeMemory()/(double)1024/1024);
        /**
         * maxMemory 1820.5
         * totalMemory 123.0
         * freeMemory 121.0499267578125
         */
    }
}

参数设置
-Xmx1g -Xmn1g

RuntimeException 不需要强制处理
Exception 需要强制处理
RuntimeException 是 Exception 子类

String 转换

package demo;

public class StringTest {
    public static void main(String[] args) {

        // Object str = "Hello";
        Object str = null;

        // 向下转型
        String str1 = (String) str;

        // toString方法获得 如果是null, 会抛出异常NullPointerException
        String str2 = str.toString();

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

    }
}

ThreadLocal

采用反射进行对象实例化,要求类中要提供有无参构造方法

简单 Java 类,本质在于进行数据的包装

package demo;

class MessageDemo {
    public static String message;

    public MessageDemo(String message) {
        this.message = message;
    }

    public void showMessage() {
        System.out.println(Thread.currentThread().getName() + " " + this.message);
    }
}

public class ThreadLocalDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            MessageDemo messageDemo = new MessageDemo("A");
            messageDemo.showMessage();
        }, "线程A").start();

        new Thread(() -> {
            MessageDemo messageDemo = new MessageDemo("B");
            messageDemo.showMessage();
        }, "线程B").start();

        new Thread(() -> {
            MessageDemo messageDemo = new MessageDemo("C");
            messageDemo.showMessage();
        }, "线程C").start();

    }
}

输出

线程B C
线程C C
线程A C

static 是全局数据,多个线程访问会出现数据不同步

ThreadLocal 保存了数据和当前线程对象

package demo;

class MessageDemo {
    public static ThreadLocal<String> local = new ThreadLocal<>();

    public MessageDemo(String message) {
        this.local.set(message);
    }

    public void showMessage() {
        System.out.println(Thread.currentThread().getName() + " " + this.local.get());
    }
}

public class ThreadLocalDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            MessageDemo messageDemo = new MessageDemo("A");
            messageDemo.showMessage();
        }, "线程A").start();

        new Thread(() -> {
            MessageDemo messageDemo = new MessageDemo("B");
            messageDemo.showMessage();
        }, "线程B").start();

        new Thread(() -> {
            MessageDemo messageDemo = new MessageDemo("C");
            messageDemo.showMessage();
        }, "线程C").start();

    }
}

输出

线程A A
线程C C
线程B B

ThreadPool

线程池有四种:
无限增长
定长执行
定时操作
单线程池

package demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 所有线程都会被这个类所管理
        ExecutorService service = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            final int temp = i;
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " " + temp);
                }
            });
        }
    }
}

ThreadLocal 引用传递

ThreadLocal 不仅保存的是一个引用对象,而且还保存有一个当前对象

不会与其他线程进行混淆

1、不使用 ThreadLocal

package demo;

class Message{
    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return this.message;
    }
}

class MessageDemo{
    public  void print(){
        System.out.println(Thread.currentThread().getName() + DataCache.message);
    }
}

class DataCache {
    public static Message message;
}

public class ThreadLocalDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            Message message = new Message();
            message.setMessage("A");
            DataCache.message = message;
            new MessageDemo().print();
        }, "线程A").start();

        new Thread(() -> {
            Message message = new Message();
            message.setMessage("B");
            DataCache.message = message;
            new MessageDemo().print();
        }, "线程B").start();

        new Thread(() -> {
            Message message = new Message();
            message.setMessage("C");
            DataCache.message = message;
            new MessageDemo().print();
        }, "线程C").start();

    }
}

执行结果

线程BA
线程AA
线程CA

2、使用 ThreadLocal

package demo;

class Message{
    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return this.message;
    }
}

class MessageDemo{
    public void print(){
        System.out.println(Thread.currentThread().getName() + DataCache.get());
    }
}

class DataCache {
    private static ThreadLocal<Message> threadLocal = new ThreadLocal<>();

    public static void set(Message message){
        threadLocal.set(message);
    }

    public static Message get(){
        return threadLocal.get();
    }
}

public class ThreadLocalDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            Message message = new Message();
            message.setMessage("A");
            DataCache.set(message);
            new MessageDemo().print();
        }, "线程A").start();

        new Thread(() -> {
            Message message = new Message();
            message.setMessage("B");
            DataCache.set(message);
            new MessageDemo().print();
        }, "线程B").start();

        new Thread(() -> {
            Message message = new Message();
            message.setMessage("C");
            DataCache.set(message);
            new MessageDemo().print();
        }, "线程C").start();
    }
}

执行结果

线程BB
线程AA
线程CC

YML 格式

person:
    name: Tom
    age: 23

抽象类

抽象类的实例化

核心认识:

  1. 抽象类比普通类多了抽象方法
  2. 抽象类不能使用关键词 new 实例化对象

普通类的继承对子类没有强制性约束

如果某些方法必须要求子类来实现,需要使用抽象类

所有关键词 new 调用构造方法实例化对象的部分都可以将其理解为“匿名对象”

A a = new B();
package demo;

abstract class PersonAbstract {
    public PersonAbstract(){
        System.out.println("PersonAbstract");
    }
    public abstract void sayHello();
}

class Person extends PersonAbstract{
    public Person() {
        // super(); // 可以省略
        System.out.println("Person");
    }

    @Override
    public void sayHello() {
        System.out.println("say hello");
    }
}

public class AbstractDemo {
    public static void main(String[] args) {
        Person person = new Person();
        person.sayHello();
        /**
         * PersonAbstract
         * Person
         * say hello
         */
    }
}

抽象类基础

理论上抽象类的所有抽象方法必须被覆写

对于非抽象方法是不需要强制覆写的

GenericServlet
HttpServlet

模板设计模式

多线程应用

Tomcat 每一个连接的用户用一个线程来处理

动态与静态

EL 表达式工作在服务器端

JSON 在服务器端生成,在客户端操作

对象引用

内存关系内容:

  1. 内存分配
  2. 垃圾回收

JDK>=1.2 四种引用关系

  1. 强引用:对象一直被引用
  2. 软引用:对象将长时间保存,一直到内存不足才被回收,常用于缓存,如 MyBatis
  3. 弱引用:一旦有垃圾收集操作之后就会被回收
  4. 虚引用:返回永远都是 null,就是不引用

1、强引用示例 Strong Reference

package demo;

public class ReferenceDemo {
    public static void main(String[] args) {
        Object obj = new Object();
        Object ref = obj; // 默认为强引用
        obj = null; // 原始的对象断开,引用关系依然存在
        System.gc(); // 垃圾回收
        System.out.println(ref);
        // java.lang.Object@2503dbd3
    }
}

2、软引用示例 Soft Reference

package demo;

import java.lang.ref.SoftReference;

public class ReferenceDemo {
    public static void main(String[] args) {
        Object obj = new Object();

        // 软引用
        SoftReference<Object> ref = new SoftReference<>(obj);
        obj = null; // 原始的对象断开
        System.gc(); // 垃圾回收

        System.out.println(ref.get());
        // java.lang.Object@2503dbd3
    }
}

3、弱引用示例 Weak Reference

package demo;

import java.lang.ref.WeakReference;

public class ReferenceDemo {
    public static void main(String[] args) {

        Object obj = new Object();

        // 使用弱引用
        WeakReference<Object> ref = new WeakReference<>(obj);

        obj = null;
        System.gc();

        System.out.println(ref.get());
        // null
    }
}

4、虚引用示例 Phantom Reference

package demo;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class ReferenceDemo {
    public static void main(String[] args) {

        Object obj = new Object();

        // 使用虚引用
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        PhantomReference<Object> ref = new PhantomReference<>(obj, queue);

        System.out.println(ref.get());
        // null
    }
}

多对多关系

数据表和简单 Java 类的映射转换

合成设计面模式的应用,相当于组合

数组的理解及应用

package demo;

class User {
    private String name;
    private int age;

    private Book[] books;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Book[] getBooks() {
        return books;
    }

    public void setBooks(Book[] books) {
        this.books = books;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

class Book {
    private String title;
    private double price;

    private User[] users;

    public Book(String title, double price) {
        this.title = title;
        this.price = price;
    }

    public User[] getUsers() {
        return users;
    }

    public void setUsers(User[] users) {
        this.users = users;
    }

    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                ", price=" + price +
                '}';
    }
}


public class MoreToMoreDemo {
    public static void main(String[] args) {
        // 实体类
        Book book1 = new Book("《三国演义》", 69.8);
        Book book2 = new Book("《红楼梦》", 76.9);
        Book book3 = new Book("《西游记》", 88.6);

        User user1 = new User("赵小四", 23);
        User user2 = new User("王小六", 26);
        User user3 = new User("刘小七", 27);

        // 数据关系
        book1.setUsers(new User[]{user1, user2});
        book2.setUsers(new User[]{user1, user3});
        book3.setUsers(new User[]{user2, user3});

        user1.setBooks(new Book[]{book1, book2});
        user2.setBooks(new Book[]{book1, book3});
        user3.setBooks(new Book[]{book2, book3});

        // 数据展示
        for (User user : book1.getUsers()) {
            System.out.println(user);
        }

        for (Book book : user1.getBooks()) {
            System.out.println(book);
        }
        /**
         * User{name='赵小四', age=23}
         * User{name='王小六', age=26}
         *
         * Book{title='《三国演义》', price=69.8}
         * Book{title='《红楼梦》', price=76.9}
         */
    }
}