1、类与对象
1.1 基本介绍
- 类是抽象的,概念的,代表一类事物,比如人类,猫类.., 即它是数据类型
- 对象是具体的,实际的,代表一个具体事物, 即 是实例
- 类是对象的模板,对象是类的一个个体,对应一个实例
对象创建在内存中是怎么存在的:
在堆中开辟对象的内存空间,它的常量存储在方法区中,栈中存储存储这个对象的引用地址,这个变量名称指向堆中对象的内存地址。
对象的属性如果不赋值是有默认值的。
1.2 内存介绍
这个部分以后会在JVM中好好介绍的:
- 栈:一般是存放基本数据类型的,比如局部变量等
- 堆:存放对象的
- 方法区:存放经常使用的东西,常量池(字符串常量池和int常量池等),类加载信息
2、成员方法
2.1 基本介绍
成员方法:就是对象操作的方法叫成员方法,引用就相当于是一个遥控器,遥控器遥控电视机做什么就相当于是成员方法(使用的一些功能)。
语法:
访问修饰符 [static] 返回值类型 方法名(形式参数列表){
方法体;
return xxx;//不一定有
}
复制代码
-
访问修饰符 (作用是控制 方法使用的范围):
- 如果不写默认访问, [有四种: public, protected, 默认, private]
-
返回值类型:
-
一个方法最多有一个返回值
-
返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
-
如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值; 而且要求返回值类型必须和 return 的 值类型一致或兼容
-
如果方法是 void,则方法体中可以没有 return 语句,或者 只写 return ;
-
-
形参列表:
- 可以有零到多个,最后传入的参数必须是同类型或兼容的类型
实例:
public class Method01 {
public static void main(String[] args) {
Person person = new Person();
person.learn();
}
}
class Person{
String name;
int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void learn(){
System.out.println("学习");
}
}
复制代码
2.2 方法的传参机制(重要)
- 如果是基本数据类型,是值拷贝
public class BasicTypeParameterDeliver{
public static void main(String[] args) {
int a = 10,b = 20;
Method method = new Method();
method.swap(a,b);
System.out.println("main方法的参数a = " + a + ", b = " + b); //10,20
}
}
class Method{
public void swap(int a,int b){
System.out.println("\na和b交换前的值\na=" + a + ",\tb=" + b); //10,20
int temp = a;
a = b;
b = temp;
System.out.println("\na和b交换后的值\na=" + a + ",\tb=" + b); //20,10
}
}
复制代码
- 如果是引用数据类型,是引用拷贝
public class BasicTypeParameterDeliver2 {
public static void main(String[] args) {
//测试
B b = new B();
// int[] arr = {1, 2, 3};
// b.test100(arr);//调用方法
// System.out.println(" main 的 arr 数组 ");
// //遍历数组
// for(int i = 0; i < arr.length; i++) {
// System.out.print(arr[i] + "\t");
// }
// System.out.println();
//测试
Person1 p = new Person1();
p.name = "jack";
p.age = 10;
b.test200(p);
//测试题, 如果 test200 执行的是 p = null ,下面的结果是 10
//测试题, 如果 test200 执行的是 p = new Person();..., 下面输出的是 10
System.out.println("main 的 p.age=" + p.age);//10000
}
}
class Person1 {
String name;
int age;
}
class B {
public void test200(Person1 p) {
p = new Person1();
p.name = "tom";
p.age = 99;
}
public void test100(int[] arr) {
arr[0] = 200;//修改元素
//遍历数组
System.out.println(" test100 的 arr 数组 ");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
System.out.println();
}
}
复制代码
2.3 递归(非常重要)
递归就是一个方法不断的调用自己,如果没有设置停止条件可能会造成栈内存溢出(StackOverflowError)。不断递归就是不断调用方法的过程,每次递归都会在栈中开辟一块空间,而下面的空间是不会释放的。
斐波那契数列问题
这样一个数列:1、1、2、3、5、8、13、21、34..., 也就是F(0)=1,F(1)=1, F(n)=F(n - 1)+F(n - 2)这样一个数列。
public class Fibonacci {
public static void main(String[] args) {
System.out.println(fibonacci(4));
}
private static int fibonacci(int num){
if (num >= 1){
if (num == 1 || num == 2){
return 1;
}else {
return fibonacci(num-2) + fibonacci(num-1);
}
}else {
System.out.println("请选择大于等于1的整数");
return -1;
}
}
}
复制代码
迷宫问题
更加难一点的是迷宫问题,需要考虑很多种情况,这个题是,找到出口。
思路就是使用递归,选择一种行走方式,如果碰到墙了就换个方向,如果没碰到墙就递归调用。
public class MiGong {
public static void main(String[] args) {
int[][] map = new int[8][7];
//把四边设置为墙,墙用1表示
for (int i = 0; i < 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
//把中间部分的墙设置好
map[3][1] = 1;
map[3][2] = 1;
map[4][3] = 1;
System.out.println("初始状态");
showMap(map);
setWay(map,1,1);
//查看地图
System.out.println("最终状态");
showMap(map);
}
/**
* 解开地图路线的方法
* 数字表示的含义:
* 0表示没走过
* 1表示墙
* 2表示可以走
* 3表示走不通
* @param map 需要解开的地图
* @param i 初始位置行索引
* @param j 初始位置列索引
*/
public static boolean setWay(int[][] map,int i,int j){
/*
规定一下路线选择的优先级
1.先向下走
2.向下走不了向右走
3.向又走不了向上走
4.向上走不了向左走
*/
if (map[i][j] == 2){
return true;
}else {
if (map[i][j] == 0){
map[i][j] = 2;
if (setWay(map,i+1,j)){
return true;
}else if (setWay(map,i,j+1)){
return true;
}else if (setWay(map,i-1,j)){
return true;
}else if (setWay(map,i,j-1)){
return true;
}else{
map[i][j] = 3;
return false;
}
}
return false;
}
}
/**
* 显示map全貌
* @param map
*/
public static void showMap(int[][] map){
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();
}
}
}
复制代码
结果如下:
初始状态
1 1 1 1 1 1 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 0 0 0 1
1 0 0 1 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1
最终状态
1 1 1 1 1 1 1
1 2 0 0 0 0 1
1 2 2 2 0 0 1
1 1 1 2 2 0 1
1 0 0 1 2 0 1
1 0 0 0 2 2 1
1 0 0 0 2 2 1
1 1 1 1 1 1 1
Process finished with exit code 0
复制代码
汉诺塔问题
几个圆圈开始时在一遍,要移动到另一边,但下面的盘子要比上面大。
这个问题的解决方法是,一共三个位置,初始位置A、目标位置C、中转位置B,如果一共5个圆盘在A处,中间要完成的一定是4个圆盘在B处才能把最大的从A移到C,如果要让圆盘到B处,就一定是A是初始位置,C是中转位置,而B是目标位置,也就是要让3个圆盘在A处才行......以此类推即可得到问题的答案。
代码如下:
public class HanoiTower {
public static void main(String[] args) {
Tower.hanoi(3,'a','b','c');
}
}
class Tower{
/**
* 移动汉诺塔的方法
* @param nums 一共几个圆盘
* @param a 位置1
* @param b 位置2
* @param c 位置3
*/
public static void hanoi(int nums,char a,char b,char c){
if (nums == 1){
System.out.println(a + "-->" + c);
}else {
hanoi(nums-1,a,c,b);
System.out.println(a + "-->" + c);
hanoi(nums-1,b,a,c);
}
}
}
复制代码
八皇后问题
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法(92)。
public class Queens {
int max = 8; //一共8个皇后,定义初始值
int[] array = new int[max];
static int count = 0;
public static void main(String[] args) {
Queens queens = new Queens();
queens.put(0);
System.out.println(count);
}
//结果输出方法
private void print() {
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]);
}
System.out.println();
}
//检查放到第n个皇后时,是否与之前的皇后冲突
private boolean check(int n) {
for (int i = 0; i < n; i++) {
if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[i] - array[n])) {
return false;
}
}
return true;
}
//放置第n个皇后
private void put(int n) {
if (n == max) { //因为下标是从0开始的,所以当n=8时代表再放就是第九个皇后了,就是已经放完了8个。直接打印结果即可。
count++;
print();
return;
}
for (int i = 0; i < max; i++) {
array[n] = i;//先把这个皇后放到此行的第一个位置
if (check(n)) {//检查放第n个皇后时是否冲突,不冲突的话就继续放置第n+1个。
put(n + 1);
}
}//如果冲突了就继续执行 array[n] = i这就相当于这一行的这个位置是不行的,我们要试试下个位置了
}
}
复制代码
2.4 方法重载
可以有同名的方法,但形参列表可以不一致。
public class MethodOverload {
public int accumulate(int num1,int num2){
return num1 + num2;
}
public String accumulate(String num1,String num2){
return num1 + num2;
}
public float accumulate(float n1,float n2){
return n1 + n2;
}
public double accumulate(double n1,double n2){
return n1 + n2;
}
}
复制代码
总结一下:
- 方法名要相同
- 形参列表:必须不同(类型,个数,顺序至少有一个不同)
- 返回值类型:不要求
为什么不通过返回值类型去判断呢?
重载一般是编译阶段就已经确定好用哪个了,可是如果重载也可以由返回值决定,在程序都没运行完,怎么知道返回值是什么类型的呢?所以不成立,甚至有的方法都没有返回值,程序员本身某一行代码也不需拿到返回值,所以他就写了一行:f()
。
所以谁知道这一行代码要调用的是有返回值的方法呢?还是void的方法呢?所以不能用返回值判断重载。
2.5 可变参数
比如有些时候,我们并不知道传入的参数到底有多少,可以采用下面这种方式:
访问修饰符 返回类型 方法名(数据类型... 形参名) {
//方法体;
}
复制代码
实例:
public class VarParameter {
public static void main(String[] args) {
System.out.println(sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
}
public static int sum(int... num){
int result = 0;
for (int i = 0; i < num.length; i++) {
result+= num[i];
}
return result;
}
}
复制代码
可变参数可以是0-任意个,可变参数的实参可以是数组,而且本质也是个数组。
2.6 作用域
public class Variables {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog);
dog.introduceIfself();
}
}
class Dog{
//全局变量可以设置值
String name = "大黄";
//也可以不设置
int age;
public Dog() {
}
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public void introduceIfself(){
String name = "一条土狗阿黄";
int age = 4;
System.out.println(name + age);
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Dog{");
sb.append("name='").append(name).append('\'');
sb.append(", age=").append(age);
sb.append('}');
return sb.toString();
}
}
复制代码
细节:
- 局部变量必须提前初始化好才能被使用,成员变量可以不赋值,直接使用
- 全局变量和局部变量重名时,遵循就近原则
- 全局变量是对象销毁时销毁,而局部变量是代码块或方法结束是销毁
- 全局变量可加修饰符,局部变量不能加修饰符
2.7 构造方法
用于创建对象的,基本语法:
[修饰符] 方法名(形参列表){
方法体;
}
复制代码
需要注意:
- 构造器也可以有修饰符,比如public、protected、private、默认
- 构造器没有返回值
- 构造器的方法要和类名一样
- 在创建对象的时候,系统会自动调用相应构造器完成初始化
- 一个类可以设置多个不同的构造器,形成重载
- 如果没有定义构造器,系统会给一个默认构造器;而如果定义了构造器,系统就不再给默认构造器了
像下面这样写就是报错的:
public class ConstructorDetails {
public static void main(String[] args) {
Worker worker = new Worker();
}
}
class Worker{
String name;
int age;
public Worker(String name, int age) {
this.name = name;
this.age = age;
}
}
复制代码
因为没有了默认构造器。
2.8 this关键字
通过构造器这一张就可以看看this关键字的作用,先看这串代码:
class A{
int id;
String username;
String password;
public A(int Aid, String Ausername, String Apassword) {
id = Aid;
username = Ausername;
password = Apassword;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("A{");
sb.append("id=").append(id);
sb.append(", username='").append(username).append('\'');
sb.append(", password='").append(password).append('\'');
sb.append('}');
return sb.toString();
}
}
复制代码
这里的构造器要给id、username、password设置属性值,但是为了避免成员变量名和形参名重复,就把名字改了,其实完全不用这样,直接使用this即可:
public A(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
复制代码
这里的this表示当前的对象,this.id就表示这个对象的id属性 = id值,这样就很清楚了。
细节:
- this可以访问本类的属性、方法、构造器,可以这么理解this.方法名(); 也就是谁调的这个方法,this就表示哪个对象
- this的另一个作用就是刚才区分类的属性名和局部变量的
- 可以使用this(参数列表)来访问构造器,这个多用于构造器定义,必须放在第一条语句
public class ThisDetails01 {
public static void main(String[] args) {
Person2 person = new Person2("张三",10);
}
}
class Person2{
String name;
int age;
public Person2() {
}
public Person2(String name, int age) {
//这行才是精髓
this();
this.name = name;
this.age = age;
}
}
复制代码