Java基础笔记二


Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除

实现泛型接口可以指定或不指定泛型。

泛型可以通过反射绕过:

List<Integer> list = new ArrayList<>();
list.add(10);
//list.add("12"); // 报错
Class<? extends List> cl = list.getClass();
Method add = cl.getDeclaredMethod("add", Object.class);
add.invoke(list,"k1");
System.out.println(list);  // [10, k1]

equals() 方法存在两种使用情况:

  • 类没有覆盖 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Objectequals()方法。
  • 类覆盖了 equals()方法 :一般我们都覆盖 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。

重写 equals 时必须重写 hashCode 方法

哈希碰撞,碰撞了说明 hashcode 一样了,但是对象不一定相等。但是例如 HashSet是先检查 hashCode 再比较的(减少equals调用次数),所以需要重写hashCode 方法。

基本类型

基本类型 位数 字节 默认值
int 32 4 0
short 16 2 0
long 64 8 0L
byte 8 1 0
char 16 2 'u0000'
float 32 4 0f
double 64 8 0d
boolean 1 false

boolean 考虑计算机高效存储因素,依赖 JVM 厂商实现。

包装类和常量池

Java 基本类型的包装类的大部分都实现了常量池技术。Byte, Short, Integer, Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在[0,127]范围的缓存数据,Boolean 直接返回 True Or False。两种浮点数类型的包装类 Float, Double 没有实现常量池技术。

Integer i1 = 10;  // 装箱
Integer i2 = new Integer(10);
Integer i3 = Integer.valueOf(10);
System.out.println(i1==i2); // false
System.out.println(i1==i3); // true

对象不要用==直接比较。

静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法)。

Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。传递的是对象引用的拷贝。

image-20210907105609428

C++的引用传递是给 arr 取别名,不拷贝。意思就是 Java 不搞别名这一套。

重载:overloading,多个方法有相同的名字不同的参数。

重写:override,是子类对父类的允许访问的方法的实现过程进行重新编写。

返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。构造方法无法被重写。如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。构造方法不能重写。

面向对象三大特征:封装、继承、多态(父类的引用指向子类的实例)。

String不可变,finalStringBuffer(加了同步锁,线程安全)和StringBuilder(非线程安全)可变。

反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。

当我们不想访问或者不能直接访问一个对象的时候,我们就需要用到代理模式

代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后可以增加一些自定义的操作。动态代理的实现方式有很多种,比如 JDK 动态代理CGLIB 动态代理等等。

例子:

interface Animal {
    void eat();
}
class Pig implements Animal{

    @Override
    public void eat() {
        System.out.println("Pig eating...");
    }
}

静态代理:

class StaticProxy implements Animal {
    private Animal animal;
    StaticProxy(Animal ani) {
        this.animal = ani;
    }

    @Override
    public void eat() {
        animal.eat();
    }
}
public class Test {
    public static void main(String[] args) {
        Animal animal = new StaticProxy(new Pig());
        animal.eat();
    }
}

动态代理

JDK:

class DynamicProxy implements InvocationHandler {
    private Object object;
    public DynamicProxy(Object obj) {
        this.object = obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(object,args);
        return result;
    }
}

public class HelloWorld {
    public static void main(String[] args) {
        Animal animal = new Pig();
        Animal dynamicProxy = (Animal) Proxy.newProxyInstance(
                animal.getClass().getClassLoader(),
                animal.getClass().getInterfaces(),
                new DynamicProxy(animal)
        );
        dynamicProxy.eat();
    }
}

Java 标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例:

interface Animal {
    void eat(String food);
}

public class Test {
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if(method.getName().equals("eat")) {
                    System.out.println("Pig eat "+args[0]);
                }
                return null;
            }
        };
        Animal dynamicProxy = (Animal) Proxy.newProxyInstance(
                Animal.class.getClassLoader(),
                new Class[] {Animal.class},
                handler
        );
        dynamicProxy.eat("yadi");
    }
}

// 输出:
// public abstract void Animal.eat(java.lang.String)
// Pig eat yadi

静态代理和动态代理的对比

灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!

JVM 层面:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

使用 == 比较枚举类型

反射

通过反射可以获取任意一个类的所有属性和方法,还可以调用这些方法和属性。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制,动态代理的实现也依赖反射。

class Pig {
    private String name;
    public Pig() {
        name = "Pig";
    }
    public void eat(String s) {
        System.out.println("public pig eat "+s);
    }
    private void run() {
        System.out.println("running...");
    }
}

public class HelloWorld {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class<?> pigClass = Class.forName("Pig");
        Pig pig = (Pig)pigClass.getDeclaredConstructor().newInstance();
        /**
         * 获取类中所有方法
         */
        for(Method method:pigClass.getDeclaredMethods()) {
            System.out.println(method.getName());
        }
        /**
         * 调用指定方法
         */
        Method eat = pigClass.getDeclaredMethod("eat",  String.class);
        eat.invoke(pig,"food");

        /**
         * 修改参数
         */
        Field field = pigClass.getDeclaredField("name");
        // 取消安全检查
        field.setAccessible(true);
        field.set(pig,"pepe");

        /**
         * 调用private方法
         */
        Method run = pigClass.getDeclaredMethod("run");
        // 为了调用private方法取消安全检查
        // 不然会报错
        // class HelloWorld cannot access a member
        // of class Pig with modifiers "private"
        run.setAccessible(true);
        run.invoke(pig);
    }
}
// 输出
/**
 * run
 * eat
 * public pig eat food
 * running...
 */

IO

Java 中常见三种 IO 模型:BIO(Blocking I/O)、NIO(Non-Blocking/New I/O)、AIO(Asynchronous I/O)。

Java 中的 NIO 可以看作是 I/O 多路复用模型。IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间->用户空间)还是阻塞的。

Java的NIO(new IO)技术,使用的就是IO多路复用模型。在linux系统上,使用的是epoll系统调用。

多路复用IO的缺点:本质上,select/epoll系统调用,属于同步IO,也是阻塞IO。都需要在读写事件就绪后,自己负责进行读写,也就是说这个读写过程是阻塞的。

本博客后台 yadihttpd 为多路复用IO。

image-20210907140642358

参考

JavaGuide

10 分钟看懂, Java NIO 底层原理