Java is Pass-by-Value, Dammit!

本文深入探讨了Java中参数传递机制的本质,纠正了常见的误解,并详细解释了Java如何通过值传递来处理对象引用。

THIS ARTICLE CONFIRMS MY UNDERSTANDING ON JAVA. ENJOY IT.


---------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Jovana Milutinovich gratiously offered to translate this article to Serbo-Croation! You can read the translation here: http://science.webhostinggeeks.com/java-je-prenos. Thanks a million, Jovana!

Introduction

I finally decided to write up a little something about Java's parameter passing. I'm really tired of hearing folks (incorrectly) state "primitives are passed by value, objects are passed by reference".

I'm a compiler guy at heart. The terms "pass-by-value" semantics and "pass-by-reference" semantics have very precise definitions, and they're often horribly abused when folks talk about Java. I want to correct that... The following is how I'd describe these

Pass-by-value
The actual parameter (or argument expression) is fully evaluated and the resulting value is  copied into a location being used to hold the formal parameter's value during method/function execution. That location is typically a chunk of memory on the runtime stack for the application (which is how Java handles it), but other languages could choose parameter storage differently.
Pass-by-reference
The formal parameter merely acts as an  alias for the actual parameter. Anytime the method/function uses the formal parameter (for reading or writing), it is actually using the actual parameter.

Java is strictly pass-by-value, exactly as in C. Read the Java Language Specification (JLS). It's spelled out, and it's correct. In http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.4.1:

When the method or constructor is invoked  (�15.12), the  values of the actual argument expressions initialize newly created parameter variables, each of the declared  Type, before execution of the body of the method or constructor. The  Identifier that appears  in the DeclaratorId may be used as a simple name in the body of the method or constructor to refer to the formal parameter.

[In the above, values is my emphasis, not theirs]

In short: Java has pointers and is strictly pass-by-value. There's no funky rules. It's simple, clean, and clear. (Well, as clear as the evil C++-like syntax will allow ;)

Note: See the note at the end of this article for the semantics of remote method invocation (RMI). What is typically called "pass by reference" for remote objects is actually incredibly bad semantics.


The Litmus Test

There's a simple "litmus test" for whether a language supports pass-by-reference semantics:

Can you write a traditional swap(a,b) method/function in the language?

A traditional swap method or function takes two arguments and swaps them such that variables passed into the function are changed outside the function. Its basic structure looks like

Figure 1: (Non-Java) Basic swap function structure

swap(Type arg1, Type arg2) {
    Type temp = arg1;
    arg1 = arg2;
    arg2 = temp;
}

If you can write such a method/function in your language such that calling

Figure 2: (Non-Java) Calling the swap function

Type var1 = ...;
Type var2 = ...;
swap(var1,var2);

actually switches the values of the variables var1 and var2, the language supports pass-by-reference semantics.

For example, in Pascal, you can write

Figure 3: (Pascal) Swap function

procedure swap(var arg1, arg2: SomeType);

    var
        temp : SomeType;
    begin
        temp := arg1;
        arg1 := arg2;
        arg2 := temp;
    end;

...

{ in some other procedure/function/program }

var
    var1, var2 : SomeType;

begin
    var1 := ...; { value "A" }
    var2 := ...; { value "B" } 
    swap(var1, var2);
    { now var1 has value "B" and var2 has value "A" }
end;

or in C++ you could write

Figure 4: (C++) Swap function

void swap(SomeType& arg1, Sometype& arg2) {
    SomeType temp = arg1;
    arg1 = arg2;
    arg2 = temp;
}

...

SomeType var1 = ...; // value "A"
SomeType var2 = ...; // value "B"
swap(var1, var2); // swaps their values!
// now var1 has value "B" and var2 has value "A"

(Please let me know if my Pascal or C++ has lapsed and I've messed up the syntax...)

But you cannot do this in Java!


Now the details...

The problem we're facing here is statements like

In Java, Objects are passed by reference, and primitives are passed by value.

This is half incorrect. Everyone can easily agree that primitives are passed by value; there's no such thing in Java as a pointer/reference to a primitive.

However, Objects are not passed by reference. A correct statement would be Object references are passed by value.

This may seem like splitting hairs, bit it is far from it. There is a world of difference in meaning. The following examples should help make the distinction.

In Java, take the case of

Figure 5: (Java) Pass-by-value example

public void foo(Dog d) {
    d = new Dog("Fifi"); // creating the "Fifi" dog
}



Dog aDog = new Dog("Max"); // creating the "Max" dog
// at this point, aDog points to the "Max" dog
foo(aDog);
// aDog still points to the "Max" dog

the variable passed in (aDog) is not modified! After calling foo, aDog still points to the "Max" Dog!

Many people mistakenly think/state that something like

Figure 6: (Java) Still pass-by-value...

public void foo(Dog d) { 
    d.setName("Fifi");
}

shows that Java does in fact pass objects by reference.

The mistake they make is in the definition of

Figure 7: (Java) Defining a Dog pointer

Dog d;

itself. When you write that definition, you are defining a pointer to a Dog object, not a Dog object itself.

On Pointers versus References...

The problem here is that the folks at Sun made a naming mistake.

In programming language design, a "pointer" is a variable that indirectly tracks the location of some piece of data. The value of a pointer is often the memory address of the data you're interested in. Some languages allow you to manipulate that address; others do not.

A "reference" is an alias to another variable. Any manipulation done to the reference variable directly changes the original variable.

Check out the second sentence of http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.3.1.

"The reference values (often just  references) are  pointers to these objects, and a special null reference, which refers to no object"

They emphasize "pointers" in their description... Interesting...

When they originally were creating Java, they had "pointer" in mind (you can see some remnants of this in things like
NullPointerException).

Sun wanted to push Java as a secure language, and one of Java's advantages was that it does not allow pointer arithmetic as C++ does.

They went so far as to try a different name for the concept, formally calling them "references". A big mistake and it's caused even more confusion in the process.

There's a good explanation of reference variables at http://www.cprogramming.com/tutorial/references.html. (C++ specific, but it says the right thing about the concept of a reference variable.)

The word "reference" in programming language design originally comes from how you pass data to subroutines/functions/procedures/methods. A reference parameter is an alias to a variable passed as a parameter.

In the end, Sun made a naming mistake that's caused confusion. Java has pointers, and if you accept that, it makes the way Java behaves make much more sense.

Calling Methods

Calling

Figure 8: (Java) Passing a pointer by value

foo(d);

passes the value of d to foo; it does not pass the object that d points to!

The value of the pointer being passed is similar to a memory address. Under the covers it may be a tad different, but you can think of it in exactly the same way. The value uniquely identifies some object on the heap.

However, it makes no difference how pointers are implemented under the covers. You program with them exactly the same way in Java as you would in C or C++. The syntax is just slightly different (another poor choice in Java's design; they should have used the same -> syntax for de-referencing as C++).

In Java,

Figure 9: (Java) A pointer

Dog d;

is exactly like C++'s

Figure 10: (C++) A pointer

Dog *d;

And using

Figure 11: (Java) Following a pointer and calling a method

d.setName("Fifi");

is exactly like C++'s

Figure 12: (C++) Following a pointer and calling a method

d->setName("Fifi");

To sum up: Java has pointers, and the value of the pointer is passed in. There's no way to actually pass an object itself as a parameter. You can only pass a pointer to an object.

Keep in mind, when you call

Figure 13: (Java) Even more still passing a pointer by value

foo(d);

you're not passing an object; you're passing a pointer to the object.

For a slightly different (but still correct) take on this issue, please see http://www-106.ibm.com/developerworks/library/j-praxis/pr1.html. It's from Peter Haggar's excellent book, Practical Java.)


A Note on Remote Method Invocation (RMI)

When passing parameters to remote methods, things get a bit more complex. First, we're (usually) dealing with passing data between two independent virtual machines, which might be on separate physical machines as well. Passing the value of a pointer wouldn't do any good, as the target virtual machine doesn't have access to the caller's heap.

You'll often hear "pass by value" and "pass by reference" used with respect to RMI. These terms have more of a "logical" meaning, and really aren't correct for the intended use.

Here's what is usually meant by these phrases with regard to RMI. Note that this is not proper usage of "pass by value" and "pass by reference" semantics:

RMI Pass-by-value
The actual parameter is  serialized and passed using a network protocol to the target remote object. Serialization essentially "squeezes" the data out of an object/primitive. On the receiving end, that data is used to build a "clone" of the original object or primitive. Note that this process can be rather expensive if the actual parameters point to large objects (or large graphs of objects).
This isn't quite the right use of "pass-by-value"; I think it should really be called something like "pass-by-memento". (See "Design Patterns" by Gamma et al for a description of the Memento pattern). 
 
RMI Pass-by-reference
The actual parameter, which  is itself a remote object,  is represented by a proxy. The proxy keeps track of where the actual parameter lives, and anytime the target method uses the formal parameter,  another remote method invocation occurs to "call back" to the actual parameter. This can be useful if the actual parameter points to a large object (or graph of objects) and there are few call backs.
This isn't quite the right use of "pass-by-reference" (again, you cannot change the actual parameter itself). I think it should be called something like "pass-by-proxy". (Again, see "Design Patterns" for descriptions of the Proxy pattern).

 


Follow up from stackoverflow.com

I posted the following as some clarification when a discussion on this article arose on http://stackoverflow.com.

The Java Spec says that everything in java is pass-by-value. There is no such thing as "pass-by-reference" in java.

The key to understanding this is that something like

Figure 14: (Java) Not a Dog; a pointer to a Dog

Dog myDog;

is not a Dog; it's actually a pointer to a Dog.

What that means, is when you have

Figure 15: (Java) Passing the Dog's location

Dog myDog = new Dog("Rover");
foo(myDog);

you're essentially passing the address of the created Dog object to the foo method. (I say essentially b/c java pointers aren't direct addresses, but it's easiest to think of them that way)

Suppose the Dog object resides at memory address 42. This means we pass 42 to the method.

If the Method were defined as

Figure 16: (Java) Looking at the called method in detail

public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}

Let's look at what's happening.

the parameter someDog is set to the value 42

at line "AAA"
someDog is followed to the Dog it points to (the Dog object at address 42) that Dog (the one at address 42) is asked to change his name to Max
at line "BBB"
a new Dog is created. Let's say he's at address 74 we assign the parameter someDog to 74
at line "CCC"
someDog is followed to the Dog it points to (the Dog object at address 74) that Dog (the one at address 74) is asked to change his name to Rowlf then, we return

Now let's think about what happens outside the method:

Did myDog change?

There's the key.

Keeping in mind that myDog is a pointer, and not an actual Dog, the answer is NO. myDog still has the value 42; it's still pointing to the original Dog.

It's perfectly valid to follow an address and change what's at the end of it; that does not change the variable, however.

Java works exactly like C. You can assign a pointer, pass the pointer to a method, follow the pointer in the method and change the data that was pointed to. However, you cannot change where that pointer points.

In C++, Ada, Pascal and other languages that support pass-by-reference, you can actually change the variable that was passed.

If Java had pass-by-reference semantics, the foo method we defined above would have changed where myDog was pointing when it assigned someDog on line BBB.

Think of reference parameters as being aliases for the variable passed in. When that alias is assigned, so is the variable that was passed in.

global r0 dr0 pAtm; pAtm = 1.01325e5; % standard atmospheric pressure, Pa. r0 = 4.500e-6; % initial radius of the cavity, m. dr0 = 0.000; % , m/s. t0 = 0.000; % start time, s. t1 = 40.000e-6; % end time, s. dt = 0.010e-6; % time step, s. %% ---------- Solve ---------- %% [t, u] = ... ode15s(@RayleighPlesset, ... (t0 : dt : t1).', ... [r0; dr0], ... odeset('RelTol', 1.000e-6, ... 'AbsTol', 1.000e-12, ... ... 'NormControl', 'on', ... 'InitialStep', 0.010 * dt, ... 'MaxStep', 0.200 * dt)); r = u(:, 1); dr = u(:, 2); %% ========== Plot Result ========== %% axesWidth = 0.820; axesHeight = 0.800; fontName = 'Times New Roman'; fontSize = 16; textInterpreter = 'latex'; hFigure = ... figure( ... 'WindowState', 'maximized' ... ); %% ---------- Pressure Axis ---------- %% hAxesPressure = ... axes(hFigure, ... 'Position', [0.666667 * (1.000 - axesWidth), 0.100, axesWidth, axesHeight], ... 'FontName', fontName, ... 'FontSize', fontSize, ... 'Color', 'none', ... 'NextPlot', 'add', ... 'YAxisLocation', 'right'); hAxesPressure.XAxis.Visible = 'off'; hAxesPressure.YAxis.Color = 'red'; % <-- axis color, RED. hAxesPressure.YLabel.Interpreter = textInterpreter; hAxesPressure.YLabel.String = '$p$ (kPa)'; hLinePressureScratch = plot(hAxesPressure, t * 1.000e6, pAtm * ones(length(t), 1) * 0.001, ... t * 1.000e6, DrivingPressure(t) * 0.001); hLinePressureScratch(1).Color = 'none'; hLinePressureScratch(2).Color = 'none'; %% ---------- Velocity Axis ---------- %% hAxesVelocity = ... axes(hFigure, ... 'Position', [0.333333 * (1.000 - axesWidth), 0.100, axesWidth, axesHeight], ... 'FontName', fontName, ... 'FontSize', fontSize, ... 'Color', 'none', ... 'NextPlot', 'add', ... 'YAxisLocation', 'right'); hAxesVelocity.XAxis.Visible = 'off'; hAxesVelocity.YAxis.Color = 'blue'; % <-- axis color, BLUE. hAxesVelocity.YLabel.Interpreter = textInterpreter; hAxesVelocity.YLabel.String = '$\dot{R}$ (m/s)'; hLineVelocityScratch = plot(hAxesVelocity, t * 1.000e6, dr); hLineVelocityScratch.Color = 'none'; %% ---------- Radius Axis ---------- %% hAxesRadius = ... axes(hFigure, ... 'Position', [0.333333 * (1.000 - axesWidth), 0.100, axesWidth, axesHeight], ... 'FontName', fontName, ... 'FontSize', fontSize, ... 'Box', 'on', ... 'Color', 'white', ... % <-- background color, WHITE. 'NextPlot', 'add', ... 'YAxisLocation', 'origin'); hAxesradius.XLabel.Interpreter = textInterpreter; hAxesRadius.XLabel.String = '\it{t} \rm{({\mu}s)}'; hAxesRadius.YLabel.Interpreter = textInterpreter; hAxesRadius.YLabel.String = '$R$ ($\mu$m)'; hLineRadiusScratch = plot(hAxesRadius, t * 1.000e6, r * 1.000e6); hLineRadiusScratch.Color = 'none'; %%Plot Radius,Velocity,Pressure%% hAxesPlot=... axes(hFigure,... 'Position',hAxesRadius.Position,... 'FontName',fontName,... 'FontSize',fontSize,... 'Color','none',... 'NextPlot','add',... 'YAxisLocation','origin',... 'YLimMode','manual',... 'YLim',[0.000,1.000],... 'Visible','off'); hLineRadiusVisible=... plot(hAxesPlot, hLineRadiusScratch.XData, (hLineRadiusScratch.YData - hAxesRadius.YLim(1))/(hAxesRadius.YLim(2) - hAxesRadius.YLim(1))); hLineRadiusVisible.Color = 'black'; hLineRadiusVisible.LineStyle = '-'; hLineVelocityVisible = ... plot(hAxesPlot, hLineVelocityScratch.XData, (hLineVelocityScratch.YData - hAxesVelocity.YLim(1))/(hAxesVelocity.YLim(2) - hAxesVelocity.YLim(1))); hLineVelocityVisible.Color = 'blue'; hLineVelocityVisible.LineStyle = '-'; hLineAtmosphericPressureVisible = ... plot(hAxesPlot, hLinePressureScratch(1).XData, (hLinePressureScratch(1).YData - hAxesPressure.YLim(1))/(hAxesPressure.YLim(2) - hAxesPressure.YLim(1))); hLineAtmosphericPressureVisible.Color = 'red'; hLineAtmosphericPressureVisible.LineStyle = '--'; hLineDrivingPressureVisible = ... plot(hAxesPlot, hLinePressureScratch(2).XData, (hLinePressureScratch(2).YData - hAxesPressure.YLim(1))/(hAxesPressure.YLim(2) - hAxesPressure.YLim(1))); hLineDrivingPressureVisible.Color = 'red'; hLineDrivingPressureVisible.LineStyle = '-.'; legend(hAxesPlot, 'cavity radius', 'rate of cavity radius', 'farfield pressure', 'driving pressure'); hAxesPlot.Legend.Location = 'best'; hAxesPlot.Legend.FontSize = fontSize; % 仿真参数 t = linspace(0, 0.005, 100); % 时间序列 (s) R = r0*(1 + 0.5*sin(2*pi*200*t)); % 示例气泡半径变化 (正弦波动) % 可视化参数 cMap = jet(256); % 颜色映射 pressureRange = [-2e5 2e5]; % 压力显示范围 (Pa) %% 创建图形窗口 fig = figure('Color','white','Position',[100 100 1200 600]); % 创建子图布局 ax1 = subplot(1,2,1); % 气泡形态显示 ax2 = subplot(1,2,2); % 压力场显示 %% 初始化气泡形态显示 axes(ax1) bubble = animatedline('Color','k','LineWidth',2); axis equal tight xlabel('X (mm)'); ylabel('Y (mm)'); title('Bubble Contour') grid on %% 初始化压力场显示 axes(ax2) [X,Y] = meshgrid(linspace(-2,2,50), linspace(-2,2,50)); pressureField = peaks(50)*1e4; % 示例压力场数据 hImage = imagesc(ax2, X(1,:), Y(:,1), pressureField); axis xy equal tight colormap(ax2, cMap); caxis(pressureRange) colorbar xlabel('X (mm)'); ylabel('Y (mm)'); title('Pressure Field (Pa)') %% 创建时间轴控件 timeSlider = uicontrol('Style','slider',... 'Position',[100 20 800 20],... 'Min',1,'Max',length(t),... 'Value',1,... 'SliderStep',[1 10]/(length(t)-1),... 'Callback',@updateFrame); %% 动画更新函数 function updateFrame(src,~) persistent hBubble hPressure % 获取当前时间步 currentTimeStep = round(src.Value); % 更新气泡轮廓 axes(ax1) theta = linspace(0, 2*pi, 100); x = R(currentTimeStep)*cos(theta); y = R(currentTimeStep)*sin(theta); clearpoints(bubble) addpoints(bubble,x,y) % 更新压力场(示例数据) axes(ax2) pressureField = (peaks(50) + 0.1*currentTimeStep)*1e4; set(hImage,'CData',pressureField) % 更新标题 title(ax1,sprintf('Bubble Contour (t = %.1f μs)', t(currentTimeStep)*1e6)) title(ax2,sprintf('Pressure Field (t = %.1f μs)', t(currentTimeStep)*1e6)) drawnow end %% RayleighPlesset方程 function du = RayleighPlesset(t, u) try % FOR DEBUGGING persistent gasConst mu b theta0; persistent pVapor rFarfield pFarfield rhoLiquid eta sigma gamma; persistent r0 dr0; persistent pDryAir0 rhoDryAir0; global pAtm; if isempty(gasConst) % All parameters are at temperature of 20℃. gasConst = 8.314; % gas constant, J/(mol·K). mu = 0.029; % mocular weight, kg/mol. % Take nytrogen as an approximation for dry air. % b = 0.03913e-3; % covolume, m3/mol. Nobel-Abel EOS is adopted. b = 0.000; % ideal gas. theta0 = 273.15 + 20.000; % initial temperature, K. pVapor = 2338.8; % vapor pressure of water, Pa. % pVapor = 0.000; rFarfield = Inf; % radius at which properties of farfield are evaluated, m. % It is usually set as Inf. % rFarfield = 200.0e-6; pFarfield = pAtm; % pressure at farfield, Pa. rhoLiquid = 998.2; % density of water at, kg/m3. eta = 1.002e-3; % dynamic viscosity of water, Pa·s. % eta = 0.000; % inviscid. sigma = 7.28e-2; % interfacial tension between the liquid and vapor phases of pure water, N/m. % sigma = 0.000; gamma = 1.000; % specific heat ratio of gas. % gamma = 1.000 for isothermal process; % gamma = 1.400 for adiabatic process. r0 = 4.500e-6; % initial radius, m. dr0 = 0.000; % initial rate of radius, m/s. pDryAir0 = pFarfield ... + DrivingPressure(0.000) ... + 2 * sigma / r0 + 4 * eta * dr0 / r0 - pVapor; % pressre of dry air, Pa. rhoDryAir0 = 1.000 / (gasConst / mu * theta0 / pDryAir0 + b / mu); end % /* if isempty(pVapor) */ r = u(1); % radius of the cavity. dr = u(2); % rate of radius of the cavity. pDriving = DrivingPressure(t); rhoDryAir = rhoDryAir0 * (r0 / r)^3; pDryAir = pDryAir0 * ((1.000 / rhoDryAir0 - b / mu) / (1.000 / rhoDryAir - b / mu))^gamma; ddr = ( ... ( ... ( ... ((pDryAir + pVapor) - (pFarfield + pDriving) ... - (4.000 * eta * dr + 2.000 * sigma) / r) ... / rhoLiquid) ... - 0.500 * ((r / rFarfield)^4 - 1.000) * dr * dr) ... / (1.000 / r - 1.000 / rFarfield) - 2 * r * dr * dr) / r / r; du = [dr; ddr]; catch exceptionDebug end % BREAKPOINT HERE. end % /* RayleighPlesset */ %驱动压 function pd = DrivingPressure(t) persistent pAmplitude period omega; if isempty(pAmplitude) pAmplitude = 1.000 * 1.01325e5; % amplitude of driving pressure, Pa. period = 40.000e-6; % period of driving pressure, s. omega = 2 * pi / period; % circular frequency of driving pressure, rad/s. end % /* if isempty(pAmplitude) */ if length(t) == 1 % if t <= period pd = pAmplitude * cos(omega * t); % else % pd = pAmplitude; % end % /* if t <= 40.000e-6 */ else dataSize = size(t); pd = zeros(dataSize); t = t(:); pd = pd(:); for ii = 1 : length(t) pd(ii) = DrivingPressure(t(ii)); end % /* for ii */ pd = reshape(pd, dataSize); end end % /* DrivingPressure */ 请给我详细解析每行代码的含义
07-26
### 代码功能概述 该 MATLAB 代码实现了 Rayleigh-Plesset 方程的数值求解,并对气泡半径、膨胀速度、驱动压力等物理量进行了可视化,同时结合动画模拟了气泡的动态演化过程。代码分为三个主要部分:求解器配置与求解、结果绘图、以及动态可视化模块[^1]。 --- ### 全局变量与参数设置 ```matlab global r0 dr0 pAtm; pAtm = 1.01325e5; % 标准大气压,单位为帕斯卡 r0 = 4.500e-6; % 初始气泡半径,单位为米 dr0 = 0.000; % 初始膨胀速度,单位为米每秒 t0 = 0.000; % 起始时间,单位为秒 t1 = 40.000e-6; % 结束时间,单位为秒 dt = 0.010e-6; % 时间步长,单位为秒 ``` 这些变量定义了气泡动力学模拟的基本参数。`global` 声明了全局变量,使得在函数之间可以共享这些变量。`pAtm` 表示环境压力,`r0` 是气泡的初始半径,`dr0` 为初始速度,`t0` 和 `t1` 定义了模拟的时间范围,`dt` 是积分的时间步长。 --- ### 求解器配置与数值求解 ```matlab [t, u] = ... ode15s(@RayleighPlesset, ... (t0 : dt : t1).', ... [r0; dr0], ... odeset('RelTol', 1.000e-6, ... 'AbsTol', 1.000e-12, ... 'NormControl', 'on', ... 'InitialStep', 0.010 * dt, ... 'MaxStep', 0.200 * dt)); ``` 使用 `ode15s` 求解器对 Rayleigh-Plesset 方程进行数值积分。该方程描述了气泡在液体中的动态演化过程。`odeset` 设置了求解器的控制参数,包括相对误差容限(`RelTol`)、绝对误差容限(`AbsTol`)、是否启用范数控制(`NormControl`)、初始步长(`InitialStep`)和最大步长(`MaxStep`)[^1]。 --- ### 结果提取与变量赋值 ```matlab r = u(:, 1); % 气泡半径随时间变化 dr = u(:, 2); % 气泡膨胀速度随时间变化 ``` 将求解结果中的第一列和第二列分别赋值给 `r` 和 `dr`,表示气泡半径和其时间导数。 --- ### 图形界面设置与绘图 ```matlab axesWidth = 0.820; axesHeight = 0.800; fontName = 'Times New Roman'; fontSize = 16; textInterpreter = 'latex'; hFigure = figure('WindowState', 'maximized'); ``` 这部分定义了绘图窗口的样式,包括坐标轴宽度、字体、字号以及文本解释器(使用 LaTeX)。创建了一个最大化窗口的图形对象 `hFigure`。 --- ### 压力轴绘制 ```matlab hAxesPressure = axes(hFigure, ... 'Position', [0.666667 * (1.000 - axesWidth), 0.100, axesWidth, axesHeight], ... 'FontName', fontName, ... 'FontSize', fontSize, ... 'Color', 'none', ... 'NextPlot', 'add', ... 'YAxisLocation', 'right'); hAxesPressure.YAxis.Color = 'red'; hAxesPressure.YLabel.String = '$p$ (kPa)'; hLinePressureScratch = plot(hAxesPressure, t * 1.000e6, pAtm * ones(length(t), 1) * 0.001, ... t * 1.000e6, DrivingPressure(t) * 0.001); ``` 创建了一个右侧 Y 轴用于显示压力变化,颜色为红色。`plot` 函数绘制了大气压和驱动压力随时间的变化曲线,时间单位转换为微秒,压力单位转换为千帕。 --- ### 速度轴绘制 ```matlab hAxesVelocity = axes(hFigure, ... 'Position', [0.333333 * (1.000 - axesWidth), 0.100, axesWidth, axesHeight], ... 'FontName', fontName, ... 'FontSize', fontSize, ... 'Color', 'none', ... 'NextPlot', 'add', ... 'YAxisLocation', 'right'); hAxesVelocity.YAxis.Color = 'blue'; hAxesVelocity.YLabel.String = '$\dot{R}$ (m/s)'; hLineVelocityScratch = plot(hAxesVelocity, t * 1.000e6, dr); ``` 创建了另一个右侧 Y 轴,用于显示气泡膨胀速度,颜色为蓝色,Y 轴标签表示速度单位为米每秒。 --- ### 半径轴绘制 ```matlab hAxesRadius = axes(hFigure, ... 'Position', [0.333333 * (1.000 - axesWidth), 0.100, axesWidth, axesHeight], ... 'FontName', fontName, ... 'FontSize', fontSize, ... 'Box', 'on', ... 'Color', 'white', ... 'NextPlot', 'add', ... 'YAxisLocation', 'origin'); hAxesRadius.XLabel.String = '\it{t} \rm{({\mu}s)}'; hAxesRadius.YLabel.String = '$R$ ($\mu$m)'; hLineRadiusScratch = plot(hAxesRadius, t * 1.000e6, r * 1.000e6); ``` 创建了主坐标轴,用于显示气泡半径随时间的变化,X 轴标签为时间(单位为微秒),Y 轴标签为气泡半径(单位为微米)。 --- ### 多图层叠加绘制 ```matlab hAxesPlot = axes(hFigure, ... 'Position', hAxesRadius.Position, ... 'FontName', fontName, ... 'FontSize', fontSize, ... 'Color', 'none', ... 'NextPlot', 'add', ... 'YAxisLocation', 'origin', ... 'YLimMode', 'manual', ... 'YLim', [0.000, 1.000], ... 'Visible', 'off'); ``` 创建了一个不可见的叠加坐标轴,用于在同一图层上绘制多个变量(半径、速度、压力),并统一坐标范围。 --- ### 动态可视化模块 ```matlab t = linspace(0, 0.005, 100); R = r0*(1 + 0.5*sin(2*pi*200*t)); ``` 生成模拟时间序列 `t` 和气泡半径 `R` 的示例数据,用于动画演示。气泡半径随时间呈正弦波动。 --- ### 气泡形态与压力场初始化 ```matlab fig = figure('Color','white','Position',[100 100 1200 600]); ax1 = subplot(1,2,1); ax2 = subplot(1,2,2); ``` 创建图形窗口并设置两个子图,分别用于显示气泡形态和压力场。 --- ### 气泡轮廓动画初始化 ```matlab axes(ax1) bubble = animatedline('Color','k','LineWidth',2); axis equal tight xlabel('X (mm)'); ylabel('Y (mm)'); title('Bubble Contour') grid on ``` 初始化气泡轮廓的动画对象,设置线条颜色为黑色,线宽为 2,同时设置坐标轴标签和标题。 --- ### 压力场图像初始化 ```matlab [X,Y] = meshgrid(linspace(-2,2,50), linspace(-2,2,50)); pressureField = peaks(50)*1e4; hImage = imagesc(ax2, X(1,:), Y(:,1), pressureField); ``` 创建二维网格数据并初始化压力场图像,使用 `imagesc` 显示图像,`peaks` 函数生成示例数据。 --- ### 时间滑块控件与动画更新函数 ```matlab timeSlider = uicontrol('Style','slider',... 'Position',[100 20 800 20],... 'Min',1,'Max',length(t),... 'Value',1,... 'SliderStep',[1 10]/(length(t)-1),... 'Callback',@updateFrame); ``` 创建一个时间滑块控件,用于控制动画播放进度,`Callback` 指定更新函数 `updateFrame`。 --- ### 动画更新函数 ```matlab function updateFrame(src,~) persistent hBubble hPressure currentTimeStep = round(src.Value); theta = linspace(0, 2*pi, 100); x = R(currentTimeStep)*cos(theta); y = R(currentTimeStep)*sin(theta); clearpoints(bubble) addpoints(bubble,x,y) pressureField = (peaks(50) + 0.1*currentTimeStep)*1e4; set(hImage,'CData',pressureField) title(ax1,sprintf('Bubble Contour (t = %.1f μs)', t(currentTimeStep)*1e6)) title(ax2,sprintf('Pressure Field (t = %.1f μs)', t(currentTimeStep)*1e6)) drawnow end ``` 该函数根据当前滑块值更新气泡轮廓和压力场图像,`theta` 用于生成气泡的圆形轮廓,`clearpoints` 和 `addpoints` 用于更新动画线条,`set` 更新图像数据,`title` 更新子图标题。 --- ### Rayleigh-Plesset 方程函数 ```matlab function du = RayleighPlesset(t, u) persistent gasConst mu b theta0; persistent pVapor rFarfield pFarfield rhoLiquid eta sigma gamma; global pAtm; if isempty(gasConst) gasConst = 8.314; mu = 0.029; b = 0.000; theta0 = 273.15 + 20.000; pVapor = 2338.8; rFarfield = Inf; pFarfield = pAtm; rhoLiquid = 998.2; eta = 1.002e-3; sigma = 7.28e-2; gamma = 1.000; r0 = 4.500e-6; dr0 = 0.000; pDryAir0 = pFarfield + DrivingPressure(0.000) + 2 * sigma / r0 + 4 * eta * dr0 / r0 - pVapor; rhoDryAir0 = 1.000 / (gasConst / mu * theta0 / pDryAir0 + b / mu); end r = u(1); dr = u(2); pDriving = DrivingPressure(t); rhoDryAir = rhoDryAir0 * (r0 / r)^3; pDryAir = pDryAir0 * ((1.000 / rhoDryAir0 - b / mu) / (1.000 / rhoDryAir - b / mu))^gamma; ddr = (( ( (pDryAir + pVapor) - (pFarfield + pDriving) - (4.000 * eta * dr + 2.000 * sigma) / r ) / rhoLiquid - 0.500 * ((r / rFarfield)^4 - 1.000) * dr * dr ) / (1.000 / r - 1.000 / rFarfield) - 2 * r * dr * dr) / r / r; du = [dr; ddr]; end ``` 该函数定义了 Rayleigh-Plesset 方程的微分形式,计算了气泡加速度 `ddr`,并返回速度和加速度组成的向量 `du`。其中考虑了气体压力、蒸汽压力、表面张力、粘性阻力等物理因素。 --- ### 驱动压力函数 ```matlab function pd = DrivingPressure(t) persistent pAmplitude period omega; if isempty(pAmplitude) pAmplitude = 1.000 * 1.01325e5; period = 40.000e-6; omega = 2 * pi / period; end if length(t) == 1 pd = pAmplitude * cos(omega * t); else dataSize = size(t); pd = zeros(dataSize); t = t(:); pd = pd(:); for ii = 1 : length(t) pd(ii) = DrivingPressure(t(ii)); end pd = reshape(pd, dataSize); end end ``` 该函数定义了随时间变化的驱动压力,采用余弦函数形式,振幅为 `pAmplitude`,周期为 `period`,角频率为 `omega`。对于多时间点输入,使用循环逐个计算每个时间点的压力值。 --- ### 总结 该代码完整实现了气泡动力学的数值求解与可视化。通过 Rayleigh-Plesset 方程建模气泡的动态行为,并结合 MATLAB 的绘图与动画功能,直观展示了气泡半径、速度、压力的变化趋势及气泡形态的演化过程。整个代码结构清晰,涵盖了从物理建模、数值求解到图形界面交互的全过程。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值