Java 笔记

数据类型和运算符

数据类型 字节数
int 4
long 8
char 2
float 4
double 8
bool 1

常量

普通常量

public class Test1 {

final int A = 20;
public static void main(String[] args) {
Test1 test1 = new Test1();
System.out.println(test1.A);
}

}

类常量

public class Test1 {

static final int B = 20;
public static void main(String[] args) {
System.out.println(B);
}
}

常量一般都定义为大写字母

运算符

public class Test2 {
public static void main(String[] args) {
int a = 30;
System.out.println(a++);
System.out.println(++a);
}
}

输出结果

30
32

数组

binarySearch(), 使用二分搜索法来搜索指定数组,以获得指定对象。

在进行此调用之前,必须根据元素的自然顺序对数组进行升序排序(通过 sort() 方法)

参数:

  • a – 要搜索的数组
  • key – 要搜索的值

返回:

  • 如果它包含在数组中,则返回搜索键的索引;
  • 否则返回 (-(插入点) – 1) 。
  • 插入点被定义为将键插入数组的那一点:即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,则为a.length
  • 注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
public class SearchArray
{
public static void main(String[] args)
{
int[] array = {2, 4, 6, 1, 3, 5, 9, 11, -5, 30, 100};
Arrays.sort(array);
int index = Arrays.binarySearch(array, 11);
System.out.println(Arrays.toString(array));
System.out.println("The index of 11 is "+index);
index = Arrays.binarySearch(array, 10);
System.out.println("The index of 10 is "+index);
}
}

输出结果

[-5, 1, 2, 3, 4, 5, 6, 9, 11, 30, 100]
The index of 11 is 8
The index of 10 is -9

String

  1. 串连接:+concat()
  2. 提取子字符串:substring()
  3. 提取字符:charAt()
  4. 获取字符串长度:length
  5. 判断字符串是否相等:equal()

有的登陆系统密码忽略大小写。此时java语言中有个方法就是equalsIgnoreCase(String str),这个方法忽略字符串大小写。

常见疑难: equals 和 == 的区别

java中的数据类型,可分为两类:

  1. 基本数据类型,也称原始数据类型。

byte,short,char,int,long,float,double,boolean

他们之间的比较,应用双等号(==),比较的是他们的值。

  1. 复合数据类型(类)

当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。 JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地址,但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。

对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。

StringBuffer

  1. 追加:append()
  2. 指定位置插入字符串:insert()
public class StringBufferTest {

public static void main(String[] args) {

StringBuffer strbuf1 = new StringBuffer();
System.out.println(strbuf1.capacity());
System.out.println(strbuf1.length());
}
}

输出结果

16
0

常见疑难 :String 和 StringBufffer 的区别

StringBuffer对象的内容可以修改;String对象一旦产生后就不可以被修改,重新赋值其实是两个对象。

StringBuffer的内部实现方式和String不同,StringBuffer在进行字符串处理时,不生成新的对象,在内存使用上要优于String类。所以在实际使用时,如果经常需要对一个字符串进行修改,例如插入、删除等操作,使用StringBuffer要更加适合一些。

String

在String类中没有用来改变已有字符串中的某个字符的方法,由于不能改变一个java字符串中的某个单独字符,所以在JDK文档中称String类的对象是不可改变的。然而,不可改变的字符串具有一个很大的优点:编译器可以把字符串设为共享的。

StringBuffer

StringBuffer类属于一种辅助类,可预先分配指定长度的内存块建立一个字符串缓冲区。这样使用StringBuffer类的append方法追加字符 比 String使用 + 操作符添加字符 到 一个已经存在的字符串后面有效率得多。因为使用 + 操作符每一次将字符添加到一个字符串中去时,字符串对象都需要寻找一个新的内存空间来容纳更大的字符串,这无凝是一个非常消耗时间的操作。添加多个字符也就意味着要一次又一次的对字符串重新分配内存。使用StringBuffer类就避免了这个问题。
StringBuffer是线程安全的,在多线程程序中也可以很方便的进行使用,但是程序的执行效率相对来说就要稍微慢一些。

类和对象

访问控制符:public ,private ,protected, default

private default protected public
同一类中可见
同一包中对子类可见
同一包中对非子类可见
不同包中对子类可见
不同包中对非子类可见

基础类 :Math,Date

重载和包

重载

重载的实质

  • 方法名相同
  • 参数个数可以不同
  • 参数类型可以不同
class overload {
// /一个普通的方法,不带参数
void test() {
System.out.println("No parameters");
}

// /重载上面的方法,并且带了一个整型参数
void test(int a) {
System.out.println("a: " + a);
}

// /重载上面的方法,并且带了两个参数
void test(int a, int b) {
System.out.println("a and b: " + a + " " + b);
}

// /重载上面的方法,并且带了一个双精度参数,与上面带一个参数的重载方法不一样
double test(double a) {
System.out.println("double a: " + a);
return a * a;
}

public static void main(String args[]) {
overload o = new overload(); //创建了对象o
o.test();
o.test(2);
o.test(2, 3);
o.test(2.0);
}
}

输出结果

No parameters
a: 2
a and b: 2 3
double a: 2.0

包 package

import com.stx8.test,相当于C#里的using语句,C++里的头文件及namespace

继承和多态

继承

extends

class People {
int a;

People() {
a = 1;
}

People(int a) {
this.a = a;
}
}

public class men extends People {
int b;

men(int a, int b) {
super(a);
this.b = b;
}

public static void main(String[] args) {
men m = new men(10, 20); //通过带参构造函数创建men类对象
System.out.println(m.a + " " + m.b); //调用对象的成员变量a和b
}
}

设计类的继承时的建议

不要使用受保护字段,即protected字段

如果不希望自己的类再被扩展,可以在类的声明之前加上final关键字。

构造函数

  • 构造函数名字与类名相同(包括大小写)
  • 一个类可以有多个构造函数
  • 构造函数没有返回值,也不用写void关键字
  • 构造函数总和new运算符一起被调用
class Test3_super{
public Test3_super() {
// TODO 自动生成的构造函数存根
System.out.println("父类构造函数");
}
public void fun1(){
System.out.println("父类F1");
}
protected void fun2() {
System.out.println("父类F2");
}
private void fun3() {
System.out.println("父类F3");
}

}

public class Test3 extends Test3_super{

Test3(){
super();
System.out.println("子类构造函数");
}
public void fun2() {
System.out.println("子类F2");

}
public static void main(String[] args) {

Test3 test3 = new Test3();
test3.fun1();
test3.fun2();

}
}

输出结果

父类构造函数
子类构造函数
父类F1
子类F2

java 的单继承性

针对同一方法,子类的访问控制权限只能等于或大于父类。

类之间的关系

多态

多态一定要遵守两个规则:

  • 方法名称一定要一样
  • 传入参数的类型一定要不一样

多态的两种表现形势

  • 重载
  • 覆盖

接口 interface

接口中只有方法名,没有具体实现的方法体。

接口的声明默认是 public,有时候也可以省略。

类实现接口是要使用 implements关键字

在类实现接口时要注意:

  • 声明类需要实现指定接口
  • 提供接口中所有方法的定义
package com.interfacetest;

interface Alarm {
void alarm();
}

class AlarmDoor implements Alarm {

void open() {
System.out.println("door open");
}
void close() {
System.out.println("door close");
}
public void alarm() {
System.out.println("a door with alarm");
}
}

public class InterfaceTest {

public static void main(String[] args) {

AlarmDoor alarmDoor = new AlarmDoor();
alarmDoor.alarm();
}
}

接口的多重实现

为解决java类中的单继承,一个类可以实现一个接口,也可以实现其他接口。

package com.interfacetest;

interface Alarm {
void alarm();
}

interface Window {
void window();
}

class AlarmDoor implements Alarm, Window {

public void window() {
System.out.println("a door with window");
}

public void alarm() {
System.out.println("a door with alarm");
}
}

public class InterfaceTest2 {

public static void main(String[] args) {

AlarmDoor alarmDoor = new AlarmDoor();
alarmDoor.alarm();
}
}

接口的属性

接口不是一个类,不可以使用关键字new来生成一个接口的实例。但是可以声明一个接口变量 “school sc”.

如果要生成一个接口的实例,可以让接口变量指向一个已经实现该接口的类的对象。

school sc = new student();

在接口中,不能声明实例字段及静态方法,但可以声明常量。接口不一定要有方法,可以全是常量。

接口的继承

和类一样,使用extends关键字实现

package com.interfacetest;

interface Lock {
void lock();
}

interface HighLock extends Lock{
void hignlock();
}

class Door implements HighLock {

@Override
public void hignlock() {
// TODO 自动生成的方法存根
System.out.println("WOW high lock");
}
@Override
public void lock() {
// TODO 自动生成的方法存根
System.out.println("just lock");
}
}

public class InterfaceTest3 {

public static void main(String[] args) {

Door door = new Door();
door.lock();
}
}

内部类

内部类就是在类的内部再创建一个类。

内部类的基本结构

package com.interclass;

//外部类
class Out {
private int age = 12;

//内部类
class In {
public void print() {
System.out.println(age);
}
}
}

public class InterclassTest1 {
public static void main(String[] args) {

Out.In in = new Out().new In();
in.print();

Out out = new Out();
Out.In in2 = out.new In();
in2.print();

}
}

匿名内部类

匿名内部类也就是没有名字的内部类,正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写。但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口

不使用匿名内部类来实现抽象方法:

package com.interclass;

abstract class Person {
public abstract void eat();
}

class Child extends Person {
public void eat() {
System.out.println("eat something");
}
}

public class InterclassTest2 {
public static void main(String[] args) {
Person p = new Child();
p.eat();
}
}

运行结果:

eat something

可以看到,我们用Child继承了Person类,然后实现了Child的一个实例,将其向上转型为Person类的引用,但是,如果此处的Child类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?

这个时候就引入了匿名内部类:

package com.interclass;

abstract class Person1 {
public abstract void eat();
}

public class InterclassTest3 {
public static void main(String[] args) {
Person p = new Person() {
public void eat() {
System.out.println("eat something");
}
};
p.eat();
}
}

运行结果:

eat something

由上面的例子可以看出,只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现

最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口

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(); } }

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(); } } ``` 内部类的好处: - 内部类的对象能够访问创建他的对象的所有方法和属性,包括私有数据 - 对于同一个包中的其他类来说,内部类是隐形的 。 - 匿名内部类可以很方便的定义回调 - 使用内部类可以方便的编写时间驱动的程序。 作为一个单独的类,只能有 `default` 和 `public` 两种访问控制符,但是作为内部类,可以使用 `private` 控制符。当内部类设置为 `private` 时,包含此内部类的外部类的方法才可以访问它。 ### 使用内部类来访问对象 内部类这个机制之所以出现,是因为存在如下两个目的: - 可以让程序中逻辑上相关的类结合在一起 - 内部类可以直接访问外部类的成员。 ## 常见疑难 接口与继承的区别: - 属性:接口中的所有属性都是公开静态常量,继承则无所谓 - 方法:接口中所有方法都是公开抽象方法,继承中所有方法不一定是抽象的 - 接口方法:接口没有构造器,继承有构造器 ## 抽象和封装 抽象就是讲拥有共同方法和属性的对象提取出来,提取后,重新设计一个更加通用、更加大众的类,这个类成为抽象类。 ### 抽象类 `abstract` 具有一个或多个抽象方法的类,本身就要被定义为抽象类。含有抽象方法的类一定是抽象类。 抽象类不仅可以有抽象方法,也可以又具体的方法,一个类中只要有一个抽象方法,就是抽象类。 抽象类中不一定含有抽象的方法,也可以全部都是具体的方法。 抽象类是可以继承的,如果子类没有实现抽象类的全部抽象方法,那么子类也是抽象类。如果实现了抽象类的全部抽象方法,那么子类就不是抽象类。 抽象类不可以被实例化。但是可以声明一个抽象类的变量指向具体子类的对象。 抽象类的好处在于,有的方法在父类中不想实现时,可以不用实现。 ```java package com.abstracttest; abstract class Door{ abstract void open(); abstract void close(); } class MyDoor extends Door{ @Override void open() { // TODO 自动生成的方法存根 System.out.println("i can open"); } @Override void close() { // TODO 自动生成的方法存根 System.out.println("i can close"); } } public class AbstractTest1 { public static void main(String[] args) { MyDoor myDoor = new MyDoor(); myDoor.close(); } } ``` ## 常见疑难 抽象与接口的区别: 共同点 - 都不能创建实例对象 - 可以声明变量,通过指向子类或实现类的对象类,来创建对象实例。 不同点 - Java 不支持多重继承,即一个子类只能有一个父类,但一个子类可以实现多个接口。 - 接口内不能有实例字段,只能有静态变量,抽象类可以拥有实例字段 - 接口内方法自动设置为 `public`的,抽象类中的方法必须手动声明访问控制符。 ## 枚举 `enum` ## 反射 程序自己能够检查自身信息。反射使得java语言具有了“动态性”,即程序首先会检查某个类中的方法、属性等信息,然后再动态的调用或动态的创建该类或该类的对象。 ### 反射类的基石 ――`Class`类 任何事物都可以用类表示,那么java中的类可以用一个什么类表示呢? 从JDK1.2开始,就出现了Class类,该类描述Java中的一切事物,该类描述了关于类事务的类名字、类的访问属性、类所属的包名等。 ### 反射的基本应用 所谓反射就是把java类中的各种成分映射成相应的java类。 不仅java类,可以用`Class`类的对象表示,而java类的各种成员:成员变量、方法、构造方法、包等也可以用相应的类表示。 Java反射机制主要提供了以下功能: - 在运行时判断任意一个对象所属的类; - 在运行时构造任意一个类的对象; - 在运行时判断任意一个类所具有的成员变量和方法; - 在运行时调用任意一个对象的方法。 反射一般会设计如下类 : - `Class` : 表示一个类的类 - `Filed` : 表示属性的类 - `Method` : 表示方法的类 - `Constructor` : 表示类的构造方法的类 &gt; `Class`类位于java.lang包中,而后面3个的类都位于java.lang.reflect包中

编写Java反射程序的步骤:

- 必须首先获取一个类的Class对象
- 然后分别调用Class对象中的方法来获取一个类的属性/方法/构造方法的结构

现有一个类:

```java
package com.reflection;
public class TestReflection {

private String username;
private String password;
private int[] age;

public void setUserName(String username) {
this.username = username;
}

private void setPassWord(String password) {
this.password = password;
}
}
</code></pre>

获取一个类的Class对象:

<pre><code class="java">TestReflection tfReflection = new TestReflection();
Class c1 = TestReflection.class;
Class c2 = Class.forName("com.reflection.TestReflection");
Class c3 = tfReflection.getClass();
</code></pre>

获取指定的包名:

<pre><code class="java">String package01 = c1.getPackage().getName();
</code></pre>

获取类的修饰符:

<pre><code class="java">int mod = c1.getModifiers();
String modifier = Modifier.toString(mod);
</code></pre>

获取指定类的完全限定名:

<pre><code class="java">String className = c1.getName();
</code></pre>

获取指定类的父类:

<pre><code class="java">Class superClazz = c1.getSuperclass();
String superClazzName = superClazz.getName();
</code></pre>

获取实现的接口:

<pre><code class="java">Class[] interfaces = c1.getInterfaces();
for (Class t : interfaces) {
System.out.println("interfacesName = " + t.getName());
}
</code></pre>

获取指定类的成员变量:

<pre><code class="java">Field[] fields = c1.getDeclaredFields();
for (Field field : fields) {
modifier = Modifier.toString(field.getModifiers()); // 获取每个字段的访问修饰符
Class type = field.getType(); // 获取字段的数据类型所对应的Class对象
String name = field.getName(); // 获取字段名
if (type.isArray()) { // 如果是数组类型则需要特别处理
String arrType = type.getComponentType().getName() + "[]";
System.out.println("" + modifier + " " + arrType + " " + name + ";");
}
else {
System.out.println("" + modifier + " " + type + " " + name + ";");
}
}
</code></pre>

获取类的构造方法:

<pre><code class="java">Constructor[] constructors = c1.getDeclaredConstructors();
for (Constructor constructor : constructors) {
String name = constructor.getName(); // 构造方法名
modifier = Modifier.toString(constructor.getModifiers()); // 获取访问修饰符
System.out.println("" + modifier + " " + name + "(");
Class[] paramTypes = constructor.getParameterTypes(); // 获取构造方法中的参数
for (int i = 0; i < paramTypes.length; i++) { if (i > 0) {
System.out.print(",");
}
if (paramTypes[i].isArray()) {
System.out.println(paramTypes[i].getComponentType().getName() + "[]");
} else {
System.out.print(paramTypes[i].getName());
}
}
System.out.println(");");
}
</code></pre>

获取成员方法:

<pre><code class="java">Method[] methods = c1.getDeclaredMethods();
for (Method method : methods) {
modifier = Modifier.toString(method.getModifiers());
Class returnType = method.getReturnType(); // 获取方法的返回类型
if (returnType.isArray()) {
String arrType = returnType.getComponentType().getName() + "[]";
System.out.print("" + modifier + " " + arrType + " " + method.getName() + "(");
} else {
System.out.print("" + modifier + " " + returnType.getName() + " " + method.getName() + "(");
}
Class[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) { if (i > 0) {
System.out.print(",");
}
if (paramTypes[i].isArray()) {
System.out.println(paramTypes[i].getComponentType().getName() + "[]");
} else {
System.out.print(paramTypes[i].getName());
}
}
System.out.println(");");
}
</code></pre>

反射调用方法,可以通过Method类的invoke方法实现动态方法的调用:

<code>public Object invoke(Object obj, Object... args)</code>
第一个参数代表对象
第二个参数代表执行方法上的参数

若反射要调用类的某个私有方法,可以在这个私有方法对应的Mehtod对象上先调用<code>setAccessible(true)</code>

<pre><code class="java">public static void test02() throws InstantiationException, IllegalAccessException, SecurityException,
NoSuchMethodException, IllegalArgumentException, InvocationTargetException {

Class c1 = TestReflection.class;
TestReflection t1 = (TestReflection) c1.newInstance(); // 利用反射来创建类的对象

Method method = c1.getDeclaredMethod("setUserName", String.class);
method.invoke(t1, "Java反射的学习");
method = c1.getDeclaredMethod("setPassWord", String.class);
method.setAccessible(true);
method.invoke(t1, "反射执行某个Private修饰的方法");

}

</code></pre>

<h3>反射的一些应用</h3>

既然String是不可变字符串对象,如何才能改变让其可变?[反射的一些应用][1]

<pre><code class="java">public static void stringReflection() throws Exception {

String s = "Hello World";

System.out.println("s = " + s); //Hello World

//获取String类中的value字段
Field valueField = String.class.getDeclaredField("value");

//改变value属性的访问权限
valueField.setAccessible(true);

char[] value = (char[]) valueField.get(s);

//改变value所引用的数组中的第5个字符
value[5] = '_';

System.out.println("s = " + s); //Hello_World
}
</code></pre>

既然String对象中没有对外提供可用的public setters等方法,因此只能通过Java中的反射机制实现。因此,前文中说到的String是不可变字符串对象只是针对“正常情况下”。而非必然。

<blockquote>
  Java的反射机制的概念:
  在Java运行时环境中,对于任意一个类,能否知道这个类的哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法?答案是肯定的。这种动态获取类的信息以及动态调用对象的方法的功能来自于Java语言的反射(Reflection)机制。
  
  Reflection是Java被视为动态(准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括modifiers(诸如static、public等等)、superclass(例如Object)、实现interfaces(例如Serializable),也包括fields和methods的所有信息,并可于运行时改变fields内容或调用methods。
</blockquote>

<h2>标注 Annotation</h2>

在实际的应用中期可以部分或全部的取代传统的XML等部署描述文件。之所以要出现标注特性,是因为部署描述文件很复杂,在具体编写时很容易出错。

<h3>标注的简单使用</h3>

<code>@SuppressWarning</code>

<pre><code class="java">public class SimpleAnnotation {
@SuppressWarnings("deprecation")
public static void main(String[] args) {
System.runFinalizersOnExit(true);
}
}
</code></pre>

<h3>几个简单的内置标注</h3>

<code>@Override</code>

<pre><code class="java">package com.Annotation;

class People{
public String toString() {
return "people name";
}
}

class Student extends People{
@Override
public String toString() {
return "student name";

}
}
public class Annotation_Test {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.toString());
}

}
</code></pre>

<blockquote>
  <code>@Override</code> 是方法标注,只能作用于方法,在覆盖父类方法却又写错了方法名的时候发挥作用。
</blockquote>

<code>@Deprecated</code>

很多时候,设计了一个包含<code>sayHello()</code>方法的类<code>Hello.java</code>,但是经过一段时间发现,<code>sayHello1()</code>可以更好更快的实现相同的功能。但是这个时候如果去掉方法<code>sayHello()</code>,那么调用该方法的类就会出现错误。为了兼容之前的类,而又不建议新设计的类使用方法<code>sayHello()</code>,就需要把<code>Hello.java</code>中的方法<code>sayHello()</code>做<code>@Deprecated</code>标注。

<pre><code class="java">package com.Annotation;

class Hello{

@Deprecated
public void sayHello(){
System.out.println("已经过时的方法");
}
public void sayHello1() {
System.out.println("现在的方法");
}
}
public class Deprecated_Test {
public static void main(String[] args) {
Hello hello = new Hello();
hello.sayHello();
hello.sayHello1();

}

}
</code></pre>

当一个类或者类成员使用<code>@Deprecated</code>修饰的话,编译器将不鼓励使用这个被标注的程序元素,而且这种修饰具有一定的“延续性”,即在代码中通过继承或者覆盖使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型并不是被声明为<code>@Deprecated</code>,但是编译器仍要报警。

<blockquote>
  <code>@Deprecated</code>标注不仅可以用在方法前面,而且可以用在参数或类的前面。
</blockquote>

<code>@SuppressWarning</code>

可以用作标注类,属性、方法等成员,主要用于屏蔽警告。该标注于前面两个标注最大不同点在于其带有参数,并且参数可以是一个,可以是多个。参数的值为警告的类型。如:

<ul>
<li>已过时的警告 : deprecation</li>
<li>没有使用警告 : unused</li>
<li>类型不安全警告 : unchecked</li>
</ul>

<blockquote>
  当<code>@SuppressWarning</code> 接收的参数为多个值得时候,必须使用数组的方式为参数赋值。例如@SuppressWarning({"deprecation","unused","unchecked"})
</blockquote>

<h2>泛型 <code>&lt;E&gt;</code></h2>

所谓泛型,其本质就是实现参数化类型,也就是说所操作的数据类型被指定为一个参数。

<h3>泛型概念的提出:</h3>

<pre><code class="java">public class GenericTest {

public static void main(String[] args) {
List list = new ArrayList();
list.add("qqyumidi");
list.add("corn");
list.add(100);

for (int i = 0; i < list.size(); i++) { String name = (String) list.get(i); // 1 System.out.println("name:" + name); } } } ``` 运行结果 name:qqyumidi name:corn Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at com.fan.GenericTest.main(GenericTest.java:15) &gt; Java2的集合框架,抽其核心,主要有三种:List、Set和Map。
需要注意的是,这里的 Collection、List、Set和Map都是接口(Interface),不是具体的类实现。 List lst = new ArrayList(); 这是我们平常经常使用的创建一个新的List的语句,在这里, List是接口,ArrayList才是具体的类。
常用集合类的继承结构如下:
Collection&lt;--List&lt;--Vector
Collection&lt;--List&lt;--ArrayList
Collection&lt;--List&lt;--LinkedList
Collection&lt;--Set&lt;--HashSet
Collection&lt;--Set&lt;--HashSet&lt;--LinkedHashSet
Collection&lt;--Set&lt;--SortedSet&lt;--TreeSet
Map&lt;--SortedMap&lt;--TreeMap
Map&lt;--HashMap

定义了一个List类型的集合,先向其中加入了两个字符串类型的值,随后加入一个Integer类型的值。这是完全允许的,因为此时list默认的类型为Object类型。在之后的循环中,由于忘记了之前在list中也加入了Integer类型的值或其他编码原因,很容易出现类似于//1中的错误。因为编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常。因此,导致此类错误编码过程中不易发现。

在如上的编码过程中,我们发现主要存在两个问题:

1.当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,改对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。

2.因此,//1处取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现“java.lang.ClassCastException”异常。

那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就不会出现“java.lang.ClassCastException”异常呢?答案就是使用泛型。

### 什么是泛型?

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

```java
public class GenericTest {

public static void main(String[] args) {
/*
List list = new ArrayList();
list.add("qqyumidi");
list.add("corn");
list.add(100);
*/

List<string> list = new ArrayList<string>();
list.add("qqyumidi");
list.add("corn");
//list.add(100); // 1 提示编译错误</string></string>

for (int i = 0; i < list.size(); i++) {
String name = list.get(i); // 2
System.out.println("name:" + name);
}
}
}

自定义泛型接口、泛型类和泛型方法

public class GenericTest1 {

public static void main(String[] args) {

Box<string> name = new Box<string>("corn");
Box<integer> num = new Box<integer>(4);
System.out.println("name:" + name.getData());
System.out.println("num:" + num.getData());
}</integer></integer></string></string>

}

class Box<t> {</t>

private T data;

public Box() {

}

public Box(T data) {
this.data = data;
}

public T getData() {
return data;
}
}

运行结果

name:corn
num:4

在泛型接口、泛型类和泛型方法的定义过程中,我们常见的如T、E、K、V等形式的参数常用于表示泛型形参,由于接收来自外部使用时候传入的类型实参。那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?

public class GenericTest {

public static void main(String[] args) {

Box<string> name = new Box<string>("corn");
Box<integer> age = new Box<integer>(712);</integer></integer></string></string>

System.out.println("name class:" + name.getClass()); // com.qqyumidi.Box
System.out.println("age class:" + age.getClass()); // com.qqyumidi.Box
System.out.println(name.getClass() == age.getClass()); // true

}

}

运行结果

name class:class com.fan.Box
num class:class com.fan.Box
true

由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。

究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

关于泛型通配符

接着上面的结论,我们知道,Box<Number>和Box<Integer>实际上都是Box类型,现在需要继续探讨一个问题,那么在逻辑上,类似于Box&lt;Number&gt;Box&lt;Integer&gt;是否可以看成具有父子关系的泛型类型呢?

所有的包装类(Integer、Long、Byte、Double、Float、Short)都是抽象类Number的子类。

关于泛型的一些特性

public class GenericTest {

public static void main(String[] args) {

Box<number> name = new Box<number>(99);
Box<integer> age = new Box<integer>(712);</integer></integer></number></number>

getData(name);
getData(age); // 1

}

public static void getData(Box<number> data){
System.out.println("data :" + data.getData());
}</number>

}

通过提示信息,我们知道Box&lt;Number&gt;在逻辑上不能视为Box&lt;Integer&gt;的父类.

我们需要一个在逻辑上可以用来表示同时是Box&lt;Integer&gt;Box&lt;Number&gt;的父类的一个引用类型,由此,类型通配符应运而生。

类型通配符一般是使用 ?代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box&lt;?&gt;在逻辑上是Box&lt;Integer&gt;Box&lt;Number&gt;…等所有Box&lt;具体类型实参&gt;的父类。由此,我们依然可以定义泛型方法,来完成此类需求。

public class GenericTest {

public static void main(String[] args) {

Box<string> name = new Box<string>("corn");
Box<integer> age = new Box<integer>(712);
Box<number> number = new Box<number>(314);</number></number></integer></integer></string></string>

getData(name);
getData(age);
getData(number);
}

public static void getData(Box<?> data) {
System.out.println("data :" + data.getData());
}

}

有时候,我们还可能听到类型通配符上限和类型通配符下限。具体有是怎么样的呢?

在上面的例子中,如果需要定义一个功能类似于getData()的方法,但对类型实参又有进一步的限制:只能是Number类及其子类。此时,需要用到类型通配符上限。

public class GenericTest {

public static void main(String[] args) {

Box<string> name = new Box<string>("corn");
Box<integer> age = new Box<integer>(712);
Box<number> number = new Box<number>(314);</number></number></integer></integer></string></string>

getData(name);
getData(age);
getData(number);

//getUpperNumberData(name); // 1
getUpperNumberData(age); // 2
getUpperNumberData(number); // 3
}

public static void getData(Box<?> data) {
System.out.println("data :" + data.getData());
}

public static void getUpperNumberData(Box<? extends Number> data){
System.out.println("data :" + data.getData());
}

}

此时,显然,在代码//1处调用将出现错误提示,而//2 //3处调用正常。

类型通配符上限通过形如Box<? extends Number>形式定义,相对应的,类型通配符下限为Box<? super Number>形式,其含义与类型通配符上限正好相反,在此不作过多阐述了。

封装

线程

程序是计算机指令集合,它以文件形式存储在磁盘上

进程就是一个执行中的程序,每一个进程都有一个独立的内存空间和资源系统。

线程是CPU调度和分配的基本单位,一个进程可以由多个线程组成,而这多个线程共享同一个存储空间,这使得线程间的通信比较容易。

线程的创建

创建线程的方法有两种 :

  • 通过实现Runnable接口的方式创建线程
  • 通过集成Threa类来创建线程

通过实现Runnable接口的方式创建线程

在java中,线程是一种对象,而不是所有的对象都可以被称为线程,只有实现了Runnable接口的对象才可以被称为线程。

Runnable接口的定义:

public interface new Runnable() {
public abstract void run() ;
}

只有实现了该接口的类才有资格被称为线程。

package com.threadtest;

class ThreadTest implements Runnable {
public void run() {
System.out.println("thread 1");
}
}
class ThreadTest2 implements Runnable{
public void run() {
System.out.println("thread 2");
}
}
public class ThreadTest1 {
public static void main(String[] args) {
ThreadTest test = new ThreadTest();
ThreadTest2 test2 = new ThreadTest2();
Thread thread = new Thread(test);
Thread thread2 = new Thread(test2);
thread.start();
thread2.start();
}

}

在java技术中,线程通常是通过调度模块来执行的。所谓抢占式调度模式是指:许多线程处于可以运行状态,即等待状态,但实际只有一个线程在运行。该线程一直运行直到他终止或是另一个优先级更高的线程变成可运行状态。

通过继承Thread类的方式创建线程

其实Thread本身也实现了Runnable接口,所以只要让一个类继承Thread类,并覆盖run()方法,也会创建进程。


package com.threadtest; class Threadtest_3 extends Thread { public void run() { System.out.println("thread 1"); } } class ThreadTest_4 extends Thread{ public void run() { System.out.println("thread 2"); } } public class ThreadTest2 { public static void main(String[] args) { Threadtest_3 threadtest_3 =new Threadtest_3(); ThreadTest_4 threadTest_4 = new ThreadTest_4(); Thread thread3 = new Thread(threadtest_3); Thread thread4 = new Thread(threadTest_4); thread3.start(); thread4.start(); } }

线程的状态

线程包括5种状态:新建状态、就绪状态、运行状态、阻塞状态和死亡状态。

新建状态

线程对象通过new关键字已经建立,在内存中有一个活跃的对象,但是没有启动该线程,所以它仍不能做任何事情,此时线程处于新建状态,程序中没有运行线程中的代码,如果线程要运行需要处于就绪状态。

就绪状态

一个线程一旦调用了start()方法,该线程就处于就绪状态。此时线程等待CPU时间片,一旦获得CPU时间周期,一旦获得CPU周期,线程就可以执行。这种状态下的任何时刻,线程是否执行完全取决于系统的调度程序。

运行状态

一旦处于就绪状态的线程获得CPU执行周期,就处于运行状态,执行多线程代码部分的运算。线程一旦运行,只是在CPU周期内获得执行权利,而一旦CPU的时间片用完,操作系统会给其他的线程运行的机会,而剥夺当前线程的执行。在选择哪个线程可以执行时,操作系统的调度程序会考虑现成的优先级,该内容后续讲解。

阻塞状态

该状态下线程无法运行,必须满足一定条件条件后才可以执行。如果线程处于阻塞状态,JVM调度机不会为其分配CPU周期。而线程满足一定条件就被解除阻塞,线程处于就绪状态,此时就获得了被执行的机会。当发生以下情况的时候线程会进入阻塞状态:

  • 线程正在等待一个输入输出操作,该操作完成前不会返回其调用者。
  • 线程调用了wait()方法或是sleep()方法。
  • 线程需要满足某种条件之后可以继续执行。

死亡状态

线程一旦退出run()方法就处于死亡状态。

线程的优先级

线程的执行顺序是一种抢占方式,优先级高的比优先级低的要获得更多的执行时间,如果想让一个线程比其他线程有更多的运行时间,可以通过设置线程的优先级解决。

具体方法如下:

public final void setPriority(int newPriority);

其中,newPriority是一个1~10的正整数,数值越大,优先级别越高。系统定义了一些常用的数值如下:

  • public final static int MIN_PRIORITY = 1 :表示最低优先级
  • public final static int MAX_PRIORITY = 10 :表示最高优先级
  • public final static int NORM_PRIORITY = 5 :表示默认优先级
package com.threadtest;

import org.omg.CORBA.PUBLIC_MEMBER;

class Threadtest_3 extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}

}
}
class ThreadTest_4 extends Thread{
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hello"+ i);
}

}
}
public class ThreadTest2 {
public static void main(String[] args) {

Threadtest_3 threadtest_3 =new Threadtest_3();
ThreadTest_4 threadTest_4 = new ThreadTest_4();

Thread thread3 = new Thread(threadtest_3);
Thread thread4 = new Thread(threadTest_4);

thread3.setPriority(3);
thread4.setPriority(Thread.MIN_PRIORITY);

thread3.start();
thread4.start();

}
}

线程的休眠与唤醒

线程的休眠 sleep()

线程处于等待状态。

package com.cjgong.avd;
///这是一个主运行类
///创建一个线程对象,让其运行
public class thread5
{
public static void main(String[] args)
{
compute27 t=new compute27();
t.start();
}
}
///创建一个线程类,在这个类中,通过休眠来输出不同结果
class compute27 extends Thread
{
int i=0;
public void run()
{
System.out.println("在工作中,不要打扰");
try
{
sleep(1000000);
}
catch(Exception e)
{
System.out.println("哦,电话来了");
}
}
}

线程唤醒 interrup()

当一个线程处于休眠状态,如果开始设置了休眠时间是1000ms,但是想在休眠了500ms的时候,让它继续执行,这时候就可以使用线程唤醒功能。

package com.cjgong.avd;
public class thread6
{
public static void main(String[] args)
{
compute28 t=new compute28();
t.start();
t.interrupt();
}
}
///创建一个线程类,在这个类中,通过休眠,让线程运行输出不同的结果
class compute28 extends Thread
{
int i=0;
public void run()
{
System.out.println("在工作中,不要打扰");
try
{
sleep(1000000);
}
catch(Exception e){System.out.println("哦,电话来了");}
}
}

线程让步 yield()

所谓线程让步,就是使当前正在运行的线程对象退出运行状态,让其他线程运行。

这个方法不能讲运行权让给指定的线程,只是允许这个线程把运行权让出来,至于给谁,就看谁能抢到。

package com.cjgong.avd;
///这是一个主运行类
///在主运行方法中,通过创建两个线程对象,让其交替执行
public class thread7
{
public static void main(String[] args)
{
compute29 t=new compute29();
compute30 t1=new compute30();
t.start();
t1.start();
}
}
///创建一个线程类
///通过循环语句来输出十个整型数据
///通过让步程序让此线程停止运行
class compute29 extends Thread
{
int i=0;
public void run()
{
for(int i=0;i<10;i++)
{
System.out.println(i);
yield();
}
}
}
///创建一个线程类
///通过循环语句来输出说明语句
class compute30 extends Thread
{
public void run()
{
for(int i=0;i<10;i++)
{
System.out.println("这个数字是:"+i);
}
}
}

线程同步

package com.cjgong.avd;
//这是一个主运行类
///在主运行方法中,通过创建两个线程对象,让其交替执行
public class thread8
{
public static void main(String[] args)
{
compute31 t=new compute31('a');
compute31 t1=new compute31('b');
t.start();
t1.start();
}
}
///创建一个线程类
///在这线程类中,使用循环语句输出字符
class compute31 extends Thread
{
char ch;
compute31(char ch)
{
this.ch=ch;
}

public void run()
{
for(int i=0;i<10;i++)
{
System.out.print(ch);
}
}
}

两个线程循环输出,就会出现抢占现象。解决这个问题的办法是可以使用线程同步,解决同步的两个方法:

  • 同步块
  • 同步化方法

同步块

同步块是使具有某个对象监视点的线程,获得运行权限的一种方法,每个对象只能在拥有这个监视点的情况下,才能获得运行权限。

同步块的结构如下:

synchronized (bObject) {
程序段
}

package com.cjgong.avd;
//这是一个主运行类
///在主运行方法中,通过创建两个线程对象,让其交替执行
public class thread8
{
public static void main(String[] args)
{
compute31 t=new compute31('a');
compute31 t1=new compute31('b');
t.start();
t1.start();
}
}
///创建一个线程类
///在这线程类中,使用循环语句输出字符
class compute31 extends Thread
{
char ch;
static Object bObject = new Object();
compute31(char ch)
{
this.ch=ch;
}

public void run()
{
synchronized (bObject) {

for(int i=0;i<10;i++) { System.out.print(ch); } } } } ``` ### 同步化方法 同步化方法就是对整个方法进行同步: 结构如下: synchronized void f() { 代码 } ```java //这是一个主运行类 ///在主运行方法中,通过创建三个线程对象,让其交替执行 public class thread11 { public static void main(String[] args) { compute34 t=new compute34(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } } ///创建一个线程类 ///在这线程类中,使用循环语句输出字符 ///在run方法中,使用同步块来给线程加一把锁 class compute34 extends Thread { int i=10; static Object obj=new Object(); public void print() { System.out.println(Thread.currentThread().getName()+":"+i); i--; } public void run() { while(i&gt;0) { synchronized(obj) { print(); } try { sleep(1000); } catch(Exception e){} } } } ``` ## 异常的处理和内存管理 ## Java 输入和输出 Java程序类库中包含大量的输入输出类,提供不同情况的不同功能。其中包括: - 关于文件操作的类 `File` - 关于以字节方式访问文件的类 `InputStream`和类`OutStream` - 关于以字符方式访问文件的类`Reader`和类`Writer` &gt; 在编写程序的过程中,若是要使用输入输出类的方法和属性值,就需要引入`java.io`类

## Java 的I/O操作

Java中I/O操作主要是指使用Java进行输入,输出操作.

Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列。Java的I/O流提供了读写数据的标准方法。任何Java中表示数据源的对象都会提供以数据流的方式读写它的数据的方法。

总结的基本概念如下:

数据流:

一组有序,有起点和终点的字节的数据序列。包括输入流和输出流。

输入流:

程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道

输出流:

程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道。

采用数据流的目的就是使得输出输入独立于设备。

数据流分类:

流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种:
1) 字节流:数据流中最小的数据单元是字节
2) 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。

### 标准I/O

### java.IO层次体系结构

在整个Java.io包中最重要的就是5个类和一个接口。

5个类指的是`File`、`OutputStream`、`InputStream`、`Writer`、`Reader`;

1个接口指的是`Serializable`.

掌握了这些IO的核心操作那么对于Java中的IO体系也就有了一个初步的认识了。

主要的类如下:

File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。

Java中字符是采用Unicode标准,一个字符是16位,即一个字符使用两个字节来表示。为此,JAVA中引入了处理字符的流。

Reader(文件格式操作):抽象类,基于字符的输入操作。
Writer(文件格式操作):抽象类,基于字符的输出操作。
RandomAccessFile(随机文件操作):它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。

### 文件或目录信息的处理 `File`

`File` 类提供了与文件或目录相关的信息

文件处理方法:

```java
package com.filetest;

import java.io.File;

public class FileTest1 {
public static void main(String[] args) {

File file = new File("d:\\","file.txt");
System.out.println(file.getName());
System.out.println(file.getParent());
System.out.println(file.getPath());
}
}

运行结果:

file.txt
d:\
d:\file.txt

文件和目录的操作:

在Java中,目录被当做一种特殊的文件使用。类File是唯一代表磁盘文件对象的类。

package com.cjgong.chaozuo;

import java.io.*;

///通过print方法来判断这个文件类对象的性质
///通过print1方法来获取文件对象的信息
///通过print2方法来获取文件对象的信息
public class file1 {
public void print(File f) {
//通过print方法来判断这个文件类对象的性质
if (f.isDirectory()) {
//判断f对象是否为目录
System.out.println("这是一个目录!");
} else {
System.out.println("这不是一个目录!");
}
if (f.exists()) {
//判断f对象是否存在
System.out.println("这个文件存在的!");
} else {
System.out.println("抱歉,这个文件不存在的!");
try {
f.createNewFile();
//当文件不存在时,创建一个文件
} catch (Exception e) {
}
}
}

public void print1(File f) {
//通过print1方法来获取文件目录对象的信息
System.out.println(f.getName());
System.out.println(f.getParent());
System.out.println(f.getPath());
}

public void print2(File f) {
//通过print2方法来获取文件对象的信息
if (f.isFile()) {
System.out.println(f.lastModified());
System.out.println(f.length());
}
}

public static void main(String[] args) {
file1 f1 = new file1(); //创建一个f1对象
File f = new File("d:\\filetest");
//调用相应的方法
f1.print(f);
f1.print1(f);
f1.print2(f);
}
}

运行结果

这不是一个目录!
抱歉,这个文件不存在的!
filetest
d:\
d:\filetest
1452923459623
0

使用文件字节输入流读取文件 FileInputStream

FileInputStream类是是InputStream的子类,并且不是抽象类。

import java.io.*;

///创建一个文件类f
///创建一个输入流对象fis,并且以f作为参数
///使用循环语句将文本文件中的字符读出
public class file3 {
public static void main(String[] args) throws Exception {
File f = new File("d:\\filetest\\file.txt");
//创建一个文件类f
FileInputStream fis = new FileInputStream(f);
//创建对象f的文件输入流
char ch;
//声明一个字符串对象ch
for (int i = 0; i < f.length(); i++) {
//通过循环读取文件类f所对应的文件
ch = (char) fis.read();
System.out.print(ch);
}
fis.close();
}
}

使用文件字节输出流输出文件 FileOutputStream

import java.io.*;

///创建一个文件类f
///创建一个输入流对象fis,并且以f作为参数
///将所有的字节都保存到一个字节数组b中。
///使用循环语句将b中的字符读出
public class file4 {
public static void main(String[] args) throws Exception {
File f = new File("d:\\filetest\\file.txt");
//创建一个文件类f
FileInputStream fis = new FileInputStream(f);
//创建一个输入流对象fis,并且以f作为参数
byte[] b = new byte[(int) f.length()];
//创建一个字节数组对象b
fis.read(b);
//读取到的内容存储到字节数组对象b
for (int i = 0; i < f.length(); i++) {
//通过循环输出相应内容
System.out.print((char) b[i]);
}
fis.close();
}
}

Java中对数据的处理

基本数据类型和对象数据类型的转换

为什么要将基本类型转换成对象数据类型:

Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)

基本数据类型 包装类
byte Byte
boolean Boolean
short Short
char Character
int Integer
long Long
float Float
double Double

由于所有的包装类具有比较相似的成员,这里以Integer类为例:

Integer类的构造方法:

public integer(int value):将整型值value包装成持有此值的Integer类对象。
public integer(string s):将由数字字符组成的串s包装成持有此值的Integer类对象,若s不是数字构成的话,则会报错。

public class file1 {
public static void main(String[] args) {
int x = 12; //创建一个整形类型变量x
String str = "13579"; //创建一个字符串类型变量str
//把变量x,str转换成对象类型t1和t2
Integer t1 = new Integer(x);
Integer t2 = new Integer(str);
//通过tostring()把对象转换成字符串。
System.out.println(t1);
System.out.println(t2);
}
}

如何处理随机性数据 Random

如何对数据进行排列、整理 Arrays

数据结构接口

Collection接口

Collection接口是数据集合接口,它位于数据结构API的最上部,构成Collection的单位被称为元素。可将Collection接口分为三个部分,分别是Map接口、Set接口和List接口。