一、类和对象:在Java中一切接对象,计算机世界中,通过类来实例化对象,对象可以类比现实世界中真真正正存在的实体;我们把具有相同属性和行为的一类对象抽象为类,使用类来描述这类对象的特性
类:抽象概念,人类,猫类,犬类,无法具体到某个实体
对象:某个类的一个实体,当有对象之后,这些属性就有了属性值,这些行为就有了相应的意义。
例如,我和彭于晏有啥区别,我俩都是人类的对象,共性:我们都有五官,他有的东西,我也有,其他任何人类对象都有,区别在于我们的属性值不同,比如他比我颜值高
1、Java是面向对象的编程语言,与c对比,c中,所有的行为也罢,属性也罢,在c中都是一个接一个的方法调用,关注的就是行为本身。Java是指世界上所有的行为都可以归纳为对象+行为+对象,行为一定是某个对象发出的,然后作用于另一个对象或某些对象。(一切都围绕对象进行,找对象,建对象,用对象)
2、类和对象谁先有?类是描述某一些对象的统称,对象是这个类的一个实例而已。这一类对象所具备的共同属性和行为(方法)都在类中定义。在Java中只可以存在一个主类
创建类的语法:
class 类名称{
//field(实例属性,成员属性)
//行为(实例方法,成员方法)
}
注意:类的命名使用有意义的大驼峰单词命名法,从第一个单词开始首字母就大写,多个单词都是首字母大写
class Person
class ChinaPerson
类中定义的成员变量都有默认值
3、类相当于一个蓝图,抽象的概念,有类之后我们就能根据类来产生一个具体的对象
类名称 引用名称 = new 类名称();
//前面是对象的引用,相当于给这个对象起了个名字,后面的是对象,在堆中存储
Person per = new Person();
4、当产生一个类的对象之后,我们就可以使用"."操作符来使用该对象的属性和方法
成员变量可以在类定义时赋值,成员变量就地初始化。
关于数据类型的特殊值null:null在Java中表示"空引用",只有名字没有保存任何堆内存中的地址,如果直接使用值为null的引用,去操作(".")任何属性或方法,成员变量和成员方法都会报错。示例如下:
public class ClassTest { public static void main(String[] args) { Person per1 = new Person(); per1.name = "元歌"; per1.age = 18; per1.sex = "男"; per1.print(); Person per2 = new Person(); per2.print(); //此时name的属性值为null,调用了一个值为null的引用,会报错NullPointerException //System.out.println(per2.name.length()); } } class Person{ //成员变量,都有默认值,默认值就是这个变量所在的类的默认值 String name;//null int age;//0 String sex;//null void eat(String food){ System.out.println(name + "正在吃" + food); } void print(){ System.out.println("姓名为:"+name+",年龄为:"+age+",性别为:"+sex); } } 二、static关键字
本能反应,和对象无关,静态,表示共有的含义
(1)修饰属性,类属性,类变量
(2)修饰方法,类方法,工具方法
(3)static修饰代码块,静态代码块
(4)static修饰内部类,静态内部类
1.static修饰的属性,称为类属性,类变量,所有对象共享
为何引入static变量?比如,要满足成员变量中,所有对象都有自己country属性,而且很多对象的这个值都是同一个值,比如中国人的所有对象的country = "中国",假设有一天解放军光复日本,所有日本人的country属性值都要改为中国,此时需要引入static,对于country这个值来说,他应该是很多个对象共有的一个属性,使用static修饰这个属性,该属性就称为静态属性
static修饰的属性在JVM方法区中存储,所有该类对象共享此属性。并且static修饰的属性,可直接通过类名称就可以操作,无需通过对象访问,如Person.country
具体过程:调用new Person()来产生Person的对象,先有类才能产生对象,二static修饰的属性不同,首先将Person类加载到内存中,Person中的所有static变量就会被加载到方法区中,类中的所有方法都在方法区存储;还有常量和静态变量。示例如下:
public class StaticTest { public static void main(String[] args) { Person1 per = new Person1(); per.name = "元歌"; per.age = 18; per.sex = "男"; Person1.country = "中国"; //编译可通过并运行,但最好不要用这种语法 //per.country = "韩国"; per.show(); Person1 per2 = new Person1(); System.out.println(per2.country); } } class Person1{ //堆中存储 String name; int age; String sex; //方法区存储 static String country = "美国"; void show(){ System.out.println("姓名:"+name+",年龄:"+age+",性别:"+sex+",国籍:"+country); } }
那么,在Java中,能否在一个方法的内部定义一个static变量呢?不能!!!因为方法中定义的变量全是局部变量,局部变量都在栈中存储,不可能定义出一个既在栈中又在方法区的属性(局部变量出了方法就被栈回收了,而静态变量不会)
关于final和static的区别:final是成员变量,在堆中存储,其值不能改变,而static是静态变量,在方法区中存储,所有的Person对象共享country这个属性。若在类中定义了一个常量,我们通常情况下会把static和final共同使用称为类的常量,类中的常量必须在定义时赋值
比如final int age = 18;age这个属性,成员变量,在类定义的时候就赋值18了,Person的所有对象都有age属性,且值都是18,而且是天然的共享概念,不用加static,所有Person对象共享这个属性,全局唯一
在类中定义常量,一般都会使用全局变量static final共同修饰,命名规则:所有单词全部大写,多个单词使用下划线分隔,如static final String STUDENT_SCHOOL = "清华大学";
总结:a static变量称为类属性,在方法区中存储,该类的所有对象共享此变量
b 若在类中定义了常量(定义时赋值),一般我们用static和final共同修饰,全局变量
c 要使用类属性,我们通常直接通过类名称.属性名称 不推荐使用对象来调用类属性,不规范
d static属性称为类属性,通过类名称直接访问,此时没有对象也能调用(包含该类的null引用)
比如Person per = null; System.out.println(Person.country); 依然可以输出国籍,与上节课空指针异常对比,它是空引用,与static无关
2.static修饰方法 --类方法、工具方法 (静态方法)
static修饰的方法也是通过类名称直接方法,没有对象就能访问
public static void main(String[] args){} 为啥主方法是个静态方法?主方法是一个程序的入口,如果主方法是个成员方法,是不得通过对象调用的,入口都没有,如何产生对象?程序从主方法开始执行,主方法要能调用起来,静态方法,直接调用,无需产生对象。
从static的语义,静态,无需产生对象就能调用的方法来判断:(1)静态方法能否访问成员变量和成员方法;(2)成员方法能否访问静态变量和静态方法 答案:F T
解释:static静态,没有对象就能访问的属性和方法,静态方法没有该类对象就可以访问,所以都没有对象,访问不了成员变量和成员方法;成员域:成员方法和成员变量,必须通过对象来调用,必须得有对象才能访问,成员方法指已经有对象了,才能调用成员方法,没对象都能调用static更何况现在都有对象,更能调用了
总结:a.在静态方法中只能调用静态方法或者静态属性,static家族之间可以相互调用。不能直接调用成员方法和成员属性,必须通过对象来调用。
b.在成员方法中既可以调用成员方法,也可以调用静态方法(此时都已产生了该对象,一定是可以访问静态域)。共享的变量如country属性,设计为静态变量,工具类的方法设计为static方法,如Arrays.copyOf()等,都是Arrays提供的操作数组的方法,设计为static方法;看见static,都和对象无关,直接通过类名称访问静态属性和方法
c.普通的类能否使用static关键字修饰,(不是内部类) static class Person{} 不可以,类定义出来是要产生对象的,假设static能修饰一个类,这个类没对象能调用了,而主类的对象则是和权限有关
三、private的用法
1、面向对象的三大特性:封装,继承和多态
封装:保护性和易用性(封装有很多表现形式)private能实现属性的封装,只是封装的其中一种方法。
关于保护性:之前写的程序中,person类的name和age属性,在类的外部主方法中或者其他地方都可以直接调用这个属性,合理吗?以银行卡这个类来说,银行卡的卡号,余额和密码三个属性,如果这三个属性直接暴露在外部,直接就在卡上贴着的话,显然不合理,不能让这些对象通过对象直接就访问了。
关于易用性:对于汽车这个类来说,车真正发动起来,是需要很多个属性间的相互配合的,也是用户不关注的。只需按一键启动(方法),方法内部就能把这些属性来进行一个调配和操作,车就打上火了。
在Java中,所谓的权限修饰符,指的是,你修饰的属性,方法,类,到底可见范围有多大,一共有四大访问修饰符,可见范围由大到小依次为:private<default(不要写这个关键字,啥权限也没有就是default)--包访问权限<protected<public
private:私有的,被private修饰的属性和方法,只在当前类的内部可见,出了类的{},对外部就完全隐藏了,外部不知道有其存在。private无法修饰外部类,类定义出来就要产生对象,供外部使用,private修饰一个类,定义之后,外部根本不知道,无法供外部使用。
public:公共的,公开的,被public修饰的东西,在当前程序中都是可见的,都是可以使用的,示例如下:
public class StaticTest { public static void main(String[] args) { //main存在于Bank类的外部 Bank的私有属性对其他类就不可见了 Bank bank = new Bank(); bank.cardNum = 123;//此时会报错 } } class Bank{ //卡号 private int cardNum; //余额 private double money; //密码 private String password; //若主方法就存在于当前这个类的内部,私有属性在当前的内部是可见的 //public static void main(String[] args) { //Bank bank = new Bank(); //bank.cardNum = 123; } }
那么,如果想在类的外部去使用这些私有属性,需要使用类提供的getter(取值)和setter(修改值)
到底哪些属性需要提供getter,让外部可见,哪些属性需要提供setter,让外部修改,都要根据这个属性的特征来决定,如cardNum只可取,money只可取,password既可以取,也可以改
快捷键生成getter和setter:右键Generate,选择getter和setter
阿里编码规范:Java类中所有的成员变量一律使用private封装,并且根据属性的实际情况对外提供getter和setter 小案例:模拟修改密码
import java.util.Scanner; public class StaticTest { public static void main(String[] args) { Car car = new Car(); // 易用性 car.start(); Bank bank = new Bank(); // 当password属性被private之后,对于这个属性就是一个保护 // 类的外部要想使用这个属性,必须按照我的规则去使用(getter和setter) bank.setPassword(); System.out.print("修改后的密码为:"); System.out.println(bank.getPassword()); } } class Bank { //卡号 private int cardNum; //余额 private double money; //密码 private String password = "123"; public int getCardNum() { return cardNum; } public double getMoney() { return money; } public String getPassword() { return password; } public void setPassword() { Scanner scanner = new Scanner(System.in); int count = 0; //验证当前密码是否正确才能修改 while (true) { System.out.println("请输入旧密码:"); String oldPsw = scanner.nextLine(); count++; // 所有引用类型的对象比较使用equals方法 if (oldPsw.equals(password)) { System.out.println("密码正确,请输入新的密码:"); String newPass = scanner.nextLine(); password = newPass; System.out.println("密码修改成功!"); break; } else { //密码输入有误 System.out.println("密码错误,请查证后再试"); if (count == 3) { System.out.println("尝试次数过多,银行卡已经锁定"); break; } } } } } class Car { private String engine; private String bianSuXiang; private String guLu; //一键启动 public void start(){ engine = "启动引擎"; bianSuXiang = "启动变速箱"; guLu = "轮胎开始转"; System.out.println("BMW 启动了~~"); } } 四、构造方法
-
构造方法:构造方法是类中非常特殊的一类方法,使用关键字new实例化对象时实际上就调用的是该类的构造方法,构造方法的作用就是产生对象
(1) 使用关键字new产生一个对象时,大致分为以下两步:
a.为对象在堆中分配空间
b.调用对象的构造方法为对象成员变量赋值
(2)重点!构造方法的语法规则:
a.方法名称与类名称完全相同
b.构造方法没有返回值声明(不是void)
c.一个类中至少存在一个构造方法,若没有显示定义,编译器就会生成一个默认的无参构造。
public class CLASSTest2 { public static void main(String[] args) { //当我们new一个对象的时候,就默认调用构造方法 Person1 per = new Person1(); } } class Person1{ private String name; private int age; //若在类中没有定义构造方法,编译后,编译器会自动生成一个默认的无参构造 public Person1(){ System.out.println("Person的构造方法"); } }
(3)当类中自定义了构造方法,则默认的无参构造就不再生成
public class CLASSTest2 { public static void main(String[] args) { //当我们new一个对象的时候,就默认调用构造方法 Person1 per = new Person1("test"); } } class Person1{ private String name; private int age; //只有一个带一个参数的有参构造,则默认的无参构造就没了 public Person1(String n){ name = n; System.out.println("Person的构造方法"); } }
(4)无参构造在内存中如何存储的?若主方法中产生了一个Person的对象 1.先在堆上开辟一块空间(大小由该类中的成员变量的属性决定)2. JVM调用对象的构造方法为所有成员变量赋值,成员变量的默认值就是在构造方法中赋值的,如name = null age = 0 3. 当构造方法调用结束,该对象就初始化完成。
(5)构造方法可以重载:构造方法是为了类中的成员变量赋值的,此时的重载只可能是参数的个数不同(为不同成员变量赋初值)成员变量的类型在类定义时就指定好了,只是初始化的变量个数不同。
public class CLASSTest2 {
public static void main(String[] args) {
//当我们new一个对象的时候,就默认调用构造方法
Person1 per = new Person1();
Person1 per1 = new Person1("元歌");
Person1 per2 = new Person1("元歌",18);
//per.Person1不可取,那是自己在调自己的构造方法,JVM产生对象时调用构造方法,对象实例化结束,无法在程序中手动调用构造方法再次实例化对象!!!
}
}
class Person1{
//如果在成员变量定义时就赋初值该如何运行?答:以无参构造为例,成员变量只有在产生对象时,才会在堆中分配空间,所以第一步先在堆中开辟空间,第二步调用构造方法,又因为成员变量的赋值在构造方法中进行的,所以name和age变为初值在堆中存储
private String name;
private int age;
//只有一个带一个参数的有参构造,则默认的无参构造就没了
public Person1(){
System.out.println("Person的无参构造");
}
public Person1(String n){
name = n;
System.out.println("name = " + name);
System.out.println("Person一个参数的有参构造");
}
public Person1(String n,int a){
name = n;
age = a;
System.out.println("name = "+ name +",age = "+ age);
System.out.println("Person两个参数的有参构造");
}
}
五、this关键字
(1)this关键字功能:a.调用当前对象的成员变量 b.调用当前对象的方法 c.表示当前对象的引用
其中,调用当前对象的方法可以分为调用普通成员的方法和构造方法的互相调用
(2)关于调用当前对象的成员变量:(示例如下)
public class ThisTest { public static void main(String[] args) { Student student = new Student("元歌",18,"男"); student.show(); } } class Student{ private String name; private int age; private String sex; //此时形参名称和成员变量名称相同 public Student(String name,int age,String sex){ //程序的设计理念,就近匹配原则,编译器会找最近的相同名称的变量在哪儿 //所以如果不加this,表示形参等于自己,对成员变量就没有任何影响,也不会通过构造方法赋初值 //如何打破就近匹配原则,从类中找同名变量呢?就用this关键字 this.name = name; this.age = age; this.sex = sex; System.out.println("Student类的有参构造"); } public void show(){ //没有this 输出结果都为默认值 System.out.println("name = " + name +",age = " + age + ",sex = " + sex); } }
(3) this调用类中的方法
a.this调用类中的成员方法(示例如下)
public class ThisTest { public static void main(String[] args) { student.fun(); } } class Student{ public void test(){ System.out.println("Student类的test成员方法"); } public void fun(){ //test是成员方法,可通过this关键字进行调用,此时写不写this效果都一样,不写的话编译器之后会默认加this this.test(); System.out.println("Student类的fun成员方法"); } }
b.this构造方法间的互相调用:正常使用Student stu1 = new Student();调用多个有参和无参构造可能会重复(示例如下),所以要使用this关键字调用其他构造方法避免重复
public class ThisTest { public static void main(String[] args) { Student stu1 = new Student(); Student stu2 = new Student("元歌"); Student stu3 = new Student("元歌",18); } } class Student{ private String name; private int age; private String sex; //若出现不同参数构造方法之间的重复调用,可用this(参数)调用其他的构造方法,使逻辑更清晰 public Student(){ System.out.println("*****************************"); } public Student(String name){ //打印功能可以用this关键字调用前面的无参构造 System.out.println("*****************************"); //调用无参构造 this(); this.name = name; } public Student(String name,int age){ System.out.println("*****************************"); this.name = name; //前两行可以用this关键字调用前面的一个参数的有参构造 //调用一个参数的有参构造 this(name); this.age = age; } }
关键点1:this调用其他的构造方法必须放在当前构造方法的首行(强制规则)
public Student(String name,int age){ //this(name); this.age = age; //这样是不可取的,this调用其他的构造方法必须放在当前构造方法的首行 this(name); }
关键点2:this调用构造方法不能成"环"
public Student(){ //无参构造方法调用一个参数的构造方法,不可取,因为此时无参和一个参数的构造方法形成闭环,不符合规定 this("test"); System.out.println("*****************************"); } public Student(String name){ //一个参数的构造方法调用无参构造方法,可以的 this(); this.name = name; } public Student(String name,int age){ //两个参数的构造方法调用一个参数的构造方法,可以的 this(name); this.age = age; }
(4)this表示当前对象的引用,了解即可 this关键字当做一面镜子
public class ThisTest {
public static void main(String[] args) {
Student stu1 = new Student();
System.out.println(stu1);
stu1.whoAmI();
Student stu2 = new Student("元歌");
System.out.println(stu2);
stu2.whoAmI();
}
}
class Student{
private String name;
private int age;
private String sex;
public void whoAmI(){
//this表示当前对象的引用,我当前是哪个对象调用的属性或方法,this就代表谁,比如主方法中先是stu1输出并调用方法,那么this就代表stu1在Student类中的地址,后来换成stu2,this也会发生变化
//当前的属性或方法是哪个对象调用的,this就代指这个对象
System.out.println(this);
}
public Student(){
System.out.println("*****************************");
}
public Student(String name){
this();
this.name = name;
}
public Student(String name,int age){
this(name);
this.age = age;
}
}
六、代码块:使用{}括起来的一段代码,根据定义的代码块
使用{}括起来的一段代码,根据定义的代码块的位置以及关键字的不同细分为以下四种代码块:普通代码块、成员代码块、静态代码块、同步代码块
(1)普通代码块:在方法中使用{}括起来的部分,可以避免变量重名,比如在大括号内int a = 10;代码块之后还可以int a = 20;此时打印结果为20,括号内的a作用域仅限于大括号内部
(2)成员代码块:定义在类中,使用{}括起来的代码块,也叫构造块,直接定义在类中,不加任何修饰符的代码块,优于构造方法执行,有几个对象产生就调用几次构造块。
public class GouZaoTest { public static void main(String[] args) { Animal animal1 = new Animal(); Animal animal2 = new Animal(); } } class Animal { private String name; { //构造块 System.out.println("2.Animal的构造块"); } public Animal(){ System.out.println("1.Animal的无参构造"); } public Animal(String name){ this.name = name; } }
补充:若给将name赋值小狗,成员代码块中加入name = test,那么在主方法中打印对象的name是什么?答案:小狗
public class GouZaoTest { public static void main(String[] args) { Animal animal1 = new Animal("小狗"); System.out.println(animal1.name); } } class Animal { String name; { //构造块 //先执行构造块,第一次传值为test name = "test"; System.out.println("2.Animal的构造块"); } public Animal(){ System.out.println("1.Animal的无参构造"); } public Animal(String name){ //第二次传值为小狗,所以取第二次的赋值,结果为小狗 this.name = name; } }
(3)静态代码块:定义在类中,使用static修饰的代码块,在类加载时执行一次(只执行一次)
public class GouZaoTest { public static void main(String[] args) { Animal animal1 = new Animal("小狗"); Animal animal2 = new Animal(); //执行结果32121 //在类加载时执行一次,与对象有多少个无关,无论产生多少对象,静态代码块只在类加载时执行一次 } } class Animal { String name; { //构造块 name = "test"; System.out.println("2.Animal的构造块"); } public Animal(){ System.out.println("1.Animal的无参构造"); } public Animal(String name){ this.name = name; System.out.println("1.Animal的有参构造"); } static { //静态代码块 System.out.println("3.Animal的静态代码块"); } }
补充:若在主方法前定义一个static静态代码块,那么是静态代码块先执行还是主方法先执行?
public class GouZaoTest { static { System.out.println("2.主类的静态代码块"); } public static void main(String[] args) { System.out.println("1.进入主方法"); //结果是21 //说明主类中静态块优先于主方法执行,JVM要执行主方法,首先得加载主类,主类一加载,静态块就执行了 } }
补充:static int age = 10;和静态代码块中定义age = 100;到底输出谁?
public class GouZaoTest { public static void main(String[] args) { System.out.println(Animal.age); } } class Animal { //此时输出结果为100 static int age = 10;//存在于方法区中,类定义的时候就会有初始值,初始值为10,这个类就被放在方法区中->这个类只是定义了,还没加载。 static { //当主方法中使用了Animal,就要把Animal从方法区加载到内存,类加载->静态代码块就执行了,age=10->100 age = 100; System.out.println("3.Animal的静态代码块"); } } 七、匿名对象和toString方法:
1.匿名对象:new出来的对象,没有引用指向,使用一次后就销毁,就再也用不成了。(没给对象起名字)
比如:new Person();匿名对象常用于测试类中的某些功能,使用一次后就被JVM销毁。
2.toString方法(非常重要!!):一定注意大小写
当一个引用类型的变量调用println函数打印时,默认输出的都是引用类型的地址。(不是真正的内存地址,java中程序员是无法知道任何确切的内存地址。)
什么是打印引用类型的地址:比如主方法中System.out.println(new Animal());默认输出都是引用类型地址
大概率我们调用打印函数打印一个引用的时候,打印这个对象具体属性值内容--->实现toString();
public class GouZaoTest { static { System.out.println("2.主类的静态代码块"); } public static void main(String[] args) { //这个时候就不返回地址了,而是默认调用toString方法,若拼写正确,左边会有个小符号 System.out.println(new Animal("元歌")); } } class Animal { private String name; public String toString(){ String ret = "name=" + this.name; return ret; } static int age = 10; static { age = 100; System.out.println("3.Animal的静态代码块"); } { //构造块 name = "test"; System.out.println("2.Animal的构造块"); } public Animal(){ System.out.println("1.Animal的无参构造"); } public Animal(String name){ this.name = name; System.out.println("1.Animal的有参构造"); } }