Java方法
何谓方法
-
System.out.println(),它是啥?
System:类
out:System类中的一个对象
println():方法
-
Java方法是语句的集合,它们在一起执行一个功能
1、方法是解决一类问题的步骤的有序集合
2、方法包含于类或对象中
3、方法在程序中被创建,在其他地方被引用
-
设计方法的原则:
方法的本意是功能块,就是实现某个功能的语句块的集合,我们设计方法的时候,最好保持方法的原子性,就是一个方法只完成一个功能,这样有利于后期的扩展
方法定义
方法包含一个方法头和一个方法体,下面是一个方法的所有部分:
- 修饰符:可选,告诉编译器如何调用该方法,定义了该方法的访问类型
- 返回值类型:方法可能会有返回值,returnValueType是方法返回值的数据类型,无返回值,returnValueType是关键字void
- 方法名:是方法的实际名称,方法名和参数表共同构成方法签名
- 参数类型:参数像是一个占位符,当方法被调用时,传递值给参数,这个值被称为实参或者变量,参数列表是指方法的参数类型、顺序和参数的个数。参数时可选的,方法可以不包含任何参数
形式参数:在方法被调用时用于接收外界输入的数据
实参:调用方法时实际传给方法的数据
- 方法体:方法体包含具体的语句,定义给该方法的功能
package cn.itcast.day05.demo01;
/*
* 方法其实就是若干语句的功能集合
* 方法好比一个工厂
* 蒙牛工厂 原料:奶牛、饲料、水
* 产出物:奶制品
* 参数(原料):就是进入方法的数据。
* 返回值(产出物):就是从方法中出来的数据
*
* 定义方法的完整格式:
* 修饰符 返回值类型 方法名称(参数类型 参数名称,...){
* 方法体
* return 返回值;
* }
* 修饰符:现阶段的固定写法,public static
* 返回值类型:也就是方法最终产生的数据结果是什么类型
* 方法名称:方法的名字,规则和变量一样,小驼峰
* 参数类型:进入方法的数据是什么类型
* 参数名称:进入方法的数据对应的变量名称
* PS:参数如果有多个,使用逗号进行隔离
* 方法体:方法需要做的事情,若干行代码
* return:两个作用,第一停止当前方法,第二将后面的返回值还给调用处
* 返回值:也就是方法执行后最终产生的数据结果
*注:return后面的“返回值”,必须和方法名称前面的“返回值类型”,保持对应
*
定义一个两个int数字相加的方法。三要素:
返回值类型:int
方法名称:sum
参数列表:int a,int b
方法的三种调用格式
1.单独调用:方法名称(参数);
2.打印调用:System.out.println(方法名称(参数))
3.赋值调用
注:此前学习的方法,返回值类型固定写为void,这种方法只能够单独调用,不能进行打印调用或则赋值调用
*/
public class Demo02MethodDefine {
public static void main(String[] args) {
//单独调用
sum(10,20);
System.out.println("=================");
//打印调用
System.out.println(sum(10,20)); //30
System.out.println("=================");
//赋值调用
int number = sum(15,25); //140
number += 100;
System.out.println("变量的值:"+number);
}
public static int sum(int a,int b){
System.out.println("方法执行啦!");
int result = a+b;
return result;
}
}
方法调用
-
调用方法:对象名.方法名(实参列表)
-
Java支持两种调用方法的方式,根据方法是否返回值来选择
-
当方法返回一个值的时候,方法调用通常被当做一个值,例如:
int max = max(10, 20);
-
如果方法返回值是void,方法调用一定是一个语句,例如:
System.out.println("Hello keke");
package com.keke.method;
/**
* @ClassName: Demo01
* @Description:
* @Author: keke
* @Date: 2021/4/4
*/
public class Demo01 {
public static void main(String[] args) {
int max = max(10, 20);
System.out.println(max);
}
//比大小
public static int max(int num1, int num2) {
int result = 0;
if (num1 == num2) {
System.out.println("num1==num2");
return 0; //终止方法
}
if (num1 > num2) {
result = num1;
} else {
result = num2;
}
return result;
}
}
package cn.itcast.day05.demo01;
//1.定义一个方法,用来【求出】两个数字之和,(你来算我,算完之后把结果告诉我)
//2.定义一个方法,用来【打印】两个数字之和,(你来算我,算完之后自己负责显示结果,不用告诉我)
//对于有返回值的方法,可以使用单独调用,打印调用和赋值调用
//但是对于无返回值的方法,只能使用单独调用,不能使用打印调用和赋值调用
public class Demo03Methodreturn {
public static void main(String[] args) {
int num = getSum(10,20);
System.out.println("返回值是:" + num); //1
System.out.println("=====================");
printsum(100,200); //2
System.out.println("=====================");
// System.out.println(printsum(2,3));
// System.out.println(void); 错误写法
// int num2 = printsum(10,20);
// int num3 = void;
// void num4 = void; 错误写法
}
public static int getSum(int a,int b){
int result = a + b;
return result;
}
public static void printsum(int a,int b){ //只要不是void都有返回值
int result = a + b;
System.out.println("结果是:"+result);
}
}
- 方法的注意事项:
1.不能嵌套定义方法
2.定义方法前后顺序无所谓
3.要执行方法一定要调用
4.如果方法有返回值,必须写return
5.return后面的返回值数据必须和方法的返回值类型对应
6.对于void方法return可以不写
7.一个方法中可以有多个return,但必须保证同时只有一个会被执行,两个return不能连写
方法重载
-
重载就是在一个类中,有相同的函数名称,但形参不同的函数
-
方法重载的规则:
方法名称必须相同
参数列表必须不同(个数不同、或类型不同、或参数多类型排列顺序不同)
方法的返回类型可以相同也可以不相同
仅仅返回类型不同不足以成为方法的重载
参数的名称不同与方法的重载无关
-
实现理论:
方法名称相同时,编译器会根据调用方法的参数个数、参数类型等逐个去匹配,以选择对应的方法,如果匹配失败,则编译报错
package cn.itcast.day05.demo02;
/*
对于功能类似的方法来说,因为参数列表不一样,却要记住那么多不同的方法名称,太麻烦
方法重载(overload):多个方法的名称一样,单参数列表不一样。
好处:只需要记住一个方法的名称,就可以实现类似的多个功能。
方法重载的相关因素:
1.参数个数不同
2.参数类型不同
3.参数的多类型顺序不同
方法重载的无关因素:
1.与参数的名称无关
2.与方法的返回值类型无关
*/
public class Demo01MethodOverload {
public static void main(String[] args) {
System.out.println(sum(10,20));
System.out.println(sum(10,20,30));
System.out.println(sum(10,20,30,40));
// System.out.println(sum(10,20,30,40,50)); 错误,没有方法匹配
}
public static int sum(int a,double b){
return (int)(a + b);
}
public static int sum(double a,int b){
return (int)(a + b);
}
public static int sum(int a,int b){
return a + b;
}
/*public static double sum(int a,int b){
return a + b + 0.0; //加不加0.0返回值都是double类型
} 报错,仅仅返回类型不同不足以成为方法的重载*/
/*public static int sum(int x,int y){
return a + b;
} 报错,参数的名称不同与方法的重载无关*/
public static int sum(double a,double b){
return (int) (a + b);
}
public static int sum(int a,int b,int c){
return a + b + c;
}
public static int sum(int a,int b,int c,int d){
return a + b + c + d;
}
}
package cn.itcast.day05.demo02;
public class Demo03OverloadJudge {
public static void open(){} //正确重载
public static void open(int a){} //正确重载
// static void open(int a,int b){} //代码错误,和第8行重载,可取其一
public static void open(double a,int b){} //正确重载
// public static void open(int a,double b){} //代码错误,和第6行重载
// public void open(int i,double d){} //代码错误,和第5行重载
public static void OPEN(){} //代码不会报错,但不是有效重载
public static void open(int i,int j){} //代码错误,和第3行冲突,可取其一
}
命令行传参(了解)
- 有时候希望运行一个程序的时候再传递给它消息,这就要靠传递命令行参数给main()函数来实现
package com.keke.method;
/**
* @ClassName: Demo02
* @Description:
* @Author: keke
* @Date: 2021/4/4
*/
public class Demo02 {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "]:" + args[i]);
}
}
}
注意:执行过程需要通过命令行的方式,而且需要去掉代码上面的包名,不去掉包名,需要到java Demo02.java命令在拥有Demo02.java的目录下执行生成class文件没问题,但执行javac Demo02命令直接在该目录下执行不行,需要到项目的src目录下执行javac com.keke.method.Demo02才可以(但是我的电脑不知道为什么不行,下面为去掉包名的执行截图)
可变参数
- JDK1.5开始,Java支持传递同类型的可变参数给一个方法
- 在方法声明中,在指定参数类型后面加一个省略号(…)
- 一个方法中只能指定一个可变参数,他必须是方法的最后一个参数,任何普通的参数必须在它之前声明
- 可变参数的本质其实就是数组
package com.keke.method;
/**
* @ClassName: Demo03
* @Description: 练习:计算一组数的最大值
* @Author: keke
* @Date: 2021/4/4
*/
public class Demo03 {
public static void main(String[] args) {
// 调用可变参数的方法
printMax(34, 3, 3, 2, 56.5);
printMax(new double[]{1, 3.4, 5});
}
public static void printMax(double... numbers) {
if (numbers.length == 0) {
System.out.println("No argument passed");
return;
}
double result = numbers[0];
//排序!
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] > result) {
result = numbers[i];
}
}
System.out.println("The max value is " + result);
}
}
递归
-
递归就是:自己调用自己
-
利用递归可以用简单的程序来解决一些复杂的问题,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复的计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合
-
递归结构包括两个部分:
递归头:什么时候不调用自身方法,如果没有头,将陷入死循环
递归体:什么时候需要调用自身方法
下面这段代码因为没有递归头导致java.lang.StackOverflowError异常
package com.keke.method;
/**
* @ClassName: Demo04
* @Description:
* @Author: keke
* @Date: 2021/4/4
*/
public class Demo04 {
public static void main(String[] args) {
Demo04 demo04 = new Demo04();
demo04.test();
}
public void test() {
test();
}
}
练习:利用递归就阶乘
package com.keke.method;
/**
* @ClassName: Demo05
* @Description: 阶乘 例:5!:5*4*3*2*1
* @Author: keke
* @Date: 2021/4/4
*/
public class Demo05 {
public static void main(String[] args) {
System.out.println(f(5));
}
public static int f(int n) {
if (n==1) {
return 1;
} else {
return n*f(n-1);
}
}
}
注意:因为Java都是使用栈机制的,相当于一个罐子,最底层是main()方法,每调用一个方法就会把加在上面,如果方法结束,就消失,main()方法结束即栈就空了,程序停止,递归会带来大量的方法调用,故若递归深度较大时,电脑很可能会卡死,所以能不用递归就不用,如果基数比较小可以如上
练习
package com.keke.method;
import javax.sound.midi.Soundbank;
import java.sql.SQLOutput;
import java.util.Scanner;
/**
* @ClassName: Demo06
* @Description: 练习:写一个控制台计算器,要求实现加减乘除功能,并且能够循环接收新的数据,通过用户交互实现
* @Author: keke
* @Date: 2021/4/4
*/
public class Demo06 {
public static void main(String[] args) {
while (true) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入运算符(+-*/,退出请输入quit):");
String operator = sc.nextLine();
if (operator.equalsIgnoreCase("quit")) {
System.out.println("退出当前计算器");
break;
}
System.out.println("请输入第一数:");
double num1 = sc.nextDouble();
System.out.println("请输入第二数:");
double num2 = sc.nextDouble();
double result = 0;
switch (operator) {
case "+":
result = add(num1, num2);
System.out.println(num1 + operator + num2 + "=" + result);
break;
case "-":
result = del(num1, num2);
System.out.println(num1 + operator + num2 + "=" + result);
break;
case "*":
result = mul(num1, num2);
System.out.println(num1 + operator + num2 + "=" + result);
break;
case "/":
if (num2 == 0) {
System.out.println("0不能作除数!");
} else {
result = div(num1, num2);
System.out.println(num1 + operator + num2 + "=" + result);
}
break;
default:
System.out.println("输入运算符错误!");
}
}
}
public static double add(double num1, double num2) {
return num1 + num2;
}
public static double del(double num1, double num2) {
return num1 - num2;
}
public static double mul(double num1, double num2) {
return num1 * num2;
}
public static double div(double num1, double num2) {
return num1 / num2;
}
}
重点扩展:Java是值传递和还是引用传递
值传递、引用传递定义:
代码解释
package com.keke.extend;
/**
* @ClassName: StringBase
* @Description: 值传递的解释
* @Author: keke
* @Date: 2021/4/4
*/
public class StringBase {
public static void main(String[] args) {
int c = 66; //c 叫做实参
String d = "hello"; //d 叫做实参
StringBase stringBase = new StringBase();
stringBase.test5(c, d); // 此处 c 与 d 叫做实参
System.out.println("c的值是:" + c + " --- d的值是:" + d);
}
public void test5(int a, String b) { // a 与 b 叫做形参
a = 55;
b = "no";
}
}
运行结果:
c的值是:66 — d的值是:hello
可以看出通过方法传递后,int 类型与 String 类型的原值并没有受到前面 test5 方法执行后的影响,还是输出了原值。这种形为通常被说成值传递。如果原值经过 test5 方法后被改变了,这种形为通常被描述为引用传递。
值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数
引用传递:指在调用函数时将实际参数的地址直接传递到函数中(的形参),那么在函数中对参数所进行的修改,将影响到实际参数
引用传递:形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作(C++ 的定义,我觉得这样说更精简形象一些,所以放了两个定义,其实意思是一样的)
注意:Java中只有值传递
数据存储
1.基本数据类型、引用类型定义
基本数据类:Java 中有八种基本数据类型“byte、short、int、long、float、double、char、boolean”
引用类型:new 创建的实体类、对象、及数组
2.基本数据类型、引用类型在内存中的存储方式
基本数据类型:存放在栈内存中。用完就消失。
引用类型:在栈内存中存放引用堆内存的地址,在堆内存中存储类、对象、数组等。当没用引用指向堆内存中的类、对象、数组时,由GC回收机制不定期自动清理。
3.基本类型、引用类型内存简单说明图
在 Java 中 值传递 与 引用传递,产生模糊不清的代码
package com.keke.extend;
/**
* @ClassName: TransmitTest
* @Description: 关于Java是值传递还是引用传递有争议的代码
* @Author: keke
* @Date: 2021/4/4
*/
public class TransmitTest {
public static void main(String[] args) {
String a = "hello"; //String 引用数据类型,调用 pass 方法后 a 的值没有改变,还是 hello
int b = 1; //int 基本数据类型,调用 pass 方法后 b 的值没有改变,还是 1
User user = new User(); //new Class 引用类型,调用 pass 方法后 name 与 age 的值改变了
user.setName("main"); // 调用 pass 后,name 为 pass 了
user.setAge(2); //调用 pass 后,age 为 4 了
pass(user, a, b); //pass 方法调用
System.out.println("main 方法 user 是:" + user.toString());
System.out.println("main 方法 a 的值是:" + a + " --- b 的值是:" + b);
}
public static void pass(User user, String a, int b) {
a = "你好";
b = 3;
user.setName("pass");
user.setAge(4);
System.out.println("pass 方法 user 是:" + user.toString());
System.out.println("pass 方法 a 的值是:" + a + " --- b 的值是:" + b);
}
}
class User {
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
运行结果:
pass 方法 user 是:User{name=‘pass’, age=4}
pass 方法 a 的值是:你好 — b 的值是:3
main 方法 user 是:User{name=‘pass’, age=4}
main 方法 a 的值是:hello — b 的值是:1
结果分析:
main 方法 a 的值是:hello — b 的值是:1 结果分析,int b,实参是 1,pass 方法调用后,值还是 1 没变,说明基本数据类型是值传递
存在争议的地方:
1.String a 是引用类型,pass 方法调用后,值还是 hello 没变。(结论:好像引用类型也是值传递啊!!!)
2.new User() 也是引用类型,在方法调用后,值居然变了。原值不应该是 name = main,age = 2 的吗?为什么 name = pass,age = 4 了呢?(结论:引用类型好像是引用传递啊???)
这就奇葩了,String 与 new 创建的类,同为引用类型,为什么产生的结果不一样呢?String 不也是一个类吗?User 不也是一个类吗?
有人解释说这个代码比喻的不对,应该用如下代码比喻,在 pass 方法中添加一行代码,user = new User(),pass 方法修改如下
public static void pass(User user, String a, int b) {
a = "你好";
b = 3;
user = new User(); //添加的代码
user.setName("pass");
user.setAge(4);
System.out.println("pass 方法 user 是:" + user.toString());
System.out.println("pass 方法 a 的值是:" + a + " --- b 的值是:" + b);
}
运行结果:
pass 方法 user 是:name = pass — age = 4
pass 方法 a 的值是:你好 — b 的值是:3
main 方法 user 是:name = main — age = 2
main 方法 a 的值是:hello — b 的值是:1
这样一来,改变了形参的值,但是实参没有改变。因此有人得出结论,Java 中只有值传递,没有引用传递(但我不这样认为,做深入研究)
其实:user的内容虽然改变了,但是user指向的那个对象并没有改变,这么说吧,user = new user假如这个对象的堆中的地址为1,user始终是指向地址为1的那个对象,所以对象的内容(属性)发生变化,不代表该对象也发生变化。
使用 user = new User() 这个代码来做验证,我觉得是符合 String 类型做形参时的验证的,但是,此示例不符合引用传递的验证。
在验证之前,我们先看下使用 user=new User(); 语句之前与之后的内存模型图,能有助于我们更好的验证结果,同时也有助于更好的理解 Java 内存模型。我们看 TransmitTest 类在 Java 内存模型中的存储图
图1 pass() 方法中没有使用 user=new User() 语句的内存模型图
在 图1 中,main() 方法中的 user 类,与 pass() 方法中的 user 类,指向的是同一个堆内存中的 User 类,红色虚线是在 main() 方法中初次给 name 属性赋的值"main"。实线部分,是在 pass() 方法中给 name 属性赋的值"pass"。因为在堆内存中只有一个 User 类实体,因此 main() 方法与 pass() 方法中的 user 指向的都是同一个 User 类 0x000031。因此,无论在 main() 方法还是 pass() 方法中,改变其 user 的属性值后,打印 User 类的属性值肯定是一样的,他们用的是一个实体类。
图2 pass() 方法中使用了 user=new User() 语句的内存模型图
在 图2 中,main() 方法中的 user 类首次加载,堆内存开辟了一个地址为 0x000031 的 User 类实体。当把 main() 方法中的实参 user 传递给 pass() 方法中形参 user 的时候,栈内存在 pass() 方法区中开辟了一个空间,并引用了地址为 0x000031 的 User 类。此时两个方法中的 User 类其实是一个。
然而当 pass() 方法中的 user=new User()语句执行后,堆内存中新开辟了一个地址为 0x000032 的 User 类,pass() 方法中的 user 从此指向了地址为 0x000032 的 User 类。
因为 pass() 方法 与 main() 方法中的 user 属性分别指向了不同的 User 类,所以两个方法中的 User 类的属性无论怎么修改,相互都不影响。
但是,这种操作是不能验证引用传递定义的。因为实参传值给形参后,形参自己改变了地址,这就和引用传递无关了。
通过上面分析,在对引用类型做方法传递的时候,是先把实参的地址给形参的,之后对形参的操作是相当于操作实参,最后有影响到实际参数!
对比值传递和引用传递的定义:
定义关键1: 是指在调用函数时将实际参数的地址直接传递到函数中(给形参了)
证明:Java 在进行方法调用传递引用类型参数的时候,就是先给形参一个与实参相同的地址的(此处与 C++ 的不同之处是,C++ 是别名,没有在内存中给形参开辟空间,而 Java 给形参开辟了一个栈内存空间,存放与实参相同的引用地址。但是这与引用传递的定义不违背啊!!!定义可没说形参是否有开辟空间的概念)。
定义关键2: 在函数中对参数所进行的修改,将影响到实际参数。
证明:Java 在进行方法调用传递引用类型参数后,修改形参的内容后,就是影响了实参的值。
String 与包装类的特殊分析
好了,解决了实例对象,我们再来说 String 与包装类,为什么 String 与包装类作为引用类型,却有值传递的功能,居然没有影响到实参!
原因如下:
我们都知道。String 类型及其他七个包装类,是一群特殊群体。当使用 String a = “hello”; 语句时,相当于执行了 String a = new String(“hello”)。然而在 Java 中每一次 new 都是一次对象的创建。如果你创建的对象在堆中不存在,便会创建一个,如果是新创建的对象,那么地址都会变的,后期改变的地址,这跟引用传递,值传递还有什么关系?
其实 String 型方法参数传值的过程,可以用以下代码来解释,我们先看 String 类型的还原:
String a = "hello"; //a 相当于实参
String a1 = a; //a1 就相当于形参
a1 = "你好";
System.out.println("a是:" + a + " --- a1是:" + a1);
运行结果:
a是:hello — a1是:你好
逐步还原解释:
String a = “hello”; 在 String 池中检查并创建一个常量:“hello”,给 a 分配一个栈内存,在此存储常量 hello 的地址。
String a1 = a; 给 a1 分配一个栈内存,在此存储常量 hello 的地址。相当于 a 把自己持有的地址,复制给了 a1。
内存图如下
a1 = “你好”; 等同于 a1 = new String(“你好”)。在 String 池中检查是否有 “你好” 的常量。如果有,将 a1 的地址指向 “你好” 的地址。如果 String 池中没有 “你好” 常量,在堆内存中创建 “你好” 常量,并将 a1 地址指向 “你好”。
内存图如下
总结如下:String 类型,在进行方法传参的时候,是先将实参地址,赋值给形参(形参在栈内存中确实新开辟了一个新的内存空间,用于存储地址)。但是当再次给 String 类型的形参赋值(与实参内容不一样的值时),形参地址变了,这就和引用传递无关了。因此,String 与其他包装类,在做形参的时候,由于他们在赋不同于实参的值时,改变了形参的地址,因此使引用传递,看起来像值传递,还有一个原因,String 类型不能像正常类对象一样直接修改是因为,String内部类的value数组定义成了private final,因此是一个不可修改类,所以如果需要修改的时候,需要重新开辟空间赋值,也就导致了它看起来是值传递,其实本质还是引用传递。
总结
1.这个题目出的不严谨,但是很好(因为涉及了 Java 内存模型)
2.就 Java 语言本身来说,只有值传递,没有引用传递。
3.根据 值传递,引用传递的定义来说:
Java 中的基本类型,属于值传递。
Java 中的引用类型,属于引用传递。
Java 中的 String 及包装类,属于特殊群体,因为String 类型不能像正常类对象一样直接修改,String内部类的value数组定义成了private final,是一个不可修改类,所以如果需要修改的时候,需要重新开辟空间赋值,也就导致了它看起来是值传递,作为形参时,由于每次赋值都相当于重新创建了对象,因此看起来像值传递,但是其特性已经破坏了,值传递、引用传递的定义。因此他们属于引用传递的定义,却表现为值传递。
重点扩展引用地址:https://blog.csdn.net/xiaojinlai123/article/details/88678367
个人以前看不懂的一道链表题
题目:删除排序列表中重复的元素
- 存在一个按升序排列的链表,给你这个链表的头节点head,请你删除所有重复的元素,使得每个元素只出现一次,返回同样的按升序排序的结果链表
- 例:1,1,2 -> 1,2
package com.itheima.interview.linkedlist2success;
/**
* 删除排序列表中重复的元素
* 存在一个按升序排列的链表,给你这个链表的头节点head,请你删除所有重复的元素,使得每个元素只出现一次,返回同样的按升序排序的结果链表
* 例:1,1,2 -> 1,2
*/
public class TrueTest {
static ListNode node1 = new ListNode(1);
static ListNode node2 = new ListNode(2);
static ListNode node3 = new ListNode(3);
static ListNode node4 = new ListNode(3);
static ListNode node5 = new ListNode(3);
static ListNode node6 = new ListNode(4);
static ListNode node7 = new ListNode(5);
static class ListNode {
int val;
ListNode next;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
@Override
public String toString() {
return val + "->" + next;
}
}
static class Solution {
public static ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return null;
}
ListNode pev = head;
ListNode cur = head;
while (cur != null) {
if (pev.val == cur.val) {
pev.next = cur.next;
} else {
pev = cur;
}
cur = cur.next;
}
return head;
}
public static void main(String[] args) {
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node6;
node6.next = node7;
node7.next = null;
System.out.println(node1);
System.out.println(deleteDuplicates(node1));
}
}
}
对于这题以前主要不理解为什么明明没有对head做任何改变但最后head居然改变了,解释之前可以先看上面一段代码我加的一段代码
package com.keke.extend;
/**
* @ClassName: TransmitTest
* @Description: 关于Java是值传递还是引用传递有争议的代码
* @Author: keke
* @Date: 2021/4/4
*/
public class TransmitTest {
public static void main(String[] args) {
String a = "hello"; //String 引用数据类型,调用 pass 方法后 b 的值没有改变,不是 hello
int b = 1; //int 基本数据类型,调用 pass 方法后 a 的值没有改变,还是 1
User user = new User(); //new Class 引用类型,调用 pass 方法后 name 与 age 的值改变了
user.setName("main"); // 调用 pass 后,name 为 pass 了
user.setAge(2); //调用 pass 后,age 为 4 了
pass(user, a, b); //pass 方法调用
System.out.println("main 方法 user 是:" + user.toString());
System.out.println("main 方法 a 的值是:" + a + " --- b 的值是:" + b);
}
public static void pass(User user, String a, int b) {
a = "你好";
b = 3;
// user = new User(); //一些人认为需要添加的一行代码,给user分配了一个新的地址
User user1 = user; //相当于把user1的地址也指向了user的地址,改变user1也就是改变user指向的地址了,也就改变user了
user1.setName("pass");
user1.setAge(4);
System.out.println("pass 方法 user1 是:" + user1.toString());
System.out.println("pass 方法 user 是:" + user.toString());
System.out.println("pass 方法 a 的值是:" + a + " --- b 的值是:" + b);
}
}
class User {
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
运行结果:
pass 方法 user1 是:User{name=‘pass’, age=4}
pass 方法 user 是:User{name=‘pass’, age=4}
pass 方法 a 的值是:你好 — b 的值是:3
main 方法 user 是:User{name=‘pass’, age=4}
main 方法 a 的值是:hello — b 的值是:1
解释:其实User user1 = user就是新建了一个user1对象也指向了user的地址,改变user1的属性值,即也就是改变了user的属性值
内存模型图如下:
结论:故原题中也就是新建了两个ListNode链表pev和cur都指向了head的地址,对pev和cur的改变也就是对head的改变
该题我原先的做法为:
package com.itheima.interview.linkedlist2;
/**
* 删除排序列表中重复的元素
* 存在一个按升序排列的链表,给你这个链表的头节点head,请你删除所有重复的元素,使得每个元素只出现一次,返回同样的按升序排序的结果链表
* 例:1,1,2 -> 1,2
*/
public class Test {
public static void main(String[] args) {
ListNode listNode1 = new ListNode(1);
ListNode listNode2 = new ListNode(2);
ListNode listNode3 = new ListNode(2);
ListNode listNode4 = new ListNode(3);
ListNode listNode5 = new ListNode(4);
ListNode listNode6 = new ListNode(4);
listNode1.next = listNode2;
listNode2.next = listNode3;
listNode3.next = listNode4;
listNode4.next = listNode5;
listNode5.next = listNode6;
System.out.println(listNode1);
RemoveSame(listNode1);
System.out.println(listNode1);
}
public static void RemoveSame(ListNode head) {
if (head == null) {
System.out.println("需要反转的链表为空!");
} else {
while (head.next != null) {
if (head.val == head.next.val && head.next.next != null) {
head.next = head.next.next;
head = head.next;
} else if (head.val == head.next.val) {
head.next = null;
break;
} else if (head.next.next != null) {
head = head.next;
} else {
break;
}
}
}
}
static class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
@Override
public String toString() {
return val + "->" + next;
}
}
}
总结一下这样不行的原因:
首先题目要求返回同样的按升序排序的结果链表,我尝试了很多次我原先写的方法若返回最后的head,无法达到题目要求,因为我的head是在不停的改变,而且是指向一个新的链表,故若返回达不到效果,该题关键点在于创建两个相同的head链表,若只创建一个,排重只能通过该链表内部比较,为了程序继续判断只能将创建的链表指向本身的next,这样的head改变不可能返回并满足题意!