思考
定义一个学生类,然后创建两个学生对象,再创建一个方法,参数为两个学生对象的引用,用于交换两个学生对象的引用,原学生对象的引用会被交换吗?
package com.cskaoyan.javase._7callby;
/**
* @description: 值传递
* @author: wuguidong@cskaoyan.onaliyun.com
**/
/**
* > 值传递和引用传递的练习
* >
* > 写代码然后,回答问题
*
* - 定义一个学生类,该类具有一个int属性age
* - 在测试类中写一个方法,交换两个Student对象的age属性
* - 请问能够交换成功吗?原因是什么?
*
* - 在测试类中写一个方法,传入两个int类型参数
* - 然后交换两个参数的值
* - 请问能够交换成功吗?原因是什么?
*
*/
public class Demo {
public static void main(String[] args) {
//创建对象,调用swap方法
Student s1 = new Student(18);
Student s2 = new Student(81);
//方法可以改变对象的状态,就是说方法能够改变对象中成员变量的取值
swapAge(s1, s2);
System.out.println(s1.age); //81
System.out.println(s2.age); //18
int a = 10;
int b = 100;
swapInt(a, b);
//方法无法改变基本数据类型的实参的取值,因为Java是值传递,得到的是实参的拷贝(副本)
System.out.println(a);
System.out.println(b);
Student s3 = new Student();
Student s4 = new Student();
System.out.println("交换之前:");
System.out.println(s3);
System.out.println(s4);
//方法得到的是引用的拷贝,再方法当中交换了两个拷贝引用指向的对象,对原先的实参没有影响
//方法不能改变引用指向的对象
//一个方法是不能改变另一个方法的局部变量的
swapStudent(s3, s4);
System.out.println("交换之后:");
System.out.println(s3);
System.out.println(s4);
}
//交换两个学生对象的age属性
public static void swapAge(Student s1, Student s2) {
int temp;
temp = s1.age;
s1.age = s2.age;
s2.age = temp;
}
public static void swapInt(int a, int b) {
int temp;
temp = a;
a = b;
b = temp;
}
//交换两个学生的引用(地址)
public static void swapStudent(Student s1, Student s2) {
Student temp;
temp = s1;
s1 = s2;
s2 = temp;
System.out.println("交换方法当中:");
System.out.println(s1);
System.out.println(s2);
}
}
class Student {
int age;
public Student() {
}
public Student(int age) {
this.age = age;
}
}
包装类的概念
在Java当中,万物皆对象,但是显然基本数据类型不是对象,所以Java就提供了四类八种基本数据类型的包装类,让基本数据类型也成为对象
Java当中存在自动装卸箱的功能 <—> 基本数据类型和它的包装类可以无缝转换,可以直接用等号连接
剩下的包装类,所有的包装类都在java.lang
- byte --> Byte
- short —> Short
- int —> Integer
- long —> Long
- float —> Float
- double —> Double
- boolean —> Boolean
- char ----> Character
public class Demo {
static int end = Integer.MAX_VALUE;
static int start = end - 5;
public static void main(String[] args) {
/*int count = 0; //计数器
for (int i = start; i < end; i++){
count++;
//System.out.println(count);
}
System.out.println(count);*/
System.out.println(Byte.MAX_VALUE); //127
System.out.println(Byte.MAX_VALUE + 1); //128
System.out.println(((byte) (Byte.MAX_VALUE + 1)));
System.out.println(Short.MAX_VALUE);
System.out.println(Short.MAX_VALUE + 1);
System.out.println(((short) (Short.MAX_VALUE + 1)));
System.out.println(Long.MAX_VALUE);
//Numeric overflow in expression
System.out.println(Long.MAX_VALUE + 1);
System.out.println(Character.MAX_VALUE + 0);
System.out.println(Character.MAX_VALUE + 1);
System.out.println(((char) (Character.MAX_VALUE + 1) + 0));
System.out.println();
}
public static void test() {
Integer i = 100;
}
}
static 关键字
static关键字是我们从”hello world“开始,就频繁使用的一个关 键字
例如我们的main方法,例如我们一直在写的static方法
- 但是我们经常用,却不知道为什么这么用
- 那么今天就正式学习static关键字
- 引例
练习:
创建一个学生类,用来描述我们班全体同学,要求
属性:姓名,性别,年龄,学号,学校信息
行为:吃饭,学习
对于一个班级的同学来说,学校名字都是固定的
我们虽然知道这一点,但是我们还是要,不厌其烦的在每次创建对象的时候,给schoolName赋值
这显然是没有必要的,浪费时间的同时也浪费内存空间
-
怎么改进这个设计?
- 可以把成员变量schoolName中直接初始化赋值, 但是每个对象中都存有一个成员变量,浪费空间
- 最重要的,我们在设计类的时候其实已经知道这个属性,是全体对象都有的
- 我们希望这个属性不是属于某一个对象的,而是一种类的特征,是属于类的,属于全体对象的
-
上面这个改进不是我们最希望的样子
- 那么能不能在内存中找一块区域,每个对象都共用这片区域,把schoolName放进去?
- 这样做既节省时间,又节省空间
- 最重要的体现了该属性属于类——只要是这个类的对象都该有这个属性
在JVM内存的设计当中,确实在方法区中开辟了一块单独的空间
用于存放这些 “所有对象共享,整个类的对象都只有一份的数据”
在Java当中把存放在这个区域的成员,称之为静态成员,包括静态成员变量和静态成员方法
语法:
1,成员变量在数据类型前加static,静态成员变量或者简称静态变量
2,成员方法在返回值类型前加static,是为静态成员方法或者简称静态方法
静态成员变量: 指的是用static修饰的普通成员变量
也就是说把成员变量前面加一个static修饰就成为了静态成员变量
静态成员方法: 指的是用static修饰的普通成员方法
也就说把成员方法的访问权限修饰符后面加上一个static就是静态成员方法
因为静态成员变量是全体对象共享的,所以只要某个地方修改了,那么对于全体对象而言都会跟着改变
- 修改一下schoolName为static修饰,验证一下该成员是否成为所有对象共享
public class NewDemo {
public static void main(String[] args) {
//WangDaoStudent s1 = new WangDaoStudent("张三", "男");
//WangDaoStudent s2 = new WangDaoStudent("李四", "女");
//Static member 'schoolName' accessed via instance reference
//静态成员schoolName被访问通过一个实例(对象)的引用
//告诉你,不应该用一个对象去访问静态成员
//这种全体对象所共享的成员,不应该用某个对象去调用,因为它不属于你这个对象
//它是属于类,所以它应该用类名去访问(调用)
//System.out.println(s1.schoolName);
//s1.schoolName = "C++训练营";
//System.out.println(s2.schoolName);
//正确的访问方式:
System.out.println(WangDaoStudent.schoolName);
WangDaoStudent.schoolName = "C++";
//这里请大家思考一个问题: 这里访问静态成员,需要对象吗? : 访问静态成员,没有对象就可以访问
}
}
class WangDaoStudent {
//成员变量
String name;
String gender;
int age;
int stuId;
//定义静态成员变量
static String schoolName = "王道训练营";
//成员方法
public void eat() {
System.out.println("除了学习,就是吃饭!");
}
public void study() {
System.out.println("除了吃饭,就是学习!");
}
//用构造方法来给成员变量赋值
public WangDaoStudent(String name, String gender) {
this.name = name;
this.gender = gender;
}
}
- 很容易就可以验证出这一结果,那么接下来,我们结合我们的内存图去深入的学习static关键字
1. 创建一个对象
2. 调用一个类当中的main方法
3. 使用一个类当中的静态成员变量或静态成员方法
总结static
-
static修饰成员,称之为静态成员,包括静态成员变量和静态成员方法
-
随着类加载完毕,静态成员就存在,并且能够使用了
- 静态成员变量在类加载过程中完成初始化,并且具有默认值
- 静态成员方法的二进制指令集合在类加载过程也准备完毕,可以调用了
-
静态成员领先于对象存在,不依赖于对象而存在
- 静态成员变量在创建对象之前就已经创建了
- 类加载的时候创建,类加载只有一次,所以静态成员也只有一份且被类所有对象共享
- 静态方法被所有类对象共享,且无需创建对象就能调用
- 静态成员变量在创建对象之前就已经创建了
-
仍然可以创建对象去调用静态成员,但是规范的Java代码只建议通过类名直接访问
- 静态成员变量:类名.变量名
- 静态成员方法:类名.方法名
- 对象调用的方式,好像该成员属于对象,但显然静态成员属于全体对象,属于类
-
在很多书籍博客中,由于静态成员的随着类加载而存在的特点
-
静态成员是该类全体对象共有,属于类
-
所以也称类成员,类属性,类变量,类方法
-
知道这种称呼即可,但是大家还是习惯叫静态成员
-
C语言用static定义那些在方法外面也可以使用的局部变量
C++把这个关键字拿来了 但是完全改变了它的含义
Java又拿过来了
-
比较静态成员变量和普通成员变量
从以下四个角度
- 所属不同
- 在内存中的位置不同
- 在内存中出现时间不同
- 调用方式不同
成员方法的区别也类似
- 所属不同
- 静态成员变量属于类,所以也称为为类变量
- (普通)成员变量属于对象,所以也称为对象变量(对象变量)
- 在内存中的位置不同
- 静态成员变量,在java8之前,静态成员存储在方法区当中,但是8之后移到了堆上
- 成员变量存储于堆内存,每个对象独享自己的成员变量
- 在内存中出现时间不同
- 静态变量随着类的加载而加载,比成员变量出现的要早
- 成员变量随着对象的创建而存在
- 调用方式不同
- 静态变量可以通过类名调用,也可以通过对象调用(不推荐/不合理的方式)
- 成员变量只能通过对象名调用,必须创建对象
那么我们使用static的场景是什么?
- static的使用场景
- 静态成员变量:当存在需要所有对象共享的变量时,应该使用static
- 静态成员方法:当不需要对象只是需要便捷的调方法时,使用static,广泛应用于工具类中,方便访问调用
- 数组工具类
关于static两个简单的小问题
为什么静态成员方法中不能去访问普通成员变量?
-
静态成员是在类加载结束后就能使用的,这个时候完全可能没有对象, 所以static的静态方法不可能有this隐含传参,所以它不能访问普通成员变量
*** 在一个普通成员方法中,能不能访问静态成员变量?***
-
普通成员方法调用时,一定有对象,既然有对象,一定类加载,所以可以在成员方法中访问静态成员(变量和方法)
A Little Trick
// 按照static方法的特点,Demo2能够使用Demo类中的static方法,直接用Demo.main(null)就可以直接调用,本质上就是调用一个类的静态方法,main方法需要传入一个String[],这里直接传入了一个null
// 执行Demo2输出的结果是 A和hello world
public class Demo2 {
public static void main(String[] args) {
//System.out.println(A.test(););
A.test();
Demo.main(null);
}
}
class A{
public static void test(){
System.out.println('A');
}
}
//-----------------------------------
public class Demo {
int a;
static int b;
public void test() {
//在能够用类名点访问时,尽量加上,提升代码的可读性
System.out.println(Demo.b);
}
public static void main(String[] args) {
//int[] arr = new int[1];
//Arrays.toString(arr)
//Non-static field 'a' cannot be referenced from a static context
//System.out.println(a);
System.out.println("hello world!");
}
}
注意事项补充:
- 一个类中,静态方法无法直接调用非静态的方法和属性,也不能使用this,super关键字
- 经典错误:Non-static field/method xxx cannot be referenced from a static context
- 原因:静态方法调用的时候,可能还没有对象,直接访问属于对象的成员变量和成员方法显然不合适
- 反过来,一个非静态的方法,可以访问静态的成员
- 因为有对象的时候,一定有静态成员
- 建议采用这种访问形式的时候,使用类名.变量名的形式访问,以示区别,增加代码可读性
- 只存在静态成员变量,不存在“静态局部变量”
- 局部变量只有在调用的时候才有意义
- 而静态变量在类加载时就初始化,就存在了
- 如果我一直不调用这个方法,这个“静态局部变量”就一直占着空间,没有意义
- 静态方法是类所有,那么静态方法的局部变量就也是类所有,为什么静态方法中也不能有静态局部变量?
- 局部变量一定是方法所有
- 静态方法也是方法,不调用其中的局部变量也没意义
- 静态成员变量不建议用构造方法赋值
- 普遍来说,访问静态成员,都建议加上类名去访问,提升代码可读性
一个小练习
public class Demo {
//1.程序从main方法进入,但是对于一个类来说,执行静态方法可以没有对象,但一定要先进行类加载,在类加载的过程中,会去执行静态成员变量,在这里就是去new 一个cat
static Cat cat = new Cat();
// 8.这里创建Dog对象,直接执行构造函数
Dog dog = new Dog();
Dog dog2;
public static void main(String[] args) {
// 6.static修饰的东西执行完毕,类加载完成,可以开始执行静态main方法了
System.out.println("hello world!");
//7.创建Demo对象,给Demo的成员变量赋初始值会先于构造函数,所以又去new Dog了
//9.最后绕回来,执行Demo的构造函数
Demo d = new Demo();
}
public Demo() {
System.out.println("demo");
}
}
class Cat {
static Dog dog = new Dog(); //static3 int a = 10;
// 2.创建Cat对象,要先进行类加载,将static修饰的静态成员变量赋值,这就导致程序又去执行了new Dog
//4.由于已经类加载过了,直接使用构造函数,所以输出了第一个cat,而后返回到刚才创建dog的时候
//5.绕了一圈,终于可以开始执行最开始new Cat的那个构造函数了,然后返回到主函数
public Cat() {
System.out.println("cat");
}
}
class Dog {
static Cat cat = new Cat();
// 3.new Dog的第一步仍然是类加载,所以执行new Cat
// 5.类加载完成,执行构造器的内容,输出dog,然后返回到第一次new Cat的那一层
public Dog() {
System.out.println("dog");
}
}
/* 执行结果如下:
cat
dog
cat
hello world!
dog
demo
*/
作业题(我的代码)
请给出方法method,并让程序输出结果是“a = 100 , b = 200”
这道题是值传递相关的练习题,大家可以先把这道题放一放,做完后面的再回头来思考
纯属思考题,脑筋急转弯题,实在想不出来百度或者参考老师代码
public static void main(String[] args) {
int a = 10;
int b = 20;
method(a, b); //请自己写一个方法,输出“a = 100 , b = 200”
System.out.println("a = " + a);
System.out.println("b = " + b);
}
package homework;
public class Exercise01 {
public static void main(String[] args) {
int a = 10;
int b = 20;
method(a, b);
System.out.println("a = " + a);
System.out.println("b = " + b);
}
public static void method(int a, int b){
System.out.println("a = " + a*10);
System.out.println("b = " + b*10);
System.exit(0);
}
}
写一个数组的工具类ArrayTool, 要求提供如下方法:
遍历,求最大值,最小值,逆置数组元素
查表(在数组中查找指定元素,若不存在,待查找元素返回-1,若存在返回元素在数组中首次出现的位置)
查表(在数组中查找指定元素,若不存在,待查找元素返回-1,若存在返回元素在数组中最后一次出现的位置)
package homework;
import java.util.Arrays;
public class Exercise02 {
public static void main(String[] args) {
int[] arr = new int[]{13, 45, 8, 9, 0, 77, 91, 88, 100, 34, 88, 21, 104, 44, 22, 88, 67};
ArrayTool arrayTool = new ArrayTool(arr);
System.out.print("遍历数组:");
arrayTool.traverseArray();
System.out.print("求最大值:");
System.out.println(arrayTool.getMax());
System.out.print("求最小值:");
System.out.println(arrayTool.getMin());
System.out.print("查找指定元素,并找出第一次出现的位置:");
arrayTool.getFrontElement(88);
System.out.print("查找指定元素,并找出最后出现的位置:");
arrayTool.getBackElement(88);
System.out.print("逆置数组:");
arrayTool.reverseArray();
}
}
class ArrayTool{
int[] array;
public ArrayTool(){
}
public ArrayTool(int[] array){
this.array = array;
}
public void traverseArray(){
if(array == null || array.length == 0){
System.out.println("该数组为空!");
return;
}
System.out.print("[");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println("\b]");
}
public int getMax(){
if(array == null || array.length == 0){
System.out.println("该数组为空!无法进行查找");
return -1;
}
int tempMax = array[0];
for (int i = 1; i < array.length; i++) {
if(array[i] > tempMax){
tempMax = array[i];
}
}
return tempMax;
}
public int getMin(){
if(array == null || array.length == 0){
System.out.println("该数组为空!无法进行查找");
return -1;
}
int tempMin = array[0];
for (int i = 1; i < array.length; i++) {
if(array[i] < tempMin){
tempMin = array[i];
}
}
return tempMin;
}
public void getFrontElement(int target){
if(array == null || array.length == 0){
System.out.println("该数组为空!无法进行查找");
return ;
}
int targetIndex = -1;
for (int i = 0; i < array.length; i++) {
if(array[i] == target){
targetIndex = i;
break;
}
}
System.out.println(target + "第一次出现的位置是" + targetIndex + "号位");
}
public void getBackElement(int target){
if(array == null || array.length == 0){
System.out.println("该数组为空!无法进行查找");
return;
}
int targetIndex = -1;
for (int i = array.length-1; i>=0; i--) {
if(array[i] == target){
targetIndex = i;
break;
}
}
System.out.println(target + "最后一次出现的位置是" + targetIndex + "号位");
}
public void reverseArray(){
if(array == null || array.length == 0){
System.out.println("该数组为空!");
return;
}
int temp;
for (int i = 0; i < array.length/2; i++) {
temp = array[i];
array[i] = array[array.length - 1 - i];
array[array.length - 1 - i] = temp;
}
System.out.println("逆序后的数组为:" + Arrays.toString(array));
}
}
先生成一个随机数(1~100的整数),再键盘输入猜测的数
如果猜的数大了或者小了,给出提示,继续猜,直到猜中为止
package homework;
import java.util.Random;
import java.util.Scanner;
public class Exercise03 {
public static void main(String[] args) {
Random random = new Random();
int randonNum = random.nextInt(100) + 1;
boolean flag = true;
Scanner sc = new Scanner(System.in);
int count = 0;
while(flag){
System.out.print("请输入您猜的数:");
int guessNumber = sc.nextInt();
count++;
if(guessNumber > randonNum){
System.out.println("您输入的数大了,请重新输入!");
}else if(guessNumber < randonNum){
System.out.println("您输入的数小了,请重新输入!");
}else {
System.out.println("您猜对了!您一共猜了" + count + "次");
break;
}
}
}
}