类与对象的区别和联系
1.类是抽象的,概念的他是数据类型
2.对象是具体的实际的,代表一个具体事务
3.类是对象的模板,对象是类的个体
**对象在内存中的存在形式**
基本数据类型在堆,引用类型如String,数组在方法区
对象的属性
属性基本说明
1.成员变量or成员属性
属性的定义语法: public/protected/默认(包级私有/private
2.属性可以定义为任何类型
3.不赋值,就有默认值
如何访问属性
对象名.属性
如何创建对象
1.先声明后创建
Cat cat;
cat = new Cat();
2.直接创建
Cat cat = new Cat();
对象创建过程
1.栈:一般存放基本数据类型
2.堆:存放对象,数组等
3,方法区:常量池(比如字符串),类加载信息(只会加载一次)等
创建流程:
1.先加载Person类信息(属性和方法信息,只会加载一次)
2.在堆中分配空间,进行默认初始化,
3.把地址赋值给p,p就指向对象了
4.进行指定初始化
**类和对象的内存分配机制**
1.创建对象,加载类的信息,没赋值前是默认值
2.进行赋值,改变默认值
3.person p1 = p2;是一个拷贝传递,p1 p2共用同一个堆里的地址
成员方法
例:public void speak(){...}
public表示方法公开,void表示没返回值,speak是方法名,通过.调用方法
方法调用机制
1.当程序执行到int ret...时,会开辟一个独立的空间,然后会跳到方法所在的栈
2.方法运行完后通过返回值提前留存的地址返回到int res...=,
3.方法完成后会销毁方法
成员方法的好处
提高方法的复用性
可以将实现的细节封装起来,以便其他用户来调用
方法的定义
访问修饰符 返回值类型 方法名(参数表){//方法体
语句;
return 返回值;}
方法使用细节
方法修饰符(作用是控制方法使用范围),public,protected,默认,private
- 一个方法只能有一个返回值,若想要多个返回值,可以将其封装在数组中返回
public class Object01{
public static void main(String[] args){
AA a = new AA();
int[] res = a.getSumAndSub(1,4);
}
}
class AA{
public int[] getSumAndSub(int n1,int n2){
int[] resArr = new int[2];
resArr[0] = n1 + n2;
resArr[1] = n1 - n2;
return resArr;
}
- 返回类型可以为任何类型(基本/引用)
- 有无return 取决于是否是void ,且返回的数据必须一致或兼容(可以自动转换的)无return可以只写return;不能带返回值
- 一个方法可以有0个参数或者多个
- 参数类型可以为任何类型,调用时传入的参数必须一致或兼容
- 个数顺序都需一致,方法定义时的参数是形参,调用时的参数是实参
- 同一个类的方法可以直接调用,跨类调用 对象名.方法名
- 跨类的方法调用和方法的修饰符有关
- 在Java中,方法(包括普通方法和静态方法)不能单独写在类(class)的外面
**Java 方法传参机制**
基本数据类型传参
对于基本数据类型,传递的是值(值拷贝),形参的任何改变不会影响实参
public class Object01{
public static void main(String[] args){
int a = 10;
int b = 20;
AA obj = new AA();
obj.swap(a,b);//运行swap方法后,原来的ab值并不会发生了交换,
//是因为运用到方法时会单独开一个栈,进行运算,无法影响到主栈
System.out.println("a=" + a + "b=" + b);//10,20
}
}
class AA{
public void swap(int a,int b){
System.out.println("交换前:" + "a=" + a + "b=" + b);//10,20
int temp = a;
a = b;
b = temp;
System.out.println("交换后:" + "a=" + a + "b=" + b);//20,10
}
}
引用数据类型传参
引用数据类型传递的是地址(传递也是值,但值是地址),可以通过形参影响实参(包括对象和数组!)
public class Object01{
public static void main(String[] args){
AA obj = new AA();
int[] arr = {1,2,3};
obj.swap(arr);
System.out.println("main方法的arr");
for(int i= 0 ;i < arr.length;i++){
System.out.print(arr[i] + "\t");// 200 2 3
//数组的值会改变是因为数组是引用数据类型,运行方法开辟新栈的时候传递过去的是数组的地址,
//arr栈和main栈里面的arr数组共用同一个地址相当于有两个变量保存了地址,改变地址所指向的值,也会影响主栈
//把其中一个地址置空不会影响另一个地址(地址是基本数据类型,改变其中一个地址的值不会影响的另一个地址
//故在方法里面改变数组的形参时也会影响到实参
}
System.out.println();
}
}
class AA{
public void swap(int[] arr){
System.out.println("方法的arr");
arr[0] = 200;//修改arr[0]
for(int i= 0 ;i < arr.length;i++){
System.out.print(arr[i] + "\t");//200 2 3
}
System.out.println();
}
}
总结:
- 基本数据类型的传递是值传递,传递的是值的副本。
- 引用数据类型的传递在表面上看起来像是引用传递(因为它传递的是对象的引用),但实际上仍然是值传递(传递的是引用值的副本)。这个副本和原始引用都指向同一个对象,因此可以通过引用修改对象的状态,但无法通过引用本身改变它指向的对象(即不能改变引用的“地址”)。
克隆对象
public class Object01{
public static void main(String[] args){
Person p1 = new Person();
p1.age = 22;
p1.name = "小罗";
MyTools tools = new MyTools();//创建tools对象
Person p2 =tools.copyPerson(p1);//
System.out.println("p1的名字" + p1.name + "p1的年龄" + p1.age);
System.out.println("p2的名字" + p2.name + "p2的年龄" + p2.age);
//可以通过输出对象的hashCode来看看对象属性是否相同
}
}
class Person{
int age;
String name;
}
class MyTools{
public Person copyPerson(Person p){
Person a = new Person();
a.name = p.name;
a.age = p.age;
return a;
}
}
递归
简介:
Java中的递归是一种强大的编程技术,它允许一个方法直接或间接地调用自身。递归在解决可以分解为相似子问题的问题时特别有用,如遍历树或图结构、计算阶乘、求解斐波那契数列等。
简单递归演示
public class Recursion01{
//编写一个main方法
public static void main(String[] args){
T t1 = new T();
t1.text(4);
int res = t1.factorial(5);
System.out.println("5的阶乘res=" + res);
}
}
class T{
//递归初探
public void text(int n){
if(n > 2){
text(n - 1);
}
System.out.println("n=" + n);
}
//阶乘
public int factorial(int n){
if(n == 1)
return 1;
else
return factorial(n - 1) * n;
}
}
递归重要规则
- 执行一个方法时,就创建一个新的受保护的空间(栈空间)
- 方法中的局部变量是独立的,不会相互影响
- 如果方法中使用的是引用类型变量,就会共享该引用类型的数据
- 递归必须向退出递归的条件逼近,否则就是无限递归,出现死龟(SrackOverfloeError)
- 当一个方法执行完毕或者遇到return,就会返回,遵守谁调用就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕.
递归--斐波那契数列
public class Recursion01{
//编写一个main方法
public static void main(String[] args){
//求斐波那契数列1,1,2,3,5,8,13...
T t1 = new T();
int res = t1.nub(-1);
System.out.println("res=" + res);
}
}
class T{
//递归--斐波那契数列
public int nub(int n){
if(n >= 1){
if(n == 1 || n == 2)
return 1;
else
return (nub(n-1)+nub(n-2));
}else {
System.out.println("请输入一个大于等于一的整数");
return 0;
}
}
}
递归--猴子吃桃
//
public class Recursion02{
//编写一个main方法
public static void main(String[] args){
A a = new A();
int peach = a.eattz(-1);
if(peach != -1)
System.out.println("第一天桃子数为=" + peach);
}
}
//day10 = 1;
//day9 = (day10 + 1)*2
//dayn = (day(n+1) + 1)*2
//如果输入的为n,dayn = eattz(n),day(n+1) = eattz(n+1)
//故两者关系应该表述为 eattz(n) = (eattz(n + 1) + 1)*2
class A
{
public int eattz(int day)
{
//返回第n天有几个桃子
if(day == 10)
return 1;
else if(day >=1 && day <= 9)
return (eattz(day + 1) + 1)*2;
else
return -1;
}
}
递归--迷宫问题
public class MG{
//编写一个main方法
public static void main(String[] args)
{
/*思路分析
1.使用一个二维数组来制定迷宫
2.观察迷宫,指定规则,由于编译器可以默认初始化为0,
我们可以把走不通的路设为1,能走的路设为0,这样可以简化初始化步骤
3.观察可知
*/
int[][] map = new int[8][7];//二维数组表示迷宫
for(int i = 0;i < 7;i++){//将第一行和最后一行置为1
map[0][i] = 1;
map[7][i] = 1;
}
for(int i = 0;i < 8;i++){//将第一列和最后一列置为1
map[i][0] = 1;
map[i][6] = 1;
}
//无规律的置1
map[3][1] = 1;
map[3][2] = 1;
System.out.println("===迷宫===");
for(int i = 0;i < map.length;i++){
for(int j = 0;j < map[i].length;j++){
System.out.print(map[i][j]);
}
System.out.println();
}
T t1 = new T();
t1.findway(map,1,1);
System.out.println("===找路情况如下===");
for(int i = 0;i < map.length;i++){
for(int j = 0;j < map[i].length;j++){
System.out.print(map[i][j]);
}
System.out.println();
}
}
}
class T{
//使用递归回溯的思想来解决老鼠出迷宫
//1.findway方法时专门来找出出迷宫的路径
//2.如果找到返回true,否则返回false
//3.map是二维数组,即表示迷宫
//4.i,j就是老鼠的位置,初始化位置为(1,1)
//5.由于是递归找路,所以规定 map数组的各个值的含义
//0表示可以走 1表示障碍物 2表示可以走 3表示走过但是走不通
//6.当map[6][5] = 2就表示找到通路了,可以结束了,否则就继续找
//7.先确定老鼠找路的策略,下->右->上->左
//改变走路策略也能走到终点
public boolean findway(int[][] map,int i,int j){//接受下一个位置的位置
if(map[6][5] == 2){
return true;
}else{
if(map[i][j] == 0){//表示此处在的位置,为0表示可以进行下一步
map[i][j] = 2;//假定该位置能走向下一个位置
//走路策略 下右上左
if(findway(map,i + 1,j)){
//先假定向下走,如果条件为真则进入下一层递归,
//对下下次路进行判断是否能走,可以再进行下下下次,不能则返回false,向下走的if为false,执行下一个if else重新进行相同判断,
//如果所有路都为false则把该点置为3,表示死路了,否则会一路找到终点,即map[6][5] == 2时,开始一路返回true,直至退出所有递归
return false;
}else if(findway(map,i,j + 1)){//向右
return false;
}else if(findway(map,i - 1,j)){//向上
return false;
}else if(findway(map,i,j - 1)){//向左
return false;
}else {
map[i][j] = 3;
return false;//都走不通,标记死机
}
}else{//为1,2,3走不了返回假
return false;
}
}
}
}
//回溯现象
//当一个位置置为3时,会返回上一个位置
//这是因为处在3这个位置会返回false,让上一次的向下为false,让他执行另一个else if
//不同路径都能找到终点,那如何求出最短路径呢?
//1.穷举2.图
递归问题--汉诺塔
public class Tower{
//编写一个main方法
public static void main(String[] args){
T t = new T();
t.move(5,'A','B','C');
}
}
class T{
public void move(int num,char a,char b,char c){
if(num == 1){
System.out.println(a + "->" + c);
}else{
//如果有多个盘,可以看成两个,最下面和最上面所有盘(num - 1)
//(1)移动上面所有盘到b,借助c
move(num - 1, a , c , b);
//(2)把最下面的这个盘移动到c
System.out.println(a + "->" + c);
//(3)再把b的所有盘移动到c,借助a
move(num - 1, b,c,a);
}
}
}
递归--八皇后问题
方法重载
Java允许在同一个类中有多种同名方法的存在,但要求形参不一样
注意细节:
1.方法名相同,
2.形参类型或个数或顺序至少有一种不同
3.返回类型无要求
可变参数
Java允许将同一个类中多个同名同功能但参数不同的方法,封装成一个方法,就可以通过可变参数实现
1.int...nums表示的是可变参数,类型是int,即可接受多个参数int(0-多)
2.可变参数实参可以为数组(由数组等效表示多个实参)
3.可变参数本质就是数组,即可以.length,遍历求和等
4.可变参数可以和普通参数放在同一个列表,但可变参数必须放在后面
5.一个形参列表中只能出现一个可变参数
public class VarParameter01{
//编写一个main方法
public static void main(String[] args){
T t = new T();
int[] arr = {1,2,3};
System.out.println(t.sum(1,5,7,9));
System.out.println(t.sum(1,5));
System.out.println(t.sum(arr));
}
}
class T{
//可以计算2,3,4,...多个数的和
//1.int...nums表示的是可变参数,类型是int,即可接受多个参数int(0-多)
//2.使用可变参数时可以当做数组来使用,即可以.length,遍历求和等
public int sum(int... nums){
//4.可变参数可以和普通参数放在同一个列表,但可变参数必须放在后面
//(int... nums,double a)x
//(double a,int... nums)对
System.out.println("接受的参数个数是=" + nums.length);
//遍历求和
int res = 0;
for(int i = 0;i < nums.length;i++){
res += nums[i];
}
return res;
}
}
/*
public class VarParameter01{
//编写一个main方法
public static void main(String[] args){
T t = new T();
System.out.println(t.sum("刘宇", 13.2, 11.8));
System.out.println(t.sum("小罗", 12.0, 12.0, 12.0));
System.out.println(t.sum("好友位", 2.0, 8.0, 8.0, 8.0, 5.0, 4.0));
}
}
class T{
public String sum(String name,double... nums){
double res = 0;
for(int i = 0; i < nums.length;i++){
res += nums[i];
}
return name + nums.length + "们课的成绩总分为=" + res;
}
}*/
变量作用域
1.全局变量:也就是属性,作用域为整个类体;可以被本类使用或者其他类使用,通过对象调用
2.局部变量:除了属性之外的局部变量,只能在它定义的地方使用.
3.全局变量可以不赋值直接使用,因为有默认值,但局部变量不能,因为局部变量没有默认值
4.属性和局部变量可以重名,访问时遵循就近原则
5.同一个作用域定义局部变量不能重名
6.属性生命周期长,伴随着对象的创建而创建,伴随对象的销毁而销毁.局部变量生命周期短,伴随代码块的执行而创建,伴随代码块的销毁而销毁.
7.属性前面可以加修饰符但局部变量不行
构造方法/构造器(即构造函数)
构造器细节即注意事项
1.构造器的修饰符可以默认
2.构造器没有返回值
3.方法名和类名一样
4.参数列表和成员方法一样的规则
5.构造器的调用由计算机本身调用,完成对对象的初始化不是创建对象
修饰符 类名(形参){...}
6.构造器可重载,没定义会有默认的构造器,若定义了构造器则默认的会被覆盖
public class VarScope{
//编写一个main方法
public static void main(String[] args){
Person p = new Person();
System.out.println("p的信息 name="+ p.name + "age=" + p.age);
Person p1 = new Person("小罗",19);
System.out.println("p1的信息 name="+ p1.name + "age=" + p1.age);
}
}
class Person{
int age;
String name;
public Person(){
age = 18;
}
public Person(String pname,int Age){
name = pname;
age = Age;
}
}
对象创建的流程分析
真正的对象在堆里,栈里的对象实际上是一个地址指向堆里(就像名字和人一样)
注意:构造器初始化会覆盖显式初始化
this关键字
什么是this
java虚拟机会给每个对象分配this,代表当前对象
this的理解
this使用细节和注意事项
1.哪个调用this,this就指向谁
2.this可以用来访问本类的属性,方法,构造器
3.访问方法:this.方法名(参数列表)
4.访问构造器:this(参数列表);注意只能在构造器调用另一个构造器,如果要在构造器使用this,必须将this置于第一条语句
5.this不能再类定义的外部使用,只能在类定义的方法中使用
public class This01{
//编写一个main方法
public static void main(String[] args){
//T t1 = new T("小罗",19);
T t1 = new T();
//t1.f2();
t1.f1();
}
}
class T{
String name = "刘宇";
int age = 18;
public T(){
//通过this访问另一个构造器
this("yingran",80);
System.out.println("T()构造器");
}
public T(String name,int age){
/*this.表示当前对象的属性
//name = name;
this.name = name;
//age = age;
this.age = age;*/
System.out.println("public T(String name,int age)构造器被调用");
}
//由于构造器的就近原则,默认为形参
public void f1(){
String name = "有味";
int age = 9;
System.out.println("name= " + name + "age=" + age);//就近原则
System.out.println("name= " + this.name + "age=" + this.age);//准确访问属性
//name= 有味age=9
//name= 刘宇age=18
}
/*
public void f2(){
System.out.println("f2方法被调用");
f1();
this.f1();
}*/
}