(1)分类
根据在类中定义位置的不同,变量分为成员变量和局部变量;
成员变量,又字段field(不要称为属性),直接定义在类中,各方法外,包括类成员变量(使用static修饰的字段)、实例成员变量;
局部变量,除了成员变量,其他都是局部变量,包括方法内部的变量、方法的形参、代码块中的变量(一对花括号{}称为代码块);
(2)初始值
成员变量,默认有初始值,各数据类型的初始值如下
局部变量,没有初始值,必须先初始化才能使用(初始化才会在内存中开辟空间);
(1)作用域
作用域,指变量的存在范围,只有在该范围内程序代码才能访问它;
当一个变量定义时,其作用域就确定了;变量根据定义的位置不同也决定了作用域不同(看变量所在的那对{});
成员变量,在整个类中都有效,可先使用再定义(但不建议);
局部变量,在定义的位置开始,到紧跟着结束的花括号为止,必须先定义再使用;
(2)生命周期
变量的作用域决定变量的生命周期,说明作用域不同,生命周期不同;
生命周期,指一个变量被创建并分配内存空间开始,到该变量被销毁并清除所占内存空间的过程;
存在位置生命周期开始生命周期结束在内存中的位置类变量使用static修饰的字段所在类的字节码加载到JVMJVM停止工作方法区实例变量没有使用static修饰的字段创建所在类的对象该对象被GC回收堆局部变量方法内部、方法形参或代码块初始化变量所在方法/代码块结束当前方法所在栈帧
局部变量定义后,必须显式初始化才能使用,因为系统不会为局部变量执行初始化操作,即定义局部变量后,系统并未为该变量分配内存空间;
直到程序为该变量赋值时,系统才为该局部变量分配内存,并将初始值保存到该内存中;
局部变量不属于任何类或实例,因为其总是保存在所在方法的栈内存中;
基本数据类型的局部变量,直接把该变量的值保存到该变量所对应的内存中;
引用数据类型的局部变量,内存存储地址,通过该地址值引用到该变量实际引用堆中对象;
栈内存中变量无需系统垃圾回收,随方法或代码块的运行结束而结束;
(1)定义
<数据类型> <变量名>; <变量名> = <初始值>; // 或 <数据类型> <变量名> = <初始值>;
(2)选择
a 考虑因素
考虑变量的生存时间,这会影响内存开销;
扩大变量作用域,不利于提高程序的高内聚;
b 使用成员变量和局部变量的情形
实际开发中,尽量缩小变量的作用域,因此在内存中停留时间越短,性能越高;
不要轻易使用static修饰,除了定义工具方法或变量属于类;
不要轻易使用实例变量,因为存在线程不安全的问题;
尽量使用局部变量;
(1)概念
在开发中,存在成千上百的Java文件,若所有Java文件都在一个目录中,管理繁琐,可使用生活中的解决方案 —— 分类,但在Java中将文件夹称为包package;
专门为当前Java文件设置包名;
(2)语法格式
package <包名>.<子包名>.<子子包名>; // 表示在<包名>文件夹下的<子包名>文件夹下的<子子包名>文件夹下必须将该语句作为Java文件中第一行代码(所有代码之前);
(3)使用
a 编译命令为 javac -d . <类名>.java;
若当前Java文件中没有使用package语句,表示在当前文件夹下生成字节码文件;
若使用package语句,表示在当前文件夹下先生成包名,再在包中生成字节码文件;
b 运行命令为java <包名>.<类名>;
(4)package最佳实践
a 包名定义准则
(a) 包名必须遵循标识符规范,且全部小写;
(b) 企业开发中,包名采用公司域名倒写,格式为package <域名倒写>.<模块名>.<组件名>;
(如爱立信域名ericsson.com,则包名为com.ericsson.pss.util,用于进销存模块中专门存储工具类的包);
注:在Android中,若package中使用了_,则无法部署到模拟器中,可使用一个字母代替_;
(c) 自定义包名不能以java开头,因为Java的安全机制会检查;
b 类的名称
类的简单名称 —— 定义类的名称
类的全限定名称 —— 包名.类名
如下,类的简单名称是PackageDemo,类的全限定名称是com.sissiy.hello.PackageDemo;
package com.sissiy.hello; public class PackageDemo{ public static void main(String[] args){ System.our.println("Helo"); } }不同的包可以有相同的类名;
c 在开发中,都是先有package,再在package中定义类;
(5)JDK中的包名
当A类和B类不在同一个包中,若A类需要使用B类,此时A类必须引入B类;
如数组的工具类Arrays类在java.util包中,Arrays类的全限定名为java.util.Arrays;
没有使用import语句,操作不在同一个包中的类需要使用全限定名;
class ImportDemo{ public static void main(String[] args){ int[] arr = {1,2,3,4,5,6,7}; String ret = java.util.Arrays.toString(); System.out.println(ret); // 打印数组 } }
使用import语句,直接把某个包中的类导入到当前类中,在该Java文件中可直接使用类的简单名称;
语法格式
import <全限定名>; // 只能导入一个类则上例更新为如下代码:
import java.util.Arrays; class ImportDemo{ public static void main(String[] args){ int[] arr = {1,2,3,4,5,6,7}; String ret = Arrays.toString(); System.out.println(ret); // 打印数组 } }注:
编译器默认寻找java.lang包下的类,如String,因此不需导入,但不会导入java.lang的子包中的类,此时还需使用import语句;
非java.lang的包都需要导入;
若同时使用同一个包中的多个类,可使用*表示引入在当前文件中使用到的包中的所有类,语法格式如下:
import <包名>.<子包名>.*;在Eclipse工具中,即使使用通配符*,在格式化代码时也会转换为N条import语句;
如下代码,即使使用import语句,每次都要使用Arrays类名去调用静态方法;
import java.util.Arrays; class StaticImportDemo{ public static void main(String[] args){ int[] arr = {1,2,3,4,5,6,7}; String str = Arrays.toString(); System.out.println(str); Arrays.sort(arr); str = Arrays.toString(); System.out.println(str); } }
把Arrays类中的静态成员作为当前Java文件的静态成员,可使用静态导入功能;
语法格式
import static <类的全限定名>.<该类中的static成员名>; import static <类的全限定名>.*; // *表示当前类的任意使用到的静态成员
上例代码更新如下
import java.util.Arrays; // 把java.util.Arrays类中的静态方法sort导入到StaticImportDemo类中 import static java.util.Arrays.sort; class StaticImportDemo{ public static void main(String[] args){ int[] arr = {1,2,3,4,5,6,7}; // 【注意】无法使用静态引入,因为Object类中也有toString方法,会造成指代不明错误,因此必须使用类名调用toString方法 String str = Arrays.toString(); System.out.println(str); sort(arr); str = Arrays.toString(); System.out.println(str); } }
注:
通过反编译工具,所谓的静态导入也是语法糖 —— 编译器的新特性;
在实际开发中不适用静态导入,因为分不清某一个静态方法/字段来源于哪个类;
Eclipse工具在格式化代码时取消所有静态导入,变成使用类名调用;
(1)概念
把对象的状态和行为看成一个统一的整体,将二者存放在一个独立的模块中(类);
“信息隐藏” 把不需要让外界知道的信息隐藏起来,尽可能隐藏对象功能实现细节,向外暴露方法,保证外界安全访问功能;
把所有字段使用private私有化,不准外界访问;把方法使用public修饰,允许外界访问;
(2)好处
使调用者正确方便地使用系统功能,防止调用者随意修改系统属性;
提高组件的重用性;
达到组件之间的低耦合性(当某个模块发生变化时,只要对外暴露的接口不变,便不影响其他模块);
如使用封装思想设置一个人的年龄,代码实现如下
class Person{ private String name; private int age; public void setAge(int age){ if(age < 0){ System.our.println("年龄不能为负数"); return; } this.age = age; } } public class PersonDemo{ public static void main(String[] args){ Person p = new Person(); p.setAge(18); } }
访问权限修饰符针对Java中封装思想,规定在一个类中能看到什么,不能看到什么;
访问权限控制有4种,访问权限修饰符有3种;
(1)private —— 私有的,表示类访问权限,即只能在本类中访问,离开本类就不能直接访问;
(2)缺省 —— 包私有,表示包访问权限,即访问者的包必须和当前定义类所在的包相同才可访问(子包也不行);
(3)protected —— 子类访问权限,表示同包可以访问,即使不同包,只要有继承关系,也可以访问;
(4)public —— 公共的,表示在当前项目中任何地方对其访问;
注:
一般,字段都使用private,为安全性而隐藏;
一般,拥有实现细节的方法使用private,不希望外界(调用者)看到该方法的实现细节;方法使用public,供外界直接调用;
// 直接暴露给外界,供调用者直接调用 public void doWork(){ methodA(); methodB(); methodC(); } // 仅仅完成部分操作,不需要调用者调用 private methodA(){} private methodB(){} private methodC(){}一般,不使用缺省,即使要使用,也只是暴露给同包中的其他类;
一般,在继承关系中,父类需要把一个方法只暴露给子类,才使用protected;
(1)概念
JavaBean是一种Java语言编写的可重用性组件(类);
(2)必须遵循的规范
类必须使用public修饰;
必须保证有公共无参数构造器,即使手动提供带参数的构造器,也得提供无参数的构造器;
包含属性的操作手段(给属性赋值,获取属性值);
(3)分类
复杂:UI,如Button、Panel、Window类
简单:domain、dao、service组件,封装数据,操作数据库,逻辑运算等
(封装有字段,并提供getter/setter)
(4)成员
方法Method
事件event
属性Property
注:属性
attribute:表示状态,Java没有该概念,很多人把字段filed称为属性attribute,不要把成员变量称为属性;
property:表示状态,但不是字段,是属性的操作方法(getter/setter)决定,框架中使用的大多是属性;
其中,getter方法只获取某一字段存储的值;
若操作的字段是boolean类型,则不应称getter方法,而是is方法;
public <数据类型> get<首字母大写的字段名>(){ return <字段名>; }setter方法只设置某一字段的值;
public void set<首字母大写的字段名>(<数据类型> <形参名>){ <字段名> = <形参名>; }每个字段都要提供一对getter/setter,以后使用Eclipse工具后getter/setter自动生成;
在JavaBean中有属性的概念,只有标准情况下,字段名和属性名相同;
(1)概念
this表示当前对象;
(2)this存在于两个位置
构造器:表示当前创建的对象;
方法:哪个对象调用this所在的方法,this表示哪个对象;
如下对含有this的代码进行内存分析
注:
当一个对象创建后,JVM会分配一个引用自身的引用this;
(3)this使用
a 解决成员变量和参数(局部变量)之间的二义性,必须使用this;
b 同类中实例方法间互相调用,可省略this,但不建议;
c 将当前对象作为参数传递给另一个方法;
d 将this作为方法的返回值(链式方法编程);
e 构造器重载时的互相调用,this(<实参>);必须写在构造器方法的第一行;
构造器中只能调用一个重载的构造器;
如下代码,语法没有错,但存在代码功能重复、代码维护性差的问题;
class User{ private String name; privte int age; User(){ } User(String name){ this.name = name; } User(String name, int age){ this.name = name; this.age = age; } }
优化代码如下,this(name)表示调用参数为String类型的构造器;
class User{ private String name; privte int age; User(){ } User(String name){ this(); this.name = name; } User(String name, int age){ this(name); // 修改处,调用User(Stirng name) this.age = age; } }
当多个构造器重载时,或多个方法重载时,一般少参数的调用多参数的,因为参数越多,该方法考虑的未知因素越多,即功能更强大;
class User{ private String name; privte int age; User(){ this(null, 0); } User(String name){ this(name, 0); } User(String name, int age){ this.name = name; this.age = age; } }
f static不能和this一起使用,因为字节码加载进JVM,static成员便存在,但此时对象还未创建,没有对象就没有this;
(1)创建对象并给对象设置初始值的方式
a 先通过无参数构造器创建一个对象,再通过对象调用相应的setter方法;
User u1 = new User(); u1.setName("Sissiy"); u1.setAge(18);
b 直接调用带参数的构造器,创建出的对象就有初始值;
User u1 = new User("Sissiy", 18);
通过构造器和通过setter方法都可完成相同功能;
(2)给对象设置数据的方式
a setter注入(属性注入)
b 构造注入
(3)方式选择
若存在带参数的构造器,推荐构造方式;
若在构建对象时需初始化多个数据,选择构造方式,构造器需要提供多个参数,参数过多不直观,推荐setter方式;
有时,需要根据数据构建对象,推荐构造方式;
如圆对象,必须根据半径确定对象,应该在构建圆对象时确定半径值;
判断点和圆的关系,是圆外?圆中?圆上?
class Point{ private double x; private double y; Point(double x, double y){ this.x = x; this.y = y; } public double getX(){ return x; } public double getY(){ return y; } } class Circle{ private double rad; private double x; private double y; Circle(){} Circle(double rad, double x, double y){ this.rad = rad; this.x = x; this.y = y; } /* 这是我的思路 public double getDis(double x1, double y1){ return (x1-x) * (x1-x) + (y-y1) * (y-y1); } */ // 老师的思路 public double getDis(Point p){ return p.getX() * p.getX() + p.getY() * p.getY(); } public int judge(double dis){ if(dis * dis == rad){ return 0; // 圆上 }else if(dis > rad){ return 1; // 圆外 }else{ return 2; // 圆内 } } } public class PointDemo{ public static void main(String[] args){ Point p1 = new Point(3, 4); Circle c1 = new Circle(1, 0, 0); // double dis = c1.getDis(p1.getX, p1.getY); double dis = c1.getDis(p1); switch(c1.judge(dis)){ case 0: System.out.println("圆上"); break; case 1: System.out.println("圆外"); break; case 2: System.out.println("圆内"); break; } } }