每个开发必知必会的10个高级Java特性 Top 10 Advanced Java Features Every Developer Must Know


作者:禅与计算机程序设计艺术

1.简介

在java开发中,各种高级特性无处不在,但是如何用好它们才能提升我们的工作效率、代码质量和代码可维护性?本文从最基础的语法到最复杂的设计模式,全面系统地剖析了java中的10个高级特性,并结合实际案例进行详细解析,让读者能够快速掌握和实践这些特性。

阅读本文前,建议读者对java语言的基本语法、面向对象编程和集合框架有一定了解。

1.1. 为什么要学习java高级特性
Java是目前最流行的跨平台编程语言之一,几乎所有主流企业都选择了Java作为其开发语言。因此,学习java高级特性对于将java应用于实际项目中非常重要。学习java高级特性可以帮助我们提升我们的工作效率、代码质量和代码可维护性,能够解决我们日常遇到的诸如线程安全、垃圾回收、反射等方面的问题。

1.2. 本文的目的
通过学习和实践java高级特性,你可以有效地提升你的编程能力,改善你的代码质量,避免出现意想不到的bug。

1.3. 文章组织结构
本文将分章节进行讲解,每章节会从相关知识点出发,逐步深入到细节的实现方法。在每个部分的最后,还会提供一些学习该特性所需的资源和参考资料。

2. 基本语法

2.1. java中的类加载机制

JVM中的类加载机制分为三种:简单加载、触发加载和自定义加载。

简单加载:通过类的完全限定名来加载类的过程称为简单加载。当编译器看到类A的引用时,只需知道类A的完全限定名即可完成加载,不需要搜索类路径或者其他相关信息。

触发加载:当JVM运行时,如果某个类需要被使用的情况,并且加载这个类的类也已经加载过了,那么这种加载方式称为触发加载。比如说,有一个线程要使用类A,而这个类还没有被加载过,此时系统就会触发加载过程。

自定义加载:有时候我们希望自己定义一个类加载的方式,自定义类加载的方式可以通过继承java.lang.ClassLoader类来实现。自定义类加载可以根据自己的需求来控制类的加载行为。

2.2. java访问修饰符

java中共有四种访问权限修饰符:public、protected、default(package-private)和private。它们的特点如下:

    - public:任何类都可以访问public修饰的方法或字段;
    
    - protected:只有同一个包内的子类可以访问protected修饰的方法或字段;
    
    - default(package-private):默认情况下,包内的所有类都可以访问default修饰的方法或字段,包外的类则无法直接访问该字段和方法。也就是说,当一个类中包含了一个包内的类,这个包内的类默认是default修饰的;
    
    - private:私有的字段只能在同一个类中访问,外部类则无法访问该字段。

3. 对象及类

3.1. java中的多态

Java是一种支持多态机制的语言。多态指的是程序运行时能够自动调用正确的方法,这种机制允许程序员创建父类类型的变量或参数,使得子类类型也可以赋值给父类类型,这样就可以调用子类重写的父类方法。

3.2. java中的内部类

内部类是指一个类中定义在另一个类的内部,外部类可以通过内部类访问它的成员。内部类主要有两种:静态内部类和非静态内部类。

静态内部类可以访问外部类的静态字段和方法,它可以使用外部类的static方法,也可以访问外部类的静态变量和成员。例如:

public class Outer {
    static int num = 1;
    
    static class InnerStatic {
        public void printNum() {
   System.out.println("num in inner: " + num);
        }
    }
}

使用方式:

Outer outer = new Outer();
Outer.InnerStatic innerStatic = outer.new InnerStatic();
innerStatic.printNum(); // output: num in inner: 1

非静态内部类可以访问外部类的非静态字段和方法,但不能访问外部类的静态字段和方法,它可以使用外部类的非static方法,但不能访问外部类的static变量和成员。例如:

public class Outer {
    String str = "hello";
    
    class InnerNonStatic {
        public void printStr() {
   System.out.println("str in inner: " + str);
        }
        
        public void printOuterStr() {
   System.out.println("outer str: " + Outer.this.str);
        }
    }
}

使用方式:

Outer outer = new Outer();
Outer.InnerNonStatic innerNonStatic = outer.new InnerNonStatic();
innerNonStatic.printStr(); // output: str in inner: hello
innerNonStatic.printOuterStr(); // output: outer str: hello
    注意事项:

    - 一般来说,内部类命名规则是:OuterClass$InnerClass形式,但如果只是内部类中有一个方法没有加上public访问权限,则可以在内部类之前加上访问修饰符。
    
    - 当内部类中有多个构造函数时,需要通过外部类的实例化来指定构造函数的参数,否则会报错。例如:
public class Test {
    static class InnerTest {
public InnerTest(String name){
   System.out.println("inner test constructor with param.");
}

public InnerTest(){
   this("test");
}
    }
    
    public static void main(String[] args) {
Test.InnerTest it = new Test().new InnerTest();
    }
}

执行输出结果:

inner test constructor with param.

此时应该传入"test"字符串作为参数,而不是创建一个新的字符串。

4. 异常处理

4.1. java异常体系

java使用异常处理机制来管理错误和异常。java的异常体系由两个关键词throws和try/catch组成,分别用来声明可能会发生的异常以及捕获异常。

throws关键字用于声明一个方法可能抛出的异常类型。例如:

public class MyException extends Exception{
    public MyException(){
        super();
    }
    
    public MyException(String message){
        super(message);
    }
}

public class Calculator {
    public double divide(int a, int b) throws ArithmeticException {
        if (b == 0){
   throw new ArithmeticException("Divide by zero!");
        }
        return ((double)a)/((double)b);
    }
    
    public static void main(String[] args) {
        try {
   Calculator calculator = new Calculator();
   System.out.println(calculator.divide(10, 0));
        } catch (ArithmeticException e) {
   System.out.println("Caught exception: "+e.getMessage());
        }
    }
}

在main方法中,Calculator对象调用divide方法,传递两个整数a和b。如果除法运算结果为零,则会抛出ArithmeticException。main方法中通过try-catch语句来捕获该异常。

如果子类继承父类的方法且抛出相同或不同的异常,则子类的方法中也应使用throws关键字声明。例如:

public interface Shape {
    void draw();
}

public class Rectangle implements Shape{
    @Override
    public void draw() throws IOException{
        System.out.println("Drawing rectangle...");
    }
}

public class Square implements Shape {
    @Override
    public void draw() throws ClassNotFoundException{
        System.out.println("Drawing square...");
    }
}

public class DrawingApp {
    public static void main(String[] args) {
        List<Shape> shapes = Arrays.asList(new Rectangle(), new Square());
        for (Shape shape : shapes){
   shape.draw();
        }
    }
}

在DrawingApp类中,Rectangle和Square对象被放置在列表shapes中。由于接口Shape规定了必须实现的draw方法,因此这里所有的shape都必须实现该方法。程序执行的时候,调用画笔对象的draw方法来绘制图形,不同画笔对象具有不同的功能,因此画笔对象可能抛出IOException和ClassNotFoundException。如果DrawignApp代码中缺少了catch块来捕获这些异常,则会导致程序异常终止。

4.2. 自定义异常类

有时候,我们可能需要自定义自己的异常类,原因有以下几点:

  • 需要更精确地描述异常信息;

  • 需要在不同地方抛出和捕获不同的异常;

  • 需要更具体地分类异常。

下面是一个简单的自定义异常类的例子:

public class AccountNotFound extends RuntimeException {
    public AccountNotFound() {}
    
    public AccountNotFound(String message) {
        super(message);
    }
    
    public AccountNotFound(String message, Throwable cause) {
        super(message, cause);
    }
}

从RuntimeException继承,自定义的AccountNotFound异常类有三个构造函数,用来生成不同的异常类。如果我们想要抛出这个异常,只需在方法签名中添加 throws AccountNotFound即可。

public boolean withdraw(double amount) throws AccountNotFound {
    // some code to check whether the account exists or not and update balance 
    if (!exists()){
        throw new AccountNotFound("Account does not exist");
    } else if (balance < amount){
        throw new IllegalArgumentException("Insufficient funds");
    } else {
        balance -= amount;
        return true;
    }
}

如果withdraw方法需要抛出AccountNotFound异常,就需要在方法签名中添加 throws AccountNotFound。如果方法中存在一些逻辑判断,而某些条件下需要抛出不同的异常,则可以将不同类型的异常写入到throws语句中。

上面的自定义异常类仅作为示例,实际项目中自定义异常类还需要遵循一些规范和要求,具体请参考相关书籍和文档。

5. 多线程

5.1. java线程创建

java中有两种线程创建方式:继承Thread类或实现Runnable接口。

Thread类是一种具体线程,它继承自Object类和Runnable接口。通过重写run方法,可以实现线程的业务逻辑。例如:

public class MyThread extends Thread {
    private int count;
    
    public MyThread(String name){
        super(name);
    }
    
    public void run(){
        synchronized (this){
   for (int i=0;i<10;i++){
       System.out.println(getName()+" count is "+count++);
       try {
  wait();
       } catch (InterruptedException e) {
  e.printStackTrace();
       }
   }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread mythread1 = new MyThread("t1");
        MyThread mythread2 = new MyThread("t2");
        
        mythread1.start();
        mythread2.start();
    }
}

通过继承Thread类,可以方便地创建线程,并启动线程。

第二种创建线程的方式是实现Runnable接口,然后将线程封装到Thread类中。Runnable接口有一个run方法,在线程启动的时候会调用该方法。例如:

public class MyThread implements Runnable {
    private int count;
    
    public MyThread(String name){
        this.name = name;
    }
    
    public void run(){
        synchronized (this){
   for (int i=0;i<10;i++){
       System.out.println(name+" count is "+count++);
       try {
  wait();
       } catch (InterruptedException e) {
  e.printStackTrace();
       }
   }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread mythread1 = new MyThread("t1");
        MyThread mythread2 = new MyThread("t2");
        
        Thread t1 = new Thread(mythread1,"t1");
        Thread t2 = new Thread(mythread2,"t2");
        
        t1.start();
        t2.start();
    }
}

通过实现Runnable接口,可以把线程的业务逻辑单独封装起来,避免与线程启动相关的代码耦合在一起。

多线程同时运行,可能会产生线程安全的问题。为了解决这个问题,java提供了synchronized关键字,可以用来同步对共享资源的访问。

5.2. volatile关键字

volatile关键字是java提供的一个轻量级同步机制,主要作用是在线程间通信和访问volatile变量时可见,可禁止指令重排序。

对volatile变量进行写操作后,不会立即刷新缓存区,而是等待其他线程通知缓存失效。也就是说,当多个线程同时修改同一个volatile变量时,其它线程总是能看到该变量的最新值。

用volatile声明的变量,不保证原子性,但是能降低同步开销,提升性能。

注意事项:

  • volatile变量只能用于线程间通信,不能保证原子性;

  • 变量修改后,缓存的旧值就变成无效数据,必须重新从主内存读取;

  • volatile变量不要滥用,通常适用于那些状态能够变化,且线程频繁交换的场景。

5.3. wait和notify

wait和notify是java提供的用来实现多线程之间的通信的两个方法。

notifyAll()方法唤醒所有正在wait的线程,notify()方法唤醒一个正在wait的线程,从等待队列中选择一个线程。

wait()方法进入等待状态,直到被notify()方法唤醒。

当wait()方法被调用时,锁释放,其他线程便有机会执行;当notify()/notifyAll()方法被调用时,锁被重新获取,正在wait的线程进入就绪状态,等待获取锁的权利。

wait()和notify()/notifyAll()方法必须配合synchronized关键字一起使用。

public class WaitNotifyDemo {
    private Object lock = new Object();
    private int count = 0;
    
    public void increment() {
        synchronized (lock) {
   while (count!= 0) {
       try {
  lock.wait();
       } catch (InterruptedException e) {
  e.printStackTrace();
       }
   }
   count++;
   System.out.println("Incremented Count to: "+count);
   lock.notifyAll();
        }
    }
    
    public void decrement() {
        synchronized (lock) {
   while (count == 0) {
       try {
  lock.wait();
       } catch (InterruptedException e) {
  e.printStackTrace();
       }
   }
   count--;
   System.out.println("Decremented Count to: "+count);
   lock.notifyAll();
        }
    }
    
    public static void main(String[] args) {
        final WaitNotifyDemo demo = new WaitNotifyDemo();
        
        ExecutorService executor = Executors.newCachedThreadPool();
        executor.execute(() -> {
   for (int i = 0; i < 10; i++) {
       demo.increment();
   }
        });
        executor.execute(() -> {
   for (int i = 0; i < 10; i++) {
       demo.decrement();
   }
        });
        executor.shutdown();
    }
}

在main方法中,创建了一个ExecutorService,用于模拟两个线程对一个变量的操作。其中,increment方法在循环中进行,调用notifyAll()方法通知等待的线程,防止程序死锁;decrement方法也是类似。

执行结果如下:

Incremented Count to: 1
Decremented Count to: 9
Incremented Count to: 2
        ...
        ...
Deincremented Count to: 0

从输出结果可以看出,两个线程按顺序对count变量进行递增和递减操作。

注意:

  • wait()和notify()方法只能在同步代码块中使用,否则会报IllegalMonitorStateException异常;

  • wait()和notify()方法在调用时都会释放锁,重新获取锁后才继续执行;

  • wait()和notify()方法必须放在同步代码块中,调用时必须获得相应对象的锁,否则会报IllegalMonitorStateException异常。

6. 集合框架

6.1. Collections工具类

Collections工具类包含许多针对集合的常用操作,包括排序(sort)、查找(binarySearch)、替换(replaceAll)。

sort()方法可以对集合元素进行排序,参数是一个Comparator接口的匿名实现类,也可以使用Collections.reverseOrder()等内置的比较器。

binarySearch()方法可以在已排好序的list或array中查询指定元素的索引位置,如果不存在则返回-(插入点+1)。

replaceAll()方法可以利用指定表达式对集合中的元素进行替换。

import java.util.*;

public class CollectionUtils {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(7, 2, 9, 4, 5, 1);
        Comparator<Integer> cmp = (o1, o2) -> Integer.compare(o2, o1);
        
        Collections.sort(list, cmp);
        System.out.println(list); // [9, 7, 5, 4, 2, 1]
        
        Collections.sort(list);
        System.out.println(list); // [1, 2, 4, 5, 7, 9]
        
        Integer key = 5;
        int index = Collections.binarySearch(list, key);
        System.out.println(index); // 2
        
        int factor = 2;
        Collections.replaceAll(list, oldVal -> oldVal * factor);
        System.out.println(list); // [2, 4, 10, 10, 14, 18]
    }
}

第一个例子中,使用自定义的比较器进行排序,按照倒序的方式进行。第二个例子中,排序方式采用的是 Collections.reverseOrder()。第三个例子中,使用二分查找方法查询key元素的索引位置,key值为5。第四个例子中,用lambda表达式代替传统的替换方法,对所有元素进行乘以2的操作。

除了上述常用的操作外,Collections还提供了很多其他的方法,例如计算集合中元素个数(size)、遍历集合(forEach)、检查集合是否为空(isEmpty)、检测集合中的元素是否唯一(frequency)、复制集合(copyOf)等。

6.2. 迭代器和生成器

Iterator接口是java集合中用于存取元素的一种方式。通过实现Iterator接口,可以把集合元素按照顺序迭代出来。例如,我们可以利用Iterator来遍历ArrayList中的元素:

import java.util.*;

public class IteratorsDemo {
    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        Iterator<Integer> iterator = arrayList.iterator();
        
        while (iterator.hasNext()) {
   System.out.println(iterator.next());
        }
    }
}

生成器表达式(Generator expression)是jdk1.8中新增加的一种高级for循环语法,可以用来遍历集合元素。与普通的for循环相比,生成器表达式可以省去显式创建循环变量的步骤,直接使用集合元素。

比较一下两种遍历集合元素的写法:

Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
for (Integer element : set) {
    System.out.println(element);
}

Set<Integer> set2 = new HashSet<>();
set2.add(1);
set2.add(2);
set2.add(3);
for (Iterator<Integer> iter = set2.iterator(); iter.hasNext(); ) {
    System.out.println(iter.next());
}

第一种写法使用Set接口来存储集合元素,采用for-each循环进行遍历。第二种写法是手动创建Iterator,使用while循环遍历。

虽然生成器表达式很方便,但是不是所有的集合都支持这种语法,而且在一些场景下生成器表达式会比Iterator更易于使用。所以,在选择遍历集合元素的方式时,需要结合具体的需求来决定。

7. IO编程

Java I/O是java语言的基础,对于程序的输入输出是必不可少的。

7.1. 输入/输出流

Java I/O流是Java对文件的输入/输出的抽象,InputStream和OutputStream分别代表输入流和输出流。

InputStream代表输入字节流,是源头,例如FileInputStream表示文件输入流, ByteArrayInputStream表示字节数组输入流。OutputStream代表输出字节流,是目的地,例如 FileOutputStream 表示文件输出流, ByteArrayOutputStream 表示字节数组输出流。

7.2. 文件I/O

Java提供了File类来处理文件,包括读取文件内容、创建文件、删除文件、移动文件等。

可以通过File类的createNewFile()方法创建空文件,通过File类的delete()方法删除文件,通过File类的renameTo()方法重命名文件,通过File类的isFile()、isDirectory()方法判断文件是否是文件还是目录,通过File类的length()方法获取文件的大小。

7.3. 序列化

Java提供ObjectOutputStream和ObjectInputStream类来实现对象的序列化和反序列化,可以把对象转换成字节序列,保存到磁盘,或从磁盘中恢复对象。

Serializable接口用来标记一个类是可序列化的,当一个类实现了Serializable接口之后,就可以使用ObjectOutputStream类的writeObject()方法来保存对象到磁盘,或通过ObjectInputStream类的readObject()方法从磁盘中恢复对象。

序列化是一种用来传输对象状态的机制,可以实现对象在网络上传输或保存到磁盘上的功能。

import java.io.*;

public class SerializationDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person person = new Person("zhangsan", 23);
        
        FileOutputStream fileOut = new FileOutputStream("person.ser");
        ObjectOutputStream out = new ObjectOutputStream(fileOut);
        out.writeObject(person);
        out.close();
        fileOut.close();
        
        FileInputStream fileIn = new FileInputStream("person.ser");
        ObjectInputStream in = new ObjectInputStream(fileIn);
        Person readPerson = (Person)in.readObject();
        in.close();
        fileIn.close();
        
        System.out.println(readPerson.getName()+","+readPerson.getAge());
    }
}

在main方法中,首先创建一个Person对象,接着打开文件输出流和对象输出流,并写入Person对象到磁盘。关闭流之后,再次打开文件输入流和对象输入流,并从磁盘中恢复Person对象,打印对象中的姓名和年龄。

Note:虽然ObjectInputStream的readObject()方法返回的是Object对象,但是在实际使用过程中,我们往往需要将其转型为具体的类对象,因为序列化后保存的是类的信息,而不是对象的实例。因此,需要显式的将Object对象转型为具体的类对象。

更多关于Java IO的内容,请参阅官方文档:https://docs.oracle.com/javase/tutorial/essential/io/

8. JDBC

JDBC(Java Database Connectivity)是java中的API,用于数据库连接和数据库操纵,是构建数据库应用程序的必备技术。

JDBC涉及到JDBC API、JDBC驱动程序和数据库。

JDBC API:Java Database Connectivity API,是Java定义的一套用于访问数据库的接口。该API定义了一系列类和接口,用于执行SQL语句、事务处理、ResultSet集、 PreparedStatements预处理SQL语句等。

JDBC驱动程序:Java的数据库驱动程序负责建立与数据库的连接、执行SQL语句、处理结果集等。JDBC驱动程序包括SQLServer、MySQL、Oracle等。

数据库:数据库是存放数据的地方,它承载着数据、元数据、结构和规则。不同的数据库产品之间有差异,但是对JDBC程序员来说,无论使用哪种数据库,都可以使用相同的API来操作数据库。

JDBC API定义了四个主要的类和接口:Connection、Statement、PreparedStatement、ResultSet。

Connection类:用于建立与数据库的连接,可以理解为数据库的入口。该类的构造方法接受三个参数:数据库URL、用户名和密码。

Statement类:用于执行SQL语句,包括SELECT、INSERT、UPDATE、DELETE等。该类的executeUpdate()方法用于执行INSERT、UPDATE、DELETE等语句,executeUpdate()方法返回受影响的行数。

PreparedStatement类:PreparedStatement接口用于预编译SQL语句,通过占位符将SQL语句中需要输入的值绑定到PreparedStatement对象中,有效防止SQL注入攻击。PreparedStatement对象可以使用setInt()、setString()等方法设置占位符的值。

ResultSet类:用于存放查询结果,包括查询记录、查询列、查询元数据等。ResultSet接口提供的各种方法用于获取查询结果,包括getInt()、getString()等。

使用JDBC操作数据库的流程:

  1. 注册JDBC驱动程序;

  2. 获取数据库连接对象Connection;

  3. 创建Statement对象或PreparedStatement对象;

  4. 设置SQL语句、参数、超时时间、结果集类型等;

  5. 执行SQL语句并获取结果集ResultSet对象;

  6. 操作ResultSet对象,获取记录和元数据等;

  7. 关闭ResultSet、Statement、Connection对象。

测试JDBC连接数据库的例子:

import java.sql.*;

public class JDBCDemo {

    public static void main(String[] args) {
        String driverName = "com.mysql.cj.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
        String username = "root";
        String password = "password";

        try {
   Class.forName(driverName).newInstance();
   Connection connection = DriverManager.getConnection(url,username,password);
   System.out.println("Database connected successfully");

   Statement statement = connection.createStatement();
   ResultSet resultSet = statement.executeQuery("select * from employee where age >? order by salary desc limit?", new Object[]{30, 1});
   while(resultSet.next()){
      int id = resultSet.getInt("id");
      String name = resultSet.getString("name");
      float salary = resultSet.getFloat("salary");

      System.out.println("ID:" + id + ", Name:" + name + ", Salary:" + salary);
   }

   resultSet.close();
   statement.close();
   connection.close();
        } catch (SQLException | ClassNotFoundException | IllegalAccessException | InstantiationException e) {
   e.printStackTrace();
        }
    }
}

在main方法中,首先定义了数据库连接参数,包括驱动名称、URL、用户名、密码。接着调用Class.forName()方法加载驱动程序,创建Connection对象。创建完毕之后,创建Statement对象,并使用executeQuery()方法执行SQL语句,接收ResultSet对象。然后,遍历ResultSet对象,获取记录和元数据,并打印到控制台。最后,关闭ResultSet、Statement、Connection对象,退出程序。

JDBC API提供的方法很多,涉及方方面面,用到的方法大家可以根据自己的需要熟悉。

9. 反射

Java反射机制是用来在运行期确定类的属性、方法、构造函数,生成类的实例对象等。通过Reflection API可以获取运行时的class对象,然后调用class的方法,动态地创建对象和调用方法。

通过反射可以实现以下功能:

  • 根据指定的配置文件动态创建对象;

  • 根据用户的输入来动态加载类并创建对象;

  • 动态修改对象中的属性;

  • 修改字节码;

下面是通过反射创建Person对象并修改其属性的例子:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

  public class ReflectionDemo {

      public static void main(String[] args) {
 try {
     Class clazz = Class.forName("reflectiondemo.Person");
     Constructor constructor = clazz.getConstructor(String.class, int.class);
     Field field = clazz.getField("age");
     Method method = clazz.getMethod("eat", String.class);
     Person person = (Person)constructor.newInstance("Tom", 20);
     person.setGender("Male");
     method.invoke(person, "apple");
     System.out.println(person.toString());
 } catch (ClassNotFoundException e) {
     e.printStackTrace();
 } catch (NoSuchMethodException e) {
     e.printStackTrace();
 } catch (InstantiationException e) {
     e.printStackTrace();
 } catch (IllegalAccessException e) {
     e.printStackTrace();
 } catch (IllegalArgumentException e) {
     e.printStackTrace();
 } catch (InvocationTargetException e) {
     e.printStackTrace();
 } catch (NoSuchFieldException e) {
     e.printStackTrace();
 }
      }
  }
  
  package reflectiondemo;

   public class Person {

       private String name;
       private int age;
       private String gender;

       public Person() {}

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

       public String getName() {
  return name;
       }

       public void setName(String name) {
  this.name = name;
       }

       public int getAge() {
  return age;
       }

       public void setAge(int age) {
  this.age = age;
       }

       public String getGender() {
  return gender;
       }

       public void setGender(String gender) {
  this.gender = gender;
       }

       public void eat(String food) {
  System.out.println(food + " eaten by " + name);
       }

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

在main方法中,首先通过Class.forName()方法加载Person类,然后获取构造器和方法对象。构造器用Person类中的参数名调用,方法用方法名调用。创建Person对象,并调用方法和属性的setter方法。最后,打印Person对象。

以上就是反射的基本用法,大家可以根据自己的需要深入研究。

10. 设计模式

设计模式(Design Pattern)是一套被反复使用、思想灵活、高度抽象的经验总结,旨在对软件工程进行更系统的、科学的分析、设计和创造。

Java中常用的设计模式有:工厂模式、代理模式、装饰器模式、模板模式、观察者模式、适配器模式、策略模式、状态模式、迭代器模式、命令模式、职责链模式、备忘录模式、组合模式、享元模式、解释器模式等。

每种设计模式都有其特定的作用和目标,并提供了清晰而完整的编码规则和实现方案。使用设计模式可以有效地提升代码质量、提高软件的可扩展性和可维护性。

在本文中,我们已经了解了Java中常用的10种设计模式,并举了使用场景。接下来,我们将深入到具体的例子,通过动手实践,进一步巩固对设计模式的认识和理解。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 19
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

禅与计算机程序设计艺术

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

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

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

打赏作者

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

抵扣说明:

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

余额充值