写给大忙人看的 Java Core 读书笔记( 三、四章节)

第三章 接口和lambda表达式

一、接口

1、静态方法与默认方法

工厂方法在接口中非常有意义,可以用静态方法来设计,调用者无需关心接口的实例是哪些类。

public interface Fruit {
    //静态方法
    static Fruit buy(int price){
        Fruit fruit;
        if(price > 5)  fruit = new Banana();
        else fruit = new Apple();
        return fruit;
    }
    String eat();
    //默认方法
    default String fly(){
        return "The fruit fly";
    }
}

默认方法主要用于 接口演化 。比如说突然所有的水果会飞了,如果不用默认方法。
那么以前的 AppleBanana 不能编译。或者你没有重新编译类,但如果调用 fly 的方法,会发生 AbstractMethodError 异常。

**默认方法冲突:**如果一个类实现了两个接口,两个接口有名称和参数相同的方法且至少其中一个为默认方法,就必须解决冲突。
解决方法,在实例中覆盖原方法,实现自己的方法,或者委托其中一个。

public class Tomato implements Fruit,Vegetable{

    @Override
    public String fly() {
        //委托其中一个
        return Fruit.super.fly();
    }
    ......
}

如果继承父类且实现接口有同样方法,按父类的方法。

2、接口示例

标准 java 类库的四个使用接口的常见情况。
Comparable 接口:

public interface Comparable<T> {
    public int compareTo(T o);
}

对数组排序,调用类提供的比较方法。即实现此接口的类,重写了 comparTo 的排序时的比较方法。

Comparator 接口:

public interface Comparator<T> {
    int compare(T o1, T o2);
    ......
}

它的源代码除了 compare 需要实现的方法,还有一堆默认方法。
可以理解它为一个外置的排序规则,实现此方法的类,可以作为一种排序的规则放入 sort() 函数中。使用 lambda 可以更方便的使用此接口。

Runable 接口

public interface Runnable {
    public abstract void run();
}

Runnable task = new HelloTask();
Thread thread = new Thread(task);
thread.start();

实现 Runable 接口的实例,可以创建一个新线程执行任务。

UI 回调

public interface EventHandler<T extends Event> extends EventListener {
    void handle(T event);
}

javaFX 不太熟,就大二刚学java期末作业写过 GUI ,现在已经忘完了,略过。

二、Lambda表达式

Java中几乎一切都是对象,没有函数类型,作为替代,函数被表达成对象,也就是实现了特定接口类的实例,lambda表达式以一种便捷的语法来创建这样的实例。

1、方法引用

list.forEach(x-> System.out.println(x));
list.forEach(System.out::println);
方式代码等同描述
类::实例方法(x,y)->x.compareToIgnoreCase(y)String::compareToIgnoreCase第一个参数边为方法的接收者,其它参数传递给方法
类::静态方法x->Objects.isNull(x)System.out::println所有的参数传递给静态方法
对象::实例方法x-> System.out.println(x)System.out::println给定对象上调用方法,参数传递给实例方法

多个重名重载方法时,编译器会从上下文找到匹配的那个。在方法引用中可以捕获 thissuper

2、构造函数引用

String[] str = {"a","b"};
Stream<Apple> stream = Arrays.stream(str).map(Apple::new);
Object[] objects = stream.toArray();
Apple[] apples = stream.toArray(Apple[] ::new);

利用流构造一个对象数组

3、使用lambda表达式
lambda的内容太多了,以后单开一篇细写吧。。

4、局部类与匿名类

    private static Random generator = new Random();
    public  static IntSequence randomInts(int low,int high){
        //内部类
        class RandomSequence implements IntSequence{
            public int next() {return low+generator.nextInt(high - low +1);}
            public boolean hasNext() {return true;}
        }
        return  new RandomSequence();
    }
  • 类隐藏在方法范围内,方法外部它永远不可访问。
  • 局部类的方法可以访问来自闭合作用域的变量,就像 lambda 表达式的变量。
    private static Random generator = new Random();
    public  static IntSequence randomInts(int low,int high){
        //匿名类
        return new IntSequence{
            public int next() {return low+generator.nextInt(high - low +1);}
            public boolean hasNext() {return true;}
        }
    }

匿名类定义了该类的接口,实现方法,并构造一个该类的对象。

public  static IntSequence randomInts(int low,int high){
    //必须提供两个及以上方法时,匿名类才是必须的,不然可以直接用lambda表达式
    return () -> low + generattor.necrInt(high - low + 1);
}

第四章 继承和反射

实例变量和静态变量统称为域。类中的域、方法和嵌套类/接口统称为类成员。

一、继承

1、将一个子类方法赋值给父类是合法的,此时调用方法,虚拟机会查看对象的实际类型,并定位方法的版本,这个过程被称作 动态方法查找
父类赋值同样适用于数组,但可能导致类型错误。

Son[] sons = new Son[10];
Far[] fars = sons;
fars[0] = new Far();

这在编译时是合法的,但在运行时会错误。sonsfars 以用相同的 Son[] 数组,它不能持有更上层父亲 Far 的对象。

2、final 修饰方法时,子类不能覆盖方法,修饰类时,该类不能被继承。

3、super代替对象引用同样有效。super::instanceMethod.

二、Object:终极父类

Java 中的所有类都直接或间接的继承与 Object 类,一个类没有显式声明父类时,会隐式的继承Object。

1、toString 方法

Objetc 类默认打印类名与哈希码,一个对象与字符串连接时,Java 编译器会自动调用 toString。

2、equals 方法

equals 默认是判断两个引用是否相等。

无论何时覆盖 equals 方法,必须同时提供一个兼容的 hashCode 方法。

//一个 equals 方法实例,当物品描述和价格均相等时,认为是相等的。
public class Item {
    private String description;
    private double price;
    ......
    @Override
    public boolean equals(Object o) {
        
        if (this == o) return true;

        if (o == null || getClass() != o.getClass()) return false;

        Item item = (Item) o;

        return Objects.equals(description,item.description) && price == item.price;
    }

    @Override
    public int hashCode() {
        ......
    }
}

getClass()instanceof,用 getClass(),因为其中一个类可能是另一个类的子类。而且调用 equals() 的要求之一它是对称的。对于非空对象 x 和 y,x.equals(y)y.equals(x) 需要相同的返回值。
如果是用 instanceof 判断的话,fa.equals(son) 可能会返回 true,但 son.equals(fa) 却会一定返回 false
只有一种情况使用 instanceof 检查是行得通的:如果相等性以及在父类中固定。就可以使用 instanceof 方法,并将 equals 方法声明为 final

3、hashCode方法

hashCodeequals 方法必须是兼容的。如果 x.equals(y)true,那么 x.hashCode() == y.hashCode()

    @Override
    public int hashCode() {
        return Objects.hash(description,price);
    }
//如果有数组的话,首先使用Array.hashCode()计算一个哈希码,然后再传给Object.hash().

在一个接口中绝不能使重新定义的 Object 类方法成为默认方法。特别是,接口绝不能为 toStringequalshasCode 定义默认方法。由于类比接口优先,所以这样的方法会被一直使用。

4、克隆对象

Object.clone() 是浅拷贝,只能简单拷贝所有实例变量,如果实例变量都是基本类型或者不可改变,那没问题。但如果它们不是,那么原对象和克隆对象将共享可变的状态。
简单来说就是 Stringint 之类的没问题,但 ListSet 这种集合克隆就比较复杂了,都有一些不同的深拷贝方法。
Cloneable 接口:是个没有任何方法的接口,称作标签接口。Object.clone 方法执行浅拷贝前会检查这个接口是否被实现,如果没有会抛出 cloneNotSupperedException

5、枚举类

枚举常量的构造次序在静态成员之前,所以不能在枚举构造函数中引用任何静态成员。解决方法是在一个静态初始化块中初始化。

三、运行时类型信息和资源

1、Class 类

Object obj = ...;
Class<?> cl = obj.getClass();
Class<?> cl2 = Class.forName(className);
//获取类对象有3种方式
Class<?> cl = Class.forName
Class<?> cl = ClassName.class
Class<?> cl = new Class().getClass()

Class 类有许多有用的方法:jdk8文档在线
比如资源加载、类加载器、服务加载等,在配置一些框架的时候较为常用。

2、反射

反射机制允许程序在运行时检查任意对象的内容,并调用它们的任意方法。

反射先得到 Class 类对象,然后再用类对象的方法去创建对象、访问属性、调用方法。
Spring依赖注入,反转控制 就是用了反射机制。
下边是个简单的模仿 Spring 原理,spring.txt 就可以看作 xml 中读取的信息注入对象,然后 Test.java 中根据文件读取的内容实例化类,调用方法。

//spring.txt
class=reflection.Service1
method=doService1
//Test.java
public class Test {
 
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void main(String[] args) throws Exception {
 
        //从spring.txt中获取类名称和方法名称
        File springConfigFile = new File("e:\\project\\j2se\\src\\spring.txt");
        Properties springConfig= new Properties();
        springConfig.load(new FileInputStream(springConfigFile));
        String className = (String) springConfig.get("class");
        String methodName = (String) springConfig.get("method");
         
        //根据类名称获取类对象
        Class clazz = Class.forName(className);
        //根据方法名称,获取方法对象
        Method m = clazz.getMethod(methodName);
        //获取构造器
        Constructor c = clazz.getConstructor();
        //根据构造器,实例化出对象
        Object service = c.newInstance();
        //调用对象的指定方法
        m.invoke(service);
         
    }
}

上边示例代码转自how2j

3、Proxy代理类

代理可以在运行时创建实现了给定接口或者接口集的新类,只有当你在编译时还不知道实现哪个接口时,才需要代理。
Spring 框架中 Bean 的作作用域 SessionRequest 的原理就是代理。

创建一个代理对象时,使用 Proxy 类的 newProxyInstance 方法,该方法需要三个参数:一个类加载器(null为默认)、一个 Class 对象数组,每个被实现的接口都有一个 Class 对象、调用处理器。

**调用处理器:**一个实现了 InvocationHandler 接口的类对象,这个接口只有一个方法:

public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

一个代理的示例:将一个数组初始化为 Integer 对象的代理。

    public static void main(String[] args) {
        Object[] values = new Object[1000];

        //此处对 Integer 的接口进行了代理,代理了接口所有的方法,具体在编译时才知道实现的哪个接口。
        for (int i = 0; i < values.length; i++) {
            Object value = new Integer(i);
            values[i] = Proxy.newProxyInstance(
                null, value.getClass().getInterfaces(), 

                //此处是InvocationHandler接口的lambd的匿名类实现
                (Object proxy, Method m, Object[] margs) -> {
                    System.out.println(value + "." + m.getName() + Arrays.toString(margs));
                    return m.invoke(value, margs);
                });
        }

        //二分查找时,才知道是 Integer 实现类。
        int position = Arrays.binarySearch(values, new Integer(500));
        System.out.println(values[position]);
    }

即使不在代码中显式调用 CompareTo 方法,通过代理还是会调用,Integer 的任何接口的所有方法都是被代理的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值