1. 关键字:static 回顾类中的实例变量(即非static的成员变量)
class Circle {
private double radius;
public Circle ( double radius) {
this . radius= radius;
}
public double findArea ( ) {
return Math . PI * radius* radius;
}
}
创建两个Circle对象:
Circle c1= new Circle ( 2.0 ) ;
Circle c2= new Circle ( 3.0 ) ;
Circle类中的变量radius是一个实例变量(instance variable),它属于类的每一个对象,c1中的radius变化不会影响c2的radius,反之亦然。
如果想让一个成员变量被类的所有实例所共享,就用static修饰即可,称为类变量(或类属性)!
1.1 类属性、类方法的设计思想 当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份
。例如,所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
此外,==在类中声明的实例方法,在类的外面必须要先创建对象,才能调用。但是有些方法的调用者和当前类的对象无关,这样的方法通常被声明为类方法
,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。==
这里的类变量、类方法,只需要使用static
修饰即可。所以也称为静态变量、静态方法。
1.2 static关键字
使用范围:
在Java类中,可用static修饰属性、方法、代码块、内部类
被修饰后的成员具备以下特点:
随着类的加载而加载
优先于对象存在
修饰的成员,被类内的所有对象所共享
访问权限允许时,可不创建对象,直接被类调用
1.3 静态变量 1.3.1 语法格式 使用static修饰的成员变量就是静态变量(或类变量、类属性)
[ 修饰符] class 类{
[ 其他修饰符] static 数据类型 变量名;
}
1.3.2 静态变量的特点
静态变量的默认值规则和实例变量一样。
静态变量值是所有对象共享。
静态变量在本类中,可以在任意方法、代码块、构造器中直接使用。
如果权限修饰符允许,在其他类中可以通过“类名.静态变量
”直接访问,也可以通过“对象.静态变量
”的方式访问(但是更推荐使用类名.静态变量的方式)。
静态变量的get/set方法也静态的,当局部变量与静态变量重名时
,使用“类名.静态变量
”进行区分。
1.3.3静态变量和实例变量的区别
静态变量
实例变量
个数
在内存中只有一份,被类的多个对象所共享
类的每一个实例对象都保存一份实例变量
内存位置
JDK6之前存放在方法区。JDK7之后存放在堆内存空间
存放在堆内存当前的对象实体中
加载时机
随着类的加载而加载,只加载一次
随着对象的加载而加载
调用者
可以被类调用,也可以被对象调用
只能被对象调用
消亡时机
随着类的消亡而消亡
随着对象的消亡而消亡
1.3.4 举例 举例1:
class Chinese {
String name;
int age;
static String nation;
public Chinese ( ) {
}
public Chinese ( String name, int age) {
this . name = name;
this . age = age;
}
@Override
public String toString ( ) {
return "Chinese{" +
"name='" + name + '\'' +
", age=" + age +
", nation='" + nation + '\'' +
'}' ;
}
}
public class StaticTest {
public static void main ( String [ ] args) {
Chinese c1 = new Chinese ( "康师傅" , 36 ) ;
c1. nation = "中华人民共和国" ;
Chinese c2 = new Chinese ( "老干妈" , 66 ) ;
System . out. println ( c1) ;
System . out. println ( c2) ;
System . out. println ( Chinese . nation) ;
}
}
对应的内存结构:(以经典的JDK6内存解析为例,此时静态变量存储在方法区)
举例2:
package com. atguigu. keyword ;
public class Employee {
private static int total;
static String company;
private int id;
private String name;
public Employee ( ) {
total++ ;
id = total;
}
public Employee ( String name) {
this ( ) ;
this . name = name;
}
public void setId ( int id) {
this . id = id;
}
public int getId ( ) {
return id;
}
public String getName ( ) {
return name;
}
public void setName ( String name) {
this . name = name;
}
public static int getTotal ( ) {
return total;
}
public static void setTotal ( int total) {
Employee . total = total;
}
@Override
public String toString ( ) {
return "Employee{company = " + company + ",id = " + id + " ,name=" + name + "}" ;
}
}
package com. atguigu. keyword ;
public class TestStaticVariable {
public static void main ( String [ ] args) {
System . out. println ( "Employee.total = " + Employee . getTotal ( ) ) ;
Employee e1 = new Employee ( "张三" ) ;
Employee e2 = new Employee ( "李四" ) ;
System . out. println ( e1) ;
System . out. println ( e2) ;
System . out. println ( "Employee.total = " + Employee . getTotal ( ) ) ;
Employee . company = "尚硅谷" ;
System . out. println ( e1) ;
System . out. println ( e2) ;
e1. company = "超级尚硅谷" ;
System . out. println ( e1) ;
System . out. println ( e2) ;
}
}
1.3.4 内存解析
1.4 静态方法 1.4.1 语法格式 用static修饰的成员方法就是静态方法。
[ 修饰符] class 类{
[ 其他修饰符] static 返回值类型 方法名( 形参列表) {
方法体
}
}
1.4.2 静态方法的特点
静态方法在本类的任意方法、代码块、构造器中都可以直接被调用。
只要权限修饰符允许,静态方法在其他类中可以通过“类名.静态方法“的方式调用。也可以通过”对象.静态方法“的方式调用(但是更推荐使用类名.静态方法的方式)。
在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。
静态方法可以被子类继承,但不能被子类重写。
静态方法的调用都只看编译时类型。
因为不需要实例就可以访问static方法,因此static方法内部不能有this,也不能有super。如果有重名问题,使用“类名.”进行区别。
1.4.3静态属性和静态方法的使用场景
静态属性:
==判断当前类的多个对象实例是否共享一个成员变量,且此成员变量的值在多个对象实例都是相同的。==
实际开发中,常将常量设置为静态的
静态方法:
==如果一个方法中操作的是静态变量,则该方法也应该是静态的==
实际开发中,常将工具类中的方法设置为静态的。
1.4.3 举例 package com. atguigu. keyword ;
public class Father {
public static void method ( ) {
System . out. println ( "Father.method" ) ;
}
public static void fun ( ) {
System . out. println ( "Father.fun" ) ;
}
}
package com. atguigu. keyword ;
public class Son extends Father {
public static void fun ( ) {
System . out. println ( "Son.fun" ) ;
}
}
package com. atguigu. keyword ;
public class TestStaticMethod {
public static void main ( String [ ] args) {
Father . method ( ) ;
Son . method ( ) ;
Father f = new Son ( ) ;
f. method ( ) ;
}
}
1.5 练习 笔试题:如下程序执行会不会报错
public class StaticTest {
public static void main ( String [ ] args) {
Demo test = null ;
test. hello ( ) ;
}
}
class Demo {
public static void hello ( ) {
System . out. println ( "hello!" ) ;
}
}
因为静态方法不随着对象的加载而加载,所以正常输出hello!
练习:
编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的方法。账号要自动生成。
编写主类,使用银行账户类,输入、输出3个储户的上述信息。
考虑:哪些属性可以设计成static属性。
2. 单例(Singleton)设计模式 2.1 设计模式概述 设计模式 是在大量的实践中总结
和理论化
之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。”套路”
经典的设计模式共有23种。每个设计模式均是特定环境下特定问题的处理方法。
简单工厂模式并不是23中经典模式的一种,是其中工厂方法模式的简化版
对软件设计模式的研究造就了一本可能是面向对象设计方面最有影响的书籍:《设计模式》:《Design Patterns: Elements of Reusable Object-Oriented Software》(即后述《设计模式》一书),由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著(Addison-Wesley,1995)。这几位作者常被称为”四人组(Gang of Four)”,而这本书也就被称为”四人组(或 GoF)”书。
2.2 何为单例模式 ==所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例 ,并且该类只提供一个取得其对象实例的方法。==
2.3 实现思路 如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private
,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法
以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的
。
2.4 单例模式的两种实现方式 2.4.1 饿汉式 class Singleton {
private Singleton ( ) {
}
private static Singleton single = new Singleton ( ) ;
public static Singleton getInstance ( ) {
return single;
}
}
2.4.2 懒汉式 class Singleton {
private Singleton ( ) {
}
private static Singleton single = null ;
public static Singleton getInstance ( ) {
if ( single == null ) {
single = new Singleton ( ) ;
}
return single;
}
}
2.4.3 饿汉式 vs 懒汉式 饿汉式:
特点:立即加载
,即在使用类的时候已经将对象创建完毕。
优点:实现起来简单
;没有多线程安全问题。
缺点:当类被加载的时候,会初始化static的实例,静态变量被创建并分配内存空间,从这以后,这个static的实例便一直占着这块内存,直到类被卸载时,静态变量被摧毁,并释放所占有的内存。因此在某些特定条件下会耗费内存
。
懒汉式:
特点:延迟加载
,即在调用静态方法时实例才被创建。
优点:实现起来比较简单;当类被加载的时候,static的实例未被创建并分配内存空间,当静态方法第一次被调用时,初始化实例变量,并分配内存,因此在某些特定条件下会节约内存
。
缺点:在多线程环境中,这种实现方法是完全错误的,线程不安全,可能会出现线程安全问题
,根本不能保证单例的唯一性。
说明:在多线程章节,会将懒汉式改造成线程安全的模式。
2.5 单例模式的优点及应用场景 由于单例模式只生成一个实例,减少了系统性能开销
,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
举例:
应用场景
Windows的Task Manager (任务管理器)就是很典型的单例模式
Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
Application 也是单例的典型应用
应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只
能有一个实例去操作,否则内容不好追加。
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
3. 理解main方法的语法 由于JVM需要调用类的main()方法,所以该方法的访问权限必须是public,又因为JVM在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到。
命令行参数用法举例
public class CommandPara {
public static void main ( String [ ] args) {
for ( int i = 0 ; i < args. length; i++ ) {
System . out. println ( "args[" + i + "] = " + args[ i] ) ;
}
}
}
java CommandPara "Tom" "Jerry" "Shkstart"
args[ 0 ] = Tom
args[ 1 ] = Jerry
args[ 2 ] = Shkstart
IDEA工具:
(1)配置运行参数
(2)运行程序
笔试题:
class Something {
public static void main ( String [ ] something_to_do) {
System . out. println ( "Do something ..." ) ;
}
}
4. 类的成员之四:代码块 4.1 静态代码块 ==如果成员变量想要初始化的值不是一个硬编码的常量值,而是需要通过复杂的计算或读取文件、或读取运行环境信息等方式才能获取的一些值,该怎么办呢?此时,可以考虑代码块(或初始化块)。==
代码块(或初始化块)的作用
:
初始化类的信息
如果想要为静态变量初始化,可以直接在静态变量的声明后面直接赋值,也可以使用静态代码块。
代码块(或初始化块)的分类
:
静态代码块随着类的加载而执行
4.1.1 语法格式 在代码块的前面加static,就是静态代码块。
【修饰符】 class 类{
static {
静态代码块
}
}
4.1.2 静态代码块的特点
可以有输出语句。
可以对类的属性、类的声明进行初始化操作。
不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
若有多个静态的代码块,那么按照从上到下的顺序依次执行。
静态代码块的执行要先于非静态代码块。
静态代码块随着类的加载而加载,且只执行一次。
package com. atguigu. keyword ;
public class Chinese {
private static String country;
private String name;
{
System . out. println ( "非静态代码块,country = " + country) ;
}
static {
country = "中国" ;
System . out. println ( "静态代码块" ) ;
}
public Chinese ( String name) {
this . name = name;
}
}
package com. atguigu. keyword ;
public class TestStaticBlock {
public static void main ( String [ ] args) {
Chinese c1 = new Chinese ( "张三" ) ;
Chinese c2 = new Chinese ( "李四" ) ;
}
}
4.2 非静态代码块 4.2.1 语法格式 【修饰符】 class 类{
{
非静态代码块
}
【修饰符】 构造器名( ) {
}
【修饰符】 构造器名( 参数列表) {
}
}
4.2.2 非静态代码块的作用 和构造器一样,也是用于实例变量的初始化等操作。
4.2.3 非静态代码块的意义 如果多个重载的构造器有公共代码,并且这些代码都是先于构造器其他代码执行的,那么可以将这部分代码抽取到非静态代码块中,减少冗余代码。
4.2.4 非静态代码块的执行特点
可以有输出语句。
可以对类的属性、类的声明进行初始化操作。
除了调用非静态的结构外,还可以调用静态的变量或方法。
若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
每次创建对象的时候,都会执行一次。且先于构造器执行。
4.3 举例 举例1:
(1)声明User类,
包含属性:username(String类型),password(String类型),registrationTime(long类型),私有化
包含get/set方法,其中registrationTime没有set方法
包含无参构造,
输出“新用户注册”,
registrationTime赋值为当前系统时间,
username就默认为当前系统时间值,
password默认为“123456”
包含有参构造(String username, String password),
输出“新用户注册”,
registrationTime赋值为当前系统时间,
username和password由参数赋值
包含public String getInfo()方法,返回:“用户名:xx,密码:xx,注册时间:xx”
(2)编写测试类,测试类main方法的代码如下:
public static void main ( String [ ] args) {
User u1 = new User ( ) ;
System . out. println ( u1. getInfo ( ) ) ;
User u2 = new User ( "song" , "8888" ) ;
System . out. println ( u2. getInfo ( ) ) ;
}
如果不用非静态代码块,User类是这样的:
package com. atguigu. block. no ;
public class User {
private String username;
private String password;
private long registrationTime;
public User ( ) {
System . out. println ( "新用户注册" ) ;
registrationTime = System . currentTimeMillis ( ) ;
username = registrationTime+ "" ;
password = "123456" ;
}
public User ( String username, String password) {
System . out. println ( "新用户注册" ) ;
registrationTime = System . currentTimeMillis ( ) ;
this . username = username;
this . password = password;
}
public String getUsername ( ) {
return username;
}
public void setUsername ( String username) {
this . username = username;
}
public String getPassword ( ) {
return password;
}
public void setPassword ( String password) {
this . password = password;
}
public long getRegistrationTime ( ) {
return registrationTime;
}
public String getInfo ( ) {
return "用户名:" + username + ",密码:" + password + ",注册时间:" + registrationTime;
}
}
如果提取构造器公共代码到非静态代码块,User类是这样的:
package com. atguigu. block. use ;
public class User {
private String username;
private String password;
private long registrationTime;
{
System . out. println ( "新用户注册" ) ;
registrationTime = System . currentTimeMillis ( ) ;
}
public User ( ) {
username = registrationTime+ "" ;
password = "123456" ;
}
public User ( String username, String password) {
this . username = username;
this . password = password;
}
public String getUsername ( ) {
return username;
}
public void setUsername ( String username) {
this . username = username;
}
public String getPassword ( ) {
return password;
}
public void setPassword ( String password) {
this . password = password;
}
public long getRegistrationTime ( ) {
return registrationTime;
}
public String getInfo ( ) {
return "用户名:" + username + ",密码:" + password + ",注册时间:" + registrationTime;
}
}
举例2:
private static DataSource dataSource = null ;
static {
InputStream is = null ;
try {
is = DBCPTest . class . getClassLoader ( ) . getResourceAsStream ( "dbcp.properties" ) ;
Properties pros = new Properties ( ) ;
pros. load ( is) ;
dataSource = BasicDataSourceFactory . createDataSource ( pros) ;
} catch ( Exception e) {
e. printStackTrace ( ) ;
} finally {
if ( is != null ) {
try {
is. close ( ) ;
} catch ( IOException e) {
e. printStackTrace ( ) ;
}
}
}
}
4.4 小结:实例变量赋值顺序
4.5 练习 练习1:分析加载顺序
子父类的执行顺序:
父类静态变量和静态初始化块,按在代码中出现的顺序依次执行。
子类静态变量和静态初始化块,按在代码中出现的顺序依次执行。
父类实例变量和实例初始化块,按在代码中出现的顺序依次执行。
执行父类构造方法。
子类实例变量员和实例初始化块,按在代码中出现的顺序依次执行。
执行子类构造方法。
class Root {
static {
System . out. println ( "Root的静态初始化块" ) ;
}
{
System . out. println ( "Root的普通初始化块" ) ;
}
public Root ( ) {
System . out. println ( "Root的无参数的构造器" ) ;
}
}
class Mid extends Root {
static {
System . out. println ( "Mid的静态初始化块" ) ;
}
{
System . out. println ( "Mid的普通初始化块" ) ;
}
public Mid ( ) {
System . out. println ( "Mid的无参数的构造器" ) ;
}
public Mid ( String msg) {
this ( ) ;
System . out. println ( "Mid的带参数构造器,其参数值:"
+ msg) ;
}
}
class Leaf extends Mid {
static {
System . out. println ( "Leaf的静态初始化块" ) ;
}
{
System . out. println ( "Leaf的普通初始化块" ) ;
}
public Leaf ( ) {
super ( "尚硅谷" ) ;
System . out. println ( "Leaf的构造器" ) ;
}
}
public class LeafTest {
public static void main ( String [ ] args) {
new Leaf ( ) ;
}
}
练习2:分析加载顺序
class Father {
static {
System . out. println ( "11111111111" ) ;
}
{
System . out. println ( "22222222222" ) ;
}
public Father ( ) {
System . out. println ( "33333333333" ) ;
}
}
public class Son extends Father {
static {
System . out. println ( "44444444444" ) ;
}
{
System . out. println ( "55555555555" ) ;
}
public Son ( ) {
System . out. println ( "66666666666" ) ;
}
public static void main ( String [ ] args) {
System . out. println ( "77777777777" ) ;
System . out. println ( "************************" ) ;
new Son ( ) ;
System . out. println ( "************************" ) ;
new Son ( ) ;
System . out. println ( "************************" ) ;
new Father ( ) ;
}
}
练习3:
package com. atguigu05. field. interview ;
public class Test04 {
public static void main ( String [ ] args) {
Zi zi = new Zi ( ) ;
}
}
class Fu {
private static int i = getNum ( "(1)i" ) ;
private int j = getNum ( "(2)j" ) ;
static {
print ( "(3)父类静态代码块" ) ;
}
{
print ( "(4)父类非静态代码块,又称为构造代码块" ) ;
}
Fu ( ) {
print ( "(5)父类构造器" ) ;
}
public static void print ( String str) {
System . out. println ( str + "->" + i) ;
}
public static int getNum ( String str) {
print ( str) ;
return ++ i;
}
}
class Zi extends Fu {
private static int k = getNum ( "(6)k" ) ;
private int h = getNum ( "(7)h" ) ;
static {
print ( "(8)子类静态代码块" ) ;
}
{
print ( "(9)子类非静态代码块,又称为构造代码块" ) ;
}
Zi ( ) {
print ( "(10)子类构造器" ) ;
}
public static void print ( String str) {
System . out. println ( str + "->" + k) ;
}
public static int getNum ( String str) {
print ( str) ;
return ++ k;
}
}
5. final关键字 5.1 final的意义 final:最终的,不可更改的
5.2 final的使用 5.2.1 final修饰类 表示这个类不能被继承,没有子类。内存地址不能改变。提高安全性,提高程序的可读性。
例如:String类、System类、StringBuffer类
final class Eunuch {
}
class Son extends Eunuch {
}
5.2.2 final修饰方法 表示这个方法不能被子类重写。
例如:Object类中的getClass()
class Father {
public final void method ( ) {
System . out. println ( "father" ) ;
}
}
class Son extends Father {
public void method ( ) {
System . out. println ( "son" ) ;
}
}
5.2.3 final修饰变量 final修饰某个变量(成员变量或局部变量),一旦赋值,它的值就不能被修改,即常量 。常量名建议使用大写字母。
例如:final double MY_PI = 3.14;
如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)
public final class Test {
public static int totalNumber = 5 ;
public final int ID ;
public Test ( ) {
ID = ++ totalNumber;
}
public static void main ( String [ ] args) {
Test t = new Test ( ) ;
System . out. println ( t. ID ) ;
}
}
public class TestFinal {
public static void main ( String [ ] args) {
final int MIN_SCORE ;
MIN_SCORE = 0 ;
final int MAX_SCORE = 100 ;
MAX_SCORE = 200 ;
}
}
class A {
private final String INFO = "atguigu" ;
public void print ( ) {
}
}
5.3 笔试题 题1:排错
public class Something {
public int addOne ( final int x) {
return ++ x;
}
}
题2:排错
public class Something {
public static void main ( String [ ] args) {
Other o = new Other ( ) ;
new Something ( ) . addOne ( o) ;
}
public void addOne ( final Other o) {
o. i++ ;
}
}
class Other {
public int i;
}
6. 抽象类与抽象方法(或abstract关键字) 6.1 由来 举例1:
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。==有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。==
举例2:
我们声明一些几何图形类:圆、矩形、三角形类等,发现这些类都有共同特征:求面积、求周长。那么这些共同特征应该抽取到一个共同父类:几何图形类中。但是这些方法在父类中又无法给出具体的实现
,而是应该交给子类各自具体实现。那么父类在声明这些方法时,就只有方法签名,没有方法体
,我们把没有方法体的方法称为抽象方法 。Java语法规定,包含抽象方法的类必须是抽象类 。
6.2 语法格式
抽象类 :被abstract修饰的类。
抽象方法 :被abstract修饰没有方法体的方法。
抽象类的语法格式
[ 权限修饰符] abstract class 类名{
}
[ 权限修饰符] abstract class 类名 extends 父类{
}
抽象方法的语法格式
[ 其他修饰符] abstract 返回值类型 方法名( [ 形参列表] ) ;
注意:抽象方法没有方法体
代码举例:
public abstract class Animal {
public abstract void eat ( ) ;
}
public class Cat extends Animal {
public void eat ( ) {
System . out. println ( "小猫吃鱼和猫粮" ) ;
}
}
public class CatTest {
public static void main ( String [ ] args) {
Cat c = new Cat ( ) ;
c. eat ( ) ;
}
}
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法 。
6.3 使用说明
抽象类不能创建对象 ,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。
理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法。
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
抽象类的子类,必须重写抽象父类中所有的 抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
6.4 注意事项
abstract只能修饰类和方法,即抽象类的抽象方法。
6.5 应用举例1
在航运公司系统中,Vehicle类需要定义两个方法分别计算运输工具的燃料效率
和行驶距离
。
问题: 卡车(Truck)和驳船(RiverBarge)的燃料效率和行驶距离的计算方法完全不同。Vehicle类不能提供计算方法,但子类可以。
解决方案: Java允许类设计者指定:超类声明一个方法但不提供实现,该方法的实现由子类提供。这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽象类。
public abstract class Vehicle {
public abstract double calcFuelEfficiency ( ) ;
public abstract double calcTripDistance ( ) ;
}
public class Truck extends Vehicle {
public double calcFuelEfficiency ( ) {
public double calcTripDistance ( ) {
}
public class RiverBarge extends Vehicle {
public double calcFuelEfficiency ( ) {
public double calcTripDistance ( ) {
}
6.6 应用举例2:模板方法设计模式(TemplateMethod) 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题 :
类比举例: 英语六级模板
制作月饼的模板:
举例1:
abstract class Template {
public final void getTime ( ) {
long start = System . currentTimeMillis ( ) ;
code ( ) ;
long end = System . currentTimeMillis ( ) ;
System . out. println ( "执行时间是:" + ( end - start) ) ;
}
public abstract void code ( ) ;
}
class SubTemplate extends Template {
public void code ( ) {
for ( int i = 0 ; i < 10000 ; i++ ) {
System . out. println ( i) ;
}
}
}
举例2:
package com. atguigu. java ;
public class TemplateMethodTest {
public static void main ( String [ ] args) {
BankTemplateMethod btm = new DrawMoney ( ) ;
btm. process ( ) ;
BankTemplateMethod btm2 = new ManageMoney ( ) ;
btm2. process ( ) ;
}
}
abstract class BankTemplateMethod {
public void takeNumber ( ) {
System . out. println ( "取号排队" ) ;
}
public abstract void transact ( ) ;
public void evaluate ( ) {
System . out. println ( "反馈评分" ) ;
}
public final void process ( ) {
this . takeNumber ( ) ;
this . transact ( ) ;
this . evaluate ( ) ;
}
}
class DrawMoney extends BankTemplateMethod {
public void transact ( ) {
System . out. println ( "我要取款!!!" ) ;
}
}
class ManageMoney extends BankTemplateMethod {
public void transact ( ) {
System . out. println ( "我要理财!我这里有2000万美元!!" ) ;
}
}
模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:
6.7 思考与练习 思考:
问题1:为什么抽象类不可以使用final关键字声明?
问题2:一个抽象类中可以定义构造器吗?
问题3:是否可以这样理解:抽象类就是比普通类多定义了抽象方法,除了不能直接进行类的实例化操作之外,并没有任何的不同?
练习1:
编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary。提供必要的构造器和抽象方法:work()。
对于Manager类来说,他既是员工,还具有奖金(bonus)的属性。
请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问。
练习2:软件外包公司外派管理
有一家软件外包公司,可以外派开发人员,该公司有两个角色:普通开发人员Developer和项目经理Manager。他们的关系如下图:
普通开发人员的工作内容是“开发项目”,项目经理的工作内容是“项目管理”。对外的报价是普通开发人员每天500,元,超过60天每天400元。项目经理每天800元,超过60天每天700元。
有一家银行需要1名项目经理、2名开发人员,现场开发90天,计算银行需要付给软件公司的总金额。
提示:创建数组 Employee[] emps = new Employee[3]。其中存储驻场的3名员工。
练习3:
创建父类Shape,包含绘制形状的抽象方法draw()。
创建Shape的子类Circle和Rectangle,重写draw()方法,绘制圆形和矩形。
绘制多个圆形和矩形。
练习4:
1、声明抽象父类Person,包含抽象方法public abstract void eat(); 2、声明子类中国人Chinese,重写抽象方法,打印用筷子吃饭 3、声明子类美国人American,重写抽象方法,打印用刀叉吃饭 4、声明子类印度人Indian,重写抽象方法,打印用手抓饭 5、声明测试类PersonTest,创建Person数组,存储各国人对象,并遍历数组,调用eat()方法
练习5:工资系统设计
编写工资系统,实现不同类型员工(多态)的按月发放工资。如果当月出现某个Employee对象的生日,则将该雇员的工资增加100元。
实验说明:
(1)定义一个Employee类,该类包含:
private成员变量name,number,birthday,其中birthday 为MyDate类的对象;
abstract方法earnings();
toString()方法输出对象的name,number和birthday。
(2)MyDate类包含:
private成员变量year,month,day ;
toDateString()方法返回日期对应的字符串:xxxx年xx月xx日
(3)定义SalariedEmployee类继承Employee类,实现按月计算工资的员工处理。该类包括:private成员变量monthlySalary;
实现父类的抽象方法earnings(),该方法返回monthlySalary值;toString()方法输出员工类型信息及员工的name,number,birthday。
(4)参照SalariedEmployee类定义HourlyEmployee类,实现按小时计算工资的员工处理。该类包括:
private成员变量wage和hour;
实现父类的抽象方法earnings(),该方法返回wage*hour值;
toString()方法输出员工类型信息及员工的name,number,birthday。
(5)定义PayrollSystem类,创建Employee变量数组并初始化,该数组存放各类雇员对象的引用。利用循环结构遍历数组元素,输出各个对象的类型,name,number,birthday,以及该对象生日。当键盘输入本月月份值时,如果本月是某个Employee对象的生日,还要输出增加工资信息。
c1[ 0 ] = new People ( "John" , "0001" , 20 ) ;
c1[ 1 ] = new People ( "Bob" , "0002" , 19 ) ;
c1[ 0 ] = new Student ( "John" , "0001" , 20 , 85.0 ) ;
c1[ 1 ] = new Officer ( "Bob" , "0002" , 19 , 90.5 ) ;
7. 接口(interface) 7.1 类比 生活中大家每天都在用USB接口,那么USB接口与我们今天要学习的接口有什么相同点呢?
USB,(Universal Serial Bus,通用串行总线)是Intel公司开发的总线架构,使得在计算机上添加串行设备(鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等)非常容易。
其实,不管是电脑上的USB插口,还是其他设备上的USB插口都只是遵循了USB规范
的一种具体设备而已。
只要设备遵循USB规范的,那么就可以与电脑互联,并正常通信。至于这个设备、电脑是哪个厂家制造的,内部是如何实现的,我们都无需关心。
Java的软件系统会有很多模块组成,那么各个模块之间也应该采用这种面向接口
的低耦合
,为系统提供更好的可扩展性和可维护性。
7.2 概述 ==接口就是定义了一组规则的规范。接口定义了一组方法或属性,而不提供实现细节。这使得不同的类或对象可以实现同一个接口,从而实现多态性和代码重用。==
例如:电脑都预留了可以插入USB设备的USB接口,USB接口具备基本的数据传输的开启功能和关闭功能。你能不能用USB进行连接,或是否具备USB通信功能,就看你能否遵循USB接口规范
例如:Java程序是否能够连接使用某种数据库产品,那么要看该数据库产品能否实现Java设计的JDBC规范
接口的本质是契约、标准、规范,就像我们的法律一样。制定好后大家都要遵守。
7.3 定义格式 接口的定义,它与定义类方式相似,但是使用 interface
关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。
引用数据类型:数组,类,枚举,接口,注解,记录。
7.3.1 接口的声明格式 接口的修饰符只能是public
示例代码:
public interface InterfaceDemo {
public static final long MAX_SPEED = 1024 ;
public abstract void in ( ) ;
public default void start ( ) {
System . out. println ( "开jdk1.8新增的默认方法" ) ;
}
public static void show ( ) {
System . out. println ( "jdk1.8新增的静态方法" ) ;
}
}
7.3.2 接口的成员说明 在JDK8.0 之前 ,接口中只允许出现:
(1)公共的静态的常量:其中public static final
可以省略
(2)公共的抽象的方法:其中public abstract
可以省略
理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现
在JDK8.0 时 ,接口中允许声明默认方法
和静态方法
:
(3)公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略
(4)公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略
==在接口中定义的方法默认情况下是抽象的,即没有方法体。但是在 Java 8 中,可以使用默认方法和静态方法来为接口中的方法添加方法体。默认方法使用 default 关键字定义,并提供实现。静态方法使用 static 关键字定义,也可以提供具体的实现。这些方法在实现接口时可以被继承或覆盖,从而提高了接口的灵活性。==
==接口中的静态方法可以不被实现,但这样做没有任何意义。在接口中声明静态方法时,它们的实现是和接口绑定的(随着接口的加载而加载),所有实现该接口的类都可以调用接口中定义的静态方法,但是不能在类中覆盖接口中的静态方法,因为它们是静态的。因此,实现类中可以不去重写这些静态方法。实现接口时需要实现的,是接口中定义的抽象方法。==
在JDK9.0 时 ,接口又增加了:
(5)私有方法
除此之外,接口中没有构造器,没有初始化块,因为接口中没有成员变量需要动态初始化。
7.4 接口的使用规则 1、类实现接口(implements)
接口不能创建对象 ,但是可以被类实现(implements
,类似于被继承)。
类与接口的关系为实现关系,即类实现接口 ,该类可以称为接口的实现类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements
关键字。
【修饰符】 class 实现类 implements 接口{
}
【修饰符】 class 实现类 extends 父类 implements 接口{
}
注意:
如果接口的实现类是非抽象类,那么必须重写接口中所有抽象方法
。
默认方法可以选择保留,也可以重写。
重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
接口中的静态方法不能被继承也不能被重写
举例:
interface USB {
public void start ( ) ;
public void stop ( ) ;
}
class Computer {
public static void show ( USB usb) {
usb. start ( ) ;
System . out. println ( "=========== USB 设备工作 ========" ) ;
usb. stop ( ) ;
}
} ;
class Flash implements USB {
public void start ( ) {
System . out. println ( "U盘开始工作。" ) ;
}
public void stop ( ) {
System . out. println ( "U盘停止工作。" ) ;
}
} ;
class Print implements USB {
public void start ( ) {
System . out. println ( "打印机开始工作。" ) ;
}
public void stop ( ) {
System . out. println ( "打印机停止工作。" ) ;
}
} ;
public class InterfaceDemo {
public static void main ( String args[ ] ) {
Computer . show ( new Flash ( ) ) ;
Computer . show ( new Print ( ) ) ;
c. show ( new USB ( ) {
public void start ( ) {
System . out. println ( "移动硬盘开始运行" ) ;
}
public void stop ( ) {
System . out. println ( "移动硬盘停止运行" ) ;
}
} ) ;
}
} ;
2、接口的多实现(implements)
之前学过,在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现
。并且,一个类能继承一个父类,同时实现多个接口。
实现格式:
【修饰符】 class 实现类 implements 接口1 ,接口2 ,接口3 。。。{
}
【修饰符】 class 实现类 extends 父类 implements 接口1 ,接口2 ,接口3 。。。{
}
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次 。
举例:
定义多个接口:
package com. atguigu. interfacetype ;
public interface A {
void showA ( ) ;
}
package com. atguigu. interfacetype ;
public interface B {
void showB ( ) ;
}
定义实现类:
package com. atguigu. interfacetype ;
public class C implements A , B {
@Override
public void showA ( ) {
System . out. println ( "showA" ) ;
}
@Override
public void showB ( ) {
System . out. println ( "showB" ) ;
}
}
测试类
package com. atguigu. interfacetype ;
public class TestC {
public static void main ( String [ ] args) {
C c = new C ( ) ;
c. showA ( ) ;
c. showB ( ) ;
}
}
3、接口的多继承(extends)
一个接口能继承另一个或者多个接口,接口的继承也使用 extends
关键字,子接口继承父接口的方法。
定义父接口:
package com. atguigu. interfacetype ;
public interface Chargeable {
void charge ( ) ;
void in ( ) ;
void out ( ) ;
}
定义子接口:
package com. atguigu. interfacetype ;
public interface UsbC extends Chargeable , USB3 {
void reverse ( ) ;
}
定义子接口的实现类:
package com. atguigu. interfacetype ;
public class TypeCConverter implements UsbC {
@Override
public void reverse ( ) {
System . out. println ( "正反面都支持" ) ;
}
@Override
public void charge ( ) {
System . out. println ( "可充电" ) ;
}
@Override
public void in ( ) {
System . out. println ( "接收数据" ) ;
}
@Override
public void out ( ) {
System . out. println ( "输出数据" ) ;
}
}
所有父接口的抽象方法都有重写。
方法签名相同的抽象方法只需要实现一次。
4、接口与实现类对象构成多态引用
实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间,也可以构成多态引用 。通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体。
接口的不同实现类:
package com. atguigu. interfacetype ;
public class Mouse implements USB3 {
@Override
public void out ( ) {
System . out. println ( "发送脉冲信号" ) ;
}
@Override
public void in ( ) {
System . out. println ( "不接收信号" ) ;
}
}
package com. atguigu. interfacetype ;
public class KeyBoard implements USB3 {
@Override
public void in ( ) {
System . out. println ( "不接收信号" ) ;
}
@Override
public void out ( ) {
System . out. println ( "发送按键信号" ) ;
}
}
测试类
package com. atguigu. interfacetype ;
public class TestComputer {
public static void main ( String [ ] args) {
Computer computer = new Computer ( ) ;
USB3 usb = new Mouse ( ) ;
computer. setUsb ( usb) ;
usb. start ( ) ;
usb. out ( ) ;
usb. in ( ) ;
usb. stop ( ) ;
System . out. println ( "--------------------------" ) ;
usb = new KeyBoard ( ) ;
computer. setUsb ( usb) ;
usb. start ( ) ;
usb. out ( ) ;
usb. in ( ) ;
usb. stop ( ) ;
System . out. println ( "--------------------------" ) ;
usb = new MobileHDD ( ) ;
computer. setUsb ( usb) ;
usb. start ( ) ;
usb. out ( ) ;
usb. in ( ) ;
usb. stop ( ) ;
}
}
5、使用接口的静态成员
接口不能直接创建对象,但是可以通过接口名直接调用接口的静态方法和静态常量。
package com. atguigu. interfacetype ;
public class TestUSB3 {
public static void main ( String [ ] args) {
USB3 . show ( ) ;
System . out. println ( USB3 . MAX_SPEED ) ;
}
}
6、使用接口的非静态方法
对于接口的静态方法,直接使用“接口名.
”进行调用即可
也只能使用“接口名.”进行调用,不能通过实现类的对象进行调用
对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
package com. atguigu. interfacetype ;
public class TestMobileHDD {
public static void main ( String [ ] args) {
MobileHDD b = new MobileHDD ( ) ;
b. start ( ) ;
b. in ( ) ;
b. stop ( ) ;
Usb3 . show ( ) ;
}
}
7.5 JDK8中相关冲突问题 7.5.1 默认方法冲突问题 (1)类冲突(类优先原则)
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名 ,子类就近选择执行父类的成员方法。 代码如下:
定义接口:
package com. atguigu. interfacetype ;
public interface Friend {
default void date ( ) {
System . out. println ( "吃喝玩乐" ) ;
}
}
定义父类:
package com. atguigu. interfacetype ;
public class Father {
public void date ( ) {
System . out. println ( "爸爸约吃饭" ) ;
}
}
定义子类:
package com. atguigu. interfacetype ;
public class Son extends Father implements Friend {
@Override
public void date ( ) {
System . out. println ( "跟康师傅学Java" ) ;
}
}
定义测试类:
package com. atguigu. interfacetype ;
public class TestSon {
public static void main ( String [ ] args) {
Son s = new Son ( ) ;
s. date ( ) ;
}
}
(2)接口冲突
当一个类同时实现了多个父接口,而多个父接口中包含方法签名相同的默认方法时。默认情况下会报错。 此时就要求实现类必须重写接口中定义的同名同参的默认方法。
声明接口:
声明接口:
public interface Interface1 {
public default void show ( ) {
System . out. println ( "Interface1" ) ;
}
}
public interface Interface2 {
public default void show ( ) {
System . out. println ( "Interface2" ) ;
}
}
选择保留其中一个,通过“接口名.super.方法名
“的方法选择保留哪个接口的默认方法。
public interface InterfaceDemo extends Interface1 , Interface2 {
@Override
default void show ( ) {
Interface2 . super . show ( ) ;
}
}
子接口重写默认方法时,default关键字可以保留。
子类重写默认方法时,default关键字不可以保留。
7.5.2 常量冲突问题
当子类继承父类又实现父接口,而父类中存在与父接口常量同名的成员变量。 会按照类优先原则使用父类的成员变量。但是要使用super关键字来指明调用父类的变量
当子类同时实现多个接口,而多个接口存在相同同名常量。 就需要使用对应的接口来调用对象接口的变量。
父类:
public class FatherClass {
public int a = 0 ;
}
父接口:
public interface Interface1 {
int a = 1 ;
}
public interface Interface2 {
int a = 2 ;
}
子类:
public class InterfaceDemo extends FatherClass implements Interface1 , Interface2 {
public void show ( ) {
System . out. println ( super . a) ;
System . out. println ( Interface1 . a) ;
System . out. println ( Interface2 . a) ;
}
}
7.6 接口的总结与面试题
接口本身不能创建对象,只能创建接口的实现类对象,接口类型的变量可以与实现类对象构成多态引用。
声明接口用interface,接口的成员声明有限制:
(1)公共的静态常量
(2)公共的抽象方法
(3)公共的默认方法(JDK8.0 及以上)
(4)公共的静态方法(JDK8.0 及以上)
(5)私有方法(JDK9.0 及以上)
类可以实现接口,关键字是implements,而且支持多实现。如果实现类不是抽象类,就必须实现接口中所有的抽象方法。如果实现类既要继承父类又要实现父接口,那么继承(extends)在前,实现(implements)在后。
接口可以继承接口,关键字是extends,而且支持多继承。
接口的默认方法可以选择重写或不重写。如果有冲突问题,另行处理。子类重写父接口的默认方法,要去掉default,子接口重写父接口的默认方法,不要去掉default。
面试题
1、为什么接口中只能声明公共的静态的常量?
因为接口是标准规范,那么在规范中需要声明一些底线边界值,当实现者在实现这些规范时,不能去随意修改和触碰这些底线,否则就有“危险”。
例如:USB1.0规范中规定最大传输速率是1.5Mbps,最大输出电流是5V/500mA
USB3.0规范中规定最大传输速率是5Gbps(500MB/s),最大输出电流是5V/900mA
例如:尚硅谷学生行为规范中规定学员,早上8:25之前进班,晚上21:30之后离开等等。
2、为什么JDK8.0 之后允许接口定义静态方法和默认方法呢?因为它违反了接口作为一个抽象标准定义的概念。
静态方法
:因为之前的标准类库设计中,有很多Collection/Colletions或者Path/Paths这样成对的接口和类,后面的类中都是静态方法,而这些静态方法都是为前面的接口服务的,那么这样设计一对API,不如把静态方法直接定义到接口中使用和维护更方便。
默认方法
:(1)我们要在已有的老版接口中提供新方法时,如果添加抽象方法,就会涉及到原来使用这些接口的类就会有问题,那么为了保持与旧版本代码的兼容性,只能允许在接口中定义默认方法实现。比如:Java8中对Collection、List、Comparator等接口提供了丰富的默认方法。(2)当我们接口的某个抽象方法,在很多实现类中的实现代码是一样的,此时将这个抽象方法设计为默认方法更为合适,那么实现类就可以选择重写,也可以选择不重写。
3、为什么JDK1.9要允许接口定义私有方法呢?因为我们说接口是规范,规范是需要公开让大家遵守的。
私有方法 :因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方法由共同的代码可以抽取,而这些共同的代码抽取出来的方法又只希望在接口内部使用,所以就增加了私有方法。
7.7 接口与抽象类之间的对比 抽象类和接口都是用于实现多态性的机制,但它们有一些区别。抽象类可以包含成员变量和非抽象方法的实现,而接口只能包含常量和抽象方法的声明。此外,一个类只能继承一个抽象类,但可以实现多个接口。
在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。
7.8 练习 笔试题: 排错
interface A {
int x = 0 ;
}
class B {
int x = 1 ;
}
class C extends B implements A {
public void pX ( ) {
System . out. println ( x) ;
}
public static void main ( String [ ] args) {
new C ( ) . pX ( ) ;
}
}
笔试题: 排错
interface Playable {
void play ( ) ;
}
interface Bounceable {
void play ( ) ;
}
interface Rollable extends Playable , Bounceable {
Ball ball = new Ball ( "PingPang" ) ;
}
class Ball implements Rollable {
private String name;
public String getName ( ) {
return name;
}
public Ball ( String name) {
this . name = name;
}
public void play ( ) {
ball = new Ball ( "Football" ) ;
System . out. println ( ball. getName ( ) ) ;
}
}
练习1:
定义一个接口用来实现两个对象的比较。
interface CompareObject {
public int compareTo ( Object o) ;
}
定义一个Circle类,声明redius属性,提供getter和setter方法
定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
定义一个测试类InterfaceTest,创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小。
思考:参照上述做法定义矩形类Rectangle和ComparableRectangle类,在ComparableRectangle类中给出compareTo方法的实现,比较两个矩形的面积大小。
练习2:交通工具案例
阿里的一个工程师,声明的属性和方法如下:
其中,有一个乘坐交通工具的方法takingVehicle(),在此方法中调用交通工具的run()。为了出行方便,他买了一辆捷安特自行车、一辆雅迪电动车和一辆奔驰轿车。这里涉及到的相关类及接口关系如下:
其中,电动车增加动力的方式是充电,轿车增加动力的方式是加油。在具体交通工具的run()中调用其所在类的相关属性信息。
请编写相关代码,并测试。
提示:创建Vehicle[]数组,保存阿里工程师的三辆交通工具,并分别在工程师的takingVehicle()中调用。
8. 内部类(InnerClass) 8.1 概述 8.1.1 什么是内部类 将一个类A定义在另一个类B里面,里面的那个类A就称为内部类(InnerClass)
,类B则称为外部类(OuterClass)
。
8.1.2 为什么要声明内部类呢 具体来说,当一个事物A的内部,还有一个部分需要一个完整的结构B进行描述,而这个内部的完整的结构B又只为外部事物A提供服务,不在其他地方单独使用,那么整个内部的完整结构B最好使用内部类。
总的来说,遵循高内聚、低耦合
的面向对象开发原则。
8.1.3 内部类的分类 根据内部类声明的位置(如同变量的分类),我们可以分为:
成员内部类 :==直接声明在外部类里面。==
局部内部类 :==声明在方法内、构造器内或代码块中的内部类==
8.2 成员内部类 8.2.1 概述 如果成员内部类中不使用外部类的非静态成员,那么通常将内部类声明为静态内部类,否则声明为非静态内部类。
语法格式:
[ 修饰符] class 外部类{
[ 其他修饰符] [ static ] class 内部类{
}
}
成员内部类的使用特征,概括来讲有如下两种角色:
成员内部类作为类的成员的角色
:
和外部类不同,Inner class还可以声明为private或protected;
可以调用外部类的结构。(注意:在静态内部类中不能使用外部类的非静态成员)
Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
成员内部类作为类的角色
:
可以在内部定义属性、方法、构造器等结构
可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
可以声明为abstract类 ,因此可以被其它的内部类继承
可以声明为final的,表示不能被继承
编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
注意点:
外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
成员内部类可以直接使用外部类的所有成员,包括私有的数据
当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
8.2.2 创建成员内部类对象
外部类名.静态内部类名 变量 = 外部类名.静态内部类名();
变量.非静态方法();
外部类名 变量1 = new 外部类();
外部类名.非静态内部类名 变量2 = 变量1.new 非静态内部类名();
变量2.非静态方法();
8.2.3 举例 8.2.3.1成员内部类—非静态成员内部类 public class Animal {
int age = 10 ;
String name = "动物" ;
class Bird {
String name = "鸟" ;
public void show ( String name) {
System . out. println ( age) ;
System . out. println ( this . name) ;
System . out. println ( this . name == name) ;
System . out. println ( Animal . this . name) ;
}
}
public static void main ( String [ ] args) {
Animal animal = new Animal ( ) ;
Bird bird = animal. new Bird ( ) ;
bird. show ( "鸟" ) ;
}
}
8.2.3.2成员内部类—静态成员内部类 class Animal {
static int age = 10 ;
static String name = "动物" ;
static class Bird {
static String name = "鸟" ;
public void show ( String name) {
System . out. println ( age) ;
System . out. println ( name) ;
System . out. println ( Bird . name == name) ;
System . out. println ( Animal . name) ;
}
}
public static void main ( String [ ] args) {
Animal. Bird bird = new Animal. Bird ( ) ;
bird. show ( "鸟" ) ;
}
}
8.3 局部内部类 8.3.1 非匿名局部内部类 语法格式:
[ 修饰符] class 外部类{
[ 修饰符] 返回值类型 方法名( 形参列表) {
[ final / abstract ] class 内部类{
}
}
}
编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。
这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
和成员内部类不同的是,它前面不能有权限修饰符等
局部内部类如同局部变量一样,有作用域
局部内部类中是否能访问外部类的非静态的成员,取决于所在的方法
举例:
常规写法
局部内部类 Student
只能在 createStudent()
方法中访问,因此如果想要创建一个 Student
类型的对象并调用它的方法,需要在 createStudent()
方法中进行。
public class Person {
private int age;
public void createStudent ( ) {
class Student {
private int age;
public void show ( ) {
System . out. println ( "Student show" ) ;
}
}
new Student ( ) . show ( ) ;
}
public static void main ( String [ ] args) {
Person person = new Person ( ) ;
person. createStudent ( ) ;
}
}
创建局部内部类实现接口(或者继承父类)
public interface Bird {
void show ( ) ;
}
public class Animal {
public static void main ( String [ ] args) {
Animal animal = new Animal ( ) ;
Bird bigBird = animal. run ( ) ;
bigBird. show ( ) ;
}
public Bird run ( ) {
class BigBird implements Bird {
@Override
public void show ( ) {
System . out. println ( "大鸟" ) ;
}
}
return new BigBird ( ) ;
}
}
8.3.2 匿名内部类 匿名内部类是在Java中创建一个没有类名的内部类的方式。 匿名内部类没有名字,可以在定义一个类的同时对其进行实例化。它与局部类很相似,不同的是它没有类名,如果某个局部类你只需要用一次,那么你就可以使用匿名内部类。这样可以使你的代码更加简洁。
匿名内部类的本质是一个带具体实现的父类或者父接口
的匿名的子类对象。 它可以用来简化代码,因为它可以省去创建子类和实现类的过程。
例如,如果你想要创建一个只使用一次的接口实现,你可以使用匿名内部类来直接实现接口中的方法,而不需要创建一个单独的实现类。匿名内部类的格式为:父类/接口 对象 = new 父类/接口() { 重写父类/接口中的方法 };
。
new 父类( [ 实参列表] ) {
重写方法. . .
}
举例
在 showBird()
方法中,使用匿名内部类创建了一个实现了 Bird
接口的对象 bird
,并重写了接口中的 show()
方法。然后,该方法返回了这个 bird
对象。
public class Animal {
private int age;
public Bird showBird ( ) {
Bird bird = new Bird ( ) {
@Override
public void show ( ) {
System . out. println ( "Bird show" ) ;
}
} ;
return bird;
}
public static void main ( String [ ] args) {
Animal animal = new Animal ( ) ;
Bird bird = animal. showBird ( ) ;
bird. show ( ) ;
}
}
8.4 练习 编写一个匿名内部类,它继承Object,并在匿名内部类中,声明一个方法public void test()打印尚硅谷。
请编写代码调用这个方法。
package com. atguigu. test01 ;
public class Test01 {
public static void main ( String [ ] args) {
new Object ( ) {
public void test ( ) {
System . out. println ( "尚硅谷" ) ;
}
} . test ( ) ;
}
}
9. 枚举类 9.1 概述
9.2 定义枚举类(JDK5.0 之前) 在JDK5.0 之前如何声明枚举类呢?
私有化
类的构造器,保证不能在类的外部创建其对象
在类的内部创建枚举类的实例。声明为:public static final
,对外暴露这些常量对象
对象如果有实例变量
,应该声明为private final
(建议,不是必须),并在构造器中初始化
示例代码:
class Season {
private final String SEASONNAME ;
private final String SEASONDESC ;
private Season ( String seasonName, String seasonDesc) {
this . SEASONNAME = seasonName;
this . SEASONDESC = seasonDesc;
}
public static final Season SPRING = new Season ( "春天" , "春暖花开" ) ;
public static final Season SUMMER = new Season ( "夏天" , "夏日炎炎" ) ;
public static final Season AUTUMN = new Season ( "秋天" , "秋高气爽" ) ;
public static final Season WINTER = new Season ( "冬天" , "白雪皑皑" ) ;
@Override
public String toString ( ) {
return "Season{" +
"SEASONNAME='" + SEASONNAME + '\'' +
", SEASONDESC='" + SEASONDESC + '\'' +
'}' ;
}
}
class SeasonTest {
public static void main ( String [ ] args) {
System . out. println ( Season . AUTUMN ) ;
}
}
9.3 定义枚举类(JDK5.0 之后) 9.3.1 enum关键字声明枚举 【修饰符】 enum 枚举类名{
常量对象列表
}
【修饰符】 enum 枚举类名{
常量对象列表;
对象的实例变量列表;
}
举例1:
package com. atguigu. enumeration ;
public enum Week {
MONDAY , TUESDAY , WEDNESDAY , THURSDAY , FRIDAY , SATURDAY , SUNDAY ;
}
public class TestEnum {
public static void main ( String [ ] args) {
Season spring = Season . SPRING ;
System . out. println ( spring) ;
}
}
9.3.2 enum方式定义的要求和特点
枚举类的常量对象列表必须在枚举类的首行,因为是常量,所以建议大写。
列出的实例系统会自动添加 public static final 修饰。
如果常量对象列表后面没有其他代码,那么“;”可以省略,否则不可以省略“;”。
编译器给枚举类默认提供的是private的无参构造,如果枚举类需要的是无参构造,就不需要声明,写常量对象列表时也不用加参数
如果枚举类需要的是有参构造,需要手动定义,有参构造的private可以省略,调用有参构造的方法就是在常量对象名后面加(实参列表)就可以。
枚举类默认继承的是java.lang.Enum类,因此不能再继承其他的类型。
JDK5.0 之后switch,提供支持枚举类型,case后面可以写枚举常量名,无需添加枚举类作为限定。
举例2:
public enum SeasonEnum {
SPRING ( "春天" , "春风又绿江南岸" ) ,
SUMMER ( "夏天" , "映日荷花别样红" ) ,
AUTUMN ( "秋天" , "秋水共长天一色" ) ,
WINTER ( "冬天" , "窗含西岭千秋雪" ) ;
private final String seasonName;
private final String seasonDesc;
private SeasonEnum ( String seasonName, String seasonDesc) {
this . seasonName = seasonName;
this . seasonDesc = seasonDesc;
}
public String getSeasonName ( ) {
return seasonName;
}
public String getSeasonDesc ( ) {
return seasonDesc;
}
}
举例3:
package com. atguigu. enumeration ;
public enum Week {
MONDAY ( "星期一" ) ,
TUESDAY ( "星期二" ) ,
WEDNESDAY ( "星期三" ) ,
THURSDAY ( "星期四" ) ,
FRIDAY ( "星期五" ) ,
SATURDAY ( "星期六" ) ,
SUNDAY ( "星期日" ) ;
private final String description;
private Week ( String description) {
this . description = description;
}
@Override
public String toString ( ) {
return super . toString ( ) + ":" + description;
}
}
package com. atguigu. enumeration ;
public class TestWeek {
public static void main ( String [ ] args) {
Week week = Week . MONDAY ;
System . out. println ( week) ;
switch ( week) {
case MONDAY :
System . out. println ( "怀念周末,困意很浓" ) ; break ;
case TUESDAY :
System . out. println ( "进入学习状态" ) ; break ;
case WEDNESDAY :
System . out. println ( "死撑" ) ; break ;
case THURSDAY :
System . out. println ( "小放松" ) ; break ;
case FRIDAY :
System . out. println ( "又信心满满" ) ; break ;
case SATURDAY :
System . out. println ( "开始盼周末,无心学习" ) ; break ;
case SUNDAY :
System . out. println ( "一觉到下午" ) ; break ;
}
}
}
经验之谈:
开发中,当需要定义一组常量时,强烈建议使用枚举类。
9.4 enum中常用方法 String toString(): 默认返回的是常量名(对象名),可以继续手动重写该方法!
static 枚举类型[] values():返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值,是一个静态方法
static 枚举类型 valueOf(String name):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
String name():得到当前枚举常量的名称。建议优先使用toString()。
int ordinal():返回当前枚举常量的次序号,默认从0开始
举例:
package com. atguigu. enumeration ;
import java. util. Scanner ;
public class TestEnumMethod {
public static void main ( String [ ] args) {
Week [ ] values = Week . values ( ) ;
for ( int i = 0 ; i < values. length; i++ ) {
System . out. println ( ( values[ i] . ordinal ( ) + 1 ) + "->" + values[ i] . name ( ) ) ;
}
System . out. println ( "------------------------" ) ;
Scanner input = new Scanner ( System . in) ;
System . out. print ( "请输入星期值:" ) ;
int weekValue = input. nextInt ( ) ;
Week week = values[ weekValue- 1 ] ;
System . out. println ( week) ;
System . out. print ( "请输入星期名:" ) ;
String weekName = input. next ( ) ;
week = Week . valueOf ( weekName) ;
System . out. println ( week) ;
input. close ( ) ;
}
}
9.5 实现接口的枚举类
和普通 Java 类一样,枚举类可以实现一个或多个接口
若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可。
若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法
语法:
enum A implements 接口1 ,接口2 {
}
enum A implements 接口1 ,接口2 {
常量名1 ( 参数) {
} ,
常量名2 ( 参数) {
} ,
}
举例:
interface Info {
void show ( ) ;
}
enum Season1 implements Info {
SPRING ( "春天" , "春暖花开" ) {
public void show ( ) {
System . out. println ( "春天在哪里?" ) ;
}
} ,
SUMMER ( "夏天" , "夏日炎炎" ) {
public void show ( ) {
System . out. println ( "宁静的夏天" ) ;
}
} ,
AUTUMN ( "秋天" , "秋高气爽" ) {
public void show ( ) {
System . out. println ( "秋天是用来分手的季节" ) ;
}
} ,
WINTER ( "冬天" , "白雪皑皑" ) {
public void show ( ) {
System . out. println ( "2002年的第一场雪" ) ;
}
} ;
private final String SEASON_NAME ;
private final String SEASON_DESC ;
private Season1 ( String seasonName, String seasonDesc) {
this . SEASON_NAME = seasonName;
this . SEASON_DESC = seasonDesc;
}
public String getSEASON_NAME ( ) {
return SEASON_NAME ;
}
public String getSEASON_DESC ( ) {
return SEASON_DESC ;
}
}
10. 注解(Annotation) 10.1 注解概述 10.1.1 什么是注解 注解(Annotation)是从JDK5.0
开始引入,以“@注解名
”在代码中存在。例如:
@SuppressWarnings ( value= ”unchecked”)
Annotation 可以像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明。还可以添加一些参数值,这些信息被保存在 Annotation 的 “name=value” 对中。
注解可以在类编译、运行时进行加载,体现不同的功能。
10.1.2 注解与注释 注解也可以看做是一种注释,通过使用 Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。但是,注解,不同于单行注释和多行注释。
对于单行注释和多行注释是给程序员看的。
而注解是可以被编译器或其他程序读取的。程序还可以根据注解的不同,做出相应的处理。
10.1.3 注解的重要性 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色
,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码
和XML配置
等。
未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,Struts2有一部分也是基于注解的了。注解是一种趋势
,一定程度上可以说:框架 = 注解 + 反射 + 设计模式
。
10.2 常见的Annotation作用 示例1:生成文档相关的注解
@author 标明开发该类模块的作者,多个作者之间使用, 分割
@version 标明该类模块的版本
@see 参考转向,也就是相关主题
@since 从哪个版本开始增加的
@param 对方法中某参数的说明,如果没有参数就不能写
@return 对方法返回值的说明,如果方法的返回值类型是void 就不能写
@exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws 显式抛出的异常就不能写
package com. annotation. javadoc ;
public class JavadocTest {
public static void main ( String [ ] args) {
}
public static double getArea ( double radius) {
return Math . PI * radius * radius;
}
}
示例2:在编译时进行格式检查(JDK内置的三个基本注解)
@Override
: 限定重写父类方法,该注解只能用于方法
@Deprecated
: 用于表示所修饰的元素(类,方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings
: 抑制编译器警告
package com. annotation. javadoc ;
public class AnnotationTest {
public static void main ( String [ ] args) {
@SuppressWarnings ( "unused" )
int a = 10 ;
}
@Deprecated
public void print ( ) {
System . out. println ( "过时的方法" ) ;
}
@Override
public String toString ( ) {
return "重写的toString方法()" ;
}
}
示例3:跟踪代码依赖性,实现替代配置文件功能
Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署。
@WebServlet ( "/login" )
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L ;
protected void doGet ( HttpServletRequest request, HttpServletResponse response) { }
protected void doPost ( HttpServletRequest request, HttpServletResponse response) {
doGet ( request, response) ;
}
}
< servlet>
< servlet-name> LoginServlet</ servlet-name>
< servlet-class> com.servlet.LoginServlet</ servlet-class>
</ servlet>
< servlet-mapping>
< servlet-name> LoginServlet</ servlet-name>
< url-pattern> /login</ url-pattern>
</ servlet-mapping>
@Transactional ( propagation= Propagation . REQUIRES_NEW , isolation= Isolation . READ_COMMITTED , readOnly= false , timeout= 3 )
public void buyBook ( String username, String isbn) {
int price = bookShopDao. findBookPriceByIsbn ( isbn) ;
bookShopDao. updateBookStock ( isbn) ;
bookShopDao. updateUserAccount ( username, price) ;
}
< tx: advice transaction-manager = " dataSourceTransactionManager" id = " txAdvice" >
< tx: attributes>
< tx: method name = " buyBook" propagation = " REQUIRES_NEW"
isolation = " READ_COMMITTED" read-only = " false" timeout = " 3" />
</ tx: attributes>
</ tx: advice>
10.3 三个最基本的注解 10.3.1 @Override
10.3.2 @Deprecated
10.3.3 @SuppressWarnings
示例代码:
package com. atguigu. annotation ;
import java. util. ArrayList ;
public class TestAnnotation {
@SuppressWarnings ( "all" )
public static void main ( String [ ] args) {
int i;
ArrayList list = new ArrayList ( ) ;
list. add ( "hello" ) ;
list. add ( 123 ) ;
list. add ( "world" ) ;
Father f = new Son ( ) ;
f. show ( ) ;
f. methodOl ( ) ;
}
}
class Father {
@Deprecated
void show ( ) {
System . out. println ( "Father.show" ) ;
}
void methodOl ( ) {
System . out. println ( "Father Method" ) ;
}
}
class Son extends Father {
}
10.4 元注解 JDK1.5在java.lang.annotation包定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。
(1)**@Target:**用于描述注解的使用范围
可以通过枚举类型ElementType的10个常量对象来指定
TYPE,METHOD,CONSTRUCTOR,PACKAGE…..
(2)**@Retention:**用于描述注解的生命周期
可以通过枚举类型RetentionPolicy的3个常量对象来指定
SOURCE(源代码)、CLASS(字节码)、RUNTIME(运行时)
唯有RUNTIME阶段才能被反射读取到
。
(3)**@Documented**:表明这个注解应该被 javadoc工具记录。
(4)**@Inherited:**允许子类继承父类中的注解
示例代码:
package java. lang ;
import java. lang. annotation. * ;
@Target ( ElementType . METHOD )
@Retention ( RetentionPolicy . SOURCE )
public @interface Override {
}
package java. lang ;
import java. lang. annotation. * ;
import static java. lang. annotation. ElementType . * ;
@Target ( { TYPE , FIELD , METHOD , PARAMETER , CONSTRUCTOR , LOCAL_VARIABLE } )
@Retention ( RetentionPolicy . SOURCE )
public @interface SuppressWarnings {
String [ ] value ( ) ;
}
package java. lang ;
import java. lang. annotation. * ;
import static java. lang. annotation. ElementType . * ;
@Documented
@Retention ( RetentionPolicy . RUNTIME )
@Target ( value= { CONSTRUCTOR , FIELD , LOCAL_VARIABLE , METHOD , PACKAGE , PARAMETER , TYPE } )
public @interface Deprecated {
}
拓展:元数据
String name = “Tom”;
10.5 自定义注解的使用 一个完整的注解应该包含三个部分: (1)声明 (2)使用 (3)读取
10.5.1 声明自定义注解 【元注解】
【修饰符】 @interface 注解名{
【成员列表】
}
自定义注解可以通过四个元注解@Retention,@Target,@Inherited,@Documented,分别说明它的声明周期,使用位置,是否被继承,是否被生成到API文档中。
Annotation 的成员在 Annotation 定义中以无参数有返回值的抽象方法的形式来声明,我们又称为配置参数。返回值类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组
可以使用 default 关键字为抽象方法指定默认返回值
如果定义的注解含有抽象方法,那么使用时必须指定返回值,除非它有默认值。格式是“方法名 = 返回值”,如果只有一个抽象方法需要赋值,且方法名为value,可以省略“value=”,所以如果注解只有一个抽象方法成员,建议使用方法名value。
package com. atguigu. annotation ;
import java. lang. annotation. * ;
@Inherited
@Target ( ElementType . TYPE )
@Retention ( RetentionPolicy . RUNTIME )
public @interface Table {
String value ( ) ;
}
package com. atguigu. annotation ;
import java. lang. annotation. * ;
@Inherited
@Target ( ElementType . FIELD )
@Retention ( RetentionPolicy . RUNTIME )
public @interface Column {
String columnName ( ) ;
String columnType ( ) ;
}
10.5.2 使用自定义注解 package com. atguigu. annotation ;
@Table ( "t_stu" )
public class Student {
@Column ( columnName = "sid" , columnType = "int" )
private int id;
@Column ( columnName = "sname" , columnType = "varchar(20)" )
private String name;
public int getId ( ) {
return id;
}
public void setId ( int id) {
this . id = id;
}
public String getName ( ) {
return name;
}
public void setName ( String name) {
this . name = name;
}
@Override
public String toString ( ) {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}' ;
}
}
10.5.3 读取和处理自定义注解 自定义注解必须配上注解的信息处理流程才有意义。
我们自己定义的注解,只能使用反射的代码读取。所以自定义注解的声明周期必须是RetentionPolicy.RUNTIME。
具体的使用见《尚硅谷_宋红康_第17章_反射机制.md》
。
10.6 JUnit单元测试 10.6.1 测试分类 黑盒测试: 不需要写代码,给输入值,看程序是否能够输出期望的值。
白盒测试: 需要写代码的。关注程序具体的执行流程。
10.6.2 JUnit单元测试介绍 JUnit 是由 Erich Gamma 和 Kent Beck 编写的一个测试框架(regression testing framework),供Java开发人员编写单元测试之用。
JUnit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。
要使用JUnit,必须在项目的编译路径中引入JUnit的库
,即相关的.class文件组成的jar包。jar就是一个压缩包,压缩包都是开发好的第三方(Oracle公司第一方,我们自己第二方,其他都是第三方)工具类,都是以class文件形式存在的。
10.6.3 引入本地JUnit.jar 第1步:在项目中File-Project Structure中操作:添加Libraries库
其中,junit-libs包内容如下:
第2步:选择要在哪些module中应用JUnit库
第3步:检查是否应用成功
注意Scope:选择Compile,否则编译时,无法使用JUnit。
第4步:下次如果有新的模块要使用该libs库,这样操作即可
10.6.4 编写和运行@Test单元测试方法 JUnit4版本,要求@Test标记的方法必须满足如下要求:
所在的类必须是public的,非抽象的,包含唯一的无参构造器。
@Test标记的方法本身必须是public,非抽象的,非静态的,void无返回值,()无参数的。
package com. atguigu. junit ;
import org. junit. Test ;
public class TestJUnit {
@Test
public void test01 ( ) {
System . out. println ( "TestJUnit.test01" ) ;
}
@Test
public void test02 ( ) {
System . out. println ( "TestJUnit.test02" ) ;
}
@Test
public void test03 ( ) {
System . out. println ( "TestJUnit.test03" ) ;
}
}
10.6.5 设置执行JUnit用例时支持控制台输入 1. 设置数据:
默认情况下,在单元测试方法中使用Scanner时,并不能实现控制台数据的输入。需要做如下设置:
在idea64.exe.vmoptions配置文件
中加入下面一行设置,重启idea后生效。
-Deditable.java.test.console = true
2. 配置文件位置:
添加完成之后,重启IDEA即可。
3. 如果上述位置设置不成功,需要继续修改如下位置
修改位置1:IDEA安装目录的bin目录(例如:D:\develop_tools\IDEA\IntelliJ IDEA 2022.1.2\bin
)下的idea64.exe.vmoptions文件。
修改位置2:C盘的用户目录C:\Users\用户名\AppData\Roaming\JetBrains\IntelliJIdea2022.1
下的idea64.exe.vmoptions`件。
10.6.6 定义test测试方法模板 选中自定义的模板组,点击”+”(1.Live Template)来定义模板。
11. 包装类 11.1 为什么需要包装类 Java提供了两个类型系统,基本数据类型
与引用数据类型
,如果要使基本数据类型具备引用数据类型的相关特征(封装、继承、多态),就可以使用基本数据类型的包装类。
Object 类的equals ( Object obj)
ArrayList 类的add ( Object obj)
add ( int number)
add ( double d)
add ( boolean b)
Set < T >
List < T >
Cllection < T >
Map < K , V >
11.2 有哪些包装类 Java针对八种基本数据类型定义了相应的引用类型:包装类(封装类)。有了类的特点,就可以调用类中的方法,Java才是真正的面向对象。
封装以后的,内存结构对比:
public static void main ( String [ ] args) {
int num = 520 ;
Integer obj = new Integer ( 520 ) ;
}
11.3 自定义包装类 public class MyInteger {
int value;
public MyInteger ( ) {
}
public MyInteger ( int value) {
this . value = value;
}
@Override
public String toString ( ) {
return String . valueOf ( value) ;
}
}
11.4 包装类与基本数据类型间的转换 11.4.1 装箱 装箱:把基本数据类型转为包装类对象
转为包装类的对象,是为了使用专门为对象设计的API和特性
基本数值—->包装对象
Integer obj1 = new Integer ( 4 ) ;
Float f = new Float ( “4.56 ”) ;
Long l = new Long ( “asdf”) ;
Integer obj2 = Integer . valueOf ( 4 ) ;
11.4.2 拆箱 拆箱:把包装类对象拆为基本数据类型
转为基本数据类型,一般是因为需要运算,Java中的大多数运算符是为基本数据类型设计的。比较、算术等
包装对象—->基本数值
Integer obj = new Integer ( 4 ) ;
int num1 = obj. intValue ( ) ;
自动装箱与拆箱:
由于我们经常要做基本类型与包装类之间的转换,从JDK5.0
开始,基本类型与包装类的装箱、拆箱动作可以自动完成。例如:
Integer i = 4 ;
i = i + 5 ;
注意:只能与自己对应的类型之间才能实现自动装箱与拆箱。
Integer i = 1 ;
Double d = 1 ;
11.5 基本数据类型、包装类与字符串间的转换 (1)基本数据类型转为字符串
方式1: 调用字符串重载的valueOf()方法
int a = 10 ;
String str = String . valueOf ( a) ;
方式2: 更直接的方式
int a = 10 ;
String str = a + "" ;
(2)字符串转为基本数据类型
方式1: 除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型,例如:
public static int parseInt(String s)
:将字符串参数转换为对应的int基本类型。
public static long parseLong(String s)
:将字符串参数转换为对应的long基本类型。
public static double parseDouble(String s)
:将字符串参数转换为对应的double基本类型。
方式2: 字符串转为包装类,然后可以自动拆箱为基本数据类型
public static Integer valueOf(String s)
:将字符串参数转换为对应的Integer包装类,然后可以自动拆箱为int基本类型
public static Long valueOf(String s)
:将字符串参数转换为对应的Long包装类,然后可以自动拆箱为long基本类型
public static Double valueOf(String s)
:将字符串参数转换为对应的Double包装类,然后可以自动拆箱为double基本类型
注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException
异常。
方式3: 通过包装类的构造器实现
int a = Integer . parseInt ( "整数的字符串" ) ;
double d = Double . parseDouble ( "小数的字符串" ) ;
boolean b = Boolean . parseBoolean ( "true或false" ) ;
int a = Integer . valueOf ( "整数的字符串" ) ;
double d = Double . valueOf ( "小数的字符串" ) ;
boolean b = Boolean . valueOf ( "true或false" ) ;
int i = new Integer ( “12 ”) ;
其他方式小结:
11.6 包装类的其它API 11.6.1 数据类型的最大最小值 Integer . MAX_VALUE 和Integer . MIN_VALUE
Long . MAX_VALUE 和Long . MIN_VALUE
Double . MAX_VALUE 和Double . MIN_VALUE
11.6.2 字符转大小写 Character . toUpperCase ( 'x' ) ;
Character . toLowerCase ( 'X' ) ;
11.6.3 整数转进制 Integer . toBinaryString ( int i)
Integer . toHexString ( int i)
Integer . toOctalString ( int i)
11.6.4 比较的方法 Double . compare ( double d1, double d2)
Integer . compare ( int x, int y)
11.7 包装类对象的特点 11.7.1 包装类缓存对象
包装类缓存对象是一种使用缓存机制来优化性能的设计模式(享元设计模式)。它通常用于将一些开销较大的操作结果进行缓存,以便下一次需要使用这个操作结果时不需要重新计算。 在Java中,包装类缓存对象主要是指Integer、Byte、Short、Long、Character这些基本类型的封装类,它们在实例化的时候会自动进行缓存。
这些封装类的对象是不可变的,所以它们的值是固定的。因此,如果两个对象的值相等,那么它们所引用的实例也一定是相等的。这种特性使得Java官方推荐对于某些简单类型的封装类,创建相同值的多个对象,使用缓存来减少对象创建的数量,从而提高性能。
需要注意的是,虽然使用包装类缓存对象可以提高性能,但是需要注意在使用时避免将缓存的对象进行修改,因为它们的值是不可变的,任何修改操作都是无效的。此外,对于缓存对象的使用需要谨慎,因为缓存变量作为静态变量,可能会对多线程环境下的线程安全性产生影响。
包装类
缓存对象范围
Byte
-128~127
Short
-128~127
Integer
-128~127
Long
-128~127
Float
没有
Double
没有
Character
0~127
Boolean
true和false
Integer a = 1 ;
Integer b = 1 ;
System . out. println ( a == b) ;
Integer i = 128 ;
Integer j = 128 ;
System . out. println ( i == j) ;
Integer m = new Integer ( 1 ) ;
Integer n = 1 ;
System . out. println ( m == n) ;
Integer x = new Integer ( 1 ) ;
Integer y = new Integer ( 1 ) ;
System . out. println ( x == y) ;
Double d1 = 1.0 ;
Double d2 = 1.0 ;
System . out. println ( d1== d2) ;
11.7.2 类型转换问题 Integer i = 1000 ;
double j = 1000 ;
System . out. println ( i== j) ;
Integer i = 1000 ;
int j = 1000 ;
System . out. println ( i== j) ;
Integer i = 1 ;
Double d = 1.0
System . out. println ( i== d) ;
11.7.3 包装类对象不可变 public class TestExam {
public static void main ( String [ ] args) {
int i = 1 ;
Integer j = new Integer ( 2 ) ;
Circle c = new Circle ( ) ;
change ( i, j, c) ;
System . out. println ( "i = " + i) ;
System . out. println ( "j = " + j) ;
System . out. println ( "c.radius = " + c. radius) ;
}
public static void change ( int a , Integer b, Circle c ) {
a += 10 ;
c. radius += 10 ;
}
}
class Circle {
double radius;
}
11.8 练习 笔试题:如下两个题目输出结果相同吗?各是什么。
Object o1 = true ? new Integer ( 1 ) : new Double ( 2.0 ) ;
System . out. println ( o1) ;
Object o2;
if ( true )
o2 = new Integer ( 1 ) ;
else
o2 = new Double ( 2.0 ) ;
System . out. println ( o2) ;
面试题:
public void method1 ( ) {
Integer i = new Integer ( 1 ) ;
Integer j = new Integer ( 1 ) ;
System . out. println ( i == j) ;
Integer m = 1 ;
Integer n = 1 ;
System . out. println ( m == n) ;
Integer x = 128 ;
Integer y = 128 ;
System . out. println ( x == y) ;
}
练习:
利用Vector代替数组处理:从键盘读入学生成绩(以负数代表输入结束),找出最高分,并输出学生成绩等级。
提示:数组一旦创建,长度就固定不变,所以在创建数组前就需要知道它的长度。而向量类java.util.Vector可以根据需要动态伸缩。
创建Vector对象:Vector v=new Vector();
给向量添加元素:v.addElement(Object obj); //obj必须是对象
取出向量中的元素:Object obj=v.elementAt(0);
注意第一个元素的下标是0,返回值是Object类型的。
计算向量的长度:v.size();
若与最高分相差10分内:A等;20分内:B等;30分内:C等;其它:D等
12.随堂复习 1. 关键字:static
2. 单例模式(或单子模式)
经典的设计模式有23种
解决的问题:在整个软件系统中,只存在当前类的唯一实例。
实现方式:饿汉式、懒汉式、枚举类等
对比饿汉式和懒汉式
饿汉式:“立即加载”,线程安全的。
懒汉式:”延迟加载”,线程不安全。
需要会手写饿汉式和懒汉式
3. 理解main()方法
public static void main(String[] args){}
理解1:作为程序的入口;普通的静态方法
理解2:如何使用main()与控制台进行数据的交互。
命令行:java 类名 “Tom” “Jerry” “123”
4. 类的成员之四:代码块
分类:静态代码块、非静态代码块
使用频率上来讲:用的比较少。
静态代码块:随着类的加载而执行
非静态代码块:随着对象的创建而执行
总结:对象的实例变量可以赋值的位置及先后顺序
① 默认初始化 ② 显式初始化 或 ⑤ 代码块中初始化 ③ 构造器中初始化
④ 有了对象以后,通过”对象.属性”或”对象.方法”的方法进行赋值
执行的先后顺序: ① - ②/⑤ - ③ - ④
5. 关键字:final
最终的
用来修饰:类、方法、变量(成员变量、局部变量)
类:不能被继承
方法:不能被重写
变量:是一个“常量”,一旦赋值不能修改。
6. 关键字:abstract
抽象的
用来修饰:类、方法
类:抽象类:不能实例化。
方法:抽象方法:没有方法体,必须由子类实现此方法。
7. 关键字:interface
8. 类的成员之五:内部类 > 成员内部类的理解
> 如何创建成员内部类的实例
> 如何在成员内部类中调用外部类的结构
> 局部内部类的基本使用(关注:如何在方法内创建匿名局部内部类的对象)
9. 枚举类:enum
10. 注解:Annotation
框架 = 注解 + 反射 + 设计模式
Java基础阶段:简单。@Override 、 @Deprecated、@SuppressWarnings
元注解:对现有的注解进行解释说明。
@Target:表明可以用来修饰的结构
@Retation:表明生命周期
如何自定义注解。
11. 包装类的使用
掌握:基本数据类型对应的包装类都有哪些?
掌握:基本数据类型、包装类、String三者之间的转换
基本数据类型 <-> 包装类:自动装箱、自动拆箱
基本数据类型、包装类 <-> String
String的valueOf(xxx)
包装类的parseXxx(String str)
12. IDEA:快捷键和debug 13.企业真题 13.1 static关键字
静态变量和实例变量的区别?(保*丰、*软国际、*软华*、北京明 信)**
类似问题:
> 说明静态变量和实例变量之间的区别和使用场景(上海*动)
静态变量被关键字static修饰,其随着类的加载而加载,而实例变量是随着对象的加载而加载。静态变量在 堆内存中存储,而实例变量在堆内存中的对象当中c
静态属性和静态方法是否可以被继承?是否可以被重写?以及原因?(*度)
类似问题:
> 在java中,可以重载一个static方法吗?可以覆盖一个static方法吗?(Vi*o)
静态属性和静态方法可以被继承,但是静态方法不能被重写。不存在多态性。
是否可以从一个static方法内部发出对非static方法的调用?(同*顺)
只能通过对象来对非静态方法的调用。
被static修饰的成员(类、方法、成员变量)能否再使用private进行修饰?(联*优势)
完全可以。除了代码块。
13.2 设计模式
单例模式、模板方法、享元设计模式
开发中都用到了那些设计模式?用在什么场合? (久*国际物流)
略
13.3main()
main()方法的public能不能换成private,为什么(凡*科技、顺*)
可以改。但是改完以后就不是程序入口了。
main()方法中是否可以调用非静态方法?(浩*科技)
只能通过对象来对非静态方法的调用。
13.4 代码块
类似问题:
> Java中类的变量初始化的顺序?(*壳)
略。
静态代码块,普通代码块,构造方法,从类加载开始的执行顺序?(恒*电子)
类似问题:
> 类加载成员变量、静态代码块、构造器的加载顺序(*科软、软**力、同*顺)
> static代码块(静态代码块)是否在类的构造函数之前被执行(联*优势)
静态代码块 –> 普通代码块 –> 构造器
13.5 final关键字
略
判断题:使用final修饰一个变量时,是引用不能改变,引用指向的对象可以改变?(*米)
引用不能改变。
引用指向的对象实体中的属性,如果没有使用final修饰,则可以改变。
判断题:final不能用于修饰构造方法?(联*优势)
是的。
final或static final 修饰成员变量,能不能进行++操作?(佳*贸易)
不能。
13.6 抽象类与接口
使用abstract修饰。
为什么不能用abstract修饰属性、私有方法、构造器、静态方法、final的方法?(止 善)**
略。 为了语言的自洽。
接口与抽象类的区别?(字*跳动、阿*校招、*度校招、**计算机技术及应用研究所、航*拓普、纬*、招 晟、汇*云通、数信**科技、北京永*鼎力、上海*连科技)**
略。
接口是否可继承接口?抽象类是否可实现(implements)接口?抽象类是否可继承实现类(concrete class)?(航*拓普、*蝶、深圳德*科技)
类似问题:
> 接口A可以继承接口B吗?接口A可以实现接口B吗?(久*国际物流)
是;是;是;
可以。必须是public static final的
使用实现类的对象进行调用。而且实现还可以重写此默认方法。
13.7 内部类
略。
类似问题:
> 说一下内部类的好处(北京楚*龙)
> 使用过内部类编程吗,有什么作用(软**力)
略
13.8 枚举类
使用enum定义的,其父类就是Enum类,就不要再继承其他的类了。
13.9 包装类
略。
13.10 综合