41 Java多线程

多线程创建

Java创建一个新的执行线程有两种方法

方法一

定义一个类继承Thread类,然后重写Thread类的run方法,创建新的对象,启动线程

小演示:

public class MyThread extends Thread{
    @override
    public void run(){
        for(int i=0; i<100; i++){
            System.out.println(i);
        }
    }
}

public class MyThreadDemo{
    public static void main(String[] args){
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();
        my1.start();
        my2.start();
    }
}
方法二

定义一个类实现Runnable接口,重写run方法,然后创建Thread类时把这个类当构造方法的参数传递过去

public class MyRunnable implements Runnable{
    @Override
    public void run(){
        for(int i=0; i<100; i++);
    }
}

public static void main(String[] args){
    MyRunnable my = new MyRunnable();
    Thread t1 = new Thread(my, "线程1");
    Thread t2 = new Thread(my, "线程2");
    
    t1.start();
    t2.start();
}

相比于继承Thread类,实现Runnable接口的好处有

  • 避免了Java单继承的局限性(继承了Thread就不能继承其他的了,如果用Runnable就灵活很多)
  • 适合多个相同程序去处理同一个资源的情况,线程和代码、数据分离,面向对象性更好

线程名字相关

Thread类中获取和设置线程名称的方法

void setName(String name):将此线程的名称更改为name

String getName():返回此线程的名称

static Thread currentThread():返回正在执行的线程对象(使用Thread.currentThread().getName()就可以得到当前执行线程的名称)

也可以写一个带参构造方法

public class MyThread extends Thread{
    public MyThread(){}
    public MyThread(String name){    //直接调用父类的带参构造,用super传过去
        super(name);
    }
}

线程调度

Java使用的是抢占式时间片调度方式

线程优先级的设置

线程默认的优先级为5,Thread类默认优先级范围为1~10,数字越大越优先

线程优先级高只会提高获取时间片的几率,不会一直抢占优先级低的

Thread类中设置和获取线程优先级的方法:

  • public final int getPriority():返回此线程的优先级
  • public final void setPriority(int newPriority):设置此线程的优先级
线程控制

相关方法:

方法名说明
static void sleep(long millis)将当前执行的线程暂停指定的毫秒数
void join()等待调用此方法的线程死亡后,后续线程才能执行
void setDeamon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,JVM将会退出

线程同步

和《操作系统》描述的一样,程序的执行如果涉及到临界区资源的时候,必须要采取线程同步来解决的。Java提供了同步代码块来实现进程同步操作临界区资源。

同步代码块与synchronized关键字

格式:

synchronized(任意对象){
    操作临界区资源的代码
}

synchronized(任意对象):相当于加锁操作

卖票的示例:

public class SellTicket implements Runnable{
    private int tickets = 100;
    private Object obj = new Object();
    
    @Override
    public void run(){
        while(true){
            synchronized(obj){
                if(tickets > 0){
                    try{
                        Thread.sleep(100);
                    } catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                                       +"sells"
                                       +tickets);
                    tickets--;
                }
            }
        }
    }
}

弊端:当线程很多时,每个线程都会去判断锁,这回耗费很多资源,降低运行效率

同步方法与synchronized关键字

使用synchronized关键字来修饰方法可以实现方法锁,其格式为

修饰符 synchronized 返回值类型 方法名(方法参数){}

这种同步方法锁的对象为this

如果同步静态方法的话,也就是

修饰符 static synchronized 返回值类型 方法名(方法参数){}

这种同步方法锁的对象为类名.class

Java实现生产者消费者模型进程同步

Java的Object类中提供了以下方法来实现:

方法名说明
void wait()使得当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法
void notify()唤醒单个处于等待的单个线程
void notifyAll()唤醒所有处于等待的线程

线程安全的一些类

  • StringBuffer

    • StringBuilder类似,但是它是实现了线程安全的
  • Vector

    • ArrayList类似,但是实现了线程安全
  • Hashtable

    • HashMap类似,但是实现了线程安全
  • Collections中的一些方法

    • 例如Collections.synchronizedList(List list)可以将指定列表返回为实现线程安全的列表

Lock锁

从JDK5开始,Java提供了一个新的锁对象Lock,它可以实现比synchronized更广泛的锁定操作

Lock是接口不能直接实例化,要使用其实现类ReentrantLock来实例化

使用lock()方法来加锁,unlock()方法来解锁

为了防止在临界区内的执行发生错误导致死锁,所以使用try...finally...来修饰临界区代码

public class SellTicket implements Runnable{
    private int tickets = 100;
    private Lock lock = new ReentrantLock();
    
    @Override
    public void run(){
        while(true){
            try {
                lock.lock();
                if(tickets > 0){
                try{
                    Thread.sleep(100);
                } catch(InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()
                                   +"sells"
                                   +tickets);
                tickets--;
                }
            } finally{
                    lock.unlock();
            }
        }
    }
}

42 Java网络编程

InetAddress类的使用

为了方便我们对IP地址的获取和操作,Java提供了一个InetAddress供我们使用

其常用方法有:

方法名说明
static InetAddress getByName(String host)确定传入参数的IP地址,可以传机器名称或IP地址
String getHostName()获取此IP地址的主机名
String getHostAdrdress()返回文本显示中的IP地址字符串
InetAddress address = InetAddress.getByName("szncu");
String name = address.getHostName();
String ip = address.getHostAddress();
System.out.println(name+ip);

Java与UDP

Java提供了DatagramSocket类作为基于UDP协议的Socket

发送数据

具体发送数据的步骤:

  • 创建发送端的Socket对象(DatagramSocket)

    • 无参构造方法创建DatagramSocket()对象
  • 创建数据,并把数据打包(DatagramPacket类)

    • DatagramPacket(byte[] buf, int length, InetAddress address, int port)
  • 调用DatagramSocket对象的方法发送数据

    • void send(DatagramPacket p)
  • 关闭发送端

    • void close()
DatagramSocket ds = new DatagramSocket();
byte[] bys = "hello world and udp".getBytes();
//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("szncu"), 23333);
ds.send(dp);
ds.close;
接收数据
  • 创建接收端的Socket对象(DatagramSocket)

    • 带参构造方法DatagramSocket(int port)
  • 创建一个数据包,用于接收数据

    • DatagramPacket(byte[] buf, int length)
  • 调用DatagramSocket对象的方法接收数据

    • void receive(DatagramPacket p)
  • 解析数据包,并把数据在控制台显示

    • byte[] getData()
    • int getLength()
  • 关闭接收端

    • void close()
DatagramSocket ds = new DatagramSocket(23333);
//创建一个接收窗口
byte[] bys = new byte[1024];
//DatagramPacket(byte[] buf, int length)
DatagramPacketd dp = new DatagramPacket(bys, bys.length);
ds.receive(dp);
//byte[] getData()获取数据缓冲区
//int getLength()获取长度
byte[] datas = dp.getData();
int len = dp.getLength();
System.out.println(new String(datas,0 len));
ds.close();

Java与TCP

Java为客户端提供了Socket类,为服务端提供了ServerSocket

发送数据
  • 创建客户端的Socket对象(Socket)

    • Socket(String host, int port)
  • 获取输出流,写数据

    • OutputStream getOutputStream()
  • 释放资源

    • void close()

Socket类中有返回此套接字的字节输出流方法:getOutputStream()方法

Socket s = new Socket("127.0.0.1", 10000);
OutputStream os = s.getOutputStream();
os.write("hello,tcp".getBytes());
s.close();
接收数据
  • 创建服务端的SOcket对象(ServerSocket)

    • ServerSocket(int port)
  • 监听客户端连接,返回一个Socket对象

    • Socket accept()
  • 获取输入流,读数据,并把数据显示在控制台

    • InputStream getInputStream()
  • 释放资源

    • void close()
ServerSocket ss = new ServerSocket(10000);
//监听socket并接受
Socket s = ss.accept();
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
String data = new String(bys, 0, len);
System.out.println(data);
s.close();
ss.close();

ps:shutdownOutput()方法可以输出一个结束标记给服务端

43 Lambda表达式

Java平台从Java 8开始,支持函数式编程

Lambda表达式的标准格式
  • 形式参数,使用一对小括号传递
  • 箭头,也就是->
  • 代码块,使用大括号包含
(形式参数) -> {代码块}

使用Lambda表达式的前提:

  • 有一个接口
  • 接口中有且只有一个抽象方法

无参数情况

public class EatableDemo{
    public static void main(String[] args){
        useEatable(() -> {
            System.out.println("eat");
        });
    }
    
    private static void useEatable(Eatable e){
        e.eat();
    }
}
//---------分割线-------------
public interface Eatable{
    void eat();
}

带一个参数的情况

public class LambdaDemo {
    public static void main(String[] args) {
        useEatable((String a) -> {
            System.out.println(a);
            System.out.println("delicious");
        });
    }

    static void useEatable(Eatable e){
        e.eat("food");
    }
}
//---------分割线-------------
public interface Eatable {
    void eat(String food);
}

带参数和返回值的情况

public class LambdaDemo {
    public static void main(String[] args) {
        useAddable((int a, int b) -> {
            return a + b;
        });
    }

    static void useAddable(Addable a) {
        System.out.println(a.add(1, 2));
    }
}
//---------分割线-------------
public interface Addable {
    int add(int x, int y);
}

Lambda表达式的省略情况

参数类型可以省略

public class LambdaDemo {
    public static void main(String[] args) {
        useAddable((a,b) -> {        //省略了int
            return a + b;
        });
    }

    static void useAddable(Addable a) {
        System.out.println(a.add(1, 2));
    }
}
//---------分割线-------------
public interface Addable {
    int add(int x, int y);
}

如果参数只有一个的话,小括号也能省略

public class LambdaDemo {
    public static void main(String[] args) {
        useEatable(a -> {            //省略了小括号
            System.out.println(a);
        });
    }

    static void useEatable(Eatable e){
        e.eat("eat food");
    }
}
//---------分割线-------------
public interface Eatable {
    void eat(String food);
}

如果代码块的语句只有一条(没有return),可以省略大括号和分号,接上面的例子

public static void main(String[] args) {
        useEatable(s -> System.out.println(s));
    }

如果只有一条且有return, return也能省略

public static void main(String[] args) {
    useAddable((a,b) -> a + b);
}

Lambda注意事项
  • 使用Lambda必须要有接口,而且接口中有且只有一个抽象方法
  • 必须有上下文环境,才能推导出Lambda对应的接口
Lambda与匿名内部类的区别
  • 所需类型不同

    • 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
    • Lambda表达式:只能是接口
  • 使用限制不同

    • 如果接口中有且仅有一个抽象方法,可以使用Lambda,可以使用匿名内部类
    • 如果有超过一个以上的抽象方法,就只能使用匿名内部类了
  • 实现原理不同

    • 匿名内部类:编译之后会产生一个单独的.class文件
    • Lambda表达式:不会产生,对应的字节码文件会在运行的时候动态生成

44 Java方法引用

对于一段Lambda表达式代码

public class LambdaDemo {
    public static void main(String[] args) {
       usePrintable(s -> System.out.println(s));
    }

    static void useEatable(Eatable e){
        e.eat("eat food");
    }
}
//---------分割线-------------
public interface Eatable {
    void eat(String food);
}

使用方法引用,Lambda表达式就可以改写为

usePrintable(System.out::println);

推导与引用

  • 如果使用Lambda,那么根据可推导就是可省略的原则,无需指定参数类型,也无需指定的重载形式,他们都将会被自动推导
  • 如果使用方法引用,也是同样可以根据上下文进行推导
  • 方法引用是Lambda的孪生兄弟

Lambda表达式支持的方法引用

常见的引用方式:

  • 引用类方法
  • 引用对象的实例方法
  • 引用类的实例方法
  • 引用构造器
引用类方法

引用类方法就是引用的静态方法,格式:

类名::静态方法
Integer::parseInt
public class YYLDemo {
    public static void main(String[] args) {
        useConverter(Integer::parseInt);
    }

    static void useConverter(Converter c){
        System.out.println(c.convert("666"));
    }
}

public interface Converter {
    int convert(String s);
}
引用对象的实例方法

就是引用类中的成员方法,格式:

对象::成员方法
"HelloWorld"::toUpperCase    ->   引用了String类中的toUpperCase()方法
public class YYDXDemo {
    public static void main(String[] args) {
        usePrinter(PrintString::printUpper);    //HELLO
    }

    static void usePrinter(Printer printer){
            printer.printUpperCase("hello");
    }
}

public class PrintString {
    public static void printUpper(String s){
        System.out.println(s.toUpperCase());
    }
}

public interface Printer {
    void printUpperCase(String s);
}
引用类的实例方法

也就是引用类中的成员方法

类名::成员方法
String::substring    ->     String subString(int beginIndex, int endIndex)
                         截取字符串返回一个子串
public class LSLDemo {
    public static void main(String[] args) {
        useMyString(String::substring);
    }

    private static void useMyString(MyString myString){
        System.out.println(myString.mySubString("hello,world and java",1, 8));
    }
}

public interface MyString {
    String mySubString(String s, int a, int b);
}

PS: 参数的调用规则是,第一个为调用者,后面的全作为参数,比如用Lambda来写就是

useMyString((s, a, b) -> s.substring(a,b));
引用构造器

也就是引用构造方法,格式:

类名::new
Student::new

和上面一样,就不演示了

45 函数式接口

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,是Java8的新特性。

函数式接口可以被隐式转换为 lambda 表达式。

@FunctionalInterface注解标记的接口就为函数式接口

如定义了一个函数式接口如下:

@FunctionalInterface
interface GreetingService 
{
    void sayMessage(String message);
}

那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):

GreetingService greetService1 = message -> System.out.println("Hello " + message);

函数式接口作为方法的参数和返回值

Runnable接口就是一个函数式接口,它只有一个抽象方法run()

public class RunnableDemo {
    static void startThread(Runnable runnable){
        new Thread(runnable).start();
    }

    public static void main(String[] args) {
        startThread(() -> System.out.println("启动线程!"));
    }
}

这样我们把一个Lambda表达式作为参数传给了函数式接口

同样函数式接口也可以当做方法的返回值

public class ComparatorDemo01 {
    static Comparator<String> getComparator(){
        return Comparator.comparingInt(String::length);
    }
}

常用的函数式接口

Java 8提供了大量的函数式接口来供我们使用,这里主要记录4个重点接口

  • Supplier接口
  • Consumer接口
  • Predicate接口
  • Function接口
Supplier接口

Supplier<T>包含一个无参的方法

  • T get():获得结果
  • 该方法不需要参数,他会按照某种实现逻辑(由Lambda实现)返回一个数据
  • 也被称作生产性接口,如果我们制定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用
public class SupplierDemo {
    public static void main(String[] args) {
        System.out.println(getString(()-> "mion"));
    }

    private static String getString(Supplier<String> stringSupplier){
        return stringSupplier.get();
    }
}
Consumer接口

Consumer <T>接口包含两个方法

  • void accept(T t):对给定的参数执行操作(操作在Lambda中写)
  • default Consumer<T> andThen(Consumer after):返回一个组合的Consumer,先执行一个操作,再执行after操作
  • 它也叫做消费型接口,消费的数据类型由泛型指定
public class Reverse {
    public static void main(String[] args) {
        operatorString("mutotomu", System.out::println, name2 -> System.out.println(new StringBuilder(name2).reverse().toString()));

    }
    private static void operatorString(String str, Consumer<String> con1, Consumer<String> con2){
        con1.andThen(con2).accept(str);
    }
}
Predicate接口

常用的4个方法:

  • boolean test(T t):按照Lambda表达式进行判断,返回一个布尔值
  • default Predicate<T> negate():返回一个逻辑非
  • default Predicate<T> and(Predicate other):返回一个与组合判断
  • default PRedicate<T> or(Predicate other):返回一个或组合判断

Predicate接口常用于判断参数是否满足指定的条件

public static void main(String[] args){
    System.out.println(checkString("hello", s -> s.length() > 8 ));
    System.out.println(checkString("helloworld", s -> s.length() > 8));
}

private static boolean checkString(String s, Predicate<String> pre){
    return pre.gegate().test(s);
}
Function接口

常用的两个方法:

  • R apply(T t):将此函数应用于给定的参数
  • default <V> Function andThen(Function after):返回一个组合函数,走两步apply

Function接口通常用于对参数进行处理和转换,通过Lambda表达式写逻辑

public class FunctionDemo {
    public static void main(String[] args) {
        String s = "mion,22";
        transfer(s, string -> string.split(",")[1], string -> Integer.parseInt(string)+70);
    }

    private static void transfer(String s, Function<String, String> func1, Function<String, Integer> fun2){
        System.out.println(func1.andThen(fun2).apply(s));
    }
}

46 Stream流

Stream流一般与Lambda表达式结合起来使用,可以很大程度地精简代码

Stream流把真正的函数式编程风格引入到了Java当中

流生成

Stream流的生成方式
  • 生成流

    • 把数据源生成流
    • 使用list.stream()生成流
  • 中间操作

    • 打开流,做各种数据过滤、映射,返回一个新的流来继续操作
    • 使用filter()操作流
  • 终结操作

    • 一个流只能有一个终结操作,当这个操作执行之后流就无法再被操作,也是最后一个操作
    • 使用forEach()进行终结操作
Stream流的常见生成方式
  • Collection集合体系可以使用默认方法stream()来生成流

    • default Stream<E> stream()
  • Map集合体系要间接生成流
  • 数组可以通过Stream接口的静态方法of(T... values)生成流

例如

List<String> list = new ArrayList<>();
Stream<String> listStream = list.stream();    //Collection体系的List直接生成

Set<String> set = new HashSet<String>();
Stream<String> setStream = set.stream();    //Collection体系的Set直接生成

Map<String, Integer> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream();    //Map体系间接生成keySet流
Stream<Integer> valueStream = map.values().stream();    //间接生成value流
Stream<Map.Entry<String, INteger>> entryStream = map.entrySet().stream(); //间接

String[] strArray = {"hello", "world", "java"};
Stream<String> strArrayStream = Stream.of(strArray);    //调用静态方法生成流
Stream<Integer> intStream = Stream.of(10, 20, 30);

流常见中间操作

filter()方法

全称Stream<T> filter(Predicate predicate),配合Lambda表达式来写过滤操作,例如过滤出开头为AKB48的来输出

list.stream().filter(s -> s.starsWith("AKB48")).forEach(System.out::println);
limit()与skip()方法

Stream<T> limit(long maxSize):返回此流中元素组成的流,截取前指定参数个数的数据

Stream<T> skip(long n):跳过指定参数个数的数据,返回剩下元素组成的流

例如跳过两个元素,把剩下元素的前两个来输出的示例

list.stream().skip(2).limit(2).forEach(System.out::println);
concat()与distinct()方法

static <T> Stream<T> concat(Stream a, Stream b):合并a和b成为一个流(注意静态方法,通过接口来调用)

Stream<T> distinct():返回该流去重后的流,根据Object.equals(Object)静态方法生成

sorted()方法

Stream<T> sorted():返回自然排序之后组成的流

Stream<T> sorted(Comparator comparator):自定比较器来返回排序后的流

map方法

<R> Stream<R> map(Function mapper):返回给定函数作用于流之后的流,Function中有apply方法

IntStream mapToInt(ToIntFunction mapper):返回一个IntStream(原始int流),其中包含给定函数作用于此流元素之后的结果。ToIntFunction中有int applyAsint(T value)来返回原始int流

实例:将字符串转换为整数输出、获取sum

public class StreamDemo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("10");
        list.add("20");
        list.add("30");
        list.add("40");
        list.add("50");
        list.stream().map(Integer::parseInt).forEach(System.out::println);
        System.out.println(list.stream().mapToInt(Integer::parseInt).sum());
    }
}

流终结操作

常见的终结操作有两个

  • void forEach(Consumer action):每个元素进行操作

    • Consumer接口中的方法:void accept(T t)对给定参数执行操作
  • long count():返回此流中的元素数

流的收集操作

对数据使用Stream流的方式操作完毕后,要把流中的数据收集到集合中,可以使用Stream的流收集方法

  • R collect(Collector collector)
  • 该方法的参数是一个Collector接口

工具类Collectors提供了具体的收集方式

  • public static <T> Collector toList():收集到List集合中
  • public static <T> Collector toSet():收集到Set集合中
  • public static Collector toMap(Function keyMapper, Function valueMapper):收集到Map集合中

47 Java反射

类加载器

当程序要使用某个类时,如果该类还未加载到内存中,系统则会通过类的加载、类的链接、类的初始化来进行初始化。如果不出现意外情况,JVM将会连续完成这三个步骤,所以这三个步骤也被统称为类加载或者类初始化。

关于类的加载

  • 将class文件读入内存,并且为之创建一个java.lang.Class对象
  • 任何类被使用时,系统都会为之建立一个java.lang.CLass对象

关于类的连接

  • 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
  • 准备阶段:负责为类的类变量分配内存,并设置默认初始化值
  • 解析阶段:将类的二进制数据中的符号引用替换为直接饮用

关于类的初始化

  • 类的初始化步骤

    • 假如类还未被加载和连接,则程序先加载并连接该类
    • 假如该类的直接父亲还未被初始化,则先初始化其直接父亲(有递归,因此会先加载Object类)
    • 假如类中有初始化语句,则系统一次执行这些初始化语句
  • 类的初始化时机

    • 创建类的实例
    • 调用类的方法
    • 访问类或者接口的类变量,或者为该类变量赋值
    • 使用反射方式来强制创建某个类或者接口对应的java.lang.Class对象时
    • 初始化某个类的子类
    • 直接使用java.exe命令来运行某个主类
类加载器的作用
  • 负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象
JVM的全盘加载机制
  • 全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
  • 父类委托:当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才会尝试从自己的类路径中加载此类
  • 缓存机制:保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换为Class对象,存储到缓存区

反射

Java的反射是指程序在运行期可以拿到一个对象的所有信息

获取Class类对象的三种方式
  • 使用类的class属性来获取Class对象,例如Student.class
  • 调用对象的的getClass()方法,这个方法是Object类中的方法
  • 使用Class类中的静态方法forName(String className),传入参数为某个类的全路径(完整包名路径)
反射获取构造方法
方法名说明
Constructor<?>[] getConstructors()返回所有public构造方法形成的数组
Constructor<?>[] getDeclaredConstructors()返回所有构造方法的数组
Constructor<T> getConstructor(Class<?>... parameterTypes)返回单个public构造方法
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)返回单个构造方法

PS: Constructor类中用于创建对象的方法:T newInstance(Object... initargs)-根据指定构造方法创建对象

通过反射的方式来new一个对象:

Class<?> c = Class.forName("包路径");
Constructor<?> con = c.getConstructor(String.class, int.class, String.class);
Object obj = con.newInstance("mion", 22, "akb48");

使用反射来创建对象的效率不高,能new就直接new

使用setAccessible(Boolean boolean)方法可以暴力反射,从而实现通过私有的构造方法来创建对象

Class<?> c = Class.forName("包路径");
Constructor<?> con = c.getConstructor(String.class);
con.setAccessible(true);
Object obj = con.newInstance("mion");    //假设1个参数的带参构造方法为private

反射获取成员变量
方法名说明
Field[] getFields()返回public成员变量对象的数组
Field[] getDeclaredFields()返回所有成员变量对象的数组
Field getField(String name)返回单个public指定成员变量对象
Field getDeclaredField(String name)返回单个指定成员变量

PS: Field类当中的void set(Object obj, Object value)方法可以给成员变量赋值

//获取Class对象
Class<?> c = Class.forName("包路径名");
//new一个对象
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
//给成员变量赋值
Field nameFiled = c.getDeclaredField("name");    //获取name成员变量
nameField.setAccessible(true);    //绕过权限
nameField.set("mion")    //赋值为mion

反射获取成员方法
方法名说明
Method[] getMethods()返回所有public成员方法的数组,包括继承的
Method[] getDeclaredMethods()返回所有成员方法的数组,不包括继承的
Method getMethod(String name, Class<?>... parameterTypes)返回单个public成员方法的对象
Method getDeclaredMethod(String name, Class<?>... parameterTypes)返回单个成员方法的对象

PS: Method类中的Object invoke(Object obj, Object... args)方法可以调用obj对象的成员方法,参数是args,返回值是Object类型

//获取Class对象
Class<?> c = Class.forName("包路径名");
//new一个对象
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();

Method m = c.getDeclaredMethod("function");    //获取方法
m.setAccessible(true);    //修改权限
m.invoke(obj);    //调用方法

反射绕过泛型检查

直接上示例,获取原始方法需要的参数类型,从而绕过泛型检查

ArrayList<Integer> array = new ArrayList<>();
Class<? extends ArrayList> c = array.getClass();
Method m = c.getMethod("add", Object.class);
m.invoke(array, "mion");
m.invoke(array, "yuihan");
m.invoke(array, "yuiyui");
System.out.println(array);    //返回[mion, yuihan, yuiyui]

48 模块化与枚举

模块化

模块是Java 9引入的新特性,其基本使用步骤为

  • 创建模块
  • 在src目录下新建一个名为module-info.java的描述性文件,该文件专门定义模块名、访问权限、模块依赖等信息
  • 模块中所有未导出的包都是模块私有的,不能再模块之外被访问

    • 如果要导出,则使用exports PackageName关键字
  • 一个模块要访问其他的模块,必须明确指定模块依赖,未指定的不能访问

    • 配置模块依赖使用requires ModuleName关键字

比如实现module1访问module2的配置文件

//module1的module-info.java文件
module myOne{
    requires myTwo;
}
//module2的module-info.java文件
module myTwo{
    exports module2;
}
模块服务的使用

从Java 6开始,Java提供了一种服务机制,允许服务提供者和服务使用者之间完成解耦

在Java 9模块化之后,服务机制进一步简化。Java 9允许服务接口定义在一个模块中,并使用uses语句来声明该服务接口,并且针对该接口提供不同的服务实现类。服务实现类可以在不同的模块当中,服务实现模块使用provides语句为服务接口指定类


枚举

Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一个年的 12 个月份,一个星期的 7 天,方向有东南西北等。

Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 ,来分割。

例如定义一个颜色的枚举类。

enum Color 
{ 
    RED, GREEN, BLUE; 
} 
public class Test
{
    // 执行输出结果
    public static void main(String[] args)
    {
        Color c1 = Color.RED;
        System.out.println(c1);
    }
}

枚举值都是public static final类型的

枚举的迭代使用
enum Color
{
    RED, GREEN, BLUE;
}
public class MyClass {
  public static void main(String[] args) {
    for (Color myVar : Color.values()) {
      System.out.println(myVar);
    }
  }
}
在switch中使用枚举类
enum Color
{
    RED, GREEN, BLUE;
}
public class MyClass {
  public static void main(String[] args) {
    Color myVar = Color.BLUE;

    switch(myVar) {
      case RED:
        System.out.println("红色");
        break;
      case GREEN:
         System.out.println("绿色");
        break;
      case BLUE:
        System.out.println("蓝色");
        break;
    }
  }
}
枚举的常用方法

enum定义的枚举类默认继承了java.lang.Enum类,并实现了java.lang.Seriablizablejava.lang.Comparable两个接口。

values(),ordinal()valueOf()方法位于java.lang.Enum类中:

方法名说明
values()返回枚举类中的所有值
ordinal()返回每个枚举常量的索引
valueOf()返回指定字符串值的枚常量
枚举类成员

枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。

枚举既可以包含具体方法,也可以包含抽象方法。 如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。

enum Color
{
    RED, GREEN, BLUE;
 
    // 构造函数
    private Color()
    {
        System.out.println("Constructor called for : " + this.toString());
    }
 
    public void colorInfo()
    {
        System.out.println("Universal Color");
    }
}
 
public class Test
{    
    // 输出
    public static void main(String[] args)
    {
        Color c1 = Color.RED;
        System.out.println(c1);
        c1.colorInfo();
    }
}

输出结果为

Constructor called for : RED
Constructor called for : GREEN
Constructor called for : BLUE
RED
Universal Color
Last modification:May 11th, 2021 at 09:46 pm