Java基础
一、基本概念
1.Java程序初始化的顺序是怎么样的?
在Java语言中,当实例化对象时,对象所在类的所有成员变量首先要进行初始化,只有当所有类成员完成初始化后,才会调用对象所在类的构造函数创建对象。
初始化一般遵循3个原则:
- 静态对象(变量)优先于非静态对象(变量)初始化,静态对象(变量)只初始化一次,而非静态对象(变量)可能会初始化多次;
- 父类优先于子类进行初始化;
- 按照成员变量的定义顺序进行初始化。即使变量定义散布于方法定义之中,它们依然在任何方法(包括构造函数)被调用之前先初始化;
加载顺序
- 父类(静态变量、静态语句块)
- 子类(静态变量、静态语句块)
- 父类(实例变量、普通语句块)
- 父类(构造函数)
- 子类(实例变量、普通语句块)
- 子类(构造函数)
实例:
class Base {
// 1.父类静态代码块
static {
System.out.println("Base static block!");
}
// 3.父类非静态代码块
{
System.out.println("Base block");
}
// 4.父类构造器
public Base() {
System.out.println("Base constructor!");
}
}
public class Derived extends Base {
// 2.子类静态代码块
static{
System.out.println("Derived static block!");
}
// 5.子类非静态代码块
{
System.out.println("Derived block!");
}
// 6.子类构造器
public Derived() {
System.out.println("Derived constructor!");
}
public static void main(String[] args) {
new Derived();
}
}
输出结果:
Base static block!
Derived static block!
Base block
Base constructor!
Derived block!
Derived constructor!
2.Java和C++的区别:
- Java是纯粹的面向对象语言,所有的对象都继承自java.lang.Obeject,C++为了兼容C即支持面向对象也支持面向过程。
- Java通过该虚拟机从而实现跨平台特性,但是C++依赖于特定的平台。
- Java没有指针,它的引用可以理解为完全指针,而C++具有和C一样的指针。
- Java支持自动垃圾回收,而C++需要手动回收。(C++11中引入智能指针,使用引用计数法垃圾回收)
- Java不支持多重继承,只能通过实现多个接口来达到相同的目的,而C++支持多重继承。
- Java不支持操作符重载,虽然可以两个String对象支持加法运算,但是这是语言内置支持的操作,不属于操作符重载,而C++可以。
- Java内置了线程的支持,而C++需要依靠第三方库。
- Java的goto是保留字,但是不可用,C++可以使用goto。
- Java不支持条件编译,C++通过#ifdef #ifndef等预处理命令从而实现条件编译。
3.反射
什么是反射?
反射(Reflection)是Java程序开发语言的特征之一,它允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。通过Class 获取class 信息称之为反射。
简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。
程序中一般的对象的类型都是在编译期就确定下来,而Java反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知 的。所有我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
Java 反射框架主要提供以下功能:
1.在运行时判断任意一个对象所属的类
2.在运行时构造任意一个类的对象
3.运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法)
4.在运行时调用任意一个对象的方法
重点:是运行时而不是编译时
主要用途:
很多人都认为反射在实际的Java开发应用中并不广泛,其实不然。
当我们在使用IDE(如Eclipse,IDEA)时,当我们输入一个对象或类并想调用它的属性或方法,这里就会用到反射。
反射最重要的用途就是开发各种通用框架
很多框架(比如 spring)都是配置化的(比如通过XML文件配置JavaBean,Action之类的),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射-----运行时动态加载需要加载的对象。
对于框架开发人员来说,反射虽小但作用非常大,它是各容器实现的核心。而对于一般的开发者来说,不深入框架开发则用反射就会少一点,不过了解一下框架的底层机制有助于丰富自己的编程思想,也是很有益的。
获得Class对象
1.调用运行时类本身的 .class属性
Class class1= Person.class;
System.out.println(class1.getName());
2.通过运行时类的对象获取 getClass();
Person p= new Person();
Class class2= p.getClass();
System.out.println(class2.getName());
3.使用Class 类的forName 静态方法
public static Class<?> forName(String className)
//在JDBC开发中常用此方法加载数据库驱动;
Class.forName(driver);
4.(了解)通过类的加载器ClassLoader
ClassLoader classLoader=this.getClas().getClassLoader();
Class class3=classLoader.loadClass(className);
System.out.println(class3.getName());
4.注解
什么是注解?
Annontation 是Java5 开始引入 的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation 像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation包中。
简单来说:注解其实就是代码中的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相对应的处理。
为什么要用注解?
传统的方式,我们是通过配置文件 .xml来告诉类是如何运行的。
有了注解技术之后,我们就可以通过注解告诉类如何运行
例如:我们以前编写 Servlet 的时候,需要在web.xml文件配置具体的信息。我们使用了注解以后,可以直接在Servlet 源代码上,增加注解……Servlet就被配置到Tomcat上了,也就是说,注解可以给类、方法上注入信息。
明显地可以看出,这样是非常直观的,并且 Servlet规范是推崇这种配置方式的。
基本Annotation
在java.lang包下存在着5个基本的Annotation,重点掌握钱三个。
1.@Override 重写注解
①如果我们使用IDE重写父类的方法,我们就可以看见它了。
②@Override是告诉编译器要检查该方法是实现父类的。可以帮我们避免一些低级的错误。
③比如,我们在实现 equals() 方法的时候,把 euqals() 打错了,那么编译器就会发现该方法并不是实现父类的,与注解 @Override 冲突,于是就会报错。
2.@Deprecated 过时注解
①该注解也非常常见,Java 在设计的时候,可能觉得某些方法设计得不好,为 了兼容以前的程序,是不能直接把它抛弃的,于是就设置它为过时。
②Date对象中的 toLocalString() 就被设置成过时了
③当我们在程序中调用它的时候,在 IDE 上会出现一条横杠,说明该方法是过时的。
@Deprecated
public String toLocaleString() {
DateFormat formatter = DateFormat.getDateTimeInstance();
return formatter.format(this);
}
3.@SuppressWarnings 抑制编译器警告注解
①该注解在我们写程序的时候并不是很常见,我们可以用它来让编译器不给予我们警告
②当我们在使用集合的时候,如果没有指定泛型,那么会提示安全检查的警告
③如果我们在类上添加了@SuppressWarnings这个注解,那么编译器就不会给予我们警告了
4.@SafeVarargs Java 7“堆污染”警告
什么是堆污染呢??当把一个不是泛型的集合赋值给一个带泛型的集合的时候,这种情况就很容易发生堆污染。
这个注解也是用来抑制编译器警告的注解,用的地方并不多。
5.@FunctionalInterface 用来指定该接口是函数式接口
用该注解显式指定该接口是一个函数式接口。
自定注解类编写规则
1.Annotation 型定义为@interface,所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口。
2.参数成员只能是用public 或默认(default)这两个访问修饰符修饰
3.参数成员只能使用基本数据类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class等数据类型,以及这一些类型的数据
4.要获取类方法和字段的注解信息,必须通过Java的反射技术来获取Annotation对象,因为你除此之外没有别的获取注解对象的方法
5.注解也可以没有定义成员,不过这样注解就没啥用了 PS:自定义注解需要使用到元注解
自定义注解实例:
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 水果名称注解
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
5.泛型
概念:
通俗的讲,泛型就是操作类型的占位符,即:假设占位符为 T ,那么此次声明的数据结构操作的数据类型为类型。
假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串甚至其他任何类型的数据进行排序,该如何实现?答案是可以使用泛型。
使用Java泛型的概念,我们可以写一个泛型方法类对一个对象数组排序。然后,调用该泛型方法来对整数数组、浮点数数组、字符串数组等进行排序。
泛型方法
你可以写一个泛型方法,该方法在调用时可以接受不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则:
- 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回之前(下面例子中的)。
- 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
- 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
- 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像 int,double,char等)
public class GenericMethodTest
{
// 泛型方法 printArray
public static < E > void printArray( E[] inputArray )
{
// 输出数组元素
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
public static void main( String args[] )
{
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
System.out.println( "整型数组元素为:" );
printArray( intArray ); // 传递一个整型数组
System.out.println( "\n双精度型数组元素为:" );
printArray( doubleArray ); // 传递一个双精度型数组
System.out.println( "\n字符型数组元素为:" );
printArray( charArray ); // 传递一个字符型数组
}
}
泛型类
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。
和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类称为参数化的类或参数化的类型。
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("菜鸟教程"));
System.out.printf("整型值为 :%d\n\n", integerBox.get());
System.out.printf("字符串为 :%s\n", stringBox.get());
}
}
类型通配符
1.类型通配符一般是使用 ? 代替具体的类型参数。例如 List<?> 在逻辑上是 List,List等所有List<具体类型实参>的父类。
2.类型通配符上限通过形如 List 类定义,如此定义就是通配符泛型值接受 Number 及其下层子类类型。
3.类型通配符下限通过形如 List<? super Number>来定义,表示类型只能接受Number及其三层父类类型,如Object类型的实例。
6.字节与字符的区别
理解编码的关键,是要把字符的概念和字节的概念理解准确。这两个概念容易混淆,我们在此做一下区分:
类型 | 概念描述 | 举例 |
---|---|---|
字符 | 人们使用的记号,抽象意义上的一个符号 | ‘1’,‘中’,‘a’,’$’…… |
字节 | 计算机中存储数据的单元,一个8位的二进制数,是一个很具体的存储空间 | 0x01,0x45,0xFA…… |
ANSI字符串 | 在内存中,如果“字符是以ANSI编码形式存在的,一个字符可能使用一个字节或多个字节来表示,那么我们称这种字符串为ANSI字符串或者多字节字符串 | “中文123”(占7字节) |
UNICODE字符串 | 在内存中,如果“字符”是以在UNNNICODE中的序号存在的,那么我们称这种字符串为UNICODE字符串或者字节字符串 | L"中文123"(占10 字节) |
字节与字符区别
7.有哪些访问修饰符
Java 面向对象的基本思想之一是封装细节并且公开接口。Java 语言采用访问控制修饰符来控制类及类的方法和变量的访问权限,从而向使用者暴露接口,但隐藏实现细节。访问控制分为四种级别:
修饰符 | 当前类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
- 类的成员不写访问修饰时默认为default。默认对同一个包中的其他类相当于公开(public),对于不是同一个包中的其他类相当于私有(private)。
- 受保护(protacted)对子类相当于公开,对不是同一包中的没有父子关系的类相当于私有。
- Java中,外部类的修饰符只能是 public或默认,类的成员(包括内部类)的修饰符可以是以上四种。
8.深拷贝与浅拷贝
- 浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用任然指向原来的对象。换而言之,浅拷贝仅仅复制所拷贝的对象,而不是复制它所引用的对象。
- 深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
9. 字符串常量池
概念
Java 中字符串对象创建有两种形式,一种为字面量形式,如 String str=“adc”; ,另一种就是使用new这种标准的构造对象的方法,如 String str = new String(“abc”); ,这两种方式我们在代码编写时都经常使用,尤其是字面量的方式。然而这两种实现其实存在着一些性能和内存占用的差别。这一切都是源于JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池。
工作原理
当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。
public class Test {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
// 以上两个局部变量都存在了常量池中
System.out.println(s1 == s2); // true
// new出来的对象不会放到常量池中,内存地址是不同的
String s3 = new String();
String s4 = new String();
/**
* 字符串的比较不可以使用双等号,这样会比较内存地址
* 字符串比较应当用equals,可见String重写了equals
*/
System.out.println(s3 == s4); // false
System.out.println(s3.equals(s4)); // true
}
}
10.解释型语言与编译型语言的区别
我们使用工具编写的字母加符号的代码,是我们能看懂的高级语言,计算机无法直接理解,计算机需要先对我们编写的代码翻译成计算机语言,才能执行我们编写的程序。
将高级语言翻译成计算机语言有编译,解释两种方式。两种方式只是翻译的时间不同。
1.编译型语言
编译型语言写在程序执行之前,需要借助一个程序,将高级语言编写的程序翻译成计算机能懂的机器语言,然后,这个机器语言就能直接执行了,也就是我们常见的(exe文件)。
2.解释型语言
解释型语言的程序不需要编译,节省了一道工序,不过解释型语言在运行的时候需要翻译,每个语句都是执行的时候才翻译,对比编译型语言,效率比较低。通俗来说,就是借助一个程序,且这个程序能试图理解编写的代码,然后按照编写的代码中的要求执行。
3.脚本语言
脚本语言也是一种解释性语言,又被称为扩建的语言,或者动态语言不需要编译,可以直接使用,由解释器来负责解释。
脚本语言一般都是以文本形式存在,类似于一种命令、
4.通俗理解编译型语言和解释型语言
同时讨论编译型语言和解释型语言的时候,这么说:编译型语言相当于做了一桌子菜再吃,解释型语言就是吃火锅。解释型的语言执行效率低。需要一边煮一边吃。
二、面向对象
1.Java的四个基本特征,对多态的理解,在项目中哪些地方用到多态?
Java的四个基本特征:
-
抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
-
封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
-
继承:继承是从已知类得到继承信息创造新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
-
多态:多态性是指允许不同子类型的对象对同一消息作出不同的响应。
多态的理解(多态的实现方式)
- 方法重载(overload):实现的是编译时的多态性(也称为前绑定)。
- 方法重写(override):实现的是运行时的多态性(也称后绑定)。运行时的多态是面向对象最精髓的东西。
- 要实现多态需要做两件事:
- a.方法重写(子类继承父类并重写父类中已有的或抽象的方法);
- b.对象造型(用父类型引用子类型对象,这样同样的引用调用同类型的方法就会根据子类对象的不同而表现出不同的行为)。
项目中对多态的应用
举一个简单的例子,在物流信息管理系统中,有两种用户:订购客户和买房客户,两个客户都可以登录系统,他们有同样的 Login 方法,但登陆之后他们会进入不同的页面,也就是在登录的时候会有不同的操作,两种客户都继承父类的 Login方法,但是对于不同的对象,拥有不同的操作。
面向对象开发方式优点
- 较高的开发效率:可以把事物进行抽象,映射为开发的对象。
- 保证软件的鲁棒性(鲁棒是Robust的音译,也就是健壮和强壮的意思。它是在异常和危险情况下系统生存的关键。):高可用性,可以把重用已有的而且在相关领域经过长期测试的代码。
- 保证软件的高可维护性:代码的可读性非常好,设计模式也使得代码结构清晰,拓展性好。
2.什么是重载和重写
- 重载:重载发生在同一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载。
- 重写:重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。根据不同的子类对象确定调用的那个方法。
3.面向对象和面向过程的区别?用面向过程可以实现面向对象吗?
面向对象和面向过程的区别
- 面向过程就像是一个细心的管家,事无巨细的都要考虑到。而面向对象就像是个家用电器,你只需要知道他的功能,不需要知道它的工作原理。
- 面向过程是一种以“事件”为中心的编程思想。就是分析出解决问题所需的步骤,然后用函数把这些步骤实现,并按顺序调用。面向对象是以“对象”为中心的编程思想。
- 简单的举个例子:汽车发动、汽车到站
*这对于面向过程来说,是两个书剑,汽车启动时一个事件,汽车到站是另一个事件,面向过程编程的过程中我们关心的是事件,而不是汽车本身。针对上述两个事件,形成两个函数,之后依次调用。(事件驱动,动词为主)
然而这对于面向对象来说,我们关❤的是汽车这类对象,两个事件只是这类对象所具有的行为,而且对于这两个行为的顺序没有强制要求。(对象驱动,名词为主,将问题抽象出具体的对象,而这个对象又自己的属性和方法,在解决问题的时候是将不同的对象组合在一起使用)
用面向过程可以实现面向对象吗?
如果是C语言来展示出面向对象的思想,C语言中是不是有个叫结构体的东西,这个里面有自己定义的变量,可以通过函数指针就可以实现对象
4.面向对象开发的六个基本原则,在项目中用过哪些原则
六个基本原则:
-
单一职责(Single Responsibility Principle 简称 SRP):一个类应该仅有一个引起它变化的原因。在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。
-
里氏替换(Liskov Substitution Principle 简称 LSP):任何时候子类型能够替换掉它们的父类型。子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。
-
依赖倒置(Dependence Inversion Principle 简称 DIP):要依赖于抽象,不要依赖于具体类。要做到依赖倒置,应该做到:①高层模块不应该依赖底层模块,二者都应该依赖于抽象;②抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
-
接口隔离(Interface Segregation Principle 简称 ISP):不应该强迫客户依赖于他们不用的方法 。接口要小而专,绝不能大而全。臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。
-
最少知识原则(Least Knowledge Principle 简称 LKP):只和你的朋友谈话。迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。
-
开闭原则(Open Closed Principle 简称 OCP):软件实体应当对扩展开放,对修改关闭。要做到开闭有两个要点:①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱。
其他原则:
- 合成聚和复用:优先使用聚合或合成关系复用代码
- 面向接口编程
- 优先使用组合,而非继承
- 一个类需要的数据应该隐藏在类的内部
- 类之间应该零耦合,或者只有传导耦合,换句话说,类之间要么没关系,要么只使用另一个类的接口提供的操作
- 在水平方向上尽可能统一地分布系统功能
在项目中用到的原则
单一职责、开发封闭、合成聚和复用(最简单的例子就是String类)、接口隔离
5.内部类有哪些
可以将一个类的定义放在另一个类的定义内部,这就是内部类。
在Java中内部类主要分为成员内部类、局部内部类、匿名内部类、静态内部类
(一)成员内部类
成员内部类也是最普通的内部类,它是外围类的一个成员,所以他是可以无限制的访问外围类的所有成员属性和方法,尽管是private的,但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。
public class OuterClass {
private String str;
public void outerDisplay(){
System.out.println("outerClass...");
}
public class InnerClass{
public void innerDisplay(){
str = "chenssy..."; //使用外围内的属性
System.out.println(str);
outerDisplay(); //使用外围内的方法
}
}
// 推荐使用getxxx()来获取成员内部类,尤其是该内部类的构造函数无参数时
public InnerClass getInnerClass(){
return new InnerClass();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.getInnerClass();
inner.innerDisplay();
}
}
--------------------
chenssy...
outerClass...
在成员内部类中要注意两点:
- 成员内部类中不能存在 static 方法,但是可以存在 static 域,前提是需要使用 funal关键字进行修饰
- 成员内部类式依附于外围类的,所以只有先创造看外围类才能够创建内部类。
局部内部类
有这样一种内部类,它是嵌套在方法和作用于内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类,局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。
//定义在方法里:
public class Parcel5 {
public Destionation destionation(String str){
class PDestionation implements Destionation{
private String label;
private PDestionation(String whereTo){
label = whereTo;
}
public String readLabel(){
return label;
}
}
return new PDestionation(str);
}
public static void main(String[] args) {
Parcel5 parcel5 = new Parcel5();
Destionation d = parcel5.destionation("chenssy");
}
}
//定义在作用域内:
public class Parcel6 {
private void internalTracking(boolean b){
if(b){
class TrackingSlip{
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip(){
return id;
}
}
TrackingSlip ts = new TrackingSlip("chenssy");
String string = ts.getSlip();
}
}
public void track(){
internalTracking(true);
}
public static void main(String[] args) {
Parcel6 parcel6 = new Parcel6();
parcel6.track();
}
}
(三)匿名内部类
匿名内部类也就是没有名字的内部类。正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写。但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口
实例1:不使用匿名内部类来实现抽象方法
abstract class Person {
public abstract void eat();
}
class Child extends Person {
public void eat() {
System.out.println("eat something");
}
}
public class Demo {
public static void main(String[] args) {
Person p = new Child();
p.eat();
}
}
运行结果:eat something
可以看到,我们用 Child 继承了 Person 类,然后实现了 Child 的一个实例,将其向上转型为 Person 类的引用
但是,如果此处的 Child 类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?
这个时候就引入了匿名内部类
实例2:匿名内部类的基本实现
abstract class Person {
public abstract void eat();
}
public class Demo {
public static void main(String[] args) {
Person p = new Person() {
public void eat() {
System.out.println("eat something");
}
};
p.eat();
}
}
运行结果:eat something
可以看到,我们直接将抽象类 Person 中的方法在大括号中实现了,这样便可以省略一个类的书写,并且,匿名内部类还能用于接口上。
实例3:在接口上使用匿名内部类
interface Person {
public void eat();
}
public class Demo {
public static void main(String[] args) {
Person p = new Person() {
public void eat() {
System.out.println("eat something");
}
};
p.eat();
}
}
运行结果:eat something
由上面的例子可以看出,只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现
最常用的情况就是在多线程的实现上,因为要实现多线程必须继承 Thread 类或是继承 Runnable 接口
实例4:Thread类的匿名内部类实现
public class Demo {
public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.print(i + " ");
}
}
};
t.start();
}
}
运行结果:1 2 3 4 5
实例5:Runnable接口的匿名内部类实现
public class Demo {
public static void main(String[] args) {
Runnable r = new Runnable() {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.print(i + " ");
}
}
};
Thread t = new Thread(r);
t.start();
}
}
运行结果:1 2 3 4 5
(四)静态内部类
关键字 static 中提到 static 可以修饰成员变量、方法、代码块,其他它还可以修饰内部类,使用 static 修饰的内部类我们称之为静态内部类,不过我们更喜欢称之为嵌套内部类。静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。
- 1.它的创建是不需要依赖于外围类的。
- 2.它不能使用任何外围类的非 static 成员变量和方法
public class OuterClass {
private String sex;
public static String name = "chenssy";
// 静态内部类
static class InnerClass1{
// 在静态内部类中可以存在静态成员
public static String _name1 = "chenssy_static";
public void display(){
// 静态内部类只能访问外围类的静态成员变量和方法
// 不能访问外围类的非静态成员变量和方法
System.out.println("OutClass name :" + name);
}
}
// 非静态内部类
class InnerClass2{
// 非静态内部类中不能存在静态成员
public String _name2 = "chenssy_inner";
// 非静态内部类中可以调用外围类的任何成员,不管是静态的还是非静态的
public void display(){
System.out.println("OuterClass name:" + name);
}
}
// 外围类方法
public void display(){
// 外围类访问静态内部类:内部类
System.out.println(InnerClass1._name1);
// 静态内部类 可以直接创建实例不需要依赖于外围类
new InnerClass1().display();
// 非静态内部的创建需要依赖于外围类
OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2();
// 方位非静态内部类的成员需要使用非静态内部类的实例
System.out.println(inner2._name2);
inner2.display();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.display();
}
}
----------------
Output:
chenssy_static
OutClass name :chenssy
chenssy_inner
OuterClass name:chenssy
6.组合、继承和代理的区别
定义
- 组合:在新类中new 另外一个类的对象,以添加该对象的特性。
- 继承:从基类继承得到子类,获得父类的特性。
- 代理:在代理类中创建某功能的类,调用类的一些方法以获得该类的部分特性。
使用场合
- 组合:各部件之间没什么关系,只需要组合即可。例如组织电脑,需要new CPU(),new RAM(),new Disk()
public class Computer{
pblic Conmputer(){
CPU cpu=new CPU();
RAM ram=new RAM();
Disk disk = new Disk();
}
}
class CPU{ }
class RAM{ }
class Disk{ }
- 继承:子类需要具有父类的功能,各子类之间有所差异。例如 Shape 类作为父类,子类有 Rectangle,CirCle…
- 代理:飞机控制类,我不想暴露太多飞机控制的功能,只需要部分前进左右转的控制(而不需要暴露发射导弹功能)。通过在代理类中 new一个飞机控制对象,然后在方法中添加飞机控制类的各个需要暴露的功能。
public class PlaneDelegation{
private PlaneControl planeControl; //private外部不可访问
// 飞行员权限代理类,普通飞行员不可以开火
PlaneDelegation(){
planeControl = new PlaneControl();
}
public void speed(){
planeControl.speed();
}
public void left(){
planeControl.left();
}
public void right(){
planeControl.right();
}
}
final class PlaneControl {// final表示不可继承,控制器都能继承那还得了
protected void speed() {}
protected void fire() {}
protected void left() {}
protected void right() {}
}
说明
- 继承:代码复用,引用不灵活;
- 组合:代码复用
- 接口:引用灵活;
- 推荐组合+接口使用,看IO中包装流 FilterInputStream 中的策略模式
7.什么是构造函数
构造函数是函数的一种特殊形式。特殊在哪里?构造函数中不需要定义返回类型(void是无须返回值的意思,请注意区分两种),且构造函数的名称与所在的类名完全一致,其余的与函数的特性相同,可以带有参数列表,可以存在函数的重载现象。
一般用来初始化一些成员变量,当要生成一个类的对象(实例)的时候就会调用类的构造函数。如果不显示声明类的构造方法,会自动生成一个默认的不带参数的空的构造函数。
public class Demo{
private int num=0;
//无参构造函数
Demo()
{
System.out.println("constractor_run");
}
//有参构造函数
Demo(int num)
{
System.out.println("constractor_args_run");
}
//普通成员函数
public void demoFunction()
{
System.out.println("function_run");
}
}
这这里要说明一点,如果在类中我们不声明构造函数,JVM会帮我们默认生成一个空参数的构造函数;如果在类中我们声明了带参数列表的构造函数,JVM就不会帮我们默认生成一个空参数的构造函数,我们想要使用空参数的构造函数就必须自己去显示的声明一个空参的构造函数。
构造函数的作用
通过开头的介绍,构造函数的轮廓已经渐渐清晰,那么为什么会有构造函数呢?构造函数有什么作用?构造函数是面向对象编程思想需求的,它的作用有一下两个:
- 创建对象。任何一个对象创建时,都需要初始化才能使用,所有任何类想要创建实例对象就必须具有构造函数。
- 对象初始化。构造函数可以对对象进行初始化,并且是给与之格式(参数列表)相符合的对象初始化,是具有一定针对性的初始化函数。
8.向上造型和向下造型
父类引用能指向子类对象,子类引用不能指向父类对象;
向上造型
父类引用指向子类对象,例如:
Father f1=new Son();
向下造型
把指向子类对象的父类引用赋给子类引用,需要强制转换,例如:
Father f1 = new Son();
Son s1 = (Son)f1;
但是运行出错的情况:
Father f2=new Father();
Son s2=(Son)f2; //编译无错但运行会出现错误
在不确定父类引用是否指向子类对象时,可以用instanceof 来判断:
if(f3 instanceof Son){
Son s3 = (Son) f3;
}
三、关键字
1.final 与 static 的区别
final
1. 数据
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
对于基本类型,final 使数值不变;
对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
final int x = 1;
// x = 2; // cannot assign value to final variable 'x'
final A y = new A();
y.a = 1;
2. 方法
声明方法不能被子类覆盖。
private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是覆盖基类方法,而是在子类中定义了一个新的方法。
3. 类
声明类不允许被继承。
static
1. 静态变量
静态变量在内存中只存在一份,只在类初始化时赋值一次。
- 静态变量:类所有的实例都共享静态变量,可以直接通过类名来访问它;
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
public class A {
private int x; // 实例变量
public static int y; // 静态变量
}
注意:不能再成员函数内部定义static变量。
2. 静态方法
静态方法在类加载的时候就存在了,它不依赖于任何实例,所以静态方法必须有实现,也就是说它不能是抽象方法(abstract)。
- 静态语句块
静态语句块在类初始化时运行一次。
- 静态内部类
内部类的一种,静态内部类不依赖外部类,且不能访问外部类的非静态的变量和方法。
- 静态导包
import static com.xxx.ClassName.*
在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
- 变量赋值顺序
静态变量的赋值和静态语句块的运行优先于实例变量的赋值和普通语句块的运行,静态变量的赋值和静态语句块的运行哪个先执行取决于它们在代码中的顺序。
public static String staticField = "静态变量";
static {
System.out.println("静态语句块");
}
public String field = "实例变量";
{
System.out.println("普通语句块");
}
最后才运行构造函数
public InitialOrderTest() {
System.out.println("构造函数");
}
存在继承的情况下,初始化顺序为:
父类(静态变量、静态语句块)
子类(静态变量、静态语句块)
父类(实例变量、普通语句块)
父类(构造函数)
子类(实例变量、普通语句块)
子类(构造函数)
2.break、continue、return
break
跳出当前循环;但是如果是嵌套循环,则只能跳出当前的这一层循环,只有逐层 break 才能跳出所有循环。
for (int i=0; i < 10; i++){
// 在执行i==6是强制终止循环,i==6不会被执行
if(i == 6 ){
break;
}
System.out.println(i);
}
输出结果:
continue
终止当前循环,但是不跳出循环(在循环中continue 后面的语句是不会执行了),请继续往下根据循环条件执行循环。
for (int i = 0; i < 10; i++) {
// i==6不会被执行,而是被中断了
if(i==6) {
continue;
}
System.out.println(i);
}
输出结果:
return
- return 从当前的方法中退出,返回到调用的方法的语句处,继续执行。
- return 返回一个值给调用该方法的语句,返回值的数据类型必须与方法的声明中的返回值的类型一致。
- return 后面也可以不带参数,不带参数就是返回空,其实主要目的就是用于想中断函数执行,返回调用函数处。
特别注意:返回值为void 的方法,从某个判断中跳出,必须用return。
3.final、finall和finalize区别
final
final 用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖和类不可被继承。
- final 属性:被final修饰的变量不可变(引用不可变)
- final 方法:不允许任何子类重写这个方法,但子类任然可以使用这个方法
- final参数:用来表示这个参数在这个函数内部不允许被修改
- final参数:此类不能被继承,所有方法都不能被重写
finally
是保证代码一定要被执行的一种机制.常用来关闭连接资源或者解锁等.
finalize
是Object的一个方法,它的目的是保证对象在被垃圾收集前完成特定资源的回收.1.9后已经过时
三、基本数据类型与运算
1、Java的基本数据类型和引用类型,自动装箱和拆箱
基本数据类型:4类8种基本数据类型。
4 整数类型,2 浮点类型,1 布尔型,1 字符型
引用类型
类(class)、接口(interface)、数组(array)
自动装箱和拆箱
- 基本数据类型和它对应的封装类型之间可以相互转换。自动拆装箱是 jdk5.0提供的新特性,它可以自动实现类型的转换
- 装箱:从基本数据类型到封装类型叫做装箱
- 拆箱:从封装类型到基本数据类型叫做拆箱
// jdk 1.5
public class TestDemo {
public static void main(String[] args) {
Integer m =10;
int i = m;
}
}
上面的代码在 jdk1.5 只后不会报错,1.5出现了自动装、拆箱。1.5之前会报错。