Skip to content

JavaSE

约 4106 个字 169 行代码 预计阅读时间 16 分钟

静态方法和实例方法

在 Java 中,类和对象调用方法的机制和目的有所不同,这涉及到 Java 的面向对象编程的基本概念:类方法(静态方法)和实例方法。

类方法(静态方法)

  • 类方法是使用 static 关键字定义的方法,它属于类本身而不是类的某个实例。
  • 类方法可以直接通过类名调用,而不需要创建类的实例。例如,Math.sqrt() 是一个静态方法,你可以直接使用类名 Math 调用它。
  • 类方法通常用于那些不需要访问对象状态(即实例变量)的操作,因为它们无法访问实例变量或实例方法。

实例方法

  • 实例方法没有 static 关键字,必须在对象被创建之后通过对象实例来调用。
  • 实例方法可以访问类的实例变量和其他实例方法,这是因为它们可以在对象的上下文中操作,有权访问对象的内部状态和行为。

区别

  • 调用主体不同:静态方法由类本身调用,与特定实例无关;实例方法必须通过类的实例调用,与具体对象的状态有关。
  • 访问权限不同:静态方法不能访问实例变量和实例方法,因为它们不依赖于对象实例;而实例方法可以访问对象中的实例变量和其他实例方法,它们可以操作对象的具体状态。
  • 使用场景不同:静态方法适合于那些不需要对象状态数据的工具方法,如辅助计算、静态数据操作等;实例方法适用于需要访问或修改对象状态的场景。

理解这两者的区别有助于更好地组织代码和实现面向对象的设计原则,比如封装和抽象。

static

成员变量按照有无 static 修饰:

  • 类变量:有 static 修饰,相当于全局变量:内存中只有一份,会被类的所有对象共享。调用方法:类名.类变量
  • 实例变量:无 static 修饰,属于每个对象的变量。调用方法:对象.实例变量

成员方法

  • 类方法:public static void leiFunction(){ };
  • 实例方法:public void shiliFunction(){ };

注意事项:

  • 类方法:不能访问实例成员(不是对象,不能调用)
  • 实例方法:可以调用类成员和实例成员

工具类

  • 用类方法设计工具类
  • 工具类不需要创建对象,直接调用类方法即可。

类中的方法可以访问 私有变量

代码块

静态代码块

  • 格式: static { }
  • 特点:类加载时自动执行

动态代码块

  • 格式:{ }
  • 特点:创建对象时自动执行

单例设计模式

确保一个类只有一个对象

  • 把类的构造器私有
  • 定义一个类变量记住类的一个对象
  • 定义一个类方法,返回对象
// 单例类
public class A {
    // 定义一个类变量记住类的一个私有对象。
    private static A a = new A();

    // 私有构造器,使得不能创建对象
    private A() {

    }

    // 定义一个类方法返回对象
    public static A getObject() {
        return a;
    }
}

懒汉式单例设计模式

第一次访问对象时,才创建对象

private static b;

public static B getInstance() {
    if(b == null) {
        b = new B();
    }
    return b;
}

继承

子类继承父类的 非私有成员,关键词:extends

  • 子类不能访问父类的私有变量,但是可以通过 类方法(getset 方法)间接来访问和读取私有变量。

权限修饰符

image-20240502143642397

单继承

一个类只能继承一个父类

默认继承 Object 类,所以所有类的祖宗都是 Object 类。

重写和重载

重写:参数列表一样。

  • 可以使用 @Override 注解,他可以指定 java 编译器,检查我们重写的格式是否正确。
  • 访问权限一样,或者大于父类的权限(public > protected > 缺省)
  • 返回值类型一样,或者范围更小
  • 私有、静态方法不能被重写(因为是私有或全局的方法)

重载:参数列表不同。

访问其他成员

在子类中访问其他成员,是依照就近原则的

image-20240502151936573

子类构造器

默认会先调用父类的无参构造器,在执行自己的构造器。

super(); // 默认存在的 

有参构造器

public Teacher(String name, int age, String skill) {
    super(name, age);
    this.skill = skill;
}

image-20240503110157616

多态

将一个方法调用和一个方法体关联起来的动作称为绑定。在程序运行之前执行绑定(如 果存在编译器和链接器的话,由它们来实现),称为 前期绑定

动态绑定或 运行时绑定。当一种语言实现后期绑定时,必须有某种机制在 运行时来确定对象的类型,并调用恰当的方法。 Java 中的所有方法绑定都是后期绑定,并以多态方式发生。

image-20240503173455500

final

final 关键字时最终的意思

  • 不能被继承
  • 不能被重写
  • 修饰变量的时候只能被赋值一次

常量:使用 static final 修饰的变量,成称为常量。

抽象类

abstract 修饰,抽象方法只有函数声明,没有方法体。

  • 抽象方法的类是 抽象类
  • 抽象类不能创建对象
  • 一个类继承抽象类必须重写完抽象类的全部抽象方法(感觉是因为父类的方法只有函数声明的原因),否则这个类也必须定义为抽象类。

接口

关键字:interface,接口是用来被类实现 implement 的,一个类可以实现多个接口。

public interface 接口名 {
    // 成员变量:常量
    // 成员方法:抽象方法(不能写方法体)
}

接口是可以 多继承 的。

在类中实现接口,需要重写接口中的抽象方法。

image-20240506161436451

默认方法

default 修饰

默认方法有方法体,可以调用其他的方法。

image-20240506100155954

静态方法

static 修饰

匿名内部类

简化函数式接口@FunctionalInterface 的匿名内部类。

  • 匿名内部类转化为 lambda 表达式。
(形参列表) -> {
    // 方法体
}

Lambda 表达式

image-20240509172800547

lambda 表达式中捕获的变量必须实际上是最终变量 ( effectively final ) 实际上的最终变量是指, 这个变量初始化之后就不会再为它赋新值。

  • 参数类型可以省略不写
  • 如果只有一个参数,参数类型可以省略,同时 () 也可以省略
  • 如果方法体代码只有一行代码,可以省略 大括号不写,有 return 的话也不写。

image-20240506170941637

枚举

枚举的第一行只能罗列一些名称,这些名称都是常量,并且每个常量记住的都是枚举类的一个对象。

  • 枚举类的构造器只能是 私有的,所以不能创建对象
  • 枚举类是 final 的,不能被继承。

Collection

image-20240513110540739

常用方法

方法名 说明
public boolean add(E e) 添加元素
public void clear() 清空
public boolean remove(E e) 删除元素
public boolean contains(Object obj) 是否存在
public boolean isEmpty() 判空
public int size() 大小
public Object[] toArray() 转化为数组

迭代器

  • Inerator<E> iterator() 返回集合的第一个元素的迭代器。
  • E.next() 获取当前位置的元素,并将迭代器移动到下一位

List

  • ArrayList 顺序表(用数组实现的)
  • LinkedList 双向链表

Set

  • HashSet
  • LinkedHashSet
  • TreeSet

可变参数

JDK 21 特性

一个形参列表,只能有一个可变参数,且在最后面。

  • 可变参数在方法内部,本质是一个数组
public static void main(String[] args) {
        test();
        test("a",10);
        test("b",10,20);
        test("c",new int[] {1,2,3,4,5,6});
}
public static void test(String name,int...nums) {
    System.out.println(nums.length);

}

Map

键值对

image-20240513164947245

HashMap 无序,基于红黑树实现

TreeMap 有序

Stream

image-20240513191518402

  • filter:筛选数据,用 lambda:

    scores.stream().filter(s -> s >= 60).forEach(s -> System.out.println(s));
    
  • Map: 映射,把原流的数据,映射到另一个 值。

    Students.stream().filter(s -> s.getHeight() > 168)
        .map(s -> s.getName()) // Student::getName
        .distinct() // 去重
        .forEach(System.out::println) // 输出
    

收集stream 流

流只能收集一次!!!!

  • 打印:forEach(System.out::println);
  • 放到集合中:collect(Collectors.toSet();
  • 放到数组中:toArray();

image-20240514110745229

IO

文件

获取文件信息

方法名 说明
public boolean exists() 判断文件是否存在
public boolean isFile() 是否为文件
public String getName() 获取文件名称
public long length() 获取文件的大小
public String getAbsolutePath() 获取绝对路径

创建、删文件

  • createNewFile(): 创建文件
  • mkdir(): 创建文件夹
  • mkdirs(): 可以创建多级文件夹
  • delete() 删除文件、空文件夹,但 不能删除非空文件夹

遍历文件夹

  • public String[] list(): 将当前目录下的 一级文件名称,返回到一个字符串数组
  • public File[] listFiles(): 返回当前目录所有的 一级文件对象
  • 递归搜索:image-20240514180401807

字符集

编码和解码使用的字符集必须一致,否则会出现乱码

ASCII 字符集:只有英文、数字、负号,占 1 个字节

GBK 字符集:汉字占两个字节,英文、数字占一个字节

UTF-8 字符集:汉字占三个字节,英文、数字占一个字节

异常

异常:Throwable

  • 异常分为两类:Error and Exception

  • 编译时期的问题:不是 RuntimeException 的异常

  • 运行时期的问题:RuntimeException

image-20240517122001044

在 Java 程序中,method2() 函数显式抛出了一个 Exception 类型的异常。在 Java 中,Exception 和它的子类(除了继承自 RuntimeException 的异常)被称为检查型异常。这意味着如果一个方法可能会抛出某种检查型异常,那么这个方法必须在其声明中用 throws 关键字声明这种异常。

处理异常

  1. try...catch...finally,注:try 中的代码越少越好(耗费资源)
  2. throws 抛出
  3. try-with-resources 把流放到 try 的参数里,可以不用关闭流(自动关)

try-catch 之后还能继续运行,throws 之后就停止运行了

  • 后续程序需要继续运行就 try
  • 后续程序不需要继续运行就 throws

有多个 catch 异常时,注意顺序。

  • 平级无所谓
  • 有上下级(包含)关系的,必须先写小的。

throws 和 throw 的区别

throws

  • 用在方法声明后面,跟的是异常类名
  • 可以跟多个异常类名,用逗号隔开
  • 抛出异常,有该方法的调用者来处理(上一层)
  • throws 表示异常的一种可能性,不一定发生

throw

  • 用在方法体内,跟的是 异常对象
  • 只能抛出一个异常对象
  • 表示抛出异常,由方法体的语句执行
  • 执行 throw 语句,一定发生了异常

异常中的方法

  • public String getMessage() 返回异常的具体信息
  • public String toString() 返回异常的简单信息描述
  • public void printStackTrace() Prints this throwable and its backtrace to the standard error stream .

IO流

按流的方向可以分为:输入流和输出流

按流中数据的最小单位可以分为:字节流和字符流

tips:

try-with-resources 处理异常:把流放到 try 的参数里,可以不用关闭流(自动关)

try(
    ) {

} catch (Exception e) {
    e.printStackTrace();
}

文件字节输入流

FileInputStream

构造器

  • FileInputStream(File file)
  • FileInputStream(String pathname)

方法:

  • public int read() 读取一个字节,没有数据返回 -1
  • public int read(byte[] buffer) 独一个字节数组,返回读取了多少个字节

文件字节输出流

FileOutputStream

构造器

  • FileOutputStream(File file):重写文件
  • FileOutputStream(File file, boolean append):追加数据

方法

  • write(int a) 写一个字节
  • write(byte[] buffer, int pos , int len) 写入字节数组
  • 可以通过 str.getBytes() 将字符串转化为字节数组,然后再写入。

文件字符输入流

FileReader

构造器、方法与字节输入流类似。

文件字符输出流

FileWriter

构造器与字节输出流类似,有追加和重写的两种。

方法

  • write(int c) 写一个字符
  • write(String str,int off,int len) 写字符串
  • write(char[] buffer,int off, int len) 写字符数组

字符输出流写出数据后,必须刷新流,或者关闭流,写出的数据才能生效。

  • fw.flush() 刷新流
  • fw.close() 关闭流

字节缓冲流

相比于字节输入流/输出流,多了缓冲池

  • public BufferedInputStream(InputStream is)
  • public BufferedOutputStream(OutputStream os)

字符缓冲流

字符缓冲输入流

  • 构造器: public BufferedReader(Reader r)

  • 方法:public String readLine() 读一行

字符缓冲输入流

  • 构造器: public BufferedWriter(Writer r)

  • 方法:public String newLine() 换行

字符输入转化流

解决不同编码时候,字符流读取文本内容乱码的内容

解决思路:先获取文件的原始字节流,再将其按真是的字符集编码转换为字符输入流

构造器:InputStreamReader(InputStream is, String charset) 按照制定的字符集编码转化为输入流

try (
                // 字节流(GBK 字符集)
                InputStream is = new FileInputStream("asd");
                // 转化为字符输入流
                Reader isr = new InputStreamReader(is,"GBK");
                // 包装为缓冲字符输入流
                BufferedReader br = new BufferedReader(isr);

                ){
            String line;
            while((line = br.readLine()) != null) {
                System.out.println(line);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

字符输出转化流

OutputStreamWriter

类似于字符输入转化流。

打印流

PrintStream , PrintWriter

image-20240515151540308

输出流

数据输出流:DataOutputStream

  • 把数据和其类型一起写出去。

  • 构造器:DataOutputStream(OutputStream out)

  • 方法image-20240515153752484

数据输入流 DataInputStream

image-20240515154608517

序列化流

对象字节输出流 ObjectOutputStream

  • 将Java对象序列化:把 Java 对象存入到文件中去
  • 构造器:ObjectOutputStream(OutputStream out)
  • 方法:writeObject(object o)
  • 注:对象需要实现序列化接口 Implements Serializable

对象字节输入流 ObjectInputStream

  • 把Java对象反序列化:把文件中的Java对象读入到内存
  • 构造器:ObjectInputStream(InputStream is)
  • 方法:readObject()

properties

Properties:属性集合类,是一个和IO流相结合的集合类

  • 属性列表中每个键和对应的值都是字符串
  • 继承的 Hashtable<K,V>

方法

  • 添加元素: public Object setProperty(String key, String value)

  • 获取元素:public String getProperty(String key)

  • 获取所有的键的集合: public Set<String> stringPropertyNames()

  • 读取文件的数据到集合中:public void load(InputStream inStream)public void load(Reader reader)

  • 写入文件:public void store(Writer writer, String comments)

多线程

Thread

Java 程序的运行原理及 JVM 的启动时多线程的

  • Java 命令去启动 JVM , JVM 会启动一个进程,该进程会启动一个主线程
  • JVM 的启动是多线程的,因为他最底油亮哥线程启动了,祝线程和垃圾回收线程。

Java 中线程的调度是:抢占式调度。

创建线程

有两种方法可以创建新的执行线程。

  • 一种是将一个类声明为 Thread 的子类。该子类应重写类 Threadrun 方法。然后可以分配并启动子类的实例。

    class PrimeThread extends Thread {
         long minPrime;
         PrimeThread(long minPrime) {
             this.minPrime = minPrime;
         }
    
         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }
    
    // create a thread
    PrimeThread p = new PrimeThread(143);
    PrimeThread p2 = new PrimeThread(143);
    p.start();
    p2.start();
    
  • 另一种方法是声明一个实现 Runnable 接口的类。然后该类实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为参数传递并启动。

    class PrimeRun implements Runnable {
         long minPrime;
         PrimeRun(long minPrime) {
             this.minPrime = minPrime;
         }
    
         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }
    
    // create a thread
    PrimeRun p = new PrimeRun(143);
    // 因为 Runable 里面只有 run 方法,所以需要调用 Thread 来 new 一个线程。
    Thread pt =  new Thread(p);
    Thread pt2 =  new Thread(p);
    

实现接口方式的好处

  • 避免单继承的局限性
  • 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码、数据有效分离。(PrimeRun 相当于资源,只创建一次,多个线程相当于多个 用户操作,实现了数据和操作分离!)

线程的生命周期

image-20240517164607884

线程的调度

  • 线程休眠:public static void sleep(long millis)
  • Waits for this thread to die.:public static void join()
  • yield its current use of a processor. public static void yield()
  • 将此线程标记为守护线程或用户线程。当唯一运行的线程都是守护线程时,Java 虚拟机退出。public final void setDaemon(boolean on)
  • 中断进程:public void interrupt()

线程安全问题

可能的原因

  1. 是否是多线程环境
  2. 是否有共享数据
  3. 是否有多条语句操作共享数据(是否是原子操作)

同步机制

为了解决多条语句共享数据带来的线程安全问题,将这些语句抱起来就放在 同步代码块

同步可以解决安全问题的关键就在那个对象上,该对象如同锁的作用

  • 多个线程必须是同一个对象(同一把锁
Object obj = new Object();
synchronized (obj) {
    // 多条语句
}

同步方法的格式及锁对象问题

  • 把同步关键字 synchronized 加在方法上。例如 private synchronized void sell()
  • 锁对象: this

静态方法及锁对象问题

  • 锁对象:类的字节码文件。例如:sellTicket.class

Lock 锁

Lock 是个接口,他的实现类有 ReentrantLock .

  • 加锁:lock()
  • 释放锁:unlock()

反射

反射:通过class文件对象,去使用该文件中的成员变量,构造方法,成员方法。

类加载过程

  • 类的加载:类加载器 读入 class 文件,创建类的对象

  • 连接:验证、准备、解析

  • 初始化

Class 类

成员变量:Field

构造方法:Constructor

成员方法:Method

获取 Class 文件对象的方式

  • Object 类中的 getClass() 方法
  • 静态类型的静态属性 class,例如:int.class
  • Class 类中的静态方法 forName(String className)

常用方法

  • 获得 public 所有的构造方法:getConstructors()
  • 获得任意的构造方法:getDeclaredConstructors()
  • 新建实例对象:newInstance()
  • 打开访问权限:setAccessible(true) (可访问私有的)
  • 获得 public 所有的的成员变量:getFields()
  • 获得自己的包括父亲的 public 的成员方法:getMethod()
  • 获得自己的成员方法:getDeclaredMethod()
  • 运行反射得到的方法:invoke()
// 获取字节码文件对象
Class c = Class.forName("com.heima.eighth.Student");

// 获得 public 的构造方法
Constructor[] cons = c.getConstructors();
for(Constructor con: cons) {
    System.out.println(con);
}

// 新建实例对象 :  Student obj = new Student()
Constructor cc = c.getConstructor();
Object obj = cc.newInstance();
System.out.println(obj); // com.heima.eighth.Student@677327b6

// 获取带参数的构造方法
Constructor ccc = c.getConstructor(String.class, int. class) 

// 获取私有变量 name 并对其赋值
Field nameField = c.getDeclaredField("name");
nameField.setAccessible(true);  // 代开私有访问权限
nameField.set(obj,"zhangsan");
System.out.println(obj);

// 获取成员方法
Method m = c.getMethod("showstr", String.class);
m.invoke(obj,"hello");

反射的应用

通过配置文件运行类中的方法

Properties prop = new Properties();
FileReader fr = new FileReader("/Users/selfknow/program/Heima/code/first/helloworld/src/com/heima/tenth/class.txt");
prop.load(fr);
fr.close();

// 获取数据
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");

// 反射
Class c = Class.forName(className);

Constructor con = c.getConstructor();
Object obj = con.newInstance();

// 调用方法
Method m = c.getMethod(methodName);
m.invoke(obj);

通过反射越过范型检查

ArrayList<Interger> 中添加字符串数据

ArrayList<Integer> array = new ArrayList<Integer>();

array.add(1);

Class c = array.getClass();
Method m = c.getMethod("add",Object.class);
m.invoke(array,"hello");

array.add(2);

System.out.println(array); // [1, hello, 2]

Last update: June 10, 2024
Created: May 14, 2024