Java编程 IV 扩展功能

在java 8 中 ,通过加入默认方法,向java的接口中加入了流处理的功能

流处理分为3种:

创建流

修改流元素

消费流元素

流的创建

使用 Stream.of 创建流对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.stream.Stream;

public class CollectionToStream{
public static void main(String[] args) {
Stream.of(
new Bubble(1),new Bubble(2),new Bubble(3)
).forEach(System.out::println);
Stream.of(
"It's","a","wonderful","day","for","pie"
);
System.out.println();

}

}

class Bubble{
private int size;

public Bubble(int size) {
this.size = size;
}
}

Stream.of 会将 传入的 序列对象转换为 Spliterator 对象来处理这些对象

使用random创建流

random 会随机生成 特定类型的对象,作为返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.* ;
import java.util.stream.*;
public class RandomGenerators{
public static <T> void show (Stream<T> stream ){
stream
.limit(4)
.forEach(System.out::println);
System.out.println("++++++++++");
}
public static void main(String[] args) {
Random rand = new Random(47);
show(rand.ints().boxed()); // 随机产生整数
show(rand.longs().boxed()); // 随机产生长整型
show(rand.doubles().boxed()); // 随机产生双精度浮点数
show(rand.ints(10,20).boxed()); // 随机产生10到20之间的整数
show(rand.longs(10,20).boxed()); // 随机产生10到20之间的长整型
show(rand.doubles(10,20).boxed()); // 随机产生10到20之间的双精度浮点数
show(rand.ints(3,3,9).boxed()); // 随机产生3到9之间的整数,长度为3
show(rand.longs(3,12,22).boxed()); // 随机产生12到22之间的长整型,长度为3
show(rand.doubles(3,11,12.3).boxed()); // 随机产生11到12.3之间的双精度浮点数,长度为3
}
}

使用range构造流

range 是将某个范围内的值作为返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

import static java.util.stream.IntStream.range;

//import static java.util.stream.IntStream
public class Ranges{
public static void main(String[] args) {
int result = 0 ;
for(int i = 10 ; i < 20 ; i++){result+=i ;}
System.out.println(result);
result = 0 ;
for(int i : range(10,20).toArray()){
result+=i ;
}
System.out.println(result);
System.out.println(range(10,20).sum());
}
}

generate()

generate 接收一个实现了supplier类的实现类,类中定义 了一个获取方法来每次获取目标对象中的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.* ;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Generator implements Supplier<String>{
Random rand = new Random(35);
char[] letter = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
@Override
public String get() {
return "" + letter[rand.nextInt(letter.length)];
}
public static void main(String[] args) {
String word = Stream.generate(new Generator())
.limit(30)
.collect(Collectors.joining());
System.out.println(word);
}
}

iterate()

iterate 则是作为一个接口 ,每次通过调用函数来创建值作为其流的返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.stream.*;

public class Fibonacci{
int x = 1 ;
Stream <Integer> numbers(){
return Stream.iterate(0,i->{
int result = x+i;
x = i;
return result;
});
}
public static void main(String[] args) {
new Fibonacci().numbers()
.limit(10)
.forEach(System.out::println);
}
}

Arrays

通过Arrays中的stream方法可以将数组转换为流

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.utils.*;
import java.util.stream.*;

public class ArrayStreams{
public static void main(String[] args){
Arrays.stream(
new double[] {3.14159,2.718,1.618})
.forEach(n->System.out.format("%f",n));
)
System.out.println();
}
}

修改流

peek() 用于查看流而非修改

sorted() 对数据进行排序

distinct() 移除重复元素

map() 将函数应用于流中的对象

option() 查询对象中是否存在请求端值

终结流

toArray() 将流转换为一个数组

foreach() 对流中的每个元素进行执行

collect() 将元素收集到一个结果集合中

reduce() 组合所有元素

allMatch() 匹配元素

findFirst() 选择一个元素

findAny() 返回一个包含某个元素的Optional

count() 统计流中元素的数量

min()

max()

文件的流式读取

InputStream(抽象类)

  • FileInputStream - 从文件读取字节
  • ByteArrayInputStream - 从字节数组读取字节
  • FilterInputStream - 装饰器的基类
    • BufferedInputStream - 使用缓冲区提高读取效率
    • DataInputStream - 读取基本数据类型
    • ObjectInputStream - 读取对象
  • PipedInputStream - 与PipedOutputStream配对,用于线程间通信
  • SequenceInputStream - 将多个InputStream合并成一个

OutputStream(抽象类)

  • FileOutputStream - 向文件写入字节
  • ByteArrayOutputStream - 向字节数组写入字节
  • FilterOutputStream - 装饰器的基类
    • BufferedOutputStream - 使用缓冲区提高写入效率
    • DataOutputStream - 写入基本数据类型
    • ObjectOutputStream - 写入对象
    • PrintStream - 打印各种数据值的便捷方式(例如System.out)
  • PipedOutputStream - 与PipedInputStream配对,用于线程间通信
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import java.io.*;
import java.util.Arrays;

/**
* Java I/O流示例代码
*/
public class JavaIOExamples {

public static void main(String[] args) {
try {
byteStreamExample();
characterStreamExample();
bufferedStreamExample();
dataStreamExample();
objectStreamExample();

} catch (Exception e) {
e.printStackTrace();
}
}
public static void byteStreamExample() throws IOException {
FileInputStream fis = null;
FileOutputStream fos = null;

try {
// 创建输入输出流
fis = new FileInputStream("source.txt");
fos = new FileOutputStream("destination.txt");

// 读取和写入
int byteData;
while ((byteData = fis.read()) != -1) {
fos.write(byteData);
}

System.out.println("文件复制完成(使用字节流)");
} finally {
// 关闭资源(始终在finally块中进行)
if (fis != null) fis.close();
if (fos != null) fos.close();
}
}
public static void characterStreamExample() throws IOException {
FileReader fr = null;
FileWriter fw = null;

try {
// 创建输入输出流
fr = new FileReader("source.txt");
fw = new FileWriter("destination.txt");

// 读取和写入
int charData;
while ((charData = fr.read()) != -1) {
fw.write(charData);
}

System.out.println("文件复制完成(使用字符流)");
} finally {
// 关闭资源
if (fr != null) fr.close();
if (fw != null) fw.close();
}
}
public static void bufferedStreamExample() throws IOException {
BufferedReader br = null;
BufferedWriter bw = null;

try {
// 创建输入输出流
br = new BufferedReader(new FileReader("source.txt"));
bw = new BufferedWriter(new FileWriter("destination.txt"));

// 按行读取和写入
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine(); // 写入一个换行符
}

System.out.println("文件复制完成(使用缓冲流)");
} finally {
// 关闭资源
if (br != null) br.close();
if (bw != null) bw.close();
}
}
public static void dataStreamExample() throws IOException {
// 写入基本数据类型
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin"))) {
dos.writeInt(100);
dos.writeFloat(123.45f);
dos.writeBoolean(true);
dos.writeUTF("Hello, Java I/O!");
}
// 读取基本数据类型
try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) {
int intValue = dis.readInt();
float floatValue = dis.readFloat();
boolean boolValue = dis.readBoolean();
String strValue = dis.readUTF();

System.out.println("读取的数据:" + intValue + ", " + floatValue + ", " +
boolValue + ", " + strValue);
}
}
public static void objectStreamExample() throws IOException, ClassNotFoundException {
// 创建一个可序列化的类的实例
Person person = new Person("张三", 30);

// 写入对象
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.obj"))) {
oos.writeObject(person);
System.out.println("对象已写入文件");
}

// 读取对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.obj"))) {
Person readPerson = (Person) ois.readObject();
System.out.println("读取的对象:" + readPerson);
}
}
}
class Person implements Serializable {
private static final long serialVersionUID = 1L;

private String name;
private int age;

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

@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}

异常

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class testa{
public static void main(String[] args) {
try {
int c = 1/0;
System.out.println("try");
} catch (Exception e) {
System.out.println("catch a error");
}finally {
System.out.println("finally");
}

}
}

在 Java 中,运行时异常对象通常由 JVM 在执行某些操作时(如除以零、空指针访问、数组越界)主动创建。一旦创建,异常对象会通过 JVM 的异常分发机制沿着调用栈向上传播。当遇到匹配的 catch 块时,该块中的异常变量引用(如 catch (IOException e) 中的 e)将自动与该异常对象绑定,从而允许程序员访问异常的详细信息并决定如何处理。

自定义异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SimpleException extends Exception {}

public class InnerException{
public void f() throws SimpleException{
System.out.println("Throw SimpleException from f()");
throw new SimpleException();
}
public static void main(String[] args) {
InnerException ie = new InnerException();
try{
ie.f();
}catch(SimpleException e){
System.out.println("Caught it!");
}
}
}

Exception 是所有异常类的基类

文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.io.File;
import java.net.URI;
import java.nio.file.*;

public class PathInfo{
static void show(String id ,Object p){
System.out.println(id+p);
}

static void info( Path p ){
show("toString:\\n ", p);
show("Exists: ",Files.exists(p));
show("RegularFile: ", Files.isRegularFile(p));
show("Absolute: ", Files.isDirectory(p));
show("FileName: ", p.getFileName());
show("Filename : ",p.isAbsolute());
show("Parent: ", p.getParent());
show("Root: ", p.getRoot());
System.out.println("--------------------");
}
public static void main(String[] args) {
System.out.println(System.getProperty("os.name"));
info(Paths.get("C:","path","to","nowhere","NoFile.txt"));
Path p = Paths.get("PathInfo.java");
info(p);
Path ap = p.toAbsolutePath();
info(ap);
info(ap.getParent());
try {
info(p.getParent());
} catch (Exception e) {
System.out.println(e);
}
URI u = p.toUri();
System.out.println("URI: " + u);
Path puri = Paths.get(u);
System.out.println(Files.exists(puri));
File f = puri.toFile();
}
}

通过 Path对象来管理 路径对象。通过File对象来管理文件对象。通过Files来判断路径指向的文件类型

字符串

字符串对象

字符串本身是储存在堆中的,当进行字符串对象的传递,本质是传递了该字符串的引用

而当对字符串进行拼接操作时,其流程是 :

通过引用 查询到堆中的字符串,计算待拼接字符串的大小,申请对应大小电脑内存,将每个字符串存入新的字符数组中,返回新的对该字符数组的引用。

常用方法

方法名 说明 示例 返回值
length() 获取字符串长度 "hello".length() 5
isEmpty() 是否为空字符串 "".isEmpty() true
isBlank() 是否全为空白字符(Java 11+) " ".isBlank() true
charAt(int index) 获取指定位置字符 "abc".charAt(1) 'b'
equals(String s) 内容相等(区分大小写) "Hi".equals("hi") false
equalsIgnoreCase(String s) 内容相等(忽略大小写) "Hi".equalsIgnoreCase("hi") true
compareTo(String s) 字典序比较 "abc".compareTo("bcd") -1
contains(CharSequence s) 是否包含子串 "hello".contains("ell") true
startsWith(String s) 是否以 s 开头 "java".startsWith("ja") true
endsWith(String s) 是否以 s 结尾 "java".endsWith("va") true
indexOf(String s) 第一次出现位置 "hello".indexOf("l") 2
lastIndexOf(String s) 最后一次出现位置 "hello".lastIndexOf("l") 3
substring(int begin) 从索引开始截取 "hello".substring(2) "llo"
substring(int begin, int end) 截取区间 [begin, end) "hello".substring(1, 4) "ell"
trim() 去除首尾空格 " hi ".trim() "hi"
strip() 去除首尾 Unicode 空白(Java 11+) " hi ".strip() "hi"
replace(old, new) 替换所有子串 "a-b".replace("-", ":") "a:b"
replaceFirst(regex, new) 替换第一个匹配项 "abc123".replaceFirst("\\\\d", "X") "abcX23"
replaceAll(regex, new) 替换所有匹配项 "abc123".replaceAll("\\\\d", "") "abc"
toUpperCase() 转大写 "java".toUpperCase() "JAVA"
toLowerCase() 转小写 "JAVA".toLowerCase() "java"
split(String regex) 按正则分割成数组 "a,b,c".split(",") ["a", "b", "c"]
join(delimiter, ...) 静态方法,连接字符串 String.join("-", "a", "b") "a-b"
toCharArray() 转为字符数组 "abc".toCharArray() ['a','b','c']
valueOf(Object o) 任意对象转字符串 String.valueOf(123) "123"
matches(regex) 是否整体匹配正则 "123".matches("\\\\d+") true
repeat(int n) 重复字符串 n 次(Java 11+) "a".repeat(3) "aaa"
stripLeading() 去除前导空白(Java 11+) " hi".stripLeading() "hi"
stripTrailing() 去除后导空白(Java 11+) "hi ".stripTrailing() "hi"
lines() 拆分为行的 Stream(Java 11+) "a\\nb\\nc".lines() Stream<String>

Scanner类

用于接收 用户输入

方法 说明
next() 读取一个单词(遇到空格/换行停止)
nextLine() 读取一整行(直到回车)
nextInt()/nextDouble()/nextBoolean() 读取整数/浮点数/布尔值
hasNextInt() 检查下一个是否是整数
close() 关闭输入流(最佳实践)

反射

反射是一种可以帮我们获取到对象实际实现类的方法

当我们进行了向上转型之后,在引用声明侧。我们只能使用引用调用基类的属性和方法。而当我们希望执行 其实际具有的方法时 就无法查看到其方法,所以可以通过反射来查询其本身所具备的方法和属性,再通过向下转型调用对应的方法

可以通过

type.getclass() 获取到类信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Anmial{
public void eat(){
System.out.println("Animal is eating");
}
public static void main(String[] args) {
Anmial a = new Cat();
System.out.println(Cat.class);
Class c = a.getClass();
System.out.println(c.getName());
}
}

class Cat extends Anmial{
public void eat(){
System.out.println("Cat is eating");
System.out.println(Cat.class);
}
}

创建的 实例a通过getClass() 获取到的类信息为 Cat

也可以通过 类字面量查询类的类名 比如 Cat.class 输出 class Cat

可以通过泛型 来限定 class引用接收的对象

1
2
3
4
5
6
7
8
9
10
public class GenericClassReferences{
public static void main(String[] args) {
Class intClass = int.class;
intClass = double.class;
Class<Integer> genericIntClass = int.class;
genericIntClass = Integer.class;
// genericIntClass = double.class; //不可用
System.out.println(intClass);
}
}

对反射的引用声明要求其是某个类的基类,所引用的对象仍然是具体的某个子类。

因为对于编译器来说,它只记录了已声明对象的类型信息,当你获取它的基类时,并不清楚基类的内容,而清楚所获得的类范围是大于子类的,所以需要使用<? super FancyToy>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.lang.reflect.*;

public class GenericToyTest{
public static void main(String[] args) throws Exception{
Class<FancyToy> ftc = FancyToy.class; // 获取类的类对象
FancyToy fancyToy = ftc.getConstructor().newInstance(); // 创建实例
Class <? super FancyToy> up = ftc.getSuperclass(); // 获取类对象的基类
Object obj = up.getConstructor().newInstance(); // 创建实例

Method method = ftc.getMethod("f"); // 查询子类中的方法 f
method.invoke(fancyToy); // 执行 输出fancy
Method method1 = up.getMethod("f"); // 查询基类中的方法 f
method1.invoke(obj); // 执行 输出 f
Method method1 = up.getMethod("f"); // 查询基类中的方法 f
method1.invoke(fancyToy); // 执行 输出fancy
}

}

class Toy{
public Toy(){ }
public void f(){
System.out.println("f");
}
}

class FancyToy extends Toy{
public FancyToy(){ }
public void f(){
System.out.println("fancy");
}
}

为什么使用基类中的方法调用子类对象 仍然会执行子类中的函数?

因为 在类加载过程中,实例方法被维护在vtable中,对于具体的方法实现是动态绑定的。

而在反射中使用getMethod 接收到的也是一个记录有函数偏移信息的结构体。

当开始执行时,会在子类对象中根据这个偏移查询函数进行执行。

动态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
InvocationHandlerhanlder = new InvocationHandler(proxy,method,args){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable){
return method.invoke(target,args);
};

}

// 通过匿名类重写了InvocationHandler 接口。

Proxy proxy = Proxy.newProxyInstance(target.getclass().getClassLoader(),target.getclass().getInterfaces(),handler);
// 将被代理类的加载器信息和接口信息传入。
// 后续通过proxy 代理执行原类型的方法

类实现 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.lang.reflect.*;
class DynamicProxy implements InvocationHandler {
Object proxied;
public DynamicProxy(Object proxied) {
this.proxied = proxied;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args);
if(args != null)
for(Object arg : args)
System.out.println(""+ arg);
return method.invoke(proxied, args);
}
}

class SimpleDynamicProxy {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
RealObject real = new RealObject();
consumer(real);
Interface proxy = (Interface) Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{Interface.class},
new DynamicProxy(real));
consumer(proxy);
}
}

总结

反射提供了一种在对象之外构建业务流程的过程。

通过“反射”和“接口” 我们可以达成业务和数据的分离。在框架设计过程中只需要关注于具体的功能实现,而对象则更像是一种数据供应商。在每个阶段只需要关注具体需要的字段和功能,而不在需要去关注对象。

泛型

泛型是一种用于声明通用类/通用函数的手段,当希望向类中(通常是容器)中添加不定类型的对象时,可以先通过泛型进行类设计,再后续的对象创建过程中再通过声明传入的类型进行使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class LinkedStack<E>{
private class Node<E>{
E item;
Node<E> next;
Node(){item=null;next=null;}
Node(E item , Node<E> next){
this.item=item;
this.next=next;
}
boolean end(){
return item==null && next==null;
}
}
private Node<E> top = new Node<>();
public void push(E item){
top = new Node<>(item,top);
}
public E pop(){
E result = top.item;
if(!top.end())
top = top.next;
return result;
}

}

泛型本质上是对编译器类型检查能力的一种扩展。通过在代码中引入类型参数,开发者可以为类和方法引入灵活的类型约束,使得在实例化时由具体类型替代这些通用类型,从而实现编译期的类型安全检查。这为容器类等通用结构提供了一种统一而安全的类型解决方案。

需要强调的是,Java 泛型的实现机制基于 类型擦除 ,即在编译阶段,所有泛型类型参数会被替换为其限定的上界类型(默认为 Object),在生成的字节码中不再保留泛型类型信息。因此,泛型是一种 编译期语法机制 ,并不属于 JVM 的运行时功能扩展,JVM 在加载和执行阶段对泛型完全无感。

泛型所带来的最大价值之一,是提升了类与方法的复用性。通过引入类型参数,开发者可以在不牺牲类型安全的前提下复用逻辑代码。与此同时,JVM 通过放弃对泛型参数的运行时类型保留,避免了类型膨胀带来的性能与兼容性问题,从而在设计上取得了 类型安全性与运行效率之间的平衡

它会在编译阶段 对泛型内部的声明进行擦除,但是如果从内部元素获取类信息仍然可以正常获取 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.*;

class Frob{

}
class Fnorkle{

}
class Quark<Q>{

}
class Particle<POSITION,MOMENTUM>{

}

public class LostInfomation{
public static void main(String[] args) {
List<Frob> list = new ArrayList<>();
list.add(new Frob());
Map<Frob,Fnorkle> map = new HashMap<>();
Quark<Fnorkle> quark = new Quark<Fnorkle>();
Particle<Long,Double> p = new Particle<>();
System.out.println(list.get(0).getClass());
System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));
System.out.println(Arrays.toString(p.getClass().getTypeParameters()));
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
}
}

输出 :

1
2
3
4
5
class Frob  // 仍然可以获取类中的元素信息
[Q]
[POSITION, MOMENTUM]
[E]
[K, V]

可以为泛型设置边界

1
<T extends Hasf>

再谈对象

在我们最初对java的内存模型讨论中,已经知道java中所有进行的声明本质上都是对堆上创建的对象的应用。这种引用可以理解为是一种可以进行安全访问的指针。

当使用多个声明指向同一个对象时,多个声明都是对同一个对象的引用别名,这意味着当通过某个别名获取目标对象并进行修改时,其他的别名对对象的属性读取也会发生修改。这是因为任何一个引用进行的修改都是对引用对象本体进行的修改

引用与值

在讨论“引用传递”和“值传递”时,本质上需要回答一个核心问题:函数调用时,到底传入了什么?

回到 C 语言的设计,所有变量在传入函数时,都会在新的栈帧中创建一个局部副本。

  • 如果传入的是普通值(如 int),则创建的是值的副本;
  • 如果传入的是指针,则创建的是指针值(地址)的副本。

从严格意义上讲, C语言中所有的传递都是值传递 ;只是当传递的是指针时,可以通过指针副本间接修改原始内存,从而表现出“引用效果”。

在更现代的语言(如 Java 和 Python)中,所有对象在传递时,传入的是 对象引用的副本 。变量本身持有的是一个指向对象的引用,而不是对象本体。

因此:

  • 如果在函数中修改引用指向的对象内容,外部对象也会被改变;
  • 如果在函数中重新赋值给引用(让引用指向新的对象),则不会影响外部原引用。

来看下面两个例子:

  1. test_plus(a,b)
    传入的是整数 ab 的值副本。函数内部修改 a,但不会影响外部 a,因为它们是不同的局部变量。
  2. test_plus1(n)
    传入的是对象 n 的引用副本。函数内部通过 n 访问并修改了 n.a。由于副本和原引用指向同一块对象,因此外部的 n.a 也发生了变化。

因此,在引用与实际对象分离的语言中,我们应更多关注 对引用指向对象的操作 ,而不是单纯地纠结于传递机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Example{
public static void main(String[] args) {
int a = 3 ;
int b = 8 ;
test_plus(a,b);
Number n = new Number(8,10);
System.out.println(a);
test_plus1(n);
System.out.println(n.a);
}

public static Integer test_plus(int a, int b){
a = a+b;
return a;
}

public static Integer test_plus1(Number n){
n.a = n.a+n.b;
return n.a;
}
}

class Number{
int a ;
int b ;
Number(int a,int b){
this.a = a;
this.b = b;
}
}

克隆对象

我们知道了所有的函数本质的上都是在传递引用的“值” 那么在某些对象不想被修改时,就需要对对象进行复制:在堆上重新创建一个相同的对象并将引用指向它。

Java中提供了 clone方法来创建对象,但是clone 只会对目标对象进行浅层克隆。如果对象中存在对其他对象的引用仍然指向得是同一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Duplo implements  Cloneable{
private int n ;

public Duplo(int n) {this.n = n;}
@Override
public Duplo clone(){
try {
return (Duplo) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
public int getValue(){return n; }
public void setValue(int n ){
this.n = n;
}
public void increment(){ this.n++;}
@Override public String toString(){
return Integer.toString(n);
}
}

public class LocalCopy{
public static Duplo g(Duplo v){
v.increment();
return v;
}
public static Duplo f(Duplo v){
v = v.clone();
v.increment();
return v;
}
public static void main(String[] args) {
Duplo a = new Duplo(11);
Duplo b = g(a);
System.out.println("a == b:" + (a==b)+"\\na=" +a +"\\nb=" +b+"\\n");
Duplo c = new Duplo(47);
Duplo d = f(c);
System.out.println("c == d:" + (c==d)+"\\nc=" +c +"\\nd=" +d+"\\n");
}
}

注解

定义注解

1
2
3
4
5
6
7
8
9
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{}

// @Target指定注解应用的位置
// @Retention 指定注解可用的阶段 源代码,类文件,运行时


Java编程 IV 扩展功能
http://gadoid.io/2025/04/29/Java编程-IV-扩展功能/
作者
Codfish
发布于
2025年4月29日
许可协议