1. 面向对象
内存图
java的内存机制
Java 把内存划分成两种:一种是栈内存,另一种是堆内存。在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java 会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。
堆内存用来存放由 new 创建的对象和数组,在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或者对象之后,还可以在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或者对象,引用变量就相当于是为数组或者对象起的一个名称。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。
这也是 Java 比较占内存的原因,实际上,栈中的变量指向堆内存中的变量,这就是 Java 中的指针!
案例1:
public class Person {
public String name;
public int age;
public void change(int i,int j){
i=100;
j=200;
}
}
public class TestPerson {
public static void main(String[] args) {
Person p = new Person();
int i=888;
int j=999;
p.change(i, j);
System.out.println("i="+i+",j="+j);
}
}
内存图1: 结果 i=888,j=999
案例2:
public class Person {
public String name;
public int age;
public void change(int i,Person p){
i=100;
p.name = "喜羊羊";
p.age = 10;
//System.out.println("**内存地址:"+p.toString());
}
}
public class TestPerson {
public static void main(String[] args) {
Person p = new Person();
//System.out.println("内存地址:"+p.toString());
p.name="灰太狼";
p.age = 88;
int i=888;
p.change(i, p);
System.out.println("i="+i+",p.name="+p.name+",p.age="+p.age);
}
}
内存分析:
结果:i=888,p.name=喜羊羊,p.age=10
案例3:
public class Person {
public String name;
public int age;
public void change(int i,Person p){
i=100;
p = new Person();
p.name = "喜羊羊";
p.age = 10;
//System.out.println("**内存地址:"+p.toString());
}
}
public class TestPerson {
public static void main(String[] args) {
Person p = new Person();
//System.out.println("内存地址:"+p.toString());
p.name="灰太狼";
p.age = 88;
int i=888;
p.change(i, p);
//System.out.println("##内存地址:"+p.toString());
System.out.println("i="+i+",p.name="+p.name+",p.age="+p.age);
}
}
i=888,p.name=灰太狼,p.age=88
案例4:
public class Student {
String name;
int age;
public void study(){
System.out.println(name+"在看书");
}
public void say(String sname){
System.out.println(name+"跟"+sname+"说你好");
}
public static void main(String[] args) {
Student stu = new Student();
stu.name="张三";
stu.age = 10;
stu.study();
stu.say("李四");
}
}
案例5:
public class Student {
String name;
int age;
Computer com; //电脑
public void study(){
System.out.println(name+"在看书");
}
public void say(String sname){
System.out.println(name+"跟"+sname+"说你好");
}
}
public class Computer {
String brand; //品牌
int cpuspeed; //处理速度
}
public class TestStudent {
public static void main(String[] args) {
Student stu = new Student();
stu.name = "张三";
stu.age = 10;
Computer c = new Computer();
c.brand = "联想";
c.cpuspeed = 10000;
stu.com = c;
System.out.println(stu.com.brand);
}
}
案例6:
总结: Java 中只存在值传递,基本数据类型传递的是基本数据类型的值,引用数据类型传递的是地址
一个栈只能指向一个堆,一个堆可以被多个栈引用
注意: 栈 :自动分配连续的内存空间,后进先出 。放置:局部变量
堆: 不连续,放置:new出的对象
方法区:(也是堆) 存放类的代码信息,static变量和方法,常量池(字符串常量)
1.1. 构造方法
构造方法的命名
构造方法使用和类相同的名字,而方法则不同。按照习惯,方法通常用小写字母开始,而构造器通常用大写字母开始。构造器通常是一个名词,因为它和类名相同;而方法通常更接近动词,因为它说明一个操作。
构造方法的作用
为了方便参数的传递,允许在新建对象的时候,同时对这个对象的一些属性进行初始化。
构造方法分为两种:
(1) 无参构造方法
public 类名(){
(2) 有参构造方法
public 类名(参数类型1 参数名称1,参数类型2 参数名称2){}
构造方法如何修饰?
(1)允许没有修饰(default)
(2)访问的修饰: public, protected, private
(3)不能有以下非访问性质的修饰: abstract, final, native, static
例题1:
/**
* 只关心你所关心的属性
* 1个类可以有多个构造方法,调用的时候,参数的类型和顺序和定义构造时一致。
* @author Administrator
*
*/
public class Penguin {
public String name; //名字
public int health;// 健康值
public int love; //亲密度
public String sex; //性别
/**
* 无参构造
*/
public Penguin(){
System.out.println("调用了Penguin()无参构造");
}
/**
* 带4个参数的构造
* @param sex
* @param love
* @param health
* @param name
*/
public Penguin(String sex,int love,int health,String name){
this.sex = sex;
this.love = love;
this.health = health;
this.name = name;
System.out.println("调用了Penguin(sex,love,health,name)带4个参数的构造");
}
/**
* 带4个参数的构造
* @param love
* @param health
* @param name
* @param sex
*/
public Penguin(int love,int health,String name,String sex){
this.sex = sex;
this.love = love;
this.health = health;
this.name = name;
System.out.println("调用了Penguin(sex,love,health,name)带4个参数的构造");
}
/**
* 带3个参数的构造
* @param love
* @param name
* @param sex
*/
public Penguin(int love,String name,String sex){
this.sex = sex;
this.love = love;
this.name = name;
System.out.println("调用了Penguin(sex,love,name)带3个参数的构造");
}
/**
* 带2个参数的构造
* @param love
* @param name
*/
public Penguin(int love,String name){
this.love = love;
this.name = name;
System.out.println("调用了Penguin(love,name)带2个参数的构造");
}
/**
* 显示宠物信息
*/
public void print(){
System.out.println("宠物的自白:\n我叫"+name+",我的健康值是:"+health+",和主人的亲密度为:"+love+",我的性别是:"+sex);
}
}
public class TestPet03 {
public static void main(String[] args) {
//new语法创建对象 ,调用无参构造
Penguin pgn = new Penguin();
//给属性赋值
pgn.name = "美美";
pgn.health = 70;
pgn.love = 30;
pgn.sex = "Q妹";
//调用方法显示信息
pgn.print();
}
}
public class TestPet04 {
public static void main(String[] args) {
//调用带4个参数的构造
Penguin pgn = new Penguin(20, 90, "小强","Q仔");
pgn.print();
System.out.println("-------------------------\n");
//调用带2个参数的构造
Penguin pgn2 = new Penguin(20,"jack");
pgn2.print();
System.out.println("-------------------------\n");
//调用带3个参数的构造
Penguin pgn3 = new Penguin(40,"jack3","Q仔");
//pgn3.health = 91;
pgn3.print();
}
}
注意:
(1)每个类都必须有构造方法,如果你不写任何构造方法,则系统会默认提供一个无参构造方法。你也可以自己写构造方法。
(2)构造方法调用必须通过new语法,构造函数在创建对象的时候自动调用。
(3)如果类中已经定义了有参构造方法,则系统不会默认分配无参方法,如果需要调用无参构造,则必须显式定义。如果显式的调用了带参构造,那么默认执行带参构造,而不会调用无参构造
(4)一个类可以有多个构造函数,构造方法不能被继承 快捷键 无参: alt+ / 带参shift +alt +s 再按字母 o ,按tab-->tab(2次)选中所有属性 ,enter 确认 ,shift+tab 光标定位到ok ,再按enter生成
练习:
(1) 编写一个Computer类,声明三个成员变量,类型分别为String、boolean、int,但不要给成员变量赋值,写一个方法show,输出这三个变量的值。在main方法中调用方法show,观察默认值是什么。
(2) 根据Person这个模板,创建一个小明对象和一个小红对象,编写有参构造方法,在main中实例化该类时,通过构造方法传入参数,而不是通过属性直接赋值。分别调用showName输出“我是小明”和“我是小红”
//提示
Person ming = new Person();
ming.name = "小明";
ming.age = 18;
ming.sex = true;
ming.showName();//我是小明
(3) 编写一个学校类School,属性至少包含名称、教室数量、学生人数、就业率,方法至少包含推荐就业,推荐就业方法每调用一次,将学生人数-1并输出学生人数。根据这个类,实例化一个网博对象,给各种属性赋值,最后使用for循环调用10次就业方法。
(4) 账户登录。编写一个用户类User,属性至少包含用户名name和密码pwd,在构造函数中将用户名和密码初始化为admin和123456。User类的方法包含一个密码判断checkPwd。该方法接收两个参数,分别是用户名和密码,业务逻辑是将接收到的两个参数与类的属性用户名、密码比较,判断用户名和密码是否正确,如果是,返回true。在main中从控制台分别接收输入用户名和密码,创建一个User对象,调用checkPwd方法,传入接收到的用户名和密码。如果返回true输出登录成功,返回false输出登录失败。
(5) 改一改。创建User对象对象时,能够将admin和123456通过构造函数传入。
1.2. Static静态
public static void main
(1)类只是用来存储和被调用的,而对象是需要执行的,执行时就必定需要知道程序的入口,这个入口就是由main所在的位置。
(2)Java的类中没有main方法,因为它不需要执行,想执行需要自己加。
(3)Java所编写的程序是由其他程序来启动执行的(由某程序找到Java程序的入口,打开门后将操作的权限暂时交给Java程序,待到Java程序执行完毕再将权限收回),因此仅仅找到位置是不行的,还得有权限,如果没有权限,就像是你找到了门但是没有钥匙,一样进不了屋子。
(4)static的数据或方法,属于整个类的而不是属于某个对象的,是不会和类的任何对象实例联系到一起。所以子类和父类之间可以存在同名的static方法名,这里不涉及重载。所以不能把任何方法体内的变量声明为static,例如:
fun() {
static int i=0; //非法。
}
static表示全局或者静态的意思, static修饰的变量和方法叫做静态变量和静态方法。
示例1:(无法直接调用方法,需要实例化)
Person.showName();
示例2:
加static后可以使用Person.showName();
总结:
(1) 静态方法可以直接通过类名调用,但静态方法内只能访问静态变量、只能调用静态方法。非静态可以调用静态。
(2) Main方法需要调用其他方法,所以自己必须在运行期间始终保持在内存中,所以使用static修饰。
交叉比较区别:成员变量和静态变量的区别
(1)两个变量的生命周期不同
成员变量随着对象的创建而存在,随着对象被回收而释放。
静态变量随着类的加载而存在,随着类的消失而消失。
注:加载顺序:启动类的static block最先加载
(父类静态成员、静态代码块—>子类静态成员、静态代码块—>父类实例成员、非static代码块——>父类构造函数—>子类实例成员、非static代码块—>子类构造函数)
(2)调用方式不同
成员变量只能被对象调用。
静态变量可以被对象调用,还可以被类名调用。
(3)别名不同
成员变量也称为实例变量。
静态变量也称为类变量。
什么时候使用static,什么时候不用static?
对静态变量的修改会被始终保留下来。创建两个人的对象,会导致密码相同。
举例说明
有违Java的面向对象的封装特性,增加的耦合。
多次使用某一个方法或变量的时候用static如数据库连接串等,因为static是在程序初始化时就已分配了内存空间,直到退出前才被释放出来,而new则是在变量进入作用域时被分配,离开时释放,每new时每创建一个类的实例,都会在内存中为非静态成员新分配一块存储,所以,常用的时候用static,只在程序初始化时分配内存空间,为各个类的实例所共用,无论类创建了多少实例,类的静态成员在内存中只占同一块区域,所以常用方法或变量可以考虑用static,大型网站一般不推荐多用static因为他长时间的占用内存空间,另外static它不属于类的某一个具体的实例,而是属于类本身,所以其中不能用this等关键字,静态方法效率上要比实例化高,缺点是不能自行销毁,而实例化的可以自行销毁,网上有人说,static失去了面向对象的特性,这一点我认为是正确的,因为static不需要new根本就产生不了对象
练习:
(1) 一群人投票,如果票数达到上限100,就停止投票。
1.3. Final常量
变量和常量的区别?
变量:代表程序的状态。程序通过改变变量的值来改变整个程序的状态,也就是实现程序的功能逻辑。
常量:代表程序运行过程中不能改变的值。(例如:圆周率的值)
语法:(在变量左侧加final)
final 数据类型 常量名称 = 值;
final 数据类型 常量名称1 = 值1, 常量名称2 = 值2
注意:常量名必须大写,多个单词之间用_
示例:
在Java语法中,常量也可以首先声明,然后再进行赋值,但是只能赋值一次,final常量只能在初始化时或者构造方法里赋值如:
Final可以用来修饰什么?
final 用于声明属性(变量->常量),方法和类。
修饰变量:
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
修饰一个基本数据类型--->不能被改变
修饰一个引用数据类型---->这个引用数据类型指向不能改变,但是这个引用数据类型所指向的对象的属性的值可以发生变化。
修饰方法:
final方法不能被重写,但可以被继承。
修饰类:
final类不能被继承,没有子类,final类中所有方法都是final的。
我们为什么要阻止改变?
static final定义的变量叫做静态常量:值不能改变,可以使用类名访问 ,可以理解为全局常量
练习:
(1)使用final定义一些常量,尝试修改改常量。
1.4. This关键字
this表示什么?
this表示当前类的对象
this关键字有哪些功能?
this可以调用属性和方法,如this.属性名
成员方法参数名和属性名相同时
this.属性名可以访问类的属性
调用构造方法,this.() 无参构造, this.(参数1,参数2,参数n) 带参构造 ,调用的语句必须放在第一句
1.5. DEBUG按F5进入类
1.6. String
创建字符串
String 长度
连接字符串concat
比较字符串是否相等
字符串指定位置的字符charAt()
判断包含字符
以xx结尾
以XX开始
去掉两边的空格
indexOf方法
该方法的作用是查找特定字符或字符串在当前字符串中的起始位置,如果不存在则返回-1。例如:
replaceAll,replaceFirst替换
split方法
substring方法
把字符串转化为相应的数值Integer.parseInt()
把其他类型转换为字符串String.valueOf()
需求:在控制台输入“从10数到20”
练习:
(1) 告诉我100以内的和
(2) 告诉我100以内所有数字的和->偶数的和
(3) 你喜欢腿长的还是个子高的?/你喜欢苹果还是芒果?
1.7. String包装类,基本数据类型,装箱,拆箱
什么叫包装类?
每一个基本数据类型都有一个与之对应的包装类,基本数据类型不是面向对象的,实际开发中存在很多的不方便,所以在设计类时为每个基本数据类型设计了对应的包装类
基本数据类型 | 包装类 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
装箱-à 基本数据类型-à包装类
(1) 包装类的构造器方法
包装类(基本数据类型 vaule)
包装类(String类型 value)
(2)包装类的 valueof() 方法
拆箱--à 包装类-----à基本数据类型
基本数据类型value() 例如 byteValue()
自动装箱 自动拆箱(Java SE 5.0以后的版本支持)