优质资源共享、学习路线引导(点击解锁)、知识定位和人群定位
Python实战微信点餐小程序
升级
本课程完美结合python flask+微信小程序,从项目搭建到腾讯云部署,打造全栈点餐系统。
Python量化交易实战
入门级
带你携手打造易于扩展、更安全、更高效的量化交易系统
本文是“零基础Java”专栏的第五篇。文章用通俗易懂的文字、图形和代码,带你从零基础走上高薪之路!
本文首发于公众号【编程指南】
类和对象
在哲学体系中,它可以分为主体和客体。在面向对象的编程语言中,所有要面对的事物都可以抽象为对象。在面向对象编程的过程中,我们利用各种对象相互配合来完成我们的程序功能。
在面向对象的语言中,所有使用中的对象都有一定的类型,这些类型之间存在等级关系,就像生物学中的门、纲、目、科、属、种一样。关系,我们可以使用继承机制来完成。
Java 语言是一种面向对象的语言,所有的对象都可以具有一定的属性和一定的行为功能。在Java语言中,对象的属性由成员变量(字段)来描述,行为由方法来描述。
类和对象的区别和联系在于,类是抽象的,它具有一类对象的所有属性和行为,相当于一个模板,而一个对象是具体的,以及相应的功能都是通过创建相应类的对象来完成的。我们在做面向对象编程时未将对象引用设置到对象的实例 超级大傻瓜,将类的属性和行为抽象出来,然后创建具体的对象来完成功能。
日期类定义
在JDK中,有一个描述日期的类:java.util.Date,它代表一个特定的瞬间,精确到毫秒。在这里,我们定义了一个简单的 Date 类来表示带有年、月和日的日期。我们先来看看下面的定义?
public class Date{
public int year;
public int month;
public int day;
}
在这个程序定义中,类被定义为public,年、月、日也被定义为public。它们是什么意思?
我们前面提到了包的概念。我们说过同一个包中的类在功能上是相关的,但并不是一个包中的所有这些类都可以被其他包中的类调用。如何区分毛呢?对于那些可以在包外使用的类,我们在定义的时候在类名前加public,但是对于不能在包外使用的类,在定义的时候不要加public。
类体中定义的年、月、日等变量称为成员变量(字段)。成员变量前面public的作用是表示这些变量是公共的,可以作为对象使用。以这种形式调用变量,称为成员变量访问。例如,在这个例子中:我们定义了一个 Date 类型的变量 d1,那么我们可以通过 d1.year 使用对象 d1 中的年份。定义成员变量时,除了定义为public,还可以有protected、default(无权限修饰符)、private,它们的限制越来越严格。总结一下:
在一个类中,我们不仅可以定义成员变量,还可以定义成员方法。成员方法前面也可以有这四种权限控件,意思是一样的。但是不要以为一个独立的类前面也可以有两个修饰符private和protected,一个类前面只能有public或者none。意思上面已经描述过了。
[外链图片传输失败,源站可能有防盗链机制,建议保存图片直接上传(img-YKlzDGpt-1652894180561)()]
我们之前了解过公共类和源程序文件的关系,这里再提一下:
每个源代码文件中至少定义一个类。如果有多个类,最多定义一个类为public。如果有公共类,源代码文件的前缀名必须与类的类名完全一致,如果没有公共类,源代码文件的前缀名可能与任何类不一致在文件中。
测试
上面的Date类已经定义好了,我们如何测试这个类呢?我们是否在这个类中定义了 main 方法?没有main方法的类可以在控制台中用java类名执行吗?显然不是,那该怎么办?有两种选择,一种是我们为它定义一个 main 方法;另一种是定义一个类来测试Date类。让我们来看看每一个。
添加主方法
我们在Date的定义中加入main方法,代码如下:
public class Date{
public int year;
public int month;
public int day;
public static void main(String[] args){
year = 2016;
month = 9;
day = 5;
System.out.println("year:" + year + " month:" + month + " day:" + day);
}
}
编译…,什么?又错了? [外链图片传输失败,源站可能有防盗链机制,建议保存图片直接上传(img-QtQLLmWZ-1652894180565)()]
不要惊慌,看看错误消息,所有这些都是诸如无法从静态上下文引用非静态变量之类的消息。这是什么意思?我们看看main方法前面有没有修饰符static。前面我们提到,任何被static修饰的成员变量或方法,都可以通过类名.member的形式来访问。如果在控制台中以java Date的命令形式执行Date类,请问,系统是否会创建一个Date对象,然后通过执行该对象中的main方法来这样执行Date类呢?错了,系统直接找到类文件Date.class,将类调入内存,然后直接执行main。此时,内存中没有 Date 对象。这就是为什么 main 被定义为静态的原因,因为 main 是通过类来做的,而不是对象。
上面的解释和这个编译错误信息有什么关系?看看年月日前面有没有修饰语static?不,这意味着这些成员变量不能通过类名直接访问,而必须通过创建类的对象,然后通过对象来访问。 main 执行的时候,内存中没有对象,所以那些没有静态修改的成员自然是无法访问的,因为它们不存在。这是逻辑吗?你明白吗?还没有?再想想!
我们得出一个可以记住的结论。在静态修改方法中未将对象引用设置到对象的实例 超级大傻瓜,只能使用该类中那些静态修改的成员(包括成员变量、方法等)。如果使用此类的非静态成员,则还必须创建此类的对象以供对象使用。好吧,如果你不明白真相,我们先记住这个结论。
根据以上结论,我们将修改测试代码:
public class Date2{
public int year;
public int month;
public int day;
public static void main(String[] args){
Date2 d1 = new Date2();
d1.year = 2016;
d1.month = 9;
d1.day = 5;
System.out.println("year:" + d1.year + " month:" + d1.month + " day:" + d1.day);
}
}
编译…,OK,运行,通过,【外链图片传输失败,源站可能有防盗链机制,建议保存图片直接上传(img-vlrBCrTD-165289418056< @6)()]
如果在非静态方法中使用静态成员会怎样?我们说是的,因为类先于对象存在,静态成员依赖于类,非静态成员依赖于对象。
在上面的程序中,我们已经分别给这些成员变量赋值了。如果我们不给值直接输出呢?让我们试试吧:
public class Date3{
public int year;
public int month;
public int day;
public static void main(String[] args){
Date3 d1 = new Date3();
System.out.println("year:" + d1.year + " month:" + d1.month + " day:" + d1.day);
}
}
编译,运行,结果:【外链图片传输失败,源站可能有防盗链机制,建议保存图片直接上传(img-2oJQEEOF-1652894180567)( )],这里有一个结论:如果一个成员变量没有显式初始化,它的初始值为0,或者等价于0的值,比如引用变量,它没有显式初始化,它的值为null,而逻辑变量没有显式初始化的值为false,这里需要注意的是,我们说的是成员变量,如果是局部变量,会在没有显式初始化的情况下使用,编译不会通过。对于例如,在上面的代码中,将其更改为:
public class Date3{
public int year;
public int month;
public int day;
public static void main(String[] args){
Date3 d1;//这里d1为局部变量,没有初始化
System.out.println("year:" + d1.year + " month:" + d1.month + " day:" + d1.day);
}
}
编译:【外链图片传输失败,源站可能有防盗链机制,建议保存图片直接上传(img-q0wPoI92-1652894180568)() ] 从编译提示中,我们知道 d1 不是用它来初始化是错误的。
创建一个测试类
代码如下:
//本类放在Date.java文件中
public class Date{
public int year;
public int month;
public int day;
}
//本类放在TestDate.java文件中
public class TestDate{
public static void main(String[] args){
Date d1 = new Date();
d1.year = 2016;
d1.month = 9;
d1.day = 5;
System.out.println("year:" + d1.year + " month:" + d1.month + " day:" + d1.day);
}
}
这两个类都是公共类,所以这两个类应该分别写在两个不同的源代码文件中,分别命名为Date.java和TestDate.java。如果要将这两个类写在一个源代码文件中,因为测试完成后Date类会被公开使用,所以我们要把Date定义为public,那么源代码文件的名字就是Date.java, TestDate类是用来测试Date类的,所以前面的public应该去掉。代码如下:
//这些代码放在Date.java文件中
class TestDate{
public static void main(String[] args){
Date d1 = new Date();
d1.year = 2016;
d1.month = 9;
d1.day = 5;
System.out.println("year:" + d1.year + " month:" + d1.month + " day:" + d1.day);
}
}
public class Date{
public int year;
public int month;
public int day;
}
这两个类的定义顺序无关紧要。编译完成后,我们在命令行执行:java TestDate。
思维包
在这个例子中,有一个问题:我们创建Date对象d1后,直接用d1.的形式给年月日赋值,如果我们赋值对他们来说不是一个有效值 的值呢?例如,月份的值不在 1 到 12 之间,以此类推。在上面的Date定义中,我们无法控制其他代码任意设置这些公共修改变量的值。
我们可以使用 private 修饰符来保护这些成员变量。被private修改的成员只能在类或对象内部被自己访问,不能被外部访问。将上面的日期代码改为:
public class Date{
private int year;
private int month;
private int day;
}
这样,我们就无法通过d1.year来访问d1对象中的年份。等等,你不能访问年份,那么这个变量有什么用呢?是的,如果没有其他办法,上面的类就是一个没用的类,我们没有办法在里面存储数据。
我该怎么办?我们通过为每个私有成员变量添加一对公共方法来设置和获取这些变量的值。这些方法的命名方式有:setXxx和getXxx,setter方法给变量赋值,getter取变量的值,布尔变量的值命名为:isXxx,xxx是变量名,上面的例子改为:
public class Date{
private int year;
private int month;
private int day;
public void setYear(int year){
//理论上year是没有公元0年的,我们对传入的值为0的实参处理为1
//year的正值表示AD,负值表示BC
if(year == 0){
//这里有两个year,一个是成员变量,一个是实参,这是允许的
//为了区分它们,在成员变量year前面加上this.
//实参year不做处理
//如果没有变量和成员变量同名,this是可以不写的
this.year = 1;
} else {
this.year = year;
}
}
//因为要取的是year的值,所以getter方法的返回值同所取变量的类型一致
public int getYear(){
return year;
}
public void setMonth(int month){
if((month > 0) && (month < 13)){
this.month = month;
} else{
this.month = 1;
}
}
public int getMonth(){
return month;
}
public void setDay(int day){
//这个方法有些复杂,因为我们需要根据year、month的值来判断实参的值是否合规
switch(month){
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:if (day < 32 && day > 0) {//在1~31范围内
this.day = day;
}else{
this.day = 1;//超出日期正常范围,我们设为1
}
break;
case 4:
case 6:
case 9:
case 11:if (day < 31 && day > 0) {//在1~30范围内
this.day = day;
}else{
this.day = 1;//超出日期正常范围,我们设为1
}
break;
case 2:if (isLeapYear()) {
if (day < 30 && day > 0) {//在1~29范围内
this.day = day;
}else{
this.day = 1;//超出日期正常范围,我们设为1
}
} else {
if (day < 29 && day > 0) {//在1~28范围内
this.day = day;
}else{
this.day = 1;//超出日期正常范围,我们设为1
}
}
break;
default:this.day = 1;//如果month的值不在上述情况下,day设置为1
break;
}
}
//这个方法判断年份是否为闰年,是闰年返回true,否则返回false
//该方法只在本类内部使用,所以定义为private
private boolean isLeapYear(){
//可被400整除或者被4整除但不能被100整除的年份为闰年,其它年份为平年
if((year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0))){
return true;
}
return false;//能执行这里,说明是平年
}
}
经过上面的改造,虽然代码长了很多,但是安全了很多。我们不再直接访问年月日的值,而是通过对应变量的getter和setter方法来访问。这些方法在访问时会判断设置值是否符合。
上面的代码其实是可以优化的。比如setDay可以优化如下:
public void setDay(int day){
//这个方法有些复杂,因为我们需要根据year、month的值来判断实参的值是否合规
this.day = 1;//这里先把day设置为1,下面的超范围的情况就不用再写代码了
switch(month){
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:if (day < 32 && day > 0) {//在1~31范围内
this.day = day;
}
break;
case 4:
case 6:
case 9:
case 11:if (day < 31 && day > 0) {//在1~30范围内
this.day = day;
}
break;
case 2:if (isLeapYear()) {
if (day < 30 && day > 0) {//在1~29范围内
this.day = day;
}
} else {
if (day < 29 && day > 0) {//在1~28范围内
this.day = day;
}
}
break;
}
}
通过上面的例子,我们看到了面向对象的一个概念:封装。我们用私有的方式隐藏数据,通过公开的访问方式对数据进行封装,使数据更加安全可靠。
成员变量的初始化非静态成员变量的初始化
在上面的例子中,如果我们新建一个Date对象,如果不初始化,可以知道当我们直接取年月日的值时,它们的值都是0 ,这显然是不合理的。为了解决这个问题,我们需要在创建 Date 对象时让系统自动初始化一个合适的值。为此,我们可以使用三种方法:
public class Date{
private int year = 1;
private int month = 1;
private int day = 1;
...
}
以上代码使新创建的Date对象的初始值全部为1。
构造函数是类中的一种特殊方法。它的方法名与类名相同。方法没有返回值,甚至没有void,构造函数不能像普通方法一样调用。当调用某个类的对象时,会调用构造方法。构造函数的作用是在创建对象时执行对象的初始化。我们使用构造函数来初始化成员变量,代码如下:
public class Date{
private int year;
private int month;
private int day;
public Date(){
year = 1;
month = 1;
day = 1;
}
...
}
上面的代码允许我们在创建新的 Date 对象时初始化它的值。上面的构造方法没有参数。这种构造方法称为默认构造方法。这是否意味着构造函数只能初始化为这个固定值?可以在创建对象时指定初始化值吗?答案是肯定的,那就是使用带参数的构造函数。代码如下:
public class Date{
private int year;
private int month;
private int day;
public Date(int year, int month, int day){
//下面的代码使用setter方法进行初始化是因为,setter方法提供了参数检查,
//如果不用setter方法,我们就需要重新写对参数的检查代码
setYear(year);
setMonth(month);
setDay(day);
}
...
}
上面的代码允许我们在创建新的Date对象时指定初始化值,例如:Date d = new Date(2016,9,5);。但是上面的代码并没有定义无参构造函数,我们不能使用无参构造方法创建Date对象,如:Date d = new Date();,编译时会出现如图错误:【外链图片传输失败,源站可能有防盗链机制,建议保存图片直接上传(img-O0JXPq7n-1652894180570)()]
这是为什么?这是因为,如果在定义类的时候没有定义构造函数,编译器会自动创建一个空的不带参数的公共构造函数作为类的构造函数。但是,只要你定义了一个构造函数,不管有没有参数,编译器都不会再自动生成了,会出现上面的错误信息。
另外,构造函数中产生的异常会被jvm忽略,即使在构造函数中使用try也无济于事。
public class Test {
public Test() {
try {
System.out.println("trying to throw an exception");
double x = 1.0/0.0;//此句会产生除0异常
} catch(Exception e) {
System.out.println("Exception captured");
} finally {
System.out.println("inside finally");
}
}
public static void main(String args[]) {
Test test = new Test();
}
}
以上代码执行后结果如图:【外链图片传输失败,源站可能有防盗链机制,建议保存图片直接上传(img-ZZdZdChf- 1652894180571)()] 从图中可以看出,没有捕获到异常。
public class Date{
private int year;
private int month;
private int day;
//下面的大括号为实例语句块
{
year = 1;
month = 1;
day = 1;
}
...
}
上面的实例块将在构造函数执行之前执行。如果出现多个实例块,则依次执行。
public class Date{
private int year = 1;
private int month = 1;
private int day = 1;
{
year = 2;
month = 2;
day = 2;
}
public Date(){
year = 3;
month = 3;
day = 3;
}
...
}
在上面的Date类中,如果要创建一个新对象,它的初始值是1、2还是3?创建对象时,在堆中为该对象分配存储空间,并将分配空间的所有内容设置为默认初始值0,然后将成员变量初始化为成员时的值定义变量,然后执行实例语句块。 ,最后执行构造函数。因此,上例的初始终值为3。
静态成员变量的初始化
public class Test{
public static int first = 1;
public static int second = 2;
}
public class Test{
public static int first;
public static int second;
//下面的语句就是静态语句块,这种语句块不能放在方法体中
static{
first = 1;
second = 2;
}
}
```在类中,可以出现多个静态语句块,多个静态语句块顺序执行。
+ 静态成员变量的初始化发生在该成员变量第一次被使用的时候,之后该成员变量不会再重复初始化。而且类中的静态成员变量的初始化发生在非静态成员变量的初始化之前,这样,下面的代码就不合适了:
公开课测试{
public int first = 1;
public static int second = first;
}
类定义中的成员
Java 类可以包含两种类型的成员:实例成员和类成员。
实例成员
int i=10;
void f(){…}
class T{
int i;
void f(){}
}
。。。
T t1,t2;
t1=new T();
t2=new T();
此时i有两份,一份在t1,一份在t2,分别供t1.i和t2.i
使用
班级成员
static int count=0;
public static void main(String args[]){…}
class T{
static int i=47;
static void f(){i++;}
}
。。。
T t1,t2;
t1=new T();
t2=new T();
此时i只有一份,可以被t1.i和t2.i使用,也可以被T.i引用,其值为47。在T之后。我++;被执行,t1.i和t2.i的值都是48;对 f 的调用可以是 t1.f();或 T.f( );使用方法。根据java编程规范,对于静态成员,我们建议只使用类名引用的方法。
类成员和实例成员总结
public class Test{
static int i=0;
int j=10;
static void f(){i++;}
void s(){f(); j++;}
public static void main(String[] args){
Test t=new Test();
t.s();
System.out.println(i);
}
}
这个
在 Java 中我们经常看到 this 关键字的使用。它用于两种情况:
this 不能用于静态方法。因为静态方法的执行根本不需要java对象的存在,而是直接使用类名的方法。方法来访问,而this代表当前对象,所以this根本不能用在静态方法中。
public class Flower {
int petalCount = 0;
String s = new String("null");
Flower(int petals) {
petalCount = petals;
System.out.println("Constructor with int arg only, petalCount= "+ petalCount);
}
Flower(String ss) {
System.out.println("Constructor with String arg only, s=" + ss);
s = ss;
}
Flower(String s, int petals) {
this(petals); //调用Flower(petals),但不能写作Flower(petals)
//! this(s); // Can't call two!
this.s = s; // 这是this的另一种使用方式
System.out.println("String & int args");
}
Flower() {
this("hi", 47);
System.out.println("default constructor (no args)");
}
void print() {
//! this(11); // Not inside non-constructor!
System.out.println("petalCount = " + petalCount + " s = "+ s);
}
public static void main(String[] args) {
Flower x = new Flower();
x.print();
}
} ///:~
私有构造函数
构造函数前还可以有public等修饰符来限制构造函数的访问权限。一般情况下,如果类是public的,那么构造函数也是public的,这样就可以通过new来调用了。那么,如果构造函数前面有一个私有修饰符会发生什么?我们不能在类之外生成这个类的对象。你可以自己测试一下。
在构造函数前面不是不能用private吗?当然可以,这是一种有用的情况。在某些情况下,某些类只能允许该类的一个实例(对象)存在于系统中。这时,我们可以将类的构造函数定义为私有的。示例代码如下:
public class Handler {
//handler变量用来保存该类对象的引用
private static Handler handler = null;
private Handler() {
/* set something here */
}
//该类的对象只能通过getHandler方法从handler这个私有变量中获取
public static getHandler(/* arglist */) {
//如果handler值非空,说明已经在系统中存在该类的对象,直接取出,不再生成,这就保证了单例
if (!handler)
handler = new Handler();
return handler;
}
public static void main(String args[]) {
Handler.getHandler();
}
}
最后
本文来自公众号【编程指南】,更多Java学习资料见【编程指南】
请登录后发表评论
注册
社交帐号登录