【开发】Java学习笔记

【开发】Java学习笔记

由于已经有了C++的基础,本博客下的内容会聚焦Java在面向对象方面的语法特性,不会对基础语法做过多讲述。

类和对象

静态变量和静态方法

Java中的静态变量,静态方法与C++用法一致,也有一些独特的特征:

  • 静态方法中不可以使用this关键字
  • 静态方法中不可以直接调用非静态方法
  • 局部变量不可以使用static关键字声明
  • 主方法必须用static关键字进行声明,即
public class A {
    public static void main(String[] args) {}
}

类的主方法

主方法是类的入口点,定义了程序从何处开始,对应C++中的main函数。

public static void main(String[] args) {
    ...
}

对象

可以用new方法来实例化一个对象,对象的赋值在Java中属于引用,比如:

People tom = new People();

真正的对象时new People()这一段代码,用tom对这个People进行引用,从而更方便进行操作。

可以看出没有指针的Java语境下可以更抽象化的去描述对象。

继承、多态、抽象类、接口

类的继承

Java只支持单继承,比如:

class Child extends Parent {}

是可以的,但是下面这个不合法:

class Child extends Parent1, Parent2 {}

值得注意的是,构造函数的写法需要用到super()

class A {
    int x;
    public A(int x) {
        this.x = x;
    }
}

class B extends A{
    public B(int x) {
        super(x);
    }
}

Object类

Object类是Java中所有对象的积累,因此任何类都可以重写Object类的代码

  • getClass()方法:可以返回对象执行时的Class实例,然后使用此实例调用getName()方法获得类名称
  • toString()方法:转化为String类可以用于输出

以下是一个小例子:

class Cat {
    protected String name;
    protected int age;
    protected double weight;
    protected Color color;

    public Cat(String N, int A, double W, Color C) {
        name = N;
        age = A;
        weight = W;
        color = C;
    }

    public String toString() {
        String output = "\n";
        output += "名字:" + name + "\n";
        output += "年龄:" + age + "\n";
        output += "重量:" + weight + "\n";
        output += "颜色:" + color + "\n";
        return output;
    }
}
  • equals()方法

区别于"=="这种比较方式,equals()方法可以比较两个对象的实际内容,而等于号只是比较两个对象的地址

对象类型的转换

  • 向上转型

假设有一位教师叫做Tom

class People {}
class Teacher extends Person {}

public class MAIN {
    public static void main(String[] args) {
        People tom = new Teacher();
	}
}

这种转型是安全的,因为这是把某个相对具体的类转化为抽象类的对象。在实际使用中,Tom只能使用People的成员而不能使用Teacher特有的成员

  • 向下转型

向下转型的写法要求更加严格一些,正确的写法是:

子类类型 子类对象 = (子类类型)父类对象;

用到了强制类型转换,比如用下面的这个例子:

Bird bird = new Pigeon();
Pigeon pigeon = (Pigeon) bird;

先采用了向上转型,再强行告诉编译器这只鸟就是一只鸽子,才能不报错地通过编译。

instanceof关键字

instanceof关键字用来判断某个实例是否为某个类,比如:

class Bird {
};

class Chicken extends Bird {
};

public class train_7 {
    public static void main(String[] args) {
        Chicken kun = new Chicken();
        System.out.println(kun instanceof Bird);
    }
}

这段代码的结果是true

用这一个语法,还可以做一些有小巧思的东西:

class Color {}
class Red extends Color {}
class Blue extends Color {}
class Yellow extends Color {}

public class exercise_4 {
    public static void judge(Color color) {
        if (color instanceof Red)
            System.out.print("红");
        if (color instanceof Blue)
            System.out.print("蓝");
        if (color instanceof Yellow)
            System.out.print("黄");
    }

    public static void main(String[] args) {
        Color[] colors = { new Red(), new Blue(), new Yellow() };

        for (int _i = 0; _i < 10; _i++) {
            for (int i = 0; i < 3; i++) {
                Color cur = colors[(int)(Math.random() * 3)];
                judge(cur);
            }
            System.out.println();
        }
    }
}

final关键字

final可以类比C++中的const

  • final变量

final关键字用来定义常量,编译器会拒绝对于final变量的再赋值

例如

final double PI = 3.14;
  • final方法

方法被定义为final类型时,可以防止子类修改父类的定义与实现方式。

也就是说,假如一个成员方法是private的,那其实会隐式地加上final,不用另外声明。

  • final类

定义为final的类不能被继承,这与C++中的用法一致。

final ClassName {}

record类

record类也是一种final类,可以更简洁、更紧凑地去定义一种类。

比如有一个final Clock,有多少小时、多少分钟的变量,以及若干方法,可以如下定义:

public record Clock(int hours, int minutesperhour) {
    // 自带构造方法
    public int getHours();
    public int getMinutesperhour();
}

抽象类

类中只要有抽象方法,就需要作为抽象类,抽象方法本身没有任何意义,需要让继承这个父类的子类去自行实现。

public abstract class Parent {
    abstract void testAbstract();
}

接口

接口是抽象类的延伸,可以认为是纯粹的抽象类。

通过interface关键字定义接口:

public interface Paintable {
    void draw();
}

接着实现部分要在类中使用:

public class A extends B implements Paintable {
    ...
}

一方面,接口可以多重使用

public class A implements Interface1, Interface2, ... {}

另一方面,接口可以继承接口,并且接受多重继承

interface A extends Interface1, Interface2, ... {}

另外,接口中可以通过default和static关键字实现有函数体的函数

  • default
interface A {
    default void func() {
        ...
    }
}

这样定义时,继承这个接口的类可以重写也可以不重写

但是继承多个接口,且这些接口有同名方法时,就无法确定default方法需要实现哪个,需要显式地书写:

<接口>.super.<方法名>(参数表)
  • static

类似于类的static方法,接口的static方法接口自己提供的,例如:

interface MathTool {
    static int add(int a, int b) { return a + b; }
}

然后调用的时候:

public class MAIN {
    public static void main(String[] args) {
        System.out.println(MathTool.add(2, 4));
    }
}

包和内部类

Java类包

由于类的名称可能经常重复,因而Java使用了包来划分区域。

类的实现中,如果class和一些变量的声明不另外加关键字,那就说明是包内可见的。

import A.B;

public class Math {
    void func();
}
import A.C;
public class Math {
    void func(int x);
}

import事实上还能够导入静态成员

import java.lang.System.out;

public class Test {
    public static void main(String[] args) {
        out.println("...");
    }
}

内部类

就是在一个类内新声明一个类。内部类可以自由使用其所在类的私有变量。

this关键字能够获得内部类和外部类的引用。

public class A {
    private int x = 7;
    private class B {
        private int x = 9;
        
        public void func() {
            this.x++;        // 内部类的x
            A.this.x++;      // 外部类的x
        }
    }
}

匿名内部类是只在创建对象时才会编写类体的一种写法,主要用于抽象类的具象化。

abstract class Dog {
    public abstract void bark();
}

public class MAIN {
    public static void main(String[] args) {
        Dog wangCai = new Dog() {
            public void bark() {
                System.out.println("汪汪");
            }
        }
        wangCai.bark();
    }
}

从中可以看出匿名类的一些注意点:

  • 匿名类不能写构造方法;
  • 匿名类不能定义静态成员;
  • 若匿名类创建的对象没有赋值给任何引用变量,会导致一次就被销毁。

异常处理

概述

作为一门面向对象的编程语言,Java在异常处理中也将异常作为类的实例形式处理。当某一方法发生错误时,这个方法会创建一个异常对象,并且传递给正在运行的系统。

标准的语法是try...catch...finally

try {
    ...
} catch (Exception1 e) {
    ...
} catch (Exception2 e) {
    ...
} 

finally {
    ...
}

无论之前的try...catch语句块是否被顺利执行完毕,都会执行finally语句,但是有四种特殊情况:

  • 在finally语句块中发生了异常;
  • 在前面的代码中使用了System.exit()退出程序;
  • 程序所在的线程死亡;
  • 关闭CPU。

自定义异常

主要有以下步骤:

  • 创建自定义异常类;
  • 在方法中通过throw关键字抛出异常对象;
  • 可以用try...catch语句捕获方法中throw处的语句;
  • 在出现异常的方法的调用者中捕获并处理异常。
public class MyException extends Exception {
    public MyException(String ErrorMessage) {
        super(ErrorMessage);
    }
}

接着在方法中使用

static int avg(int number1, int number2) throws MyException {
    if (number1 < 0 || number2 < 0) throw new MyException("不可以使用负数");
    if (number1 > 100 || number2 > 100) throw new MyException("数值过大");
    return (number1 + number2) / 2;d
}

异常类型分为受检异常和非受检异常,其中受检异常一定需要用try...catch处理,或是有相应回应,否则不予通过。

另外,Java 7开始提供了try...with...resources语句。他能够很容易关闭在try...catch语句中使用的资源,该语句能够确保每个资源在程序运行结束后都被关闭。

try (Resource r = new Resource()) {
    r.doTask();
} catch (Exception e) {
    e.printStackTrace();
}

字符串

这里主要说明字符串生成器。

使用字符串生成器StringBuilder的原因是"+"的字符串拼接会产生一个新的字符串实例,会在内存中创建新的字符串对象。如果重复对字符串进行修改,将极大地增加系统开销。因而设计了StringBuilder类大大提高了频繁增加字符串的效率。

StringBuilder builder = new StringBuilder("");
for (int j = 0; j < 100000; j++) {
    builder.append(j);
}
System.out.println(builder);

这里也涉及了Java的一个变成特点。我在写Java代码的时候发现如果对于局部变量进行修改的话,编译器会发出警告。所以在Java的思想中,局部变量本就该是尽量不变的量,每一次“新赋值”都应当是一个新变量。一方面,频繁修改局部变量都会产生新的实例;另一方面,假如频繁改造,变量的语义会变模糊。

枚举类型

基本用法

首先有比较简单的声明方式

enum SeasonEnum {
    SPRING, SUMMER, AUTUMN, WINTER
}

接着有几个常用方法

方法 含义
values() 将枚举类型成员以数组的形式返回
valueOf() 将普通字符串转换为枚举实例
compareTo() 比较两个枚举对象在定义时的顺序
ordinal() 得到枚举成员的位置索引

构造方法

事实上枚举类型中的成员也可以当作类使用,构造方法用private修饰,以下是个例子

enum DayEnum {
    Mon("星期一"), Tue("星期二"), Wed("星期三"), Thu("星期四"),
    Fri("星期五"), Sat("星期六"), Sun("星期日");

    private String chinese;
    private DayEnum(String chinese) {
        this.chinese = this.toString() + " : " + chinese;
    }
    public String getChinese() {
        return chinese;
    }
}

public class train_2 {
    public static void main( String[] args) {
        DayEnum[] days = DayEnum.values();
        for (DayEnum day : days) {
            System.out.println(day.getChinese());
        }
    }
}

泛型

泛型机制能够在创建对象的时候再去指定具体的数据类型,若不加声明,则默认为Object类。

类名<T>;

定义泛型时,可以声明多个类型:

class MyClass<T1, T2> {}

泛型类也可以使用数组类型:

public class ArrayClass<T> {
    private T[] array;
}

集合类的容器也都被定义了泛型:

集合类 泛型定义
ArrayList ArrayList< E >
HashMap HashMap< K, V >
HashSet HashSet< E >

除此之外,泛型还有更高级的用法

一个是可以限制泛型可用的类型

class myClass<T extends anyClass> {}

另外可以使用类型通配符,作用是在创建一个泛型类对象时限制这个泛型类的类型实现或继承某个接口或类的子类。

泛型类名称<? extends List> a = null;

需要使用时可以单独实例化:

A<? extends List> a = null;
a = new A<ArrayList>();
a = new A<LinkedList>();

也可以把该实例放到方法中:

public void func(A<? extends List> a) {}

若使用A<?>的形式实例化泛型类对象,则默认可以指定实例化为Object即以下的子类类型。

List<?> l1 = new ArrayList<Integer>();
List<?> l2 = new LinkedList<Integer>();

这两种写法都是可接受的。

继承泛型类和实现泛型接口也都可以使用:

class ExtendClass<T1> {}
class SubClass<T1, T2, T3> extends ExtendClass<T1> {}
interface Interface1<T1> {}
class SubClass<T1, T2, T3> implements Interface1<T1> {}

lambda表达式

基本格式如下

(参数表) -> {}

实现函数式接口

函数式接口指的是接口中只含一个抽象方法,只含一个抽象方法就可以用lambda表达式实现。

下面实现了无参方法和有参方法:

interface SayInterface1 {
    String say();
}
interface SayInterface2 {
    String say(String word1, String word2);
}

public class MAIN {
    public static void main(String[] args) {
        SayInterface1 func1 = () -> "这是个lambda表达式";
        SayInterface2 func2 = (x, y) -> x + y;
        System.out.println(func1.say());
        System.out.println(func2.say("这是个", "lambda表达式"));
    }
}

lambda表达式也可以使用代码块

interface Count {
    String cnt(int x);
}

public class MAIN {
    public static void main(String[] args) {
        Count func = (n) -> {
            if (n == 1) return "一";
            else if (n == 2) return “二";
            else return ”不会;
        }
        System.out.println(func.cnt(1));
    }
}

调用外部变量

lambda表达式中,局部变量作为final使用,但是成员变量可以被更改。

异常处理

很多接口的抽象方法为了保证程序的安全性,会在定义时就抛出异常。但是lambda表达式中并没有抛出异常的语法,这是以你为lambda表达式会默认抛出抽象方法原有的异常,当此方法被调用的时候就需要进行异常处理。

引用方法

静态方法的引用:

类名::方法名

成员方法的引用:

对象名::方法名

带泛型方法的引用:

类名/对象名::<类型>方法名

引用构造方法,无参和有参语法都相同:

类名::new

数组类型可能被当作泛型,这种时候可能会引用数组构造方法:

类名[]::new

Function接口

java.util.function包提供了很多预定义函数式接口,可以用来封装lambda表达式的对象。

最常用的类型是Function< T, R >,两个泛型的含义是:

  • T:被操作的类型,可以理解为方法参数;
  • R:操作结果类型,可以理解为方法的返回类型。

Function接口还得使用一些方法:

方法 功能 返回值
apply(T t) 按照实现逻辑执行函数 R
andThen(Function<? super R, ? extends V> after) 先执行apply(T),把执行结果作为方法的参数执行after函数 (T t) -> after.apply(apply(t))
compose(Function<? super V, ? extends T> before) 先执行before,再执行apply() (V v) -> apply(before.apply(v))
static identity 返回·一个Function对桑,此对象的apply()方法只会返回参数值 t -> t
import java.util.function.Function;

public class exercise_1 {
    Function<Long, Long> fac = (n) -> {
        long res = 1;
        for (long i = 1; i <= n; i++) {
            res *= i;
        }
        return res;
    };

    public static void main(String[] args) {
        exercise_1  ex = new exercise_1();
        for (long i = 1; i <= 10; i++) {
            System.out.println(i + "! = " + ex.fac.apply(i));
        }
    }
}

I/O

File类

File类的构造有三种方式:

File file = new File(String path);
File file = new File(String parent, String child);
File file = new File(File f, String child);

其中File类代表抽象路径,而String类代表具体路径。

获取文件信息有以下方法:

方法 返回值 说明
getName() String 文件名
getParent() String 文件父路径
getAbsolutePath() String 文件绝对路径
canRead() boolean 是否可读
canWrite() boolean 是否可写
exists() boolean 是否存在
length() long 文件长度(字节为单位)
isFile() boolean 是否为标准文件
isDirectory() boolean 是否为目录
idHidden() boolean 是否为隐藏文件
lastModified() long 文件最后修改时间

FileInputStream类和FileOutputStream类

首先看OutputStream类这一整个抽象类,提供了以下方法:

方法 说明
write(int b) 写入指定字节
write(byte[] b) 写入指定字节数组
write(byte[] b, int off, int len) 写入字节数组,从偏移量off开始len个字节
flush() 彻底完成输出并清空缓存区
close() 关闭输出流

而FileInputStream有以下构造方法:

  • FileInputStream(String name)
  • FileInputStream(File file)

下面给一个简单的例子:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class eg_3 {
    public static void main(String[] args) {
        File file = new File("(pathname)");

        try {
            FileOutputStream out = new FileOutputStream(file);
            byte[] content = "(message)".getBytes();
            out.write(content);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            FileInputStream in = new FileInputStream(file);
            byte[] getContent = new byte[1024];
            int len = in.read(getContent);
            System.out.println("文件中信息为:" + new String(getContent, 0, len));
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

不难发现这两个类都只接受字节流的输入输出,写入读取之前之后都需要进行另外的处理。

FileReader类和FileWriter类

Writer类是字符输出流的抽象类,FileWriter就是其子类。FileReader类和FileWriter类都是字符流的类,可以避免汉字由于读取方式问题而产生乱码的可能性。

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class eg_4 {
    public static void main(String[] args) {
        File file = new File("(pathname)");

        try {
            FileWriter fw = new FileWriter(file);
            String word = "(message)";
            fw.write(word);
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            FileReader fr = new FileReader(file);
            char[] ch = new char[1024];
            int len = fr.read(ch);
            System.out.println("文件中的信息是:" + new String(ch, 0, len));
            fr.close();
        } catch(IOException e) {
            e.printStackTrace();
        }
    }
}

带缓存输入/输出流

  • BufferedInputStream类和BufferedOutputStream类

这两个类分别可以用InputStream和OutputStream进行构造,默认的缓存区大小是32B,也可以指定缓存区大小。

缓存是I/O的一种性能优化。缓存流为I/O流增加了内存缓存区,这使得再流上执行skip()、mark()和reset()方法都成为可能。

  • BufferedReader类和BufferedWriter类

这两个类分别继承于Reader类和Writer类,具有内部缓存机制,能够以行作为单位输入输出。

带缓存字符数据读取文件的过程如下:

[文件] --> (InputStream) --> (InputStreamReader) --> (BufferedReader) --> [字符数据]

BufferedReader类常用方法:

  • read():读取单个字符
  • readLine():读取一个文本行

BufferedWriter类都返回void,常用方法如下:

  • write(String s, int off, int len):写入字符串一部分
  • flush():刷新缓存
  • newLine():写入行分隔符
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class eg_5 {
    public static void main(String[] args) {
        String[] content = { "(Message1)", "(Message2)", "(Message3)" };
        File file = new File("(pathname)");

        try {
            FileWriter fw = new FileWriter(file);
            BufferedWriter bw = new BufferedWriter(fw);
            for (int k = 0; k < content.length; k++) {
                bw.write(content[k]);;
                bw.newLine();
            }

            bw.close();
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            FileReader fr = new FileReader(file);
            BufferedReader br = new BufferedReader(fr);
            String tmp = null;
            int i = 1;
            while ((tmp = br.readLine()) != null) {
                System.out.println("第" + i + "行:" + tmp);
                i++;
            }

            br.close();;
            fr.close();
        } catch (IOException e){
            e.printStackTrace();
        }
    }
}

数据输入/输出流

名字叫做DataInputStream和DataOutputStream,以下为一个例子:

import javax.xml.crypto.Data;
import java.io.*;

public class eg_6 {
    public static void main(String[] args) {
        File file = new File("(pathname)");
        try {
            FileOutputStream fos = new FileOutputStream(file);
            DataOutputStream dos = new DataOutputStream(fos);

            dos.writeUTF("使用writeUTF()方法写入数据");
            dos.writeDouble(114.514);
            dos.writeInt(1919);
            dos.writeBoolean(true);

            dos.close();
            fos.close();

            FileInputStream fis = new FileInputStream(file);
            DataInputStream dis = new DataInputStream(fis);

            System.out.println("readUTF: " + dis.readUTF());
            System.out.println("readDouble: " + dis.readDouble());
            System.out.println("readInt: " + dis.readInt());
            System.out.println("readBoolean: " + dis.readBoolean());

            dis.close();
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

对象序列化输入/输出流

分别为ObjectInputStream和ObjectOutputStream。一个类只有实现Serializable或Externalizable接口才能被序列化或反序列化,以下为例子:

// 类的建立
import java.io.Serializable;

public class Book implements Serializable {
    private String name;
    private double price;

    public Book(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Name: " + this.name + ", Price: " + this.price;
    }
}
// 序列化并写入文件

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class eg_7 {
    public static void main(String[] args) {
        Book book1 = new Book("JAVA", 89.9);
        Book book2 = new Book("C++", 79.9);

        File fileObject = new File("(pathname)");

        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileObject))) {
            oos.writeObject(book1);
            oos.writeObject(book2);

            System.out.println(book1);
            System.out.println(book2);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
// 读取文件并反序列化

import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class eg_8 {
    public static void main(String[] args) {
        File fileObject = new File("(pathname)");

        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileObject))) {
            Book book1 = (Book) ois.readObject();
            Book book2 = (Book) ois.readObject();

            System.out.println(book1);
            System.out.println(book2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
posted @ 2026-03-25 17:54  R4y  阅读(4)  评论(0)    收藏  举报