方法定义
修饰符 返回值类型 方法名(参数类型,参数名){
...
方法体
...
return 返回值;
}
-
修饰符:可选常用public / static。
-
返回值类型:方法可能会存在返回值。returnValueType是方法的返回值的类型。有返回至请根据返回类型进行设置(基本数据类型),无返回值则void。
-
方法名:遵循驼峰命名法,见名知意。
-
参数类型:参数就是一个占位符,当方法被调用时,传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型、顺序和参数的个数。参数可选,方法可以不包含任何参数。
-
形参:在方法被调用时用于接受外界输入的参数。
-
实参:调用方法时实际传给方法的参数。
-
-
方法体:定义功能。
静态方法与非静态方法
静态方法与非静态方法的区别
静态方法和类一起加载,不能直接调用非静态方法。
静态方法可以通过类调用或者在main 方法中可以直接调用。
非静态方法与实例化对象相关,需要实例化对象后才能被调用。
public class Demo{
public static void main(String[] args){
fn(); // error "Non-static method 'say()' cannot be referenced from a static context"
Demo.fn(); // error "Non-static method 'say()' cannot be referenced from a static context"
fn2(); // 调用了静态方法
Demo demo = new Demo();
demo.fn(); // 调用了非静态方法
demo.fn3(); // 调用了非静态方法
Demo.fn2(); // 调用了静态方法
}
public void fn(){
System.out.println("调用了非静态方法")
}
puclic static void fn2(){
System.out.println("调用了静态方法")
}
public void fn3(){
fn1(); // 非静态方法是可以互相调用的。
}
}
方法调用
- 静态方法的调用
public class Main{
public static void main(String[] args){
Main.test();
test();
}
public static void test(){
}
}
- 非静态方法的调用
public class Main{
public static void main(String[] args){
Main main = new Main();
main.test();
}
public void test(){
}
}
方法重载
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0ovcJtDs-1653532454463)(imgclip.png “imgclip.png”)]
public class Main{
public static void main(String[] args){
test(10.0,10.0);
test(10,20);
}
public static void test(int a,int b){
System.out.println(a+','+b);
}
public static void test(double a,double b){
System.out.println(a+','+b);
}
}
形参与实参
形参与实参的概念
-
形参:函数体中需要使用到的参数,目的用于接收调用该函数时传入的参数。
-
实参:实际的参数,就是调用方法传递的参数。
可变参数
一个方法中只能定义一个可变参数,它必须是方法的最后一个参数。任何普通参数都必须在它之前声明。
public class Demo{
public static void main(String[] args){
Demo demo = new Demo();
int sum = demo.test(1,2,3);
System.out.println(sum);
}
public static int test(int... numbers){
int sum = 0;
for(int i = 0; i < numbers.length; i++){
sum += numbers[i];
}
return sum;
}
}
递归方法
简单来说就是自己调用自己。
递归结构包含两个部分:
-
递归头:什么时候不调用自身方法。如果没有递归头将会陷入死循环。
-
递归体:什么时候调用自身方法。
一定要注意栈溢出带来的问题
public class Main{
public static void main(String[] args){
test(); //=> error,栈溢出
}
public static void test(){
test();
}
}
使用递归处理阶乘问题:
// 5! 5*4*3*2*1
public class Main{
public static void main(String[] args){
int a = test(5);
System.out.print(a);
}
public static int test(int i){
if(i==1){
return 1;
}else{
return i * test(i-1);
}
}
}
值传递与引用传递
什么是值传递、引用传递
- 值传递:调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改将不会影响实际的参数。
public class Demo{
public static void main(String[] args){
int a = 1;
Demo.changeA(a);
System.out.print("print in main ->",a);
}
public static void changeA(int a){
a = 10;
System.out.println("print in changeA ->",a);
}
}
----> 输出结果:
print in changeA ->10
print in main ->1
- 引用传递:调用函数时将实际参数的地址直接传递到函数中,这样函数中如果对参数进行修改将会影响实际的参数。
public class Demo{
public static void main(String[] args){
Demo2 demo2 = new Demo2();
demo2.setName("hello");
Demo.changeObj(demo2);
System.out.println("print in main ->",demo2);
}
public static void changeObj(Object obj){
obj.setName("world")
System.out.println("print in changeObj ->",obj);
}
}
public class Demo2{
private String name; // 封装一个私有属性
public String getName(){
return this.name;
}
public void setName(String name){
this.name = name;
}
}
----> 输出结果:
print in changeObj ->{name='world'}
print in main ->{name='world'}
经过上述的方法执行后,实参的值竟然被改变了,那按照上面的引用传递的定义,实际参数的值被改变了,这不就是引用传递了么。于是,根据上面的两段代码,得出一个新的结论:
Java的方法中,在传递普通类型的时候是值传递,在传递对象类型的时候是引用传递。但是,这种表述仍然是错误的。
public class Demo{
public static void main(String[] args){
String name = "hello";
changeString(name);
System.out.println("print in main ->",name);
}
public static void changeString(String name){
name = "world";
System.out.println("print in changeString ->",name);
}
}
----> 输出结果:
print in changeObj ->world
print in main ->hello
在java 到底是值传递还是引用传递?
-
错误理解一:值传递和引用传递区分条件是传递的内容,如果传递的是基本数据类型的值就是值传递,如果传递的是引用数据类型的值就是引用传递。
-
错误理解二:java 就是引用传递。
答案只能是值传递!!
实际上在证明java 到底是值传递还是引用传递的过程中,验证方法就已经出错了,当然得到的结果就是错的。
为什么说是实验结果出错呢?本质上还是得从值传递和引用传递的根本区别入手:
值传递 | 引用传递 | |
---|---|---|
根本区别 | 会创建副本(复制一份) | 不会创建副本 |
执行效果 | 函数中 无法改变原始参数 | 函数中 可以改变原始参数 |
那么最终如何验证java 是值传递的?
public class Demo{
public static void main(String[] args){
Demo2 demo2 = new Demo2();
demo2.setName("hello");
changeObj(demo2);
System.out.println("print in main ->",demo2);
}
public static void changeObj(Demo2 obj){
obj = new Demo2();
obj.setName("world");
System.out.println("print in changeObj ->",obj);
}
}
public class Demo2{
private String name; // 封装一个私有属性
public String getName(){
return this.name;
}
public void setName(String name){
this.name = name;
}
}
----> 输出结果:
print in changeObj ->{name='world'}
print in main ->{name='hello'}
结论
-
java中方法参数传递方式是按值传递。
-
如果参数是基本类型,传递的是基本类型的字面量值的拷贝。
-
如果参数是引用类型,传递的是该参量所引用的对象在堆中地址值的拷贝。
main 方法
main 方法描述
public class Demo{
public static void main(String[] args){
}
}
【public】 :main 方法是启动时候由JVM 进行加载的,public 的可访问权最高,所以必须使用public 修饰
【static】:调用方法的方式可以是对象调用、类调用,而main 方法因为是JVM 调用所以无需生成对象,所以使用static 修饰
【void】:调用main 方法无返回值,所以使用void修饰
【main】:C语言规定使用main 作为方法名
【String[]】:此字符串数组用来运行时接受用户输入的数据;因为字符串在JAVA 中具有普遍性,所以使用字符串是最优选择;数组的话因为参数不只一个,所以使用String[] 作为参数
不过在JDK 1.5 引入动态参数后String[] args也可以使用String… args 来实现。
public class Demo{
public static void main(String... args){
}
}
main 方法的重载
public class Demo {
public static void main(String args) {
System.out.println("main");
}
public static void main(String[] args) {
main("test");
}
}
main 被其他方法调用
public class Demo {
public static int count = 3;
public static void main2(String[] args) {
count--;
main(args);
}
public static void main(String[] args) {
System.out.println("main 方法执行次数:" + count);
if (count <= 0) System.exit(0);
main2(args);
}
}
输出结果:
main 方法执行次数:3
main 方法执行次数:2
main 方法执行次数:1
main 方法执行次数:0
main 方法被子类继承重写
public class Demo {
public static void main(String[] args) {
System.out.println("父类main 方法");
}
}
class Demo2 extends Demo{
public static void main(String[] args) {
System.out.println("子类main 方法");
}
}
输出结果:
子类main 方法
this 指向
this
指向类的实例对象
- this 修饰属性,
this.[属性名]
给实例中的属性赋值。 - this 修饰方法,
this.[方法名]
调用实例对象中的方法。 - this 修饰构造器,在构造器中直接调用
this([参数])
传递到同名重载构造器中,且this修饰构造器必须放在第一行。
public class Main{
public Main(){
}
// 有参构造器
public Main(String name,int age){
this(age);
this.name = name;
}
public Main(int age){
this.age = age;
}
public static void main(String[] args){
Main m = new Main("haha",20);
m.say('hello,world')
}
// 属性
int age;
String name;
String word;
public void say(String word){
// this执行实例化本身
this.word = word;
System.out.println(this.name);
System.out.println(this.age);
System.out.println("他说:" + this.word)
}
}
静态代码块
https://blog.csdn.net/qq_35868412/article/details/89360250
java 中的四种代码块
-
方法块:静态方法、非静态方法、构造方法
-
非静态代码块:非静态代码块会 在创建对象时被调用,每次创建时都会被调用,优先于构造方法执行。
-
静态代码块:与类一并加载,用static{}包裹起来的代码片段,只会执行一次。静态代码块优先于非静态代码块执行。
-
同步代码块:使用synchronized(){}包裹起来的代码块,在多线程环境下,对共享数据的读写操作是需要互斥进行的,否则会导致数据的不一致性。
静态代码块与非静态代码块的异同点
相同点:都是jvm 加载类之后构造函数执行之前执行,在类中可以定义多个,一般代码块中对一些static 变量进行赋值操作。注意是赋值操作,避免空指针异常
package com.example.demo;
import java.util.HashMap;
public class test2 {
private static HashMap<Integer,String> hashMap = null;
static {
hashMap.put(1,"2"); // 对一个指针进行操作,语法错误
}
public static void main(String[] args) {
System.out.println(hashMap);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wtCtVK3j-1653532590899)(imgclip.png “imgclip.png”)]
正确操作:
package com.example.demo;
import java.util.HashMap;
public class test2 {
private static HashMap<Integer,String> hashMap = null;
static {
hashMap = new HashMap<>(); // 先赋值再操作
hashMap.put(1,"2");
}
public static void main(String[] args) {
System.out.println(hashMap);
}
}
不同点:静态代码块优先执行于非静态代码块,静态代码块只在第一次实例化对象执行一次,之后不再执行。而非静态代码块每实例化一次执行一次。
面试题
请问输出情况:
public class Demo {
public static String name = "静态属性";
static {
System.out.println(name);
System.out.println("静态代码块");
}
{
System.out.println("非静态代码块1");
}
public Demo() {
System.out.println("无参构造器");
}
{
System.out.println("非静态代码块2");
}
public void fn1() {
System.out.println("非静态方法");
}
public static void main(String[] args) {
Demo demo = new Demo();
System.out.println("main 静态方法块");
Demo demo2 = new Demo();
demo2.fn1();
}
}
---> 输出结果
静态属性
静态代码块
非静态代码块1
非静态代码块2
无参构造器
main 静态方法块
非静态代码块1
非静态代码块2
无参构造器
非静态方法
出现继承情况:
class Demo2 {
public static String name = "静态属性";
static {
System.out.println("子类静态属性" + Demo1.name);
System.out.println("Demo2 静态代码块");
}
{
System.out.println("Demo2 非静态代码块1");
}
public Demo2() {
System.out.println("Demo2 无参构造器");
}
{
System.out.println("Demo2 非静态代码块2");
}
}
class Demo1 extends Demo2 {
public static String name = "静态属性";
static {
System.out.println(name);
System.out.println("父类静态属性" + Demo2.name);
System.out.println("静态代码块");
}
{
System.out.println("非静态代码块1");
}
public Demo1() {
// super();
System.out.println("无参构造器");
}
{
System.out.println("非静态代码块2");
}
public void fn1() {
System.out.println("非静态方法");
}
}
public class Demo {
public static void main(String[] args) {
Demo1 demo = new Demo1();
System.out.println("main 静态方法块");
Demo1 demo1 = new Demo1();
demo1.fn1();
}
}
---> 输出结果
子类静态属性null
Demo2 静态代码块
静态属性
父类静态属性静态属性
静态代码块
Demo2 非静态代码块1
Demo2 非静态代码块2
非静态代码块1
非静态代码块2
无参构造器
main 静态方法块
Demo2 非静态代码块1
Demo2 非静态代码块2
非静态代码块1
非静态代码块2
无参构造器
非静态方法
结论
父静态属性 -> 父静态代码块 -> 子静态属性 -> 子静态代码块 -> 子静态方法main -> 父非静态代码块 -> 父构造方法 -> 子非静态代码块 -> 子构造方法 -> 非静态方法